diff --git a/CHANGELOG.md b/CHANGELOG.md index 98eeddd..f34a685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,18 @@ -## 2.0 +## 2.2.0 + +* 全権限を許可するトークンへ更新 +* トークン更新を促すように +* ログイン中のユーザーを明示するように +* トークンがおかしい場合、登録しているアカウントが他にあればそれを使うように +* デザイン調整 + +## 2.1.0 + +* 「ねこみみアジャスター」機能を追加 +* デザイン調整 +* セキュリティアップデート + +## 2.0.0 * デザイン面・機能面での大幅な作り直し diff --git a/migration/1643366857976-addTokenVersion.ts b/migration/1643366857976-addTokenVersion.ts new file mode 100644 index 0000000..40bb25e --- /dev/null +++ b/migration/1643366857976-addTokenVersion.ts @@ -0,0 +1,14 @@ +import {MigrationInterface, QueryRunner} from 'typeorm'; + +export class addTokenVersion1643366857976 implements MigrationInterface { + name = 'addTokenVersion1643366857976' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query('ALTER TABLE "user" ADD "tokenVersion" integer NOT NULL DEFAULT 1'); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('ALTER TABLE "user" DROP COLUMN "tokenVersion"'); + } + +} diff --git a/package.json b/package.json index 73db396..ecb0a5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey-tools", - "version": "2.1.0", + "version": "2.2.0", "description": "", "main": "built/app.js", "author": "Xeltica", diff --git a/src/backend/const.ts b/src/backend/const.ts index 28a4594..0d6af5a 100644 --- a/src/backend/const.ts +++ b/src/backend/const.ts @@ -1 +1,50 @@ export const defaultTemplate = '昨日のMisskeyの活動は\n\nノート: {notesCount}({notesDelta})\nフォロー : {followingCount}({followingDelta})\nフォロワー :{followersCount}({followersDelta})\n\nでした。\n{url}'; + +/** + * 現在のMisskeyアプリトークンバージョン。 + * ver 2: + * * 全権限を許可するように(将来的に使うため) + * * アプリ名をMisskey Toolsに + * ver 1: + * * 初回バージョン +*/ +export const currentTokenVersion = 2; + +export const misskeyAppInfo = { + name: 'Misskey Tools', + description: 'A Professional Toolkit Designed for Misskey.', + permission: [ + 'read:account', + 'write:account', + 'read:blocks', + 'write:blocks', + 'read:drive', + 'write:drive', + 'read:favorites', + 'write:favorites', + 'read:following', + 'write:following', + 'read:messaging', + 'write:messaging', + 'read:mutes', + 'write:mutes', + 'write:notes', + 'read:notifications', + 'write:notifications', + 'read:reactions', + 'write:reactions', + 'write:votes', + 'read:pages', + 'write:pages', + 'write:page-likes', + 'read:page-likes', + 'read:user-groups', + 'write:user-groups', + 'read:channels', + 'write:channels', + 'read:gallery', + 'write:gallery', + 'read:gallery-likes', + 'write:gallery-likes', + ], +} as const; diff --git a/src/backend/controllers/meta.ts b/src/backend/controllers/meta.ts index 871001c..3ebf810 100644 --- a/src/backend/controllers/meta.ts +++ b/src/backend/controllers/meta.ts @@ -3,13 +3,19 @@ * @author Xeltica */ +import { readFile } from 'fs'; import { Get, JsonController } from 'routing-controllers'; +import { promisify } from 'util'; +import { Meta } from '../../common/types/meta'; +import { currentTokenVersion } from '../const'; @JsonController('/meta') export class MetaController { - @Get() get() { + @Get() async get(): Promise { + const {version} = JSON.parse(await promisify(readFile)(__dirname + '/../../meta.json', { encoding: 'utf-8'})); return { - honi: 'ほに', + version, + currentTokenVersion, }; } } diff --git a/src/backend/functions/users.ts b/src/backend/functions/users.ts index 4139f96..02f94c0 100644 --- a/src/backend/functions/users.ts +++ b/src/backend/functions/users.ts @@ -4,6 +4,7 @@ import { DeepPartial } from 'typeorm'; import { genToken } from './gen-token'; import { IUser } from '../../common/types/user'; import { config } from '../../config'; +import { currentTokenVersion } from '../const'; /** * IUser インターフェイスに変換します。 @@ -61,9 +62,9 @@ export const getUserByToolsToken = (token: string): Promise = export const upsertUser = async (username: string, host: string, token: string): Promise => { const u = await getUser(username, host); if (u) { - await Users.update(u.id, { token }); + await Users.update(u.id, { token, tokenVersion: currentTokenVersion }); } else { - await Users.insert({ username, host, token }); + await Users.insert({ username, host, token, tokenVersion: currentTokenVersion }); } }; diff --git a/src/backend/models/entities/user.ts b/src/backend/models/entities/user.ts index 3b5e5cd..7b3a9bf 100644 --- a/src/backend/models/entities/user.ts +++ b/src/backend/models/entities/user.ts @@ -98,4 +98,11 @@ export class User implements IUser { default: false, }) public bannedFromRanking: boolean; + + @Column({ + type: 'integer', + default: 1, + comment: 'Misskey API トークンのバージョン。現行と違う場合はアップデートを要求する', + }) + public tokenVersion: number; } diff --git a/src/backend/router.ts b/src/backend/router.ts index 94162dc..b22d08f 100644 --- a/src/backend/router.ts +++ b/src/backend/router.ts @@ -10,6 +10,7 @@ import { config } from '../config'; import { upsertUser, getUser, updateUser, updateUsersToolsToken } from './functions/users'; import { api } from './services/misskey'; import { die } from './die'; +import { misskeyAppInfo } from './const'; export const router = new Router(); @@ -36,9 +37,8 @@ router.get('/login', async ctx => { // ホスト名の正規化 host = meta.uri.replace(/^https?:\/\//, ''); - const name = 'みす廃あらーと'; - const description = 'ついついノートしすぎていませんか?'; - const permission = ['write:notes', 'write:notifications', 'write:drive', 'read:account', 'write:account']; + + const { name, permission, description } = misskeyAppInfo; if (meta.features.miauth) { // MiAuthを使用する diff --git a/src/common/types/meta.ts b/src/common/types/meta.ts new file mode 100644 index 0000000..9f8d4df --- /dev/null +++ b/src/common/types/meta.ts @@ -0,0 +1,4 @@ +export interface Meta { + version: string; + currentTokenVersion: number; +} diff --git a/src/common/types/user.ts b/src/common/types/user.ts index 49f8460..39d3a6f 100644 --- a/src/common/types/user.ts +++ b/src/common/types/user.ts @@ -19,5 +19,6 @@ export interface IUser { rating: number; bannedFromRanking: boolean; isAdmin?: boolean; + tokenVersion: number; } diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx index b21ad88..7e13d8f 100644 --- a/src/frontend/App.tsx +++ b/src/frontend/App.tsx @@ -13,7 +13,7 @@ import { store } from './store'; import { ModalComponent } from './Modal'; import { useTheme } from './misc/theme'; import { getBrowserLanguage, resources } from './langs'; -import { LOCALSTORAGE_KEY_LANG } from './const'; +import { LOCALSTORAGE_KEY_LANG, XELTICA_STUDIO_URL } from './const'; import 'xeltica-ui/dist/css/xeltica-ui.min.css'; import './style.scss'; @@ -46,7 +46,7 @@ const AppInner : React.VFC = () => { return error ? (
-
+

