diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5bdd3f1c..88e8055daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - Chat UIが削除されました ### Improvements +- カスタム絵文字一括編集機能 +- カスタム絵文字一括インポート ### Bugfixes diff --git a/packages/backend/package.json b/packages/backend/package.json index 65da382e2d..c940e98301 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -180,6 +180,7 @@ "typeorm": "0.2.39", "typescript": "4.4.4", "ulid": "2.3.0", + "unzipper": "0.10.11", "uuid": "8.3.2", "web-push": "3.4.5", "websocket": "1.0.34", diff --git a/packages/backend/src/misc/gen-avatar.ts b/packages/backend/src/misc/gen-identicon.ts similarity index 90% rename from packages/backend/src/misc/gen-avatar.ts rename to packages/backend/src/misc/gen-identicon.ts index 8838ec8d15..5cedd7afaf 100644 --- a/packages/backend/src/misc/gen-avatar.ts +++ b/packages/backend/src/misc/gen-identicon.ts @@ -1,5 +1,6 @@ /** - * Random avatar generator + * Identicon generator + * https://en.wikipedia.org/wiki/Identicon */ import * as p from 'pureimage'; @@ -34,9 +35,9 @@ const cellSize = actualSize / n; const sideN = Math.floor(n / 2); /** - * Generate buffer of random avatar by seed + * Generate buffer of an identicon by seed */ -export function genAvatar(seed: string, stream: WriteStream): Promise { +export function genIdenticon(seed: string, stream: WriteStream): Promise { const rand = gen.create(seed); const canvas = p.make(size, size); const ctx = canvas.getContext('2d'); diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 3dc7c67ec2..85141cdc41 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -159,7 +159,7 @@ export class UserRepository extends Repository { if (user.avatarUrl) { return user.avatarUrl; } else { - return `${config.url}/random-avatar/${user.id}`; + return `${config.url}/identicon/${user.id}`; } } diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 2fbc1b1c01..f9994c3b59 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -213,6 +213,16 @@ export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id'] }); } +export function createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) { + return dbQueue.add('importCustomEmojis', { + user: user, + fileId: fileId, + }, { + removeOnComplete: true, + removeOnFail: true, + }); +} + export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { return dbQueue.add('deleteAccount', { user: user, diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index 3930b9d6d4..a420866dcf 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -52,7 +52,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); }; - await writeMeta(`{"metaVersion":1,"emojis":[`); + await writeMeta(`{"metaVersion":2,"emojis":[`); const customEmojis = await Emojis.find({ where: { @@ -64,9 +64,9 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); for (const emoji of customEmojis) { - const exportId = ulid().toLowerCase(); const ext = mime.extension(emoji.type); - const emojiPath = path + '/' + exportId + (ext ? '.' + ext : ''); + const fileName = emoji.name + (ext ? '.' + ext : ''); + const emojiPath = path + '/' + fileName; fs.writeFileSync(emojiPath, '', 'binary'); let downloaded = false; @@ -77,8 +77,12 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi logger.error(e); } + if (!downloaded) { + fs.unlinkSync(emojiPath); + } + const content = JSON.stringify({ - id: exportId, + fileName: fileName, downloaded: downloaded, emoji: emoji, }); diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts new file mode 100644 index 0000000000..eb386bbb42 --- /dev/null +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -0,0 +1,84 @@ +import * as Bull from 'bull'; +import * as tmp from 'tmp'; +import * as fs from 'fs'; +const unzipper = require('unzipper'); +import { getConnection } from 'typeorm'; + +import { queueLogger } from '../../logger'; +import { downloadUrl } from '@/misc/download-url'; +import { DriveFiles, Emojis } from '@/models/index'; +import { DbUserImportJobData } from '@/queue/types'; +import addFile from '@/services/drive/add-file'; +import { genId } from '@/misc/gen-id'; + +const logger = queueLogger.createSubLogger('import-custom-emojis'); + +// TODO: 名前衝突時の動作を選べるようにする +export async function importCustomEmojis(job: Bull.Job, done: any): Promise { + logger.info(`Importing custom emojis ...`); + + const file = await DriveFiles.findOne({ + id: job.data.fileId, + }); + if (file == null) { + done(); + return; + } + + // Create temp dir + const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { + tmp.dir((e, path, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + }); + }); + + logger.info(`Temp dir is ${path}`); + + const destPath = path + '/emojis.zip'; + + try { + fs.writeFileSync(destPath, '', 'binary'); + await downloadUrl(file.url, destPath); + } catch (e) { // TODO: 何度か再試行 + logger.error(e); + throw e; + } + + const outputPath = path + '/emojis'; + const unzipStream = fs.createReadStream(destPath); + const extractor = unzipper.Extract({ path: outputPath }); + extractor.on('close', async () => { + const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8'); + const meta = JSON.parse(metaRaw); + + for (const record of meta.emojis) { + if (!record.downloaded) continue; + const emojiInfo = record.emoji; + const emojiPath = outputPath + '/' + record.fileName; + await Emojis.delete({ + name: emojiInfo.name, + }); + const driveFile = await addFile(null, emojiPath, record.fileName, null, null, true); + const emoji = await Emojis.insert({ + id: genId(), + updatedAt: new Date(), + name: emojiInfo.name, + category: emojiInfo.category, + host: null, + aliases: emojiInfo.aliases, + url: driveFile.url, + type: driveFile.type, + }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); + + cleanup(); + + logger.succ('Imported'); + done(); + }); + unzipStream.pipe(extractor); + logger.succ(`Unzipping to ${outputPath}`); +} diff --git a/packages/backend/src/queue/processors/db/index.ts b/packages/backend/src/queue/processors/db/index.ts index 1542f401ef..5fffa378f5 100644 --- a/packages/backend/src/queue/processors/db/index.ts +++ b/packages/backend/src/queue/processors/db/index.ts @@ -12,6 +12,7 @@ import { importUserLists } from './import-user-lists'; import { deleteAccount } from './delete-account'; import { importMuting } from './import-muting'; import { importBlocking } from './import-blocking'; +import { importCustomEmojis } from './import-custom-emojis'; const jobs = { deleteDriveFiles, @@ -25,6 +26,7 @@ const jobs = { importMuting, importBlocking, importUserLists, + importCustomEmojis, deleteAccount, } as Record | Bull.ProcessPromiseFunction>; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts new file mode 100644 index 0000000000..ef0f315022 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.update(emoji.id, { + updatedAt: new Date(), + aliases: [...new Set(emoji.aliases.concat(ps.aliases))], + }); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts new file mode 100644 index 0000000000..a99cd3c978 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -0,0 +1,37 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { insertModerationLog } from '@/services/insert-moderation-log'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps, me) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.delete(emoji.id); + + await getConnection().queryResultCache!.remove(['meta_emojis']); + + insertModerationLog(me, 'deleteEmoji', { + emoji: emoji, + }); + } +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts similarity index 95% rename from packages/backend/src/server/api/endpoints/admin/emoji/remove.ts rename to packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index 440c1008c7..870245ac92 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -37,7 +37,7 @@ export default define(meta, async (ps, me) => { await getConnection().queryResultCache!.remove(['meta_emojis']); - insertModerationLog(me, 'removeEmoji', { + insertModerationLog(me, 'deleteEmoji', { emoji: emoji, }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts new file mode 100644 index 0000000000..04895b8f20 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts @@ -0,0 +1,21 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { createImportCustomEmojisJob } from '@/queue/index'; +import ms from 'ms'; +import { ID } from '@/misc/cafy-id'; + +export const meta = { + secure: true, + requireCredential: true as const, + requireModerator: true, + params: { + fileId: { + validator: $.type(ID), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps, user) => { + createImportCustomEmojisJob(user, ps.fileId); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts new file mode 100644 index 0000000000..4c771b4e42 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.update(emoji.id, { + updatedAt: new Date(), + aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), + }); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts new file mode 100644 index 0000000000..33dccbc642 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + await Emojis.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + aliases: ps.aliases, + }); + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts new file mode 100644 index 0000000000..d40ed52da7 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true as const, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + category: { + validator: $.optional.nullable.str, + }, + }, +}; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + await Emojis.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + category: ps.category, + }); + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 85fe21accb..764306c7d8 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -23,7 +23,7 @@ import Logger from '@/services/logger'; import { envOption } from '../env'; import { UserProfiles, Users } from '@/models/index'; import { networkChart } from '@/services/chart/index'; -import { genAvatar } from '@/misc/gen-avatar'; +import { genIdenticon } from '@/misc/gen-identicon'; import { createTemp } from '@/misc/create-temp'; import { publishMainStream } from '@/services/stream'; import * as Acct from 'misskey-js/built/acct'; @@ -84,9 +84,9 @@ router.get('/avatar/@:acct', async ctx => { } }); -router.get('/random-avatar/:x', async ctx => { +router.get('/identicon/:x', async ctx => { const [temp] = await createTemp(); - await genAvatar(ctx.params.x, fs.createWriteStream(temp)); + await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); ctx.set('Content-Type', 'image/png'); ctx.body = fs.createReadStream(temp); }); diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 16144b6d57..9e21fb29e3 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -1522,6 +1522,11 @@ big-integer@^1.6.16: resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== +big-integer@^1.6.17: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1532,6 +1537,14 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bl@^4.0.1, bl@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" @@ -1546,6 +1559,11 @@ bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + blurhash@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.4.tgz#a7010ceb3019cd2c9809b17c910ebf6175d29244" @@ -1677,6 +1695,11 @@ buffer-from@^1.0.0, buffer-from@^1.1.1: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + buffer-writer@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" @@ -1707,6 +1730,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + bufferutil@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" @@ -1875,6 +1903,13 @@ cbor@8.1.0: dependencies: nofilter "^3.1.0" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" @@ -2789,6 +2824,13 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -3480,6 +3522,16 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3690,7 +3742,7 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.2.0: +graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== @@ -4007,7 +4059,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4800,6 +4852,11 @@ lilconfig@^2.0.3: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + loader-runner@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" @@ -5204,7 +5261,7 @@ mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@0.x, mkdirp@^0.5.4: +mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.4: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -6598,7 +6655,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -6780,6 +6837,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -6914,7 +6978,7 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -setimmediate@^1.0.5: +setimmediate@^1.0.5, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -7584,6 +7648,11 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504" integrity sha512-UUfa1DjjU5flcjMdaFIiIEGDTyu2y/IiMjOX4uGXa7meKBS4vD4f2Uy/tken9Qkd4Jsm4sRsfZcIIPqrRVF3Mg== +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= + ts-jest@^25.2.1: version "25.5.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" @@ -7827,6 +7896,22 @@ unpipe@1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +unzipper@0.10.11: + version "0.10.11" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" + integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 8e4ff6e455..e0113019ac 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -14,6 +14,10 @@ module.exports = { "plugin:vue/vue3-recommended" ], rules: { + // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため + // data の禁止理由: 抽象的すぎるため + // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため + "id-denylist": ["error", "window", "data", "e"], "vue/attributes-order": ["error", { "alphabetical": false }], diff --git a/packages/client/package.json b/packages/client/package.json index 88f8077df3..c2dc821b39 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -21,7 +21,6 @@ "@types/katex": "0.11.1", "@types/matter-js": "0.17.6", "@types/mocha": "8.2.3", - "@types/node": "16.11.12", "@types/oauth": "0.9.1", "@types/parse5": "6.0.3", "@types/punycode": "2.1.0", diff --git a/packages/client/src/components/note.sub.vue b/packages/client/src/components/MkNoteSub.vue similarity index 66% rename from packages/client/src/components/note.sub.vue rename to packages/client/src/components/MkNoteSub.vue index de4218e535..30c27e6235 100644 --- a/packages/client/src/components/note.sub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -10,13 +10,13 @@

