Paste from clipboard with a specific variable

Can you copy one number that doesn't work here (edit it a bit for privacy), maybe it uses some special character that is not matched?

One of the numbers that doesn't work is shown in the example :ok_hand:

7 917 809‑97‑58‬

I see. That seems to contain an invisible unicode character 0x202c at the end (you will notice when trying to select or delete only the 8).

You could modify the script to remove that character:

let phoneNumber = clipboardContentString.replace(/[\s\-\(\)\u200B-\u200D\u202C\uFEFF]/g, "");
1 Like

Nice spot Andreas!

@T-N-T Here is the full updated script. You'll notice that I added:

  // set to true to show dialog, set to false to not show the dialog
  let debug = true;

at the beginning of the script.

If you want to turn off the dialog to display the summary of the script, set debug to false.

I hope this helps!

async (clipboardContentString) => {

  // set to true to show dialog, set to false to not show the dialog
  let debug = true;

  // Function to display dialog with input, escaping any double quotes
  function displayDialog(message) {
    let safeMessage = message.replace(/"/g, '\\"'); // Escape double quotes
    let debugScript = `
    display dialog "${safeMessage}"
    `;
    runAppleScript(debugScript);
  }

  // Error handling block
  try {
    // Remove whitespace, dashes, parentheses, and invisible unicode characters
    let phoneNumber = clipboardContentString.replace(/[\s\-\(\)\u200B-\u200D\u202C\uFEFF]/g, "");

    // Regex check for valid phone number format (example: international format)
    let isValidPhoneNumber = /^\+?[1-9]\d{1,14}$/.test(phoneNumber);

    // Stop execution if phone number is not valid
    if (!isValidPhoneNumber) {
      displayDialog(`Invalid phone number format: ${phoneNumber}. Execution stopped.`);
      return phoneNumber;
    }

    // Escape any special characters to prevent command injection
    let escapedPhoneNumber = phoneNumber.replace(/[^a-zA-Z0-9+]/g, '');

    // Construct the shell command with the escaped phone number
    let script = `/usr/bin/open whatsapp://send\?phone=${escapedPhoneNumber}`;

    if (debug == true) {
      // Use AppleScript with better readability and extra new lines between key-value pairs
      let appleScript = `
        set dialogText to "Clipboard Content:\n" & "${clipboardContentString}" & "\n\n" & \
          "\nFormatted Phone Number:\n" & "${phoneNumber}" & "\n\n" & \
          "\nIs Valid Phone Number:\n" & "${isValidPhoneNumber}" & "\n\n" & \
          "\nShell Script:\n" & "${script}"
        display dialog dialogText
      `;
    
       // Execute the AppleScript
      runAppleScript(appleScript); 
    };

    // Execute the shell command
    runShellScript({ script });

    return phoneNumber;
  } catch (error) {
    // Display dialog with the last variable that caused an error
    displayDialog(`Error encountered: ${error.message}`);
    throw error; // Re-throw the error for any higher-level handling
  }
};

Guys, would it be too much trouble for you to send me the entire script so I can paste it into the program?
I'm not very good with programming at all...

I copied what you sent above, pasted it into the program, and now it crashes )

@T-N-T Here's a new and improved version of the script that should work:

