Last active
March 13, 2021 14:01
-
-
Save hamelsmu/3af1350a177f7dfd0515baa8c700732e to your computer and use it in GitHub Desktop.
GitHub Notification Sanity With `ghapi`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"id": "quick-checklist", | |
"metadata": {}, | |
"source": [ | |
"## GitHub Notification Sanity With `ghapi`\n", | |
"\n", | |
"Problem: I am part of lots of user groups on GitHub , and GitHub's notification filters are only so fine grained. Anytime one of these groups are mentioned on GitHub, I get notified. **The mention filter is not good enough for this as if any of the groups I'm part of are mentioned, I get a notification.** This results in a tremendous amount of notification spam for me. \n", | |
"\n", | |
"Fortunately, we can use [ghapi](https://ghapi.fast.ai/) to mark notifications that don't contain my handle as read en-masse.\n", | |
"\n", | |
"To use `ghapi` to access authenticated operations (other than when running through GitHub Actions), you will need a GitHub [personal access token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token), which is a secret code used to access your account. If you don't have one, [click here](https://github.com/settings/tokens/new) to create one. You'll be asked to enter a name -- choose anything you like, for instance \"*ghapi*\". You'll also be asked to choose \"scopes\"; this limits what you'll be able to do with the API using this token. If you're not sure, click \"*repo*\" \"*gist*\", \"*notifications*\", and \"*workflow*\". Then click \"Generate Token\" at the bottom of the screen, and copy the token (the long string of letters and numbers shown). You can easily do that by clicking the little clipboard icon next to the token.\n", | |
"\n", | |
"Rather than pasting that token into every script, it's easiest to save it as an environment variable. If you save it as `$GITHUB_TOKEN` then it will be most convenient, so add this to the end of your `.bashrc` or `.zshrc` file:\n", | |
"\n", | |
" export GITHUB_TOKEN=xxx\n", | |
"\n", | |
"...replacing the `xxx` with the token you just copied. (Don't forget to `source` that file after you change it.), pass a [GitHub token].\n", | |
"\n", | |
"As well as your `token`, you can also pass any parameters you want auto-inserted into relevant methods, such as `owner` and `repo`:\n", | |
"\n", | |
"_The above instructions on authentication are also [available here](https://ghapi.fast.ai/)._\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"id": "ordered-desert", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from ghapi.all import *\n", | |
"from fastprogress.fastprogress import progress_bar\n", | |
"import os" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"id": "supposed-panic", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"class Sanity:\n", | |
" def __init__(self, token=None): self.token = token\n", | |
"\n", | |
" def get_notifications(self, org):\n", | |
" \"\"\"Gets all notifications corresponding to Pull Requests and Issues.\"\"\"\n", | |
" return (GhApi(token=self.token).activity\n", | |
" .list_notifications_for_authenticated_user(per_page=100)\n", | |
" .filter(lambda x: x.repository.owner['login'] ==org.lower() and \n", | |
" x.subject['type'] in ('PullRequest', 'Issue')))\n", | |
"\n", | |
" def get_comments(self, org, repo, issue_num):\n", | |
" \"\"\"Get the main comment body and a list of all subsequent comments\"\"\"\n", | |
" g = GhApi(owner=org, repo=repo, token=self.token)\n", | |
" return ' '.join(g.issues.list_comments(issue_num).map(lambda x: x.body) + [g.issues.get(issue_num).body])\n", | |
" \n", | |
" def get_threads(self, org):\n", | |
" \"Get a list of threads and their comments corresponding to notifications.\"\n", | |
" return [{'comments':self.get_comments(org=n.repository.owner.login,\n", | |
" repo=n.repository.name,\n", | |
" issue_num=int(n.subject.url.split('/')[-1])),\n", | |
" 'thread': int(n.url.split('/')[-1])} for n in self.get_notifications(org=org)]\n", | |
" \n", | |
" def mark_read_nokw(self, org, keyword):\n", | |
" \"Mark all threads that do not contain keyword as read.\"\n", | |
" gh = GhApi(token=self.token)\n", | |
" for t in progress_bar(self.get_threads(org=org)): \n", | |
" if keyword not in t['comments']: gh.activity.mark_thread_as_read(t['thread'])" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "southeast-coverage", | |
"metadata": {}, | |
"source": [ | |
"### Mark all notifications that do not contain my handle as read" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"id": "interstate-syntax", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"\n", | |
" <div>\n", | |
" <style>\n", | |
" /* Turns off some styling */\n", | |
" progress {\n", | |
" /* gets rid of default border in Firefox and Opera. */\n", | |
" border: none;\n", | |
" /* Needs to be in here for Safari polyfill so background images work as expected. */\n", | |
" background-size: auto;\n", | |
" }\n", | |
" .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n", | |
" background: #F44336;\n", | |
" }\n", | |
" </style>\n", | |
" <progress value='31' class='' max='31' style='width:300px; height:20px; vertical-align: middle;'></progress>\n", | |
" 100.00% [31/31 00:08<00:00]\n", | |
" </div>\n", | |
" " | |
], | |
"text/plain": [ | |
"<IPython.core.display.HTML object>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"# the env variable is named MY_GITHUB_TOKEN so it is not confused with GITHUB_TOKEN in Actions\n", | |
"s= Sanity(token=os.getenv('MY_GITHUB_TOKEN'))\n", | |
"s.mark_read_nokw(org='github', keyword='@hamelsmu', )" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "adult-feature", | |
"metadata": {}, | |
"source": [ | |
"Notes:\n", | |
"- Since notifications are paginated, this only grabs 100 notifications at a time. There are ways to [handle pagination](https://ghapi.fast.ai/page.html) in ghapi, but I don't need that, because this only grabs notifications marked as unread. Therefore, if I run this many times a day, this is good enough. \n", | |
"\n", | |
"- This is only an example, you may want modify this to suit your needs. For example, you might want to expand beyond Pull Requests and Issues. " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"id": "friendly-option", | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.8.3" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment