├── .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) [](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()
--------------------------------------------------------------------------------