diff --git a/.gitignore b/.gitignore
index fff7c2e..b498d61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
-settings.py
\ No newline at end of file
+settings.py
+schedule.sh
+week.plist
+day.plist
\ No newline at end of file
diff --git a/README.md b/README.md
index 26d5b81..3aff33c 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,23 @@
# pocket-snack
When your Pocket list is overwhelming, pocket-snack lets you see just what you can read today
-Each time the `refresh` command runs, it moves everything in `My List` to `Archive` with a particular tag, then randomly selects X number of items with that tag to go back into `My List` so that instead of an overwhelming number of things to be read, you just have the number you can comfortably expect to read per cycle (day, week etc).
+## A note on version 2
-This is a work in progress, but all functions described here should be working.
+All commands have changed since version 1 - read the _Usage_ section carefully. This was necessary in order to provide better functionality without making the code too confusing.
+
+One of the changes is that the `refresh` command from version 1 no longer exists. This is so that `--since`, and `--before` can be used with both `--stash` and `lucky_dip`. If you want replicate `refresh` you simply need to run `--stash` followed by `--lucky_dip`. From the command line you could do:
+```bash
+pocketsnack -s && pocketsnack -d
+```
+The automation of `pocketsnack refresh` has _also_ been removed. This didn't really work very consistently, and was causing a lot of maintenance headaches. I'm looking at how to bring it back in a different way, but for now it's been removed.
## Getting started
-### tl;dr
+### Quick version
-1. install python 3 and the _requests_ module
+1. make sure you have installed Python 3 and it is callable with `python3`
2. copy `settings-example.py` to `settings.py`
-3. create Pocket app and paste consumer key into settings.py
+3. create Pocket app and paste consumer key into `settings.py`
4. run `bash install.sh` and follow the prompts
### Dependencies
@@ -20,7 +26,7 @@ You will need Python 3.x installed and it must be called by `python3`, rather th
On MacOS the easiest thing to do is to [install Python 3 using Homebrew](https://docs.brew.sh/Homebrew-and-Python): `brew install python`.
-Then install the Python **requests** module using **pip**: `pip3 install requests`
+The install script should install the `requests` module for you when you run `bash install.sh`. If you prefer, you can install it manually using **pip**: `pip3 install requests`
### Settings
@@ -37,71 +43,101 @@ You can adjust most settings, but the defaults in **settings-example.py** should
### Pocket access token
-Pocket uses OAuth to confirm that your app has permission from your user account to do stuff in your account. This means you need to authorise the app before you can do anything else. Once you have copied you app consumer key into settings.py, when you run the `install.sh` bash script, this will run `authorise` for you. If you are doing everything manually you should run `pocketsnack authorise` to get your token (see below).
+Pocket uses OAuth to confirm that your app has permission from your user account to do stuff in your account. This means you need to authorise the app before you can do anything else. Once you have copied you app consumer key into settings.py, when you run the `install.sh` bash script, this will run `authorise` for you. If you prefer to install manually, or want to change the Pocket account details, you should run `pocketsnack --authorise` to get your token (see below).
You should now have a line at the bottom of settings.py saying something like `pocket_access_token = 'aa11bb-zz9900xx'`
## Usage
-For most users you simply need to run `install.sh` and then forget about it. The install script will ask a series of questions, authorise your app, and optionally set up a daily or weekly task to refresh your list. On MacOS this is done via **launchd** and on Linux via **cron**. If you want to use cron instead of launchd on Mac, just choose 'Linux' when asked - but I wouldn't recommend it (read on for why).
+To run commands, use `pocketsnack [command]`.
+
+### -h, --help
+
+Outputs help for each command
+
+## admin commands
+
+### -t, --test
+
+Outputs the first article returned by a call to the API. Normally you will never need to use this.
+
+### -u, --authorise
+
+This command has an 's', not a 'z', and the short version is a 'u', not an 'a'.
+
+You need this to authorise your app. This command is automatically run by `install.sh`. Everything else works exclusively on the command line, but _authorise_ needs to open a browser to complete the authorisation process, so you need to run this on a machine with a web browser. It will authorise your app with your user, wait for you to confirm that you have completed the authorisation (by typing 'done') and then add the token to `settings.py`. Use it if you want to change the Pocket account you are using with pocketsnack.
+
+## action commands
-In both cases, `pocketsnack refresh` will run at the specified time. In the case of **cron**, this will only happen if the machine is up and logged on - e.g. a server. In the case of **launchd**, the script will be associated with your user account. If your machine is sleeping or your account logged out at the scheduled time, it will run immediately when you wake the machine up or log in. This allows you to set it for, e.g. 5am and be confident that when you open your Macbook at 7am the script will run and your Pocket account will refresh.
+### -d, --lucky_dip
-If you use launchd, log files for stdout and errors will be created wherever you saved _pocketsnack_.
+Returns items with the archive tag from the archive to the list, and removes the archive tag. The number of items returned is determined by `items_per_cycle` in `settings.py`. Note that if `num_videos` and `num_images` add up to more than `items_per_cycle`, _lucky_dip_ will only return the total specified in `items_per_cycle`. Videos take precedence.
-To run commands manually, use `pocketsnack [command]`.
+### -p, --purge
-## commands
+You can use **purge_tags** to clear all tags in your List, Archive, or both, excluding the `archive_tag` and any `retain_tags`. This is useful if you've been using the Aus GLAM Blogs Pocket tool or anything else that retains the original tags from articles.
+
+`--purge` requires a second argument: `--list`, `--archive`, or `--all`, depending on where you want to purge tags.
+
+**NOTE** that by design, `--purge` will process **all** items in your archive, not just items with the `archive_tag`. This may lead to miss-matches between the number returned by `--info --archive` and the number of items processed by `--purge --archive`.
-### archive
+### -s, --stash
-This tells you how many items are in your archive and how many of them are 'long reads'. You can set the wordcount defining a long read in `settings.py`.
+Adds the archive tag to everything in your list, and then archives them. Depending on the value of `ignore_faves` and `ignore_tags` in `settings.py`, and any before/since values, some items may be excluded and remain in the List.
-### authorise
+## optional flags
-This has an 's', not a 'z'.
+### -a, --archive
-You need this to authorise your app. Everything else works exclusively on the command line, but _authorise_ needs to open a browser to complete the authorisation process, so you need to run this on a machine with a web browser. It will authorise your app with your user, wait for you to confirm that you have completed the authorisation (by typing 'done') and then add the token to `settings.py`.
+Used in combination with `--info`, this tells you how many items are in your archive and how many of them are 'long reads'. You can set the wordcount defining a long read in `settings.py`. Used with `--purge`, it purges tags on items in the archive.
-### list
+### -l, --list
Same as _archive_ but for your list instead of your archive.
-### lucky_dip
+### -b, -all
-Returns items from the archive to the list, and removes the archive tag. The number of items returned is determined by `items_per_cycle` in `settings.py`. Note that if `num_videos` and `num_images` add up to more than `items_per_cycle`, _lucky_dip_ will only return the total specified in `items_per_cycle`. Videos take precedence.
+For use with `--purge` - purge tags from _both_ the List and the Archive.
-### purge_tags
+### -n, --since SINCE
-You can use **purge_tags** to clear all tags in your List, Archive, or both, excluding the `archive_tag` and any `retain_tags`. This is useful if you've been using the Aus GLAM Blogs Pocket tool or anything else that retains the original tags from articles.
+Restrict the current _action command_ to only items updated more recently than _SINCE_ number of days.
-`purge_tags` requires a second argument: `list`, `archive`, or `all`, depending on where you want to purge tags.
+### -o, --before BEFORE
-### refresh
+Restrict the current _action command_ to only items updated less recently than _BEFORE_ number of days.
-Runs `stash` followed by `lucky_dip`. This is the command that is run by launchd or cron if you set it up using `install.sh`.
+## examples
-### stash
+Stash only items updated in the last 2 days:
-Adds the archive tag to everything in your list, and then archives them. Depending on the value of `ignore_faves` and `ignore_tags` in `settings.py` some items may remain in the List.
+`pocketsnack --stash -n 2`
-## Uninstalling
+Stash only items NOT updated in the last 7 days:
-Don't like _pocket-snack_ any more or want to re-install it in a new directory? No problem, you will just need to do a little maintenance:
+`pocketsnack --stash -o 7`
-1. If you set up `refresh` on a Mac you should unload the plist file:
+Purge tags on all items in the List that were updated in the last day:
- `launchctl unload ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist`
+`pocketsnack -pln 1`
-2. Once unloaded, you can delete it:
+Run lucky_dip:
- `rm ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist`
+`pocketsnack --lucky_dip`
+
+Run lucky_dip but only choose from items last updated longer ago than one week:
+
+`pocketsnack -d -o 7`
+
+## Uninstalling or moving to a new directory
+
+Don't like _pocket-snack_ any more or want to re-install it in a new directory? No problem, you will just need to do a little maintenance:
-3. Delete the executable link - if you don't do this when re-installing in a different directory, running `pocketsnack` will fail because it will still be pointing at the old directory.
+1. Delete the executable link - if you don't do this when re-installing in a different directory, running `pocketsnack` will fail because it will still be pointing at the old directory.
`rm /usr/local/bin/pocketsnack`
-Now you can safely delete the pocket-snack directory.
+2. Now you can safely delete the pocket-snack directory.
## Bugs and suggestions
diff --git a/daily.plist b/daily.plist
deleted file mode 100644
index b59b9ad..0000000
--- a/daily.plist
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
- Label
- com.getpocket.pocketsnack
- EnvironmentVariables
-
- PATH
- /bin:/usr/bin:/usr/local/bin
-
- StandardOutPath
- PATH
- StandardErrorPath
- PATH
- ProgramArguments
-
- /usr/local/bin/pocketsnack
- refresh
-
- StartCalendarInterval
-
- Minute
- 0
- Hour
- 6
- Weekday
- 1
-
- KeepAlive
-
- Crashed
-
-
-
-
\ No newline at end of file
diff --git a/hourly.plist b/hourly.plist
deleted file mode 100644
index d7ea2b9..0000000
--- a/hourly.plist
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- Label
- com.getpocket.pocketsnack
- EnvironmentVariables
-
- PATH
- /bin:/usr/bin:/usr/local/bin
-
- StandardOutPath
- /var/log/pocketsnack.log
- StandardErrorPath
- /var/log/pocketsnack.error.log
- ProgramArguments
-
- /usr/local/bin/pocketsnack
- refresh
-
- StartCalendarInterval
-
- Minute
- 0
- Hour
- 6
-
- KeepAlive
-
- Crashed
-
-
-
-
\ No newline at end of file
diff --git a/install.sh b/install.sh
index 1a392a1..5f0ba75 100644
--- a/install.sh
+++ b/install.sh
@@ -10,6 +10,7 @@ fi
echo 'checking for Python3...'
if [ $(which python3) ]; then
echo 'Python 3 is installed and python3 command works'
+ pip3 install requests >/dev/null
echo 'Installation complete ✅ You can now get started!'
echo '-------------------------------------------------'
# check if user wants to just authorise straight away
@@ -21,134 +22,6 @@ if [ $(which python3) ]; then
echo 'Authorise pocketsnack in the browser window that opens in a moment, then return to this command line to finish setting up.'
sleep 5s
pocketsnack authorise
- # here we should ask if they want to set up a regular job
- echo 'Do you want to automatically refresh your Pocket List? This will run "stash" and then "lucky_dip" once per day, or once per week.'
- auto=("No Thanks" "Yes Daily" "Yes Weekly")
- select automated in "${auto[@]}"
- do
- case $REPLY in
- 3)
- # which day do they want to refresh their list? Must be between 1 (Monday) and 7 (Sunday)
- echo "Which day do you want to refresh your Pocket list?"
- select day in Monday Tuesday Wednesday Thursday Friday Saturday Sunday;
- do
- if [[ -z $day ]]; then
- echo "Please enter a number between 1 and 7."
- else
- daynum=$REPLY
- # what hour do they want to refresh their list? Must be a number between 0 and 23. Is 6 by default.
- echo 'Enter an hour between 0 and 23 for when you want to run your script.'
- read hour
- isnum='^2[0-3]$|^1?[0-9]$'
- while ! [[ $hour =~ $isnum ]]
- do
- echo 'Invalid choice - please enter a whole number between 0 and 23'
- read hour
- done
- # are they running MacOS or Linux
- echo "Ok last question: are you running (1) MacOS or (2) Linux?"
- select operating_system in MacOS Linux;
- do
- case $operating_system in
- MacOS)
- # create a new plist file with the hour number and put it in the right place
- # if ~/Library/LaunchAgents doesn't exist it need to be created
- if [ ! -d ~/Library/LaunchAgents ] ; then
- mkdir ~/Library/LaunchAgents
- fi
- sed "26 s/[0-9][0-9]*/$hour/" ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist
- # edit the file in place to update the day
- sed -i '' -e "28 s/[0-9][0-9]*/$daynum/" ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist
- # edit the file in place to update the logfiles
- logpath="$(pwd)/pocketsnack.log"
- errorpath="$(pwd)/pocketsnack-error.log"
- sed -i '' -e "13c\\
- $logpath
- " ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist
- sed -i '' -e "15c\\
- $errorpath
- " ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist
- # unload launchd file in case we're re-loading it
- # send any error messages into the ether so users don't get an error on first install
- launchctl unload ~/Library/Launchagents/com.getpocket.pocketsnack.plist 2> /dev/null
- # load launchd file
- launchctl load -w ~/Library/Launchagents/com.getpocket.pocketsnack.plist
- echo "You're all set up! Enjoy your new Pocket experience."
- exit 0
- ;;
- Linux)
- # edit crontab
- (crontab -l ; echo "0 $hour * * $day /usr/local/bin/pocketsnack refresh") 2> /dev/null | sort | uniq | crontab -
- echo "You're all set up! Enjoy your new Pocket experience."
- exit 0
- ;;
- *)
- echo "Please select 1 for MacOS or 2 for Linux"
- ;;
- esac
- done
- fi
- done
- ;;
- 2)
- # what hour do they want to refresh their list? Must be a number between 0 and 23. Is 6 by default.
- echo 'Enter an hour between 0 and 23 for when you want to run your script.'
- read hour
- isnum='^2[0-3]$|^1?[0-9]$'
- while ! [[ $hour =~ $isnum ]]
- do
- echo 'Invalid choice - please enter a whole number between 0 and 23'
- read hour
- done
- # are they running MacOS or Linux
- echo "Ok last question: are you running (1) MacOS or (2) Linux?"
- select operating_system in MacOS Linux
- do
- case $operating_system in
- MacOS)
- # create a new plist file with the hour number and put it in the right place
- if [ ! -d ~/Library/LaunchAgents ] ; then
- mkdir ~/Library/LaunchAgents
- fi
- sed "22 s/[0-9][0-9]*/$hour/" ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist
- # edit the file in place to update the logfiles
- logpath="$(pwd)/pocketsnack.log"
- errorpath="$(pwd)/pocketsnack-error.log"
- sed -i '' -e "13c\\
- $logpath
- " ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist
- sed -i '' -e "15c\\
- $errorpath
- " ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist
- # unload any existing launchd file
- # send any error messages into the ether so users don't get an error on first install
- launchctl unload ~/Library/Launchagents/com.getpocket.pocketsnack.plist 2> /dev/null
- # load new launchd file
- launchctl load -w ~/Library/Launchagents/com.getpocket.pocketsnack.plist
- echo "You're all set up! Enjoy your new Pocket experience."
- exit 0
- ;;
- Linux)
- # edit crontab
- (crontab -l ; echo "0 $hour * * * /usr/local/bin/pocketsnack refresh") 2> /dev/null | sort | uniq | crontab -
- echo "You're all set up! Enjoy your new Pocket experience."
- exit 0
- ;;
- *)
- echo "Please select 1 for MacOS or 2 for Linux"
- ;;
- esac
- done
- ;;
- 1)
- echo 'No script scheduled. You can use pocket-snack manually at any time using the `pocketsnack` command.'
- exit 0
- ;;
- *)
- echo 'Please select 1 to exit, 2 to schedule daily refreshes or 3 to schedule weekly refreshes.'
- ;;
- esac
- done
;;
No)
echo 'Run "pocketsnack authorise" when you are ready to authorise your app.'
diff --git a/main.py b/main.py
index 3068aca..08213b8 100755
--- a/main.py
+++ b/main.py
@@ -25,19 +25,22 @@
# Import libraries
# ----------------
-import requests
+from argparse import ArgumentParser
# bundled with Python
-from datetime import datetime
-import json
-import sys
-import urllib
-import webbrowser
+import os
+import subprocess
# local modules
import settings
import pocket_toolkit as pt
+# ----------------
+# Settings
+# ----------------
+
+# TODO: clean this up
+
# assign short variable names from the settings file
consumer_key = settings.pocket_consumer_key
redirect_uri = settings.pocket_redirect_uri
@@ -59,92 +62,146 @@
settings.longreads_wordcount
]
+# ----------------
+# argparser arguments
+# ----------------
-if __name__ == '__main__':
- arguments = sys.argv
+parser = ArgumentParser(description='\033[1;36mpocketsnack: a command line tool for decluttering your Pocket account\033[1;m')
+admin = parser.add_argument_group('admin commands')
+actions = parser.add_argument_group('action commands')
+mex = parser.add_mutually_exclusive_group()
+timers = parser.add_mutually_exclusive_group()
+
+mex.add_argument(
+ "-a", "--archive", action="store_true", help="get information on TBR items in archive (with -i) or purge tags in archive (with -p)"
+)
+mex.add_argument(
+ "-b", "--all", action="store_true", help="purge all tags in both list and archive (with -p)"
+)
+actions.add_argument(
+ "-d", "--lucky_dip", action="store_true", help="move random items tagged 'tbr' from archive to list, depending on settings"
+)
+actions.add_argument(
+ "-i", "--info", action="store_true", help="get information on items in list or TBR items in archive"
+)
+mex.add_argument(
+ "-l", "--list", action="store_true", help="get information on items in list (with -i) or purge tags in list (with -p)"
+)
+timers.add_argument(
+ "-n", "--since", type=int, help="only act on items where last activity is newer than a given number of days. Use with any action command"
+)
+timers.add_argument(
+ "-o", "--before", type=int, help="only act on items where last activity is older than a given number of days. Use with any action command"
+)
+actions.add_argument(
+ "-p", "--purge", action="store_true", help="remove all tags from list, archive, or both, depending on the second argument provided and excepting tags listed in 'retain_tags' in settings"
+)
+actions.add_argument(
+ "-s", "--stash", action="store_true", help="add 'tbr' tag to all items in user list and archive them, with exceptions as per settings"
+)
+admin.add_argument(
+ "-t", "--test", action="store_true", help="test whether API call returns data"
+)
+admin.add_argument(
+ "-u", "--authorise", action="store_true", help="authorise app to connect to a Pocket account"
+)
+
+options = parser.parse_args()
- if len(arguments) > 1:
+# ----------------
+# What happens with each combination?
+# ----------------
- if arguments[1] == 'authorise':
- # Run authorise once first to retrieve a pocket_access_token
- auth = pt.authorise(consumer_key, redirect_uri)
- print(auth)
+if __name__ == '__main__':
- elif arguments[1] == "list":
- # Retrieve info about the user's list
- response = pt.get_list(consumer_key, settings.pocket_access_token)
- items = response['list']
+ # Find all args that have a value other than False
+ # This helps with error messages for optional args
+ # that need to be used in combination with something else
+ true_vars = []
+ orphans = ['list', 'archive', 'all', 'since', 'before']
+ for x in vars(options):
+ if vars(options)[x]:
+ true_vars.append(x)
+
+ if options.authorise:
+ # Run authorise once first to retrieve a pocket_access_token
+ auth = pt.authorise(consumer_key, redirect_uri)
+ print(auth)
+
+ elif options.lucky_dip:
+ print('\033[0;36mRunning lucky dip...\033[0;m')
+ dip = pt.lucky_dip(consumer_key, settings.pocket_access_token, settings.archive_tag, settings.items_per_cycle, settings.num_videos, settings.num_images, settings.num_longreads, settings.longreads_wordcount, options.before, options.since)
+ print('\033[0;36m' + dip + '\033[0;m')
+
+ elif options.info:
+
+ def print_info(response, collection):
+ items = str(len(response))
longreads = 0
- for item in items:
+ for item in response:
# is it a long read?
- if 'word_count' in items[item]:
- words = int(items[item]['word_count'])
+ if 'word_count' in response[item]:
+ words = int(response[item]['word_count'])
longread = True if words > settings.longreads_wordcount else False
else:
longread = False
if longread:
longreads += 1
- print('The user list has ' + str(len(response['list'])) + ' items and ' + str(longreads) + ' are longreads.')
- elif arguments[1] == "archive":
- # Retrieve info about the user's list
- response = pt.get_tbr(consumer_key, settings.pocket_access_token, archive_tag)
- items = response['list']
- longreads = 0
- for item in items:
- # is it a long read?
- if 'word_count' in items[item]:
- words = int(items[item]['word_count'])
- longread = True if words > settings.longreads_wordcount else False
- else:
- longread = False
- if longread:
- longreads += 1
- print('The TBR archive has ' + str(len(response['list'])) + ' items and ' + str(longreads) + ' are longreads.')
-
- elif arguments[1] == 'refresh':
- print('Refreshing at ' + datetime.now().strftime('%a %d %b %Y %H:%M'))
- refresh = pt.refresh(*refresh_settings)
- print('\033[0;36m' + refresh + '\033[0;m')
-
- elif arguments[1] == 'stash':
- stash = pt.stash(consumer_key, settings.pocket_access_token, archive_tag, settings.replace_all_tags, settings.retain_tags, settings.ignore_faves, settings.ignore_tags)
- print('\033[0;36m' + stash + '\033[0;m')
-
- elif arguments[1] == 'lucky_dip':
- print('\033[0;36mRunning lucky dip...\033[0;m')
- dip = pt.lucky_dip(consumer_key, settings.pocket_access_token, settings.archive_tag, settings.items_per_cycle, settings.num_videos, settings.num_images, settings.num_longreads, settings.longreads_wordcount)
- print('\033[0;36m' + dip + '\033[0;m')
-
- elif arguments[1] == 'purge_tags':
-
- if len(arguments) < 3:
- print('\033[0;36mWhoops, you need to use a second argument with purge_tags.\033[0;m')
- print('\033[0;36mTry "list", "archive" or "all".\033[0;m')
-
- elif arguments[2] == 'list':
- print('\033[0;36mPurging tags in the list\033[0;m')
- purge = pt.purge_tags('unread', settings.retain_tags, archive_tag, consumer_key, settings.pocket_access_token)
- print(purge)
-
- elif arguments[2] == 'archive':
- print('\033[0;36mPurging tags in the archive\033[0;m')
- purge = pt.purge_tags('archive', settings.retain_tags, archive_tag, consumer_key, settings.pocket_access_token)
- print(purge)
-
- elif arguments[2] == 'all':
- print('\033[0;36mPurging tags in both the archive and the list\033[0;m')
- purge = pt.purge_tags('all', settings.retain_tags, archive_tag, consumer_key, settings.pocket_access_token)
- print(purge)
+ if options.before:
+ print(collection + 'has ' + items + ' items ' + 'updated prior to ' + str(options.before) + ' days ago and ' + str(longreads) + ' are longreads.')
+ elif options.since:
+ print(collection + 'has ' + items + ' items ' + 'updated since ' + str(options.since) + ' days ago and ' + str(longreads) + ' are longreads.')
+ else:
+ print(collection + 'has ' + items + ' items and ' + str(longreads) + ' are longreads.')
+
+ if options.archive:
+ response = pt.info(consumer_key, settings.pocket_access_token, archive_tag, options.before, options.since)
+ if len(response) > 0:
+ print_info(response, 'The TBR archive ')
else:
- print('\033[0;36mWhoops, you used an unknown argument to purge_tags.\033[0;m')
- print('\033[0;36mPossible options are "purge_tags list", "purge_tags archive" or "purge_tags all".\033[0;m')
+ print('No items match that query')
- elif arguments[1] == 'test':
- result = pt.test(consumer_key, settings.pocket_access_token)
- print(result)
+ elif options.list:
+ response = pt.info(consumer_key, settings.pocket_access_token, False, options.before, options.since)
+ if len(response) > 0:
+ print_info(response, 'The user List ')
+ else:
+ print('No items match that query')
else:
- print('That argument is unknown. Check README.md for valid pocketsnack arguments.') # TODO: need to create a man page for this
+ print('\n \033[0;36m--info\033[0;m requires a second argument (-a or -l). Check \033[0;36mpocketsnack --help\033[0;m for more information\n')
+
+ elif options.purge:
+
+ if options.list:
+ print('\033[0;36mPurging tags in the list\033[0;m')
+ purge = pt.purge_tags('unread', settings.retain_tags, archive_tag, consumer_key, settings.pocket_access_token, options.before, options.since)
+ print(purge)
+
+ elif options.archive:
+ print('\033[0;36mPurging tags in the archive\033[0;m')
+ purge = pt.purge_tags('archive', settings.retain_tags, archive_tag, consumer_key, settings.pocket_access_token, options.before, options.since)
+ print(purge)
+
+ elif options.all:
+ print('\033[0;36mPurging tags in both the archive and the list\033[0;m')
+ purge = pt.purge_tags('all', settings.retain_tags, archive_tag, consumer_key, settings.pocket_access_token, options.before, options.since)
+ print(purge)
+
+ else:
+ print('\n \033[0;36m--purge\033[0;m requires a second argument (-a, -l or -b). Check \033[0;36mpocketsnack --help\033[0;m for more information\n')
+
+ elif options.stash:
+ stash = pt.stash(consumer_key, settings.pocket_access_token, archive_tag, settings.replace_all_tags, settings.retain_tags, settings.ignore_faves, settings.ignore_tags, options.before, options.since)
+ print('\033[0;36m' + stash + '\033[0;m')
+
+ elif options.test:
+ result = pt.test(consumer_key, settings.pocket_access_token)
+ print(result)
+
+ elif set(true_vars).intersection(orphans):
+ print('\n That command cannot be used by itself. Check \033[0;36mpocketsnack --help\033[0;m for more information\n')
+
else:
- print('Whoops, you forgot to add an "argument". If you have not run anything yet, start with "pocketsnack authorise"')
\ No newline at end of file
+ print('\033[0;36mpocketsnack\033[0;m requires commands and/or flags to do anything useful. Try \033[0;36mpocketsnack -h\033[0;m for more information')
\ No newline at end of file
diff --git a/pocket_toolkit.py b/pocket_toolkit.py
index 6c43278..3960185 100755
--- a/pocket_toolkit.py
+++ b/pocket_toolkit.py
@@ -26,6 +26,7 @@
import requests
# bundled with Python
+from datetime import datetime, time, timedelta
import fileinput
import json
import random
@@ -48,7 +49,7 @@
# Your new app will show a 'consumer key', which you need to paste into the first line in settings.py
# -----------------
-# Request functions
+# reusable functions
# -----------------
# (TODO: make this a proper class object)
@@ -73,6 +74,33 @@ def connection_live():
pass
return False
+# make a unix timestamp for before/after flags with Pocket's 'since' param
+def get_timestamp(since):
+ now = datetime.now()
+ delta = timedelta(days=since)
+ since_time = datetime.strftime(now - delta, '%c')
+ strptime = time.strptime(since_time)
+ return time.mktime(strptime) # return Unix timestamp
+
+def get_item_list(params, before, since):
+ if before:
+ all_items = get(params)
+ params['since'] = get_timestamp(before)
+ since_items = get(params)
+ # get non-intersection of 2 groups to get only items last changed 'before'
+ item_list = all_items.json()['list'] # everything
+ since_list = since_items.json()['list'] # only things since 'before'
+ if len(since_list) > 0:
+ for key in since_list.keys():
+ item_list.pop(key, None) # remove everything from items_list that is in since_list
+ return item_list
+ elif since:
+ timestamp = get_timestamp(since)
+ params['since'] = timestamp
+ return get(params).json()['list']
+ else:
+ return get(params).json()['list']
+
# --------------------
# process tag updates
# --------------------
@@ -141,21 +169,29 @@ def authorise(consumer_key, redirect_uri): # With an 's'. Deal with it.
print(line.rstrip())
return '\033[0;36mToken added to settings.py - you are ready to use pocketsnack.\033[0;m'
-# this is used in main.py
-def get_list(consumer_key, pocket_access_token):
- params = {"consumer_key": consumer_key, "access_token": pocket_access_token}
- request = requests.post('https://getpocket.com/v3/get', headers=headers, params=params)
- return request.json()
+# ------------------------------
+# Read info about Pocket account
+# ------------------------------
+
+def info(consumer_key, pocket_access_token, archive_tag, before, since):
+
+ params = {
+ "consumer_key": consumer_key,
+ "access_token": pocket_access_token,
+ }
+ if archive_tag:
+ # state is archive & use archive tag
+ params['state'] = 'archive'
+ params['tag'] = archive_tag
+ else:
+ # state is unread
+ params['state'] = 'unread'
-# this is used in main.py
-def get_tbr(consumer_key, pocket_access_token, archive_tag):
- # only return items in the archive, tagged with whatever the archive tag is
- params = {"consumer_key": consumer_key, "access_token": pocket_access_token, "state": "archive", "tag": archive_tag}
- request = requests.post('https://getpocket.com/v3/get', headers=headers, params=params)
- return request.json()
+ items = get_item_list(params, before, since)
+ return items
# choose items to put back into the user List
-def lucky_dip(consumer_key, pocket_access_token, archive_tag, items_per_cycle, num_videos, num_images, num_longreads, longreads_wordcount):
+def lucky_dip(consumer_key, pocket_access_token, archive_tag, items_per_cycle, num_videos, num_images, num_longreads, longreads_wordcount, before, since):
def run_lucky_dip(attempts):
if connection_live() == True:
@@ -176,8 +212,8 @@ def readd(selection):
# get everything in the archive with the archive_tag
params = {"consumer_key": consumer_key, "access_token": pocket_access_token, "state": "archive", "tag": archive_tag}
- request = get(params)
- tbr = request.json()['list']
+
+ tbr = get_item_list(params, before, since)
# before we go any further, make sure there actually is something in the TBR list!
if len(tbr) > 0:
@@ -331,7 +367,12 @@ def readd(selection):
if not random_choice:
completed_message += str(chosen['longreads']) + ' long reads and ' + str(chosen['shortreads']) + ' short reads, '
# add this to the end regardless
- completed_message += 'with ' + str(remaining) + ' other items remaining to be read.'
+ caveat = ''
+ if before:
+ caveat = 'last updated earlier than ' + str(before) + ' days ago '
+ if since:
+ caveat = 'last updated more recently than ' + str(since) + ' days ago '
+ completed_message += 'with ' + str(remaining) + ' other items ' + caveat + 'remaining to be read.'
return completed_message
# else if there's nothing tagged with the archive_tag
else:
@@ -352,7 +393,7 @@ def readd(selection):
# purge tags
# -----------------
-def purge_tags(state, retain_tags, archive_tag, consumer_key, pocket_access_token):
+def purge_tags(state, retain_tags, archive_tag, consumer_key, pocket_access_token, before, since):
params = {
"consumer_key": consumer_key,
@@ -364,45 +405,33 @@ def purge_tags(state, retain_tags, archive_tag, consumer_key, pocket_access_toke
# check we're online
if connection_live() == True:
# GET the list
- request = get(params).json()['list']
+ request = get_item_list(params, before, since)
actions = []
- for item in request:
- # find the item tags
- item_tags = []
- if 'tags' in request[item]:
- for tag in request[item]['tags']:
- item_tags.append(tag)
- # keep any retain_tags like we use in stash
- if len(item_tags) > 0:
- update = {"item_id": item, "action": "tags_replace"} # item is the ID because it's the dict key
+ if len(request) > 0:
+ for item in request:
+ item_tags = []
+ # find the item tags
+ if 'tags' in request[item]:
+ for tag in request[item]['tags']:
+ item_tags.append(tag)
+ # keep any retain_tags like we use in stash
retain_tags.add(archive_tag) # we don't want to wipe out the archive tag on archived items!
- update["tags"] = list(retain_tags.intersection(item_tags))
- actions.append(update)
- # otherwise just clear all tags
- else:
- update = {"item_id": item, "action": "tags_clear"} # item is the ID because it's the dict key
+ update = {"item_id": item}
+ intersect = list(retain_tags.intersection(item_tags))
+ if len(intersect) > 0:
+ update['action'] = 'tags_replace' # item is the ID because it's the dict key
+ update["tags"] = intersect # update tags to keep the retain_tags
+ # otherwise just clear all tags
+ else:
+ update['action'] = 'tags_clear' # item is the ID because it's the dict key
actions.append(update)
-
- process_items(actions, consumer_key, pocket_access_token)
- return '\033[0;36mUndesirable elements have been purged.\033[0;m'
-
-# -----------------
-# refresh
-# -----------------
-
-def refresh(consumer_key, pocket_access_token, archive_tag, replace_all_tags, retain_tags, favorite, ignore_tags, items_per_cycle, num_videos, num_images, num_longreads, longreads_wordcount):
- # this is the job that should run regularly
- # run stash
- stash_msg = stash(consumer_key, pocket_access_token, archive_tag, replace_all_tags, retain_tags, favorite, ignore_tags)
- print(stash_msg)
- if stash_msg != '\033[0;31mSorry, no connection after 4 attempts.\033[0;m':
- # run lucky_dip
- print('\033[0;36mRunning lucky dip...\033[0;m')
- ld_message = lucky_dip(consumer_key, pocket_access_token, archive_tag, items_per_cycle, num_videos, num_images, num_longreads, longreads_wordcount)
- return ld_message
- else:
- return '\033[0;31mRefresh aborted.\033[0;m'
+
+ process_items(actions, consumer_key, pocket_access_token)
+ return '\033[1;36mUndesirable elements have been purged.\033[1;m'
+
+ else:
+ return '\033[0;36mNo items from which to purge tags.\033[0;m'
"""
Stash
@@ -422,83 +451,81 @@ def refresh(consumer_key, pocket_access_token, archive_tag, replace_all_tags, re
# stash items
# -----------------
-def stash(consumer_key, pocket_access_token, archive_tag, replace_all_tags, retain_tags, favorite, ignore_tags):
+def stash(consumer_key, pocket_access_token, archive_tag, replace_all_tags, retain_tags, favorite, ignore_tags, before, since):
+ print('\033[0;36mStashing items...\033[0;m')
# if ignore_faves is set to True, don't get favorite items
+ params = {"consumer_key": consumer_key, "access_token": pocket_access_token, "detailType": "complete", "state": "unread"}
if favorite:
- params = {"consumer_key": consumer_key, "access_token": pocket_access_token, "detailType": "complete", "state": "unread", "favorite": "0"}
- print('\033[0;36mStashing items...\033[0;m')
+ params['favorite'] = "0"
print('\033[0;36mSkipping favorited items...\033[0;m')
- else:
- params = {"consumer_key": consumer_key, "access_token": pocket_access_token, "detailType": "complete", "state": "unread"}
def run_stash(attempts):
- if connection_live() == True:
- # GET the list
- request = get(params)
- list_items = request.json()
- actions = []
- item_list = list_items['list']
- # copy items_list so we can alter the copy whilst iterating through the original
- items_to_stash = dict(item_list)
- for item in item_list:
- item_tags = []
- if 'tags' in item_list[item]:
- for tag in item_list[item]['tags']:
- item_tags.append(tag)
-
- # filter out any items with the ignore tags before dealing with the rest
- if len(ignore_tags) > 0 and len(ignore_tags.intersection(item_tags)) > 0:
- # pop it out of the items_to_stash
- items_to_stash.pop(item, None)
- # Now we process all the tags first, before we archive everything
- elif replace_all_tags:
- # set up the action dict
- action = {"item_id": item, "action": "tags_replace"} # item is the ID because it's the dict key
- # are we retaining any tags?
- if retain_tags: # retain_tags should either be False or a Set
- # find the common tags between retain_tags and item_tags
- # to do this we need retain_tags to be a set, but you can't JSON serialise a set, so we need to turn the result into a list afterwards
- tags_to_keep = list(retain_tags.intersection(item_tags))
- # don't forget to add the archive_tag!
- tags_to_keep.append(archive_tag)
- action["tags"] = tags_to_keep
- # Anything that is still in the user list can be presumed to not have been read
- # when they read it they will archive it (without the archive_tag because lucky_dip removes it)
- else:
- action["tags"] = archive_tag
- actions.append(action)
- else: # if replace_all_tags is False, just add the archive tag without removing any tags
- action = {"item_id": item, "action": "tags_add"} # add new tag rather than replacing all of them
+ if connection_live() == True:
+ # GET the list
+ item_list = get_item_list(params, before, since)
+ # we store all the 'actions' in an array, then send one big HTTP request to the Pocket API
+ actions = []
+ # copy items_list so we can alter the copy whilst iterating through the original
+ items_to_stash = dict(item_list)
+ for item in item_list:
+ item_tags = []
+ if 'tags' in item_list[item]:
+ for tag in item_list[item]['tags']:
+ item_tags.append(tag)
+
+ # filter out any items with the ignore tags before dealing with the rest
+ if len(ignore_tags) > 0 and len(ignore_tags.intersection(item_tags)) > 0:
+ # pop it out of the items_to_stash
+ items_to_stash.pop(item, None)
+ # Now we process all the tags first, before we archive everything
+ elif replace_all_tags:
+ # set up the action dict
+ action = {"item_id": item, "action": "tags_replace"} # item is the ID because it's the dict key
+ # are we retaining any tags?
+ if retain_tags: # retain_tags should either be False or a Set
+ # find the common tags between retain_tags and item_tags
+ # to do this we need retain_tags to be a set, but you can't JSON serialise a set, so we need to turn the result into a list afterwards
+ tags_to_keep = list(retain_tags.intersection(item_tags))
+ # don't forget to add the archive_tag!
+ tags_to_keep.append(archive_tag)
+ action["tags"] = tags_to_keep
+ # Anything that is still in the user list can be presumed to not have been read
+ # when they read it they will archive it (without the archive_tag because lucky_dip removes it)
+ else:
action["tags"] = archive_tag
- actions.append(action)
+ actions.append(action)
+ else: # if replace_all_tags is False, just add the archive tag without removing any tags
+ action = {"item_id": item, "action": "tags_add"} # add new tag rather than replacing all of them
+ action["tags"] = archive_tag
+ actions.append(action)
- # Update the tags
- process_items(actions, consumer_key, pocket_access_token)
+ # Update the tags
+ process_items(actions, consumer_key, pocket_access_token)
- # Now archive everything
- archive_actions = []
+ # Now archive everything
+ archive_actions = []
- for item in items_to_stash:
- item_action = {"item_id": item, "action": "archive"}
- archive_actions.append(item_action)
+ for item in items_to_stash:
+ item_action = {"item_id": item, "action": "archive"}
+ archive_actions.append(item_action)
- print('\033[0;36mArchiving ' + str(len(archive_actions)) + ' items...\033[0;m')
+ print('\033[0;36mArchiving ' + str(len(archive_actions)) + ' items...\033[0;m')
- # archive items
- process_items(archive_actions, consumer_key, pocket_access_token)
+ # archive items
+ process_items(archive_actions, consumer_key, pocket_access_token)
- # return a list of what was stashed and, if relevant, what wasn't
- skipped_items = len(item_list) - len(items_to_stash)
- return str(len(items_to_stash)) + ' items archived with "' + archive_tag + '" and ' + str(skipped_items) + ' items skipped due to retain tag.'
+ # return a list of what was stashed and, if relevant, what wasn't
+ skipped_items = len(item_list) - len(items_to_stash)
+ return str(len(items_to_stash)) + ' items archived with "' + archive_tag + '" and ' + str(skipped_items) + ' items skipped due to retain tag.'
+ else:
+ if attempts < 4:
+ attempts += 1
+ time.sleep(10)
+ print('\033[0;36mAttempting to connect...\033[0;m')
+ return run_stash(attempts)
else:
- if attempts < 4:
- attempts += 1
- time.sleep(10)
- print('\033[0;36mAttempting to connect...\033[0;m')
- return run_stash(attempts)
- else:
- msg = "\033[0;31mSorry, no connection after 4 attempts.\033[0;m"
- return msg
+ msg = "\033[0;31mSorry, no connection after 4 attempts.\033[0;m"
+ return msg
return run_stash(0)