- +
{{ $ts.continueThread }} @@ -24,63 +24,36 @@
- \ No newline at end of file + diff --git a/packages/client/src/pages/featured.vue b/packages/client/src/pages/featured.vue index efa74ca599..725c70f0f7 100644 --- a/packages/client/src/pages/featured.vue +++ b/packages/client/src/pages/featured.vue @@ -10,7 +10,7 @@ import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; const pagination = { - endpoint: 'notes/featured', + endpoint: 'notes/featured' as const, limit: 10, offsetMode: true, }; diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 9815e68986..6a4a28b6b4 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -95,8 +95,8 @@ - diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue index 54d695091d..764daa0d3e 100644 --- a/packages/client/src/pages/follow-requests.vue +++ b/packages/client/src/pages/follow-requests.vue @@ -1,6 +1,6 @@ - diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue index d185e796c3..7138d269a9 100644 --- a/packages/client/src/pages/my-antennas/index.vue +++ b/packages/client/src/pages/my-antennas/index.vue @@ -38,7 +38,7 @@ export default defineComponent({ } }, pagination: { - endpoint: 'antennas/list', + endpoint: 'antennas/list' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue index a5bbc3fd2d..97b563f6f8 100644 --- a/packages/client/src/pages/my-clips/index.vue +++ b/packages/client/src/pages/my-clips/index.vue @@ -3,7 +3,7 @@
{{ $ts.add }} - + {{ item.name }}
{{ item.description }}
@@ -13,71 +13,64 @@ - diff --git a/packages/client/src/pages/my-groups/index.vue b/packages/client/src/pages/my-groups/index.vue index db5ccde466..4b2b2963a8 100644 --- a/packages/client/src/pages/my-groups/index.vue +++ b/packages/client/src/pages/my-groups/index.vue @@ -87,15 +87,15 @@ export default defineComponent({ })), tab: 'owned', ownedPagination: { - endpoint: 'users/groups/owned', + endpoint: 'users/groups/owned' as const, limit: 10, }, joinedPagination: { - endpoint: 'users/groups/joined', + endpoint: 'users/groups/joined' as const, limit: 10, }, invitationPagination: { - endpoint: 'i/user-group-invites', + endpoint: 'i/user-group-invites' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue index 94a869b9ff..e6fcba1b34 100644 --- a/packages/client/src/pages/my-lists/index.vue +++ b/packages/client/src/pages/my-lists/index.vue @@ -3,7 +3,7 @@
{{ $ts.createList }} - +
{{ list.name }}
@@ -13,50 +13,41 @@ - diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index d40082381c..72ac85ee90 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -82,21 +82,21 @@ export default defineComponent({ showNext: false, error: null, prev: { - endpoint: 'users/notes', + endpoint: 'users/notes' as const, limit: 10, - params: init => ({ + params: computed(() => ({ userId: this.note.userId, untilId: this.note.id, - }) + })), }, next: { reversed: true, - endpoint: 'users/notes', + endpoint: 'users/notes' as const, limit: 10, - params: init => ({ + params: computed(() => ({ userId: this.note.userId, sinceId: this.note.id, - }) + })), }, }; }, diff --git a/packages/client/src/pages/notifications.vue b/packages/client/src/pages/notifications.vue index 695c54a535..090e80f99a 100644 --- a/packages/client/src/pages/notifications.vue +++ b/packages/client/src/pages/notifications.vue @@ -6,70 +6,62 @@ - diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index 5cb3948f1c..429d1ddea2 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -106,7 +106,7 @@ export default defineComponent({ page: null, error: null, otherPostsPagination: { - endpoint: 'users/pages', + endpoint: 'users/pages' as const, limit: 6, params: computed(() => ({ userId: this.page.user.id diff --git a/packages/client/src/pages/pages.vue b/packages/client/src/pages/pages.vue index f1dd64f119..dcccf7f7c4 100644 --- a/packages/client/src/pages/pages.vue +++ b/packages/client/src/pages/pages.vue @@ -62,15 +62,15 @@ export default defineComponent({ })), tab: 'featured', featuredPagesPagination: { - endpoint: 'pages/featured', + endpoint: 'pages/featured' as const, noPaging: true, }, myPagesPagination: { - endpoint: 'i/pages', + endpoint: 'i/pages' as const, limit: 5, }, likedPagesPagination: { - endpoint: 'i/page-likes', + endpoint: 'i/page-likes' as const, limit: 5, }, }; diff --git a/packages/client/src/pages/preview.vue b/packages/client/src/pages/preview.vue index 9d1ebb74ed..8eb4549516 100644 --- a/packages/client/src/pages/preview.vue +++ b/packages/client/src/pages/preview.vue @@ -4,24 +4,18 @@
- diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue index e0608654c7..8ef73858f6 100644 --- a/packages/client/src/pages/reset-password.vue +++ b/packages/client/src/pages/reset-password.vue @@ -3,62 +3,51 @@
- + - {{ $ts.save }} + {{ i18n.locale.save }}
-