async (clipboardContentString) => {
  
  // Set to true to show dialog, set to false to not show the dialog
  let debug = false;

  // Function to display dialog with input, escaping any double quotes
  function displayDialog(message) {
    let safeMessage = message.replace(/"/g, '\\"'); // Escape double quotes
    let debugScript = `
    display dialog "${safeMessage}"
    `;
    runAppleScript(debugScript);
  }

  // Error handling block
  try {
    // Remove all characters except '+' and digits
    let phoneNumber = clipboardContentString.replace(/[^+\d]/g, "");

    // Regex check for valid phone number format (E.164 international format)
    let isValidPhoneNumber = /^\+?[1-9]\d{1,14}$/.test(phoneNumber);

    // Stop execution if phone number is not valid
    if (!isValidPhoneNumber) {
      displayDialog(`Invalid phone number format: ${phoneNumber}. Execution stopped.`);
      return phoneNumber;
    }

    // Construct the shell command with the cleaned phone number
    let script = `/usr/bin/open whatsapp://send?phone=${phoneNumber}`;

    if (debug) {
      // Use AppleScript with better readability and extra new lines between key-value pairs
      let appleScript = `
        set dialogText to "Clipboard Content:\n" & "${clipboardContentString}" & "\n\n" & \
          "Formatted Phone Number:\n" & "${phoneNumber}" & "\n\n" & \
          "Is Valid Phone Number:\n" & "${isValidPhoneNumber}" & "\n\n" & \
          "Shell Script:\n" & "${script}"
        display dialog dialogText
      `;
    
      // Execute the AppleScript
      runAppleScript(appleScript); 
    }

    // Execute the shell command
    runShellScript({ script });

    return phoneNumber;
  } catch (error) {
    // Display dialog with the error message
    displayDialog(`Error encountered: ${error.message}`);
    throw error; // Re-throw the error for any higher-level handling
  }
};

This one works :+1:
And in Google Sheets and everywhere, thank you very much :handshake:

1 Like

Guys, tell me, is it difficult to add to this script the ability to perform an action also from the selected text?

That is, everything is the same, only not from the clipboard, but from the selected text.

Previously (before WhatsApp was updated) both options worked.
When I used the "%@" function

Are you using the "Transform & Copy Selection With Java Script" action like described here Paste from clipboard with a specific variable - #2 by Andreas_Hegenberg ?
That should work with the selected text

You are right! There was an option, not like in your example.
Now everything works and with selection!
You are very cool guys! Thank you very much :+1:
This saves me a lot of time and nerves )))

Found where the script doesn't work.
Let's say we right-click on the phone number from this site:
https://bamper.by/zapchast_dvigatel/50315-B462580995/

And select "copy phone number" (FireFox browser)

Run the script and get: ο»Ώο»Ώο»Ώο»ΏInvalid phone number format: . Execution stopped.

At the same time, if after "copy phone number" we paste this text into some editor, then from there this number will work according to the script.

It's probably easier to shoot a video, but is it possible to post it somewhere here?

And another thing.
Previously, I could drop something into the clipboard, then select the phone number, run the script and paste the text from the clipboard into the opened WhatsApp chat.

Now, if I copy some text into the clipboard, then select the phone number, run the script, then the phone number is pasted into the clipboard.

@T-N-T I updated the script to make it works like this:

If there is text selected, check if it's a valid phone number. If yes, then open it in WhatsApp. If no, then check if the clipboard content is a valid phone number. If yes, then open it in WhatsApp. If no, then display an error.

To make this script work, the Action type must be:
Run Real JavaScript

It will not work if the Action type is:
Transform & Copy Selection With Java Script, or;
Transform Clipboard Contents with JavaScript

(async function () {
	// Enable or disable debug mode (set to false to disable debug dialogs)
	const DEBUG = true;

	/**
	 * Function to display a dialog box using AppleScript.
	 * This is used to show messages to the user.
	 * @param {string} title - The title of the dialog box.
	 * @param {string} message - The message content to display.
	 */
	async function showDialog(title, message) {
		// Prepare the AppleScript command to display a dialog
		const appleScript = `display dialog ${JSON.stringify(
			message
		)} with title ${JSON.stringify(
			title
		)} buttons {"OK"} default button "OK"`;
		// Execute the AppleScript command to show the dialog
		await runAppleScript(appleScript);
	}

	/**
	 * Function to validate a phone number against the E.164 international format.
	 * E.164 is an international standard for phone numbers.
	 * @param {string} phoneNumber - The phone number to validate.
	 * @returns {boolean} - Returns true if the phone number is valid, false otherwise.
	 */
	function isValidE164(phoneNumber) {
		// Regular expression to match E.164 phone numbers
		return /^\+?[1-9]\d{1,14}$/.test(phoneNumber);
	}

	/**
	 * Function to extract a valid phone number from input text.
	 * It sanitizes the input by removing unwanted characters and checks if it's a valid phone number.
	 * @param {string} input - The input text that may contain a phone number.
	 * @returns {string|null} - Returns the sanitized phone number if valid, or null if invalid.
	 */
	function extractValidPhoneNumber(input) {
		// If input is null or empty, return null
		if (!input) return null;
		// Remove all characters except '+' and digits
		const sanitized = input.replace(/[^+\d]/g, "");
		// Check if the sanitized number is a valid E.164 phone number
		return isValidE164(sanitized) ? sanitized : null;
	}

	/**
	 * Function to get a valid phone number from content.
	 * @param {string} content - The content to extract the phone number from.
	 * @param {string} sourceLabel - A label indicating the source ("Selection" or "Clipboard").
	 * @returns {{phoneNumber: string, source: string, content: string} | null} - Returns an object with the phone number, source, and content, or null if not found.
	 */
	function getPhoneNumberFromContent(content, sourceLabel) {
		// Attempt to extract a valid phone number from the content
		const phoneNumber = extractValidPhoneNumber(content);

		if (phoneNumber) {
			// Return the phone number, source label, and the original content
			return { phoneNumber, source: sourceLabel, content: content };
		} else {
			// Return null if no valid phone number is found
			return null;
		}
	}

	try {
		// Step 1: Retrieve the clipboard content first
		// This ensures that BTT updates any internal states related to the clipboard and selection
		const clipboardContent = await callBTT("get_clipboard_content", {});

		// Step 2: Retrieve the selected text from BTT's variables
		const selectionContent = await get_string_variable({
			variable_name: "selected_text",
		});

		// Step 3: Attempt to get the phone number from the selection
		const selectionResult = getPhoneNumberFromContent(
			selectionContent,
			"Selection"
		);

		// Step 4: If not found in selection, attempt to get from clipboard
		const finalResult =
			selectionResult ||
			getPhoneNumberFromContent(clipboardContent, "Clipboard");

		// Step 5: If no valid phone number is found, show an error dialog
		if (!finalResult) {
			await showDialog(
				"Invalid Input",
				`No valid phone number found.

Selection Content:
${selectionContent || "[No Selection]"}

Clipboard Content:
${clipboardContent || "[Clipboard is Empty]"}`
			);
			// Exit the script since there's no valid phone number to proceed with
			return;
		}

		// Step 6: If debug mode is enabled, show debug information to the user
		if (DEBUG) {
			await showDialog(
				"Debug Information",
				`Source: ${finalResult.source}
Phone Number: ${finalResult.phoneNumber}`
			);
		}

		// Step 7: Open WhatsApp with the valid phone number
		// This constructs a shell command to open WhatsApp using the 'whatsapp://' URL scheme
		const shellScriptOpenWhatsApp = `/usr/bin/open "whatsapp://send?phone=${finalResult.phoneNumber}"`;
		// Execute the shell command to open WhatsApp
		await runShellScript({ script: shellScriptOpenWhatsApp });

		// Step 8: Return the valid phone number to BTT (optional)
		// This can be used if you need to pass the phone number back to BTT for further actions
		returnToBTT(finalResult.phoneNumber);
	} catch (error) {
		// If any error occurs during the execution of the script, show an error dialog to the user
		await showDialog("Script Error", `An error occurred: ${error.message}`);
	}
})();

Π’Ρ€ΠΎΠ΄Π΅ Π±Ρ‹ ΠΏΠΎΠΊΠ° Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚, Π½ΡƒΠΆΠ½ΠΎ Ρ‚Π΅ΡΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ.
ΠŸΠΎΠ΄ΡΠΊΠ°ΠΆΠΈΡ‚Π΅, ΠΊΠ°ΠΊ Ρ‚ΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠ±Ρ€Π°Ρ‚ΡŒ это ΠΎΠΊΠ½ΠΎ?
ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅

@T-N-T


English reply

I made some further modifications to the code to simplify it. I hope you'll be able to understand how it works.

πŸ‘ˆ Click to see updated code (English version):
/**
 * Script to Open WhatsApp with a Selected or Copied Phone Number
 *
 * **Description:**
 * This script extracts a phone number from either the selected text or the clipboard,
 * validates it according to the E.164 international format, and then opens WhatsApp
 * to send a message to that number.
 *
 * **How to Use:**
 * - Set up this script in BetterTouchTool (BTT) as a **"Run Real JavaScript"** action.
 *   - **Important:** This script will **not** work with other action types like
 *     "Transform & Copy Selection With JavaScript" or "Transform Clipboard Contents with JavaScript".
 * - To use the script:
 *   1. Select a phone number in any application, or copy it to the clipboard.
 *   2. Trigger the BTT action that runs this script.
 *
 * **Turning Debug Mode On/Off:**
 * - The `DEBUG` variable controls whether debug dialogs are shown.
 *   - Set `DEBUG = true` to enable debug mode (shows additional dialogs with information).
 *   - Set `DEBUG = false` to disable debug mode (no debug dialogs will appear).
 *
 * **Requirements:**
 * - BetterTouchTool (BTT) version **4.867** or later.
 * - WhatsApp MacOS app.
 *
 * **Author:** https://community.folivora.ai/u/fortred2
 * **Date:** 2024-11-14
 */

