From f06fcc20566f7a1fc0806436e0d7532e796041fc Mon Sep 17 00:00:00 2001 From: Xeltica Date: Tue, 4 Aug 2020 16:43:13 +0900 Subject: [PATCH] 1.0.0 --- package.json | 14 ++++++++------ src/app.ts | 1 + src/const.ts | 2 +- src/format.ts | 33 +++++++++++++++++++++++++++++++++ src/misskey.ts | 16 ++++++++++++++++ src/router.ts | 20 +++++++++++++++++++- src/users.ts | 5 +++++ src/views/_base.pug | 15 +++++++++++---- src/views/_components.pug | 3 +++ src/views/about.pug | 8 ++++++++ src/views/error.pug | 3 +-- src/views/logined.pug | 3 ++- src/views/term.pug | 12 ++++++++++++ src/views/welcome.pug | 16 ++++++---------- src/worker.ts | 34 ++++++++++++++++++++++++++++++++++ styles/style.scss | 4 ++++ yarn.lock | 30 ++++++++++++++++++++++++++++++ 17 files changed, 194 insertions(+), 25 deletions(-) create mode 100644 src/format.ts create mode 100644 src/misskey.ts create mode 100644 src/views/about.pug create mode 100644 src/views/term.pug create mode 100644 src/worker.ts diff --git a/package.json b/package.json index eaf10e0..291a864 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misshaialert", - "version": "1.0.0-alpha.1", + "version": "1.0.0", "description": "", "main": "built/app.js", "author": "Xeltica", @@ -22,6 +22,7 @@ "@types/koa-bodyparser": "^4.3.0", "@types/koa-mount": "^4.0.0", "@types/koa-static": "^4.0.1", + "@types/node-cron": "^2.0.3", "@types/uuid": "^8.0.0", "axios": "^0.19.2", "koa": "^2.13.0", @@ -31,13 +32,14 @@ "koa-session": "^6.0.0", "koa-static": "^5.0.0", "koa-views": "^6.3.0", + "node-cron": "^2.0.3", "pg": "^8.3.0", "pug": "^3.0.0", + "reflect-metadata": "^0.1.10", "sass": "^1.26.10", "typeorm": "0.2.25", "typescript": "^3.9.7", - "uuid": "^8.3.0", - "reflect-metadata": "^0.1.10" + "uuid": "^8.3.0" }, "devDependencies": { "@types/axios": "^0.14.0", @@ -45,6 +47,7 @@ "@types/koa-router": "^7.4.1", "@types/koa-session": "^5.10.2", "@types/koa-views": "^2.0.4", + "@types/node": "^8.0.29", "@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/parser": "^3.7.0", "copyfiles": "^2.3.0", @@ -55,7 +58,6 @@ "npm-run-all": "^4.1.5", "prettier": "^2.0.5", "rimraf": "^3.0.2", - "ts-node": "3.3.0", - "@types/node": "^8.0.29" + "ts-node": "3.3.0" } -} \ No newline at end of file +} diff --git a/src/app.ts b/src/app.ts index 08ab0df..9e39145 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,5 +2,6 @@ import { initDb } from './db'; (async () => { await initDb(); + (await import('./worker')).default(); (await import('./server')).default(); })(); \ No newline at end of file diff --git a/src/const.ts b/src/const.ts index 0434c77..d0964f3 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,5 +1,5 @@ export default { - version: '1.0.0-alpha.1', + version: '1.0.0', changelog: [ '初版' ], diff --git a/src/format.ts b/src/format.ts new file mode 100644 index 0000000..eddd6ac --- /dev/null +++ b/src/format.ts @@ -0,0 +1,33 @@ +import { api } from './misskey'; +import { config } from './config'; +import { User } from './models/entities/user'; +import { updateUser } from './users'; + +export const format = async (user: User): Promise => { + const miUser = await api>(user.host, 'users/show', { username: user.username }, user.token); + if (miUser.error) { + throw miUser.error; + } + const notesDelta = toSignedString(miUser.notesCount - user.prevNotesCount); + const followingDelta = toSignedString(miUser.followingCount - user.prevFollowingCount); + const followersDelta = toSignedString(miUser.followersCount - user.prevFollowersCount); + + await updateUser(user.username, user.host, { + prevNotesCount: miUser.notesCount, + prevFollowingCount: miUser.followingCount, + prevFollowersCount: miUser.followersCount, + }); + + return `昨日のMisskeyの活動は + +ノート: ${miUser.notesCount}(${notesDelta}) +フォロー : ${miUser.followingCount}(${followingDelta}) +フォロワー :${miUser.followersCount}(${followersDelta}) + +でした。 +${config.url} + +#misshaialert`; +}; + +export const toSignedString = (num: number): string => num < 0 ? num.toString() : '+' + num; \ No newline at end of file diff --git a/src/misskey.ts b/src/misskey.ts new file mode 100644 index 0000000..ac11586 --- /dev/null +++ b/src/misskey.ts @@ -0,0 +1,16 @@ +import axios from 'axios'; +import _const from './const'; + +export const ua = `Mozilla/5.0 misshaialertBot/${_const.version} +https://github.com/Xeltica/misshaialert Node/${process.version}`; + +axios.defaults.headers['User-Agent'] = ua; + +axios.defaults.validateStatus = (stat) => stat < 500; + +export const api = (host: string, endpoint: string, arg: Record, i?: string): Promise => { + const a = { ...arg }; + if (i) { + a.i = i; + } + return axios.post(`https://${host}/api/${endpoint}`, a).then(res => res.data); +}; \ No newline at end of file diff --git a/src/router.ts b/src/router.ts index 5619879..1f361ef 100644 --- a/src/router.ts +++ b/src/router.ts @@ -4,7 +4,7 @@ import { die } from './die'; import { v4 as uuid } from 'uuid'; import { config } from './config'; import axios from 'axios'; -import { upsertUser, getUser, getUserCount } from './users'; +import { upsertUser, getUser, getUserCount, updateUser } from './users'; export const router = new Router(); @@ -32,6 +32,14 @@ router.get('/login', async ctx => { ctx.redirect(url); }); +router.get('/terms', async ctx => { + await ctx.render('term'); +}); + +router.get('/about', async ctx => { + await ctx.render('about'); +}); + router.get('/miauth', async ctx => { const session = ctx.query.session as string | undefined; if (!session) { @@ -41,12 +49,14 @@ router.get('/miauth', async ctx => { const host = sessionHostCache[session]; if (!host) { await die(ctx, '問題が発生しました。お手数ですが、最初からやり直してください。'); + return; } const url = `https://${host}/api/miauth/${session}/check`; const { token, user } = (await axios.post(url)).data; if (!token || !user) { await die(ctx, '問題が発生しました。お手数ですが、最初からやり直してください。'); + return; } await upsertUser(user.username, host, token); @@ -54,7 +64,15 @@ router.get('/miauth', async ctx => { if (!u) { await die(ctx, '問題が発生しました。お手数ですが、最初からやり直してください。'); + return; } + + await updateUser(u.username, u.host, { + prevNotesCount: user.notesCount, + prevFollowingCount: user.followingCount, + prevFollowersCount: user.followersCount, + }); + await ctx.render('logined', { user: u }); }); diff --git a/src/users.ts b/src/users.ts index dc0c098..16dc7db 100644 --- a/src/users.ts +++ b/src/users.ts @@ -1,5 +1,6 @@ import { User } from './models/entities/user'; import { Users } from './models'; +import { DeepPartial } from 'typeorm'; export const getUser = (username: string, host: string): Promise => { return Users.findOne({ username, host }); @@ -14,6 +15,10 @@ export const upsertUser = async (username: string, host: string, token: string): } }; +export const updateUser = async (username: string, host: string, record: DeepPartial): Promise => { + await Users.update({ username, host }, record); +}; + export const deleteUser = async (username: string, host: string): Promise => { await Users.delete({ username, host }); }; diff --git a/src/views/_base.pug b/src/views/_base.pug index a6f9b89..3045274 100644 --- a/src/views/_base.pug +++ b/src/views/_base.pug @@ -6,19 +6,26 @@ html link(href='https://unpkg.com/sanitize.css' rel='stylesheet') meta(name="viewport", content="width=device-width, initial-scale=1.0") block meta + - const title = 'みす廃アラート' + - const desc = '✨Misskey での1日のノート数、フォロー数、フォロワー数をカウントし、深夜0時にお知らせする便利サービスです。'; title= title + meta(name='description' content=desc) + meta(property='og:title' content=title) + meta(property='og:description' content=desc) + meta(property='og:type' content='website') + meta(name='twitter:card' content='summary') + meta(name='twitter:site' content='@Xeltica') + meta(name='twitter:creator' content='@Xeltica') link(rel='stylesheet' href='/assets/style.css') block style body .xd-main - h1 みす廃あらーと + h1: a(href="/") みす廃あらーと block content footer.xd-footer.xd-container - a(href="/privacy-policy") プライバシーポリシー - | ・ a(href="/terms") 利用規約 | ・ - a(href="https://github.com/Xeltica/misshaialert") リポジトリ + +exta(href="https://github.com/Xeltica/misshaialert") リポジトリ p (C)2020 Xeltica - a(href="/about") version #{version} block footer diff --git a/src/views/_components.pug b/src/views/_components.pug index e69de29..e297d20 100644 --- a/src/views/_components.pug +++ b/src/views/_components.pug @@ -0,0 +1,3 @@ +mixin exta() + a(href=attributes.href target="_blank" rel="noopener noreferrer") + block \ No newline at end of file diff --git a/src/views/about.pug b/src/views/about.pug new file mode 100644 index 0000000..4caa579 --- /dev/null +++ b/src/views/about.pug @@ -0,0 +1,8 @@ +extends _base + +block content + section + h2 バージョン !{version} + ul + each log in changelog + li= log diff --git a/src/views/error.pug b/src/views/error.pug index bb428b5..7b606b3 100644 --- a/src/views/error.pug +++ b/src/views/error.pug @@ -3,5 +3,4 @@ extends _base block content section h2 エラー - p= error - a(href="/") トップに戻る \ No newline at end of file + p= error \ No newline at end of file diff --git a/src/views/logined.pug b/src/views/logined.pug index 0c9b324..398e852 100644 --- a/src/views/logined.pug +++ b/src/views/logined.pug @@ -2,4 +2,5 @@ extends _base block content section - h2 おかえりなさい、@!{ user.username }@!{ user.host } さん。 \ No newline at end of file + h2 おかえりなさい、@!{ user.username }@!{ user.host } さん。 + p みす廃あらーとの設定は完了しています。Have a good Misskey 👍 \ No newline at end of file diff --git a/src/views/term.pug b/src/views/term.pug new file mode 100644 index 0000000..7a0f363 --- /dev/null +++ b/src/views/term.pug @@ -0,0 +1,12 @@ +extends _base + +block content + section + h2 利用規約 + ul + li 本サービスは無保証で提供されます。本サービスを利用したことによる損害などについて、管理人は一切責任を負わないものとします。 + li ユーザーはインスタンスの諸規約に従った上で本サービスを使うものとします。インスタンスの規約により、自動投稿が禁止されている場合は本サービスを使用しないでください。 + li 本サービスでは、接続先のアカウントが存在しない、トークンが失効してしまったなどの場合に、自動的にユーザーアカウントを削除します。 + li 本サービスの仕様は、事前の予告無しに変更される可能性があります。 + li 本サービスは、事前の予告無しに突然閉鎖される可能性があります。 + li 本規約は、事前の予告無しに変更される可能性があります。 diff --git a/src/views/welcome.pug b/src/views/welcome.pug index 1a6d80e..214101e 100644 --- a/src/views/welcome.pug +++ b/src/views/welcome.pug @@ -12,7 +12,7 @@ block content input.xd-input(type="text" placeholder="ホスト名(例: misskey.io)" name="host" required) input.xd-button.primary(type="submit", value="ログイン") p Misskey 以外のソフトウェアには対応していません。マストドンユーザーは - a(href="https://donhaialert.herokuapp.com/" target="_blank" rel="noopener noreferrer") ドン廃あらーと + +exta(href="https://donhaialert.herokuapp.com/") ドン廃あらーと | をお使いください。 section @@ -28,10 +28,6 @@ block content i.fas.fa-users | 登録者数 dd !{usersCount} 人 - dt - i.fas.fa-comment - | 累計ノート数 - dd 0 ノート .xd-card .header h1.title @@ -40,14 +36,14 @@ block content .body p 何か困ったことがあったら、以下のアカウントにメッセージを送ってください。 ul - li: a(href="https://misskey.io/@ebi") @ebi@misskey.io - li: a(href="https://groundpolis.app/@X") @X@groundpolis.app - li: a(href="https://twitter.com/Xeltica") @Xeltica@twitter.com - li: a(href="mailto:xeltica@gmail.com") xeltica@gmail.com + li: +exta(href="https://misskey.io/@ebi") @ebi@misskey.io + li: +exta(href="https://groundpolis.app/@X") @X@groundpolis.app + li: +exta(href="https://twitter.com/Xeltica") @Xeltica@twitter.com + li: +exta(href="mailto:xeltica@gmail.com") xeltica@gmail.com .xd-card .header h1.title i.fas.fa-hashtag | タイムライン .body - p 準備中。 + p 近いうちに、ここで #misshaialert タグのタイムラインを表示します。まだ工事中です diff --git a/src/worker.ts b/src/worker.ts new file mode 100644 index 0000000..bd433cb --- /dev/null +++ b/src/worker.ts @@ -0,0 +1,34 @@ +import cron from 'node-cron'; +import { Users } from './models'; +import { api } from './misskey'; +import { format } from './format'; +import { deleteUser } from './users'; + +export default (): void => { + cron.schedule('0 0 0 * * *', async () => { + const users = await Users.createQueryBuilder() + .select() + .getMany(); + for (const user of users) { + try { + const text = await format(user); + + const res = await api(user.host, 'notes/create', { + text, + visibility: 'home' + }, user.token); + if (res.error) { + throw res.error; + } + } catch (e) { + if (e.code === 'NO_SUCH_USER' || e.code === 'AUTHENTICATION_FAILED') { + // ユーザーが削除されている場合、レコードからも消してとりやめ + console.info(`${user.username}@${user.host} is deleted, so delete this user from the system`); + await deleteUser(user.username, user.host); + } else { + console.error(e); + } + } + } + }); +}; \ No newline at end of file diff --git a/styles/style.scss b/styles/style.scss index c15ff7e..98a1dee 100644 --- a/styles/style.scss +++ b/styles/style.scss @@ -2,4 +2,8 @@ body { background: $bg-pale-1; +} + +h1> a { + border-bottom: none; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3c37b06..f4aa6ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -213,6 +213,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== +"@types/node-cron@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-2.0.3.tgz#b5bb940523d265f6a36548856ec0c278ea5a35d6" + integrity sha512-gwBBGeY2XeYBLE0R01K9Sm2hvNcPGmoloL6aqthA3QmBB1GYXTHIJ42AGZL7bdXBRiwbRV8b6NB5iKpl20R3gw== + dependencies: + "@types/tz-offset" "*" + "@types/node@*": version "14.0.27" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" @@ -241,6 +248,11 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/tz-offset@*": + version "0.0.0" + resolved "https://registry.yarnpkg.com/@types/tz-offset/-/tz-offset-0.0.0.tgz#d58f1cebd794148d245420f8f0660305d320e565" + integrity sha512-XLD/llTSB6EBe3thkN+/I0L+yCTB6sjrcVovQdx2Cnl6N6bTzHmwe/J8mWnsXFgxLrj/emzdv8IR4evKYG2qxQ== + "@types/uuid@^8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" @@ -2032,6 +2044,14 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-cron@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-2.0.3.tgz#b9649784d0d6c00758410eef22fa54a10e3f602d" + integrity sha512-eJI+QitXlwcgiZwNNSRbqsjeZMp5shyajMR81RZCqeW0ZDEj4zU9tpd4nTh/1JsBiKbF8d08FCewiipDmVIYjg== + dependencies: + opencollective-postinstall "^2.0.0" + tz-offset "0.0.1" + nodemon@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" @@ -2150,6 +2170,11 @@ only@~0.0.2: resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q= +opencollective-postinstall@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -3198,6 +3223,11 @@ typescript@^3.9.7: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +tz-offset@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tz-offset/-/tz-offset-0.0.1.tgz#fef920257024d3583ed9072a767721a18bdb8a76" + integrity sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ== + undefsafe@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae"