1.0.0
This commit is contained in:
parent
a0f6dabbdf
commit
f06fcc2056
17 changed files with 194 additions and 25 deletions
12
package.json
12
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"
|
||||
}
|
||||
}
|
|
@ -2,5 +2,6 @@ import { initDb } from './db';
|
|||
|
||||
(async () => {
|
||||
await initDb();
|
||||
(await import('./worker')).default();
|
||||
(await import('./server')).default();
|
||||
})();
|
|
@ -1,5 +1,5 @@
|
|||
export default {
|
||||
version: '1.0.0-alpha.1',
|
||||
version: '1.0.0',
|
||||
changelog: [
|
||||
'初版'
|
||||
],
|
||||
|
|
33
src/format.ts
Normal file
33
src/format.ts
Normal 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
16
src/misskey.ts
Normal 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);
|
||||
};
|
|
@ -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 });
|
||||
});
|
||||
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
mixin exta()
|
||||
a(href=attributes.href target="_blank" rel="noopener noreferrer")
|
||||
block
|
8
src/views/about.pug
Normal file
8
src/views/about.pug
Normal file
|
@ -0,0 +1,8 @@
|
|||
extends _base
|
||||
|
||||
block content
|
||||
section
|
||||
h2 バージョン !{version}
|
||||
ul
|
||||
each log in changelog
|
||||
li= log
|
|
@ -4,4 +4,3 @@ block content
|
|||
section
|
||||
h2 エラー
|
||||
p= error
|
||||
a(href="/") トップに戻る
|
|
@ -3,3 +3,4 @@ extends _base
|
|||
block content
|
||||
section
|
||||
h2 おかえりなさい、@!{ user.username }@!{ user.host } さん。
|
||||
p みす廃あらーとの設定は完了しています。Have a good Misskey 👍
|
12
src/views/term.pug
Normal file
12
src/views/term.pug
Normal file
|
@ -0,0 +1,12 @@
|
|||
extends _base
|
||||
|
||||
block content
|
||||
section
|
||||
h2 利用規約
|
||||
ul
|
||||
li 本サービスは無保証で提供されます。本サービスを利用したことによる損害などについて、管理人は一切責任を負わないものとします。
|
||||
li ユーザーはインスタンスの諸規約に従った上で本サービスを使うものとします。インスタンスの規約により、自動投稿が禁止されている場合は本サービスを使用しないでください。
|
||||
li 本サービスでは、接続先のアカウントが存在しない、トークンが失効してしまったなどの場合に、自動的にユーザーアカウントを削除します。
|
||||
li 本サービスの仕様は、事前の予告無しに変更される可能性があります。
|
||||
li 本サービスは、事前の予告無しに突然閉鎖される可能性があります。
|
||||
li 本規約は、事前の予告無しに変更される可能性があります。
|
|
@ -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
34
src/worker.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
|
@ -3,3 +3,7 @@
|
|||
body {
|
||||
background: $bg-pale-1;
|
||||
}
|
||||
|
||||
h1> a {
|
||||
border-bottom: none;
|
||||
}
|
30
yarn.lock
30
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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue