What's wrong with this Applescript? - applescript

My app has some rudimentary applescriptability. There is one method (receivedInstantMessage) that takes a single parameter (message) and passes it to my app which then processes it.
The following applescript:
tell application "MyApp"
receivedInstantMessage "This is a message"
end tell
Works perfectly. My app presents a dialog containing the message ("This is a message").
I'm trying to set it up so that when I send an IM to iChat, it runs an applescript that will send the contents of the message to my app. I have told iChat to run a script when a message is received and I know that part is working. The script I am now using does not work:
using terms from application "iChat"
on message received theMessage from theBuddy for theChat
tell application "MyApp"
receivedInstantMessage theMessage
end tell
end message received
end using terms from
Nothing happens when I receive a message. Even if I substitute the message variable (theMessage) from iChat and use an arbitrary string it still does nothing.
What am I doing wrong. I'm quite new to applescript (being a REALbasic coder normally).
[Update]: This seems to work now. A simple restart of the Mac fixed things. Very odd...

just a semantic detail: Consider the scripter is sending messages to your app, not receiving them. Yes, your app is receiving them, but the terminology you chose "receivedInstantMessage" is from the point of view of your app, not the scripter.
Additionally, it is considered naff to have terminology in camel case. AppleScript terminology can (and often should) contain spaces. And if you really want to do it properly you should separate the terminology into nouns and verbs. (Where nouns are properly-modelled objects, with properties, and verbs are commands for manipulating them. In this case you probably want something like send message "bla", where a message is an object with properties like sender, recipient, channel etc. and send is a command which takes a message object as a parameter - check the dictionary of Snak for a quite nice - but not perfect - implementation).
Sorry if this sounds anal. I have been applescripting for many years, and while I really appreciate it when developers add applescript support, I know I speak for all applescripters when I say that poorly constructed dictionaries, and poor terminology choices are frustrating and irritating, especially as the app gets more mature and the developer starts to say things like "I know the applescript interface needs a complete overhaul but I don't want to break existing scripts!". ALL applescripters prefer it if the scripting interface gets better, even if it breaks existing scripts. So: Do it wrong now, but be prepared to fundamentally improve it later. :)
Even Apple has some lousy terminology, for example in iTunes there is an updatePodcast and updateAllPodcasts command. This is just wrong, according to their own technote 2106 - pay particular attention to the section on naming rules.. They should have a podcast object and an update command, so that you could also do things like "delete every podcast whose name contains "Ann Coulter". ("Whose" clauses are one of the coolest features of appleScript!)

Related

Send file URL and args to (running) macOS app via command line

I've been trying to create a way to tell my (running) macOS app to open some files and supply some additional arguments to the command.
For cold-start apps, using the
$ open MyApp.app fileA.txt --args --foo-arg
would launch the app and I would be able to inspect the --foo-arg via UserDefaults/CommandLine/ProcessInfo. However, if the app is already running, the --foo-arg is missing from UserDefaults/ProcessInfo‌/CommandLine.
I've been struggling to wrap my head around a solution here because I have a few requirements which make things a tad more difficult.
Requirements
File paths sent to app must be opened/saved with sandbox permissions
Arguments and file paths must be intercepted by app at the same time.
Potential Solutions
XPC
Some people have suggested I use XPC but after reading about it, I'm not sure how that solution might look?
Do I have to create a Launch Agent app-companion which is always running so that it can detect command line operations and pass it to my app?
How does this work with sandboxing because each process has their own permission entitlements?
Apple Script
Should I use Apple script to tell my app to open these files with arguments, thus getting around the sandboxing feature?
When opening files via AppleScript, can I save those files swell?
URL Scheme
I can register my app to have its own URL scheme but the way NSApplicationDelegate handles the incoming URLs comes in two batches. First, the URLs it can open, followed by the URL schemes or the file paths it can't open. ie:
open -a MyApp.app myapp:foo; open -a MyApp.app file.txt
I can probably make this work but it's a tad tacky and I really want to do this the right way.
A command-line tool which ingests its arguments and turns them in to Apple Events is the way to go. You can see how this works from the user's point of view by installing the BBEdit command-line tools and then running man bbedit or man bbdiff in a Terminal window.
From your command-line tool's point of view, the "interesting" parts are:
Figure out whether the application is running: +[NSRunningApplication runningApplicationsWithBundleIdentifier:] will help with that.
If the application is not running, then use -[NSWorkspaceURLForApplicationWithBundleIdentifier:] to first locate the application by bundle ID, then -[NSWorkspace launchApplicationAtURL:options:configuration:error:] to launch the application. This will return an NSRunningApplication instance, or NIL and an error. (Make sure to handle the error case.)
Using the NSRunningApplication instance obtained from either step 1 or step 2, you can now use either the NSAppleEventDescriptor APIs or the low-level AppleEvent C APIs to construct an event. (The higher-level API is probably easier to use.)
That would go something like this:
Construct a target descriptor using the processIdentifier from your running application:
targetDesc = [NSAppleEventDescriptor descriptorWithProcessIdentifier: myRunningApplication.processIdentifier;
Construct an "open documents" event, addressed to your target application:
event = [NSAppleEventDescriptor appleEventWithEventClass: kCoreEventClass eventID: kAEOpenDocuments targetDescriptor: targetDesc returnID: kAutoGenerateReturnID transactionID: kAnyTransactionID];
Note: I use kCoreEventClass/kAEOpenDocuments as an example - if you're trying to open one or more files with additional information, that's fine. If you're doing some other work, then you should invent a four-character code for an event class which is specific to your application, and a four-character event ID which is unique to the operation you're requesting.
Add the command arguments to the event. For each argument, this consists of creating an appropriate descriptor based on the argument's intrinsic type (boolean, int, string, file URL), and then adding it to the event using a keyword parameter.
(An Apple Event "keyword" is a four-character code. You can invent your own, with constraints (don't use all-lowercase, and you can use ones defined in AEDataModel.h or AERegistry.h where they fit with your needs).
For each descriptor you create, add it to the event using -[setParamDescriptor: forKeyword:]:
myURLParamDesc = [NSAppleEventDescriptor descriptorWithFileURL: myFileURL];
[event setParamDescriptor: myURLParamDesc forKey: kMyFileParamKeyword];
When you've added all of the parameters to the event, send it:
[event sendWithOptions: kAENoReply timeout: FLOAT_MAX error: &error];
On the application side, you'll need to use -[NSAppleEventManager setEventHandler: andSelector: forEventClass: andID:]. This will get called for your custom event class and ID that you invented above, at which point you can use the descriptor APIs to pull the event apart and run your operation.
Sandboxing takes care of itself: your application automatically gets a sandboxing extension for files that it's been passed via Apple Events.
Your command-line tool is not sandboxed -- it can't be, because it's run from Terminal and (potentially) other nonsandboxed apps.
However, the tool must be signed with the hardened runtime, and with com.apple.security.automation.apple-events = YES and a com.apple.security.temporary-exception.apple-events naming your application's bundle identifier, so that the tool can send Apple Events to your application.
(And the tool will need an Info.plist with an NSAppleEventsUsageDescription string.)
I've left a fair amount as an exercise for the reader; but hopefully this will get you started.

Windows SendTo from script

I'm writing an application where I have to send an email with an attachment using the default mail application.
Before the email is sent, I want the user to be able to edit the text, i.e. the application should just open the mail client with pre-filled recipient and attachment and give the user the opportunity to send it.
At very minimum I need the same effect I'd got if I selected "SendTo/Mail Recipient" from the context menu of the file.
Solutions based on the "mailto:" trick won't work as there are mail clients that do not support the "attachment=" part.
The most complete solution I've found is this one:
http://www.codeproject.com/Articles/3839/SendTo-mail-recipient
but it seems a lot of code for something so simple! (and also crashes when compiled with VS2008)
Is there any other option? It would be ok even if it was an external tool or script (e.g. a .vbs script to be launched with cscript).
I would advise you to use MAPI (Messaging Application Program Interface).
If dotNet can be part of the solution, here's a ready-to-use class in C# : Class for creating MAPI Mail Messages. It would give you something like this:
MapiMailMessage message = new MapiMailMessage("Test Message", "Test Body");
message.Recipients.Add("Test#Test.com");
message.Files.Add(#"C:\del.txt");
message.ShowDialog();
Otherwise, you can always do it in C++ if you feel confortable with it, like this answer suggest.
Then, you will be able to ShellExecute the binary executable and pass it some parameters.
Hope this helps :-)

Automator / AppleScript to process incoming emails in Mac Mail

I'm designing an app that allows users to email me crash reports if my app ever crashes. I'd like to leave Mac Mail running on a computer and when an email comes through, an automator script / AppleScript runs to process the contents of the body of the email.
I've got the entire parsing/processing done in a python script, except I have to manually copy the contents of the email into a file and then run my parser on that file.
What's the best way to set this up so I can the contents of the email be pushed into my parsing script?
Many thanks!
Probably the simplest approach is to define a Mail.app Rule. You can set up filtering conditions to specify the set of incoming email to apply the rule to and among the rule actions you can specify is one to run an AppleScript on incoming messages. Rules are managed with Mail.app Preferences -> Rules. Apple supplies examples of Rule Action scripts with Mac OS X. Look in /Library/Scripts/Mail Scripts/Rule Actions or search the web.
Here's a script that extracts from email into a file using a mail rule: MacScripter / Mail rule script for message export. Might be good for sample code for what you're doing.
Use the Dictionary in Applescript Editor to see the properties of mail and you'll quickly be able to see the properties of any mail message. Here's a quick and dirty example of getting the content of a mail message.
tell application "Mail"
set the_messages to selection
repeat with this_message in the_messages
set mytext to content of this_message
end repeat
end tell
Modify the script linked to above that copies output to a temporary file and then pass that file to your Python script to act on.

Help me build this tool in windows envoirnment

I made a desktop music application in adobe air.
I want to update the status of some IM clients running EG: Yahoo messenger,Gtalk,AIM,MSN etc.
with the current playing song.
I am not desktop developer.This is first time i am making something for desktop.
SO is there any way in any Programming language that i can make something which will change the Data (Status message) of a running IM client.
Please Just guide me through this problem .
Edit: I dont want to ask for username/password of users IMs accounts , so via API is not a solution in this case .
It will be like Person X running mine music application and also logged to various third party IM clients (YIM,Gtalk etc).
SO if he is playing a song in the music application , then mine app will update presence status message on the IM clients to " Listening to bla bla song ".
So it is like high-jacking/Hacking the data of the running third party IM client.
Have a look at libpurple, it might have the functionality you require.
There is also telepathy, but I think it is related to the former somehow (one uses the other or they do the same thing).
EDIT: for the recent edit: it looks to me like you want something like MSN Messenger displaying the currently playing track in Windows Media Player. This requires a plugin for the messaging client, no way around that.
Perhaps an easier way to get this done would be to develop a plugin for one of the numerous multi-platform IM clients such as GAIM or Trillian. This would let you target stuff across the board without undue effort . . .
I think your only option is to write a plugin for each chat client you want to target, which could take some time.
So let me suggest an alternative: Add last.fm audioscrobbler support to your application. You would simply send the Now Playing info to last.fm via the API (http://www.last.fm/api/submissions), and it will appear on the user's profile page. Most music players already support this method because it's a pretty popular service, and a lot of people link to their last.fm profiles on their blog/facebook/etc.

Session 0 Isolation

Vista puts out a new security preventing Session 0 from accessing hardware like the video card, and the user no longer logs into session 0. I know this means that I cannot show the user a GUI, however, does that also mean I can't show one at all? The way my code is set up right now, it would be more work to make it command line only, however if I can use my existing code and just programmatically manage the GUI it would take a lot less code.
Is this possible?
The article from MSDN says this:
• A service attempts to create a user interface (UI), such as a dialog box, in Session 0. Because the user is not running in Session 0, he or she never sees the UI and therefore cannot provide the input that the service is looking for. The service appears to stop functioning because it is waiting for a user response that does not occur.
Which makes me think it is possible to have an automated UI, but someone told me that you couldn't use SendKeys with a service because it was disabled in Session 0.
EDIT: I don't actually need to show the user the GUI
You can show one; it just doesn't show up.
There is a little notification in the taskbar about there being a GUI window and a way to switch to it.
Anyway, there actually is a TerminalServices API command to switch active session that you could call if you really needed it to show up.
You can write a separate process which provides the UI for your service process. The communication between your UI and service process can be done in various ways (search the web for "inter process communication" or "IPC").
Your service can have a GUI. It's simply that no human will ever see it. As the MSDN quote suggests, a service can display a dialog box. The call to MessageBox won't fail; it just won't ever return — there won't be anyone to press its buttons.
I'm not sure what you mean by wanting to "manage the GUI." Do you actually mean pretending to send input to the controls, as with SendInput? I see no reason that it wouldn't be possible; you'd be injecting input into your own program's queue, after all, and SendInput's Vista-specific warnings don't say anything about that. But I think you'd be making things much more complicated than they need to be. Revisit the idea to alter your program to have no UI at all. (That's not the same as having a console program. Consoles are UI.)
Instead of simulating the mouse messages necessary to click a button, for instance, eliminate the middle-man and simply call directly the function that the button-click event would have called.

Resources