From 80d1b304fa8ef3650c348aa957c5f68afe9e13d5 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Thu, 14 Nov 2019 07:43:19 +1100 Subject: [PATCH 01/19] use argparse for commands to resolve #28 --- main.py | 197 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 111 insertions(+), 86 deletions(-) diff --git a/main.py b/main.py index 3068aca..29443d3 100755 --- a/main.py +++ b/main.py @@ -26,6 +26,7 @@ # ---------------- import requests +from argparse import ArgumentParser # bundled with Python from datetime import datetime @@ -59,92 +60,116 @@ settings.longreads_wordcount ] +parser = ArgumentParser(description='pocketsnack: a command line tool for decluttering your Pocket account') +subparsers = parser.add_subparsers(help='for more help on these options use [command] --help (e.g. purge --help)') + +parser.add_argument( + "-a", "--archive", action="store_true", help="return data about items tagged 'tbr' in user's Pocket archive" +) +parser.add_argument( + "-u", "--authorise", action="store_true", help="authorise app to connect to a Pocket account" +) +parser.add_argument( + "-l", "--list", action="store_true", help="return data about items in user's Pocket list" +) +parser.add_argument( + "-d", "--lucky_dip", action="store_true", help="move random items tagged 'tbr' from archive to list, depending on settings" +) +parser.add_argument( + "-r", "--refresh", action="store_true", help="run 'stash' and then 'lucky_dip' in one operation" +) + +# stash +# TODO: this needs to work like 'purge' with optional args to stash only items added more recently than X days or alternatively only items less recent than X days +# we also need this functionality for 'refresh' - maybe can use 'parents' option in argparse? + +parser.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" +) +parser.add_argument( + "-t", "--test", action="store_true", help="test whether API call returns data" +) +# purge +purge_choice = subparsers.add_parser( + "purge", help="remove all tags from list, archive, or both ('all'), depending on the second argument provided and excepting tags listed in 'retain_tags' in settings.py. Defaults to 'list' if no argument is provided" +) +purge_choice.add_argument( + 'purge_choice', default='list', const="list", nargs='?', choices=('list', 'archive', 'all'), help="remove all tags from list, archive, or both" +) + +options = parser.parse_args() if __name__ == '__main__': - arguments = sys.argv - - if len(arguments) > 1: - - if arguments[1] == 'authorise': - # Run authorise once first to retrieve a pocket_access_token - auth = pt.authorise(consumer_key, redirect_uri) - print(auth) - - elif arguments[1] == "list": - # Retrieve info about the user's list - response = pt.get_list(consumer_key, settings.pocket_access_token) - 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 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) - 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') - - elif arguments[1] == 'test': - result = pt.test(consumer_key, settings.pocket_access_token) - print(result) - else: - print('That argument is unknown. Check README.md for valid pocketsnack arguments.') # TODO: need to create a man page for this - 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 + if options.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.') + + if options.authorise: + # Run authorise once first to retrieve a pocket_access_token + auth = pt.authorise(consumer_key, redirect_uri) + print(auth) + + if 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) + print('\033[0;36m' + dip + '\033[0;m') + + if options.list: + # Retrieve info about the user's list + response = pt.get_list(consumer_key, settings.pocket_access_token) + 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 user list has ' + str(len(response['list'])) + ' items and ' + str(longreads) + ' are longreads.') + + # purge options + if hasattr(options, 'purge_choice'): + + if options.purge_choice == '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) + + if options.purge_choice == '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) + + if options.purge_choice == '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.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') + + if 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) + print('\033[0;36m' + stash + '\033[0;m') + + if options.test: + result = pt.test(consumer_key, settings.pocket_access_token) + print(result) \ No newline at end of file From c00a27af6b6983cbf076d500f5bfde1881d2079c Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Thu, 14 Nov 2019 07:46:47 +1100 Subject: [PATCH 02/19] add message for when no args given --- main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 29443d3..74b663b 100755 --- a/main.py +++ b/main.py @@ -172,4 +172,7 @@ if options.test: result = pt.test(consumer_key, settings.pocket_access_token) - print(result) \ No newline at end of file + print(result) + + else: + print('\033[0;36mpocketsnack\033[0;m requires arguments or flags to do anything. Try \033[0;36mpocketsnack -h\033[0;m for more information') \ No newline at end of file From 740f5b9e608bae8aec7459de97e66c563eb11d05 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Thu, 14 Nov 2019 19:21:41 +1100 Subject: [PATCH 03/19] rearrange archive and list commands to be info tbr and info list respectively --- main.py | 100 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/main.py b/main.py index 74b663b..62f2060 100755 --- a/main.py +++ b/main.py @@ -63,21 +63,18 @@ parser = ArgumentParser(description='pocketsnack: a command line tool for decluttering your Pocket account') subparsers = parser.add_subparsers(help='for more help on these options use [command] --help (e.g. purge --help)') -parser.add_argument( - "-a", "--archive", action="store_true", help="return data about items tagged 'tbr' in user's Pocket archive" -) parser.add_argument( "-u", "--authorise", action="store_true", help="authorise app to connect to a Pocket account" ) -parser.add_argument( - "-l", "--list", action="store_true", help="return data about items in user's Pocket list" -) parser.add_argument( "-d", "--lucky_dip", action="store_true", help="move random items tagged 'tbr' from archive to list, depending on settings" ) parser.add_argument( "-r", "--refresh", action="store_true", help="run 'stash' and then 'lucky_dip' in one operation" ) +parser.add_argument( + "-t", "--test", action="store_true", help="test whether API call returns data" +) # stash # TODO: this needs to work like 'purge' with optional args to stash only items added more recently than X days or alternatively only items less recent than X days @@ -86,9 +83,18 @@ parser.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" ) -parser.add_argument( - "-t", "--test", action="store_true", help="test whether API call returns data" + +# SUBPARSERS + +# new 'info' subparser to reduce confusion between v1 option 'archive' and 'stash' +info = subparsers.add_parser( + 'info', help='get information on contents of Pocket account' ) + +info.add_argument( + 'info_choice', default='list', const="list", nargs='?', choices=('list', 'tbr'), help="return data about items in user's Pocket list or items tagged 'tbr' in users's Pocket archive. For example \033[1;36mpocketsnack info tbr\033[1;m. Defaults to 'list'" +) + # purge purge_choice = subparsers.add_parser( "purge", help="remove all tags from list, archive, or both ('all'), depending on the second argument provided and excepting tags listed in 'retain_tags' in settings.py. Defaults to 'list' if no argument is provided" @@ -101,76 +107,80 @@ if __name__ == '__main__': - if options.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.') + # print(options) if options.authorise: # Run authorise once first to retrieve a pocket_access_token auth = pt.authorise(consumer_key, redirect_uri) print(auth) - if options.lucky_dip: + 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) print('\033[0;36m' + dip + '\033[0;m') - if options.list: - # Retrieve info about the user's list - response = pt.get_list(consumer_key, settings.pocket_access_token) - 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 user list has ' + str(len(response['list'])) + ' items and ' + str(longreads) + ' are longreads.') + elif hasattr(options, 'info_choice'): + + if options.info_choice == 'tbr': + # 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 options.info_choice == 'list': + # Retrieve info about the user's list + response = pt.get_list(consumer_key, settings.pocket_access_token) + 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 user list has ' + str(len(response['list'])) + ' items and ' + str(longreads) + ' are longreads.') # purge options - if hasattr(options, 'purge_choice'): + elif hasattr(options, 'purge_choice'): if options.purge_choice == '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) - if options.purge_choice == 'archive': + elif options.purge_choice == '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) - if options.purge_choice == 'all': + elif options.purge_choice == '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.refresh: + elif options.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') - if options.stash: + 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) print('\033[0;36m' + stash + '\033[0;m') - if options.test: + elif options.test: result = pt.test(consumer_key, settings.pocket_access_token) print(result) From 31300de98011451907fcd546951753394bfc17af Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Thu, 14 Nov 2019 21:52:47 +1100 Subject: [PATCH 04/19] refactor commands --- main.py | 84 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/main.py b/main.py index 62f2060..0635b92 100755 --- a/main.py +++ b/main.py @@ -60,55 +60,46 @@ settings.longreads_wordcount ] -parser = ArgumentParser(description='pocketsnack: a command line tool for decluttering your Pocket account') -subparsers = parser.add_subparsers(help='for more help on these options use [command] --help (e.g. purge --help)') +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() -parser.add_argument( - "-u", "--authorise", action="store_true", help="authorise app to connect to a Pocket account" +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)" ) -parser.add_argument( +actions.add_argument( "-d", "--lucky_dip", action="store_true", help="move random items tagged 'tbr' from archive to list, depending on settings" ) -parser.add_argument( - "-r", "--refresh", action="store_true", help="run 'stash' and then 'lucky_dip' in one operation" +actions.add_argument( + "-i", "--info", action="store_true", help="get information on items in list or TBR items in archive" ) -parser.add_argument( - "-t", "--test", action="store_true", help="test whether API call returns data" +mex.add_argument( + "-l", "--list", action="store_true", help="get information on items in list (with -i) or purge tags in list (with -p)" ) - -# stash -# TODO: this needs to work like 'purge' with optional args to stash only items added more recently than X days or alternatively only items less recent than X days -# we also need this functionality for 'refresh' - maybe can use 'parents' option in argparse? - -parser.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" +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" ) - -# SUBPARSERS - -# new 'info' subparser to reduce confusion between v1 option 'archive' and 'stash' -info = subparsers.add_parser( - 'info', help='get information on contents of Pocket account' +actions.add_argument( + "-r", "--refresh", action="store_true", help="run 'stash' and then 'lucky_dip' in one operation" ) - -info.add_argument( - 'info_choice', default='list', const="list", nargs='?', choices=('list', 'tbr'), help="return data about items in user's Pocket list or items tagged 'tbr' in users's Pocket archive. For example \033[1;36mpocketsnack info tbr\033[1;m. Defaults to 'list'" +admin.add_argument( + "-t", "--test", action="store_true", help="test whether API call returns data" ) - -# purge -purge_choice = subparsers.add_parser( - "purge", help="remove all tags from list, archive, or both ('all'), depending on the second argument provided and excepting tags listed in 'retain_tags' in settings.py. Defaults to 'list' if no argument is provided" +admin.add_argument( + "-u", "--authorise", action="store_true", help="authorise app to connect to a Pocket account" ) -purge_choice.add_argument( - 'purge_choice', default='list', const="list", nargs='?', choices=('list', 'archive', 'all'), help="remove all tags from list, archive, or both" +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" ) options = parser.parse_args() if __name__ == '__main__': - # print(options) - if options.authorise: # Run authorise once first to retrieve a pocket_access_token auth = pt.authorise(consumer_key, redirect_uri) @@ -119,9 +110,8 @@ 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 hasattr(options, 'info_choice'): - - if options.info_choice == 'tbr': + elif options.info: + if options.archive: # Retrieve info about the user's list response = pt.get_tbr(consumer_key, settings.pocket_access_token, archive_tag) items = response['list'] @@ -137,7 +127,7 @@ longreads += 1 print('The TBR archive has ' + str(len(response['list'])) + ' items and ' + str(longreads) + ' are longreads.') - elif options.info_choice == 'list': + elif options.list: # Retrieve info about the user's list response = pt.get_list(consumer_key, settings.pocket_access_token) items = response['list'] @@ -153,24 +143,29 @@ longreads += 1 print('The user list has ' + str(len(response['list'])) + ' items and ' + str(longreads) + ' are longreads.') - # purge options - elif hasattr(options, 'purge_choice'): + else: + 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.purge_choice == 'list': + 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) print(purge) - elif options.purge_choice == 'archive': + 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) print(purge) - elif options.purge_choice == 'all': + 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) 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.refresh: print('Refreshing at ' + datetime.now().strftime('%a %d %b %Y %H:%M')) refresh = pt.refresh(*refresh_settings) @@ -184,5 +179,8 @@ result = pt.test(consumer_key, settings.pocket_access_token) print(result) + elif options.list or options.archive or options.all: + print('\n That command cannot be used by itself. Check \033[0;36mpocketsnack --help\033[0;m for more information\n') + else: - print('\033[0;36mpocketsnack\033[0;m requires arguments or flags to do anything. Try \033[0;36mpocketsnack -h\033[0;m for more information') \ 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 From bedc8261a601d587bc045d9091c541f6397d5197 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Fri, 15 Nov 2019 07:39:19 +1100 Subject: [PATCH 05/19] intersect true_vars with orphans for cleaner error messages --- main.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 0635b92..c4e4cbc 100755 --- a/main.py +++ b/main.py @@ -39,6 +39,12 @@ 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 @@ -60,10 +66,15 @@ settings.longreads_wordcount ] +# ---------------- +# argparser arguments +# ---------------- + 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)" @@ -80,26 +91,45 @@ 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="test whether API call returns data" +) +timers.add_argument( + "-o", "--before", type=int, help="test whether API call returns data" +) 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( "-r", "--refresh", action="store_true", help="run 'stash' and then 'lucky_dip' in one operation" ) +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" ) -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" -) options = parser.parse_args() +# ---------------- +# What happens with each combination? +# ---------------- + if __name__ == '__main__': + # 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) @@ -179,7 +209,8 @@ result = pt.test(consumer_key, settings.pocket_access_token) print(result) - elif options.list or options.archive or options.all: + #elif options.list or options.archive or options.all or options: + 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: From c15992bba462f8cfa531cde3c43754d1fc7b8d5e Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sat, 16 Nov 2019 18:30:42 +1100 Subject: [PATCH 06/19] add since and prior flags to --info --- main.py | 74 ++++++++++++++++++++++++++++------------------- pocket_toolkit.py | 64 +++++++++++++++++++++++++++++++++------- 2 files changed, 98 insertions(+), 40 deletions(-) diff --git a/main.py b/main.py index c4e4cbc..4681907 100755 --- a/main.py +++ b/main.py @@ -92,10 +92,10 @@ "-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="test whether API call returns data" + "-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="test whether API call returns data" + "-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" @@ -141,37 +141,54 @@ print('\033[0;36m' + dip + '\033[0;m') elif options.info: + + def print_info(collection, items, longreads): + if options.before: + print(collection + 'has ' + items + ' items ' + 'updated prior to ' + str(options.before) + ' days ago and ' + longreads + ' are longreads.') + elif options.since: + print(collection + 'has ' + items + ' items ' + 'updated since ' + str(options.since) + ' days ago and ' + longreads + ' are longreads.') + else: + print(collection + 'has ' + items + ' items and ' + longreads + ' are longreads.') + if options.archive: + collection = 'The TBR 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.') + response = pt.info(consumer_key, settings.pocket_access_token, archive_tag, options.before, options.since) + if 'list' in response: + 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_info(collection, str(len(items)), str(longreads)) + else: + print('No items match that query') elif options.list: + collection = 'The user List ' # Retrieve info about the user's list - response = pt.get_list(consumer_key, settings.pocket_access_token) - 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 user list has ' + str(len(response['list'])) + ' items and ' + str(longreads) + ' are longreads.') + response = pt.info(consumer_key, settings.pocket_access_token, False, options.before, options.since) + if 'list' in response: + 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_info(collection, str(len(items)), str(longreads)) + else: + print('No items match that query') else: 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') @@ -209,7 +226,6 @@ result = pt.test(consumer_key, settings.pocket_access_token) print(result) - #elif options.list or options.archive or options.all or options: 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') diff --git a/pocket_toolkit.py b/pocket_toolkit.py index 6c43278..9a96135 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 @@ -141,19 +142,60 @@ 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 +# ------------------------------ -# 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() +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' + if before: + # get all items using params + all_items = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) + # Pocket needs a Unix timestamp + # TODO: make this a function we can use for lucky_dip and stash + now = datetime.now() + delta = timedelta(days=-before) # we are effectively getting 'since' here + since_time = datetime.strftime(now + delta, '%c') + strptime = time.strptime(since_time) + date = time.mktime(strptime) + params['since'] = date + since_items = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) + # get non-intersection of 2 groups to get only items changed 'before' + items_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(): + items_list.pop(key, None) # remove everything from items_list that is in since_list + return_value = {} + return_value['list'] = items_list # reconstruct the dict + return return_value + elif since: + # limit to items since 'since' + # Pocket needs a Unix timestamp + now = datetime.now() + delta = timedelta(days=-since) # minus the number of days + since_time = datetime.strftime(now + delta, '%c') + strptime = time.strptime(since_time) + date = time.mktime(strptime) + params['since'] = date + request = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) + return request.json() + else: + # if no before/since params just gert everything in either the list or archive according to params + request = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) + return request.json() + # 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): From be36ca17848125f2703939ac36d565e23919cddc Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 08:27:29 +1100 Subject: [PATCH 07/19] make reusable get_timestamp function --- pocket_toolkit.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/pocket_toolkit.py b/pocket_toolkit.py index 9a96135..e643a33 100755 --- a/pocket_toolkit.py +++ b/pocket_toolkit.py @@ -49,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) @@ -74,6 +74,15 @@ def connection_live(): pass return False +# make a unix timestamp for before/after flags with Pocket's 'since' param +def get_timestamp(since): + # TODO: make this a function we can use for lucky_dip and stash + 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 + # -------------------- # process tag updates # -------------------- @@ -162,14 +171,7 @@ def info(consumer_key, pocket_access_token, archive_tag, before, since): if before: # get all items using params all_items = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) - # Pocket needs a Unix timestamp - # TODO: make this a function we can use for lucky_dip and stash - now = datetime.now() - delta = timedelta(days=-before) # we are effectively getting 'since' here - since_time = datetime.strftime(now + delta, '%c') - strptime = time.strptime(since_time) - date = time.mktime(strptime) - params['since'] = date + params['since'] = get_timestamp(before) since_items = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) # get non-intersection of 2 groups to get only items changed 'before' items_list = all_items.json()['list'] # everything @@ -181,14 +183,7 @@ def info(consumer_key, pocket_access_token, archive_tag, before, since): return_value['list'] = items_list # reconstruct the dict return return_value elif since: - # limit to items since 'since' - # Pocket needs a Unix timestamp - now = datetime.now() - delta = timedelta(days=-since) # minus the number of days - since_time = datetime.strftime(now + delta, '%c') - strptime = time.strptime(since_time) - date = time.mktime(strptime) - params['since'] = date + params['since'] = get_timestamp(since) request = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) return request.json() else: @@ -465,10 +460,11 @@ def refresh(consumer_key, pocket_access_token, archive_tag, replace_all_tags, re # ----------------- def stash(consumer_key, pocket_access_token, archive_tag, replace_all_tags, retain_tags, favorite, ignore_tags): + print('\033[0;36mStashing items...\033[0;m') # if ignore_faves is set to True, don't get favorite items + # TODO: here we need to check if before or since are set and adjust params accordingly 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') print('\033[0;36mSkipping favorited items...\033[0;m') else: params = {"consumer_key": consumer_key, "access_token": pocket_access_token, "detailType": "complete", "state": "unread"} From 41f9e69d309f040884cf97d37d3169196ed5e6fb Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 09:48:23 +1100 Subject: [PATCH 08/19] add since/before flags to stash --- main.py | 4 ++-- pocket_toolkit.py | 38 ++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/main.py b/main.py index 4681907..77be46e 100755 --- a/main.py +++ b/main.py @@ -215,11 +215,11 @@ def print_info(collection, items, longreads): elif options.refresh: print('Refreshing at ' + datetime.now().strftime('%a %d %b %Y %H:%M')) - refresh = pt.refresh(*refresh_settings) + refresh = pt.refresh(*refresh_settings, options.before, options.since) print('\033[0;36m' + refresh + '\033[0;m') 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) + 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: diff --git a/pocket_toolkit.py b/pocket_toolkit.py index e643a33..ba49f23 100755 --- a/pocket_toolkit.py +++ b/pocket_toolkit.py @@ -428,18 +428,19 @@ def purge_tags(state, retain_tags, archive_tag, consumer_key, pocket_access_toke # 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' + +# 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, before, since): +# # 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, before, since) +# 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' """ Stash @@ -459,15 +460,20 @@ 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 # TODO: here we need to check if before or since are set and adjust params accordingly + 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"} + 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"} + if before: + timestamp = get_timestamp(before) + params['since'] = timestamp + elif since: + timestamp = get_timestamp(since) + params['since'] = timestamp def run_stash(attempts): if connection_live() == True: From 7e8ad415026c20dfa5168821edadca6ce072a0b9 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 10:32:23 +1100 Subject: [PATCH 09/19] fix stash so before actually works --- main.py | 11 ++-- pocket_toolkit.py | 150 +++++++++++++++++++++++++--------------------- 2 files changed, 88 insertions(+), 73 deletions(-) diff --git a/main.py b/main.py index 77be46e..930041b 100755 --- a/main.py +++ b/main.py @@ -137,7 +137,7 @@ 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) + 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: @@ -213,10 +213,11 @@ def print_info(collection, items, longreads): 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.refresh: - print('Refreshing at ' + datetime.now().strftime('%a %d %b %Y %H:%M')) - refresh = pt.refresh(*refresh_settings, options.before, options.since) - print('\033[0;36m' + refresh + '\033[0;m') + + # elif options.refresh: + # print('Refreshing at ' + datetime.now().strftime('%a %d %b %Y %H:%M')) + # refresh = pt.refresh(*refresh_settings, options.before, options.since) + # print('\033[0;36m' + refresh + '\033[0;m') 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) diff --git a/pocket_toolkit.py b/pocket_toolkit.py index ba49f23..aa8ff8d 100755 --- a/pocket_toolkit.py +++ b/pocket_toolkit.py @@ -76,7 +76,6 @@ def connection_live(): # make a unix timestamp for before/after flags with Pocket's 'since' param def get_timestamp(since): - # TODO: make this a function we can use for lucky_dip and stash now = datetime.now() delta = timedelta(days=since) since_time = datetime.strftime(now - delta, '%c') @@ -192,7 +191,7 @@ def info(consumer_key, pocket_access_token, archive_tag, before, since): return request.json() # 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: @@ -213,6 +212,9 @@ 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} + + # TODO: check before and since + request = get(params) tbr = request.json()['list'] @@ -463,86 +465,98 @@ def purge_tags(state, retain_tags, archive_tag, consumer_key, pocket_access_toke 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 - # TODO: here we need to check if before or since are set and adjust params accordingly params = {"consumer_key": consumer_key, "access_token": pocket_access_token, "detailType": "complete", "state": "unread"} if favorite: params['favorite'] = "0" print('\033[0;36mSkipping favorited items...\033[0;m') - if before: - timestamp = get_timestamp(before) - params['since'] = timestamp - elif since: - timestamp = get_timestamp(since) - params['since'] = timestamp + # before or since flags exist, add the 'since' param + # TESTING: check 'before' runs properly 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 + + 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 + + elif since: + timestamp = get_timestamp(since) + params['since'] = timestamp + item_list = get(params).json()['list'] + else: + item_list = get(params).json()['list'] + + # 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) From 704f21e64e98d2666f2b970f6cffd400495d232b Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 11:00:03 +1100 Subject: [PATCH 10/19] add before/since flags for lucky_dip --- pocket_toolkit.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/pocket_toolkit.py b/pocket_toolkit.py index aa8ff8d..58cb89a 100755 --- a/pocket_toolkit.py +++ b/pocket_toolkit.py @@ -212,11 +212,26 @@ 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} + + 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' + tbr = 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(): + tbr.pop(key, None) # remove everything from items_list that is in since_list + elif since: + timestamp = get_timestamp(since) + params['since'] = timestamp + tbr = get(params).json()['list'] + else: + tbr = get(params).json()['list'] - # TODO: check before and since - - request = get(params) - tbr = request.json()['list'] + # request = get(params) + # tbr = request.json()['list'] # before we go any further, make sure there actually is something in the TBR list! if len(tbr) > 0: @@ -370,7 +385,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 before ' + str(before) + ' days ago ' + if since: + caveat = 'last updated after ' + 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: @@ -469,13 +489,10 @@ def stash(consumer_key, pocket_access_token, archive_tag, replace_all_tags, reta if favorite: params['favorite'] = "0" print('\033[0;36mSkipping favorited items...\033[0;m') - # before or since flags exist, add the 'since' param - # TESTING: check 'before' runs properly def run_stash(attempts): if connection_live() == True: - # GET the list - + # GET the list, first checking for before/after flags if before: all_items = get(params) params['since'] = get_timestamp(before) @@ -486,7 +503,6 @@ def run_stash(attempts): 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 - elif since: timestamp = get_timestamp(since) params['since'] = timestamp From 1242a16accc827b77d6a2067f52d42ef72409b96 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 11:57:04 +1100 Subject: [PATCH 11/19] clean up --info using new get_item_list function --- main.py | 67 ++++++++++------------------ pocket_toolkit.py | 108 ++++++++++++---------------------------------- 2 files changed, 50 insertions(+), 125 deletions(-) diff --git a/main.py b/main.py index 930041b..cf1986a 100755 --- a/main.py +++ b/main.py @@ -100,9 +100,6 @@ 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( - "-r", "--refresh", action="store_true", help="run 'stash' and then 'lucky_dip' in one operation" -) 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" ) @@ -142,53 +139,39 @@ elif options.info: - def print_info(collection, items, longreads): + def print_info(response, collection): + items = str(len(response)) + longreads = 0 + for item in response: + # is it a long read? + 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 + if options.before: - print(collection + 'has ' + items + ' items ' + 'updated prior to ' + str(options.before) + ' days ago and ' + longreads + ' are longreads.') + 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 ' + longreads + ' are longreads.') + print(collection + 'has ' + items + ' items ' + 'updated since ' + str(options.since) + ' days ago and ' + str(longreads) + ' are longreads.') else: - print(collection + 'has ' + items + ' items and ' + longreads + ' are longreads.') + print(collection + 'has ' + items + ' items and ' + str(longreads) + ' are longreads.') if options.archive: - collection = 'The TBR archive ' - # Retrieve info about the user's list response = pt.info(consumer_key, settings.pocket_access_token, archive_tag, options.before, options.since) - if 'list' in response: - 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_info(collection, str(len(items)), str(longreads)) + if len(response) > 0: + print_info(response, 'The TBR archive ') else: - print('No items match that query') + print('No items match that query') elif options.list: - collection = 'The user List ' - # Retrieve info about the user's list response = pt.info(consumer_key, settings.pocket_access_token, False, options.before, options.since) - if 'list' in response: - 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_info(collection, str(len(items)), str(longreads)) + if len(response) > 0: + print_info(response, 'The user List ') else: - print('No items match that query') + print('No items match that query') else: 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') @@ -213,12 +196,6 @@ def print_info(collection, items, longreads): 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.refresh: - # print('Refreshing at ' + datetime.now().strftime('%a %d %b %Y %H:%M')) - # refresh = pt.refresh(*refresh_settings, options.before, options.since) - # print('\033[0;36m' + refresh + '\033[0;m') - 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') diff --git a/pocket_toolkit.py b/pocket_toolkit.py index 58cb89a..9346fab 100755 --- a/pocket_toolkit.py +++ b/pocket_toolkit.py @@ -82,6 +82,25 @@ def get_timestamp(since): 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 # -------------------- @@ -167,29 +186,10 @@ def info(consumer_key, pocket_access_token, archive_tag, before, since): else: # state is unread params['state'] = 'unread' - if before: - # get all items using params - all_items = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) - params['since'] = get_timestamp(before) - since_items = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) - # get non-intersection of 2 groups to get only items changed 'before' - items_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(): - items_list.pop(key, None) # remove everything from items_list that is in since_list - return_value = {} - return_value['list'] = items_list # reconstruct the dict - return return_value - elif since: - params['since'] = get_timestamp(since) - request = requests.post('https://getpocket.com/v3/get', headers=headers, params=params) - return request.json() - else: - # if no before/since params just gert everything in either the list or archive according to params - 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, before, since): @@ -213,25 +213,7 @@ 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} - 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' - tbr = 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(): - tbr.pop(key, None) # remove everything from items_list that is in since_list - elif since: - timestamp = get_timestamp(since) - params['since'] = timestamp - tbr = get(params).json()['list'] - else: - tbr = get(params).json()['list'] - - # 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: @@ -387,9 +369,9 @@ def readd(selection): # add this to the end regardless caveat = '' if before: - caveat = 'last updated before ' + str(before) + ' days ago ' + caveat = 'last updated earlier than ' + str(before) + ' days ago ' if since: - caveat = 'last updated after ' + str(since) + ' days ago ' + 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 @@ -446,24 +428,6 @@ def purge_tags(state, retain_tags, archive_tag, consumer_key, pocket_access_toke 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, before, since): -# # 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, before, since) -# 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' - """ Stash @@ -492,24 +456,8 @@ def stash(consumer_key, pocket_access_token, archive_tag, replace_all_tags, reta def run_stash(attempts): if connection_live() == True: - # GET the list, first checking for before/after flags - 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 - elif since: - timestamp = get_timestamp(since) - params['since'] = timestamp - item_list = get(params).json()['list'] - else: - item_list = get(params).json()['list'] - + # 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 From f453fbb46e9fdb5b3ccd6f75c5d12a83444dab39 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 12:49:16 +1100 Subject: [PATCH 12/19] add before/since flags to purge --- main.py | 6 +++--- pocket_toolkit.py | 43 ++++++++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/main.py b/main.py index cf1986a..49da8ad 100755 --- a/main.py +++ b/main.py @@ -180,17 +180,17 @@ def print_info(response, collection): 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) + 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) + 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) + purge = pt.purge_tags('all', settings.retain_tags, archive_tag, consumer_key, settings.pocket_access_token, options.before, options.since) print(purge) else: diff --git a/pocket_toolkit.py b/pocket_toolkit.py index 9346fab..3960185 100755 --- a/pocket_toolkit.py +++ b/pocket_toolkit.py @@ -393,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, @@ -405,28 +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' + + 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 From fbb3646a63055818e5377e7c8e76fdff74964d0e Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 16:14:11 +1100 Subject: [PATCH 13/19] remove scheduling from setup script --- .gitignore | 5 +- daily.plist | 36 --------------- hourly.plist | 34 -------------- install.sh | 128 --------------------------------------------------- main.py | 18 +++++--- 5 files changed, 15 insertions(+), 206 deletions(-) delete mode 100644 daily.plist delete mode 100644 hourly.plist 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/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..f414997 100644 --- a/install.sh +++ b/install.sh @@ -21,134 +21,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 49da8ad..5dff3c3 100755 --- a/main.py +++ b/main.py @@ -25,15 +25,11 @@ # 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 @@ -109,6 +105,9 @@ admin.add_argument( "-u", "--authorise", action="store_true", help="authorise app to connect to a Pocket account" ) +admin.add_argument( + "--schedule", action="store_true", help="schedule pocketsnack commands to run automatically" +) options = parser.parse_args() @@ -203,7 +202,12 @@ def print_info(response, collection): elif options.test: result = pt.test(consumer_key, settings.pocket_access_token) print(result) - + + elif options.schedule: + print('scheduling...') + path = os.path.dirname(os.path.realpath(__file__)) + subprocess.Popen(['./schedule.sh'], cwd=path) + 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') From ba827fc5c69e55a56abcb5c40180b9d90bb18a35 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 18:12:20 +1100 Subject: [PATCH 14/19] add requests check to install script --- install.sh | 1 + main.py | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/install.sh b/install.sh index f414997..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 diff --git a/main.py b/main.py index 5dff3c3..08213b8 100755 --- a/main.py +++ b/main.py @@ -105,9 +105,6 @@ admin.add_argument( "-u", "--authorise", action="store_true", help="authorise app to connect to a Pocket account" ) -admin.add_argument( - "--schedule", action="store_true", help="schedule pocketsnack commands to run automatically" -) options = parser.parse_args() @@ -203,11 +200,6 @@ def print_info(response, collection): result = pt.test(consumer_key, settings.pocket_access_token) print(result) - elif options.schedule: - print('scheduling...') - path = os.path.dirname(os.path.realpath(__file__)) - subprocess.Popen(['./schedule.sh'], cwd=path) - 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') From 82bfdeee755d06bb7869f1079cc6efddb746ea16 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 19:26:07 +1100 Subject: [PATCH 15/19] update README --- README.md | 82 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 26d5b81..89186c9 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # 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. -## Getting started +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. -### tl;dr +## Getting started -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 4. run `bash install.sh` and follow the prompts @@ -20,7 +24,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 +41,77 @@ 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]`. -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. +### -h, --help -If you use launchd, log files for stdout and errors will be created wherever you saved _pocketsnack_. +Outputs help for each command -To run commands manually, use `pocketsnack [command]`. +## admin commands -## commands +### -t, --test -### archive +Outputs the first article returned by a call to the API. Normally you will never need to use this. -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`. +### -u, --authorise -### authorise - -This has an 's', not a 'z'. +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. 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`. -### list +## action commands -Same as _archive_ but for your list instead of your archive. +### -d, --lucky_dip -### lucky_dip +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. -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. - -### purge_tags +### -p, --purge 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_tags` requires a second argument: `list`, `archive`, or `all`, depending on where you want to purge tags. +`purge_tags` requires a second argument: `--list`, `--archive`, or `--all`, depending on where you want to purge tags. -### refresh +### -s, --stash -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`. +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. -### stash +## optional flags -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. +### -a, --archive -## Uninstalling +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`. Use with `--info`. -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: +### -l, --list -1. If you set up `refresh` on a Mac you should unload the plist file: +Same as _archive_ but for your list instead of your archive. - `launchctl unload ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist` +### -b, -all -2. Once unloaded, you can delete it: +For use with `--purge` - purge tags from both the List and the Archive. - `rm ~/Library/LaunchAgents/com.getpocket.pocketsnack.plist` +### -n, --since SINCE + +Restrict the current action to only items updated more recently than _SINCE_ number of days, + +### -o, --before BEFORE + +Restrict the current action to only items updated less recently than _BEFORE_ number of days, + +## 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 From b2ba61c8b74069c7fd3338e87279fdcf71906f9b Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 19:33:57 +1100 Subject: [PATCH 16/19] update v2 README --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 89186c9..5ce1e7d 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,11 @@ The automation of `pocketsnack refresh` has _also_ been removed. This didn't rea ## Getting started +### Quick version + 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 @@ -63,7 +65,7 @@ Outputs the first article returned by a call to the API. Normally you will never 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. 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`. +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 @@ -85,7 +87,7 @@ Adds the archive tag to everything in your list, and then archives them. Dependi ### -a, --archive -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`. Use with `--info`. +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. ### -l, --list @@ -93,15 +95,15 @@ Same as _archive_ but for your list instead of your archive. ### -b, -all -For use with `--purge` - purge tags from both the List and the Archive. +For use with `--purge` - purge tags from _both_ the List and the Archive. ### -n, --since SINCE -Restrict the current action to only items updated more recently than _SINCE_ number of days, +Restrict the current _action command_ to only items updated more recently than _SINCE_ number of days, ### -o, --before BEFORE -Restrict the current action to only items updated less recently than _BEFORE_ number of days, +Restrict the current _action command_ to only items updated less recently than _BEFORE_ number of days, ## Uninstalling or moving to a new directory From 39811c5c8eea04028c4751d57258853b253caa16 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 19:39:39 +1100 Subject: [PATCH 17/19] update v2 README --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5ce1e7d..58e6c7e 100644 --- a/README.md +++ b/README.md @@ -99,11 +99,33 @@ For use with `--purge` - purge tags from _both_ the List and the Archive. ### -n, --since SINCE -Restrict the current _action command_ to only items updated more recently than _SINCE_ number of days, +Restrict the current _action command_ to only items updated more recently than _SINCE_ number of days. ### -o, --before BEFORE -Restrict the current _action command_ to only items updated less recently than _BEFORE_ number of days, +Restrict the current _action command_ to only items updated less recently than _BEFORE_ number of days. + +## examples + +Stash only items updated in the last 2 days: + +`pocketsnack --stash -n 2` + +Stash only items NOT updated in the last 7 days: + +`pocketsnack --stash -o 7` + +Purge tags on all items in the List that were updated in the last day: + +`pocketsnack -pn 1` + +Run lucky_dip: + +`pocketsnack --lucky_dip` + +Run lucky_dip but only choose from items last updated longer ago than one week: + +`pocketsnack -l -o 7` ## Uninstalling or moving to a new directory From 71cf77316390f387bd9d6a84e3c7f388276896b0 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 19:40:33 +1100 Subject: [PATCH 18/19] fix incorrect command in README example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58e6c7e..f0f30ea 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Run lucky_dip: Run lucky_dip but only choose from items last updated longer ago than one week: -`pocketsnack -l -o 7` +`pocketsnack -d -o 7` ## Uninstalling or moving to a new directory From 228e7aad2cf03518771de7f42bc5574fc105c04f Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 17 Nov 2019 19:54:20 +1100 Subject: [PATCH 19/19] update README with more detail on --purge --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f0f30ea..3aff33c 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,9 @@ Returns items with the archive tag from the archive to the list, and removes the 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_tags` requires a second argument: `--list`, `--archive`, or `--all`, depending on where you want to purge tags. +`--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`. ### -s, --stash @@ -117,7 +119,7 @@ Stash only items NOT updated in the last 7 days: Purge tags on all items in the List that were updated in the last day: -`pocketsnack -pn 1` +`pocketsnack -pln 1` Run lucky_dip: