[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 thepythonista://
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)
Notes:
- 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
x_callback_url.open_url()
function - This currently supports only the
x-success
parameter, I'll add support forx-source
andx-error
at a later point (x-success
is 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!
-
no exceptions are showing, and the print statements ive put into the callback dont get called indicating that the swizzel method is not getting called or has changed
-
When it comes back, is the app still running? I.e anything you had printed to the console is still there?
-
I see that API is depreciated, so likely needs to be updated to use application:openURL:options: instead.
-
ended up using
openPythonistaURL:
instead of
application:openURL:sourceApplication:annotation:cheers D
-
@eddo888 what do you mean?
did you swizzleopenPythonistaURL:
instead of swizzling -[UIApplicationDelegate application:openURL:options:]
?
-
here is my uodated and sligjtly modified x_callback_url.py
# coding: utf-8 import swizzle from objc_util import * import sys, re, os, argparse import ctypes, json, urllib, uuid, urllib import webbrowser def argue(): parser = argparse.ArgumentParser() parser.add_argument('-v', '--verbose', action='store_true', help='verbose mode') parser.add_argument('-t', '--test', action='store_true', help='run test') return parser.parse_args() def params(data): if len(data) == 0: return '' p = '&'.join( map( lambda x: '%s=%s'%(x,urllib.quote(data[x])), data.keys() ) ) return '?%s'%p def reverse(url): query = NSURLComponents.componentsWithURL_resolvingAgainstBaseURL_(nsurl(url), False) parameters = dict() if query.queryItems() is not None: for queryItem in query.queryItems(): parameters[str(queryItem.name())] = str(queryItem.value()) return parameters def open_url(url, handler): global _handler global _requestID _requestID = uuid.uuid1() _handler = handler x_success = urllib.quote('pythonista://?request=%s'%_requestID) url_with_uuid = url.replace('?','?x-success=%s&'%x_success) #sys.stderr.write('> %s\n'% url_with_uuid) webbrowser.open(url_with_uuid) def openPythonistaURL_(_self, _sel, url): url_str = str(ObjCInstance(url)) #sys.stderr.write('< %s\n'%url_str) global _call_me, _handler, _requestID if '?request=%s'%_requestID in url_str: url_str = url_str.replace('?request=%s&'%_requestID, '?') parameters = reverse(url_str) if _handler: _handler(parameters) return True elif _call_me in url_str: #print url_str parameters = reverse(url_str) x_parameters = dict() for x in [ 'x-source', 'x-success', 'x-error', 'x-cancel', 'x-script', ]: if x in parameters.keys(): x_parameters[x] = parameters[x] del parameters[x] #print '%s\n%s'%( # json.dumps(x_parameters), # json.dumps(parameters) #) if 'x-script' not in x_parameters.keys(): return try: import importlib mod = importlib.import_module( x_parameters['x-script'] ) res = str(mod.main(parameters)) url=x_parameters['x-success']+'?args=%s'%urllib.quote(res) except: error=str(sys.exc_info()[0]) url=x_parameters['x-error']+'?args=%s'%urllib.quote(error) #print url webbrowser.open(url) return True else: #print('original url=%s'%url_str) obj = ObjCInstance(_self) original_method = getattr(obj, 'original'+c.sel_getName(_sel), None) if original_method: _annotation = ObjCInstance(annotation) if annotation else None return original_method( ObjCInstance(app), ObjCInstance(url), ObjCInstance(source_app), _annotation ) return def test(): data={ 'statement' : 'select * from MyTable' } url='generaldb://x-callback-url/execute-select-statement' + params(data) print url def myhandler(parameters): print parameters for row in parameters['rows'].split('\n'): print row return open_url(url,myhandler) def setup(): global NSURLComponents, _call_me, _handler, _requestID _call_me = 'pythonista://x-callback-url' _handler = None _requestID = None NSURLComponents = ObjCClass('NSURLComponents') appDelegate = UIApplication.sharedApplication().delegate() # Do the swizzling cls = ObjCInstance(c.object_getClass(appDelegate.ptr)) swizzle.swizzle( cls, 'openPythonistaURL:', openPythonistaURL_ ) #print 'swizzled' return def main(): setup() args = argue() if args.test : test(); return print 'setup complete:'#, sys.argv #webbrowser.open('workflow://') return if __name__ == '__main__': main()
-
(fyi, you should wrap the code block with ```, to get proper syntax highlighting. reading the code w/out that is really difficult)
-
cheers thought i was in confluence not markdown :-)
-
def params(data): '?' + urllib.urlencode(data)
https://docs.python.org/2/library/urllib.html#urllib.urlencode
-
def params(data): if len(data) == 0: return '' p = '&'.join( map( lambda x: '%s=%s'%(x,urllib.quote(data[x])), data.keys() ) ) print data print urllib.urlencode(data) print p return '?%s'%p
yields
{'statement': 'select * from MyTable'} statement=select+%2A+from+MyTable statement=select%20%2A%20from%20MyTable
the client receiving the params preferrs %20 to +
-
@eddo888 In Python 3 you can do that using the
quote_via
parameter, likeurllib.parse.urlencode({...}, quote_via=urllib.parse.quote)
. However in Python 2 thequote_via
parameter doesn't exist yet. You can manually replace every+
with%20
, likeurllib.parse.urlencode({...}).replace("+", "%20")
. This should produce the same result and is much shorter than building the whole parameter string by hand.
-
Thanks for taking the time to respond and guide, I hope you have a fun weekend. Cheers Dave
-
How do I use this and get arguments from workflow?
-
This awesome piece of code leads to the famous 404...
https://github.com/lukaskollmer/pythonista-scripts/blob/master/x-callback-url/x_callback_url.py
Someone stil has it available?
I'm trying to load a file from IAwriter to process it in phytonista...
-
last edited by
-
@dc2 said:
I'm trying to load a file from IAwriter to process it in phytonista...
x-callback-url will not help you for that.
If you want to load a file into Pythonista, you have to share it to Pythonista script or to import it into Pythonista via "open external folder", if the app allows it.
-
@cvp Thanks, i think that sharing it as input in a script may be the way to go!
After trying to share the file to "Preview Markdown.py", I found that instead of the text, the path of the file is returned by appex.get_text()....
Somewhere the contents gets mixed up, probably in IAwriter...
-
last edited by
-
@dc2 did you try to share to "run Pythonista script", then the standard "import file"?
-
If i open iAWriter and the file i want to share, then the "share" button, followed by "run Pythonista script" and "import file", the result is that the text file is saved into the script folder.
Not exactly what i was looking for, but from there i could probably manipulate the file and convert it to HTML in accordance with my template.
I was hoping to use the "preview markdown" example and extend that script. But as mentioned before, it only retrieves the path rather that the content.