{t('error')}

{t('_error.sorry')}

@@ -67,7 +67,7 @@ const AppInner : React.VFC = () => {
-

(C)2020-2022 Xeltica Studio

+

(C)2020-2022 Xeltica Studio

{t('termsOfService')}

diff --git a/src/frontend/components/Header.tsx b/src/frontend/components/Header.tsx index 7e9ded6..0a6fee9 100644 --- a/src/frontend/components/Header.tsx +++ b/src/frontend/components/Header.tsx @@ -2,6 +2,8 @@ import React, { HTMLProps } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import { useGetMetaQuery } from '../services/session'; +import { CHANGELOG_URL } from '../const'; export type HeaderProps = { hasTopLink?: boolean; @@ -12,12 +14,18 @@ export type HeaderProps = { const messageNumber = Math.floor(Math.random() * 6) + 1; export const Header: React.FC = ({hasTopLink, children, className, style}) => { + const {data: meta} = useGetMetaQuery(undefined); const { t } = useTranslation(); return (

{hasTopLink ? {t('title')} : t('title')} + {meta && ( + + v{meta?.version} + + )}

{t(`_welcomeMessage.pattern${messageNumber}`)}

{children} diff --git a/src/frontend/components/MisshaiPage.tsx b/src/frontend/components/MisshaiPage.tsx index 5d8676e..b299175 100644 --- a/src/frontend/components/MisshaiPage.tsx +++ b/src/frontend/components/MisshaiPage.tsx @@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux'; import { alertModes } from '../../common/types/alert-mode'; import { IUser } from '../../common/types/user'; import { Visibility } from '../../common/types/visibility'; -import { LOCALSTORAGE_KEY_TOKEN } from '../const'; +import { LOCALSTORAGE_KEY_ACCOUNTS, LOCALSTORAGE_KEY_TOKEN } from '../const'; import { $post, $put } from '../misc/api'; import { useGetScoreQuery, useGetSessionQuery } from '../services/session'; import { showModal } from '../store/slices/screen'; @@ -169,6 +169,13 @@ export const MisshaiPage: React.VFC = () => { if (session.error) { console.error(session.error); localStorage.removeItem(LOCALSTORAGE_KEY_TOKEN); + const a = localStorage.getItem(LOCALSTORAGE_KEY_ACCOUNTS); + if (a) { + const accounts = JSON.parse(a) as string[]; + if (accounts.length > 0) { + localStorage.setItem(LOCALSTORAGE_KEY_TOKEN, accounts[0]); + } + } location.reload(); } }, [session.error]); diff --git a/src/frontend/const.ts b/src/frontend/const.ts index 3eab000..1983a42 100644 --- a/src/frontend/const.ts +++ b/src/frontend/const.ts @@ -4,3 +4,6 @@ export const LOCALSTORAGE_KEY_LANG = 'lang'; export const LOCALSTORAGE_KEY_ACCOUNTS = 'accounts'; export const API_ENDPOINT = `//${location.host}/api/v1/`; + +export const CHANGELOG_URL = 'https://github.com/Xeltica/MisskeyTools/blob/master/CHANGELOG.md'; +export const XELTICA_STUDIO_URL = 'https://xeltica.work'; diff --git a/src/frontend/langs/ja-JP.json b/src/frontend/langs/ja-JP.json index a55bf5f..b3f682a 100644 --- a/src/frontend/langs/ja-JP.json +++ b/src/frontend/langs/ja-JP.json @@ -45,6 +45,8 @@ "upload": "アップロード", "preview": "プレビュー", "catAdjuster": "ねこみみアジャスター", + "shouldUpdateToken": "認証トークンの更新が必要です。更新しないと一部機能が正常に動作しません。", + "update": "更新する", "_welcomeMessage": { "pattern1": "ついついノートしすぎていませんか?", "pattern2": "Misskey, しすぎていませんか?", diff --git a/src/frontend/pages/index.session.tsx b/src/frontend/pages/index.session.tsx index b67409a..3778357 100644 --- a/src/frontend/pages/index.session.tsx +++ b/src/frontend/pages/index.session.tsx @@ -10,16 +10,19 @@ import { useDispatch } from 'react-redux'; import { LOCALSTORAGE_KEY_ACCOUNTS } from '../const'; import { IUser } from '../../common/types/user'; import { setAccounts } from '../store/slices/screen'; -import { useGetSessionQuery } from '../services/session'; +import { useGetMetaQuery, useGetSessionQuery } from '../services/session'; import { AdminPage } from '../components/AdminPage'; import { $get } from '../misc/api'; import { NekomimiPage } from '../components/NekomimiPage'; +import { Card } from '../components/Card'; +import { CurrentUser } from '../components/CurrentUser'; export const IndexSessionPage: React.VFC = () => { const [selectedTab, setSelectedTab] = useState('misshai'); const {t, i18n} = useTranslation(); const dispatch = useDispatch(); - const { data } = useGetSessionQuery(undefined); + const { data: session } = useGetSessionQuery(undefined); + const { data: meta } = useGetMetaQuery(undefined); useEffect(() => { const accounts = JSON.parse(localStorage.getItem(LOCALSTORAGE_KEY_ACCOUNTS) || '[]') as string[]; @@ -31,12 +34,12 @@ export const IndexSessionPage: React.VFC = () => { it.push({ label: t('_nav.misshai'), key: 'misshai' }); it.push({ label: t('_nav.accounts'), key: 'accounts' }); it.push({ label: t('_nav.catAdjuster'), key: 'nekomimi', isNew: true }); - if (data?.isAdmin) { + if (session?.isAdmin) { it.push({ label: 'Admin', key: 'admin' }); } it.push({ label: t('_nav.settings'), key: 'settings' }); return it; - }, [i18n.language, data]); + }, [i18n.language, session]); const component = useMemo(() => { switch (selectedTab) { @@ -57,7 +60,16 @@ export const IndexSessionPage: React.VFC = () => {
-
+
+ + + {session && meta && meta.currentTokenVersion !== session.tokenVersion && ( +
+ {t('shouldUpdateToken')} + {t('update')} +
+ )} +
{component}
diff --git a/src/frontend/services/session.ts b/src/frontend/services/session.ts index 950163f..6c239ec 100644 --- a/src/frontend/services/session.ts +++ b/src/frontend/services/session.ts @@ -2,14 +2,15 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { API_ENDPOINT, LOCALSTORAGE_KEY_TOKEN } from '../const'; import { IUser } from '../../common/types/user'; import { Score } from '../../common/types/score'; +import { Meta } from '../../common/types/meta'; export const sessionApi = createApi({ reducerPath: 'session', - baseQuery: fetchBaseQuery({ baseUrl: API_ENDPOINT + 'session' }), + baseQuery: fetchBaseQuery({ baseUrl: API_ENDPOINT }), endpoints: (builder) => ({ getSession: builder.query({ query: () => ({ - url: '/', + url: '/session/', headers: { 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, } @@ -17,7 +18,15 @@ export const sessionApi = createApi({ }), getScore: builder.query({ query: () => ({ - url: '/score', + url: '/session/score', + headers: { + 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, + } + }) + }), + getMeta: builder.query({ + query: () => ({ + url: '/meta', headers: { 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, } @@ -29,4 +38,5 @@ export const sessionApi = createApi({ export const { useGetSessionQuery, useGetScoreQuery, + useGetMetaQuery, } = sessionApi;