Compare commits

...

2 commits

Author SHA1 Message Date
64c4622200 Create the unnotifier 2023-07-27 16:34:26 -06:00
87315d5a40 Update requirements 2023-07-27 16:34:13 -06:00
4 changed files with 182 additions and 41 deletions

View file

@ -0,0 +1,9 @@
py_project(
name = "gh-unnotifier",
main = "src/python/ghunnotif/__main__.py",
main_deps = [
py_requirement("click"),
py_requirement("requests"),
py_requirement("pytimeparse"),
]
)

View file

@ -0,0 +1,125 @@
#!/usr/bin/env python3
import os
from pathlib import Path
import tomllib
from typing import Optional
from datetime import datetime, timedelta, timezone
from time import sleep
import click
import requests
import pytimeparse
class Client(object):
BASE = "https://api.github.com"
HEADERS = {"Accept": "application/vnd.github+json"}
def __init__(self, token):
self._token = token
self._session = requests.Session()
self._session.headers["Authorization"] = f"Bearer {token}"
def get_notifications(self, page=1):
resp = self._session.get(f"{self.BASE}/notifications", headers=self.HEADERS, params={"page": page, "all": "false"})
resp.raise_for_status()
return resp.json()
def get_all_notifications(self):
page = 1
while True:
results = self.get_notifications(page=page)
if not results:
return
yield from results
page += 1
def read(self, notif):
return self._session.patch(notif["url"]).raise_for_status()
def unsubscribe(self, notif):
return self._session.delete(notif["subscription_url"]).raise_for_status()
def get_pr(self,
url: Optional[str] = None,
repo: Optional[str] = None,
id: Optional[int] = None):
url = url or f"{self.BASE}/{repo}/pulls/{id}"
resp = self._session.get(url, headers=self.HEADERS)
resp.raise_for_status()
return resp.json()
@click.group()
def cli():
pass
@cli.command()
@click.option("--config", "config_path", type=Path, default=lambda: Path(os.getenv("BUILD_WORKSPACE_DIRECTORY", "")) / "projects/gh-unnotifier/config.toml")
def oneshot(config_path: Path):
with open(config_path, "rb") as fp:
config = tomllib.load(fp)
client = Client(config["gh-unnotifier"]["api_key"])
for notif in client.get_all_notifications():
subject = notif["subject"]
pr = None
if "/pulls/" in subject["url"]:
pr = client.get_pr(url=subject["url"])
if pr["state"] == "closed":
client.read(notif)
client.unsubscribe(notif)
print("Resolved", notif["id"])
continue
print(notif["id"], notif["subscription_url"], notif["reason"], notif["subject"], pr)
def parse_seconds(text: str) -> timedelta:
return timedelta(seconds=pytimeparse.parse(text))
@cli.command()
@click.option("--config", "config_path", type=Path, default=lambda: Path(os.getenv("BUILD_WORKSPACE_DIRECTORY", "")) / "projects/gh-unnotifier/config.toml")
@click.option("--schedule", "schedule", default="15 seconds", type=parse_seconds)
def maintain(config_path: Path, schedule: timedelta):
with open(config_path, "rb") as fp:
config = tomllib.load(fp)
client = Client(config["gh-unnotifier"]["api_key"])
mark = None
while True:
try:
prev = mark
mark = datetime.now(timezone.utc)
tick = mark + schedule
assert tick - schedule == mark
for notif in client.get_all_notifications():
subject = notif["subject"]
# Don't waste time on notifications which haven't changed since the last scrub
updated_at = datetime.fromisoformat(notif["updated_at"])
if prev and updated_at < prev:
continue
pr = None
if "/pulls/" in subject["url"]:
pr = client.get_pr(url=subject["url"])
if pr["state"] == "closed":
client.read(notif)
client.unsubscribe(notif)
click.echo(f"Resolved {notif['id']}")
continue
click.echo("Napping...")
sleep((tick - datetime.now(timezone.utc)).total_seconds())
except KeyboardInterrupt:
break
if __name__ == "__main__":
cli()

View file

@ -53,3 +53,4 @@ toml
unify
yamllint
yaspin
pytimeparse

View file

@ -1,25 +1,26 @@
aiohttp==3.8.4
aiohttp==3.8.5
aiohttp-basicauth==1.0.0
aiosignal==1.3.1
aiosql==8.0
aiosql==9.0
alabaster==0.7.13
async-lru==2.0.2
annotated-types==0.5.0
async-lru==2.0.4
async-timeout==4.0.2
attrs==23.1.0
autocommand==2.2.2
autoflake==2.1.1
autoflake==2.2.0
Babel==2.12.1
beautifulsoup4==4.12.2
black==23.3.0
black==23.7.0
blinker==1.6.2
build==0.10.0
cachetools==5.3.1
certifi==2023.5.7
charset-normalizer==3.1.0
certifi==2023.7.22
charset-normalizer==3.2.0
cheroot==10.0.0
CherryPy==18.8.0
click==8.1.3
colored==1.4.4
click==8.1.6
colored==2.2.3
commonmark==0.9.1
coverage==7.2.7
decorator==5.1.1
@ -28,53 +29,54 @@ docutils==0.20.1
ExifRead==3.0.0
flake8==6.0.0
Flask==2.3.2
frozenlist==1.3.3
hypothesis==6.75.9
frozenlist==1.4.0
hypothesis==6.82.0
ibis==3.2.0
icmplib==3.0.3
idna==3.4
imagesize==1.4.1
inflect==6.0.4
inflect==7.0.0
iniconfig==2.0.0
isort==5.12.0
itsdangerous==2.1.2
jaraco.collections==4.2.0
jaraco.collections==4.3.0
jaraco.context==4.3.0
jaraco.functools==3.7.0
jaraco.text @ git+https://github.com/arrdem/jaraco.text.git@0dd8d0b25a93c3fad896f3a92d11caff61ff273d
jaraco.functools==3.8.0
jaraco.text==3.11.1
jedi==0.18.2
Jinja2==3.1.2
jsonschema==4.17.3
jsonschema-spec==0.1.4
lark==1.1.5
jsonschema==4.18.4
jsonschema-spec==0.2.3
jsonschema-specifications==2023.7.1
lark==1.1.7
lazy-object-proxy==1.9.0
libsass==0.22.0
livereload==2.6.3
lxml==4.9.2
Markdown==3.4.3
lxml==4.9.3
Markdown==3.4.4
MarkupSafe==2.1.3
mccabe==0.7.0
meraki==1.33.0
meraki==1.34.0
mirakuru==2.5.1
mistune==2.0.5
more-itertools==9.1.0
more-itertools==10.0.0
multidict==6.0.4
mypy-extensions==1.0.0
octorest==0.4
openapi-schema-validator==0.4.4
openapi-spec-validator==0.5.6
openapi-schema-validator==0.6.0
openapi-spec-validator==0.6.0
packaging==23.1
parso==0.8.3
pathable==0.4.3
pathspec==0.11.1
picobox==3.0.0
pip==23.1.2
pip-tools==6.13.0
platformdirs==3.5.1
pluggy==1.0.0
port-for==0.6.3
portend==3.1.0
prompt-toolkit==3.0.38
pip-tools==7.1.0
platformdirs==3.9.1
pluggy==1.2.0
port-for==0.7.1
portend==3.2.0
prompt-toolkit==3.0.39
proquint==0.2.1
psutil==5.9.5
psycopg==3.1.9
@ -83,30 +85,34 @@ pudb==2022.1.3
py==1.11.0
pycodestyle==2.10.0
pycryptodome==3.18.0
pydantic==1.10.8
pydantic==2.1.1
pydantic_core==2.4.0
pyflakes==3.0.1
Pygments==2.15.1
pyproject_hooks==1.0.0
pyrsistent==0.19.3
pytest==7.3.1
pytest==7.4.0
pytest-cov==4.1.0
pytest-postgresql==5.0.0
pytest-pudb==0.7.0
pytest-timeout==2.1.0
pytimeparse==1.1.8
pytz==2023.3
PyYAML==6.0
PyYAML==6.0.1
recommonmark==0.7.1
redis==4.5.5
redis==4.6.0
referencing==0.29.3
requests==2.31.0
retry==0.9.2
rfc3339-validator==0.1.4
setuptools==67.7.2
rpds-py==0.9.2
setuptools==68.0.0
six==1.16.0
smbus2==0.4.2
snowballstemmer==2.2.0
sortedcontainers==2.4.0
soupsieve==2.4.1
Sphinx==7.0.1
Sphinx==7.1.1
sphinx_mdinclude==0.5.3
sphinxcontrib-applehelp==1.0.4
sphinxcontrib-devhelp==1.0.2
@ -117,19 +123,19 @@ sphinxcontrib-openapi==0.8.1
sphinxcontrib-programoutput==0.17
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tempora==5.2.2
tempora==5.5.0
termcolor==2.3.0
toml==0.10.2
tornado==6.3.2
typing_extensions==4.6.3
typing_extensions==4.7.1
unify==0.5
untokenize==0.1.1
urllib3==2.0.2
urllib3==2.0.4
urwid==2.1.2
urwid-readline==0.13
wcwidth==0.2.6
websocket-client==1.5.2
Werkzeug==2.3.4
websocket-client==1.6.1
Werkzeug==2.3.6
wheel==0.40.0
yamllint==1.32.0
yarl==1.9.2