Completed
Push — master ( 1cb6cd...b745b3 )
by Equim
01:20 queued 19s
created

app.js (1 issue)

Severity
1
'use strict';
2
3
const
4
    express    = require('express'),
5
    superagent = require('superagent'),
6
    cheerio    = require('cheerio'),
7
    colors     = require('colors'),
8
    program    = require('commander'),
9
    moment     = require('moment'),
10
    co         = require('co'),
11
    thunkify   = require('thunkify'),
12
    access     = require('./lib/access.js');
13
14
program
15
    .option('-h, --help')
16
    .option('-v, --version')
17
    .option('-p, --port [value]', parseInt)
18
    .option('-f, --fullLog')
19
    .parse(process.argv);
20
21
// 别问我为什么这里逻辑这么奇怪……测试的结果确实是这样的啊hhh
22
if (!program.help || !program.version) {
23
    console.log(
24
`${`CSUEMS API v${require('./package').version}
25
by The Liberators`.rainbow}${program.help ? '' :
26
`
27
28
Preparation:
29
  ${'(This section is WIP)'.grey}
30
31
Usage:
32
  npm start [-- <options...>]
33
34
Options:
35
  -h, --help          print this message and exit.
36
  -v, --version       print the version and exit.
37
  -f, --fullLog       enable full log, by default only errors are logged.
38
  -p, --port [value]  specify a port to listen, process.env.PORT || 2333 by default.
39
40
Examples:
41
  $ npm start -- -p 43715               # listening to 43715
42
  $ forever start app.js                # deploy with forever as daemon (root access recommended)
43
  $ pm2 start -i 0 -n "csuapi" app.js   # deploy with pm2 as daemon  (root access recommended)`}`);
44
    process.exit(0);
45
}
46
47
superagent.Request.prototype.endThunk = thunkify(superagent.Request.prototype.end);
48
49
const
50
    logging = (log) => console.log(`${moment().format('[[]YY-MM-DD HH:mm:ss[]]')} ${log}`),
51
    fullLogging = (log) => program.fullLog && logging(log),
52
    getSem = () => {
53
        // 以系统时间获取当前学期
54
        let now = new Date();
55
        let month = now.getMonth();
56
        let year = now.getFullYear();
57
        if (month <= 6) {
58
            return `${year - 1}-${year}-${month > 0 ? 2 : 1}`;
59
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
60
            return `${year}-${year + 1}-1`;
61
        }
62
    },
63
//  wait = (ms) => (callback) => setTimeout(callback, ms),
64
    port = program.port || process.env.PORT || 2333,
65
    app = express();
66
67
// 获取文档
68
app.get('/doc', (req, res) => res.sendFile(`${__dirname}/doc/API.html`));
69
70
// 查成绩API,通过GET传入用户名和密码
71
app.get(/^\/g(?:|rades)$/, co.wrap(function *(req, res) {
72
    if (!req.query.id || !req.query.pwd || (req.query.sem && !(/^20\d{2}-20\d{2}-[1-2]$/).test(req.query.sem))) {
73
        res.status(404).send({ error: "参数不正确" });
74
        return;
75
    }
76
77
    let start = new Date();
78
    fullLogging('Started to query the grades: '.cyan + req.query.id.yellow);
79
80
    let headers;
81
    try {
82
        headers = yield access.login(req.query.id, req.query.pwd);
83
    } catch (err) {
84
        logging(err.eqMessage.inner.red);
85
        res.status(404).send({ error: err.eqMessage.public });
86
        return;
87
    }
88
    fullLogging('Successfully logged in.'.green);
89
90
    let ires;
91
    try {
92
        // 实际上xnxq01id为空的时候和GET这个URL的效果是一样的,都是查询所有学期
93
        ires = yield superagent
94
            .post('http://csujwc.its.csu.edu.cn/jsxsd/kscj/yscjcx_list')
95
            .set(headers)
96
            .type('form')
97
            .send({ xnxq01id: req.query.sem })
98
            .endThunk();
99
    } catch (err) {
100
        logging(`Failed to get grades page\n${err.stack}`.red);
101
        res.status(404).send({ error: '无法进入成绩页面' });
102
        return;
103
    } finally {
104
        // 直接异步进行?
105
        co(function *() {
106
            try {
107
                yield access.logout(headers);
108
            } catch (err) {
109
                logging(err.eqMessage.inner.red);
110
                return;
111
            }
112
            fullLogging('Successfully logged out: '.green + req.query.id.yellow);
113
        });
114
    }
115
    fullLogging('Successfully entered grades page.'.green);
116
117
    let $ = cheerio.load(ires.text);
118
119
    let top = $('#Top1_divLoginName').text();
120
    let result = {
121
        name: top.match(/\s.+\(/)[0].replace(/\s|\(/g, ''),
122
        id: top.match(/\(.+\)/)[0].replace(/\(|\)/g, ''),
123
        grades: {},
124
        'subject-count': 0,
125
        failed: {},
126
        'failed-count': 0,
127
    };
128
    // 获取成绩列表
129
    $('#dataList tr').each(function (index) {
130
        if (index === 0) {
131
            return;
132
        }
133
134
        let element = $(this).find('td');
135
136
        let title = element.eq(3).text().match(/].+$/)[0].substring(1);
137
        let item = {
138
            sem: element.eq(2).text(),
139
            reg: element.eq(4).text(),
140
            exam: element.eq(5).text(),
141
            overall: element.eq(6).text()
142
        };
143
        if (req.query.details) {
144
            item.id = element.eq(3).text().match(/\[.+\]/)[0].replace(/\[|\]/g, '');
145
            item.attr = element.eq(8).text();
146
            item.genre = element.eq(9).text();
147
            item.credit = element.eq(7).text();
148
        }
149
150
        // 如果有补考记录,则以最高分的为准(暂不考虑NaN)
151
        if (title in result.grades && item.overall < result.grades[title].overall) {
152
            return;
153
        }
154
155
        result.grades[title] = item;
156
157
        // 挂科判定
158
        if (element.eq(6).css('color')) {
159
            result.failed[title] = item;
160
        } else {
161
            delete result.failed[title];
162
        }
163
    });
164
165
    result['subject-count'] = Object.keys(result.grades).length;
166
    result['failed-count'] = Object.keys(result.failed).length;
167
168
    res.send(JSON.stringify(result));
169
    fullLogging(`Successfully responded. (req -> res processed in ${new Date() - start} ms)`.green);
170
}));
171
172
// 查考试API,通过GET传入用户名和密码
173
app.get(/^\/e(?:|xams)$/, co.wrap(function *(req, res) {
174
    if (!req.query.id || !req.query.pwd || (req.query.sem && !(/^20\d{2}-20\d{2}-[1-2]$/).test(req.query.sem))) {
175
        res.status(404).send({ error: "参数不正确" });
176
        return;
177
    }
178
179
    let start = new Date();
180
    fullLogging('Started to query the exams: '.cyan + req.query.id.yellow);
181
182
    let headers;
183
    try {
184
        headers = yield access.login(req.query.id, req.query.pwd);
185
    } catch (err) {
186
        logging(err.eqMessage.inner.red);
187
        res.status(404).send({ error: err.eqMessage.public });
188
        return;
189
    }
190
    fullLogging('Successfully logged in.'.green);
191
192
    let ires;
193
    let _sem = req.query.sem || getSem();
194
    try {
195
        ires = yield superagent
196
            .post('http://csujwc.its.csu.edu.cn/jsxsd/xsks/xsksap_list')
197
            .set(headers)
198
            .type('form')
199
            .send({
200
                xqlbmc: '',
201
                xnxqid: _sem,
202
                xqlb: ''
203
            })
204
            .endThunk();
205
    } catch (err) {
206
        logging(`Failed to reach exams page\n${err.stack}`.red);
207
        res.status(404).send({ error: '无法进入考试页面' });
208
        return;
209
    } finally {
210
        co(function *() {
211
            try {
212
                yield access.logout(headers);
213
            } catch (err) {
214
                logging(err.eqMessage.inner.red);
215
            }
216
            fullLogging('Successfully logged out: '.green + req.query.id.yellow);
217
        });
218
    }
219
    fullLogging('Successfully entered exams page.'.green);
220
221
    let $ = cheerio.load(ires.text);
222
223
    let top = $('#Top1_divLoginName').text();
224
    let result = {
225
        name: top.match(/\s.+\(/)[0].replace(/\s|\(/g, ''),
226
        id: top.match(/\(.+\)/)[0].replace(/\(|\)/g, ''),
227
        sem: _sem,
228
        exams: {},
229
        'exams-count': 0,
230
    };
231
    $('#dataList tr').each(function (index) {
232
        if (index === 0) {
233
            return;
234
        }
235
236
        let element = $(this).find('td');
237
238
        let title = element.eq(3).text();
239
        let item = {
240
            time: element.eq(4).text(),
241
            location: element.eq(5).text(),
242
            seat: element.eq(6).text()
243
        };
244
245
        result.exams[title] = item;
246
        result['exams-count']++;
247
    });
248
249
    res.send(JSON.stringify(result));
250
    fullLogging(`Successfully responded. (req -> res processed in ${new Date() - start} ms)`.green);
251
}));
252
253
app.listen(port, () => {
254
    logging(`The API is now running on port ${port}. Full logging is ${program.fullLog ? 'enabled' : 'disabled'}`.green);
255
});