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