(async function () {
  // Enable or disable debug mode.
  // Set to 'true' to show debug dialogs, or 'false' to hide them.
  const DEBUG = false;

  /**
   * Displays a dialog box using AppleScript to show messages to the user.
   * @param {string} title - The title of the dialog box.
   * @param {string} message - The message content to display.
   */
  async function showDialog(title, message) {
    // Prepare the AppleScript command to display a dialog
    const appleScript = `display dialog ${JSON.stringify(
      message
    )} with title ${JSON.stringify(
      title
    )} buttons {"OK"} default button "OK"`;
    // Execute the AppleScript command to show the dialog
    await runAppleScript(appleScript);
  }

  /**
   * Validates a phone number against the E.164 international format.
   * @param {string} phoneNumber - The phone number to validate.
   * @returns {boolean} True if the phone number is valid, false otherwise.
   */
  function isValidE164(phoneNumber) {
    // Regular expression to match E.164 phone numbers
    return /^\+?[1-9]\d{1,14}$/.test(phoneNumber);
  }

  /**
   * Extracts a valid phone number from input text.
   * Removes unwanted characters and checks if the number is valid.
   * @param {string} input - The input text that may contain a phone number.
   * @returns {string|null} The sanitized phone number if valid, or null if not.
   */
  function extractValidPhoneNumber(input) {
    // If input is null or empty, return null
    if (!input) return null;
    // Remove all characters except '+' and digits
    const sanitized = input.replace(/[^+\d]/g, "");
    // Check if the sanitized number is valid according to E.164 format
    return isValidE164(sanitized) ? sanitized : null;
  }

  /**
   * Attempts to extract a valid phone number from the provided content.
   * @param {string} content - The content to extract the phone number from.
   * @param {string} sourceLabel - Indicates the source ("Selection" or "Clipboard").
   * @returns {Object|null} An object with the phone number, source, and content, or null if not found.
   */
  function getPhoneNumberFromContent(content, sourceLabel) {
    // Attempt to extract a valid phone number from the content
    const phoneNumber = extractValidPhoneNumber(content);

    if (phoneNumber) {
      // Return the phone number, source label, and the original content
      return { phoneNumber, source: sourceLabel, content: content };
    } else {
      // Return null if no valid phone number is found
      return null;
    }
  }

  try {
    // Note: The following functions are provided by the BetterTouchTool (BTT) JavaScript environment:
    // - callBTT
    // - get_string_variable
    // - runAppleScript
    // - runShellScript
    // - returnToBTT

    // Step 1: Retrieve the clipboard content BEFORE getting 'selected_text'
    const clipboardContent = await callBTT("get_clipboard_content", {});

    // Step 2: Retrieve the selected text from BTT's variables
    const selectionContent = await get_string_variable({
      variable_name: "selected_text",
    });

    // Step 3: Attempt to get the phone number from the selection
    const selectionResult = getPhoneNumberFromContent(
      selectionContent,
      "Selection"
    );

    // Step 4: If not found in selection, attempt to get from clipboard
    const finalResult =
      selectionResult ||
      getPhoneNumberFromContent(clipboardContent, "Clipboard");

    // Step 5: If no valid phone number is found, show an error dialog
    if (!finalResult) {
      await showDialog(
        "Invalid Input",
        `No valid phone number found.

Selection Content:
${selectionContent || "[No Selection]"}

Clipboard Content:
${clipboardContent || "[Clipboard is Empty]"}`
      );
      // Exit the script since there's no valid phone number to proceed with
      return;
    }

    // Step 6: If debug mode is enabled, show debug information to the user
    if (DEBUG) {
      await showDialog(
        "Debug Information",
        `Source: ${finalResult.source}
Phone Number: ${finalResult.phoneNumber}`
      );
    }

    // Step 7: Open WhatsApp with the valid phone number
    // Constructs a shell command to open WhatsApp using the 'whatsapp://' URL scheme
    const shellScriptOpenWhatsApp = `/usr/bin/open "whatsapp://send?phone=${finalResult.phoneNumber}"`;
    // Execute the shell command to open WhatsApp
    await runShellScript({ script: shellScriptOpenWhatsApp });

    // Step 8: Return the valid phone number to BTT
    // This is required for BTT to receive the phone number for further actions
    returnToBTT(finalResult.phoneNumber);
  } catch (error) {
    // If any error occurs during execution, show an error dialog to the user
    await showDialog("Script Error", `An error occurred: ${error.message}`);
  }
})();

