Skip to content

Clean up notifications #1

Clean up notifications

Clean up notifications #1

# Marks as read ("closes") the notification threads of the account whose token
# is used, when the account that opened the issue or pull request AND every
# account that posted a comment on it are all in an allow-list (by default
# probonopd and github-actions). Reviews and review comments are not considered.
#
# Threads that are not about an issue or pull request, or where the opener or any
# commenter is someone else, are left untouched. Runs only when started by hand,
# and changes nothing unless "dry_run" is set to false (it lists what it would
# do first).
#
# Needs a classic personal access token with the "notifications" scope (add
# "repo" as well to read participants in private repositories) in the
# NOTIFICATIONS_TOKEN secret: the automatic GITHUB_TOKEN cannot read or change a
# user's notifications.
name: Clean up notifications
on:
workflow_dispatch:
inputs:
dry_run:
description: Only list what would be marked as read; change nothing
type: boolean
default: true
allowed:
description: Comma-separated logins that may be the sole participants
default: probonopd,github-actions
permissions: {}
concurrency:
group: cleanup-notifications
cancel-in-progress: false
jobs:
cleanup:
if: github.repository == 'AppImage/appimage.github.io'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.NOTIFICATIONS_TOKEN }}
DRY_RUN: ${{ inputs.dry_run }}
ALLOWED: ${{ inputs.allowed }}
steps:
- name: Mark matching notifications as read
run: |
set -u
if [ -z "${GH_TOKEN:-}" ] ; then
echo "::error::Set the NOTIFICATIONS_TOKEN secret to a classic personal access token with the 'notifications' scope."
exit 1
fi
# Compare logins case-insensitively and treat "github-actions" and
# "github-actions[bot]" as the same account.
norm() { tr 'A-Z' 'a-z' | sed 's/\[bot\]$//' ; }
ALLOW=$(echo "$ALLOWED" | tr ',' '\n' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | norm | grep . | sort -u)
echo "Allowed sole participants:"
echo "$ALLOW" | sed 's/^/ /'
echo ""
# The opener of an issue or pull request and the authors of its
# comments (reviews and review comments are deliberately not counted).
# $1 is the API URL of the issue or pull request (.subject.url).
participants() {
local u="$1" issue
issue=$(echo "$u" | sed 's#/pulls/#/issues/#') # PR conversation comments are issue comments
gh api "$u" --jq '.user.login // empty' 2>/dev/null # the opener
gh api --paginate "$issue/comments" --jq '.[].user.login' 2>/dev/null # comment authors
}
# Unread notifications of the token's account, across all repositories
gh api --paginate "/notifications?all=false&per_page=100" \
--jq '.[] | [.id, .subject.type, (.subject.url // ""), .repository.full_name, .subject.title] | @tsv' \
> threads.tsv || true
if [ ! -s threads.tsv ] ; then
echo "No unread notifications."
exit 0
fi
CHECKED=0 ; MATCHED=0 ; KEPT=0
while IFS=$'\t' read -r ID TYPE URL REPO TITLE ; do
CHECKED=$((CHECKED + 1))
case "$TYPE" in
Issue|PullRequest) ;;
*) echo "keep [$TYPE] $REPO: $TITLE (not an issue or pull request)" ; KEPT=$((KEPT + 1)) ; continue ;;
esac
if [ -z "$URL" ] ; then
echo "keep $REPO: $TITLE (no subject URL)" ; KEPT=$((KEPT + 1)) ; continue
fi
PARTS=$(participants "$URL" | norm | grep . | sort -u)
if [ -z "$PARTS" ] ; then
echo "keep $REPO: $TITLE (could not read participants)" ; KEPT=$((KEPT + 1)) ; continue
fi
OTHERS=$(comm -23 <(echo "$PARTS") <(echo "$ALLOW"))
if [ -n "$OTHERS" ] ; then
echo "keep $REPO: $TITLE (others: $(echo "$OTHERS" | paste -sd, -))"
KEPT=$((KEPT + 1))
continue
fi
MATCHED=$((MATCHED + 1))
if [ "$DRY_RUN" = "false" ] ; then
if gh api --silent -X PATCH "/notifications/threads/$ID" 2>/dev/null ; then
echo "closed $REPO: $TITLE"
else
echo "::warning::Could not mark thread $ID ($REPO: $TITLE) as read"
fi
else
echo "would close $REPO: $TITLE (participants: $(echo "$PARTS" | paste -sd, -))"
fi
done < threads.tsv
echo ""
echo "Checked $CHECKED notification(s): matched $MATCHED, kept $KEPT."
if [ "$DRY_RUN" != "false" ] ; then
echo "Dry run: nothing was changed. Re-run with dry_run = false to mark the matching ones as read."
fi