@waline/vercel 1.8.0 → 1.10.0

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 CHANGED
@@ -1,28 +1,27 @@
1
1
  {
2
2
  "name": "@waline/vercel",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
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
- "@cloudbase/node-sdk": "^2.7.1",
11
- "@koa/cors": "^3.1.0",
9
+ "@cloudbase/node-sdk": "^2.9.0",
10
+ "@koa/cors": "^3.2.0",
12
11
  "akismet": "^2.0.6",
13
- "deta": "^1.0.2",
12
+ "deta": "^1.1.0",
14
13
  "dompurify": "^2.3.6",
15
14
  "fast-csv": "^4.3.6",
16
15
  "jsdom": "^19.0.0",
17
16
  "jsonwebtoken": "^8.5.1",
18
- "katex": "^0.15.2",
17
+ "katex": "^0.15.3",
19
18
  "leancloud-storage": "^4.12.2",
20
19
  "markdown-it": "^12.3.2",
21
20
  "markdown-it-emoji": "^2.0.0",
22
21
  "markdown-it-sub": "^1.0.0",
23
22
  "markdown-it-sup": "^1.0.0",
24
23
  "mathjax-full": "^3.2.0",
25
- "nodemailer": "^6.7.2",
24
+ "nodemailer": "^6.7.3",
26
25
  "nunjucks": "^3.2.3",
27
26
  "phpass": "^0.1.1",
28
27
  "prismjs": "^1.27.0",
@@ -30,7 +29,7 @@
30
29
  "request-promise-native": "^1.0.9",
31
30
  "think-logger3": "^1.3.1",
32
31
  "think-model": "^1.5.4",
33
- "think-model-mysql": "^1.1.6",
32
+ "think-model-mysql": "^1.1.7",
34
33
  "think-model-postgresql": "^1.1.6",
35
34
  "think-model-sqlite": "^1.2.2",
36
35
  "think-mongo": "^2.1.2",
@@ -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
  };
@@ -441,7 +441,12 @@ module.exports = class extends BaseRest {
441
441
 
442
442
  async putAction() {
443
443
  const data = this.post();
444
- const oldData = await this.modelInstance.select({ objectId: this.id });
444
+ let oldData = await this.modelInstance.select({ objectId: this.id });
445
+ if (think.isEmpty(oldData)) {
446
+ return this.success();
447
+ }
448
+
449
+ oldData = oldData[0];
445
450
  const preUpdateResp = await this.hook('preUpdate', {
446
451
  ...data,
447
452
  objectId: this.id,
@@ -451,18 +456,22 @@ module.exports = class extends BaseRest {
451
456
  return this.fail(preUpdateResp);
452
457
  }
453
458
 
454
- await this.modelInstance.update(data, { objectId: this.id });
459
+ const newData = await this.modelInstance.update(data, {
460
+ objectId: this.id,
461
+ });
455
462
 
456
463
  if (
457
464
  oldData.status === 'waiting' &&
458
465
  data.status === 'approved' &&
459
466
  oldData.pid
460
467
  ) {
461
- let pComment = await this.modelInstance.select({ objectId: oldData.pid });
468
+ let pComment = await this.modelInstance.select({
469
+ objectId: oldData.pid,
470
+ });
462
471
  pComment = pComment[0];
463
472
 
464
473
  const notify = this.service('notify');
465
- await notify.run(oldData, pComment, true);
474
+ await notify.run(newData, pComment, true);
466
475
  }
467
476
 
468
477
  await this.hook('postUpdate', data);
@@ -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
+ };
@@ -19,10 +19,11 @@ module.exports = class extends think.Controller {
19
19
  'color: white; background: #0078E7; padding:5px 0;',
20
20
  'padding:4px;border:1px solid #0078E7;'
21
21
  );
22
+ const params = new URLSearchParams(location.search.slice(1));
22
23
  const waline = new Waline({
23
24
  el: '#waline',
24
- path: '/',
25
- lang: new URLSearchParams(location.search.slice(1)).get('lng'),
25
+ path: params.get('path') || '/',
26
+ lang: params.get('lng'),
26
27
  serverURL: location.protocol + '//' + location.host + location.pathname.replace(/\\/+$/, '')
27
28
  });
28
29
  </script>
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('localhost', '127.0.0.1', 'github.com');
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)
@@ -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
+ };
@@ -5,6 +5,15 @@ const { GRAVATAR_STR } = process.env;
5
5
  const env = new nunjucks.Environment();
6
6
  env.addFilter('md5', (str) => helper.md5(str));
7
7
 
8
+ const DEFAULT_GRAVATAR_STR = `{%- set numExp = r/^[0-9]+$/g -%}
9
+ {%- set qqMailExp = r/^[0-9]+@qq.com$/ig -%}
10
+ {%- if numExp.test(nick) -%}
11
+ https://q1.qlogo.cn/g?b=qq&nk={{nick}}&s=100
12
+ {%- elif qqMailExp.test(mail) -%}
13
+ https://q1.qlogo.cn/g?b=qq&nk={{mail|replace('@qq.com', '')}}&s=100
14
+ {%- else -%}
15
+ https://seccdn.libravatar.org/avatar/{{mail|md5}}
16
+ {%- endif -%}`;
8
17
  module.exports = class extends think.Service {
9
18
  async stringify(comment) {
10
19
  const fn = think.config('avatarUrl');
@@ -15,8 +24,7 @@ module.exports = class extends think.Service {
15
24
  }
16
25
  }
17
26
 
18
- const gravatarStr =
19
- GRAVATAR_STR || 'https://seccdn.libravatar.org/avatar/{{mail|md5}}';
27
+ const gravatarStr = GRAVATAR_STR || DEFAULT_GRAVATAR_STR;
20
28
  return env.renderString(gravatarStr, comment);
21
29
  }
22
30
  };
@@ -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
- console.log(pushplus);
396
+ const discord = await this.discord({ title, content }, comment, parent);
361
397
  if (
362
- [wechat, qq, telegram, qywxAmWechat, pushplus].every(think.isEmpty) &&
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
- };