Completed
Push — master ( 35c902...cd00b8 )
by Equim
01:12
created

app.js (2 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
// 查成绩API,通过GET传入用户名和密码
71
app.get(/^\/g(?:|rades)$/, function (req, res, next) {
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 View Code Duplication
    }
76
    if (fullLog) {
77
        var start = new Date();
78
        console.log(`${timeStamp()} Started to query the grades: `.cyan + req.query.id.yellow);
79
    }
80
    access.login(req.query.id, req.query.pwd, res, function (headers) {
81
        if (fullLog) {
82
            console.log(`${timeStamp()} Successfully logged in.`.green);
83
        }
84
85
        // 实际上xnxq01id为空的时候和GET这个URL的效果是一样的,都是查询所有学期
86
        superagent
87
            .post('http://csujwc.its.csu.edu.cn/jsxsd/kscj/yscjcx_list')
88
            .set(headers)
89
            .type('form')
90
            .send({
91
                xnxq01id: req.query.sem
92
            })
93
            .end(function (err, iires) {
94
                if (err) {
95
                    console.log(`${timeStamp()} Failed to get grades page\n${err.stack}`.red);
96
                    res.status(404).send({ error: '无法进入成绩页面' });
97
                    return next(err);
98
                }
99
                if (fullLog) {
100
                    console.log(`${timeStamp()} Successfully entered grades page.`.green);
101
                }
102
103
                let $ = cheerio.load(iires.text);
104
105
                let result = {
106
                    name: escaper.unescape($('#Top1_divLoginName').text().match(/\s.+\(/)[0].replace(/\s|\(/g, '')),
107
                    id: escaper.unescape($('#Top1_divLoginName').text().match(/\(.+\)/)[0].replace(/\(|\)/g, '')),
108
                    grades: {},
109
                    'subject-count': 0,
110
                    failed: {},
111
                    'failed-count': 0,
112
                };
113
114
                // 获取成绩列表
115
                $('#dataList tr').each(function (index) {
116
                    if (index === 0) {
117
                        return;
118
                    }
119
                    let element = $(this).find('td');
120
121
                    let title = escaper.unescape(element.eq(3).text().match(/].+$/)[0].substring(1));
122
123
                    let item = {
124
                        sem: escaper.unescape(element.eq(2).text()),
125
                        reg: escaper.unescape(element.eq(4).text()),
126
                        exam: escaper.unescape(element.eq(5).text()),
127
                        overall: escaper.unescape(element.eq(6).text())
128
                    };
129
                    if (req.query.details) {
130
                        item.id = escaper.unescape(element.eq(3).text().match(/\[.+\]/)[0].replace(/\[|\]/g, ''));
131
                        item.attr = escaper.unescape(element.eq(8).text());
132
                        item.genre = escaper.unescape(element.eq(9).text());
133
                        item.credit = escaper.unescape(element.eq(7).text());
134
                    }
135
136
                    // 如果有补考记录,则以最高分的为准
137
                    // 这段代码是拿可读性换了时间复杂度……
138
                    if (title in result.grades) {
139
                        // 暂不考虑NaN
140
                        if (item.overall < result.grades[title].overall) {
141
                            return;
142
                        }
143
                        if (!element.eq(6).css('color')) {
144
                            delete result.failed[title];
145
                        }
146
                    } else if (element.eq(6).css('color')) {
147
                        result.failed[title] = item;
148
                    }
149
150
                    result.grades[title] = item;
151
                });
152
153
                result['subject-count'] = Object.keys(result.grades).length;
154
                result['failed-count'] = Object.keys(result.failed).length;
155
156
                access.logout(headers, res, function() {
157
                    // 返回JSON
158
                    res.send(JSON.stringify(result));
159
                    if (fullLog) {
160
                        console.log(`${timeStamp()} Successfully logged out: `.green +
161
                            req.query.id.yellow +
162
                            ` (processed in ${new Date() - start} ms)`.green);
163 View Code Duplication
                    }
164
                });
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...
165
            });
166
    });
167
});
168
169
// 查考试API,通过GET传入用户名和密码
170
app.get(/^\/e(?:|xams)$/, function (req, res, next) {
171
    if (!req.query.id || !req.query.pwd || (req.query.sem && !(/^20\d{2}-20\d{2}-[1-2]$/).test(req.query.sem))) {
172
        res.status(404).send({ error: "参数不正确" });
173
        return;
174
    }
175
    if (fullLog) {
176
        var start = new Date();
177
        console.log(`${timeStamp()} Started to query the exams: `.cyan + req.query.id.yellow);
178
    }
179
    access.login(req.query.id, req.query.pwd, res, function (headers) {
180
        if (fullLog) {
181
            console.log(`${timeStamp()} Successfully logged in.`.green);
182
        }
183
        superagent
184
            .post('http://csujwc.its.csu.edu.cn/jsxsd/xsks/xsksap_list')
185
            .set(headers)
186
            .type('form')
187
            .send({
188
                xqlbmc: '',
189
                xnxqid: req.query.sem || getSem(),
190
                xqlb: ''
191
            })
192
            .end(function (err, iires) {
193
                if (err) {
194
                    console.log(`${timeStamp()} Failed to reach exams page\n${err.stack}`.red);
195
                    res.status(404).send({ error: '获取成绩失败' });
196
                    return next(err);
197
                }
198
                if (fullLog) {
199
                    console.log(`${timeStamp()} Successfully entered exams page.`.green);
200
                }
201
202
                let $ = cheerio.load(iires.text);
203
204
                let result = {
205
                    name: escaper.unescape($('#Top1_divLoginName').text().match(/\s.+\(/)[0].replace(/\s|\(/g, '')),
206
                    id: escaper.unescape($('#Top1_divLoginName').text().match(/\(.+\)/)[0].replace(/\(|\)/g, '')),
207
                    sem: req.query.sem || getSem(),
208
                    exams: {},
209
                    'exams-count': 0,
210
                };
211
212
                $('#dataList tr').each(function (index) {
213
                    if (index === 0) {
214
                        return;
215
                    }
216
                    let element = $(this).find('td');
217
                    let title = escaper.unescape(element.eq(3).text());
218
219
                    let item = {
220
                        time: escaper.unescape(element.eq(4).text()),
221
                        location: escaper.unescape(element.eq(5).text()),
222
                        seat: escaper.unescape(element.eq(6).text())
223
                    };
224
225
                    result.exams[title] = item;
226
                    result['exams-count']++;
227
                });
228
229
                access.logout(headers, res, function() {
230
                    res.send(JSON.stringify(result));
231
                    if (fullLog) {
232
                        console.log(`${timeStamp()} Successfully logged out: `.green +
233
                            req.query.id.yellow +
234
                            ` (processed in ${new Date() - start} ms)`.green);
235
                    }
236
                });
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...
237
            });
238
    });
239
});
240
241
app.listen(port, () => {
242
    console.log(`${timeStamp()} The API is now running on port ${port}. Full logging is ${fullLog ? 'enabled' : 'disabled'}`.green);
243
});