├── .gitignore ├── ruby_version.alfredworkflow ├── Pocket workflow.alfredworkflow ├── update.json ├── pocket.json ├── resp.html ├── info.plist ├── README.md ├── LICENSE-MIT ├── server.py ├── pocket.py ├── alfred.py └── actions.py /.gitignore: -------------------------------------------------------------------------------- 1 | auth.json 2 | *.pyc 3 | -------------------------------------------------------------------------------- /ruby_version.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/pocket_alfred/master/ruby_version.alfredworkflow -------------------------------------------------------------------------------- /Pocket workflow.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/pocket_alfred/master/Pocket workflow.alfredworkflow -------------------------------------------------------------------------------- /update.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2.0, 3 | "remote_json": "https://raw.github.com/altryne/pocket_alfred/master/pocket.json" 4 | } -------------------------------------------------------------------------------- /pocket.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2.0, 3 | "download_url": "https://github.com/altryne/pocket_alfred/raw/master/Pocket%20workflow.alfredworkflow", 4 | "description": "Rewrote the whole thing in python" 5 | } -------------------------------------------------------------------------------- /resp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Path for alfred 6 | 9 | 10 | 11 |

Pocket connection succesfull. This window will now close...

12 | 13 | -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | nikipore.alfredpython 7 | createdby 8 | Jan Müller 9 | description 10 | Python library for Alfred workflow API 11 | name 12 | alfred-python 13 | webaddress 14 | https://github.com/nikipore/alfred-python 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Pocket for alfred 2 | 3 | This workflow uses oAuth to authenticate you with Pocket's API and **doesn't** store any passwords or keys 4 | 5 | [Download the latest build](https://drone.io/github.com/altryne/pocket_alfred/files/pocket_alfred.alfredworkflow) [![Build Status](https://drone.io/github.com/altryne/pocket_alfred/status.png)](https://drone.io/github.com/altryne/pocket_alfred/latest) 6 | 7 | # 8 | 9 | ##Usage : 10 | 11 | First login with alfred using **"pocket_login"** workflow 12 | 13 | Then, use "pocket" to save urls to pocket 14 | 15 | ##Support for : 16 | * Chrome (if open) 17 | * Safari (if open) 18 | * Clipboard (if has url) 19 | 20 | 21 | ## Copyright & License 22 | 23 | Copyright 2014 Alex Wolkov 24 | 25 | [MIT License](LICENSE-MIT) 26 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Alex Wolkov 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import SimpleHTTPServer 2 | import SocketServer 3 | # Server port 4 | import os 5 | import signal 6 | import sys 7 | import pocket 8 | 9 | 10 | logger = pocket.logger 11 | 12 | PORT = 2222 13 | 14 | class ServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 15 | def do_GET(self): 16 | logger.info("received a connection") 17 | pocket.getAuthToken() 18 | logger.info('Stopping servers') 19 | SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) 20 | sys.stdout.write("Welcome!") 21 | httpd.socket.close() 22 | logger.info('Stopping servers 2') 23 | processes = os.popen("ps -eo pid,command | grep server.py | grep -v grep | awk '{print $1}'").read().splitlines() 24 | logger.info('Processes pids : '+ " ".join(processes)) 25 | for pid in processes: 26 | logger.info('Trying to stop proccess with the id ' + pid) 27 | cmd = os.kill(int(pid),signal.SIGTERM) 28 | logger.info('Success shutting down proccess') 29 | 30 | # Initialize server object 31 | httpd = SocketServer.TCPServer(("", PORT), ServerHandler) 32 | #print "serving at port", PORT 33 | logger.info("Server started at 2222") 34 | httpd.serve_forever() -------------------------------------------------------------------------------- /pocket.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import urllib2 4 | 5 | logger = logging.getLogger('alfred.fundbox') 6 | hdlr = logging.FileHandler('/var/tmp/alfred.fundbox.log') 7 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') 8 | hdlr.setFormatter(formatter) 9 | logger.addHandler(hdlr) 10 | logger.setLevel(logging.INFO) 11 | 12 | CONSUMER_KEY = '12483-e2a74088174a17f2872c4d82' 13 | REDIRECT_URI = 'http://alexw.me' 14 | POCKET_API_URL = 'https://getpocket.com/v3/oauth/' 15 | 16 | 17 | def getRequestCode(): 18 | logger.info("Trying to get request code") 19 | req_data = json.dumps({ 20 | "consumer_key": CONSUMER_KEY, "redirect_uri": REDIRECT_URI 21 | }) 22 | resp_data = makeRequest(req_data, POCKET_API_URL + 'request/') 23 | logger.info("got response data : " + str(resp_data)) 24 | return resp_data 25 | 26 | def getAuthToken(): 27 | try: 28 | code = json.loads(open('code.json').read())["code"] 29 | except: 30 | print "Please try to login first with pocket_login" 31 | logger.info("request code is" + code) 32 | req_data = json.dumps({ 33 | "consumer_key": CONSUMER_KEY, "code": code 34 | }) 35 | logger.info("Trying to get auth token") 36 | try: 37 | resp_data = makeRequest(req_data, POCKET_API_URL + 'authorize/') 38 | logger.info('Token received! :'+ resp_data["access_token"]) 39 | with open('auth.json', 'w') as myFile: 40 | myFile.write(json.dumps(resp_data)) 41 | print "Logged in as "+ resp_data["username"] 42 | except Exception: 43 | logger.error(Exception.message) 44 | print "Could not login - something went wrong" 45 | 46 | def post(obj): 47 | try: 48 | token = json.loads(open('auth.json').read())["access_token"] 49 | except: 50 | print "Please try to login first with pocket_login" 51 | 52 | req_data = { 53 | "consumer_key": CONSUMER_KEY, 54 | "access_token": token 55 | } 56 | req_data.update(obj) 57 | 58 | resp = makeRequest(json.dumps(req_data),'https://getpocket.com/v3/add/') 59 | if resp["status"] == 1: 60 | print "Succesfully posted to pocket" 61 | 62 | def makeRequest(request_data, request_url): 63 | request_headers = {'Content-Type': 'application/json; charset=UTF-8', 'X-Accept': 'application/json'} 64 | request = urllib2.Request(request_url, request_data, request_headers) 65 | response = urllib2.urlopen(request) 66 | data = json.load(response) 67 | return data 68 | -------------------------------------------------------------------------------- /alfred.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import itertools 3 | import os 4 | import plistlib 5 | import unicodedata 6 | import sys 7 | 8 | from xml.etree.ElementTree import Element, SubElement, tostring 9 | 10 | 11 | """ 12 | You should run your script via /bin/bash with all escape options ticked. 13 | The command line should be 14 | 15 | python yourscript.py "{query}" arg2 arg3 ... 16 | """ 17 | UNESCAPE_CHARACTERS = u""" ;()""" 18 | 19 | _MAX_RESULTS_DEFAULT = 9 20 | 21 | preferences = plistlib.readPlist('info.plist') 22 | bundleid = preferences['bundleid'] 23 | 24 | class Item(object): 25 | @classmethod 26 | def unicode(cls, value): 27 | try: 28 | items = value.iteritems() 29 | except AttributeError: 30 | return unicode(value) 31 | else: 32 | return dict(map(unicode, item) for item in items) 33 | 34 | def __init__(self, attributes, title, subtitle, icon=None): 35 | self.attributes = attributes 36 | self.title = title 37 | self.subtitle = subtitle 38 | self.icon = icon 39 | 40 | def __str__(self): 41 | return tostring(self.xml(), encoding='utf-8') 42 | 43 | def xml(self): 44 | item = Element(u'item', self.unicode(self.attributes)) 45 | for attribute in (u'title', u'subtitle', u'icon'): 46 | value = getattr(self, attribute) 47 | if value is None: 48 | continue 49 | if len(value) == 2 and isinstance(value[1], dict): 50 | (value, attributes) = value 51 | else: 52 | attributes = {} 53 | SubElement(item, attribute, self.unicode(attributes)).text = unicode(value) 54 | return item 55 | 56 | def args(characters=None): 57 | return tuple(unescape(decode(arg), characters) for arg in sys.argv[1:]) 58 | 59 | def config(): 60 | return _create('config') 61 | 62 | def decode(s): 63 | return unicodedata.normalize('NFC', s.decode('utf-8')) 64 | 65 | def uid(uid): 66 | return u'-'.join(map(unicode, (bundleid, uid))) 67 | 68 | def unescape(query, characters=None): 69 | for character in (UNESCAPE_CHARACTERS if (characters is None) else characters): 70 | query = query.replace('\\%s' % character, character) 71 | return query 72 | 73 | def work(volatile): 74 | path = { 75 | True: '~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data', 76 | False: '~/Library/Application Support/Alfred 2/Workflow Data' 77 | }[bool(volatile)] 78 | return _create(os.path.join(os.path.expanduser(path), bundleid)) 79 | 80 | def write(text): 81 | sys.stdout.write(text) 82 | 83 | def xml(items, maxresults=_MAX_RESULTS_DEFAULT): 84 | root = Element('items') 85 | for item in itertools.islice(items, maxresults): 86 | root.append(item.xml()) 87 | return tostring(root, encoding='utf-8') 88 | 89 | def _create(path): 90 | if not os.path.isdir(path): 91 | os.mkdir(path) 92 | if not os.access(path, os.W_OK): 93 | raise IOError('No write access: %s' % path) 94 | return path 95 | -------------------------------------------------------------------------------- /actions.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | import urllib 5 | import alfred 6 | import pocket 7 | 8 | (action, query) = alfred.args() # proper decoding and unescaping of command line arguments 9 | 10 | 11 | def login(): 12 | code = pocket.getRequestCode() 13 | with open('code.json', 'w') as myFile: 14 | myFile.write(json.dumps(code)) 15 | 16 | results = [alfred.Item( 17 | attributes={'uid': alfred.uid(0), 'arg': code["code"]}, 18 | title='Login!', 19 | icon='icon.png', 20 | subtitle=u'Login with Pocket.com (you will be taken to pocket.com)' 21 | )] # a single Alfred result 22 | xml = alfred.xml(results) # compiles the XML answer 23 | alfred.write(xml) # writes the XML back to Alfred 24 | 25 | def post(): 26 | obj = applescript(query) 27 | pocket.post(obj) 28 | 29 | 30 | def applescript(argument): 31 | if argument == "isChrome": 32 | return os.popen("""osascript -e 'tell app "System Events" to count processes whose name is "Google Chrome"' """).read().rstrip() 33 | if argument == "isSafari": 34 | return os.popen("""osascript -e 'tell app "System Events" to count processes whose name is "Safari"' """).read().rstrip() 35 | if argument == "chrome": 36 | obj = {} 37 | obj["url"] = os.popen(""" osascript -e 'tell application "Google Chrome" to return URL of active tab of front window' """).readline() 38 | obj["title"] = os.popen(""" osascript -e 'tell application "Google Chrome" to return title of active tab of front window' """).readline() 39 | return obj 40 | if argument == "safari": 41 | obj = {} 42 | obj["url"] = os.popen(""" osascript -e 'tell application "Safari" to return URL of front document' """).readline() 43 | obj["title"] = os.popen(""" osascript -e 'tell application "Safari" to return name of front document' """).readline() 44 | return obj 45 | if argument == 'isClipboard': 46 | clip = os.popen(""" osascript -e "get the clipboard" """).readline() 47 | try: 48 | url = re.search("(?Phttps?://[^\s]+)", clip).group("url") 49 | except: 50 | url = "" 51 | return bool(url) 52 | if argument == 'clip': 53 | obj = {} 54 | clip = os.popen(""" osascript -e "get the clipboard" """).readline() 55 | try: 56 | obj["url"] = re.search("(?Phttps?://[^\s]+)", clip).group("url") 57 | except: 58 | obj["url"] = "" 59 | return obj 60 | 61 | 62 | def get_actions(): 63 | arr = [] 64 | isChrome = bool(int(applescript("isChrome"))) 65 | isSafari = bool(int(applescript("isSafari"))) 66 | isClipboard = applescript("isClipboard") 67 | 68 | if isChrome: 69 | chrome_url = applescript("chrome") 70 | arr.append(alfred.Item( 71 | attributes={'uid': alfred.uid(0), 'arg': "chrome"}, 72 | title='Pocket - save url from Chrome', 73 | icon='icon.png', 74 | subtitle= chrome_url["title"].decode('utf8') 75 | )) 76 | if isSafari: 77 | s_url = applescript("safari") 78 | arr.append(alfred.Item( 79 | attributes={'uid': alfred.uid(0), 'arg': "safari"}, 80 | title='Pocket - save url from Safari', 81 | icon='icon.png', 82 | subtitle=s_url["title"].decode('utf8') 83 | )) 84 | if isClipboard: 85 | c_url = applescript("clip") 86 | arr.append(alfred.Item( 87 | attributes={'uid': alfred.uid(0), 'arg': "clip"}, 88 | title='Pocket - save url from Clipboard', 89 | icon='icon.png', 90 | subtitle=c_url["url"].decode('utf8') 91 | )) 92 | xml = alfred.xml(arr) 93 | alfred.write(xml) 94 | 95 | if action == 'login': 96 | login() 97 | elif action == 'post': 98 | post() 99 | elif action == 'get_actions': 100 | get_actions() --------------------------------------------------------------------------------