Let me know if you encounter any issues or bugs with the JavaScript. If you encounter an issue or bug, it would help me a lot if you shared a screen recordings. This makes identifying the cause a lot easier for me.

You can remove the debug window by setting DEBUG to false instead of true in the script, like this:

const DEBUG = false;

ChatGPT helped me translate the message and the JavaScript into Russian. I hope this helps you understand the code. Please see below the Russian version of the JavaScript. I did some testing with both English and Russian versions and they appear to be working correctly.


Russian translation

Π― внСс Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ измСнСния Π² ΠΊΠΎΠ΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠΏΡ€ΠΎΡΡ‚ΠΈΡ‚ΡŒ Π΅Π³ΠΎ. НадСюсь, Π²Ρ‹ смоТСтС ΠΏΠΎΠ½ΡΡ‚ΡŒ, ΠΊΠ°ΠΊ ΠΎΠ½ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚.

πŸ‘ˆ Click to see updated code (Russian version):
/**
 * Π‘ΠΊΡ€ΠΈΠΏΡ‚ для открытия WhatsApp с Π²Ρ‹Π±Ρ€Π°Π½Π½Ρ‹ΠΌ ΠΈΠ»ΠΈ скопированным Π½ΠΎΠΌΠ΅Ρ€ΠΎΠΌ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°
 *
 * **ОписаниС:**
 * Π­Ρ‚ΠΎΡ‚ скрипт ΠΈΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ‚ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° ΠΈΠ· Π²Ρ‹Π΄Π΅Π»Π΅Π½Π½ΠΎΠ³ΠΎ тСкста ΠΈΠ»ΠΈ Π±ΡƒΡ„Π΅Ρ€Π° ΠΎΠ±ΠΌΠ΅Π½Π°,
 * провСряСт Π΅Π³ΠΎ соотвСтствиС ΠΌΠ΅ΠΆΠ΄ΡƒΠ½Π°Ρ€ΠΎΠ΄Π½ΠΎΠΌΡƒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρƒ E.164, Π° Π·Π°Ρ‚Π΅ΠΌ ΠΎΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ WhatsApp
 * для ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ сообщСния Π½Π° этот Π½ΠΎΠΌΠ΅Ρ€.
 *
 * **Как ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ:**
 * - НастройтС этот скрипт Π² BetterTouchTool (BTT) ΠΊΠ°ΠΊ дСйствиС **"Run Real JavaScript"**.
 *   - **Π’Π°ΠΆΠ½ΠΎ:** Π­Ρ‚ΠΎΡ‚ скрипт **Π½Π΅** Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с Π΄Ρ€ΡƒΠ³ΠΈΠΌΠΈ Ρ‚ΠΈΠΏΠ°ΠΌΠΈ дСйствий, Ρ‚Π°ΠΊΠΈΠΌΠΈ ΠΊΠ°ΠΊ
 *     "Transform & Copy Selection With JavaScript" ΠΈΠ»ΠΈ "Transform Clipboard Contents with JavaScript".
 * - Π§Ρ‚ΠΎΠ±Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ скрипт:
 *   1. Π’Ρ‹Π΄Π΅Π»ΠΈΡ‚Π΅ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° Π² любом ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ ΠΈΠ»ΠΈ скопируйтС Π΅Π³ΠΎ Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°.
 *   2. ЗапуститС дСйствиС BTT, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ выполняСт этот скрипт.
 *
 * **Π’ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΈ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ Ρ€Π΅ΠΆΠΈΠΌΠ° ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ:**
 * - ΠŸΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Π°Ρ `DEBUG` ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΠΈΡ€ΡƒΠ΅Ρ‚, ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°ΡŽΡ‚ΡΡ Π»ΠΈ ΠΎΡ‚Π»Π°Π΄ΠΎΡ‡Π½Ρ‹Π΅ Π΄ΠΈΠ°Π»ΠΎΠ³ΠΈ.
 *   - УстановитС `DEBUG = true`, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π²ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ Ρ€Π΅ΠΆΠΈΠΌ ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ (ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Π΄ΠΈΠ°Π»ΠΎΠ³ΠΈ с ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠ΅ΠΉ).
 *   - УстановитС `DEBUG = false`, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ Ρ€Π΅ΠΆΠΈΠΌ ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ (ΠΎΡ‚Π»Π°Π΄ΠΎΡ‡Π½Ρ‹Π΅ Π΄ΠΈΠ°Π»ΠΎΠ³ΠΈ Π½Π΅ Π±ΡƒΠ΄ΡƒΡ‚ ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°Ρ‚ΡŒΡΡ).
 *
 * **ВрСбования:**
 * - BetterTouchTool (BTT) вСрсии **4.867** ΠΈΠ»ΠΈ Π²Ρ‹ΡˆΠ΅.
 * - WhatsApp для macOS.
 *
 * **Автор:** https://community.folivora.ai/u/fortred2
 * **Π”Π°Ρ‚Π°:** 2024-11-14
 */

