Internal variables in "Variable Value Did Change"

Hey guys,

Is there a way to use "Variable Value Did Change" with some of the internal vars, like "BTTNowPlayingInfoTitle" for example? I've tried with only "BTTNowPlayingInfoTitle" as Variable name, also with "{BTTNowPlayingInfoTitle}" but it does not work. The "workaround" is to assign BTTNowPlayingInfoTitle to a persistent variable and the I can use this new var with "Variable Value Did Change".

The use-case is to detect any change, no matter what was previous value.

it’s possible that some of the older variables are not yet integrated into the new variable system, I‘ll check!

Hey @Andreas_Hegenberg, I'm using the custom persistent variable atm, but since the script which is updating it is being executed every 8 sec, this causes an automation (under "Automations & Named & Other Triggers"), which is depended on this var, to be executed every 8 sec, which I think should not happen since the variable value has not changed during these 8 seconds. Is this correct?

you mean the variable doesn't change but the variable did change trigger is still executed? That would be weird, there is a check for whether the variable value is equal to the new one before triggering a variable did change trigger

Btw. 4.952 alpha (uploading) should make sure that the variable change trigger is automatically called for the now playing variables

Here's the scenario:

  1. A script executed every 8 sec in a floating menu is doing: set_persistent_string_variable "custom_var_menubarx_url" to songTitle, which is working fine.

  2. Under "Automations & Named & Other Triggers", I have:


    and this is being executed every 8 seconds, even if "custom_var_menubarx_url" is still the same.

ah true, when updating via script, it didn't check for equality so currently your script would need to check that. I have changed this in 4.953 alpha (uploading), it should now be the same for any way to set a variable.

I think 4.952 aplha, broke something related to the playing variables, since for example the following script no longer works (script result is: "script completed without result"):

on itemScript(itemUUID)
	tell application "BetterTouchTool"
		set playing_app to get_string_variable "BTTCurrentlyPlayingApp"
	end tell

	return playing_app
end itemScript

However there's a working/playing app:

The string in the red (below screenshot) should not be paused, but Playing as seen in the code above it:

Just tested 4.953 alpha still have these issues.

Ah I think I accidentally confused BTTCurrentlyPlayingApp and BTTCurrentlyPlaying, hopefully fixed in the 4.954 build

Hey @Andreas_Hegenberg, trigger works (I can see it in "Recently used"), but the action "Run Real JavaScript" it seems it is not being executed. This javascript should set_persistent_string_variable with name view_count, which however is not being set/updated. The trigger for this automation is if
"BTTNowPlayingInfoTitle" value did change.

If I run the script manually (from BTT) it is working.

The script is javascript + applescript which is checking tabs in safari, the weird thing is that if I bring safari as active window/app the javascript is being executed (trigger also is shown in "recently used", when safari becomes active like BTTNowPlayingInfoTitle is changed which is not true).

I hope that I was able to explain it somewhat understandable.

weird, can you post the script or does it contain sensitive data?
It sounds a bit like there is some sort of exception when it's executed while Safari is not active

Here it is:

async function itemScript(itemUUID) {
	// AppleScript to get the matching URL from Safari
	const appleScript = `
        set matchingURL to ""
        tell application "Safari"
            repeat with w in windows
                repeat with t in tabs of w
                    set tabName to name of t
                    if tabName ends with " - YouTube" then
                        set tabName to text 1 thru -11 of tabName
                    end if
                    tell application "BetterTouchTool"
                        set nowPlayingTitle to get_string_variable "BTTNowPlayingInfoTitle"
                    end tell
                    if tabName is equal to nowPlayingTitle then
                        set matchingURL to URL of t
                        exit repeat
                    end if
                end repeat
                if matchingURL is not "" then exit repeat
            end repeat
        end tell
        return matchingURL
    `;

	// Execute the AppleScript and get the URL
	const scriptCommand = `osascript -e '${appleScript.replace(/'/g, "'\\''")}'`;
	const matchingURL = await runShellScript({ script: scriptCommand });

	// Proceed with the rest of your logic using the matching URL
	const baseUrl = matchingURL.match(/(https:\/\/www\.youtube\.com\/watch\?v=[\w-]+)/)[0];

	const command = `curl -s ${baseUrl} | /opt/homebrew/bin/ggrep -o -P "(?<=shortViewCount\\\":\\\{\\\"simpleText\\\":\\\").+?(?=\\\"\\\},\\\"originalViewCount)";`;

	try {
		// Execute the shell command
		let viewCount = await runShellScript({ script: command });

		if (!viewCount) {
			// Define another command to run if viewCount is empty
			const fallbackCommand = `curl -s ${baseUrl} | /opt/homebrew/bin/ggrep -o -P "(?<=extraShortViewCount\\\":\\\{\\\"simpleText\\\":\\\").+?(?=\\\"\\\},\\\"entityKey)";`;

			// Execute the fallback command
			viewCount = await runShellScript({ script: fallbackCommand });
		}

		viewCount = viewCount.trim(); // Clean up the result

		// Set the variable to store the view count. This is used then in floating menu ViewCount and script:
		// /Users/myuser/Library/CloudStorage/OneDrive/Documents/btt/scripts/youtube_views_message.js
		await set_persistent_string_variable({ variableName: "view_count", to: viewCount });

		// Retrieve the stored view count
		const retrievedViewCount = await get_string_variable({ variableName: "view_count" });

		// Return the retrieved view count
		return retrievedViewCount;
	} catch (error) {
		console.error("Error:", error);
		return "Error";
	}
}

