Skip to content

Commit 8f6735a

Browse files
committed
Add a convenience script to find specific webhook deliveries
In the previous commit, I added a script to delete existing installations of the GitForWindowsHelper GitHub App for unintended repositories, to avoid cluttering the list of webhook deliveries. I ran this script a couple times in the past, but it was always a bit late for that because I had to click literally over 200 times on that button just to get to payloads that were delivered two days prior. A better way is to use the GitHub REST API to let a script search for specific deliveries, say, for a `check_run` that has completed around a given time. I used this script today to great effect to find the `check_run` and `workflow_job` webhook payloads that I mentioned in git-for-windows/git-for-windows-automation#60 It uses a bit of an ad-hoc strategy to avoid having to load sequential pages of deliveries until the desired time window is reached. That strategy could probably be improved, but it seems to work well enough for now. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent a76e34b commit 8f6735a

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

get-webhook-event-payload.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
(async () => {
2+
const fs = require('fs')
3+
4+
let eventType = undefined
5+
let aroundDate = undefined
6+
7+
// parse arguments, e.g. --event-type=check_run --date='Tue, 21 Nov 2023 11:13:12 GMT'
8+
const args = process.argv.slice(2)
9+
while (args.length) {
10+
let option = args.shift()
11+
12+
const optionWithArgument = option.match(/^(--[^=]+)=(.*)$/)
13+
if (optionWithArgument) {
14+
option = optionWithArgument[1]
15+
args.unshift(optionWithArgument[2])
16+
}
17+
18+
const getArg = () => {
19+
if (!args.length) throw new Error(`'${option} requires an argument!`)
20+
return args.shift()
21+
}
22+
23+
if (option === '--event-type') eventType = getArg()
24+
else if (option === '--date') {
25+
const arg = getArg()
26+
if (isNaN(Date.parse(arg))) throw new Error(`--date requires a valid date (got '${arg}')`)
27+
aroundDate = new Date(arg)
28+
} else
29+
throw new Error(`Unhandled option: '${option}`)
30+
}
31+
32+
const since = aroundDate ? aroundDate.getTime() - 5 * 60 * 1000 : undefined // 5 minutes before
33+
const until = aroundDate ? aroundDate.getTime() + 5 * 60 * 1000 : undefined // 5 minutes after
34+
35+
const localSettings = JSON.parse(fs.readFileSync('local.settings.json'))
36+
process.env.GITHUB_APP_ID = localSettings.Values.GITHUB_APP_ID
37+
process.env.GITHUB_APP_PRIVATE_KEY = localSettings.Values.GITHUB_APP_PRIVATE_KEY
38+
39+
const gitHubRequestAsApp = require('./GitForWindowsHelper/github-api-request-as-app')
40+
41+
const getAtCursor = async cursor => {
42+
const answer = await gitHubRequestAsApp(console, 'GET', `/app/hook/deliveries?per_page=30${cursor ? `&cursor=${cursor}` : ''}`)
43+
answer.forEach(e => {
44+
e.epoch = (new Date(e.delivered_at)).getTime()
45+
})
46+
// sort newest to oldest
47+
answer.sort((a, b) => b.epoch - a.epoch)
48+
const events = answer.filter(e => {
49+
if (eventType && e.event !== eventType) return false
50+
if (since && e.epoch < since) return false
51+
if (until && e.epoch > until) return false
52+
return true
53+
})
54+
const newest = answer.shift()
55+
const oldest = answer.pop() || newest
56+
return {
57+
events, newest, oldest
58+
}
59+
}
60+
61+
const getMatchingEvents = async () => {
62+
let answer = await getAtCursor()
63+
64+
if (!since || !until || answer.newest === answer.oldest) return answer.events
65+
66+
if (answer.oldest.epoch < since) return answer.events
67+
68+
if (answer.oldest.epoch > until) {
69+
let tooNew = answer.oldest
70+
// first find a good starting cursor
71+
while (answer.oldest.epoch > until) {
72+
tooNew = answer.oldest
73+
74+
const rate = (answer.newest.id - answer.oldest.id) / (answer.newest.epoch - answer.oldest.epoch)
75+
let cursor = Math.floor(answer.oldest.id - rate * (answer.oldest.epoch - until))
76+
answer = await getAtCursor(cursor)
77+
}
78+
79+
while (answer.newest.epoch < until) {
80+
const tooOldID = answer.newest.id
81+
// we overshot, now the time window does not include `until`, backtrack via bisecting
82+
const rate = (tooNew.id - answer.newest.id) / (tooNew.epoch - answer.newest.epoch)
83+
let cursor = Math.floor(tooNew.id - rate * (tooNew.epoch - until))
84+
answer = await getAtCursor(cursor)
85+
// if we received events from the same time window, shift back by the same amount
86+
while (tooOldID === answer.newest.id) {
87+
cursor += (cursor - tooOldID)
88+
answer = await getAtCursor(cursor)
89+
}
90+
}
91+
92+
while (answer.oldest.epoch > until) {
93+
// we overshot, maybe again, now even the oldest is too new
94+
answer = await getAtCursor(answer.oldest.id - 1)
95+
}
96+
}
97+
98+
const events = [...answer.events]
99+
while (answer.oldest.epoch > since) {
100+
answer = await getAtCursor(answer.oldest.id - 1)
101+
events.push([...answer.events])
102+
}
103+
104+
return events
105+
}
106+
107+
const events = await getMatchingEvents()
108+
for (const e of events) {
109+
const fullEvent = await gitHubRequestAsApp(console, 'GET', `/app/hook/deliveries/${e.id}`)
110+
console.log(`id: ${e.id}\naction: ${e.action}\nrequest: ${JSON.stringify(fullEvent.request, null, 2)}`)
111+
}
112+
})().catch(console.log)

0 commit comments

Comments
 (0)