Skip to content

Commit

Permalink
feat(qoj): export rank list as markdown
Browse files Browse the repository at this point in the history
  • Loading branch information
memset0 committed Jul 26, 2024
1 parent 9f69e97 commit 72d2809
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 18 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "CPAssistant.js",
"version": "1.1.1",
"version": "1.1.2",
"description": "",
"main": "index.js",
"license": "GPL-3.0-only",
Expand Down
136 changes: 136 additions & 0 deletions src/modules/qoj/features/RankListExport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// import * as h from 'h';
import Module from '../../../types/module';
import Feature from '../../../types/feature';
import { writeClipboard } from '../../../utils/browser';

interface RanklistProblem {
id: string;
accepted: number;
attempted: number;
link?: string;
$el: HTMLElement;
}

interface RanklistTeam {
name: string;
solved: number;
penalty: number;
status: Array<string>;
selected: boolean;
$el: HTMLElement;
}

export default class RankListExport extends Feature {
run() {
this.on('/results/<name>', (args) => {
const $ranklist = document.getElementsByClassName('standings')[0];

const problems = Array.from($ranklist.children[0].children[0].children)
.slice(2, -3)
.map(($el) => {
const text = ($el as HTMLElement).innerText;
const data = text.split(/[\s\/]+/);
return {
id: data[0],
accepted: parseInt(data[1], 10),
attempted: parseInt(data[2], 10),
$el,
} as RanklistProblem;
});
console.log(problems);

const teams = Array.from($ranklist.children[0].children)
.slice(1, -2)
.map(($el) => {
const children = Array.from($el.children).map(($el) => ($el as HTMLElement).innerText.trim());

return {
name: children[1],
solved: parseInt(children[children.length - 3], 10),
penalty: parseInt(children[children.length - 2], 10),
status: children.slice(2, -3),
selected: false,
$el,
} as RanklistTeam;
});
console.log(teams);

for (const team of teams) {
team.$el.addEventListener('click', () => {
this.log(team.name);
team.selected = !team.selected;
if (team.selected) {
team.$el.style.color = 'red';
} else {
team.$el.style.color = '';
}
});
}

const exportRank = (teams: Array<RanklistTeam>) => {
const data: Array<Array<string>> = [[`[Team](https://qoj.ac/results/${args.name})`]];
data[0].push('Solved');
data[0].push('Penalty');
data[0].push.apply(
data[0],
problems.map((problem) => `${problem.id}<br>${problem.accepted}`)
);
for (const team of teams) {
const line = [team.name];
line.push(team.solved.toString());
line.push(team.penalty.toString());
line.push.apply(
line,
team.status.map((source) => {
const data = source.trim().split(/\n+/);
if (data[0].startsWith('+')) {
data[0] = '**' + data[0] + '**';
return '[' + data.join('<br>') + '](#)';
} else {
return data.join('<br>');
}
})
);
data.push(line);
}
this.log(data);

let result = '| ';
for (let i = 0; i < data[0].length; i++) {
result += data[0][i] + ' | ';
}
result += '\n| ';
for (let i = 0; i < data[0].length; i++) {
result += ' --- |';
}
result += '\n';
for (let i = 1; i < data.length; i++) {
result += '| ';
for (let j = 0; j < data[i].length; j++) {
result += data[i][j] + ' | ';
}
result += '\n';
}
this.log('export rank', result);
writeClipboard(result);
};

const $buttonExport = document.createElement('button');
$buttonExport.innerText = 'Export';
$buttonExport.addEventListener('click', () => {
exportRank(teams.filter((team) => team.selected));
});
const $buttonExportAll = document.createElement('button');
$buttonExportAll.innerText = 'Export All';
$buttonExportAll.addEventListener('click', () => {
exportRank(teams);
});
document.body.children[0].appendChild($buttonExport);
document.body.children[0].appendChild($buttonExportAll);
});
}

constructor(module: Module, name: string) {
super(module, name);
}
}
3 changes: 3 additions & 0 deletions src/modules/qoj/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import config from '../../config';
import App from '../../app';
import Module from '../../types/module';
import RankListExport from './features/RanklistExport';

export default class ModuleQOJ extends Module {
run() {}

constructor(app: App) {
super(app, 'qoj', config.match.qoj);

this.register(new RankListExport(this, 'ranklist-export'));
}
}
35 changes: 18 additions & 17 deletions src/types/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export default class Feature {
}
}

on(match: string | Array<string>, func: (args: Dict<string>, params: Dict<string>) => void): boolean {
if (match instanceof Array) {
on(matcher: string | Array<string>, func: (args: Dict<string>, params: Dict<string>) => void): boolean {
if (matcher instanceof Array) {
let ok = false;
for (const singleMatch of match) {
for (const singleMatch of matcher) {
ok = this.on(singleMatch, func);
if (ok) {
return ok;
Expand All @@ -46,30 +46,31 @@ export default class Feature {
}

const args: Dict<string> = {};
const matchParts = match.slice(1).split('/');
const pathParts = location.pathname.slice(1).split('/');
if (matchParts[matchParts.length - 1] == '') {
--matchParts.length;
const matcherParts = matcher.slice(1).split('/');
const currentPaths = location.pathname.slice(1).split('/');

if (matcherParts[matcherParts.length - 1] == '') {
--matcherParts.length;
}
if (pathParts[pathParts.length - 1] == '') {
--pathParts.length;
if (currentPaths[currentPaths.length - 1] == '') {
--currentPaths.length;
}

if (pathParts.length < matchParts.length) {
if (currentPaths.length < matcherParts.length) {
return false;
}
if (pathParts.length > matchParts.length && matchParts[matchParts.length - 1] !== '*') {
if (currentPaths.length > matcherParts.length && matcherParts[matcherParts.length - 1] !== '*') {
return false;
}

for (let i = 0; i < matchParts.length; i++) {
if (matchParts[i] === '*') {
for (let i = 0; i < matcherParts.length; i++) {
if (matcherParts[i] === '*') {
break;
} else if (matchParts[i].startsWith('<') && matchParts[i].endsWith('>')) {
const key = matchParts[i].slice(1, matchParts[i].length - 1);
args[key] = pathParts[i];
} else if (matcherParts[i].startsWith('<') && matcherParts[i].endsWith('>')) {
const key = matcherParts[i].slice(1, matcherParts[i].length - 1);
args[key] = currentPaths[i];
} else {
if (matchParts[i] != pathParts[i]) {
if (matcherParts[i] != currentPaths[i]) {
return false;
}
}
Expand Down

0 comments on commit 72d2809

Please sign in to comment.