(async function () {
  // Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΈΠ»ΠΈ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ Ρ€Π΅ΠΆΠΈΠΌ ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ.
  // УстановитС 'true', Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΎΡ‚Π»Π°Π΄ΠΎΡ‡Π½Ρ‹Π΅ Π΄ΠΈΠ°Π»ΠΎΠ³ΠΈ, ΠΈΠ»ΠΈ 'false', Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠΊΡ€Ρ‹Ρ‚ΡŒ ΠΈΡ….
  const DEBUG = false;

  /**
   * ΠžΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°Π΅Ρ‚ Π΄ΠΈΠ°Π»ΠΎΠ³ΠΎΠ²ΠΎΠ΅ ΠΎΠΊΠ½ΠΎ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ AppleScript, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ сообщСния ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŽ.
   * @param {string} title - Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π΄ΠΈΠ°Π»ΠΎΠ³ΠΎΠ²ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π°.
   * @param {string} message - Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΠΌΠΎΠ΅ сообщСния для отобраТСния.
   */
  async function showDialog(title, message) {
    // ΠŸΠΎΠ΄Π³ΠΎΡ‚ΠΎΠ²ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ AppleScript для отобраТСния Π΄ΠΈΠ°Π»ΠΎΠ³Π°
    const appleScript = `display dialog ${JSON.stringify(
      message
    )} with title ${JSON.stringify(
      title
    )} buttons {"OK"} default button "OK"`;
    // Π’Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ AppleScript для ΠΏΠΎΠΊΠ°Π·Π° Π΄ΠΈΠ°Π»ΠΎΠ³Π°
    await runAppleScript(appleScript);
  }

  /**
   * ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° Π½Π° соотвСтствиС ΠΌΠ΅ΠΆΠ΄ΡƒΠ½Π°Ρ€ΠΎΠ΄Π½ΠΎΠΌΡƒ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρƒ E.164.
   * @param {string} phoneNumber - НомСр Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° для ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ.
   * @returns {boolean} True, Ссли Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° дСйствитСлСн, ΠΈΠ½Π°Ρ‡Π΅ false.
   */
  function isValidE164(phoneNumber) {
    // РСгулярноС Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ для соотвСтствия Π½ΠΎΠΌΠ΅Ρ€Π°ΠΌ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ E.164
    return /^\+?[1-9]\d{1,14}$/.test(phoneNumber);
  }

  /**
   * Π˜Π·Π²Π»Π΅ΠΊΠ°Π΅Ρ‚ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° ΠΈΠ· Π²Ρ…ΠΎΠ΄Π½ΠΎΠ³ΠΎ тСкста.
   * УдаляСт Π½Π΅Π½ΡƒΠΆΠ½Ρ‹Π΅ символы ΠΈ провСряСт, являСтся Π»ΠΈ Π½ΠΎΠΌΠ΅Ρ€ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΌ.
   * @param {string} input - Π’Ρ…ΠΎΠ΄Π½ΠΎΠΉ тСкст, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°.
   * @returns {string|null} ΠžΡ‡ΠΈΡ‰Π΅Π½Π½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°, Ссли ΠΎΠ½ дСйствитСлСн, ΠΈΠ»ΠΈ null, Ссли Π½Π΅Ρ‚.
   */
  function extractValidPhoneNumber(input) {
    // Если Π²Ρ…ΠΎΠ΄Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ пусты ΠΈΠ»ΠΈ Ρ€Π°Π²Π½Ρ‹ null, Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒ null
    if (!input) return null;
    // Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС символы, ΠΊΡ€ΠΎΠΌΠ΅ '+' ΠΈ Ρ†ΠΈΡ„Ρ€
    const sanitized = input.replace(/[^+\d]/g, "");
    // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ, являСтся Π»ΠΈ ΠΎΡ‡ΠΈΡ‰Π΅Π½Π½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΌ ΠΏΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρƒ E.164
    return isValidE164(sanitized) ? sanitized : null;
  }

  /**
   * ΠŸΡ‹Ρ‚Π°Π΅Ρ‚ΡΡ ΠΈΠ·Π²Π»Π΅Ρ‡ΡŒ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° ΠΈΠ· прСдоставлСнного содСрТимого.
   * @param {string} content - Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΠΌΠΎΠ΅ для извлСчСния Π½ΠΎΠΌΠ΅Ρ€Π° Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°.
   * @param {string} sourceLabel - Π£ΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ источник ("Π’Ρ‹Π΄Π΅Π»Π΅Π½ΠΈΠ΅" ΠΈΠ»ΠΈ "Π‘ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°").
   * @returns {Object|null} ΠžΠ±ΡŠΠ΅ΠΊΡ‚ с Π½ΠΎΠΌΠ΅Ρ€ΠΎΠΌ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°, источником ΠΈ содСрТимым, ΠΈΠ»ΠΈ null, Ссли Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ΠΎ.
   */
  function getPhoneNumberFromContent(content, sourceLabel) {
    // ΠŸΠΎΠΏΡ‹Ρ‚Π°Ρ‚ΡŒΡΡ ΠΈΠ·Π²Π»Π΅Ρ‡ΡŒ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° ΠΈΠ· содСрТимого
    const phoneNumber = extractValidPhoneNumber(content);

    if (phoneNumber) {
      // Π’Π΅Ρ€Π½ΡƒΡ‚ΡŒ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°, ΠΌΠ΅Ρ‚ΠΊΡƒ источника ΠΈ ΠΎΡ€ΠΈΠ³ΠΈΠ½Π°Π»ΡŒΠ½ΠΎΠ΅ содСрТимоС
      return { phoneNumber, source: sourceLabel, content: content };
    } else {
      // Π’Π΅Ρ€Π½ΡƒΡ‚ΡŒ null, Ссли Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½
      return null;
    }
  }

  try {
    // ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅: Π‘Π»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ ΠΏΡ€Π΅Π΄ΠΎΡΡ‚Π°Π²Π»ΡΡŽΡ‚ΡΡ срСдой JavaScript BetterTouchTool (BTT):
    // - callBTT
    // - get_string_variable
    // - runAppleScript
    // - runShellScript
    // - returnToBTT

    // Π¨Π°Π³ 1: ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ содСрТимоС Π±ΡƒΡ„Π΅Ρ€Π° ΠΎΠ±ΠΌΠ΅Π½Π° ΠŸΠ•Π Π•Π” ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ΠΌ 'selected_text'
    const clipboardContent = await callBTT("get_clipboard_content", {});

    // Π¨Π°Π³ 2: ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π²Ρ‹Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΉ тСкст ΠΈΠ· ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… BTT
    const selectionContent = await get_string_variable({
      variable_name: "selected_text",
    });

    // Π¨Π°Π³ 3: ΠŸΠΎΠΏΡ‹Ρ‚Π°Ρ‚ΡŒΡΡ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° ΠΈΠ· выдСлСния
    const selectionResult = getPhoneNumberFromContent(
      selectionContent,
      "Π’Ρ‹Π΄Π΅Π»Π΅Π½ΠΈΠ΅"
    );

    // Π¨Π°Π³ 4: Если Π² Π²Ρ‹Π΄Π΅Π»Π΅Π½ΠΈΠΈ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ΠΎ, ΠΏΠΎΠΏΡ‹Ρ‚Π°Ρ‚ΡŒΡΡ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ· Π±ΡƒΡ„Π΅Ρ€Π° ΠΎΠ±ΠΌΠ΅Π½Π°
    const finalResult =
      selectionResult ||
      getPhoneNumberFromContent(clipboardContent, "Π‘ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°");

    // Π¨Π°Π³ 5: Если Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½, ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ сообщСниС ΠΎΠ± ошибкС
    if (!finalResult) {
      await showDialog(
        "НСвСрный Π²Π²ΠΎΠ΄",
        `Π”Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½.

Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΠΌΠΎΠ΅ выдСлСния:
${selectionContent || "[НСт выдСлСния]"}

Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΠΌΠΎΠ΅ Π±ΡƒΡ„Π΅Ρ€Π° ΠΎΠ±ΠΌΠ΅Π½Π°:
${clipboardContent || "[Π‘ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π° пуст]"}`
      );
      // Π’Ρ‹ΠΉΡ‚ΠΈ ΠΈΠ· скрипта, Ρ‚Π°ΠΊ ΠΊΠ°ΠΊ Π½Π΅Ρ‚ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ Π½ΠΎΠΌΠ΅Ρ€Π° Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° для продолТСния
      return;
    }

    // Π¨Π°Π³ 6: Если Ρ€Π΅ΠΆΠΈΠΌ ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½, ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΎΡ‚Π»Π°Π΄ΠΎΡ‡Π½ΡƒΡŽ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŽ
    if (DEBUG) {
      await showDialog(
        "ΠžΡ‚Π»Π°Π΄ΠΎΡ‡Π½Π°Ρ информация",
        `Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: ${finalResult.source}
НомСр Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°: ${finalResult.phoneNumber}`
      );
    }

    // Π¨Π°Π³ 7: ΠžΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ WhatsApp с Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΌ Π½ΠΎΠΌΠ΅Ρ€ΠΎΠΌ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°
    // БоставляСт ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ shell для открытия WhatsApp с использованиСм схСмы URL 'whatsapp://'
    const shellScriptOpenWhatsApp = `/usr/bin/open "whatsapp://send?phone=${finalResult.phoneNumber}"`;
    // Π’Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ shell для открытия WhatsApp
    await runShellScript({ script: shellScriptOpenWhatsApp });

    // Π¨Π°Π³ 8: Π’Π΅Ρ€Π½ΡƒΡ‚ΡŒ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° Π² BTT
    // Π­Ρ‚ΠΎ трСбуСтся, Ρ‡Ρ‚ΠΎΠ±Ρ‹ BTT ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ» Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π° для Π΄Π°Π»ΡŒΠ½Π΅ΠΉΡˆΠΈΡ… дСйствий
    returnToBTT(finalResult.phoneNumber);
  } catch (error) {
    // Если Π²ΠΎ врСмя выполнСния происходит ошибка, ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŽ Π΄ΠΈΠ°Π»ΠΎΠ³ΠΎΠ²ΠΎΠ΅ ΠΎΠΊΠ½ΠΎ с ошибкой
    await showDialog("Ошибка скрипта", `ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка: ${error.message}`);
  }
})();

