diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 2b49e7abb..57082f314 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -825,6 +825,7 @@ export interface ScoreboardView) => Promise; + checker?: (this: ContestScoreboardHandler) => boolean; } export class ContestScoreboardHandler extends ContestDetailBaseHandler { @@ -884,16 +885,17 @@ class ScoreboardService extends Service { addView }>( id: string, name: string, args: T, - { display, supportedRules, cacheTime }: { + { display, supportedRules, cacheTime, checker }: { display: (this: ContestScoreboardHandler, args: ParseArgs) => Promise; supportedRules: string[]; cacheTime?: number; + checker?: (this: ContestScoreboardHandler) => boolean; }, ) { if (this.views[id]) throw new Error(`View ${id} already exists`); this.ctx.effect(() => { this.views[id] = { - id, name, args, display, supportedRules, cacheTime, + id, name, args, display, supportedRules, cacheTime, checker, }; return () => { delete this.views[id]; @@ -901,9 +903,11 @@ class ScoreboardService extends Service { }); } - getAvailableViews(rule: string) { - return Object.fromEntries(Object.values(this.views).filter((i) => i.supportedRules.includes(rule) || i.supportedRules.includes('*')) - .map((i) => [i.id, i.name])); + getAvailableViews(rule: string, handler: ContestScoreboardHandler) { + return Object.fromEntries(Object.values(this.views).filter((i) => ( + (i.supportedRules.includes(rule) || i.supportedRules.includes('*')) + && (!i.checker || i.checker.call(handler)) + )).map((i) => [i.id, i.name])); } getView(id: string) { @@ -963,7 +967,7 @@ export async function apply(ctx: Context) { const page_name = tdoc.rule === 'homework' ? 'homework_scoreboard' : 'contest_scoreboard'; - const availableViews = scoreboard.getAvailableViews(tdoc.rule); + const availableViews = scoreboard.getAvailableViews(tdoc.rule, this); this.response.body = { tdoc: this.tdoc, tsdoc: this.tsdocAsPublic(), rows, udict, pdict, page_name, groups, availableViews, }; diff --git a/packages/onsite-toolkit/index.ts b/packages/onsite-toolkit/index.ts index db44e709e..7ef7af2ac 100644 --- a/packages/onsite-toolkit/index.ts +++ b/packages/onsite-toolkit/index.ts @@ -144,6 +144,7 @@ export function apply(ctx: Context, config: ReturnType) { ...lockDuration && { scoreboard_freeze_duration: moment().startOf('day').seconds(lockDuration).format('HH:mm:ss.SSS'), }, + scoreboard_type: tdoc.rule === 'acm' ? 'pass-fail' : 'score', penalty_time: 20, }), ...Object.keys(STATUS_SHORT_TEXTS).map((i) => getFeed('judgement-types', { @@ -265,7 +266,12 @@ export function apply(ctx: Context, config: ReturnType) { if (!ContestModel.isDone(tdoc)) throw new ContestNotEndedError(); this.binary(await generateCdpZip(tdoc), `contest-${tdoc._id}-cdp.zip`); }, - supportedRules: ['acm', 'oi'], + checker() { + return ContestModel.isDone(this.tdoc) + && !!this.tdoc.lockAt + && (this.user.own(this.tdoc) || this.user.hasPerm(PERM.PERM_EDIT_CONTEST)); + }, + supportedRules: ['acm', 'oi', 'ioi'], }); scoreboard.addView('resolver', 'Resolver', { tdoc: 'tdoc' }, { @@ -279,10 +285,15 @@ export function apply(ctx: Context, config: ReturnType) { const source = new URL(`/d/${tdoc.domainId}/contest/${tdoc._id}/resolver-cdp/${tokenId}`, SystemModel.get('server.url')).toString(); const target = new URL('https://resolver.hydrooj.com'); target.searchParams.set('source', source); - target.searchParams.set('mode', tdoc.rule === 'oi' ? 'oi' : 'acm'); + target.searchParams.set('mode', tdoc.rule === 'acm' ? 'acm' : 'oi'); this.response.redirect = target.toString(); }, - supportedRules: ['acm', 'oi'], + checker() { + return ContestModel.isDone(this.tdoc) + && !!this.tdoc.lockAt + && (this.user.own(this.tdoc) || this.user.hasPerm(PERM.PERM_EDIT_CONTEST)); + }, + supportedRules: ['acm', 'oi', 'ioi'], }); });