Pocket Bar

Image preview of Pocket Bar plugin.

pocket.10m.py

Edit
Open on GitHub
#!/usr/bin/env -S PATH="${PATH}:/usr/local/bin" python3

# <xbar.title>Pocket Bar</xbar.title>
# <xbar.version>v1.6.1</xbar.version>
# <xbar.author>Sergey Shlyapugin</xbar.author>
# <xbar.author.github>inbalboa</xbar.author.github>
# <xbar.desc>Basic Pocket client.</xbar.desc>
# <xbar.image>https://i.imgur.com/XQnh7US.png</xbar.image>
# <xbar.dependencies>python3,pocket-api,keyring</xbar.dependencies>
# <xbar.abouturl>https://github.com/inbalboa/pocketbar</xbar.abouturl>

from argparse import ArgumentParser
from dataclasses import dataclass
import json
from pathlib import Path
import subprocess
import sys

APPNAME = 'pocketbar'
CMD = sys.argv[0]
CACHE_PATH = f'~/Library/Caches/{APPNAME}/articles.json'


@dataclass(frozen=True)
class Article:
    id: str
    link: str
    title: str
    cmd: str

    def __str__(self):
        title_ = self.title.replace('|', 'ā€”').strip() if self.title else self.link
        return f'''{title_}|href={self.link} length=60\nāž– {title_}|alternate=true length=60 bash={self.cmd} param1=--delete param2={self.id} terminal=false refresh=true'''


def get_secrets():
    consumer_key = keyring.get_password(APPNAME, 'consumer_key')
    access_token = keyring.get_password(APPNAME, 'access_token')
    return consumer_key, access_token


def update_secrets():
    consumer_key = get_input('\"Enter your consumer key from\\n\\"https://getpocket.com/developer/apps/\\"\"', hidden=True)
    if not consumer_key:
        return None, None
    keyring.set_password(APPNAME, 'consumer_key', consumer_key)

    pocket = Pocket(consumer_key=consumer_key)
    redirect_uri = 'https://getpocket.com/connected_applications'
    request_token = pocket.get_request_token(redirect_uri)
    auth_url = f'https://getpocket.com/auth/authorize?request_token={request_token}&redirect_uri={redirect_uri}'
    subprocess.Popen(['open', auth_url])
    get_ok('\"Press the Authorize button in the opened browser tab, then close this dialog.\"')
    access_token = pocket.get_access_token(request_token)
    keyring.set_password(APPNAME, 'access_token', access_token)


def parse_args():
    parser = ArgumentParser(description='Pocket Bar')
    parser.add_argument('-a', '--add', action='store_true', help='add item')
    parser.add_argument('-d', '--delete', type=str, help='delete item')
    parser.add_argument('-f', '--full', action='store_true', help='full retrieve')
    parser.add_argument('-s', '--secrets', action='store_true', help='update secrets')
    args = parser.parse_args()
    return args


def pocket_icon():
    return 'iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAQAAABuvaSwAAABIElEQVR4Xs3OvS9DYRQH4KdKfaViRWq2SUpCzKJithKrpISko6E6WJBg8L8ws0g6dJMIg6nNbZcmEkLEcG96qduZ31nec/LknJd/lVkVF84T6kLF7HeaU/Xoxm1C3XhUlYvxisCyQSMJNWhZYCXGa+ryeiWvYe03HjdjoDNNmzSCvHoSPlJX1A/6bHhw0BvvedO2o1/KpqbPCP/4xqqGeQwpe9W2Y0vTh3NZzGtYjfFipw35i7b3iIarFmI84V45eof83VlEKbs3EeOUSzVTUTds276xqJtScynlW+Y8O5HRnYxTz+a6x0Uth519YcZUtBS7KRklgWvrcrKypq27Figl3ENawZXAkzt3ngSuFKSTaJhRS3YdO7ZryWhv+If5AkpGXVSbf9oEAAAAAElFTkSuQmCC'


def get_ok(caption):
    osa_bin = 'osascript'
    osa_params = f"-e 'Tell application \"System Events\" to display alert \"Pocket Bar\" message {caption} buttons \"Close\" default button \"Close\"'"
    task = subprocess.Popen(f'{osa_bin} {osa_params} > /dev/null', shell=True)
    task.wait()


