@waline/vercel 1.8.1 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -2
- package/src/config/config.js +2 -4
- package/src/controller/db.js +56 -0
- package/src/logic/base.js +7 -1
- package/src/logic/db.js +30 -0
- package/src/middleware/dashboard.js +4 -1
- package/src/service/notify.js +40 -2
- package/src/service/storage/inspirecloud.js +0 -180
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waline/vercel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.1",
|
|
4
4
|
"description": "vercel server for waline comment system",
|
|
5
5
|
"repository": "https://github.com/walinejs/waline",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "lizheming <i@imnerd.org>",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@byteinspire/api": "^1.0.12",
|
|
10
9
|
"@cloudbase/node-sdk": "^2.9.0",
|
|
11
10
|
"@koa/cors": "^3.2.0",
|
|
12
11
|
"akismet": "^2.0.6",
|
package/src/config/config.js
CHANGED
|
@@ -17,7 +17,6 @@ const {
|
|
|
17
17
|
AVATAR_PROXY,
|
|
18
18
|
GITHUB_TOKEN,
|
|
19
19
|
DETA_PROJECT_KEY,
|
|
20
|
-
INSPIRECLOUD_SERVICE_SECRET,
|
|
21
20
|
OAUTH_URL,
|
|
22
21
|
|
|
23
22
|
MARKDOWN_CONFIG = '{}',
|
|
@@ -37,6 +36,7 @@ const {
|
|
|
37
36
|
QQ_TEMPLATE,
|
|
38
37
|
TG_TEMPLATE,
|
|
39
38
|
WX_TEMPLATE,
|
|
39
|
+
DISCORD_TEMPLATE,
|
|
40
40
|
} = process.env;
|
|
41
41
|
|
|
42
42
|
let storage = 'leancloud';
|
|
@@ -64,9 +64,6 @@ if (LEAN_KEY) {
|
|
|
64
64
|
} else if (DETA_PROJECT_KEY) {
|
|
65
65
|
storage = 'deta';
|
|
66
66
|
jwtKey = jwtKey || DETA_PROJECT_KEY;
|
|
67
|
-
} else if (INSPIRECLOUD_SERVICE_SECRET) {
|
|
68
|
-
storage = 'inspirecloud';
|
|
69
|
-
jwtKey = jwtKey || INSPIRECLOUD_SERVICE_SECRET;
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
if (think.env === 'cloudbase' && storage === 'sqlite') {
|
|
@@ -117,4 +114,5 @@ module.exports = {
|
|
|
117
114
|
QQTemplate: QQ_TEMPLATE,
|
|
118
115
|
TGTemplate: TG_TEMPLATE,
|
|
119
116
|
WXTemplate: WX_TEMPLATE,
|
|
117
|
+
DiscordTemplate: DISCORD_TEMPLATE,
|
|
120
118
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require('fs/promises');
|
|
2
|
+
const BaseRest = require('./rest');
|
|
3
|
+
|
|
4
|
+
module.exports = class extends BaseRest {
|
|
5
|
+
async getAction() {
|
|
6
|
+
const exportData = {
|
|
7
|
+
type: 'waline',
|
|
8
|
+
version: 1,
|
|
9
|
+
time: Date.now(),
|
|
10
|
+
tables: ['Comment', 'Counter', 'Users'],
|
|
11
|
+
data: {
|
|
12
|
+
Comment: [],
|
|
13
|
+
Counter: [],
|
|
14
|
+
Users: [],
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < exportData.tables.length; i++) {
|
|
19
|
+
const tableName = exportData.tables[i];
|
|
20
|
+
const model = this.model(tableName);
|
|
21
|
+
const data = await model.select({});
|
|
22
|
+
exportData.data[tableName] = data;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return this.success(exportData);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async postAction() {
|
|
29
|
+
const file = this.file('file');
|
|
30
|
+
try {
|
|
31
|
+
const jsonText = await fs.readFile(file.path, 'utf-8');
|
|
32
|
+
const importData = JSON.parse(jsonText);
|
|
33
|
+
if (!importData || importData.type !== 'waline') {
|
|
34
|
+
return this.fail('import data format not support!');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < importData.tables.length; i++) {
|
|
38
|
+
const tableName = importData.tables[i];
|
|
39
|
+
const model = this.model(tableName);
|
|
40
|
+
|
|
41
|
+
// delete all data at first
|
|
42
|
+
await model.delete({});
|
|
43
|
+
// then add data one by one
|
|
44
|
+
for (let j = 0; j < importData.data[tableName].length; j++) {
|
|
45
|
+
await model.add(importData.data[tableName][j]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return this.success();
|
|
49
|
+
} catch (e) {
|
|
50
|
+
if (think.isPrevent(e)) {
|
|
51
|
+
return this.success();
|
|
52
|
+
}
|
|
53
|
+
return this.fail(e.message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
package/src/logic/base.js
CHANGED
|
@@ -17,7 +17,13 @@ module.exports = class extends think.Logic {
|
|
|
17
17
|
secureDomains = think.isArray(secureDomains)
|
|
18
18
|
? secureDomains
|
|
19
19
|
: [secureDomains];
|
|
20
|
-
secureDomains.push(
|
|
20
|
+
secureDomains.push(
|
|
21
|
+
'localhost',
|
|
22
|
+
'127.0.0.1',
|
|
23
|
+
'github.com',
|
|
24
|
+
'api.twitter.com',
|
|
25
|
+
'www.facebook.com'
|
|
26
|
+
);
|
|
21
27
|
|
|
22
28
|
const match = secureDomains.some((domain) =>
|
|
23
29
|
think.isFunction(domain.test)
|
package/src/logic/db.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const Base = require('./base');
|
|
2
|
+
|
|
3
|
+
module.exports = class extends Base {
|
|
4
|
+
async __before(...args) {
|
|
5
|
+
await super.__before(...args);
|
|
6
|
+
|
|
7
|
+
const { userInfo } = this.ctx.state;
|
|
8
|
+
if (think.isEmpty(userInfo)) {
|
|
9
|
+
return this.fail(401);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (userInfo.type !== 'administrator') {
|
|
13
|
+
return this.fail(403);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @api {GET} /db export site data
|
|
19
|
+
* @apiGroup Site
|
|
20
|
+
* @apiVersion 0.0.1
|
|
21
|
+
*/
|
|
22
|
+
async getAction() {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @api {GET} /db import site data
|
|
26
|
+
* @apiGroup Site
|
|
27
|
+
* @apiVersion 0.0.1
|
|
28
|
+
*/
|
|
29
|
+
async postAction() {}
|
|
30
|
+
};
|
|
@@ -13,7 +13,10 @@ module.exports = function () {
|
|
|
13
13
|
window.SITE_URL = ${JSON.stringify(process.env.SITE_URL)};
|
|
14
14
|
window.SITE_NAME = ${JSON.stringify(process.env.SITE_NAME)};
|
|
15
15
|
</script>
|
|
16
|
-
<script src="
|
|
16
|
+
<script src="${
|
|
17
|
+
process.env.WALINE_ADMIN_MODULE_ASSET_URL ||
|
|
18
|
+
'https://cdn.jsdelivr.net/npm/@waline/admin'
|
|
19
|
+
}"></script>
|
|
17
20
|
</body>
|
|
18
21
|
</html>`;
|
|
19
22
|
};
|
package/src/service/notify.js
CHANGED
|
@@ -317,6 +317,42 @@ module.exports = class extends think.Service {
|
|
|
317
317
|
});
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
async discord({ title, content }, self, parent) {
|
|
321
|
+
const { DISCORD_WEBHOOK, SITE_NAME, SITE_URL } = process.env;
|
|
322
|
+
if (!DISCORD_WEBHOOK) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const data = {
|
|
327
|
+
self,
|
|
328
|
+
parent,
|
|
329
|
+
site: {
|
|
330
|
+
name: SITE_NAME,
|
|
331
|
+
url: SITE_URL,
|
|
332
|
+
postUrl: SITE_URL + self.url + '#' + self.objectId,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
title = nunjucks.renderString(title, data);
|
|
336
|
+
content = nunjucks.renderString(
|
|
337
|
+
think.config('DiscordTemplate') ||
|
|
338
|
+
`💬 {{site.name|safe}}的文章《{{postName}}》有新评论啦
|
|
339
|
+
【评论者昵称】:{{self.nick}}
|
|
340
|
+
【评论者邮箱】:{{self.mail}}
|
|
341
|
+
【内容】:{{self.comment}}
|
|
342
|
+
【地址】:{{site.postUrl}}`,
|
|
343
|
+
data
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
return request({
|
|
347
|
+
uri: DISCORD_WEBHOOK,
|
|
348
|
+
method: 'POST',
|
|
349
|
+
form: {
|
|
350
|
+
content: title + '\n' + content,
|
|
351
|
+
},
|
|
352
|
+
json: true,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
320
356
|
async run(comment, parent, disableAuthorNotify = false) {
|
|
321
357
|
const { AUTHOR_EMAIL, BLOGGER_EMAIL } = process.env;
|
|
322
358
|
const { mailSubject, mailTemplate, mailSubjectAdmin, mailTemplateAdmin } =
|
|
@@ -357,9 +393,11 @@ module.exports = class extends think.Service {
|
|
|
357
393
|
const qq = await this.qq(comment, parent);
|
|
358
394
|
const telegram = await this.telegram(comment, parent);
|
|
359
395
|
const pushplus = await this.pushplus({ title, content }, comment, parent);
|
|
360
|
-
|
|
396
|
+
const discord = await this.discord({ title, content }, comment, parent);
|
|
361
397
|
if (
|
|
362
|
-
[wechat, qq, telegram, qywxAmWechat, pushplus].every(
|
|
398
|
+
[wechat, qq, telegram, qywxAmWechat, pushplus, discord].every(
|
|
399
|
+
think.isEmpty
|
|
400
|
+
) &&
|
|
363
401
|
!isReplyAuthor
|
|
364
402
|
) {
|
|
365
403
|
mailList.push({ to: AUTHOR, title, content });
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
const inspirecloud = require('@byteinspire/api');
|
|
2
|
-
const Base = require('./base');
|
|
3
|
-
|
|
4
|
-
module.exports = class extends Base {
|
|
5
|
-
constructor(tableName) {
|
|
6
|
-
super(tableName);
|
|
7
|
-
this.db = inspirecloud.db;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
parseWhere(where) {
|
|
11
|
-
const _where = {};
|
|
12
|
-
if (think.isEmpty(where)) {
|
|
13
|
-
return _where;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const parseKey = (k) => (k === 'objectId' ? '_id' : k);
|
|
17
|
-
for (const k in where) {
|
|
18
|
-
if (k === '_complex') {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (think.isString(where[k])) {
|
|
23
|
-
_where[parseKey(k)] =
|
|
24
|
-
k === 'objectId' ? this.db.ObjectId(where[k]) : where[k];
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
if (where[k] === undefined) {
|
|
28
|
-
_where[parseKey(k)] = undefined;
|
|
29
|
-
}
|
|
30
|
-
if (Array.isArray(where[k])) {
|
|
31
|
-
if (where[k][0]) {
|
|
32
|
-
const handler = where[k][0].toUpperCase();
|
|
33
|
-
switch (handler) {
|
|
34
|
-
case 'IN':
|
|
35
|
-
_where[parseKey(k)] = {
|
|
36
|
-
$in:
|
|
37
|
-
k === 'objectId'
|
|
38
|
-
? where[k][1].map(this.db.ObjectId)
|
|
39
|
-
: where[k][1],
|
|
40
|
-
};
|
|
41
|
-
break;
|
|
42
|
-
case 'NOT IN':
|
|
43
|
-
_where[parseKey(k)] = {
|
|
44
|
-
$nin:
|
|
45
|
-
k === 'objectId'
|
|
46
|
-
? where[k][1].map(this.db.ObjectId)
|
|
47
|
-
: where[k][1],
|
|
48
|
-
};
|
|
49
|
-
break;
|
|
50
|
-
case 'LIKE': {
|
|
51
|
-
const first = where[k][1][0];
|
|
52
|
-
const last = where[k][1].slice(-1);
|
|
53
|
-
let reg;
|
|
54
|
-
if (first === '%' && last === '%') {
|
|
55
|
-
reg = new RegExp(where[k][1].slice(1, -1));
|
|
56
|
-
} else if (first === '%') {
|
|
57
|
-
reg = new RegExp(where[k][1].slice(1) + '$');
|
|
58
|
-
} else if (last === '%') {
|
|
59
|
-
reg = new RegExp('^' + where[k][1].slice(0, -1));
|
|
60
|
-
}
|
|
61
|
-
_where[parseKey(k)] = { $regex: reg };
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
case '!=':
|
|
65
|
-
_where[parseKey(k)] = { $ne: where[k] };
|
|
66
|
-
break;
|
|
67
|
-
case '>':
|
|
68
|
-
_where[parseKey(k)] = { $gt: where[k] };
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return _where;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
where(where) {
|
|
79
|
-
const filter = this.parseWhere(where);
|
|
80
|
-
if (!where._complex) {
|
|
81
|
-
return filter;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const filters = [];
|
|
85
|
-
for (const k in where._complex) {
|
|
86
|
-
if (k === '_logic') {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
filters.push({
|
|
91
|
-
...this.parseWhere({ [k]: where._complex[k] }),
|
|
92
|
-
...filter,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return { [`$${where._complex._logic}`]: filters };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async _select(where, { desc, limit, offset, field } = {}) {
|
|
100
|
-
const instance = this.db.table(this.tableName);
|
|
101
|
-
const query = instance.where(this.where(where));
|
|
102
|
-
|
|
103
|
-
if (desc) {
|
|
104
|
-
query.sort({ [desc]: -1 });
|
|
105
|
-
}
|
|
106
|
-
if (limit) {
|
|
107
|
-
query.limit(limit);
|
|
108
|
-
}
|
|
109
|
-
if (offset) {
|
|
110
|
-
query.skip(offset);
|
|
111
|
-
}
|
|
112
|
-
if (field) {
|
|
113
|
-
const _field = {};
|
|
114
|
-
field.forEach((f) => {
|
|
115
|
-
_field[f] = 1;
|
|
116
|
-
});
|
|
117
|
-
query.projection(_field);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const data = await query.find();
|
|
121
|
-
data.forEach((item) => {
|
|
122
|
-
item.objectId = item._id.toString();
|
|
123
|
-
delete item._id;
|
|
124
|
-
});
|
|
125
|
-
return data;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async select(where, options = {}) {
|
|
129
|
-
let data = [];
|
|
130
|
-
let ret = [];
|
|
131
|
-
let offset = options.offset || 0;
|
|
132
|
-
do {
|
|
133
|
-
options.offset = offset + data.length;
|
|
134
|
-
ret = await this._select(where, options);
|
|
135
|
-
data = data.concat(ret);
|
|
136
|
-
} while (ret.length === 1000);
|
|
137
|
-
|
|
138
|
-
return data;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async count(where = {}) {
|
|
142
|
-
const instance = this.db.table(this.tableName);
|
|
143
|
-
const query = instance.where(this.where(where));
|
|
144
|
-
|
|
145
|
-
return query.count();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async add(data) {
|
|
149
|
-
const instance = this.db.table(this.tableName);
|
|
150
|
-
const tableData = instance.create(data);
|
|
151
|
-
await instance.save(tableData);
|
|
152
|
-
|
|
153
|
-
tableData.objectId = tableData._id.toString();
|
|
154
|
-
delete tableData._id;
|
|
155
|
-
return tableData;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async update(data, where) {
|
|
159
|
-
const instance = this.db.table(this.tableName);
|
|
160
|
-
const query = instance.where(this.where(where));
|
|
161
|
-
const items = await query.find();
|
|
162
|
-
|
|
163
|
-
return Promise.all(
|
|
164
|
-
items.map(async (item) => {
|
|
165
|
-
const updateData = typeof data === 'function' ? data(item) : data;
|
|
166
|
-
for (const k in updateData) {
|
|
167
|
-
item[k] = updateData[k];
|
|
168
|
-
}
|
|
169
|
-
await instance.save(item);
|
|
170
|
-
return item;
|
|
171
|
-
})
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async delete(where) {
|
|
176
|
-
const instance = this.db.table(this.tableName);
|
|
177
|
-
const query = instance.where(this.where(where));
|
|
178
|
-
return query.delete();
|
|
179
|
-
}
|
|
180
|
-
};
|