├── README.md ├── barcode_scanner.py ├── comment_code.py ├── convert_pyui_json.py ├── copy_filename.py ├── dl.py ├── gamefaqs_dl.py ├── get_img_index.py ├── getpwd.py ├── img_paste_horiz.py ├── img_resize.py ├── img_rotate.py ├── launch_startup.py ├── mk_entropy.py ├── move_vids.py ├── qrread.py ├── save_to_pythonista.py ├── telnet_starwars.py ├── touchid_secure.py ├── webopen.py ├── youtube_time_url.py └── ytdl.py /README.md: -------------------------------------------------------------------------------- 1 | ##Pythonista 2.0/3.0 Scripts (for iOS, iPad): 2 | 3 | GitHub Repos 4 | ------------ 5 | 6 | | Script Name | Description | 7 | | ----------------------------------- | ------------- | 8 | | [barcode_scanner][woo] | Scan barcode (Objective C utility) | 9 | | [comment_code][woo] | Comment/uncommon selected code in editor | 10 | | [convert_pyui_json][woo] | Convert pyui to json and vice versa | 11 | | [copy_filename][woo] | Copy currently opened file's filename to clipboard | 12 | | [deDMCA][woo] | Search for torrents removed by DMCA (unfinished, Sept 2016) | 13 | | [dl][woo] | Download GitHub (or PyPi) repo simply | 14 | | [gamefaqs_dl][woo] | Download a printable GameFAQs.com FAQ | 15 | | [get_img_index][woo] | Get image's index from file name | 16 | | [getpwd][woo] | Get password from keychain | 17 | | [img_paste_horiz][woo] | Paste `x` photos horizontally into a new image | 18 | | [img_resize][woo] | Resize an image (fixed ratio) | 19 | | [img_rotate][woo] | Rotate an image | 20 | | [launch_startup][woo] | pythonista_startup for [pybitcointools][pybtc] | 21 | | [mk_entropy][woo] | Drag finger to create entropy for a hash value | 22 | | [move_vids][woo] | Move video files from src => dest folder | 23 | | [qrread][woo] | Read a QR Code's data | 24 | | [save_to_pythonista][woo] | Import external application's file into Pythonista | 25 | | [telnet_starwars][woo] | Play ASCII animation of Star Wars | 26 | | [webopen][woo] | Wrapper for web browser.open | 27 | | [youtube_time_url][woo] | Create a time-coded YouTube URL | 28 | | [ytdl][woo] | Download YouTube video with youtube-dl into default dir | 29 | 30 | 31 | [woo]: https://github.com/wizardofozzie 32 | [pybtc]: https://github.com/wizardofozzie/pybitcointools 33 | -------------------------------------------------------------------------------- /barcode_scanner.py: -------------------------------------------------------------------------------- 1 | 2 | # coding: utf-8 3 | # From https://gist.github.com/omz/11891cb1c7ed459d34c7 4 | # Barcode scanner demo for Pythonista 5 | # Based on http://www.infragistics.com/community/blogs/torrey-betts/archive/2013/10/10/scanning-barcodes-with-ios-7-objective-c.aspx 6 | 7 | from objc_util import * 8 | from ctypes import c_void_p 9 | import ui 10 | import sound 11 | 12 | found_codes = set() 13 | main_view = None 14 | 15 | AVCaptureSession = ObjCClass('AVCaptureSession') 16 | AVCaptureDevice = ObjCClass('AVCaptureDevice') 17 | AVCaptureDeviceInput = ObjCClass('AVCaptureDeviceInput') 18 | AVCaptureMetadataOutput = ObjCClass('AVCaptureMetadataOutput') 19 | AVCaptureVideoPreviewLayer = ObjCClass('AVCaptureVideoPreviewLayer') 20 | dispatch_get_current_queue = c.dispatch_get_current_queue 21 | dispatch_get_current_queue.restype = c_void_p 22 | 23 | def captureOutput_didOutputMetadataObjects_fromConnection_(_self, _cmd, _output, _metadata_objects, _conn): 24 | objects = ObjCInstance(_metadata_objects) 25 | for obj in objects: 26 | s = str(obj.stringValue()) 27 | if s not in found_codes: 28 | found_codes.add(s) 29 | sound.play_effect('digital:PowerUp7') 30 | main_view['label'].text = 'Last scan: ' + s 31 | 32 | MetadataDelegate = create_objc_class('MetadataDelegate', methods=[captureOutput_didOutputMetadataObjects_fromConnection_], protocols=['AVCaptureMetadataOutputObjectsDelegate']) 33 | 34 | @on_main_thread 35 | def main(): 36 | global main_view 37 | delegate = MetadataDelegate.new() 38 | main_view = ui.View(frame=(0, 0, 400, 400)) 39 | main_view.name = 'Barcode Scanner' 40 | session = AVCaptureSession.alloc().init() 41 | device = AVCaptureDevice.defaultDeviceWithMediaType_('vide') 42 | _input = AVCaptureDeviceInput.deviceInputWithDevice_error_(device, None) 43 | if _input: 44 | session.addInput_(_input) 45 | else: 46 | print('Failed to create input') 47 | return 48 | output = AVCaptureMetadataOutput.alloc().init() 49 | queue = ObjCInstance(dispatch_get_current_queue()) 50 | output.setMetadataObjectsDelegate_queue_(delegate, queue) 51 | session.addOutput_(output) 52 | output.setMetadataObjectTypes_(output.availableMetadataObjectTypes()) 53 | prev_layer = AVCaptureVideoPreviewLayer.layerWithSession_(session) 54 | prev_layer.frame = ObjCInstance(main_view).bounds() 55 | prev_layer.setVideoGravity_('AVLayerVideoGravityResizeAspectFill') 56 | ObjCInstance(main_view).layer().addSublayer_(prev_layer) 57 | label = ui.Label(frame=(0, 0, 400, 30), flex='W', name='label') 58 | label.background_color = (0, 0, 0, 0.5) 59 | label.text_color = 'white' 60 | label.text = 'Nothing scanned yet' 61 | label.alignment = ui.ALIGN_CENTER 62 | main_view.add_subview(label) 63 | session.startRunning() 64 | main_view.present('sheet') 65 | main_view.wait_modal() 66 | session.stopRunning() 67 | delegate.release() 68 | session.release() 69 | output.release() 70 | if found_codes: 71 | print 'All scanned codes:\n' + '\n'.join(found_codes) 72 | 73 | if __name__ == '__main__': 74 | main() 75 | -------------------------------------------------------------------------------- /comment_code.py: -------------------------------------------------------------------------------- 1 | ##Comment/Uncomment selected lines 2 | 3 | import editor 4 | 5 | text = editor.get_text() 6 | selection = editor.get_line_selection() 7 | selected_text = text[selection[0]:selection[1]] 8 | is_comment = selected_text.strip().startswith('# ') 9 | replacement = '' 10 | for line in selected_text.splitlines(): 11 | if is_comment: 12 | if line.strip().startswith('# '): 13 | replacement += line[line.find('# ') + 2:] + '\n' 14 | elif line.strip().startswith('#'): 15 | replacement += line[line.find('#') + 1:] + '\n' 16 | else: 17 | replacement += line + '\n' 18 | else: 19 | replacement += '# ' + line + '\n' 20 | 21 | editor.replace_text(selection[0], selection[1], replacement) 22 | editor.set_selection(selection[0], selection[0] + len(replacement) - 1) 23 | -------------------------------------------------------------------------------- /convert_pyui_json.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## Convert a json file into a pyui file and vice versa 3 | 4 | import editor, os 5 | 6 | extensions = ('json', 'pyui') 7 | 8 | def pyui2json2pyui(): 9 | target_file = editor.get_path() 10 | filename = os.path.basename(target_file) 11 | fname = os.path.splitext(filename)[0] 12 | rootpath, extension = os.path.splitext(target_file) 13 | assert extension.lstrip(".") in extensions, "'{}' must be in {}".format(extension.lstrip("."), extensions) 14 | other_extension = [ext for ext in extensions if ext != extension.lstrip(".")][0] 15 | destination_file = rootpath + "." + other_extension 16 | os.rename(target_file, destination_file) 17 | fmt = '{} was renamed to {}' 18 | print fmt.format(filename, os.path.basename(destination_file)) 19 | 20 | pyui2json2pyui() 21 | -------------------------------------------------------------------------------- /copy_filename.py: -------------------------------------------------------------------------------- 1 | import clipboard, editor, console, os 2 | 3 | def main(): 4 | try: 5 | choice = console.alert("Copy what?", "Choose...", "File (full)", "File (~/Documents)", "Dir") 6 | except: 7 | choice = 1 8 | fn = editor.get_path() 9 | fn = fn[fn.find("/Documents"):] if choice == 2 else os.path.split(fn)[0][8:] if choice == 3 else fn[8:] 10 | clipboard.set(fn) 11 | 12 | if __name__ == "__main__": 13 | main() 14 | -------------------------------------------------------------------------------- /dl.py: -------------------------------------------------------------------------------- 1 | ## Downloadista, modified so that github_download() accepts: 2 | ## * clipboard GitHub URL 3 | ## * GitHub URL 4 | ## * "user/repo" 5 | 6 | 7 | from __future__ import absolute_import 8 | from __future__ import print_function 9 | import zipfile 10 | import tarfile 11 | import shutil 12 | import os, sys, io 13 | import re 14 | import clipboard 15 | 16 | from os import path 17 | 18 | try: 19 | from urllib.request import urlretrieve 20 | except: 21 | from urllib import urlretrieve 22 | 23 | DOCUMENTS = os.path.expanduser("~/Documents") 24 | 25 | REGEX_GITHUB_URL = re.compile(r'''^http(s?)://([\w-]*\.)?github\.com/(?P[\w-]+)/(?P[\w-]*)((/tree|/blob)/(?P[\w-]*))?''') 26 | 27 | 28 | def extract_git_id(giturl): 29 | m = REGEX_GITHUB_URL.match(giturl) 30 | #print m.groupdict() #{'repo': 'pybitcointools', 'user': 'wizardofozzie', 'branch': None} 31 | return m 32 | 33 | def _change_dir(dir="Documents"): 34 | pwd = os.getcwd() 35 | if pwd != DOCUMENTS: 36 | os.chdir(os.path.expanduser("~/{0}".format(dir))) 37 | 38 | 39 | def is_github_url(url): 40 | return bool(REGEX_GITHUB_URL.match(url)) 41 | 42 | 43 | def _decode_github_url(url): 44 | if is_github_url(url): 45 | ret = extract_git_id(url) 46 | gd = ret.groupdict() 47 | user, repo = gd.get("user"), gd.get("repo") 48 | branch = gd.get("branch") 49 | branch = "master" if branch is None else branch 50 | return (user, repo, branch) 51 | 52 | 53 | 54 | def github_download(*args): 55 | branch = None 56 | if len(args) == 0: 57 | return github_download(clipboard.get()) 58 | elif len(args) ==1 and is_github_url(args[0]): 59 | user, repo, branch = _decode_github_url(args[0]) 60 | elif (len(args) == 1 and re.match(r'^[0-9a-zA-Z]*/[0-9a-zA-Z]*$', str(args[0]))): 61 | user, repo = args[0].split("/", 1) 62 | branch = "master" 63 | elif len(args)==2: 64 | user, repo = args 65 | branch = "master" 66 | elif len(args) == 3: 67 | user, repo, branch = args 68 | else: return 69 | branch = "master" if not branch else branch 70 | 71 | _change_dir("Documents") 72 | print(('Downloading {0}...'.format(repo))) 73 | base_url = 'https://github.com/{0}/{1}/archive/{2}.zip' 74 | url = base_url.format(user, repo, branch) 75 | zipname = '{0}.zip'.format(repo) 76 | urlretrieve(url, zipname) 77 | with zipfile.ZipFile(open(zipname, "rb")) as zip_file: 78 | print('Extracting...') 79 | print('\n'.join(name for name in zip_file.namelist())) 80 | zip_file.extractall() 81 | 82 | dst = os.path.join(DOCUMENTS, zipname[:-len(".zip")]) 83 | src = "{dir}-{branch}".format(dir=dst, branch=branch) 84 | os.remove(zipname) 85 | try: 86 | os.rename(src, dst) 87 | except OSError: 88 | os.rename(dst, "{}.BAK".format(dst)) 89 | os.rename(src, dst) 90 | print('Done.') 91 | 92 | # If branch is a version tag the directory 93 | # is slightly different 94 | #if re.match('^v[0-9.]*$', branch): 95 | # dirname = repo + '-' + branch[1:] 96 | #else: 97 | # dirname = repo + '-' + branch 98 | 99 | return os.path.basename(dst) 100 | 101 | 102 | gdl = github_download 103 | 104 | def pypi_download(package, version): 105 | _change_dir("Documents") 106 | print(('Downloading {0}...'.format(package))) 107 | url = 'https://pypi.python.org/packages/source/{0}/{1}/{1}-{2}.tar.gz'.format(package[0], package, version) 108 | tarname = package + '.tar.gz' 109 | urlretrieve(url, tarname) 110 | 111 | print('Extracting...') 112 | t = tarfile.open(tarname) 113 | t.extractall() 114 | os.remove(tarname) 115 | print('Done.') 116 | 117 | dirname = package + '-' + str(version) 118 | return dirname 119 | 120 | if __name__ == "__main__": 121 | github_download() 122 | -------------------------------------------------------------------------------- /gamefaqs_dl.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## Download a GameFaqs.com FAQ in printable text format 3 | ## 4 | ## 5 | 6 | import os, sys, re, random, appex, console, clipboard, html2text, requests 7 | 8 | 9 | RE_URL = re.compile(ur'^http(s)?://(www\.)?gamefaqs\.com/.*/faqs/[0-9]{3,8}$', re.IGNORECASE) 10 | 11 | 12 | def main(): 13 | if appex.is_running_extension(): 14 | url = appex.get_url() 15 | else: 16 | url = clipboard.get().strip() 17 | if not RE_URL.match(url): 18 | try: 19 | url = console.input_alert("Enter gamefaqs URL", "", "https://www.gamefaqs.com/") 20 | except KeyboardInterrupt: 21 | sys.exit(0) 22 | 23 | newurl = "{0}?print=1".format(url) 24 | #baseurl = http://www.gamefaqs.com/ps3/959558-fallout-new-vegas/faqs/61226 25 | if RE_URL.match(url): 26 | h = html2text.HTML2Text() 27 | r = requests.get( 28 | url=newurl, 29 | headers={"User-agent": "Mozilla/5.0{0:06}".format(random.randrange(999999))} 30 | ) 31 | html_content = r.text.decode('utf-8') 32 | rendered_content = html2text.html2text(html_content) 33 | filename = url.partition("gamefaqs.com/")[-1].partition("/")[-1].partition("/faqs")[0]+".txt" 34 | filepath = os.path.join(os.path.expanduser("~/Documents"), filename) 35 | 36 | with open(filepath, "w") as fo: 37 | fo.write(rendered_content) 38 | 39 | console.hud_alert('Success! Saved {0}'.format(filename), "success") 40 | 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /get_img_index.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## get index of image for filename ____.jpg 3 | ## see https://forum.omz-software.com/topic/3035/pick_image-index-of-picked-image 4 | 5 | import photos 6 | 7 | def get_img_index(filename): 8 | c = photos.get_count() 9 | for i in range(c): 10 | m = photos.get_metadata(i) 11 | if m.get('filename') == filename: 12 | return i 13 | 14 | img = photos.pick_image( 15 | show_albums=True, 16 | include_metadata=True, 17 | original=True, 18 | raw_data=False, 19 | multi=False 20 | ) 21 | 22 | filename = img[1].get('filename') 23 | i = get_img_index(filename) 24 | print filename + ' = ' + str(i) 25 | -------------------------------------------------------------------------------- /getpwd.py: -------------------------------------------------------------------------------- 1 | ##Fetch password from keychain with touchid authentication 2 | 3 | import keychain, sys 4 | try: 5 | from touchid import authenticate 6 | except ImportError: 7 | Exception("Couldn't load touchid module") 8 | 9 | 10 | def get_apikey(service="", account=""): 11 | assert service is not None and account is not None 12 | d = {} 13 | d.update(keychain.get_services()) 14 | if service in d: 15 | assert d.get(service, "") == account 16 | PWD = keychain.get_password(service, account) 17 | touchid_status = 0 18 | touchid_status = authenticate("Authenticate API lookup", allow_passcode=True, timeout=10) 19 | if touchid_status: 20 | return PWD 21 | else: 22 | sys.stderr.write("Bad fingerprint!") 23 | else: 24 | raise Exception("API key not found") 25 | -------------------------------------------------------------------------------- /img_paste_horiz.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## Paste chosen/shared images horizontally into a new image, then save and show new image 3 | 4 | import Image, photos, dialogs, sys, appex 5 | 6 | 7 | images = [] 8 | 9 | if appex.is_running_extension(): 10 | images = appex.get_images() 11 | else: 12 | while True: 13 | im = photos.pick_image() 14 | if im is None: 15 | break 16 | images.append(im) 17 | 18 | 19 | number_of_images = len(images) 20 | assert 1 <= number_of_images <= 50 21 | 22 | 23 | widths, heights = zip(*(j.size for j in images)) 24 | 25 | 26 | total_width = sum(widths) 27 | max_height = max(heights) 28 | 29 | new_image = Image.new("RGB", (total_width, max_height)) 30 | 31 | x_offset = 0 32 | for im in images: 33 | new_image.paste(im, (x_offset, 0)) 34 | x_offset += im.size[0] 35 | 36 | 37 | try: 38 | photos.save_image(new_image) 39 | dialogs.hud_alert("Saved!") 40 | new_image.show() 41 | except: 42 | dialogs.hud_alert("could not save file!".title(), "error") 43 | sys.exit(0) 44 | -------------------------------------------------------------------------------- /img_resize.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## Resize an image (either from external apps' share-sheet, or via photo-roll) 3 | import photos, appex, sys, console 4 | 5 | 6 | if appex.is_running_extension(): 7 | im = appex.get_image() 8 | else: 9 | im = photos.pick_image() 10 | 11 | if im is None: 12 | console.hud_alert("No image chosen", "error", 1.0) 13 | sys.exit(0) 14 | else: 15 | assert str(type(im))[8:-2][4:].partition("ImagePlugin")[0] in ("Bmp", "Gif", "Jpeg", "Png", "Ppm", "Tiff") 16 | 17 | width, height = im.size 18 | percentage = int(console.input_alert("Resize image to _%", "Enter number")) 19 | fraction = percentage / 100.0 20 | new_width = int(round(width*float(fraction))) 21 | new_height = int(round(height*float(fraction))) 22 | im2 = im.resize((new_width, new_height)) 23 | saved = photos.save_image(im2) 24 | 25 | 26 | if saved: 27 | console.hud_alert("Successfully saved resized image ({0}%)".format(int(percentage))) 28 | else: 29 | console.hud_alert("Unsuccessfully saved resized image ({0}%)".format(int(percentage)), "error") 30 | -------------------------------------------------------------------------------- /img_rotate.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## Rotate an image (either from external apps' share-sheet, or via photo-roll) 3 | import photos, appex, sys, console, Image 4 | 5 | 6 | if appex.is_running_extension(): 7 | im = appex.get_image() 8 | else: 9 | im = photos.pick_image() 10 | 11 | if im is None: 12 | console.hud_alert("No image chosen", "error", 1.0) 13 | sys.exit(0) 14 | else: 15 | assert str(type(im))[8:-2][4:].partition("ImagePlugin")[0] in ("Bmp", "Gif", "Jpeg", "Png", "Ppm", "Tiff") 16 | keep_original = console.alert("Rotate original image?", "Rotate & copy?", \ 17 | "Rotate Original", "Rotate Copy", hide_cancel_button=True) 18 | if keep_original == 2: 19 | im2 = im.copy() 20 | else: 21 | im2 = im 22 | 23 | degrees = int(console.input_alert("Rotate image __ degrees", "Use multiples of 90")) 24 | 25 | assert divmod(abs(degrees), 360)[1] in (0, 90, 180, 270) 26 | 27 | deg = (degrees % 360) if not (0 < degrees < 360) else degrees 28 | img = im2.rotate(deg) 29 | 30 | saved = photos.save_image(img) 31 | 32 | if saved: 33 | console.hud_alert("Successfully saved rotated image") 34 | img.show() 35 | else: 36 | console.hud_alert("Successfully saved rotated image", "error") 37 | -------------------------------------------------------------------------------- /launch_startup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os, sys, console 3 | import clipboard as cl 4 | 5 | clset, clget = cl.set, cl.get 6 | h = help 7 | 8 | 9 | PYFW = os.path.join(sys.executable.rpartition("/")[0], "Frameworks/PythonistaKit.framework") 10 | PYLIB = os.path.join(PYFW, "pylib") 11 | HOME1 = os.path.expanduser("~") 12 | HOME2 = DOCS = os.path.join(HOME1, "Documents") 13 | PYBTC = os.path.join(HOME2, "pybitcointools-master") # os.path.expanduser("~/Documents/pybitcointools-master") 14 | 15 | # PYBTC_source = os.path.expanduser("~/Documents/pybitcointools-master/bitcoin") 16 | # PYBTC_dest = os.path.expanduser("~/Documents/site-packages/bitcoin") 17 | 18 | #from pythonista_startup 19 | sys.path.insert(0, PYBTC) 20 | 21 | # try: 22 | # from bitcoin import * 23 | # console.hud_alert("pybitcointools successfully imported!".title(), "success", 1.42) 24 | # except ImportError: 25 | # console.hud_alert("Unable to import... pybitcointools".title(), "error", 1.42) 26 | 27 | def to_bytes(x): 28 | if sys.version_info.major > 2 and isinstance(x, str): 29 | x = bytes(x, 'utf-8') 30 | return x 31 | 32 | s2b = str2bytes = to_bytes 33 | 34 | 35 | def from_bytes(x): 36 | if sys.version_info.major > 2 and isinstance(x, bytes): 37 | x = str(x, 'utf-8') 38 | return x 39 | 40 | b2s = bytes2str = from_bytes 41 | 42 | try: 43 | import cd_ls_pwd # import the three functions 44 | cd = cd_ls_pwd.cd # send up a top-level alias 45 | ls = cd_ls_pwd.ls # send up a top-level alias 46 | pwd = cd_ls_pwd.pwd # send up a top-level alias 47 | except ImportError: 48 | pass 49 | -------------------------------------------------------------------------------- /mk_entropy.py: -------------------------------------------------------------------------------- 1 | import ui 2 | import hashlib 3 | import clipboard 4 | 5 | ## see https://forum.omz-software.com/topic/3186/entropy-builder-finger-dragging-ui 6 | 7 | 8 | class TouchHash(ui.View): 9 | def __init__(self): 10 | self.flex = 'WH' 11 | self.name = 'Swipe/Touch around to generate a hash' 12 | self.hash = hashlib.sha256() 13 | self.count = 0 14 | self.textview = ui.TextView() 15 | self.textview.touch_enabled = False 16 | self.textview.editable = False 17 | self.textview.flex = 'WH' 18 | self.add_subview(self.textview) 19 | self.present() 20 | 21 | def do_hash_generation(self, location, prev_location, timestamp): 22 | if self.count < 999: 23 | self.hash.update('{}{}{}{:15f}'.format(location[0],location[1],prev_location,timestamp)) 24 | self.count += 3 25 | self.name = str( 26 | float(self.count)/10 27 | ) + '% complete' 28 | self.textview.text = 'Hash: ' + self.hash.hexdigest() #show the text in the textview 29 | elif self.count == 999: 30 | self.name = str(100.0) + '% complete' 31 | print self.hash.hexdigest() 32 | clipboard.set(self.hash.hexdigest()) 33 | self.close() #close the view 34 | 35 | def touch_began(self, touch): 36 | self.do_hash_generation(touch.location, touch.prev_location, touch.timestamp) 37 | 38 | def touch_moved(self, touch): 39 | self.do_hash_generation(touch.location, touch.prev_location, touch.timestamp) 40 | 41 | def touch_ended(self, touch): 42 | #do nothing so that user can touch random spots 43 | pass 44 | 45 | hash = TouchHash() 46 | -------------------------------------------------------------------------------- /move_vids.py: -------------------------------------------------------------------------------- 1 | ## Move video files downloaded with youtube-dl from Documents => Documents/VIDEO 2 | 3 | import os 4 | import os.path as path 5 | 6 | VIDEO_EXTENSIONS = [ 7 | ".mp4", ".m4a", ".webm" 8 | ] 9 | 10 | 11 | def get_vid_names(dir="~/Documents", fullpath=False): 12 | assert path.isdir(path.expanduser(dir)) 13 | filez = [f for f in os.listdir(path.expanduser(dir)) if path.isfile(f)] 14 | ret = [x for x in filez if path.splitext(x)[-1] in VIDEO_EXTENSIONS] 15 | return ret if not fullpath else map(lambda x: path.join(path.expanduser(dir), x)[8:], ret) 16 | 17 | 18 | def move_vids(fromdir='~/Documents', todir='~/Documents/VIDEO'): 19 | os.chdir(path.expanduser(fromdir)) 20 | vidlist = get_vid_names(fromdir) 21 | for f in vidlist: 22 | try: 23 | os.rename( 24 | path.join(path.expanduser(fromdir), f)[8:], 25 | path.join(path.expanduser(todir), f)[8:] 26 | ) 27 | print("==> {0}/{1}".format(todir, f)) 28 | except: 29 | print("Could not move file:\t {fn}".format(fn=f)) 30 | 31 | 32 | if __name__ == "__main__": 33 | move_vids() 34 | -------------------------------------------------------------------------------- /qrread.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## Decode QR Code from an image using goqr.me api (from a screenshot, cropped or uncropped) 3 | ## 4 | 5 | import sys, os 6 | import requests, photos, editor, console, dialogs, clipboard, appex 7 | from urllib import quote 8 | 9 | from io import BytesIO 10 | from urlparse import urljoin 11 | 12 | GOQRME_BASEURL = "http://api.qrserver.com/v1/" 13 | 14 | BYTES_PER_KB = 1<<20 15 | 16 | def get_image_bytes(index=None): 17 | '''Pick photo from camera roll (or specify by by index number) and return its bytes in a io.BytesIO object''' 18 | #assert isinstance(index, (int, None)) or (isinstance(index, basestring) and str(index).isdigit()) 19 | if index is None: 20 | im = photos.pick_image(show_albums=False, include_metadata=False, \ 21 | original=True, raw_data=True, multi=False) 22 | else: 23 | im = photos.get_image(int(index), original=True, raw_data=True) 24 | # check image size < 1Mb 25 | filesizeb = len(im) 26 | filesizekb = round(float(filesizeb) / (BYTES_PER_KB), 3) 27 | if filesizeb > 1000: 28 | raise ValueError("Image size exceeds 1Mb limit by {} bytes".format(round(filesizekb-1000.0, 2))) 29 | b = BytesIO() 30 | b.write(im) 31 | return b 32 | 33 | 34 | 35 | def main(): 36 | # TODO 37 | if appex.is_running_extension(): 38 | if appex.get_text(): 39 | # check if it's an image file, pass bytes 40 | pass 41 | elif appex.get_image_data(): 42 | # check len(imgdata)>0 and PNG 43 | 44 | elif appex.get_url(): 45 | # check url works and it's to an img 46 | pass 47 | read_qrcode_file_url = urljoin(GOQRME_BASEURL, "/v1/read-qr-code/").rstrip("/") + "/" 48 | filebytes = get_image_bytes().getvalue() #if not appex.is_running_extension() else appex.get_image_data() 49 | 50 | r = requests.post(read_qrcode_file_url, files={"file": filebytes }) 51 | 52 | r.raise_for_status() 53 | 54 | result = r.json() 55 | symbol = result[0]["symbol"] 56 | error_reason = symbol[0]["error"] 57 | if error_reason is not None: 58 | sys.stderr.write("QR code reading error: {0}".format(error_reason)) 59 | return None 60 | else: 61 | data = symbol[0]["data"] 62 | return data 63 | 64 | if __name__ == "__main__": 65 | main() 66 | 67 | 68 | 69 | 70 | ### creating a qr code 71 | 72 | #baseurl = "https://api.qrserver.com/v1/create-qr-code/?data={}&size={}&charset-source={}&charset_target={}&ecc={}&color={}&bgcolor={}&margin={}&qzone={}&format={}" 73 | 74 | # kwargz = { 75 | # 'size': '200x200', 76 | # 'charset_source': 'UTF-8', 77 | # 'charset_target': 'UTF-8', 78 | # 'ecc': 'L', 79 | # 'color': '0-0-0', 80 | # 'bgcolor': '255-255-255', 81 | # 'margin': '1', 82 | # 'qzone': '0', 83 | # 'fmt': 'png' 84 | # } 85 | -------------------------------------------------------------------------------- /save_to_pythonista.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## import a file to Pythonista via share-sheet 3 | 4 | import appex, os, shutil, sys 5 | from urlparse import urlparse 6 | from urllib import url2pathname 7 | 8 | if not appex.is_running_extension(): 9 | sys.stderr.write("Must run from share sheet") 10 | sys.exit(1) 11 | else: 12 | file_url = appex.get_url() 13 | file_url = file_url[len("file://"):] if file_url.startswith("file://") else file_url 14 | p = urlparse(file_url) # for % encoded URLs 15 | filepath = url2pathname(p.path) 16 | name = os.path.basename(filepath) 17 | docsdir = os.path.expanduser("~/Documents") 18 | dest = os.path.join(docsdir, name) 19 | try: 20 | shutil.copy(filepath, dest) 21 | print "Success! Copied {0} to {1}".format(filename, filepath) 22 | except: 23 | sys.stderr.write("Could not copy {0} from {1}".format(filename, filepath)) 24 | -------------------------------------------------------------------------------- /telnet_starwars.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | STARWARS! 4 | ''' 5 | import telnetlib 6 | import ui 7 | 8 | HOST = "towel.blinkenlights.nl" 9 | playing = False 10 | 11 | def button_pressed(sender): 12 | global playing 13 | if sender.title == "Play": 14 | sender.title = 'Pause' 15 | playing = True 16 | else: 17 | sender.title = 'Play' 18 | playing = False 19 | 20 | 21 | tn = telnetlib.Telnet(HOST) 22 | #view 23 | view = ui.View() 24 | view.background_color = (0,0,0) 25 | view.frame = (0,0,800,800) 26 | #textview 27 | tv = ui.TextView() 28 | tv.font = ('Courier',17) 29 | tv.flex = 'LRTB' 30 | tv.text_color = (1,1,1) 31 | tv.name = 'textview1' 32 | tv.frame = (0, 60, 800, 400) 33 | tv.background_color = (0,0,0) 34 | view.add_subview(tv) 35 | #button 36 | btn = ui.Button() 37 | btn.name = 'button1' 38 | btn.title = 'Play' 39 | btn.action = button_pressed 40 | btn.tint_color = (1,1,1) 41 | btn.flex = 'LR' 42 | btn.frame = (359, 6, 80, 32) 43 | view.add_subview(btn) 44 | 45 | view.present('fullscreen') 46 | tn.read_until('\r\n') 47 | 48 | while True: 49 | if playing: 50 | txt = '' 51 | lines = tn.read_until('[H').replace('[H','') 52 | view['textview1'].text = lines 53 | -------------------------------------------------------------------------------- /touchid_secure.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # from @omz https://gist.github.com/omz/66a763a9db15dc847690 3 | 4 | from objc_util import * 5 | import threading 6 | 7 | NSBundle = ObjCClass('NSBundle') 8 | LocalAuthentication = NSBundle.bundleWithPath_('/System/Library/Frameworks/LocalAuthentication.framework') 9 | LocalAuthentication.load() 10 | LAContext = ObjCClass('LAContext') 11 | 12 | # authenticate() will raise one of these exceptions when authentication 13 | # fails. They all derive from AuthFailedException, so you can catch that 14 | # if you don't care about the failure reason, but you could also handle 15 | # cancellation differently, for example. 16 | class AuthFailedException (Exception): pass 17 | class AuthCancelledException (AuthFailedException): pass 18 | class AuthTimeoutException (AuthFailedException): pass 19 | class AuthNotAvailableException (AuthFailedException): pass 20 | class AuthFallbackMechanismSelectedException (AuthFailedException): pass 21 | 22 | def is_available(): 23 | '''Return True if TouchID authentication is available, False otherwise''' 24 | context = LAContext.new().autorelease() 25 | return bool(context.canEvaluatePolicy_error_(1, None)) 26 | 27 | def authenticate(reason='', allow_passcode=True, timeout=None): 28 | '''Authenticate the user via TouchID or passcode. Returns True on success, raises AuthFailedException (or a subclass) otherwise.''' 29 | if not is_available(): 30 | raise AuthNotAvailableException('Touch ID is not available.') 31 | policy = 2 if allow_passcode else 1 32 | context = LAContext.new().autorelease() 33 | event = threading.Event() 34 | result = {} 35 | def callback(_cmd, success, _error): 36 | result['success'] = success 37 | if _error: 38 | error = ObjCInstance(_error) 39 | result['error'] = error 40 | event.set() 41 | handler = ObjCBlock(callback, restype=None, argtypes=[c_void_p, c_bool, c_void_p]) 42 | context.evaluatePolicy_localizedReason_reply_(policy, reason, handler) 43 | if not event.wait(timeout): 44 | #NOTE: invalidate() is a private method (there's apparently no public API to cancel the TouchID dialog) 45 | context.invalidate() 46 | raise AuthTimeoutException('Timeout') 47 | success = result.get('success', False) 48 | error = result.get('error') 49 | if success: 50 | return True 51 | elif error: 52 | error_code = error.code() 53 | if error_code == -2: 54 | raise AuthCancelledException('Cancelled by user') 55 | elif error_code == -3: 56 | raise AuthFallbackMechanismSelectedException('Fallback authentication mechanism selected') 57 | else: 58 | desc = error.localizedDescription() or 'Unknown error' 59 | raise AuthFailedException(desc) 60 | else: 61 | raise AuthFailedException('Unknown error') 62 | 63 | # Demo: 64 | def main(): 65 | try: 66 | reason = 'We need you fingerprint to ste...ehm... to log you in. You have 10 seconds.' 67 | authenticate(reason, allow_passcode=True, timeout=10) 68 | print('Success!') 69 | except AuthFailedException as e: 70 | print(e) 71 | 72 | if __name__ == '__main__': 73 | main() 74 | -------------------------------------------------------------------------------- /webopen.py: -------------------------------------------------------------------------------- 1 | ##Takes URL or clipboard's URL and opens link externally in Safari 2 | 3 | import dialogs, clipboard, webbrowser 4 | 5 | 6 | def webopen(url=None): 7 | base_url = "safari-{0}" 8 | cburl = clipboard.get().encode() 9 | if not cburl.startswith("http"): 10 | cburl = dialogs.input_alert("URL?") 11 | url = base_url.format(cburl) if not url else base_url.format(url) 12 | webbrowser.open(url) 13 | -------------------------------------------------------------------------------- /youtube_time_url.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | ## Takes a youtube URL and time code and copies time-coded URL to clipboard 3 | import appex, requests, clipboard, console, dialogs, collections, sys 4 | 5 | # Thanks to @cclauss 6 | # from https://github.com/cclauss/Ten-lines-or-less/blob/master/form_dialog_from_fields_dict.py 7 | 8 | def form_dialog_from_fields_dict(title, fields_dict): 9 | return dialogs.form_dialog(title, [{'title': k, 'type': v} for k, v in fields_dict.items()]) 10 | 11 | 12 | my_fields_dict1 = collections.OrderedDict(( 13 | ('URL', 'url'), ('Time', 'text') 14 | )) 15 | 16 | my_fields_dict2 = collections.OrderedDict(( 17 | ('Time', 'text'), 18 | )) 19 | 20 | if appex.is_running_extension(): 21 | url = appex.get_url() 22 | d = form_dialog_from_fields_dict("Enter timestamp as mm:ss", my_fields_dict2) 23 | if d is None: sys.exit() 24 | ts = d['Time'] 25 | else: 26 | d = form_dialog_from_fields_dict("Enter YouTube URL and time (mm:ss)", my_fields_dict1) 27 | if d is None: sys.exit() 28 | url = d['URL'] 29 | ts = d['Time'] 30 | 31 | 32 | def main(): 33 | assert any([x in url for x in ("youtube", "youtu.be")]), "{0} is not a YouTube URL!".format(url) 34 | assert ":" in ts, "timestamp must be written as (hh:)mm:ss" 35 | 36 | if ts.count(":") == 1: 37 | mins, secs = map(int, ts.split(":")) 38 | hrs = 0 39 | elif ts.count(":") == 2: 40 | hrs, mins, secs = map(int, ts.split(":")) 41 | else: 42 | sys.stderr.write("Bad timestamp (too many ':')") 43 | sys.exit(0) 44 | 45 | seconds = hrs*(60**2) + mins*(60**1) + secs*(60**0) 46 | newurl = "{url}?t={seconds}".format(url=url, seconds=seconds) 47 | 48 | clipboard.set(newurl) 49 | dialogs.hud_alert("{0} copied to clipboard".format(newurl)) 50 | return newurl 51 | 52 | main() 53 | -------------------------------------------------------------------------------- /ytdl.py: -------------------------------------------------------------------------------- 1 | import youtubedl as ytdl 2 | import clipboard 3 | 4 | with ytdl.YoutubeDL({}) as ydl: 5 | ydl.download([clipboard.get()]) 6 | 7 | try: 8 | from move_vids import move_vids 9 | move_vids() 10 | print("Done!") 11 | except ImportError: 12 | pass 13 | --------------------------------------------------------------------------------