def get_input(caption, hidden=False):
    osa_bin = 'osascript'
    hidden_text = ' with hidden answer' if hidden else ''
    osa_params = f"-e 'Tell application \"System Events\" to display dialog {caption} default answer \"\" with title \"Pocket Bar\" with icon 1 {hidden_text}' -e 'text returned of result'"
    task = subprocess.Popen(f'{osa_bin} {osa_params}', shell=True, stdout=subprocess.PIPE)
    answer_text = task.stdout.read()
    task.wait()

    return answer_text.decode().replace('\n', '').replace('\r', '').strip()


def print_error(error):
    print('!|color=#ECB935')
    print('---')
    print(f'Exception: {error}')


def print_refresh():
    print('---')
    print('Refresh|refresh=yes')
    print(f'Full refresh|alternate=true bash={CMD} param1=--full terminal=false refresh=yes')
    print('---')
    print('Open Pocket|href="https://getpocket.com/" refresh=no')
    print(f'Re-authorize...|alternate=true bash={CMD} param1=--secrets terminal=false refresh=true')


def print_secrets_error():
    print('!|color=#ECB935')
    print('---')
    print('Need authorization')
    print('---')
    print(f'Authorize...|bash={CMD} param1=--secrets terminal=false refresh=true')


def print_import_error():
    print('!|color=#ECB935')
    print('---')
    print('Need to install pocket-api or/and keyring packages')
    print('---')
    print('Install (with PIP)...|bash=pip3 param1=install param2=-U param3=pocket-api param4=keyring terminal=true refresh=true')


def get_cache(cache_path):
    try:
        with open(Path(cache_path).expanduser()) as json_file:
            return json.load(json_file)
    except:
        return {}


def set_cache(cache_path, json_data):
    expanded_cache_path = Path(cache_path).expanduser()
    expanded_cache_path.parent.mkdir(exist_ok=True)
    with open(expanded_cache_path, 'w+') as json_file:
        json.dump(json_data, json_file)


def update_from_cache(main_dict, update_dict):
    if update_dict['status'] == 2:
        return main_dict
    res = dict(main_dict)
    res_list = res.get('list', {})
    res_list.update(update_dict.get('list', {}))
    res.update(update_dict)
    res['list'] = res_list
    return res


def main():
    parsed_args = parse_args()

    try:
        global keyring, Pocket, PocketException
        import keyring
        from pocket import Pocket, PocketException
    except ImportError:
        print_import_error()
        print_refresh()
        return

    consumer_key, access_token = get_secrets()
    pocket = Pocket(consumer_key=consumer_key, access_token=access_token)

    if parsed_args.add:
        new_url = get_input('\"Save an item to Pocket:\"')
        if new_url:
            pocket.add(url=new_url)
        return
    elif parsed_args.delete:
        pocket.delete(parsed_args.delete).commit()
        return
    elif parsed_args.secrets:
        update_secrets()
        return

    raw_articles = {} if parsed_args.full else get_cache(CACHE_PATH)
    try:
        raw_answer = pocket.retrieve(detailType='simple', since=raw_articles.get('since'))
    except PocketException as e:
        if e.http_code in (400, 401):
            print_secrets_error()
        else:
            print_error(e)
        print_refresh()
        return
    except Exception as e:
        print_error(e)
        print_refresh()
        return

    raw_articles = update_from_cache(raw_articles, raw_answer)
    set_cache(CACHE_PATH, raw_articles)

    adapted_articles = [Article(
                            id=i.get('item_id'),
                            link=i.get('resolved_url', i.get('given_url')),
                            title=i.get('resolved_title', i.get('given_title')),
                            cmd=CMD
                        )
                        for i in sorted(raw_articles['list'].values(), key=lambda x: x.get('time_added', ''), reverse=True) if i['status'] == '0']
    print(f'{len(adapted_articles)}|font=Verdana size=14 templateImage={pocket_icon()}')
    print('---')
    print(*adapted_articles, sep='\n')
    print('---')
    print(f'āž• Save a URL|bash={CMD} param1=--add terminal=false refresh=true')
    print_refresh()


if __name__ == '__main__':
    main()