Completed
Push — master ( 5e0f69...985619 )
by Equim
01:05
created

app.js (5 issues)

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