├── README.md ├── scripts ├── ImportObjCClass.py ├── importFramework.py ├── Dictionary Lookup.py ├── New Script with info header.py ├── youtube_archiving │ ├── Append to 2Archive.py │ └── LKEvernoteApi.py ├── PythonistaBackup.py ├── x_callback_url.py ├── get_system_console.py ├── abfahrt.py ├── Top Songs.py └── ical.py ├── .gitignore └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # pythonista-scripts 2 | 3 | My collections of little scripts for [Pythonista](http://omz-software.com/pythonista/index.html) 4 | 5 | Most of these are several years old and might be broken by now 6 | -------------------------------------------------------------------------------- /scripts/ImportObjCClass.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Put this script into the editor action (wrench) menu. 4 | # Whenever you want to import an Objective-C class, 5 | # you jutst need to type the classname, select it and invoke this script via the action menu. 6 | # This script will check if a classname with the name you selected exists 7 | # and add ` = ObjcClass(_classname_)` to the end of the line 8 | 9 | import editor, console, sys 10 | from objc_util import ObjCClass 11 | 12 | selection = editor.get_selection() 13 | line = editor.get_line_selection() 14 | 15 | text = editor.get_text() 16 | 17 | classname = text[int(selection[0]):int(selection[1])] 18 | 19 | try: 20 | ObjCClass(classname) 21 | editor.set_selection(selection[1]) 22 | editor.replace_text(selection[1], selection[1], ' = ObjCClass(\'{}\')'.format(classname)) 23 | except: 24 | console.hud_alert(sys.exc_info()[1].message, 'error') 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | -------------------------------------------------------------------------------- /scripts/importFramework.py: -------------------------------------------------------------------------------- 1 | from objc_util import * 2 | 3 | 4 | class ImportFrameworkError(Exception): 5 | def __init__(self, name): 6 | self.name = name 7 | 8 | def __str__(self): 9 | return 'Couldn\'t import {}.framewrork. (Neither from public, nor from private frameworks)'.format(self.name) 10 | 11 | def _framework_path(name, private): 12 | return '/System/Library/{}Frameworks/{}.framework'.format('Private' if private else '', name) 13 | 14 | def importFramework(name): 15 | global _framework_path # This is required to work when the import stuff is importes in the startup file 16 | public_path = _framework_path(name, private=False) 17 | private_path = _framework_path(name, private=True) 18 | 19 | bundle = NSBundle.bundleWithPath_(public_path) 20 | 21 | if not bundle is None: 22 | bundle.load() 23 | else: 24 | # Could not load public bundle, will try private 25 | bundle = NSBundle.bundleWithPath_(private_path) 26 | if not bundle is None: 27 | bundle.load() 28 | else: 29 | raise ImportFrameworkError(name) 30 | 31 | if __name__ == '__main__': 32 | importFramework('UIKit') 33 | importFramework('') 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Lukas Kollmer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/Dictionary Lookup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from objc_util import ObjCClass, UIApplication, CGSize, on_main_thread 3 | import sys 4 | import appex 5 | import dialogs 6 | import ui 7 | 8 | UIReferenceLibraryViewController = ObjCClass('UIReferenceLibraryViewController') 9 | 10 | 11 | @on_main_thread 12 | def main(): 13 | input = ' '.join(sys.argv[1:]) or dialogs.text_dialog() 14 | if input: 15 | referenceViewController = UIReferenceLibraryViewController.alloc().initWithTerm_(input) 16 | 17 | rootVC = UIApplication.sharedApplication().keyWindow().rootViewController() 18 | tabVC = rootVC.detailViewController() 19 | 20 | referenceViewController.setTitle_("Definition: '{}'".format(input)) 21 | referenceViewController.setPreferredContentSize_(CGSize(540, 540)) 22 | referenceViewController.setModalPresentationStyle_(2) 23 | 24 | #tabVC.addTabWithViewController_(referenceViewController) 25 | tabVC.presentViewController_animated_completion_(referenceViewController, True, None) 26 | 27 | if __name__ == '__main__': 28 | if not appex.is_running_extension(): 29 | main() 30 | else: 31 | dialogs.hud_alert('Script does not work in app extension', icon='error') 32 | -------------------------------------------------------------------------------- /scripts/New Script with info header.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Pythonista template to include some basic information in a header comment 6 | 7 | You need to save this file to '~/Documents/Templates', so that Pythonista 8 | can find and list it in the "New File" dialog 9 | ''' 10 | 11 | import datetime 12 | import dialogs 13 | import editor 14 | import os 15 | 16 | __author__ = 'Lukas Kollmer' 17 | __copyright__ = 'Copyright © 2016 Lukas Kollmer ' 18 | 19 | 20 | def title_key_dict(title, key): 21 | return {'type': 'text', 'title': title, 'key': key} 22 | 23 | fields = ([title_key_dict('Author', 'author_name'), # section 1 rows 24 | title_key_dict('Email', 'email')], 25 | [title_key_dict('Description', 'description'), # section 2 rows 26 | title_key_dict('Documentation', 'documentation')]) 27 | 28 | form_sections = (('About', fields[0], None), 29 | ('File info', fields[1], None)) 30 | 31 | data = dialogs.form_dialog('New File', sections=form_sections) 32 | assert data, 'No data entered.' 33 | #data['filename'] = os.path.basename(editor.get_path()) 34 | data['copyright_year'] = datetime.datetime.now().year 35 | 36 | fmt = """#!/usr/bin/env python3 37 | # -*- coding: utf-8 -*- 38 | 39 | ''' 40 | {description} 41 | 42 | {documentation} 43 | ''' 44 | 45 | import sys 46 | 47 | __author__ = '{author_name}' 48 | __copyright__ = 'Copyright © {copyright_year}, {author_name} <{email}>' 49 | __credits__ = ['{author_name}'] 50 | __email__ = '{email}' 51 | __license__ = 'MIT' 52 | __maintainer__ = '{author_name}' 53 | __status__ = 'Pre-Alpha' 54 | __version__ = '0.0.1' 55 | """ 56 | 57 | editor.replace_text(0, len(editor.get_text()), fmt.format(**data)) 58 | -------------------------------------------------------------------------------- /scripts/youtube_archiving/Append to 2Archive.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import re 3 | import sys 4 | import appex 5 | import console 6 | import datetime 7 | import clipboard 8 | import urllib2 9 | 10 | 11 | #clipboard.set('https://youtu.be/fTTGALaRZoc') 12 | 13 | add_if_already_exists = False 14 | 15 | console.clear() 16 | 17 | 18 | import LKEvernoteApi 19 | 20 | 21 | def title_of_url(url): 22 | try: 23 | soup = BeautifulSoup(urllib2.urlopen(url)) 24 | return soup.title.string 25 | except: # caution: avoid naked exceptions 26 | return '' 27 | 28 | 29 | guid = '__YOUR_NOTE_GUID_HERE__' 30 | 31 | user_input = '' # input is a built-in function so use a different name 32 | 33 | if appex.is_running_extension(): 34 | LKEvernoteApi.log_progress('load url provided to app extension') 35 | user_input = appex.get_url() 36 | else: 37 | LKEvernoteApi.log_progress('not running from extension, checking arguments') 38 | if len(sys.argv) > 1: 39 | evernote.log_progress('argument found, use that') 40 | user_input = sys.argv[1] 41 | else: 42 | LKEvernoteApi.log_progress('no arguments found, will use clipboard text') 43 | user_input = clipboard.get() 44 | if not user_input: 45 | sys.exit('Clipboard is empty, no arguments passed to script') 46 | 47 | LKEvernoteApi.log_progress('Loading title of passed url') 48 | url_title = title_of_url(user_input) 49 | if url_title: 50 | url_title = ' ({}) '.format(url_title.replace('&', 'and')) 51 | 52 | LKEvernoteApi.log_progress('create ENML string') 53 | fmt = ' {}{}(@ {:%d %b %Y %H:%M})' 54 | en_todo_text = fmt.format(input, url_title, datetime.datetime.now()) 55 | print(en_todo_text) 56 | 57 | LKEvernoteApi.log_progress('call ´appendNote´ function') 58 | LKEvernoteApi.append_to_note(guid=guid, new_content=en_todo_text, main_new_content=user_input, 59 | add_if_already_exists=add_if_already_exists) 60 | 61 | LKEvernoteApi.log_progress('Done') 62 | 63 | if appex.is_running_extension(): 64 | appex.finish() 65 | #utilities.quit() 66 | else: 67 | sys.exit() 68 | -------------------------------------------------------------------------------- /scripts/PythonistaBackup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | '''Creates a zip archive of your Pythonista files and uploads it to S3.''' 3 | 4 | from __future__ import print_function 5 | 6 | import boto 7 | from boto.s3.key import Key 8 | import console 9 | import keychain 10 | from objc_util import NSBundle 11 | import os 12 | import requests 13 | import shutil 14 | import sys 15 | import tempfile 16 | import time 17 | import ui 18 | 19 | try: 20 | from reprint_line import reprint 21 | except ImportError: 22 | reprint = print 23 | 24 | console.clear() 25 | 26 | 27 | @ui.in_background 28 | def perform_backup(quiet=True): 29 | try: 30 | is_amazon_up = requests.get('http://s3.amazonaws.com').status_code == 200 31 | except requests.exceptions.ConnectionError: 32 | is_amazon_up = False 33 | if not is_amazon_up: 34 | if quiet: 35 | return 36 | else: 37 | sys.exit('ERROR: Unable to connect to s3.amazonaws.com') 38 | doc_path = os.path.expanduser('~/Documents') 39 | os.chdir(doc_path) 40 | backup_path = os.path.join(doc_path, 'Backup.zip') 41 | if os.path.exists(backup_path): 42 | os.remove(backup_path) 43 | print('Creating backup archive...') 44 | shutil.make_archive(os.path.join(tempfile.gettempdir(), 'Backup'), 'zip') 45 | shutil.move(os.path.join(tempfile.gettempdir(), 'Backup.zip'), backup_path) 46 | print('Backup archive created, uploading to S3 ...') 47 | 48 | date_text = time.strftime('%Y-%b-%d') 49 | time_text = time.strftime('%I-%M-%S-%p') 50 | info_dict_version_key = 'CFBundleShortVersionString' 51 | main_bundle = NSBundle.mainBundle() 52 | app_version = str(main_bundle.objectForInfoDictionaryKey_(info_dict_version_key))[0] 53 | 54 | AWS_ACCESS_KEY_ID = keychain.get_password('aws', 'AWS_ACCESS_KEY_ID') 55 | AWS_SECRET_ACCESS_KEY = keychain.get_password('aws', 'AWS_SECRET_ACCESS_KEY') 56 | 57 | bucket_name = 'lukaskollmer' 58 | 59 | def percent_cb(complete, total): 60 | reprint('{}'.format(round(float(complete) / float(total) * 100, 2))) 61 | 62 | s3 = boto.connect_s3(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) 63 | bucket = s3.get_bucket(bucket_name) 64 | 65 | filename = 'Backup-{}.zip'.format(time_text) 66 | k = Key(bucket) 67 | k.storage_class = 'REDUCED_REDUNDANCY' 68 | k.key = '/Backup/Pythonista{}/{}/{}'.format(app_version, date_text, filename) 69 | print('0.0 %') 70 | k.set_contents_from_filename('Backup.zip', cb=percent_cb, num_cb=10) 71 | print('Successfully uploaded') 72 | os.remove(backup_path) 73 | 74 | if __name__ == '__main__': 75 | perform_backup(quiet=False) 76 | -------------------------------------------------------------------------------- /scripts/x_callback_url.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import swizzle 3 | from objc_util import * 4 | import ctypes 5 | import json, urllib 6 | import uuid 7 | import sys 8 | import webbrowser 9 | 10 | NSURLComponents = ObjCClass('NSURLComponents') 11 | appDelegate = UIApplication.sharedApplication().delegate() 12 | _handler = None 13 | _requestID = None 14 | 15 | class x_callback_response (object): 16 | full_url = None 17 | source_app = None 18 | parameters = None 19 | 20 | def __str__(self): 21 | return ''.format(self.source_app, self.parameters) 22 | 23 | def open_url(url, handler): 24 | global _handler 25 | global _requestID 26 | _requestID = uuid.uuid1() 27 | _handler = handler 28 | url_with_uuid = url + 'xcallbackresponse-' + str(_requestID) 29 | webbrowser.open(url_with_uuid) 30 | 31 | def application_openURL_sourceApplication_annotation_(_self, _sel, app, url, source_app, annotation): 32 | url_str = str(ObjCInstance(url)) 33 | 34 | if not 'xcallbackresponse-' + str(_requestID) in url_str: 35 | print('not from x-callback-url, will run original function') 36 | obj = ObjCInstance(_self) 37 | original_method = getattr(obj, 'original'+c.sel_getName(_sel), None) 38 | if original_method: 39 | _annotation = ObjCInstance(annotation) if annotation else None 40 | return original_method(ObjCInstance(app), ObjCInstance(url), ObjCInstance(source_app), _annotation) 41 | else: 42 | x_callback_info = x_callback_response() 43 | x_callback_info.full_url = url_str 44 | x_callback_info.source_app = str(ObjCInstance(source_app)) 45 | 46 | query = NSURLComponents.componentsWithURL_resolvingAgainstBaseURL_(nsurl(url_str), False) 47 | x_callback_info.parameters = dict() 48 | for queryItem in query.queryItems(): 49 | x_callback_info.parameters[str(queryItem.name())] = str(queryItem.value()) 50 | 51 | if _handler: 52 | _handler(x_callback_info) 53 | return True 54 | 55 | 56 | 57 | # Do the swizzling 58 | cls = ObjCInstance(c.object_getClass(appDelegate.ptr)) 59 | swizzle.swizzle(cls, 'application:openURL:sourceApplication:annotation:', application_openURL_sourceApplication_annotation_) 60 | 61 | 62 | 63 | 64 | if __name__ == '__main__': 65 | import console 66 | console.clear() 67 | 68 | draft_uuid = '9B0A1EF8-B2D8-4050-8EE4-B6D8AC0F229B' 69 | url = 'drafts4://x-callback-url/get?uuid={}&x-success=pythonista://'.format(draft_uuid) 70 | 71 | def my_handler(info): 72 | print(info.full_url) 73 | print(info.parameters['text']) 74 | 75 | open_url(url, my_handler) 76 | -------------------------------------------------------------------------------- /scripts/get_system_console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2016 Lukas Kollmer 3 | # Code adopted for Pythonista from http://stackoverflow.com/a/6145556/2513803 4 | 5 | __all__ = ['get_log'] 6 | 7 | from ctypes import * 8 | from objc_util import * 9 | import time 10 | 11 | c = cdll.LoadLibrary(None) 12 | 13 | asl_new = c.asl_new 14 | asl_new.restype = c_void_p 15 | asl_new.argtypes = [c_uint32] 16 | 17 | asl_search = c.asl_search 18 | asl_search.restype = c_void_p 19 | asl_search.argtypes = [c_void_p, c_void_p] 20 | 21 | aslresponse_next = c.aslresponse_next 22 | aslresponse_next.restype = c_void_p 23 | aslresponse_next.argtypes = [c_void_p] 24 | 25 | asl_key = c.asl_key 26 | asl_key.restype = c_char_p 27 | asl_key.argtypes = [c_void_p, c_uint32] 28 | 29 | asl_get = c.asl_get 30 | asl_get.restype = c_char_p 31 | asl_get.argtypes = [c_void_p, c_char_p] 32 | 33 | aslresponse_free = c.aslresponse_free 34 | aslresponse_free.restype = None 35 | aslresponse_free.argtypes = [c_void_p] 36 | 37 | 38 | ASL_TYPE_QUERY = 0 #c_uint32 39 | 40 | def get_log(): 41 | 42 | logs = [] 43 | 44 | 45 | q, m = None, None #aslmsg 46 | 47 | q = asl_new(ASL_TYPE_QUERY) 48 | 49 | r = asl_search(None, q) #aslresponse 50 | 51 | m = aslresponse_next(r) 52 | while m != None: 53 | tmpDict = NSMutableDictionary.dictionary() 54 | 55 | i = c_uint(0) 56 | key = asl_key(m, i) 57 | while key != None: 58 | 59 | keyString = NSString.stringWithUTF8String_(key) 60 | val = asl_get(m, key) 61 | 62 | valString = NSString.stringWithUTF8String_(val) 63 | 64 | tmpDict.setObject_forKey_(valString, keyString) 65 | 66 | # continue while statement 67 | i = c_uint(i.value + 1) 68 | key = asl_key(m, i) 69 | logs.append(tmpDict) 70 | 71 | 72 | # continue while statement 73 | m = aslresponse_next(r) 74 | aslresponse_free(r) 75 | return logs 76 | 77 | if __name__ == '__main__': 78 | import console 79 | import ui 80 | import editor 81 | import dialogs 82 | 83 | items = [] 84 | 85 | for log in get_log(): 86 | items.append('{} - {}'.format(log['CFLog Local Time'], log['Message'])) 87 | 88 | log_text = '\n'.join(items) 89 | 90 | def share(sender): 91 | dialogs.share_text(log_text) 92 | 93 | theme = editor.get_theme_dict() 94 | 95 | view = ui.TextView() 96 | view.name = 'Pythonista System Log' 97 | view.text = log_text 98 | view.font = ('Menlo-Regular', 15) 99 | view.editable = False 100 | 101 | share_button = ui.ButtonItem(title='Share', action=share) 102 | view.right_button_items = [share_button] 103 | 104 | editor.present_themed(view) 105 | -------------------------------------------------------------------------------- /scripts/youtube_archiving/LKEvernoteApi.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import re 3 | import sys 4 | import console 5 | import datetime 6 | import clipboard 7 | from string import Template 8 | import evernote.edam.type.ttypes as Types 9 | from evernote.api.client import EvernoteClient 10 | import evernote.edam.userstore.constants as UserStoreConstants 11 | from evernote.edam.notestore.ttypes import NoteFilter, NotesMetadataResultSpec 12 | 13 | 14 | #__all__ = ['log_progress', 'get_all_notebooks', 'appentToNote'] 15 | 16 | log_progress = True 17 | 18 | 19 | _auth_token = '__YOUR_EVERNOTE_AUTH_TOKEN_HERE__' 20 | _progress = 0 21 | 22 | _client = None 23 | _note_store = None 24 | 25 | def _get_client(): 26 | global _client 27 | if _client is None: 28 | log_progress('login to evernote') 29 | _client = EvernoteClient(token=_auth_token, sandbox=False) 30 | return _client 31 | 32 | def _get_note_store(): 33 | global _note_store 34 | if _note_store is None: 35 | log_progress('load the note store') 36 | _note_store = _get_client().get_note_store() 37 | return _note_store 38 | 39 | 40 | def log_progress(msg, color='default'): 41 | global log_progress 42 | global _progress 43 | if log_progress: 44 | progress_str = str(_progress).zfill(2) 45 | message = '{0} – {1}'.format(progress_str, msg) 46 | print(message) 47 | _progress += 1 48 | 49 | def get_all_notebooks(): 50 | return _get_note_store().listNotebooks() 51 | 52 | def get_all_notes_in_notebook(notebook): 53 | guid = notebook.guid 54 | filter = NoteFilter(notebookGuid=guid) 55 | 56 | #def __init__(self, includeTitle=None, includeContentLength=None, includeCreated=None, includeUpdated=None, includeDeleted=None, includeUpdateSequenceNum=None, includeNotebookGuid=None, includeTagGuids=None, includeAttributes=None, includeLargestResourceMime=None, includeLargestResourceSize=None,): 57 | resultsSpec = NotesMetadataResultSpec(includeTitle=True) 58 | fetch_results = _get_note_store().findNotesMetadata(filter, 0, 10, resultsSpec) 59 | real_notes = [] 60 | for n in fetch_results.notes: 61 | #note = _get_note_store().getNote(n.guid, withContent=True, withResourcesData=False, withResourcesRecognition=False, withResourcesAlternateData=False) 62 | note = _get_note_store().getNote(n.guid, True, False, False, False) 63 | real_notes.append(note) 64 | return real_notes 65 | 66 | #Accepts the GUID(string) of the note you want to append 67 | def append_to_note(guid, new_content, main_new_content, add_if_already_exists): 68 | 69 | #Get the note to be updated using the note's guid http://dev.evernote.com/documentation/reference/NoteStore.html#Fn_NoteStore_getNote 70 | note_store = _get_note_store() 71 | 72 | log_progress('load the \'2Archive\' note') 73 | note = note_store.getNote(guid, True, True, False, False) 74 | 75 | #Regular expressions used to replicate ENML tags. These same tags will be used to "rebuild" the note with the existing note metadata 76 | log_progress('do the regEx stuff') 77 | xmlTag = re.search('<\?xml.*?>', note.content).group() 78 | docTag = re.search('<\!DOCTYPE.*?>', note.content).group() 79 | noteOpenTag = re.search('<\s*en-note.*?>', note.content).group() 80 | noteCloseTag = re.search('<\s*/en-note.*?>', note.content).group() 81 | breakTag = '
' 82 | 83 | #Rebuild the note using the new content 84 | log_progress('Rebuild the note using the new content') 85 | content = note.content.replace(xmlTag, "").replace(noteOpenTag, "").replace(noteCloseTag, "").replace(docTag, "").strip() 86 | 87 | if main_new_content in content: 88 | if add_if_already_exists: 89 | content += breakTag + "".join(new_content) 90 | else: 91 | log_progress('url already in note') 92 | else: 93 | content += breakTag + ''.join(new_content) 94 | template = Template ('$xml $doc $openTag $body $closeTag') 95 | note.content = template.substitute(xml=xmlTag,doc=docTag,openTag=noteOpenTag,body=content,closeTag=noteCloseTag) 96 | 97 | #Update the note 98 | 99 | log_progress('save the updated note to evernote') 100 | try: 101 | _get_note_store().updateNote(note) 102 | except: 103 | print sys.exc_info() 104 | 105 | #Return updated note (object) to the function 106 | return note 107 | 108 | if __name__ == '__main__': 109 | pass 110 | -------------------------------------------------------------------------------- /scripts/abfahrt.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | 3 | """ 4 | abfahrt.py 5 | 6 | TODO: 7 | - make cells auto-update the time remaining 8 | - swipe left to create reminders 9 | - rewrite everithing using objc_util? 10 | """ 11 | 12 | import ui 13 | import location 14 | import requests 15 | from datetime import datetime 16 | 17 | endpoins = { 18 | 'nearby_stations': 'https://www.mvg.de/fahrinfo/api/location/nearby', 19 | 'departures': 'https://www.mvg.de/fahrinfo/api/departure/' 20 | } 21 | headers = {'X-MVG-Authorization-Key': '5af1beca494712ed38d313714d4caff6'} 22 | 23 | def make_request(url, parameters): 24 | response = requests.get(url, headers=headers, params=parameters) 25 | return response.json() 26 | 27 | 28 | def get_nearby_stations(location): 29 | params = { 30 | 'latitude': location['latitude'], 31 | 'longitude': location['longitude'] 32 | } 33 | 34 | return make_request(endpoins['nearby_stations'], params)['locations'] 35 | 36 | def get_departures(station_id): 37 | url = endpoins['departures'] + str(station_id) 38 | 39 | return make_request(url, {'footway': 0})['departures'] 40 | 41 | 42 | 43 | class NearbyStationsViewController(ui.View): 44 | def __init__(self): 45 | self.tv = ui.TableView() 46 | self.tv.flex = 'WH' 47 | self.tv.data_source = self 48 | self.tv.delegate = self 49 | self.add_subview(self.tv) 50 | self.name = 'Nearby' 51 | self.right_button_items = [ui.ButtonItem(image=ui.Image.named('refresh'), title="refresh", action=lambda x: self.load_stations())] 52 | self.load_stations() 53 | 54 | def load_stations(self): 55 | self.stations = get_nearby_stations(location.get_location()) 56 | self.tv.reload_data() 57 | 58 | # Data Source 59 | def tableview_number_of_rows(self, tableview, section): 60 | return len(self.stations) 61 | 62 | def tableview_cell_for_row(self, tableview, section, row): 63 | station = self.stations[row] 64 | distance = station['distance'] 65 | cell = ui.TableViewCell('value1') 66 | cell.text_label.text = station['name'] 67 | cell.detail_text_label.text = f'{distance} m' 68 | return cell 69 | 70 | # Delegate 71 | def tableview_did_select(self, tableview, section, row): 72 | #print(f'show upcoming departures for {self.stations[row]}') 73 | self.navigation_view.push_view(DeparturesViewController(self.stations[row])) 74 | 75 | 76 | class DeparturesViewController(ui.View): 77 | def __init__(self, station): 78 | self.station = station 79 | self.departures = [] 80 | 81 | self.tv = ui.TableView() 82 | self.tv.flex = "WH" 83 | self.tv.data_source = self 84 | self.tv.delegate = self # TODO use an objc delegate instead to spport custom swipe actions? (notifications!!!) 85 | self.add_subview(self.tv) 86 | 87 | self.load_departures() 88 | 89 | def load_departures(self): 90 | self.departures = get_departures(self.station['id']) 91 | self.tv.reload_data() 92 | 93 | 94 | def tableview_number_of_rows(self, tableview, section): 95 | return len(self.departures) 96 | 97 | def tableview_cell_for_row(self, tableview, section, row): 98 | departure = self.departures[row] 99 | product = departure['product'] 100 | destination = departure['destination'] 101 | label = departure['label'] 102 | 103 | time_remaining = (datetime.fromtimestamp(departure['departureTime']/1000.0) - datetime.now()).total_seconds() 104 | if time_remaining > 60: 105 | time_remaining = f'{int(time_remaining / 60)} min' 106 | else: 107 | time_remaining = f'{int(time_remaining)} sec' 108 | 109 | cell = ui.TableViewCell('value1') 110 | cell.text_label.text = f'{label} - {destination}' 111 | cell.text_label.text_color = departure['lineBackgroundColor'] 112 | cell.detail_text_label.text = time_remaining 113 | return cell 114 | 115 | 116 | 117 | if __name__ == '__main__': 118 | #stations = get_nearby_stations(location.get_location()) 119 | #for station in stations: 120 | # print(station['name']) 121 | 122 | view = NearbyStationsViewController() 123 | nv = ui.NavigationView(view) 124 | nv.navigation_bar_hidden = False 125 | nv.present() 126 | -------------------------------------------------------------------------------- /scripts/Top Songs.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from objc_util import * 3 | import console 4 | import ui 5 | import sys 6 | 7 | console.clear() 8 | 9 | 10 | number_of_songs = 100 11 | dismiss_after_selection = True 12 | 13 | 14 | class Song(object): 15 | def __init__(self, title, albumTitle, playCount, persistentID, objcMediaItem): 16 | self.title = title 17 | self.albumTitle = albumTitle 18 | self.playCount = playCount 19 | self.persistentID = persistentID 20 | self.objcMediaItem = objcMediaItem 21 | 22 | def __str__(self): 23 | return '{0} | {1} ({2})'.format(self.playCount, self.title, self.albumTitle) 24 | 25 | importFramework('MediaPlayer') 26 | 27 | MPMediaQuery = ObjCClass('MPMediaQuery') 28 | MPMediaItemCollection = ObjCClass('MPMediaItemCollection') 29 | MPMediaPropertyPredicate = ObjCClass('MPMediaPropertyPredicate') 30 | MPMusicPlayerController = ObjCClass('MPMusicPlayerController') 31 | UITableViewController = ObjCClass('UITableViewController') 32 | UIViewController = ObjCClass('UIViewController') 33 | UIPopoverController = ObjCClass('UIPopoverController') 34 | UITableView = ObjCClass('UITableView') 35 | UITableViewCell = ObjCClass('UITableViewCell') 36 | UIColor = ObjCClass('UIColor') 37 | UINavigationController = ObjCClass('UINavigationController') 38 | UIBarButtonItem = ObjCClass('UIBarButtonItem') 39 | NSNumber = ObjCClass('NSNumber') 40 | UISwitch = ObjCClass('UISwitch') 41 | UIImage = ObjCClass('UIImage') 42 | 43 | songsQuery = MPMediaQuery.songsQuery() 44 | 45 | systemPlayer = MPMusicPlayerController.systemMusicPlayer() 46 | 47 | songs = [] 48 | repeat_switch = None 49 | 50 | for collection in songsQuery.valueForKey_('collections'): 51 | for mediaItem in collection.valueForKey_('items'): 52 | title = mediaItem.valueForKey_('title') 53 | album = mediaItem.valueForKey_('albumTitle') 54 | playCount = int(str(mediaItem.valueForKey_('playCount'))) 55 | persistentID = mediaItem.valueForKey_('persistentID') 56 | song = Song(title, album, playCount, persistentID, mediaItem) 57 | artwork_object = mediaItem.valueForKey_('artwork') 58 | if artwork_object: 59 | song.artwork = artwork_object.imageWithSize_(CGSize(200, 200)) 60 | else: 61 | song.artwork = UIImage.alloc().init() 62 | songs.append(song) 63 | 64 | 65 | songs.sort(key=lambda x: x.playCount, reverse=True) 66 | 67 | songs = songs[:number_of_songs] 68 | 69 | 70 | UITableView = ObjCClass('UITableView') 71 | UIViewController = ObjCClass('UIViewController') 72 | UITableViewCell = ObjCClass('UITableViewCell') 73 | 74 | 75 | def tableView_numberOfRowsInSection_(_self, _cmd, _tv, section): 76 | return len(songs) 77 | 78 | tableView_numberOfRowsInSection_.encoding = 'q0@0:0@0@0' 79 | tableView_numberOfRowsInSection_.restype = c_long 80 | 81 | 82 | def tableView_cellForRowAtIndexPath_(_self, _cmd, _tv, _ip): 83 | cell = UITableViewCell.alloc().initWithStyle_reuseIdentifier_(1, 'Cell') 84 | row = ObjCInstance(_ip).row() 85 | song = songs[row] 86 | cell.textLabel().setText_('{0}'.format(song.title)) 87 | cell.detailTextLabel().setText_('{0}'.format(song.playCount)) 88 | cell.setAccessoryType_(4) 89 | try: 90 | cell.imageView().setImage_(song.artwork) 91 | except: 92 | pass 93 | return cell.ptr 94 | 95 | tableView_cellForRowAtIndexPath_.encoding = '@0@0:0@0@0' 96 | 97 | 98 | def tableView_didSelectRowAtIndexPath_(_self, _cmd, _tv, _ip): 99 | row = ObjCInstance(_ip).row() 100 | song = songs[row] 101 | play_song(song) 102 | 103 | if dismiss_after_selection: 104 | ObjCInstance(_self).dismissViewControllerAnimated_completion_(True, None) 105 | 106 | 107 | tableView_didSelectRowAtIndexPath_.encoding = '@0@0:0@0@0' 108 | 109 | 110 | def tableView_accessoryButtonTappedForRowWithIndexPath_(_self, _cmd, _tv, _ip): 111 | view = ObjCInstance(_self) 112 | row = ObjCInstance(_ip).row() 113 | 114 | cell = ObjCInstance(tableView_cellForRowAtIndexPath_(_self, None, _tv, _ip)) 115 | 116 | accessory_view = ObjCInstance(cell.valueForKey_('_accessoryView')) 117 | 118 | song = songs[row] 119 | song_info_view = ui.load_view('SongInfo') 120 | ObjCInstance(song_info_view['artworkImageView']).setImage_(song.artwork) 121 | song_info_view['song_title_label'].text = str(song.title) 122 | song_info_view['song_album_label'].text = str(song.albumTitle) 123 | song_info_view['song_playcount_label'].text = str(song.playCount) 124 | 125 | song_info_view.present('popover') 126 | 127 | 128 | tableView_accessoryButtonTappedForRowWithIndexPath_.encoding = '@0@0:0@0@0' 129 | 130 | 131 | def dismiss(_self, _cmd): 132 | vc = ObjCInstance(_self) 133 | vc.dismissViewControllerAnimated_completion_(True, None) 134 | 135 | 136 | def play_song(song): 137 | collection = MPMediaItemCollection.collectionWithItems_(ns([song.objcMediaItem])) 138 | systemPlayer.setQueueWithItemCollection_(collection) 139 | repeat = repeat_switch.isOn() 140 | systemPlayer.setRepeatMode_(2 if repeat else 0) 141 | systemPlayer.play() 142 | 143 | 144 | @on_main_thread 145 | def main(): 146 | global repeat_switch 147 | 148 | rootVC = UIApplication.sharedApplication().keyWindow().rootViewController() 149 | tabVC = rootVC.detailViewController() 150 | methods = [tableView_numberOfRowsInSection_, tableView_cellForRowAtIndexPath_, tableView_didSelectRowAtIndexPath_, tableView_accessoryButtonTappedForRowWithIndexPath_, dismiss] 151 | protocols = ['UITableViewDataSource', 'UITableViewDelegate'] 152 | CustomViewController = create_objc_class('CustomViewController', UIViewController, methods=methods, protocols=protocols) 153 | vc = CustomViewController.new().autorelease() 154 | vc.title = 'Top Songs' 155 | table_view = UITableView.alloc().initWithFrame_style_(((0, 0), (0, 0)), 0) 156 | table_view.setDataSource_(vc) 157 | table_view.setDelegate_(vc) 158 | vc.view = table_view 159 | repeat_message = UIBarButtonItem.alloc().initWithTitle_style_target_action_('Repeat', 0, None, None) 160 | repeat_message.setTintColor_(UIColor.blackColor()) 161 | repeat_switch = UISwitch.alloc().init() 162 | repeat_switch.setOn_animated_(True, False) 163 | repeat_switch_bar_button_item = UIBarButtonItem.alloc().initWithCustomView_(repeat_switch) 164 | vc.navigationItem().setLeftBarButtonItems_([repeat_message, repeat_switch_bar_button_item]) 165 | closeButton = UIBarButtonItem.alloc().initWithBarButtonSystemItem_target_action_(0, vc, sel('dismiss')) 166 | navController = UINavigationController.alloc().initWithRootViewController_(vc) 167 | vc.navigationItem().setRightBarButtonItem_(closeButton) 168 | vc.setModalPresentationStyle_(0) #2 for form sheet 169 | navController.setModalPresentationStyle_(0) 170 | tabVC.presentViewController_animated_completion_(navController, True, None) 171 | #tabVC.addTabWithViewController_(vc) 172 | 173 | if __name__ == '__main__': 174 | main() 175 | -------------------------------------------------------------------------------- /scripts/ical.py: -------------------------------------------------------------------------------- 1 | 2 | # coding: utf-8 3 | ''' 4 | ical - Access iOS system calendar from Pythonista 5 | author: Lukas Kollmer 6 | note: Work in progress. not finished 7 | ''' 8 | 9 | __all__ = ['Event', 'has_access', 'save_event', 'delete_event', 'get_events', 'get_calendars', 'get_calendar'] 10 | 11 | from objc_util import * 12 | import sys 13 | import datetime 14 | import time 15 | import calendar as pysyscal 16 | import ctypes 17 | 18 | 19 | EKEventStore = ObjCClass('EKEventStore') 20 | EKEvent = ObjCClass('EKEvent') 21 | NSDate = ObjCClass('NSDate') 22 | NSDateComponents = ObjCClass('NSDateComponents') 23 | NSCalendar = ObjCClass('NSCalendar') 24 | 25 | AUTHORIZATION_STATUS_NOT_DETERMINED = 0 26 | AUTHORIZATION_STATUS_RESTRICTED = 1 27 | AUTHORIZATION_STATUS_DENIED = 2 28 | AUTHORIZATION_STATUS_AUTHORIZED = 3 29 | 30 | DEFAULT_CALENDAR_SPECIFIER = 'me.kollmer.ioscalendar.useDefaultCalendar' 31 | 32 | _event_store = None 33 | 34 | debug = True 35 | 36 | 37 | class Calendar (object): 38 | title = None 39 | identifier = None 40 | objc_object = None 41 | 42 | 43 | class Event (object): 44 | loaded_from_system_calendar = False 45 | start_date = None 46 | end_date = None 47 | title = None 48 | calendar = None 49 | is_all_day = None 50 | organizer = None 51 | identifier = None 52 | objc_event_object = None 53 | 54 | def __init__(self): 55 | pass 56 | 57 | def __str__(self): 58 | return '