From bb35d88f7aa680fb19ba768b81584356e33c1775 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 14 Feb 2021 10:56:44 +1100 Subject: [PATCH 1/2] make initial config creation friendlier Now asks if you want to create a config file rather than just opening the text editor. --- pocketsnack/pocketsnack.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pocketsnack/pocketsnack.py b/pocketsnack/pocketsnack.py index 17f460c..d3d940b 100644 --- a/pocketsnack/pocketsnack.py +++ b/pocketsnack/pocketsnack.py @@ -299,5 +299,9 @@ def print_info(response, collection): except FileNotFoundError: print(' \033[46;97mpocketsnack\033[0;m needs a config file!') - conf = pt.config() - print(conf) + user_input = input(' Do you want to create one now using your default text editor (y/n)?') + if user_input in ["y", "yes", "Y", "Yes", "YES"]: + conf = pt.config() + print(conf) + else: + print(' Some other time then.') From 582e774dff692ef7a66d56916f420c87cfa1e8c7 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 14 Feb 2021 12:12:21 +1100 Subject: [PATCH 2/2] fave originals when deduping - add option in config to fave originals when deduping - make config creation friendlier - make incomplete config messages more helpful fixes #39 --- README.md | 3 ++- pocketsnack/pocketsnack.py | 10 ++++++-- pocketsnack/toolkit.py | 43 ++++++++++++++++++++++++++------ poetry.lock | 50 +++++++++++++++++++------------------- pyproject.toml | 4 +-- 5 files changed, 72 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 45176c8..410c04a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is the version 3.x documentation. If you prefer to use an older version you ## tl;dr -1. make sure you have installed Python version 3.x (preferably 3.7 or higher) +1. make sure you have installed Python version 3.6.0 or higher. 2. `pip install pocketsnack` (you may need to use `pip3` instead) 3. `pocketsnack --config` 4. Add your Pocket API consumer key to the config file @@ -35,6 +35,7 @@ You can adjust most settings, but the defaults should be sensible for most users | archive_tag | string | the tag to use to identify items in your 'to be read' archive| | ignore_tags | list | a list of tag names - items with any of these tags will be ignored by `--stash` and remain in your Pocket List| | ignore_faves | boolean | if set to `true` favorited items will be ignored by `--stash` and remain in your Pocket List| +| fave_dupes | boolean | if set to `true` the remaining (original) item will be favorited when duplicates are removed with `--dedupe`| | replace_all_tags | boolean | if set to `true` all tags will be removed by `--stash` when adding the `archive_tag`, except anything in `retain_tags`| | retain_tags | list | a list of tag names - these tags will not be removed by `--purge`, nor by `--stash` if `replace_all_tags` is set to `true`| | longreads_wordcount | integer | determines how long a 'longread' is. | diff --git a/pocketsnack/pocketsnack.py b/pocketsnack/pocketsnack.py index d3d940b..b1e4b2a 100644 --- a/pocketsnack/pocketsnack.py +++ b/pocketsnack/pocketsnack.py @@ -50,6 +50,9 @@ def main(): if vars(options)[x]: true_vars.append(x) + if S['pocket_consumer_key'] == 'YOUR_KEY_HERE': + print(' ⚠️ You have not set a pocket_consumer_key in your configuration file. Run \033[46;97mpocketsnack --config\033[0;m or check the README for help.') + if options.config: conf = pt.config() print(conf) @@ -69,7 +72,7 @@ def main(): location = tag if tag else state if state != 'unread' else 'list' print(' \033[46;97mChecking for duplicates in ' + location + '\033[0;m') - pt.dedupe(state, tag, consumer_key, access_token) + pt.dedupe(state, tag, S['fave_dupes'], consumer_key, access_token) elif options.lucky_dip: print(' \033[46;97mRunning lucky dip...\033[0;m') @@ -216,6 +219,9 @@ def print_info(response, collection): # we do nothing here pass + except ValueError: + print(" 😳 Whoops, look's like there is a problem with your config file. Try \033[46;97mpocketsnack --config\033[0;m to fix this") + # ----------------------------------- # Parse commands (the action is here) # ----------------------------------- @@ -258,7 +264,7 @@ def print_info(response, collection): "-d", "--lucky_dip", action="store_true", help="move random items tagged 'tbr' from archive to list, depending on config" ) actions.add_argument( - "--dedupe", action="store_true", help="de-duplicate list (-l), archive (-a), tbr items (--tbr) or all (-b)- defaults to list" + "--dedupe", action="store_true", help="de-duplicate list (-l), archive (-a), tbr items (--tbr) or all (-b). Defaults to list" ) actions.add_argument( "-i", "--info", action="store_true", help="get information on items in list or TBR items in archive" diff --git a/pocketsnack/toolkit.py b/pocketsnack/toolkit.py index 43170ca..7dd4d9c 100755 --- a/pocketsnack/toolkit.py +++ b/pocketsnack/toolkit.py @@ -140,6 +140,7 @@ def config(): new_config.write('ignore_tags: \n') new_config.write(' - ignore\n') new_config.write('ignore_faves: true\n') + new_config.write('fave_dupes: true\n') new_config.write('replace_all_tags: false\n') new_config.write('retain_tags:\n') new_config.write(' - glam blog club\n') @@ -172,7 +173,7 @@ def config(): else: subprocess.call(["xdg-open", config_file]) - return 'Config file opened for editing.' + return ' Config file opened for editing' # ---------------- # Authorise @@ -607,7 +608,7 @@ def test(consumer_key, pocket_access_token): # de-duplicate # ----------------- -def dedupe(state, tag, consumer_key, pocket_access_token): +def dedupe(state, tag, fave_dupes, consumer_key, pocket_access_token): # Retrieving items # ---------------- @@ -635,6 +636,8 @@ def dedupe(state, tag, consumer_key, pocket_access_token): # and make a list called 'items_to_delete' items_to_delete = [] + # the originals will be faved if the config file says to do that + items_to_fave = [] # loop over each key (not the whole object) in item_list # 'item' here refers to each item's key, not the whole object/dictionary @@ -675,6 +678,12 @@ def dedupe(state, tag, consumer_key, pocket_access_token): print(' \033[46;97m' + item + '\033[0;m occurs ' + str(len(summary[item])) + ' times') # keep only the most recently added item by slicing the list to make a new list of everything except the last one (which will be the *first* one that was found) duplicates = summary[item][:-1] + + # TODO: optionally fave the dupe we're keeping + if fave_dupes: + # get last item (which we are keeping) and add to faving list + items_to_fave.append(summary[item][-1]) + # add each duplicate in the duplicates list for this url to the items_to_delete list for item in duplicates: items_to_delete.append(item) @@ -687,21 +696,39 @@ def dedupe(state, tag, consumer_key, pocket_access_token): # With our list of duplicate item ids, we create one final list of a bunch of JSON objects actions = [] - + faves = [] # for each item to be deleted, append a dictionary to the actions list for item_id in items_to_delete: actions.append({"action":"delete", "item_id": item_id}) + for item_id in items_to_fave: + faves.append({"action":"favorite", "item_id": item_id}) + + # Turn the lists and component dictionaries into JSON strings + actions_string = json.dumps(actions) + faves_string = json.dumps(faves) + # Now URL encode it using urllib + actions_escaped = urllib.parse.quote(actions_string) + faves_escaped = urllib.parse.quote(faves_string) # Double check you really want to delete them if len(actions) > 0: - print(' \033[107;95mAbout to delete ' + str(len(actions)) + ' duplicate items.\033[0;m') + if fave_dupes: + print(' \033[107;95mAbout to delete ' + str(len(actions)) + ' duplicate items and favorite the originals.\033[0;m') + else: + print(' \033[107;95mAbout to delete ' + str(len(actions)) + ' duplicate items.\033[0;m') print(' \033[107;95mDelete these items? Type "delete" to confirm.\033[0;m') check = input('>>') if check == 'delete': - # first turn the list and its component dictionaries into a JSON string - actions_string = json.dumps(actions) - # now URL encode it using urllib - actions_escaped = urllib.parse.quote(actions_string) + + if fave_dupes: + # fave remaining + faved = send(faves_escaped, consumer_key, pocket_access_token) + if str(faved) == '': + print(' \033[46;97mRemaining duplicates faved...\033[0;m') + else: + print(' \033[46;97mSomething went wrong favoriting your dupes 😟\033[0;m') + print(faved.data) + # now POST to pocket and assign the response to a parameter at the same time. deleted = send(actions_escaped, consumer_key, pocket_access_token) # provide feedback on what happened diff --git a/poetry.lock b/poetry.lock index 4250834..594bfbb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,50 +1,50 @@ [[package]] -category = "main" -description = "Python command-line parsing library" name = "argparse" +version = "1.4.0" +description = "Python command-line parsing library" +category = "main" optional = false python-versions = "*" -version = "1.4.0" [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = "*" -version = "2020.12.5" [[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.0.0" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" [[package]] -category = "main" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -version = "5.4.1" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.25.1" [package.dependencies] certifi = ">=2017.4.17" @@ -54,25 +54,25 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.26.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.26.3" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] -content-hash = "a7e71ab52386abe8827ed9d2efa499eebb06c91403f5a8f937136dd062eb915d" lock-version = "1.1" -python-versions = "^3.7" +python-versions = "^3.6" +content-hash = "93d2f2040b9797788f09412564d681cbcd61957e0f8042af7df31356b2df8b26" [metadata.files] argparse = [ diff --git a/pyproject.toml b/pyproject.toml index 133d48f..77bb513 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pocketsnack" -version = "3.1.0a" +version = "3.1.0b0" description = "KonMari your Pocket tsundoku from the command line" authors = ["Hugh Rundle "] license = "GPL-3.0-or-later" @@ -18,7 +18,7 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.7" +python = "^3.6" requests = "^2.25.1" argparse = "^1.4.0" pyyaml = "^5.4.1"