Π‘ΠΎΠΎΠ±Ρ‰ΠΈΡ‚Π΅ ΠΌΠ½Π΅, Ссли Π²Ρ‹ ΡΡ‚ΠΎΠ»ΠΊΠ½Π΅Ρ‚Π΅ΡΡŒ с ΠΊΠ°ΠΊΠΈΠΌΠΈ-Π»ΠΈΠ±ΠΎ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ°ΠΌΠΈ ΠΈΠ»ΠΈ ошибками Π² JavaScript. Если Π²ΠΎΠ·Π½ΠΈΠΊΠ½Π΅Ρ‚ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ° ΠΈΠ»ΠΈ ошибка, ΠΌΠ½Π΅ ΠΎΡ‡Π΅Π½ΡŒ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚, Ссли Π²Ρ‹ ΠΏΠΎΠ΄Π΅Π»ΠΈΡ‚Π΅ΡΡŒ записями экрана. Π­Ρ‚ΠΎ Π·Π½Π°Ρ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΎΠ±Π»Π΅Π³Ρ‡ΠΈΡ‚ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈΡ‡ΠΈΠ½Ρ‹.

Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΡƒΠ±Ρ€Π°Ρ‚ΡŒ ΠΎΠΊΠ½ΠΎ ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ, установив DEBUG Π² Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ false вмСсто true Π² скриптС ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ:

const DEBUG = false;

Π― внСс Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ измСнСния Π² ΠΊΠΎΠ΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ это ΠΏΡ€ΠΎΡ‰Π΅. НадСюсь, Π²Ρ‹ ΠΏΠΎΠΉΠΌΠ΅Ρ‚Π΅, ΠΊΠ°ΠΊ ΠΎΠ½ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚.

ChatGPT ΠΏΠΎΠΌΠΎΠ³ ΠΌΠ½Π΅ пСрСвСсти это сообщСниС ΠΈ JavaScript Π½Π° русский язык. НадСюсь, это ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ Π²Π°ΠΌ ΠΏΠΎΠ½ΡΡ‚ΡŒ ΠΊΠΎΠ΄.

Π― ΠΏΡ€ΠΎΠ²Π΅Π» Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ обсуТдСниС ΠΊΠ°ΠΊ с английской, Ρ‚Π°ΠΊ ΠΈ с русской вСрсиСй JavaScript, ΠΈ ΠΎΠ½ΠΈ, каТСтся, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎ.

Thank you very much.
My first impression is that everything works.
I will test it and give you feedback :handshake:

1 Like