I'm aware that it is not the best approach, also I've tried to used JXA, but without success, so I ended up with this.

I think the forum software prevents downloading .js files, I get some weird permission error :sweat_smile: Could you zip it?

added as code block.

1 Like

Just checking, is there a reason you use osascript and runShellScript instead of

let result = await runAppleScript(appleScript);

?

chatGPT/or some other AI tool suggested like this :slight_smile: , I have -10 knowledge about js or applescript :\

I've now tried with let result = await runAppleScript(appleScript);, but it doesn't make any difference.

btw, if I change songs fast enough (meaning that BTTNowPlayingInfoTitle is changing as well, which should trigger the javascrip action), it is working even if safari is not active.

Play/Pause (executed with almost no delay) is also triggering the automation and the script.

Uh parsing that youtube html looks like hell :smiley:

I have't tried a lot, but maybe try this (two less indirections by getting the variable via javascript, and replacing the runShellScript with runAppleScript. The path from BTT JS => Apple Script => BTT Apple Script Function => BTT JS => Shell Script is super complex

async function itemScript(itemUUID) {
  // AppleScript to get the matching URL from Safari
  let nowPlayingTitle = await get_string_variable({variableName: "BTTNowPlayingInfoTitle"});
  const appleScript = `
        set matchingURL to ""
tell application "Safari"
  repeat with w in windows
    repeat with t in tabs of w
      set tabName to name of t
      if tabName ends with " - YouTube" then
        set tabName to text 1 thru -11 of tabName
      end if
      if tabName is equal to "${nowPlayingTitle}" then
        set matchingURL to URL of t
        exit repeat
      end if
    end repeat
    if matchingURL is not "" then exit repeat
  end repeat
end tell
return matchingURL
    `;

  // Execute the AppleScript and get the URL
  let matchingURL = await runAppleScript(appleScript);
 if(!matchingURL) {return "no url";}
   // Proceed with the rest of your logic using the matching URL
  const baseUrl = matchingURL.match(/(https:\/\/www\.youtube\.com\/watch\?v=[\w-]+)/)[0];

  const command = `curl -s ${baseUrl} | /opt/homebrew/bin/ggrep -o -P "(?<=shortViewCount\\\":\\\{\\\"simpleText\\\":\\\").+?(?=\\\"\\\},\\\"originalViewCount)";`;

  try {
    // Execute the shell command
    let viewCount = await runShellScript({ script: command });

    if (!viewCount) {
      // Define another command to run if viewCount is empty
      const fallbackCommand = `curl -s ${baseUrl} | /opt/homebrew/bin/ggrep -o -P "(?<=extraShortViewCount\\\":\\\{\\\"simpleText\\\":\\\").+?(?=\\\"\\\},\\\"entityKey)";`;

      // Execute the fallback command
      viewCount = await runShellScript({ script: fallbackCommand });
    }

        if (!viewCount) {
            throw new Error("xx");
        }

        viewCount = viewCount.trim(); // Clean up the result

        // Set the variable to store the view count (stubbed as a placeholder function)
        await set_persistent_string_variable({ variableName: "view_count", to: viewCount });

        // Retrieve the stored view count (stubbed as a placeholder function)
        const retrievedViewCount = await get_string_variable({ variableName: "view_count" });

        // Return the retrieved view count
        return retrievedViewCount;
    } catch (error) {
        console.error("Error:", error);
        return "Error" + error;
    }
}

Appreciate this, however It still doesn't work ;. On the below screenshot you can see an example:

maybe looking at the logs can help. If you quit BTT and call this terminal command BTT will write every scripting function call (e.g. set_persistent_string_variable) to the logs in ~/Library/Application Support/BetterTouchTool/Logs

defaults write com.hegenberg.BetterTouchTool BTTDebugLogging YES

You can also write any other log there by calling BTTLog("log message");