0
0
Fork 0
This commit is contained in:
Xeltica 2020-08-04 16:43:13 +09:00
parent a0f6dabbdf
commit f06fcc2056
17 changed files with 194 additions and 25 deletions

View file

@ -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"
}
}
}

View file

@ -2,5 +2,6 @@ import { initDb } from './db';
(async () => {
await initDb();
(await import('./worker')).default();
(await import('./server')).default();
})();

View file

@ -1,5 +1,5 @@
export default {
version: '1.0.0-alpha.1',
version: '1.0.0',
changelog: [
'初版'
],

33
src/format.ts Normal file
View file

@ -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<string> => {
const miUser = await api<Record<string, any>>(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;

16
src/misskey.ts Normal file
View file

@ -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 = <T>(host: string, endpoint: string, arg: Record<string, unknown>, i?: string): Promise<T> => {
const a = { ...arg };
if (i) {
a.i = i;
}
return axios.post<T>(`https://${host}/api/${endpoint}`, a).then(res => res.data);
};

View file

@ -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<DefaultState, Context>();
@ -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 });
});

View file

@ -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<User | undefined> => {
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<User>): Promise<void> => {
await Users.update({ username, host }, record);
};
export const deleteUser = async (username: string, host: string): Promise<void> => {
await Users.delete({ username, host });
};

View file

@ -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

View file

@ -0,0 +1,3 @@
mixin exta()
a(href=attributes.href target="_blank" rel="noopener noreferrer")
block

8
src/views/about.pug Normal file
View file

@ -0,0 +1,8 @@
extends _base
block content
section
h2 バージョン !{version}
ul
each log in changelog
li= log

View file

@ -3,5 +3,4 @@ extends _base
block content
section
h2 エラー
p= error
a(href="/") トップに戻る
p= error

View file

@ -2,4 +2,5 @@ extends _base
block content
section
h2 おかえりなさい、@!{ user.username }@!{ user.host } さん。
h2 おかえりなさい、@!{ user.username }@!{ user.host } さん。
p みす廃あらーとの設定は完了しています。Have a good Misskey 👍

12
src/views/term.pug Normal file
View file

@ -0,0 +1,12 @@
extends _base
block content
section
h2 利用規約
ul
li 本サービスは無保証で提供されます。本サービスを利用したことによる損害などについて、管理人は一切責任を負わないものとします。
li ユーザーはインスタンスの諸規約に従った上で本サービスを使うものとします。インスタンスの規約により、自動投稿が禁止されている場合は本サービスを使用しないでください。
li 本サービスでは、接続先のアカウントが存在しない、トークンが失効してしまったなどの場合に、自動的にユーザーアカウントを削除します。
li 本サービスの仕様は、事前の予告無しに変更される可能性があります。
li 本サービスは、事前の予告無しに突然閉鎖される可能性があります。
li 本規約は、事前の予告無しに変更される可能性があります。

View file

@ -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 タグのタイムラインを表示します。まだ工事中です

34
src/worker.ts Normal file
View file

@ -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<any>(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);
}
}
}
});
};

View file

@ -2,4 +2,8 @@
body {
background: $bg-pale-1;
}
h1> a {
border-bottom: none;
}

View file

@ -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"