diff --git a/migration/1609941393782-template.ts b/migration/1609941393782-template.ts new file mode 100644 index 0000000..1533555 --- /dev/null +++ b/migration/1609941393782-template.ts @@ -0,0 +1,14 @@ +import {MigrationInterface, QueryRunner} from 'typeorm'; + +export class template1609941393782 implements MigrationInterface { + name = 'template1609941393782' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query('ALTER TABLE "user" ADD "template" character varying(280)'); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('ALTER TABLE "user" DROP COLUMN "template"'); + } + +} diff --git a/src/functions/format.ts b/src/functions/format.ts index 8a70539..b306792 100644 --- a/src/functions/format.ts +++ b/src/functions/format.ts @@ -1,13 +1,66 @@ import { config } from '../config'; +import { User } from '../models/entities/user'; import { Score } from '../types/Score'; -export const format = (score: Score): string => `昨日のMisskeyの活動は +export const defaultTemplate = `昨日のMisskeyの活動は -ノート: ${score.notesCount}(${score.notesDelta}) -フォロー : ${score.followingCount}(${score.followingDelta}) -フォロワー :${score.followersCount}(${score.followersDelta}) +ノート: {notesCount}({notesDelta}) +フォロー : {followingCount}({followingDelta}) +フォロワー :{followersCount}({followersDelta}) でした。 -${config.url} +{url}`; -#misshaialert`; +export type Variable = { + description?: string; + replace?: string | ((score: Score, user: User) => string); +}; + +export const variables: Record = { + notesCount: { + description: 'ノート数', + replace: (score) => String(score.notesCount), + }, + followingCount: { + description: 'フォロー数', + replace: (score) => String(score.followingCount), + }, + followersCount: { + description: 'フォロワー数', + replace: (score) => String(score.followersCount), + }, + notesDelta: { + description: '昨日とのノート数の差', + replace: (score) => String(score.notesDelta), + }, + followingDelta: { + description: '昨日とのフォロー数の差', + replace: (score) => String(score.followingDelta), + }, + followersDelta: { + description: '昨日とのフォロワー数の差', + replace: (score) => String(score.followersDelta), + }, + url: { + description: 'みす廃アラートのURL', + replace: config.url, + }, + username: { + description: 'ユーザー名', + replace: (_, user) => String(user.username), + }, + host: { + description: '所属するインスタンスのホスト名', + replace: (_, user) => String(user.host), + }, +}; + +const variableRegex = /\{([a-zA-Z0-9_]+?)\}/g; + +export const format = (score: Score, user: User): string => { + const template = user.template || defaultTemplate; + return template.replace(variableRegex, (m, name) => { + const v = variables[name]; + return !v || !v.replace ? m : typeof v.replace === 'function' ? v.replace(score, user) : v.replace; + }) + '\n\n#misshaialert'; +}; diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index 3a5aa6f..122d93f 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -72,4 +72,11 @@ export class User { default: false, }) public remoteFollowersOnly: boolean; + + @Column({ + type: 'varchar', + length: 280, + nullable: true, + }) + public template: string | null; } \ No newline at end of file diff --git a/src/server/router.ts b/src/server/router.ts index 359a14c..6875c17 100644 --- a/src/server/router.ts +++ b/src/server/router.ts @@ -13,6 +13,7 @@ import { AlertMode, alertModes } from '../types/AlertMode'; import { Users } from '../models'; import { send } from '../services/send'; import { visibilities, Visibility } from '../types/Visibility'; +import { defaultTemplate, variables } from '../functions/format'; export const router = new Router(); @@ -67,6 +68,8 @@ router.get('/', async ctx => { user, // To Activate Groundpolis Mode isGroundpolis: meta.version.includes('gp'), + defaultTemplate, + templateVariables: variables, usersCount: await getUserCount(), score: await getScores(user), from: ctx.query.from, @@ -209,6 +212,8 @@ router.post('/update-settings', async ctx => { const flag = ctx.request.body.flag; + const template = ctx.request.body.template?.trim(); + const token = ctx.cookies.get('token'); if (!token) { await die(ctx, 'ログインしていません'); @@ -226,6 +231,7 @@ router.post('/update-settings', async ctx => { alertMode: mode, localOnly: flag === 'localOnly', remoteFollowersOnly: flag === 'remoteFollowersOnly', + template: template === defaultTemplate || !template ? null : template, visibility, }); diff --git a/src/services/send.ts b/src/services/send.ts index f1890a6..0f6d985 100644 --- a/src/services/send.ts +++ b/src/services/send.ts @@ -4,7 +4,7 @@ import { getScores } from '../functions/get-scores'; import { api } from './misskey'; export const send = async (user: User): Promise => { - const text = format(await getScores(user)); + const text = format(await getScores(user), user); if (user.alertMode === 'note') { console.info(`send ${user.username}@${user.host}'s misshaialert as a note`); diff --git a/src/views/mypage.pug b/src/views/mypage.pug index 9cf30a4..8524f41 100644 --- a/src/views/mypage.pug +++ b/src/views/mypage.pug @@ -70,77 +70,57 @@ block content | スコア通知方法に「Misskey に通知」を選んでいる場合、Groundpolis v3 および Misskey v12 の最新版以外では動作しません。めいすきーや古いバージョンをお使いの方は、「自動的にノートを投稿」をお使いください。 form(method="post", action="/update-settings") p: label スコア通知方法: - select#alertModeSelector(name="alertMode", tabindex=1) + select#alertModeSelector(name="alertMode") each set in alertModes option(value=set[0], selected=(user.alertMode === set[0]))= set[1] #hideWhenAlertModeNotNote p: label 公開範囲: - select(name="visibility", tabindex=2) + select(name="visibility") each set in visibilities option(value=set[0], selected=(user.visibility === set[0]))= set[1] p | フラグ
label - input(type="radio", name="flag", value="none", checked=!user.localOnly && !user.remoteFollowersOnly, tabindex=3) + input(type="radio", name="flag", value="none", checked=!user.localOnly && !user.remoteFollowersOnly) | なし(標準)
label - input(type="radio", name="flag", value="localOnly", checked=user.localOnly, tabindex=4) + input(type="radio", name="flag", value="localOnly", checked=user.localOnly) | ローカルのみ
if isGroundpolis label - input(type="radio", name="flag", value="remoteFollowersOnly", checked=user.remoteFollowersOnly, tabindex=5) + input(type="radio", name="flag", value="remoteFollowersOnly", checked=user.remoteFollowersOnly) | リモートフォロワーとローカル
#hideWhenAlertModeNothing div - label 投稿テンプレート - div: textarea(name="template", disabled, tabindex=6) - details(tabindex=7) + label 投稿テンプレート
+ textarea(name="template")=user.template || defaultTemplate + details() summary ヘルプ - p - code {notesCount} - | などのテキストを挿入することで、投稿時に自動的に値が埋め込まれます。これを変数といいます。変数の表を次に示します。 + ul + li 空欄にすると、デフォルト値にリセットされます。 + li ハッシュタグ #misshaialert は、テンプレートに関わらず自動付与されます。 + li + code {notesCount} + | といった形式のテキストは変数として扱われ、これを含めると投稿時に自動的に値が埋め込まれます。 table thead: tr th 変数 th 説明 tbody - tr - td {notesCount} - td ノート数 - tr - td {followingCount} - td フォロー数 - tr - td {followersCount} - td フォロワー数 - tr - td {notesDelta} - td 昨日とのノート数の差 - tr - td {followingDelta} - td 昨日とのフォロー数の差 - tr - td {followersDelta} - td 昨日とのフォロワー数の差 - tr - td {url} - td みす廃アラートのURL - tr - td {ranking} - td みす廃ランキングの順位 - tr - td {rating} - td みす廃レート - button.primary(type="submit", tabindex=8) 保存 + each val, key in templateVariables + tr + td=key + td=val.description + button.primary(type="submit") 保存 section.xd-card#settings .header h1.title 操作 .body - form.mb-2(action="/send", method="post", tabindex=9): button#send(style="display: inline-block") アラートをテスト送信 - form.mb-2(action="/logout", method="post", tabindex=10): button#logout(style="display: inline-block") ログアウト - form.mb-2(action="/optout", method="post", tabindex=11): button.danger#optout(style="display: inline-block") アカウント連携を解除する + form.mb-2(action="/send", method="post"): button#send(style="display: inline-block") アラートをテスト送信 + form.mb-2(action="/logout", method="post"): button#logout(style="display: inline-block") ログアウト + form.mb-2(action="/optout", method="post"): button.danger#optout(style="display: inline-block") アカウント連携を解除する block script script. diff --git a/styles/_xeltica-design.scss b/styles/_xeltica-design.scss index 120e8bd..31259af 100644 --- a/styles/_xeltica-design.scss +++ b/styles/_xeltica-design.scss @@ -171,10 +171,10 @@ textarea { border: none; outline: none; height: 8rem; - line-height: 1; + line-height: 1.2; + color: var(--fg); &:focus { border: 1px solid var(--primary); - color: var(--fg); } }