Skip to content

Commit ec37ea1

Browse files
committed
support receipt i18n
1 parent baf5b15 commit ec37ea1

File tree

14 files changed

+428
-420
lines changed

14 files changed

+428
-420
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434
- run: yarn
3535
- name: Build
3636
run: yarn build:pkg
37+
- name: Copy Origin Bundle to Dist
38+
run: cp dist/xcpc-tools.js /tmp/xcpc-tools-bundle.js
3739
- name: Create Zip for Windows
3840
run: zip -r /tmp/xcpc-tools-win.zip dist/pkg/xcpc-tools-win.exe
3941
- name: Create Tar for MacOS
@@ -50,6 +52,7 @@ jobs:
5052
with:
5153
tag_name: ${{ steps.tag.outputs.version }}-${{ steps.tag.outputs.sha_short }}
5254
files: |
55+
/tmp/xcpc-tools-bundle.js
5356
/tmp/xcpc-tools-win.zip
5457
/tmp/xcpc-tools-macos.tar.gz
5558
/tmp/xcpc-tools-linux.tar.gz

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,24 @@
66
"packages/*"
77
],
88
"scripts": {
9-
"start:client": "node -r ./register.js packages/client/index.ts --debug",
9+
"start:client": "node -r ./register.js packages/server/index.ts --client --debug",
1010
"start:server": "node -r ./register.js packages/server/index.ts --debug",
1111
"lint": "eslint packages --ext js,ts,tsx,jsx",
1212
"build": "yarn build:ui:prod && node -r ./register.js build.ts",
13+
"build:server": "node -r ./register.js build.ts",
1314
"build:pkg": "yarn build:ui:prod && node -r ./register.js build.ts && pkg dist/xcpc-tools.js --targets linux,macos,win --out-path dist/pkg"
1415
},
1516
"devDependencies": {
1617
"@expo-google-fonts/noto-color-emoji": "^0.2.3",
1718
"@expo-google-fonts/noto-sans-sc": "^0.2.3",
1819
"@hydrooj/eslint-config": "^1.0.10",
1920
"@hydrooj/register": "^1.0.1",
20-
"@hydrooj/utils": "^1.4.23",
21-
"@types/node": "^20.12.13",
21+
"@hydrooj/utils": "^1.4.24",
22+
"@types/node": "^20.14.2",
23+
"@yao-pkg/pkg": "^5.12.0",
2224
"dejavu-fonts-ttf": "^2.37.3",
2325
"eslint": "^8.57.0",
2426
"eslint-import-resolver-typescript": "^3.6.1",
25-
"pkg": "^5.8.1",
2627
"typescript": "^5.3.3"
2728
},
2829
"resolutions": {

packages/server/client/balloon.ts

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,73 @@
1+
/* eslint-disable no-await-in-loop */
12
import path from 'path';
23
import EscPosEncoder from '@freedom_sky/esc-pos-encoder';
34
import superagent from 'superagent';
45
import { config } from '../config';
56
import {
7+
convertToChinese,
68
fs, Logger, sleep,
79
} from '../utils';
810

911
const encoder = new EscPosEncoder();
1012

13+
const i18n = {
14+
zh: {
15+
receipt: '气球打印单',
16+
location: '座位',
17+
problem: '题目',
18+
color: '颜色',
19+
comment: '备注',
20+
team: '队伍',
21+
status: '队伍当前气球状态',
22+
},
23+
en: {
24+
receipt: 'Balloon Receipt',
25+
location: 'Location',
26+
problem: 'Balloon',
27+
color: 'Color',
28+
comment: 'Comment',
29+
team: 'Team',
30+
status: 'Team Balloon Status',
31+
},
32+
};
33+
1134
export const receiptText = (
12-
id: number, location: string, problem: string, color: string, comment: string, teamname: string, status: string,
35+
id: number, location: string, problem: string, color: string, comment: string, teamname: string, status: string, lang: 'zh' | 'en' = 'zh',
1336
) => encoder
1437
.initialize()
1538
.codepage('cp936')
1639
.setPinterType(80) // wrong typo in the library
1740
.align('center')
1841
.bold(true)
1942
.size(2)
20-
.line('气球打印单')
43+
.line(i18n[lang].receipt)
2144
.emptyLine(1)
2245
.line(`ID: ${id}`)
2346
.emptyLine(1)
2447
.bold(false)
2548
.size(1)
2649
.line('===========================================')
2750
.emptyLine(1)
28-
.oneLine('座位', location)
29-
.oneLine('气球', problem)
30-
.oneLine('颜色', color)
31-
.oneLine('备注', comment)
51+
.oneLine(i18n[lang].location, location)
52+
.oneLine(i18n[lang].problem, problem)
53+
.oneLine(i18n[lang].color, color)
54+
.oneLine(i18n[lang].comment, comment)
3255
.emptyLine(1)
3356
.align('center')
3457
.bold(true)
3558
.line('===========================================')
3659
.emptyLine(2)
3760
.size(0)
38-
.line(`队伍: ${teamname}`)
39-
.line('队伍当前气球状态:')
61+
.line(`${i18n[lang].team}: ${teamname}`)
62+
.line(`${i18n[lang].status}:`)
4063
.line(`${status}`)
4164
.emptyLine(2)
4265
.line('Powered by hydro-dev/xcpc-tools')
4366
.emptyLine(3)
4467
.cut()
4568
.encode();
4669

47-
const logger = new Logger('fetcher');
70+
const logger = new Logger('balloon');
4871

4972
let timer = null;
5073
let printer = null;
@@ -53,18 +76,18 @@ async function getReceiptStatus(receipt) {
5376
const lp = receipt.split('/').pop();
5477
const oldPrinter = printer;
5578
printer = {
56-
printer: lp,
57-
info: fs.readFileSync(`/sys/class/usb/${lp}/device/ieee1284_id`, 'utf8').trim(),
79+
printer: receipt,
80+
info: fs.readFileSync(`/sys/class/usbmisc/${lp}/device/ieee1284_id`, 'utf8').trim(),
5881
};
5982
if (!oldPrinter || oldPrinter.info === printer.info) return;
6083
logger.info('Printer changed:', printer.printer, printer.info);
6184
const usbDevices = fs.readdirSync('/dev/usb');
6285
for (const f of usbDevices) {
6386
if (f.startsWith('lp')) {
64-
const lpid = fs.readFileSync(`/sys/class/usb/${f}/device/ieee1284_id`, 'utf8').trim();
87+
const lpid = fs.readFileSync(`/sys/class/usbmisc/${f}/device/ieee1284_id`, 'utf8').trim();
6588
if (lpid === oldPrinter.info) {
6689
logger.info('Printer found:', f, ':', lpid);
67-
oldPrinter.printer = f;
90+
oldPrinter.printer = `/dev/usb/${f}`;
6891
printer = oldPrinter;
6992
break;
7093
}
@@ -73,34 +96,37 @@ async function getReceiptStatus(receipt) {
7396
if (oldPrinter.info !== printer.info) throw Error('Printer not found, please check the printer connection.');
7497
}
7598

76-
async function printBalloon(doc) {
99+
async function printBalloon(doc, lang) {
77100
const bReceipt = receiptText(
78101
doc.balloonid,
79102
doc.location ? doc.location : 'N/A',
80103
doc.problem,
81-
doc.contestproblem.color,
104+
lang === 'zh' ? convertToChinese(doc.contestproblem.color) : doc.contestproblem.color,
82105
doc.awards ? doc.awards : 'N/A',
83106
doc.team,
84107
doc.total ? Object.keys(doc.total).map((k) => `- ${k}: ${doc.total[k].color}`).join('\n') : 'N/A',
108+
lang,
85109
);
86110
if (printer) {
87111
await getReceiptStatus(printer.printer);
88-
fs.writeFileSync(path.resolve(printer), bReceipt);
112+
fs.writeFileSync(path.resolve(printer.printer), bReceipt);
89113
}
90114
}
91115

92116
async function fetchTask(c) {
93117
if (timer) clearTimeout(timer);
94-
logger.info('Fetching Task from tools server...');
118+
logger.info('Fetching balloon task from tools server...');
95119
try {
96120
const { body } = await superagent.post(`${c.server}/client/${c.token}/balloon`).send();
97-
if (body.doc) {
98-
logger.info(`Print task ${body.doc.tid}#${body.doc._id}...`);
99-
await printBalloon(body.doc);
100-
await superagent.post(`${c.server}/client/${c.token}/doneballoon/${body.doc._id}`);
101-
logger.info(`Print task ${body.doc.tid}#${body.doc._id} completed.`);
121+
if (body.balloons) {
122+
for (const doc of body.balloons) {
123+
logger.info(`Print balloon task ${doc.teamid}#${doc.balloonid}...`);
124+
await printBalloon(doc, config.receiptLang);
125+
await superagent.post(`${c.server}/client/${c.token}/doneballoon/${doc.balloonid}`);
126+
logger.info(`Print task ${doc.teamid}#${doc.balloonid} completed.`);
127+
}
102128
} else {
103-
logger.info('No print task, sleeping...');
129+
logger.info('No balloon task, sleeping...');
104130
await sleep(5000);
105131
}
106132
} catch (e) {
@@ -113,4 +139,5 @@ async function fetchTask(c) {
113139
export async function apply() {
114140
await getReceiptStatus(config.balloon);
115141
if (config.token && config.server && config.balloon) await fetchTask(config);
142+
else logger.error('Config not found, please check the config.yaml');
116143
}

packages/server/client/printer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,5 @@ export async function apply() {
107107
}
108108
}
109109
if (config.token && config.server && config.printers?.length) await fetchTask(config);
110+
else logger.error('Config not found, please check the config.yaml');
110111
}

packages/server/config.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import path from 'path';
22
import Schema from 'schemastery';
3-
import { getPrinters } from 'unix-print';
43
import { version } from './package.json';
5-
import { fs, Logger, yaml } from './utils';
4+
import {
5+
fs, getPrinters, Logger, yaml,
6+
} from './utils';
67

78
const logger = new Logger('init');
89

@@ -31,21 +32,22 @@ password:
3132
let printers = [];
3233
if (isClient) {
3334
printers = await getPrinters().then((r) => r.map((p) => p.printer)).catch(() => []);
34-
logger.info(printers.length, 'printers found:', printers.map((p) => p.printer).join(', '));
35+
logger.info(printers.length, 'printers found:', printers.join(', '));
3536
if (process.platform === 'linux') {
3637
const usbDevices = fs.readdirSync('/dev/usb');
3738
for (const f of usbDevices) {
3839
if (f.startsWith('lp')) {
3940
const lpid = fs.readFileSync(`/sys/class/usbmisc/${f}/device/ieee1284_id`, 'utf8').trim();
40-
logger.info('USB Printer found:', f, ':', lpid);
41-
logger.info('If you want to use this printer for balloon print, set balloon:', f, 'in config.yaml.');
41+
logger.info(`USB Printer ${f} found: ${lpid}`);
42+
logger.info(`If you want to use this printer for balloon print, please set balloon: /dev/usb/${f} in config.yaml.`);
4243
}
4344
}
4445
} else logger.info('If you want to use balloon client, please run this on linux.');
4546
}
4647
const clientConfigDefault = yaml.dump({
4748
server: '',
4849
balloon: '',
50+
balloonLang: 'zh',
4951
printers,
5052
token: '',
5153
});
@@ -86,7 +88,8 @@ const serverSchema = Schema.intersect([
8688
]);
8789
const clientSchema = Schema.object({
8890
server: Schema.string().role('url').required(),
89-
balloon: Schema.string().required(),
91+
balloon: Schema.string(),
92+
balloonLang: Schema.union(['zh', 'en']).default('zh').required(),
9093
printers: Schema.array(Schema.string()).default([]).description('printer id list, will disable printing if unset'),
9194
token: Schema.string().required().description('Token generated on server'),
9295
fonts: Schema.array(Schema.string()).default([]),

packages/server/handler/client.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class ClientBallloonConnectHandler extends Handler {
9393
logger.info(`Client ${client.name}(${ip}) connected.`);
9494
const balloons = await this.ctx.db.balloon.find({ printDone: 0, shouldPrint: true }).sort({ time: 1 });
9595
this.response.body = { balloons };
96-
logger.info(`Client ${client.name} connected, print ${balloons.length} tasks sent.`);
96+
logger.info(`Client ${client.name} connected, balloon ${balloons.length} tasks sent.`);
9797
await this.ctx.db.client.updateOne({ id: params.cid }, { $set: { updateAt: new Date().getTime(), ip } });
9898
await this.ctx.db.balloon.update({ balloonid: { $in: balloons.map((b) => b.balloonid) } },
9999
{ $set: { receivedAt: new Date().getTime() } }, { multi: true });
@@ -104,12 +104,12 @@ class ClientBalloonDoneHandler extends Handler {
104104
async post(params) {
105105
const client = await this.ctx.db.client.findOne({ id: params.cid });
106106
if (!client) throw new ForbiddenError('Client', null, 'Client not found');
107-
const balloon = await this.ctx.db.balloon.findOne({ balloonid: +params.tid });
108-
if (!balloon) throw new ValidationError('Balloon', params.tid, 'Balloon not found');
109-
await this.ctx.db.balloon.updateOne({ balloonid: +params.tid }, { $set: { printDone: 1, printDoneAt: new Date().getTime() } });
107+
const balloon = await this.ctx.db.balloon.findOne({ balloonid: +params.bid });
108+
if (!balloon) throw new ValidationError('Balloon', params.bid, 'Balloon not found');
109+
await this.ctx.db.balloon.updateOne({ balloonid: +params.bid }, { $set: { printDone: 1, printDoneAt: new Date().getTime() } });
110110
if (!balloon.done) await this.ctx.fetcher.setBalloonDone(balloon.balloonid);
111111
this.response.body = { code: 1 };
112-
logger.info(`Client ${client.name} connected, print task ${balloon.teamid}#${balloon.balloonid} completed.`);
112+
logger.info(`Client ${client.name} connected, balloon task ${balloon.teamid}#${balloon.balloonid} completed.`);
113113
}
114114
}
115115

@@ -118,5 +118,5 @@ export async function apply(ctx: Context) {
118118
ctx.Route('client_print_fetch', '/client/:cid/print', ClientPrintConnectHandler);
119119
ctx.Route('client_print_done', '/client/:cid/doneprint/:tid', ClientPrintDoneHandler);
120120
ctx.Route('client_balloon_fetch', '/client/:cid/balloon', ClientBallloonConnectHandler);
121-
ctx.Route('client_balloon_done', '/client/:cid/doneballoon/:tid', ClientBalloonDoneHandler);
121+
ctx.Route('client_balloon_done', '/client/:cid/doneballoon/:bid', ClientBalloonDoneHandler);
122122
}

packages/server/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ async function apply(ctx) {
4949
}
5050
await ctx.lifecycle.flush();
5151
await ctx.parallel('app/listen');
52-
logger.success('Server started');
52+
logger.success('Tools started');
5353
process.send?.('ready');
5454
await ctx.parallel('app/ready');
5555
}

0 commit comments

Comments
 (0)