2
2
import { Context , Service } from 'cordis' ;
3
3
import superagent from 'superagent' ;
4
4
import { config } from '../config' ;
5
- import { Logger } from '../utils' ;
5
+ import { Logger , mongoId } from '../utils' ;
6
6
7
7
const logger = new Logger ( 'fetcher' ) ;
8
8
const fetch = ( url : string , type : 'get' | 'post' = 'get' ) => superagent [ type ] ( new URL ( url , config . server ) . toString ( ) )
@@ -11,10 +11,10 @@ export interface IBasicFetcher {
11
11
contest : Record < string , any >
12
12
cron ( ) : Promise < void >
13
13
contestInfo ( ) : Promise < boolean >
14
- getToken ? ( username : string , password : string ) : Promise < void >
15
- teamInfo ? ( ) : Promise < void >
16
- balloonInfo ? ( all : boolean ) : Promise < void >
17
- setBalloonDone ? ( bid : string ) : Promise < void >
14
+ getToken ( username : string , password : string ) : Promise < void >
15
+ teamInfo ( ) : Promise < void >
16
+ balloonInfo ( all : boolean ) : Promise < void >
17
+ setBalloonDone ( bid : string ) : Promise < void >
18
18
}
19
19
class BasicFetcher extends Service implements IBasicFetcher {
20
20
contest : any ;
@@ -60,7 +60,7 @@ class BasicFetcher extends Service implements IBasicFetcher {
60
60
}
61
61
}
62
62
63
- class DomJudgeFetcher extends BasicFetcher {
63
+ class DOMjudgeFetcher extends BasicFetcher {
64
64
async contestInfo ( ) {
65
65
let contest ;
66
66
if ( ! config . contestId ) {
@@ -141,10 +141,96 @@ class DomJudgeFetcher extends BasicFetcher {
141
141
}
142
142
}
143
143
144
+ class HydroFetcher extends BasicFetcher {
145
+ async contestInfo ( ) {
146
+ const ids = config . contestId . split ( '/' ) ;
147
+ const [ domainId , contestId ] = ids . length === 2 ? ids : [ 'system' , config . contestId ] ;
148
+ const { body } = await fetch ( `/d/${ domainId } /contest/${ contestId } ` ) ;
149
+ if ( ! body || ! body . tdoc ) {
150
+ logger . error ( 'Contest not found' ) ;
151
+ return false ;
152
+ }
153
+ const contest = body . tdoc ;
154
+ contest . freeze_time = contest . lockAt ;
155
+ const old = this ?. contest ?. _id ;
156
+ this . contest = {
157
+ info : contest , id : contest . _id , name : contest . title , domainId,
158
+ } ;
159
+ logger . info ( `Connected to ${ contest . name } (id=${ contest . id } )` ) ;
160
+ return old === this . contest . id ;
161
+ }
162
+
163
+ async getToken ( username , password ) {
164
+ const res = await fetch ( '/login' , 'post' ) . send ( { uname : username , password, rememberme : 'on' } )
165
+ . redirects ( 0 ) . ok ( ( i ) => i . status === 302 ) ;
166
+ if ( ! res ) throw new Error ( 'Failed to get token' ) ;
167
+ config . token = `Bearer ${ res . header [ 'set-cookie' ] [ 0 ] . split ( ';' ) [ 0 ] . split ( '=' ) [ 1 ] } ` ;
168
+ }
169
+
170
+ async teamInfo ( ) {
171
+ const { body } = await fetch ( `/d/${ this . contest . domainId } /contest/${ this . contest . id } /user` ) ;
172
+ if ( ! body || ! body . length ) return ;
173
+ const teams = body . tsdocs . filter ( ( t ) => body . udict [ t . uid ] ) . map ( ( t ) => ( body . udict [ t . uid ] ) ) ;
174
+ for ( const team of teams ) {
175
+ await this . ctx . db . teams . update ( { id : team . _id } , { $set : team } , { upsert : true } ) ;
176
+ }
177
+ logger . debug ( `Found ${ teams . length } teams` ) ;
178
+ }
179
+
180
+ async balloonInfo ( all ) {
181
+ if ( all ) logger . info ( 'Sync all balloons...' ) ;
182
+ const { body } = await fetch ( `/d/${ this . contest . domainId } /contest/${ this . contest . id } /balloon?todo=${ all ? 'false' : 'true' } ` ) ;
183
+ if ( ! body || ! body . length ) return ;
184
+ const balloons = body ;
185
+ for ( const balloon of balloons ) {
186
+ const teamTotal = await this . ctx . db . balloon . find ( { teamid : balloon . teamid , time : { $lt : ( balloon . time * 1000 ) . toFixed ( 0 ) } } ) ;
187
+ const encourage = teamTotal . length < ( config . freezeEncourage ?? 0 ) ;
188
+ const totalDict = { } ;
189
+ for ( const t of teamTotal ) {
190
+ totalDict [ t . problem ] = t . contestproblem ;
191
+ }
192
+ const shouldPrint = this . contest . info . freeze_time ? ( balloon . time * 1000 ) < this . contest . info . freeze_time || encourage : true ;
193
+ if ( ! shouldPrint && ! balloon . done ) await this . setBalloonDone ( balloon . balloonid ) ;
194
+ const contestproblem = {
195
+ id : String . fromCharCode ( this . contest . pids . indexOf ( balloon . pid ) + 65 ) ,
196
+ name : body . pdict [ balloon . pid ] . title ,
197
+ rgb : this . contest . balloon [ balloon . pid ] . color ,
198
+ color : this . contest . balloon [ balloon . pid ] . name ,
199
+ } ;
200
+ await this . ctx . db . balloon . update ( { balloonid : balloon . balloonid } , {
201
+ $set : {
202
+ balloonid : balloon . _id ,
203
+ time : mongoId ( balloon . _id ) . timestamp ,
204
+ problem : contestproblem . id ,
205
+ contestproblem,
206
+ team : body . udict [ balloon . uid ] . displayName ,
207
+ teamid : balloon . uid ,
208
+ location : body . udict [ balloon . uid ] . studentId ,
209
+ affiliation : body . udict [ balloon . uid ] . school ,
210
+ awards : balloon . first ? 'First of Problem' : (
211
+ this . contest . info . freeze_time && ( balloon . time * 1000 ) > this . contest . info . freeze_time
212
+ && encourage ? 'Encourage Balloon' : ''
213
+ ) ,
214
+ done : balloon . sent ,
215
+ total : totalDict ,
216
+ printDone : balloon . done ? 1 : 0 ,
217
+ shouldPrint,
218
+ } ,
219
+ } , { upsert : true } ) ;
220
+ }
221
+ logger . debug ( `Found ${ balloons . length } balloons` ) ;
222
+ }
223
+
224
+ async setBalloonDone ( bid ) {
225
+ await fetch ( `/d/${ this . contest . domainId } /contest/${ this . contest . id } /balloon` , 'post' ) . send ( { balloon : bid } ) ;
226
+ logger . debug ( `Balloon ${ bid } set done` ) ;
227
+ }
228
+ }
229
+
144
230
const fetcherList = {
145
231
server : BasicFetcher ,
146
- domjudge : DomJudgeFetcher ,
147
- hydro : BasicFetcher , // TODO: HydroFetcher
232
+ domjudge : DOMjudgeFetcher ,
233
+ hydro : HydroFetcher ,
148
234
} ;
149
235
150
236
export async function apply ( ctx ) {
0 commit comments