[Share Code] Implemented x-callback-url
This morning I asked if it is possible to use x-callback-urls with Pythonista and access data other apps provide.
I did some experimentation and came up with a script that is working perfectly.
What it does:
- Add support for x-callback.urls to Pythonista
- Replace the default
UIApplication -openURL:sourceApplication:annotation:method with a custom one (Using @JonB's swizzle.py)
- The custom
-openURL:method checks if the url that is opened is the callback from the other app
- If the url is from the other app, a handler function that you provide will be called, with some information about the url as a parameter
- Passed information includes:
- The full url called
- The app that opened the callback-url
- A dictionary containing all parameters and their values from the url (This is where you get data you requested from another app)
- If the url is NOT in response to the x-callback-url, Pythonistas default
-openURL:will be called, with all parameters passed to the swizzled one. This ensures that other scripts using the
pythonista://url scheme to open/launch files still work.
How to use it:
(Using Drafts as an example, works with any app that supports x-callback-urls)
import x_callback_url url = 'drafts4://x-callback-url/get?uuid=YOUR_UUID&x-success=pythonista://' def handler(url_info): print(info) # Do something with the data passed back to Pythonista (available through url_info.parameters) x_callback_url.open_url(url, handler)
- You'll also need swizzle.py
- If you don't want to do anything with data passed back to Pythonista, you can omit the handler parameter and just pass the url to
- This currently supports only the
x-successparameter, I'll add support for
x-errorat a later point (
x-successis by far the most important one)
- If you have any questions about this, just ask!
Where you can get this awesome piece of code:
- Just download it from GitHub
I have to say that this is the Pythonista script I'm by far the most proud of. Thanks @omz for making this great app!
I will point out that, at least for drafts, I believe you could have used [[draft]], or [[title]] or [[body]] in the callback url in order to pass data to pythonistas argv:
your solution does seem more general for a variety of other apps. kudos!
This looks awesome! Unfortunately I'm not able to get it working! Would you mind taking a look at this example and telling me what's going on?
# coding: utf-8 import x_callback_url url = "working-copy://x-callback-url/status/?repo=MY_REPO&unchanged=1&key=MY_KEY&x-success=pythonista://" def handler(response): print(response) x_callback_url.open_url(url, handler)
This results in the following output in the console:
Traceback (most recent call last): File "_ctypes/callbacks.c", line 314, in 'calling callback function' File "/private/var/mobile/Containers/Shared/AppGroup/84B2FC5A-8F6A-4B20-BA21-BE5B5A07629F/Documents/site-packages/x_callback_url.py", line 32, in application_openURL_sourceApplication_annotation_ url_str = str(ObjCInstance(url)) NameError: global name 'ObjCInstance' is not defined
what version of pythonista are you using?
This looks like the x callback module was cleared, so its tlobals no longe exist.
You may be able to correct this with a
from objc_utils import ObjCInstance
inside the callback function that is failing (i.e make sure all dependencies of that function are imported locally, and do not rely on closures to expose globals)
I threw some imports in, but now I'm stuck at:
NameError: global name 'c' is not defined
# coding: utf-8 import x_callback_url from objc_util import ObjCInstance _handler = None _requestID = None url = "working-copy://x-callback-url/status/?repo=MY_REPO&unchanged=1&key=MY_KEY&x-success=pythonista://" def handler(response): print(response) x_callback_url.open_url(url, handler)
EDIT: Ahh. Putting a
from objc_util import *into my calling module got me over the worst of it. But I still need access to the globals (because with the code above I end up with
EDIT2: Well I'm stumped. I wondered if the leading
_was messing with the global variables (something something name mangling?) so I renamed
g_handler, respectively and it worked! Everything ran fine after that. A little later I tried reverting that change (the engineer in me likes reproducible errors) and everything still worked fine, despite my earlier problems. So then I tried removing the extra import in my calling module (
from objc_utils import *) and everything still worked fine. So it seems my original issue has vanished without a trace and now I cannot reproduce it.
Please excuse the double post, but this is about a different issue to my last post.
I note that the OP's version of this script assumes that the x-callback response data will be formatted something like
app://xcallbackresponse-REQUEST_ID/?query=value&query=value. But one of the apps I'm working with formats its response like
app://xcallbackresponse-REQUEST_IDvaluewhich causes the URL parsing in this script to break.
Here is a modified version which handles this case a little more gracefully by setting
Nonewhen it can't parse the URL directly and by creating a new
x_callback_response.raw_response_datawhich is simply the response URL without the
@Subject22 thank you! I'll look into it when I find some time
This is absolutely fantastic !
have you by any chance tried calling pythonista from another app ?
say workflow->pythonista->callback ?
When trying to use this recipe and consequently swizzle.py I'm seeing the follow error:
Traceback (most recent call last): File "_ctypes/callbacks.c", line 234, in 'calling callback function' File "/private/var/mobile/Containers/Shared/AppGroup/74CC34ED-493E-431F-9C45-5BD2EF3B2AE0/Pythonista3/Documents/firebaseapp/swizzle.py", line 146, in saveData File "/var/containers/Bundle/Application/71C9338F-1BD7-4D52-9DAD-EE24DDF5139E/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 796, in __call__ method_name, kwarg_order = resolve_instance_method(obj, self.name, args, kwargs) File "/var/containers/Bundle/Application/71C9338F-1BD7-4D52-9DAD-EE24DDF5139E/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 403, in resolve_instance_method raise AttributeError('No method found for %s' % (name,)) AttributeError: No method found for originalsaveData
Any ideas how I can debug this?
I posted a github comment -- basically the code was swizzling a subclass's method, but then is passed an instance of the parent class, which does not have the original method. Swizzling the parent class (somewhat manual in this case) resolves the issue