Description: Autogenerated patch header for a single-debian-patch file.
 The delta against upstream is either kept as a single patch, or maintained
 in some VCS, and exported as a single patch instead of more manageable
 atomic patches.
Forwarded: not-needed

---
--- gmailieer-1.6.orig/.github/workflows/python-test.yml
+++ gmailieer-1.6/.github/workflows/python-test.yml
@@ -19,7 +19,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.9", "3.10", "3.11"]
+        python-version: ["3.9", "3.10", "3.11", "3.12"]
 
     steps:
     - uses: actions/checkout@v3
@@ -40,11 +40,7 @@ jobs:
         if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
     - name: Lint with ruff
       run: |
-        # default set of ruff rules with GitHub Annotations
-        # E501 Line too long
-        # E741 Ambiguous variable name: `l`
-        # F403 `from .gmailieer import *` used; unable to detect undefined names
-        ruff --ignore=E501,E741,F403 --output-format=github --target-version=py39 .
+        ruff --output-format=github .
     - name: Test with pytest
       run: |
         pytest -v tests
@@ -66,7 +62,7 @@ jobs:
     if: "startsWith(github.ref, 'refs/tags/')"
     needs: [ tests ]
     steps:
-      - uses: actions/download-artifact@v2
+      - uses: actions/download-artifact@v4.1.7
         with:
           name: wheels
       - uses: conda-incubator/setup-miniconda@v2
--- /dev/null
+++ gmailieer-1.6/.ruff.toml
@@ -0,0 +1,63 @@
+# Exclude a variety of commonly ignored directories.
+exclude = [
+    ".bzr",
+    ".direnv",
+    ".eggs",
+    ".git",
+    ".git-rewrite",
+    ".hg",
+    ".mypy_cache",
+    ".nox",
+    ".pants.d",
+    ".pytype",
+    ".ruff_cache",
+    ".svn",
+    ".tox",
+    ".venv",
+    "__pypackages__",
+    "_build",
+    "buck-out",
+    "build",
+    "dist",
+    "node_modules",
+    "venv",
+]
+
+# Same as Black.
+line-length = 88
+indent-width = 4
+
+# Assume Python 3.9 -- sync with GitHub testing matrix
+target-version = "py39"
+
+[lint]
+# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`)  codes by default.
+# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
+# McCabe complexity (`C901`) by default.
+select = ["E4", "E7", "E9", "F", "I", "UP", "S", "C4", "EXE", "SIM", "PERF"]
+ignore = [
+    "E501",  # Line too long
+    "E741",  # Ambiguous variable name: `l`
+    "F403",  # `from .gmailieer import *` used; unable to detect undefined names
+    "S101",  # Use of `assert` detected
+]
+
+# Allow fix for all enabled rules (when `--fix`) is provided.
+fixable = ["ALL"]
+unfixable = []
+
+# Allow unused variables when underscore-prefixed.
+dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
+
+[format]
+# Like Black, use double quotes for strings.
+quote-style = "double"
+
+# Like Black, indent with spaces, rather than tabs.
+indent-style = "space"
+
+# Like Black, respect magic trailing commas.
+skip-magic-trailing-comma = false
+
+# Like Black, automatically detect the appropriate line ending.
+line-ending = "auto"
--- gmailieer-1.6.orig/docs/index.md
+++ gmailieer-1.6/docs/index.md
@@ -20,7 +20,7 @@ While Lieer has been used to successfull
 ## Requirements
 
 * Python 3
-* `notmuch >= 0.25` python bindings
+* Notmuch 0.30+ for `notmuch2` Python bindings
 * `google_api_python_client` (sometimes `google-api-python-client`)
 * `google_auth_oauthlib`
 * `tqdm` (optional - for progress bar)
@@ -34,36 +34,35 @@ This assumes your root mail folder is in
 
 1. Make a directory for the lieer storage and state files (local repository).
 
-```sh
-$ cd    ~/.mail
-$ mkdir account.gmail
-$ cd    account.gmail/
-```
-
-All commands should be run from the local mail repository unless otherwise specified.
+   ```sh
+   $ cd    ~/.mail
+   $ mkdir account.gmail
+   $ cd    account.gmail/
+   ```
 
+   All commands should be run from the local mail repository unless otherwise specified.
 
 2. Ignore the `.json` files in notmuch. Any tags listed in `new.tags` will be added to newly pulled messages. Process tags on new messages directly after running gmi, or run `notmuch new` to trigger the `post-new` hook for [initial tagging](https://notmuchmail.org/initial_tagging/). The `new.tags` are not ignored by default if you do not remove them, but you can prevent custom tags from being pushed to the remote by using e.g. `gmi set --ignore-tags-local new`. In your notmuch config file (usually `~/.notmuch-config`):
 
-```
-[new]
-tags=new
-ignore=/.*[.](json|lock|bak)$/
-```
+   ```
+   [new]
+   tags=new
+   ignore=/.*[.](json|lock|bak)$/
+   ```
 
 3. Initialize the mail storage:
 
-```sh
-$ gmi init your.email@gmail.com
-```
+   ```sh
+   $ gmi init your.email@gmail.com
+   ```
 
-`gmi init` will now open your browser and request limited access to your e-mail.
+   `gmi init` will now open your browser and request limited access to your e-mail.
 
-> The access token is stored in `.credentials.gmailieer.json` in the local mail repository. If you wish, you can specify [your own api key](#using-your-own-api-key) that should be used.
+   > The access token is stored in `.credentials.gmailieer.json` in the local mail repository. If you wish, you can specify [your own api key](#using-your-own-api-key) that should be used.
 
 4. You're now set up, and you can do the initial pull.
 
-> Use `gmi -h` or `gmi command -h` to get more usage information.
+   > Use `gmi -h` or `gmi command -h` to get more usage information.
 
 ## Pull
 
@@ -111,6 +110,9 @@ Lieer may be used as a simple stand-in f
 gmi send -t -C ~/.mail/account.gmail
 ```
 
+For example, you can enable git send-email with `git config sendemail.sendmailcmd "gmi send -t -C
+$HOME/.mail/account.gmail"`.
+
 Like the real sendmail program, the raw message is read from `stdin`.
 
 Most sendmail implementations allow passing additional recipients in additional
@@ -227,12 +229,31 @@ We translate some of the GMail labels to
 
 The 'trash' local tag can be replaced using the `--local-trash-tag` option.
 
-# Using your own API key
+# Using your own OAuth 2 Client
+
+Lieer ships with a set of OAuth 2 credentials that is shared openly, this shares API quota, but [cannot be used to access data](https://github.com/gauteh/lieer/pull/9) unless access is gained to your private `access_token` or `refresh_token`.
+
+The minor risk of using these public OAuth 2 credentials is that a malicious application running on your computer could pretend to be the lieer application, and gain access to your Gmail account. Since that already involves a malicious application running on your computer, it could likely just read the existing `.credentials.gmailieer.json` file directly, making this attack not particularly interesting.
+
+In any case, you can generate your own OAuth 2 credentials instead. This requires you have a Google account and access to Google Cloud Platform.
+
+1. [Create a project on GCP](https://cloud.google.com/resource-manager/docs/creating-managing-projects), or go to an existing one.
+2. [Enable access to the Gmail API](https://console.developers.google.com/flows/enableapi?apiid=gmail) for that GCP project
+3. [Configure the OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent), which involves naming your application (e.g. "Lieer Gmail Access") and setting other app-related metadata, most of which will be shown when you visit the OAuth 2 consent screen to give Lieer access to your Gmail account.
+    - The important piece is to make sure to configure the `https://mail.google.com/` scope for your application, otherwise Lieer won't be able to access the Gmail API on your behalf.
+    - You **don't** need to verify your application, you'll just get a scary warning about unverified applications when you go through the OAuth 2 flow, which you can safely ignore.
+4. [Create the OAuth 2 credentials](https://console.cloud.google.com/apis/credentials) by selecting 'Create Credentials' > 'OAuth client ID'.
+    - For `Application type`, select `Desktop app`
+    - For `Name`, name it `Lieer` or `gmi client` or something you'll recognize
+
+![A screenshot of the GCP OAuth 2 credential creation dialog](create-credentials.png)
 
-Lieer ships with an API key that is shared openly, this key shares API quota, but [cannot be used to access data](https://github.com/gauteh/lieer/pull/9) unless access is gained to your private `access_token` or `refresh_token`.
+5. [Download the credentials](https://console.cloud.google.com/auth/clients) by clicking the little download icon next to your newly created OAuth 2 client ID
+    - This will download the `client_secret.json` file you need for Lieer.
 
-You can get an [api key](https://console.developers.google.com/flows/enableapi?apiid=gmail) for a CLI application to use for yourself. Store the `client_secret.json` file somewhere safe and specify it to `gmi auth -c`. You can do this on a repository that is already initialized, possibly using `-f` to force reauthorizing with the new client secrets.
+![A screenshot of a mouse hovering over the "Download JSON" icon](download-json.png)
 
+Store the `client_secret.json` file somewhere safe and specify it to `gmi auth -c`. You can do this on a repository that is already initialized, possibly using `-f` to force reauthorizing with the new client secrets.
 
 # Privacy policy
 
--- gmailieer-1.6.orig/lieer/gmailieer.py
+++ gmailieer-1.6/lieer/gmailieer.py
@@ -18,15 +18,16 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+import argparse
 import os
 import sys
-import argparse
+
 import googleapiclient
 import googleapiclient.errors
 import notmuch2
 
-from .remote import Remote
 from .local import Local
+from .remote import Remote
 
 
 class Gmailieer:
@@ -619,7 +620,7 @@ class Gmailieer:
 
         if self.list_labels:
             for k, l in self.remote.labels.items():
-                print("{0: <30} {1}".format(l, k))
+                print(f"{l: <30} {k}")
             return
 
         if self.force:
@@ -855,14 +856,12 @@ class Gmailieer:
                 previous.delete()
                 previous = self.load_resume(resume_file, last_id)
 
-        for mset in self.remote.all_messages():
-            (total, gids) = mset
-
-            self.bar.total = total
+        for total, gids in self.remote.all_messages():
+            if not self.args.quiet and self.bar:
+                self.bar.total = total
             self.bar_update(len(gids))
 
-            for m in gids:
-                message_gids.append(m["id"])
+            message_gids.extend(m["id"] for m in gids)
 
             if self.limit is not None and len(message_gids) >= self.limit:
                 break
@@ -938,10 +937,7 @@ class Gmailieer:
         """
 
         if len(msgids) > 0:
-            if resume:
-                total = len(msgids) + len(previous.meta_fetched)
-            else:
-                total = len(msgids)
+            total = len(msgids) + len(previous.meta_fetched) if resume else len(msgids)
 
             self.bar_create(leave=True, total=total, desc="receiving metadata")
 
@@ -1050,7 +1046,7 @@ class Gmailieer:
                         ", ".join(cli_recipients.difference(header_recipients))
                     )
                 )
-        elif not header_recipients == cli_recipients:
+        elif header_recipients != cli_recipients:
             raise ValueError(
                 "Recipients passed via sendmail(1) arguments ({}) differ from those in message headers ({}), perhaps you are missing the '-t' option?".format(
                     ", ".join(cli_recipients), ", ".join(header_recipients)
--- gmailieer-1.6.orig/lieer/local.py
+++ gmailieer-1.6/lieer/local.py
@@ -15,15 +15,16 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-import os
-import shutil
+import base64
 import fcntl
 import json
-import base64
-from pathlib import Path
+import os
+import shutil
 import tempfile
+from pathlib import Path
 
 import notmuch2
+
 from .remote import Remote
 
 
@@ -51,22 +52,20 @@ class Local:
 
     labels_translate_default = {v: k for k, v in translate_labels_default.items()}
 
-    ignore_labels = set(
-        [
-            "archive",
-            "arxiv",
-            "attachment",
-            "encrypted",
-            "signed",
-            "passed",
-            "replied",
-            "muted",
-            "mute",
-            "todo",
-            "Trash",
-            "voicemail",
-        ]
-    )
+    ignore_labels = {
+        "archive",
+        "arxiv",
+        "attachment",
+        "encrypted",
+        "signed",
+        "passed",
+        "replied",
+        "muted",
+        "mute",
+        "todo",
+        "Trash",
+        "voicemail",
+    }
 
     def update_translation(self, remote, local):
         """
@@ -123,10 +122,10 @@ class Local:
 
             if os.path.exists(self.config_f):
                 try:
-                    with open(self.config_f, "r") as fd:
+                    with open(self.config_f) as fd:
                         self.json = json.load(fd)
                 except json.decoder.JSONDecodeError:
-                    print("Failed to decode config file `{}`.".format(self.config_f))
+                    print(f"Failed to decode config file `{self.config_f}`.")
                     raise
             else:
                 self.json = {}
@@ -201,7 +200,7 @@ class Local:
             if len(t.strip()) == 0:
                 self.ignore_tags = set()
             else:
-                self.ignore_tags = set([tt.strip() for tt in t.split(",")])
+                self.ignore_tags = {tt.strip() for tt in t.split(",")}
 
             self.write()
 
@@ -209,7 +208,7 @@ class Local:
             if len(t.strip()) == 0:
                 self.ignore_remote_labels = set()
             else:
-                self.ignore_remote_labels = set([tt.strip() for tt in t.split(",")])
+                self.ignore_remote_labels = {tt.strip() for tt in t.split(",")}
 
             self.write()
 
@@ -267,20 +266,20 @@ class Local:
 
             if os.path.exists(self.state_f):
                 try:
-                    with open(self.state_f, "r") as fd:
+                    with open(self.state_f) as fd:
                         self.json = json.load(fd)
                 except json.decoder.JSONDecodeError:
-                    print("Failed to decode state file `{}`.".format(self.state_f))
+                    print(f"Failed to decode state file `{self.state_f}`.")
                     raise
 
             elif os.path.exists(config.config_f):
                 try:
-                    with open(config.config_f, "r") as fd:
+                    with open(config.config_f) as fd:
                         self.json = json.load(fd)
                 except json.decoder.JSONDecodeError:
-                    print("Failed to decode config file `{}`.".format(config.config_f))
+                    print(f"Failed to decode config file `{config.config_f}`.")
                     raise
-                if any(k in self.json.keys() for k in ["last_historyId", "lastmod"]):
+                if any(k in self.json for k in ["last_historyId", "lastmod"]):
                     migrate_from_config = True
             else:
                 self.json = {}
@@ -347,10 +346,8 @@ class Local:
             )
 
         if any(
-            [
-                not os.path.exists(os.path.join(self.md, mail_dir))
-                for mail_dir in ("cur", "new", "tmp")
-            ]
+            not os.path.exists(os.path.join(self.md, mail_dir))
+            for mail_dir in ("cur", "new", "tmp")
         ):
             raise Local.RepositoryException(
                 "local repository not initialized: could not find mail dir structure"
@@ -368,7 +365,7 @@ class Local:
 
         ## Lock repository
         try:
-            self.lckf = open(".lock", "w")
+            self.lckf = open(".lock", "w")  # noqa: SIM115
             if block:
                 fcntl.lockf(self.lckf, fcntl.LOCK_EX)
             else:
@@ -564,7 +561,7 @@ class Local:
         except LookupError:
             nmsg = None
 
-        self.print_changes("deleting %s: %s." % (gid, fname))
+        self.print_changes(f"deleting {gid}: {fname}.")
 
         if not self.dry_run:
             if nmsg is not None:
@@ -681,9 +678,7 @@ class Local:
             nmsg = None
 
         if nmsg is None:
-            self.print_changes(
-                "adding message: %s: %s, with tags: %s" % (gid, fname, str(labels))
-            )
+            self.print_changes(f"adding message: {gid}: {fname}, with tags: {labels}")
             if not self.dry_run:
                 try:
                     (nmsg, _) = db.add(fname, sync_flags=True)
@@ -720,8 +715,7 @@ class Local:
                     self.__update_cache__(nmsg, (gid, fname))
 
                 self.print_changes(
-                    "changing tags on message: %s from: %s to: %s"
-                    % (gid, str(otags), str(labels))
+                    f"changing tags on message: {gid} from: {str(otags)} to: {str(labels)}"
                 )
 
                 return True
--- gmailieer-1.6.orig/lieer/nobar.py
+++ gmailieer-1.6/lieer/nobar.py
@@ -1,5 +1,3 @@
-#! /usr/bin/env python3
-#
 # Regular non-TTY drop-in replacement for tqdm
 #
 # Copyright © 2020  Gaute Hope <eg@gaute.vetsj.com>
--- gmailieer-1.6.orig/lieer/remote.py
+++ gmailieer-1.6/lieer/remote.py
@@ -15,13 +15,15 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+import json
 import os
 import time
+
 import googleapiclient
 from apiclient import discovery
-from google_auth_oauthlib.flow import InstalledAppFlow
-from google.oauth2.credentials import Credentials
 from google.auth.transport.requests import Request
+from google.oauth2.credentials import Credentials
+from google_auth_oauthlib.flow import InstalledAppFlow
 
 
 class Remote:
@@ -73,8 +75,8 @@ class Remote:
     ]
 
     # these cannot be changed manually
-    read_only_labels = set(["SENT", "DRAFT"])
-    read_only_tags = set(["sent", "draft"])
+    read_only_labels = {"SENT", "DRAFT"}
+    read_only_tags = {"sent", "draft"}
 
     DEFAULT_IGNORE_LABELS = [
         "CATEGORY_PERSONAL",
@@ -89,7 +91,7 @@ class Remote:
     # query to use
     query = "-in:chats"
 
-    not_sync = set(["CHAT"])
+    not_sync = {"CHAT"}
 
     # used to indicate whether all messages that should be updated where updated
     all_updated = True
@@ -505,8 +507,23 @@ class Remote:
         """
         Store valid credentials in json format
         """
-        with open(path, "w") as storage:
-            storage.write(credentials.to_json())
+        with open(path + ".new", "w") as storage:
+            try:
+                storage.write(credentials.to_json())
+            except AttributeError:
+                storage.write(
+                    json.dumps(
+                        {
+                            "token": credentials.token,
+                            "refresh_token": credentials.refresh_token,
+                            "token_uri": credentials.token_uri,
+                            "client_id": credentials.client_id,
+                            "client_secret": credentials.client_secret,
+                            "scopes": credentials.scopes,
+                        }
+                    )
+                )
+        os.rename(path + ".new", path)
 
     def __get_credentials__(self):
         """
@@ -527,7 +544,11 @@ class Remote:
             )
 
         if not credentials or not credentials.valid:
-            if credentials and credentials.expired and credentials.refresh_token:
+            if (
+                credentials
+                and (credentials.expired or not credentials.token)
+                and credentials.refresh_token
+            ):
                 credentials.refresh(Request())
 
             elif self.CLIENT_SECRET_FILE is not None:
@@ -645,39 +666,33 @@ class Remote:
         if len(add) > 0 or len(rem) > 0:
             # check if this message has been changed remotely since last pull
             hist_id = int(gmsg["historyId"])
-            if hist_id > last_hist:
-                if not force:
-                    print(
-                        "update: remote has changed, will not update: %s (add: %s, rem: %s) (%d > %d)"
-                        % (gid, add, rem, hist_id, last_hist)
-                    )
-                    self.all_updated = False
-                    return None
+            if hist_id > last_hist and not force:
+                print(
+                    "update: remote has changed, will not update: %s (add: %s, rem: %s) (%d > %d)"
+                    % (gid, add, rem, hist_id, last_hist)
+                )
+                self.all_updated = False
+                return None
 
             if "TRASH" in add:
                 if "SPAM" in add:
                     print(
-                        "update: %s: Trying to add both TRASH and SPAM, dropping SPAM (add: %s, rem: %s)"
-                        % (gid, add, rem)
+                        f"update: {gid}: Trying to add both TRASH and SPAM, dropping SPAM (add: {add}, rem: {rem})"
                     )
                     add.remove("SPAM")
                 if "INBOX" in add:
                     print(
-                        "update: %s: Trying to add both TRASH and INBOX, dropping INBOX (add: %s, rem: %s)"
-                        % (gid, add, rem)
+                        f"update: {gid}: Trying to add both TRASH and INBOX, dropping INBOX (add: {add}, rem: {rem})"
                     )
                     add.remove("INBOX")
             elif "SPAM" in add:
                 if "INBOX" in add:
                     print(
-                        "update: %s: Trying to add both SPAM and INBOX, dropping INBOX (add: %s, rem: %s)"
-                        % (gid, add, rem)
+                        "update: {gid}: Trying to add both SPAM and INBOX, dropping INBOX (add: {add}, rem: {rem})"
                     )
                     add.remove("INBOX")
 
-            self.print_changes(
-                "gid: %s: add: %s, remove: %s" % (gid, str(add), str(rem))
-            )
+            self.print_changes(f"gid: {gid}: add: {str(add)}, remove: {str(rem)}")
             if self.dry_run:
                 return None
             else:
--- gmailieer-1.6.orig/lieer/resume.py
+++ gmailieer-1.6/lieer/resume.py
@@ -15,8 +15,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-import os
 import json
+import os
 import tempfile
 
 
--- gmailieer-1.6.orig/requirements.txt
+++ gmailieer-1.6/requirements.txt
@@ -2,3 +2,4 @@ google-api-python-client
 google_auth_oauthlib
 tqdm
 notmuch2
+setuptools
--- gmailieer-1.6.orig/setup.py
+++ gmailieer-1.6/setup.py
@@ -5,13 +5,13 @@ https://packaging.python.org/en/latest/d
 https://github.com/pypa/sampleproject
 """
 
-# Always prefer setuptools over distutils
-from setuptools import setup, find_packages
-
 # To use a consistent encoding
 from codecs import open
 from os import path
 
+# Always prefer setuptools over distutils
+from setuptools import find_packages, setup
+
 here = path.abspath(path.dirname(__file__))
 
 # Get the long description from the README file
@@ -50,7 +50,7 @@ setup(
     keywords="gmail notmuch synchronization tags",
     # You can just specify the packages manually here if your project is
     # simple. Or you can use find_packages().
-    packages=find_packages(),
+    packages=find_packages(exclude=["tests"]),
     # Alternatively, if you want to distribute just a my_module.py, uncomment
     # this:
     #   py_modules=["my_module"],
--- gmailieer-1.6.orig/tests/test_local.py
+++ gmailieer-1.6/tests/test_local.py
@@ -1,4 +1,5 @@
 import pytest
+
 import lieer
 
 
