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
|
||||
yamllint
|
||||
yaspin
|
||||
pytimeparse
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue