wip: feat: ミス廃アラート
This commit is contained in:
parent
720895df07
commit
063781478b
@ -6,9 +6,9 @@ import { getMisskey } from '@/libs/misskey.js';
|
|||||||
import { prisma } from '@/libs/prisma.js';
|
import { prisma } from '@/libs/prisma.js';
|
||||||
import { connection } from '@/libs/redis.js';
|
import { connection } from '@/libs/redis.js';
|
||||||
import { queues } from '@/queue/index.js';
|
import { queues } from '@/queue/index.js';
|
||||||
import { format } from '@/services/holic/format';
|
import { format } from '@/services/holic/format.js';
|
||||||
import { avg } from '@/utils/avg.js';
|
import { avg } from '@/utils/avg.js';
|
||||||
import { toAcct } from '@/utils/to-acct';
|
import { toAcct } from '@/utils/to-acct.js';
|
||||||
|
|
||||||
const NAME = 'holicAggregate';
|
const NAME = 'holicAggregate';
|
||||||
|
|
||||||
@ -24,6 +24,9 @@ export const holicAggregateWorker = new Worker<HolicAggregateQueueType>(NAME, as
|
|||||||
// ステータスをお問い合わせ
|
// ステータスをお問い合わせ
|
||||||
const { account } = job.data;
|
const { account } = job.data;
|
||||||
const { misskeySession: session } = account;
|
const { misskeySession: session } = account;
|
||||||
|
const acct = toAcct(session);
|
||||||
|
|
||||||
|
console.log(`[holic] Aggregating for ${acct}`);
|
||||||
const data = await getMisskey(session.host).request('i', {}, session.token);
|
const data = await getMisskey(session.host).request('i', {}, session.token);
|
||||||
if (!('notesCount' in data)) {
|
if (!('notesCount' in data)) {
|
||||||
job.discard();
|
job.discard();
|
||||||
@ -43,7 +46,9 @@ export const holicAggregateWorker = new Worker<HolicAggregateQueueType>(NAME, as
|
|||||||
const rating = records1week.length > 0 ? avg([
|
const rating = records1week.length > 0 ? avg([
|
||||||
...records1week.map(r => r.notesCount),
|
...records1week.map(r => r.notesCount),
|
||||||
data.notesCount,
|
data.notesCount,
|
||||||
]) : data.notesCount;
|
]) : 0;
|
||||||
|
|
||||||
|
console.log(`[holic] RATING of ${acct}: ${rating}`);
|
||||||
|
|
||||||
// 今日分のデータ作成
|
// 今日分のデータ作成
|
||||||
const today = await prisma.holicRecord.create({
|
const today = await prisma.holicRecord.create({
|
||||||
@ -57,15 +62,8 @@ export const holicAggregateWorker = new Worker<HolicAggregateQueueType>(NAME, as
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const yesterday: HolicRecord = records1week[0] ?? {
|
|
||||||
id: 0,
|
const yesterday: HolicRecord = records1week[0] ?? today;
|
||||||
rating: 0,
|
|
||||||
date: new Date(),
|
|
||||||
accountId: account.misskeySessionId,
|
|
||||||
notesCount: 0,
|
|
||||||
followingCount: 0,
|
|
||||||
followersCount: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const text = format({
|
const text = format({
|
||||||
today,
|
today,
|
||||||
@ -74,7 +72,6 @@ export const holicAggregateWorker = new Worker<HolicAggregateQueueType>(NAME, as
|
|||||||
session,
|
session,
|
||||||
});
|
});
|
||||||
|
|
||||||
const acct = toAcct(session);
|
|
||||||
if (account.alertAsNote) {
|
if (account.alertAsNote) {
|
||||||
queues.holicNoteQueue.add(acct, {
|
queues.holicNoteQueue.add(acct, {
|
||||||
account,
|
account,
|
||||||
|
@ -5,7 +5,8 @@ import type { MisskeySession, HolicAccount } from '@prisma/client';
|
|||||||
|
|
||||||
import { getMisskey } from '@/libs/misskey.js';
|
import { getMisskey } from '@/libs/misskey.js';
|
||||||
import { connection } from '@/libs/redis.js';
|
import { connection } from '@/libs/redis.js';
|
||||||
import { isVisibility } from '@/utils/is-visibility';
|
import { isVisibility } from '@/utils/is-visibility.js';
|
||||||
|
import { toAcct } from '@/utils/to-acct.js';
|
||||||
|
|
||||||
const NAME = 'holicNote';
|
const NAME = 'holicNote';
|
||||||
|
|
||||||
@ -20,6 +21,8 @@ export const holicNoteQueue = new Queue<HolicNoteQueueType>(NAME, { connection }
|
|||||||
export const holicNoteWorker = new Worker<HolicNoteQueueType>(NAME, async (job) => {
|
export const holicNoteWorker = new Worker<HolicNoteQueueType>(NAME, async (job) => {
|
||||||
const { session, account, text } = job.data;
|
const { session, account, text } = job.data;
|
||||||
|
|
||||||
|
console.log(`[holic] Processing note job for ${toAcct(session)}`);
|
||||||
|
|
||||||
const api = getMisskey(session.host);
|
const api = getMisskey(session.host);
|
||||||
|
|
||||||
if (!isVisibility(account.noteVisibility)) {
|
if (!isVisibility(account.noteVisibility)) {
|
||||||
@ -36,6 +39,7 @@ export const holicNoteWorker = new Worker<HolicNoteQueueType>(NAME, async (job)
|
|||||||
localOnly: account.noteLocalOnly,
|
localOnly: account.noteLocalOnly,
|
||||||
}, session.token);
|
}, session.token);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(`[holic] Failed to create a note: ${e}`);
|
||||||
if (!misskey.api.isAPIError(e)) throw e;
|
if (!misskey.api.isAPIError(e)) throw e;
|
||||||
if (e.code === 'RATE_LIMIT_EXCEEDED') {
|
if (e.code === 'RATE_LIMIT_EXCEEDED') {
|
||||||
// delay 1h
|
// delay 1h
|
||||||
|
@ -4,6 +4,7 @@ import type { MisskeySession, HolicAccount } from '@prisma/client';
|
|||||||
|
|
||||||
import { getMisskey } from '@/libs/misskey.js';
|
import { getMisskey } from '@/libs/misskey.js';
|
||||||
import { connection } from '@/libs/redis.js';
|
import { connection } from '@/libs/redis.js';
|
||||||
|
import { toAcct } from '@/utils/to-acct';
|
||||||
|
|
||||||
const NAME = 'holicNotification';
|
const NAME = 'holicNotification';
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ export const holicNotificationQueue = new Queue<HolicNotificationQueueType>(NAME
|
|||||||
|
|
||||||
export const holicNotificationWorker = new Worker<HolicNotificationQueueType>(NAME, async (job) => {
|
export const holicNotificationWorker = new Worker<HolicNotificationQueueType>(NAME, async (job) => {
|
||||||
const { session, text } = job.data;
|
const { session, text } = job.data;
|
||||||
|
console.log(`[holic] Processing note job for ${toAcct(session)}`);
|
||||||
|
|
||||||
const api = getMisskey(session.host);
|
const api = getMisskey(session.host);
|
||||||
await api.request('notifications/create', {
|
await api.request('notifications/create', {
|
||||||
|
@ -48,5 +48,5 @@ export const format = (p: VariableParameter): string => {
|
|||||||
return template.replace(variableRegex, (m, name) => {
|
return template.replace(variableRegex, (m, name) => {
|
||||||
const v = variables[name];
|
const v = variables[name];
|
||||||
return !v ? m : typeof v === 'function' ? v(p) : v;
|
return !v ? m : typeof v === 'function' ? v(p) : v;
|
||||||
}) + '\n\n#missholic';
|
}) + '\n\n#misskeholic';
|
||||||
};
|
};
|
||||||
|
@ -13,11 +13,13 @@ const IndexWelcome = lazy(() => import('@/pages/index.welcome'));
|
|||||||
const Settings = lazy(() => import('@/pages/settings'));
|
const Settings = lazy(() => import('@/pages/settings'));
|
||||||
const Appearance = lazy(() => import('@/pages/settings/appearance'));
|
const Appearance = lazy(() => import('@/pages/settings/appearance'));
|
||||||
const Account = lazy(() => import('@/pages/settings/account'));
|
const Account = lazy(() => import('@/pages/settings/account'));
|
||||||
const AnnouncementsPage = lazy(() => import('@/pages/announcements'));
|
const Announcements = lazy(() => import('@/pages/announcements'));
|
||||||
const AboutPage = lazy(() => import('@/pages/about'));
|
const About = lazy(() => import('@/pages/about'));
|
||||||
const AppsNoteScheduler = lazy(() => import('@/pages/apps/note-scheduler'));
|
const AppsNoteScheduler = lazy(() => import('@/pages/apps/note-scheduler'));
|
||||||
const AppsNoteSchedulerNew = lazy(() => import('@/pages/apps/note-scheduler.new'));
|
const AppsNoteSchedulerNew = lazy(() => import('@/pages/apps/note-scheduler.new'));
|
||||||
const AppsNoteSchedulerEdit = lazy(() => import('@/pages/apps/note-scheduler.edit'));
|
const AppsNoteSchedulerEdit = lazy(() => import('@/pages/apps/note-scheduler.edit'));
|
||||||
|
const Misskeholic = lazy(() => import('@/pages/apps/misskeholic'));
|
||||||
|
const MisskeholicUser = lazy(() => import('@/pages/apps/misskeholic.user'));
|
||||||
const NotFound = lazy(() => import('@/pages/not-found'));
|
const NotFound = lazy(() => import('@/pages/not-found'));
|
||||||
|
|
||||||
export const App : React.FC = () => {
|
export const App : React.FC = () => {
|
||||||
@ -34,11 +36,13 @@ export const App : React.FC = () => {
|
|||||||
<Route path="account" element={<Account />}/>
|
<Route path="account" element={<Account />}/>
|
||||||
<Route path="*" element={<p>Not Found</p>}/>
|
<Route path="*" element={<p>Not Found</p>}/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/announcements/:id" element={<AnnouncementsPage />}/>
|
<Route path="/announcements/:id" element={<Announcements />}/>
|
||||||
<Route path="/about" element={<AboutPage />}/>
|
<Route path="/about" element={<About />}/>
|
||||||
<Route path="/apps/note-scheduler" element={<AppsNoteScheduler />}/>
|
<Route path="/apps/note-scheduler" element={<AppsNoteScheduler />}/>
|
||||||
<Route path="/apps/note-scheduler/new" element={<AppsNoteSchedulerNew />}/>
|
<Route path="/apps/note-scheduler/new" element={<AppsNoteSchedulerNew />}/>
|
||||||
<Route path="/apps/note-scheduler/edit/:id" element={<AppsNoteSchedulerEdit />}/>
|
<Route path="/apps/note-scheduler/edit/:id" element={<AppsNoteSchedulerEdit />}/>
|
||||||
|
<Route path="/apps/misskeholic" element={<Misskeholic />}/>
|
||||||
|
<Route path="/apps/misskeholic/:id" element={<MisskeholicUser />}/>
|
||||||
<Route path="*" element={<NotFound />}/>
|
<Route path="*" element={<NotFound />}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
<VStack as="footer" alignItems="center" css={{ padding: '$2xl $m' }}>
|
<VStack as="footer" alignItems="center" css={{ padding: '$2xl $m' }}>
|
||||||
|
112
packages/frontend/src/components/domains/holic/HolicSettings.tsx
Normal file
112
packages/frontend/src/components/domains/holic/HolicSettings.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import type { RouterOutput } from '@/libs/trpc';
|
||||||
|
|
||||||
|
import { HStack } from '@/components/layouts/HStack';
|
||||||
|
import { VStack } from '@/components/layouts/VStack';
|
||||||
|
import { Alert } from '@/components/primitives/Alert';
|
||||||
|
import { Button } from '@/components/primitives/Button';
|
||||||
|
import { Card } from '@/components/primitives/Card';
|
||||||
|
import { Radio } from '@/components/primitives/Radio';
|
||||||
|
import { RadioGroup } from '@/components/primitives/RadioGroup';
|
||||||
|
import { Switch } from '@/components/primitives/Switch';
|
||||||
|
import { Text } from '@/components/primitives/Text';
|
||||||
|
import { Textarea } from '@/components/primitives/Textarea';
|
||||||
|
|
||||||
|
type Account = NonNullable<RouterOutput['holic']['getAccount']>;
|
||||||
|
|
||||||
|
export type HolicSettingsDraft = Omit<Account, 'misskeySessionId'>;
|
||||||
|
|
||||||
|
export type HolicSettingsProp = {
|
||||||
|
draft?: HolicSettingsDraft;
|
||||||
|
onUpdateDraft?: (newDraft: HolicSettingsDraft) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DRAFT_DEFAULT: HolicSettingsDraft = {
|
||||||
|
alertAsNote: false,
|
||||||
|
alertAsNotification: true,
|
||||||
|
noteVisibility: 'home',
|
||||||
|
noteLocalOnly: false,
|
||||||
|
rankingVisible: false,
|
||||||
|
template: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const HolicSettings: React.FC<HolicSettingsProp> = (p) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [draft, setDraft] = useState<HolicSettingsDraft>(DRAFT_DEFAULT);
|
||||||
|
const [isHelpDialogOpened, setHelpDialogOpened] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!p.draft) return;
|
||||||
|
setDraft(p.draft);
|
||||||
|
}, [p.draft]);
|
||||||
|
|
||||||
|
const updateDraft = <K extends keyof HolicSettingsDraft, V extends HolicSettingsDraft[K]>(key: K, value: V) => {
|
||||||
|
const newDraft = { ...draft };
|
||||||
|
newDraft[key] = value;
|
||||||
|
setDraft(newDraft);
|
||||||
|
p.onUpdateDraft?.(newDraft);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card pale>
|
||||||
|
<h1>{t('alertMode')}</h1>
|
||||||
|
<VStack>
|
||||||
|
<Switch checked={draft.alertAsNote} onChange={v => updateDraft('alertAsNote', v)}>
|
||||||
|
{t('_misshaiAlert.note')}
|
||||||
|
</Switch>
|
||||||
|
<Switch checked={draft.alertAsNotification} onChange={v => updateDraft('alertAsNotification', v)}>
|
||||||
|
{t('_misshaiAlert.notification')}
|
||||||
|
</Switch>
|
||||||
|
</VStack>
|
||||||
|
</Card>
|
||||||
|
<Card pale>
|
||||||
|
<h1>{t('visibility')}</h1>
|
||||||
|
<VStack>
|
||||||
|
<RadioGroup value={draft.noteVisibility} onValueChange={v => updateDraft('noteVisibility', v)}>
|
||||||
|
<Radio value="home">{t('_visibility.home')}</Radio>
|
||||||
|
<Radio value="followers">{t('_visibility.followers')}</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
<Switch checked={draft.noteLocalOnly} onChange={v => updateDraft('noteLocalOnly', v)}>
|
||||||
|
{t('noFederation')}
|
||||||
|
</Switch>
|
||||||
|
</VStack>
|
||||||
|
</Card>
|
||||||
|
<Card pale>
|
||||||
|
<h1>{t('_misshaiAlert.holicRanking')}</h1>
|
||||||
|
<VStack>
|
||||||
|
<Switch checked={draft.rankingVisible} onChange={v => updateDraft('rankingVisible', v)}>
|
||||||
|
{t('_misshaiAlert.useRanking')}
|
||||||
|
</Switch>
|
||||||
|
</VStack>
|
||||||
|
</Card>
|
||||||
|
<Card pale>
|
||||||
|
<h1>{t('template')}</h1>
|
||||||
|
<p>
|
||||||
|
{t('_template.description')}<br/>
|
||||||
|
<Text as="span" fontSize="s" color="muted">{t('_template.hashtagAutomaticInsertion')}</Text>
|
||||||
|
</p>
|
||||||
|
<VStack alignItems="left">
|
||||||
|
<HStack>
|
||||||
|
<Button size="small" primary>{t('_template.insertVariables')}</Button>
|
||||||
|
<Button size="small" flat primary css={{ fontSize: 20, padding: 0 }} onClick={() => setHelpDialogOpened(true)}>
|
||||||
|
<i className="ti ti-help-circle"/>
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
<Textarea rows={8} placeholder={t('_template.default')} value={draft.template ?? ''} onChange={e => updateDraft('template', e.target.value)} />
|
||||||
|
</VStack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
description={t('_template.insertVariablesHelp')}
|
||||||
|
open={isHelpDialogOpened}
|
||||||
|
onOpenChange={setHelpDialogOpened}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -14,6 +14,7 @@ export type AlertProp = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
description: string;
|
description: string;
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
|
hasCancel?: boolean;
|
||||||
okText?: string;
|
okText?: string;
|
||||||
danger?: boolean;
|
danger?: boolean;
|
||||||
onOkClick?: () => void;
|
onOkClick?: () => void;
|
||||||
@ -32,11 +33,13 @@ export const Alert: React.FC<AlertProp> = (p) => {
|
|||||||
{p.description}
|
{p.description}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<HStack justifyContent="right">
|
<HStack justifyContent="right">
|
||||||
|
{p.hasCancel && (
|
||||||
<$.Cancel asChild>
|
<$.Cancel asChild>
|
||||||
<Button flat>
|
<Button flat>
|
||||||
{p.cancelText ?? t('cancel')}
|
{p.cancelText ?? t('cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</$.Cancel>
|
</$.Cancel>
|
||||||
|
)}
|
||||||
<$.Action asChild>
|
<$.Action asChild>
|
||||||
<Button danger={p.danger} primaryGradient={!p.danger} onClick={p.onOkClick}>
|
<Button danger={p.danger} primaryGradient={!p.danger} onClick={p.onOkClick}>
|
||||||
{p.okText ?? t('ok')}
|
{p.okText ?? t('ok')}
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
"upload": "アップロード",
|
"upload": "アップロード",
|
||||||
"preview": "プレビュー",
|
"preview": "プレビュー",
|
||||||
"dashboard": "ダッシュボード",
|
"dashboard": "ダッシュボード",
|
||||||
"missHaiAlert": "ミス廃アラート",
|
"misskeholicAlert": "ミス廃アラート",
|
||||||
"avatarCropper": "アバタークロッパー",
|
"avatarCropper": "アバタークロッパー",
|
||||||
"questionBox": "質問ボックス",
|
"questionBox": "質問ボックス",
|
||||||
"followingManager": "フォローマネージャー",
|
"followingManager": "フォローマネージャー",
|
||||||
@ -77,9 +77,9 @@
|
|||||||
"datetime": "日時",
|
"datetime": "日時",
|
||||||
|
|
||||||
"_welcome": {
|
"_welcome": {
|
||||||
"misshaiAlertTitle": "ミス廃アラート",
|
"misskeholicAlertTitle": "ミス廃アラート",
|
||||||
"misshaiAlertDescription": "Misskeyにのめり込んでいませんか?ミス廃アラートを使えば、毎日のMisskeyでの活動量を定期投稿できます。",
|
"misskeholicAlertDescription": "Misskeyにのめり込んでいませんか?ミス廃アラートを使えば、毎日のMisskeyでの活動量を定期投稿できます。",
|
||||||
"misshaiRankingDescription": "ミス廃ランキングでは、Misskeyでの活動を数値化し、ランキング表示します。",
|
"misskeholicRankingDescription": "ミス廃ランキングでは、Misskeyでの活動を数値化し、ランキング表示します。",
|
||||||
"avatarCropperDescription": "丸形、角丸形、そして猫耳のついた状態をプレビューしながら、アイコンを自在に切り抜けます。魅力的なアバターを、もっと魅力的に。",
|
"avatarCropperDescription": "丸形、角丸形、そして猫耳のついた状態をプレビューしながら、アイコンを自在に切り抜けます。魅力的なアバターを、もっと魅力的に。",
|
||||||
"questionBoxDescription": "匿名で質問できるページを開設しよう。届いた質問には、お使いのMisskeyアカウントで回答できます。",
|
"questionBoxDescription": "匿名で質問できるページを開設しよう。届いた質問には、お使いのMisskeyアカウントで回答できます。",
|
||||||
"followingManagerDescription": "増えすぎたフォロー・フォロワーを管理できる高機能なマネージャー。所属サーバーや、フォローバックされていないアカウントで絞り込むことができ、一括処理にも対応しています。",
|
"followingManagerDescription": "増えすぎたフォロー・フォロワーを管理できる高機能なマネージャー。所属サーバーや、フォローバックされていないアカウントで絞り込むことができ、一括処理にも対応しています。",
|
||||||
@ -95,16 +95,17 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"_widgets": {
|
"_widgets": {
|
||||||
"misshaiData": "今日のミス廃データ",
|
"holicData": "今日のミス廃データ",
|
||||||
"announcements": "お知らせ",
|
"announcements": "お知らせ",
|
||||||
"questionBox": "質問ボックス",
|
"questionBox": "質問ボックス",
|
||||||
"apps": "アプリ",
|
"apps": "アプリ",
|
||||||
"unreadHints": "未読のヒント"
|
"unreadHints": "未読のヒント"
|
||||||
},
|
},
|
||||||
|
|
||||||
"_missHai": {
|
"_misshaiAlert": {
|
||||||
"ranking": "ミス廃ランキング",
|
"gettingStarted": "利用を開始する",
|
||||||
"rankingDescription": "ユーザーの「ミス廃レート」を算出し、高い順にランキング表示しています。",
|
"holicRanking": "ミス廃ランキング",
|
||||||
|
"holicRankingDescription": "ユーザーの「ミス廃レート」を算出し、高い順にランキング表示しています。",
|
||||||
"showAll": "全員分見る",
|
"showAll": "全員分見る",
|
||||||
"data": "ミス廃データ",
|
"data": "ミス廃データ",
|
||||||
"dataBody": "内容",
|
"dataBody": "内容",
|
||||||
@ -118,6 +119,15 @@
|
|||||||
"notification": "Misskeyに通知する",
|
"notification": "Misskeyに通知する",
|
||||||
"notificationWarning": "Misskeyのバージョンによっては動作しません。"
|
"notificationWarning": "Misskeyのバージョンによっては動作しません。"
|
||||||
},
|
},
|
||||||
|
"_noteScheduler": {
|
||||||
|
"createNew": "新規作成",
|
||||||
|
"edit": "編集",
|
||||||
|
"delete": "削除",
|
||||||
|
"noNotes": "予約投稿はまだ作成されていません。",
|
||||||
|
"deleteConfirm": "本当に予約投稿「{{note}}」を削除しますか?",
|
||||||
|
"accountToNote": "投稿するアカウント",
|
||||||
|
"schedule": "投稿を予約"
|
||||||
|
},
|
||||||
"_accounts": {
|
"_accounts": {
|
||||||
"switchAccount": "アカウント切り替え",
|
"switchAccount": "アカウント切り替え",
|
||||||
"useAnother": "他のアカウントで登録する"
|
"useAnother": "他のアカウントで登録する"
|
||||||
@ -135,7 +145,7 @@
|
|||||||
},
|
},
|
||||||
"_template": {
|
"_template": {
|
||||||
"description": "アラートの自動投稿をカスタマイズできます。",
|
"description": "アラートの自動投稿をカスタマイズできます。",
|
||||||
"description2": "ハッシュタグ #misshaialert は、テンプレートに関わらず自動付与されます。",
|
"hashtagAutomaticInsertion": "ハッシュタグ #missholicalert は、テンプレートに関わらず自動付与されます。",
|
||||||
"default": "昨日のMisskeyの活動は\n\nノート: {notesCount}({notesDelta})\nフォロー : {followingCount}({followingDelta})\nフォロワー :{followersCount}({followersDelta})\n\nでした。\n{url}",
|
"default": "昨日のMisskeyの活動は\n\nノート: {notesCount}({notesDelta})\nフォロー : {followingCount}({followingDelta})\nフォロワー :{followersCount}({followersDelta})\n\nでした。\n{url}",
|
||||||
"insertVariables": "変数を挿入する",
|
"insertVariables": "変数を挿入する",
|
||||||
"insertVariablesHelp": "{ } で囲われた文字列は変数と呼ばれ、特別な意味を持ちます。これを含めると、投稿時に自動的に値が埋め込まれます。",
|
"insertVariablesHelp": "{ } で囲われた文字列は変数と呼ばれ、特別な意味を持ちます。これを含めると、投稿時に自動的に値が埋め込まれます。",
|
||||||
@ -185,14 +195,5 @@
|
|||||||
"no": "キャンセル",
|
"no": "キャンセル",
|
||||||
"success": "アカウントを解除しました。トップ画面に戻ります。",
|
"success": "アカウントを解除しました。トップ画面に戻ります。",
|
||||||
"failure": "アカウントを解除できませんでした。"
|
"failure": "アカウントを解除できませんでした。"
|
||||||
},
|
|
||||||
"_noteScheduler": {
|
|
||||||
"createNew": "新規作成",
|
|
||||||
"edit": "編集",
|
|
||||||
"delete": "削除",
|
|
||||||
"noNotes": "予約投稿はまだ作成されていません。",
|
|
||||||
"deleteConfirm": "本当に予約投稿「{{note}}」を削除しますか?",
|
|
||||||
"accountToNote": "投稿するアカウント",
|
|
||||||
"schedule": "投稿を予約"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { blackA, olive, oliveDark, tomato, tomatoDark, whiteA } from '@radix-ui/colors';
|
import {
|
||||||
|
blackA,
|
||||||
|
olive,
|
||||||
|
oliveDark,
|
||||||
|
tomato,
|
||||||
|
tomatoDark,
|
||||||
|
whiteA,
|
||||||
|
} from '@radix-ui/colors';
|
||||||
import { createStitches } from '@stitches/react';
|
import { createStitches } from '@stitches/react';
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
61
packages/frontend/src/pages/apps/misskeholic.tsx
Normal file
61
packages/frontend/src/pages/apps/misskeholic.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type { RouterOutput } from '@/libs/trpc';
|
||||||
|
|
||||||
|
import { VStack } from '@/components/layouts/VStack';
|
||||||
|
import { PageRoot } from '@/components/PageRoot';
|
||||||
|
import { Button } from '@/components/primitives/Button';
|
||||||
|
import { Heading } from '@/components/primitives/Heading';
|
||||||
|
import { styled } from '@/libs/stitches';
|
||||||
|
import { trpc } from '@/libs/trpc';
|
||||||
|
|
||||||
|
const StyledButton = styled('button', {
|
||||||
|
borderRadius: '$2',
|
||||||
|
padding: '$m',
|
||||||
|
background: '$card',
|
||||||
|
boxShadow: '$s',
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: '$fg',
|
||||||
|
});
|
||||||
|
|
||||||
|
type SessionSelectorProp = {
|
||||||
|
onSelect?: (session: RouterOutput['account']['getMisskeySessions'][number]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SessionSelector: React.FC<SessionSelectorProp> = (p) => {
|
||||||
|
const [sessions] = trpc.account.getMisskeySessions.useSuspenseQuery();
|
||||||
|
const { mutateAsync: run } = trpc.holic.adminForceRunAll.useMutation();
|
||||||
|
|
||||||
|
const onClickForceRunButton = () => run();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>Misskey アカウントを選択してください。</p>
|
||||||
|
<VStack>
|
||||||
|
{sessions.map(s => (
|
||||||
|
<StyledButton key={s.id} as={Link} to={`/apps/misskeholic/${s.id}`} onClick={() => {
|
||||||
|
p.onSelect?.(s);
|
||||||
|
}}>
|
||||||
|
@{s.username}@{s.host}
|
||||||
|
</StyledButton>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
<Button danger onClick={onClickForceRunButton}>Force RUN</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MisskeholicPage: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageRoot title={t('misskeholicAlert')}>
|
||||||
|
<Heading>{t('misskeholicAlert')}</Heading>
|
||||||
|
<SessionSelector/>
|
||||||
|
</PageRoot>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MisskeholicPage;
|
56
packages/frontend/src/pages/apps/misskeholic.user.tsx
Normal file
56
packages/frontend/src/pages/apps/misskeholic.user.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { DRAFT_DEFAULT, HolicSettings } from '@/components/domains/holic/HolicSettings';
|
||||||
|
import { VStack } from '@/components/layouts/VStack';
|
||||||
|
import { PageRoot } from '@/components/PageRoot';
|
||||||
|
import { Button } from '@/components/primitives/Button';
|
||||||
|
import { Heading } from '@/components/primitives/Heading';
|
||||||
|
import { trpc } from '@/libs/trpc';
|
||||||
|
|
||||||
|
const MisskeholicUserPage: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const [ sessions ] = trpc.account.getMisskeySessions.useSuspenseQuery();
|
||||||
|
const [ holicAccount, { refetch } ] = trpc.holic.getAccount.useSuspenseQuery({ sessionId: id ?? '' });
|
||||||
|
const { mutateAsync: createAccount } = trpc.holic.createAccount.useMutation();
|
||||||
|
|
||||||
|
const session = useMemo(() => sessions.find(s => s.id === id), [id, sessions]);
|
||||||
|
|
||||||
|
const [draft, setDraft] = useState(DRAFT_DEFAULT);
|
||||||
|
|
||||||
|
if (session === null) return null;
|
||||||
|
|
||||||
|
const startHolic = async () => {
|
||||||
|
if (!session) return;
|
||||||
|
await createAccount({
|
||||||
|
sessionId: session.id,
|
||||||
|
...draft,
|
||||||
|
});
|
||||||
|
await refetch();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<PageRoot title={t('misskeholicAlert')}>
|
||||||
|
<Heading>{t('misskeholicAlert')}</Heading>
|
||||||
|
{!holicAccount ? (
|
||||||
|
<>
|
||||||
|
<VStack>
|
||||||
|
<p>いくつかの項目を設定し、ミス廃アラートをセットアップしましょう。</p>
|
||||||
|
<HolicSettings draft={draft} onUpdateDraft={setDraft} />
|
||||||
|
<Button primaryGradient size="large" css={{ margin: '$xl auto 0 auto' }} onClick={startHolic}>
|
||||||
|
ミス廃アラートを開始する
|
||||||
|
</Button>
|
||||||
|
</VStack>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<pre>
|
||||||
|
{JSON.stringify(holicAccount, null, ' ')}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</PageRoot>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MisskeholicUserPage;
|
@ -111,6 +111,7 @@ const NoteCard: React.FC<{ note: RouterOutput['noteScheduler']['list'][number] }
|
|||||||
</VStack>
|
</VStack>
|
||||||
<Alert
|
<Alert
|
||||||
danger
|
danger
|
||||||
|
hasCancel
|
||||||
description={t('_noteScheduler.deleteConfirm', { note: note.text })}
|
description={t('_noteScheduler.deleteConfirm', { note: note.text })}
|
||||||
open={deleteConfirmDialogOpened}
|
open={deleteConfirmDialogOpened}
|
||||||
onOpenChange={setDeleteConfirmDialogOpened}
|
onOpenChange={setDeleteConfirmDialogOpened}
|
||||||
|
Loading…
Reference in New Issue
Block a user