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