diff options
Diffstat (limited to 'src/misc')
58 files changed, 0 insertions, 2032 deletions
diff --git a/src/misc/acct.ts b/src/misc/acct.ts deleted file mode 100644 index 5106b1a09e..0000000000 --- a/src/misc/acct.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as Misskey from 'misskey-js'; - -export const getAcct = (user: Misskey.Acct) => { - return user.host == null ? user.username : `${user.username}@${user.host}`; -}; - -export const parseAcct = (acct: string): Misskey.Acct => { - if (acct.startsWith('@')) acct = acct.substr(1); - const split = acct.split('@', 2); - return { username: split[0], host: split[1] || null }; -}; diff --git a/src/misc/antenna-cache.ts b/src/misc/antenna-cache.ts deleted file mode 100644 index a23eeb45ec..0000000000 --- a/src/misc/antenna-cache.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Antennas } from '@/models/index'; -import { Antenna } from '@/models/entities/antenna'; -import { subsdcriber } from '../db/redis'; - -let antennasFetched = false; -let antennas: Antenna[] = []; - -export async function getAntennas() { - if (!antennasFetched) { - antennas = await Antennas.find(); - antennasFetched = true; - } - - return antennas; -} - -subsdcriber.on('message', async (_, data) => { - const obj = JSON.parse(data); - - if (obj.channel === 'internal') { - const { type, body } = obj.message; - switch (type) { - case 'antennaCreated': - antennas.push(body); - break; - case 'antennaUpdated': - antennas[antennas.findIndex(a => a.id === body.id)] = body; - break; - case 'antennaDeleted': - antennas = antennas.filter(a => a.id !== body.id); - break; - default: - break; - } - } -}); diff --git a/src/misc/api-permissions.ts b/src/misc/api-permissions.ts deleted file mode 100644 index 160cdf9fd6..0000000000 --- a/src/misc/api-permissions.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const kinds = [ - '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', -]; -// IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions). diff --git a/src/misc/app-lock.ts b/src/misc/app-lock.ts deleted file mode 100644 index a32b600612..0000000000 --- a/src/misc/app-lock.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { redisClient } from '../db/redis'; -import { promisify } from 'util'; -import * as redisLock from 'redis-lock'; - -/** - * Retry delay (ms) for lock acquisition - */ -const retryDelay = 100; - -const lock: (key: string, timeout?: number) => Promise<() => void> - = redisClient - ? promisify(redisLock(redisClient, retryDelay)) - : async () => () => { }; - -/** - * Get AP Object lock - * @param uri AP object ID - * @param timeout Lock timeout (ms), The timeout releases previous lock. - * @returns Unlock function - */ -export function getApLock(uri: string, timeout = 30 * 1000) { - return lock(`ap-object:${uri}`, timeout); -} - -export function getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000) { - return lock(`instance:${host}`, timeout); -} - -export function getChartInsertLock(lockKey: string, timeout = 30 * 1000) { - return lock(`chart-insert:${lockKey}`, timeout); -} diff --git a/src/misc/before-shutdown.ts b/src/misc/before-shutdown.ts deleted file mode 100644 index 33abf5fb4d..0000000000 --- a/src/misc/before-shutdown.ts +++ /dev/null @@ -1,92 +0,0 @@ -// https://gist.github.com/nfantone/1eaa803772025df69d07f4dbf5df7e58 - -'use strict'; - -/** - * @callback BeforeShutdownListener - * @param {string} [signalOrEvent] The exit signal or event name received on the process. - */ - -/** - * System signals the app will listen to initiate shutdown. - * @const {string[]} - */ -const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM']; - -/** - * Time in milliseconds to wait before forcing shutdown. - * @const {number} - */ -const SHUTDOWN_TIMEOUT = 15000; - -/** - * A queue of listener callbacks to execute before shutting - * down the process. - * @type {BeforeShutdownListener[]} - */ -const shutdownListeners = []; - -/** - * Listen for signals and execute given `fn` function once. - * @param {string[]} signals System signals to listen to. - * @param {function(string)} fn Function to execute on shutdown. - */ -const processOnce = (signals, fn) => { - for (const sig of signals) { - process.once(sig, fn); - } -}; - -/** - * Sets a forced shutdown mechanism that will exit the process after `timeout` milliseconds. - * @param {number} timeout Time to wait before forcing shutdown (milliseconds) - */ -const forceExitAfter = timeout => () => { - setTimeout(() => { - // Force shutdown after timeout - console.warn(`Could not close resources gracefully after ${timeout}ms: forcing shutdown`); - return process.exit(1); - }, timeout).unref(); -}; - -/** - * Main process shutdown handler. Will invoke every previously registered async shutdown listener - * in the queue and exit with a code of `0`. Any `Promise` rejections from any listener will - * be logged out as a warning, but won't prevent other callbacks from executing. - * @param {string} signalOrEvent The exit signal or event name received on the process. - */ -async function shutdownHandler(signalOrEvent) { - if (process.env.NODE_ENV === 'test') return process.exit(0); - - console.warn(`Shutting down: received [${signalOrEvent}] signal`); - - for (const listener of shutdownListeners) { - try { - await listener(signalOrEvent); - } catch (err) { - console.warn(`A shutdown handler failed before completing with: ${err.message || err}`); - } - } - - return process.exit(0); -} - -/** - * Registers a new shutdown listener to be invoked before exiting - * the main process. Listener handlers are guaranteed to be called in the order - * they were registered. - * @param {BeforeShutdownListener} listener The shutdown listener to register. - * @returns {BeforeShutdownListener} Echoes back the supplied `listener`. - */ -export function beforeShutdown(listener) { - shutdownListeners.push(listener); - return listener; -} - -// Register shutdown callback that kills the process after `SHUTDOWN_TIMEOUT` milliseconds -// This prevents custom shutdown handlers from hanging the process indefinitely -processOnce(SHUTDOWN_SIGNALS, forceExitAfter(SHUTDOWN_TIMEOUT)); - -// Register process shutdown callback -// Will listen to incoming signal events and execute all registered handlers in the stack -processOnce(SHUTDOWN_SIGNALS, shutdownHandler); diff --git a/src/misc/cache.ts b/src/misc/cache.ts deleted file mode 100644 index 71fbbd8a4c..0000000000 --- a/src/misc/cache.ts +++ /dev/null @@ -1,43 +0,0 @@ -export class Cache<T> { - private cache: Map<string | null, { date: number; value: T; }>; - private lifetime: number; - - constructor(lifetime: Cache<never>['lifetime']) { - this.cache = new Map(); - this.lifetime = lifetime; - } - - public set(key: string | null, value: T): void { - this.cache.set(key, { - date: Date.now(), - value - }); - } - - public get(key: string | null): T | undefined { - const cached = this.cache.get(key); - if (cached == null) return undefined; - if ((Date.now() - cached.date) > this.lifetime) { - this.cache.delete(key); - return undefined; - } - return cached.value; - } - - public delete(key: string | null) { - this.cache.delete(key); - } - - public async fetch(key: string | null, fetcher: () => Promise<T>): Promise<T> { - const cachedValue = this.get(key); - if (cachedValue !== undefined) { - // Cache HIT - return cachedValue; - } - - // Cache MISS - const value = await fetcher(); - this.set(key, value); - return value; - } -} diff --git a/src/misc/cafy-id.ts b/src/misc/cafy-id.ts deleted file mode 100644 index 39886611e1..0000000000 --- a/src/misc/cafy-id.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Context } from 'cafy'; - -export class ID<Maybe = string> extends Context<string | (Maybe extends {} ? string : Maybe)> { - public readonly name = 'ID'; - - constructor(optional = false, nullable = false) { - super(optional, nullable); - - this.push((v: any) => { - if (typeof v !== 'string') { - return new Error('must-be-an-id'); - } - return true; - }); - } - - public getType() { - return super.getType('String'); - } - - public makeOptional(): ID<undefined> { - return new ID(true, false); - } - - public makeNullable(): ID<null> { - return new ID(false, true); - } - - public makeOptionalNullable(): ID<undefined | null> { - return new ID(true, true); - } -} diff --git a/src/misc/captcha.ts b/src/misc/captcha.ts deleted file mode 100644 index f36943b589..0000000000 --- a/src/misc/captcha.ts +++ /dev/null @@ -1,56 +0,0 @@ -import fetch from 'node-fetch'; -import { URLSearchParams } from 'url'; -import { getAgentByUrl } from './fetch'; -import config from '@/config/index'; - -export async function verifyRecaptcha(secret: string, response: string) { - const result = await getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => { - throw `recaptcha-request-failed: ${e}`; - }); - - if (result.success !== true) { - const errorCodes = result['error-codes'] ? result['error-codes']?.join(', ') : ''; - throw `recaptcha-failed: ${errorCodes}`; - } -} - -export async function verifyHcaptcha(secret: string, response: string) { - const result = await getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => { - throw `hcaptcha-request-failed: ${e}`; - }); - - if (result.success !== true) { - const errorCodes = result['error-codes'] ? result['error-codes']?.join(', ') : ''; - throw `hcaptcha-failed: ${errorCodes}`; - } -} - -type CaptchaResponse = { - success: boolean; - 'error-codes'?: string[]; -}; - -async function getCaptchaResponse(url: string, secret: string, response: string): Promise<CaptchaResponse> { - const params = new URLSearchParams({ - secret, - response - }); - - const res = await fetch(url, { - method: 'POST', - body: params, - headers: { - 'User-Agent': config.userAgent - }, - timeout: 10 * 1000, - agent: getAgentByUrl - }).catch(e => { - throw `${e.message || e}`; - }); - - if (!res.ok) { - throw `${res.status}`; - } - - return await res.json() as CaptchaResponse; -} diff --git a/src/misc/check-hit-antenna.ts b/src/misc/check-hit-antenna.ts deleted file mode 100644 index 3789054b26..0000000000 --- a/src/misc/check-hit-antenna.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Antenna } from '@/models/entities/antenna'; -import { Note } from '@/models/entities/note'; -import { User } from '@/models/entities/user'; -import { UserListJoinings, UserGroupJoinings } from '@/models/index'; -import { getFullApAccount } from './convert-host'; -import { parseAcct } from '@/misc/acct'; -import { Packed } from './schema'; - -/** - * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい - */ -export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> { - if (note.visibility === 'specified') return false; - - if (note.visibility === 'followers') { - if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; - if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; - } - - if (!antenna.withReplies && note.replyId != null) return false; - - if (antenna.src === 'home') { - if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; - if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; - } else if (antenna.src === 'list') { - const listUsers = (await UserListJoinings.find({ - userListId: antenna.userListId! - })).map(x => x.userId); - - if (!listUsers.includes(note.userId)) return false; - } else if (antenna.src === 'group') { - const joining = await UserGroupJoinings.findOneOrFail(antenna.userGroupJoiningId!); - - const groupUsers = (await UserGroupJoinings.find({ - userGroupId: joining.userGroupId - })).map(x => x.userId); - - if (!groupUsers.includes(note.userId)) return false; - } else if (antenna.src === 'users') { - const accts = antenna.users.map(x => { - const { username, host } = parseAcct(x); - return getFullApAccount(username, host).toLowerCase(); - }); - if (!accts.includes(getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false; - } - - const keywords = antenna.keywords - // Clean up - .map(xs => xs.filter(x => x !== '')) - .filter(xs => xs.length > 0); - - if (keywords.length > 0) { - if (note.text == null) return false; - - const matched = keywords.some(and => - and.every(keyword => - antenna.caseSensitive - ? note.text!.includes(keyword) - : note.text!.toLowerCase().includes(keyword.toLowerCase()) - )); - - if (!matched) return false; - } - - const excludeKeywords = antenna.excludeKeywords - // Clean up - .map(xs => xs.filter(x => x !== '')) - .filter(xs => xs.length > 0); - - if (excludeKeywords.length > 0) { - if (note.text == null) return false; - - const matched = excludeKeywords.some(and => - and.every(keyword => - antenna.caseSensitive - ? note.text!.includes(keyword) - : note.text!.toLowerCase().includes(keyword.toLowerCase()) - )); - - if (matched) return false; - } - - if (antenna.withFile) { - if (note.fileIds && note.fileIds.length === 0) return false; - } - - // TODO: eval expression - - return true; -} diff --git a/src/misc/check-word-mute.ts b/src/misc/check-word-mute.ts deleted file mode 100644 index e2e871dd2b..0000000000 --- a/src/misc/check-word-mute.ts +++ /dev/null @@ -1,39 +0,0 @@ -const RE2 = require('re2'); -import { Note } from '@/models/entities/note'; -import { User } from '@/models/entities/user'; - -type NoteLike = { - userId: Note['userId']; - text: Note['text']; -}; - -type UserLike = { - id: User['id']; -}; - -export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: string[][]): Promise<boolean> { - // 自分自身 - if (me && (note.userId === me.id)) return false; - - const words = mutedWords - // Clean up - .map(xs => xs.filter(x => x !== '')) - .filter(xs => xs.length > 0); - - if (words.length > 0) { - if (note.text == null) return false; - - const matched = words.some(and => - and.every(keyword => { - const regexp = keyword.match(/^\/(.+)\/(.*)$/); - if (regexp) { - return new RE2(regexp[1], regexp[2]).test(note.text!); - } - return note.text!.includes(keyword); - })); - - if (matched) return true; - } - - return false; -} diff --git a/src/misc/content-disposition.ts b/src/misc/content-disposition.ts deleted file mode 100644 index 9df7ed4688..0000000000 --- a/src/misc/content-disposition.ts +++ /dev/null @@ -1,6 +0,0 @@ -const cd = require('content-disposition'); - -export function contentDisposition(type: 'inline' | 'attachment', filename: string): string { - const fallback = filename.replace(/[^\w.-]/g, '_'); - return cd(filename, { type, fallback }); -} diff --git a/src/misc/convert-host.ts b/src/misc/convert-host.ts deleted file mode 100644 index 6e9f6ed3e9..0000000000 --- a/src/misc/convert-host.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { URL } from 'url'; -import config from '@/config/index'; -import { toASCII } from 'punycode/'; - -export function getFullApAccount(username: string, host: string | null) { - return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`; -} - -export function isSelfHost(host: string) { - if (host == null) return true; - return toPuny(config.host) === toPuny(host); -} - -export function extractDbHost(uri: string) { - const url = new URL(uri); - return toPuny(url.hostname); -} - -export function toPuny(host: string) { - return toASCII(host.toLowerCase()); -} - -export function toPunyNullable(host: string | null | undefined): string | null { - if (host == null) return null; - return toASCII(host.toLowerCase()); -} diff --git a/src/misc/count-same-renotes.ts b/src/misc/count-same-renotes.ts deleted file mode 100644 index 6628761182..0000000000 --- a/src/misc/count-same-renotes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Notes } from '@/models/index'; - -export async function countSameRenotes(userId: string, renoteId: string, excludeNoteId: string | undefined): Promise<number> { - // 指定したユーザーの指定したノートのリノートがいくつあるか数える - const query = Notes.createQueryBuilder('note') - .where('note.userId = :userId', { userId }) - .andWhere('note.renoteId = :renoteId', { renoteId }); - - // 指定した投稿を除く - if (excludeNoteId) { - query.andWhere('note.id != :excludeNoteId', { excludeNoteId }); - } - - return await query.getCount(); -} diff --git a/src/misc/create-temp.ts b/src/misc/create-temp.ts deleted file mode 100644 index 04604cf7d0..0000000000 --- a/src/misc/create-temp.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as tmp from 'tmp'; - -export function createTemp(): Promise<[string, any]> { - return new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); -} diff --git a/src/misc/detect-url-mime.ts b/src/misc/detect-url-mime.ts deleted file mode 100644 index 274c291737..0000000000 --- a/src/misc/detect-url-mime.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createTemp } from './create-temp'; -import { downloadUrl } from './download-url'; -import { detectType } from './get-file-info'; - -export async function detectUrlMime(url: string) { - const [path, cleanup] = await createTemp(); - - try { - await downloadUrl(url, path); - const { mime } = await detectType(path); - return mime; - } finally { - cleanup(); - } -} diff --git a/src/misc/download-text-file.ts b/src/misc/download-text-file.ts deleted file mode 100644 index e8e23cc120..0000000000 --- a/src/misc/download-text-file.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as fs from 'fs'; -import * as util from 'util'; -import Logger from '@/services/logger'; -import { createTemp } from './create-temp'; -import { downloadUrl } from './download-url'; - -const logger = new Logger('download-text-file'); - -export async function downloadTextFile(url: string): Promise<string> { - // Create temp file - const [path, cleanup] = await createTemp(); - - logger.info(`Temp file is ${path}`); - - try { - // write content at URL to temp file - await downloadUrl(url, path); - - const text = await util.promisify(fs.readFile)(path, 'utf8'); - - return text; - } finally { - cleanup(); - } -} diff --git a/src/misc/download-url.ts b/src/misc/download-url.ts deleted file mode 100644 index c96b4fd1d6..0000000000 --- a/src/misc/download-url.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as fs from 'fs'; -import * as stream from 'stream'; -import * as util from 'util'; -import got, * as Got from 'got'; -import { httpAgent, httpsAgent, StatusError } from './fetch'; -import config from '@/config/index'; -import * as chalk from 'chalk'; -import Logger from '@/services/logger'; -import * as IPCIDR from 'ip-cidr'; -const PrivateIp = require('private-ip'); - -const pipeline = util.promisify(stream.pipeline); - -export async function downloadUrl(url: string, path: string) { - const logger = new Logger('download'); - - logger.info(`Downloading ${chalk.cyan(url)} ...`); - - const timeout = 30 * 1000; - const operationTimeout = 60 * 1000; - const maxSize = config.maxFileSize || 262144000; - - const req = got.stream(url, { - headers: { - 'User-Agent': config.userAgent - }, - timeout: { - lookup: timeout, - connect: timeout, - secureConnect: timeout, - socket: timeout, // read timeout - response: timeout, - send: timeout, - request: operationTimeout, // whole operation timeout - }, - agent: { - http: httpAgent, - https: httpsAgent, - }, - http2: false, // default - retry: 0, - }).on('response', (res: Got.Response) => { - if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !config.proxy && res.ip) { - if (isPrivateIp(res.ip)) { - logger.warn(`Blocked address: ${res.ip}`); - req.destroy(); - } - } - - const contentLength = res.headers['content-length']; - if (contentLength != null) { - const size = Number(contentLength); - if (size > maxSize) { - logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`); - req.destroy(); - } - } - }).on('downloadProgress', (progress: Got.Progress) => { - if (progress.transferred > maxSize) { - logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`); - req.destroy(); - } - }); - - try { - await pipeline(req, fs.createWriteStream(path)); - } catch (e) { - if (e instanceof Got.HTTPError) { - throw new StatusError(`${e.response.statusCode} ${e.response.statusMessage}`, e.response.statusCode, e.response.statusMessage); - } else { - throw e; - } - } - - logger.succ(`Download finished: ${chalk.cyan(url)}`); -} - -function isPrivateIp(ip: string) { - for (const net of config.allowedPrivateNetworks || []) { - const cidr = new IPCIDR(net); - if (cidr.contains(ip)) { - return false; - } - } - - return PrivateIp(ip); -} diff --git a/src/misc/emoji-regex.ts b/src/misc/emoji-regex.ts deleted file mode 100644 index 8b07fbd8f2..0000000000 --- a/src/misc/emoji-regex.ts +++ /dev/null @@ -1,3 +0,0 @@ -const twemojiRegex = require('twemoji-parser/dist/lib/regex').default; - -export const emojiRegex = new RegExp(`(${twemojiRegex.source})`); diff --git a/src/misc/emojilist.ts b/src/misc/emojilist.ts deleted file mode 100644 index de7591f5a0..0000000000 --- a/src/misc/emojilist.ts +++ /dev/null @@ -1,7 +0,0 @@ -// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb -export const emojilist = require('../emojilist.json') as { - name: string; - keywords: string[]; - char: string; - category: 'people' | 'animals_and_nature' | 'food_and_drink' | 'activity' | 'travel_and_places' | 'objects' | 'symbols' | 'flags'; -}[]; diff --git a/src/misc/extract-custom-emojis-from-mfm.ts b/src/misc/extract-custom-emojis-from-mfm.ts deleted file mode 100644 index b29ce281b3..0000000000 --- a/src/misc/extract-custom-emojis-from-mfm.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as mfm from 'mfm-js'; -import { unique } from '@/prelude/array'; - -export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] { - const emojiNodes = mfm.extract(nodes, (node) => { - return (node.type === 'emojiCode' && node.props.name.length <= 100); - }); - - return unique(emojiNodes.map(x => x.props.name)); -} diff --git a/src/misc/extract-hashtags.ts b/src/misc/extract-hashtags.ts deleted file mode 100644 index b0a74df219..0000000000 --- a/src/misc/extract-hashtags.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as mfm from 'mfm-js'; -import { unique } from '@/prelude/array'; - -export function extractHashtags(nodes: mfm.MfmNode[]): string[] { - const hashtagNodes = mfm.extract(nodes, (node) => node.type === 'hashtag'); - const hashtags = unique(hashtagNodes.map(x => x.props.hashtag)); - - return hashtags; -} diff --git a/src/misc/extract-mentions.ts b/src/misc/extract-mentions.ts deleted file mode 100644 index cc19b161a8..0000000000 --- a/src/misc/extract-mentions.ts +++ /dev/null @@ -1,11 +0,0 @@ -// test is located in test/extract-mentions - -import * as mfm from 'mfm-js'; - -export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] { - // TODO: 重複を削除 - const mentionNodes = mfm.extract(nodes, (node) => node.type === 'mention'); - const mentions = mentionNodes.map(x => x.props); - - return mentions; -} diff --git a/src/misc/extract-url-from-mfm.ts b/src/misc/extract-url-from-mfm.ts deleted file mode 100644 index e6d5d0104a..0000000000 --- a/src/misc/extract-url-from-mfm.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as mfm from 'mfm-js'; -import { unique } from '@/prelude/array'; - -// unique without hash -// [ http://a/#1, http://a/#2, http://b/#3 ] => [ http://a/#1, http://b/#3 ] -const removeHash = (x: string) => x.replace(/#[^#]*$/, ''); - -export function extractUrlFromMfm(nodes: mfm.MfmNode[], respectSilentFlag = true): string[] { - const urlNodes = mfm.extract(nodes, (node) => { - return (node.type === 'url') || (node.type === 'link' && (!respectSilentFlag || !node.props.silent)); - }); - const urls: string[] = unique(urlNodes.map(x => x.props.url)); - - return urls.reduce((array, url) => { - const urlWithoutHash = removeHash(url); - if (!array.map(x => removeHash(x)).includes(urlWithoutHash)) array.push(url); - return array; - }, [] as string[]); -} diff --git a/src/misc/fetch-meta.ts b/src/misc/fetch-meta.ts deleted file mode 100644 index a0bcdd4d48..0000000000 --- a/src/misc/fetch-meta.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Meta } from '@/models/entities/meta'; -import { getConnection } from 'typeorm'; - -let cache: Meta; - -export async function fetchMeta(noCache = false): Promise<Meta> { - if (!noCache && cache) return cache; - - return await getConnection().transaction(async transactionalEntityManager => { - // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する - const meta = await transactionalEntityManager.findOne(Meta, { - order: { - id: 'DESC' - } - }); - - if (meta) { - cache = meta; - return meta; - } else { - const saved = await transactionalEntityManager.save(Meta, { - id: 'x' - }) as Meta; - - cache = saved; - return saved; - } - }); -} - -setInterval(() => { - fetchMeta(true).then(meta => { - cache = meta; - }); -}, 1000 * 10); diff --git a/src/misc/fetch-proxy-account.ts b/src/misc/fetch-proxy-account.ts deleted file mode 100644 index e0eedea4c8..0000000000 --- a/src/misc/fetch-proxy-account.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { fetchMeta } from './fetch-meta'; -import { ILocalUser } from '@/models/entities/user'; -import { Users } from '@/models/index'; - -export async function fetchProxyAccount(): Promise<ILocalUser | null> { - const meta = await fetchMeta(); - if (meta.proxyAccountId == null) return null; - return await Users.findOneOrFail(meta.proxyAccountId) as ILocalUser; -} diff --git a/src/misc/fetch.ts b/src/misc/fetch.ts deleted file mode 100644 index f4f16a27e2..0000000000 --- a/src/misc/fetch.ts +++ /dev/null @@ -1,141 +0,0 @@ -import * as http from 'http'; -import * as https from 'https'; -import CacheableLookup from 'cacheable-lookup'; -import fetch from 'node-fetch'; -import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; -import config from '@/config/index'; -import { URL } from 'url'; - -export async function getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record<string, string>) { - const res = await getResponse({ - url, - method: 'GET', - headers: Object.assign({ - 'User-Agent': config.userAgent, - Accept: accept - }, headers || {}), - timeout - }); - - return await res.json(); -} - -export async function getHtml(url: string, accept = 'text/html, */*', timeout = 10000, headers?: Record<string, string>) { - const res = await getResponse({ - url, - method: 'GET', - headers: Object.assign({ - 'User-Agent': config.userAgent, - Accept: accept - }, headers || {}), - timeout - }); - - return await res.text(); -} - -export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number }) { - const timeout = args?.timeout || 10 * 1000; - - const controller = new AbortController(); - setTimeout(() => { - controller.abort(); - }, timeout * 6); - - const res = await fetch(args.url, { - method: args.method, - headers: args.headers, - body: args.body, - timeout, - size: args?.size || 10 * 1024 * 1024, - agent: getAgentByUrl, - signal: controller.signal, - }); - - if (!res.ok) { - throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText); - } - - return res; -} - -const cache = new CacheableLookup({ - maxTtl: 3600, // 1hours - errorTtl: 30, // 30secs - lookup: false, // nativeのdns.lookupにfallbackしない -}); - -/** - * Get http non-proxy agent - */ -const _http = new http.Agent({ - keepAlive: true, - keepAliveMsecs: 30 * 1000, - lookup: cache.lookup, -} as http.AgentOptions); - -/** - * Get https non-proxy agent - */ -const _https = new https.Agent({ - keepAlive: true, - keepAliveMsecs: 30 * 1000, - lookup: cache.lookup, -} as https.AgentOptions); - -const maxSockets = Math.max(256, config.deliverJobConcurrency || 128); - -/** - * Get http proxy or non-proxy agent - */ -export const httpAgent = config.proxy - ? new HttpProxyAgent({ - keepAlive: true, - keepAliveMsecs: 30 * 1000, - maxSockets, - maxFreeSockets: 256, - scheduling: 'lifo', - proxy: config.proxy - }) - : _http; - -/** - * Get https proxy or non-proxy agent - */ -export const httpsAgent = config.proxy - ? new HttpsProxyAgent({ - keepAlive: true, - keepAliveMsecs: 30 * 1000, - maxSockets, - maxFreeSockets: 256, - scheduling: 'lifo', - proxy: config.proxy - }) - : _https; - -/** - * Get agent by URL - * @param url URL - * @param bypassProxy Allways bypass proxy - */ -export function getAgentByUrl(url: URL, bypassProxy = false) { - if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) { - return url.protocol == 'http:' ? _http : _https; - } else { - return url.protocol == 'http:' ? httpAgent : httpsAgent; - } -} - -export class StatusError extends Error { - public statusCode: number; - public statusMessage?: string; - public isClientError: boolean; - - constructor(message: string, statusCode: number, statusMessage?: string) { - super(message); - this.name = 'StatusError'; - this.statusCode = statusCode; - this.statusMessage = statusMessage; - this.isClientError = typeof this.statusCode === 'number' && this.statusCode >= 400 && this.statusCode < 500; - } -} diff --git a/src/misc/format-time-string.ts b/src/misc/format-time-string.ts deleted file mode 100644 index bfb2c397ae..0000000000 --- a/src/misc/format-time-string.ts +++ /dev/null @@ -1,50 +0,0 @@ -const defaultLocaleStringFormats: {[index: string]: string} = { - 'weekday': 'narrow', - 'era': 'narrow', - 'year': 'numeric', - 'month': 'numeric', - 'day': 'numeric', - 'hour': 'numeric', - 'minute': 'numeric', - 'second': 'numeric', - 'timeZoneName': 'short' -}; - -function formatLocaleString(date: Date, format: string): string { - return format.replace(/\{\{(\w+)(:(\w+))?\}\}/g, (match: string, kind: string, unused?, option?: string) => { - if (['weekday', 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName'].includes(kind)) { - return date.toLocaleString(window.navigator.language, {[kind]: option ? option : defaultLocaleStringFormats[kind]}); - } else { - return match; - } - }); -} - -export function formatDateTimeString(date: Date, format: string): string { - return format - .replace(/yyyy/g, date.getFullYear().toString()) - .replace(/yy/g, date.getFullYear().toString().slice(-2)) - .replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long'})) - .replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short'})) - .replace(/MM/g, (`0${date.getMonth() + 1}`).slice(-2)) - .replace(/M/g, (date.getMonth() + 1).toString()) - .replace(/dd/g, (`0${date.getDate()}`).slice(-2)) - .replace(/d/g, date.getDate().toString()) - .replace(/HH/g, (`0${date.getHours()}`).slice(-2)) - .replace(/H/g, date.getHours().toString()) - .replace(/hh/g, (`0${(date.getHours() % 12) || 12}`).slice(-2)) - .replace(/h/g, ((date.getHours() % 12) || 12).toString()) - .replace(/mm/g, (`0${date.getMinutes()}`).slice(-2)) - .replace(/m/g, date.getMinutes().toString()) - .replace(/ss/g, (`0${date.getSeconds()}`).slice(-2)) - .replace(/s/g, date.getSeconds().toString()) - .replace(/tt/g, date.getHours() >= 12 ? 'PM' : 'AM'); -} - -export function formatTimeString(date: Date, format: string): string { - return format.replace(/\[(([^\[]|\[\])*)\]|(([yMdHhmst])\4{0,3})/g, (match: string, localeformat?: string, unused?, datetimeformat?: string) => { - if (localeformat) return formatLocaleString(date, localeformat); - if (datetimeformat) return formatDateTimeString(date, datetimeformat); - return match; - }); -} diff --git a/src/misc/gen-avatar.ts b/src/misc/gen-avatar.ts deleted file mode 100644 index f03ca9f96d..0000000000 --- a/src/misc/gen-avatar.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Random avatar generator - */ - -import * as p from 'pureimage'; -import * as gen from 'random-seed'; -import { WriteStream } from 'fs'; - -const size = 256; // px -const n = 5; // resolution -const margin = (size / n); -const colors = [ - '#e57373', - '#F06292', - '#BA68C8', - '#9575CD', - '#7986CB', - '#64B5F6', - '#4FC3F7', - '#4DD0E1', - '#4DB6AC', - '#81C784', - '#8BC34A', - '#AFB42B', - '#F57F17', - '#FF5722', - '#795548', - '#455A64', -]; -const bg = '#e9e9e9'; - -const actualSize = size - (margin * 2); -const cellSize = actualSize / n; -const sideN = Math.floor(n / 2); - -/** - * Generate buffer of random avatar by seed - */ -export function genAvatar(seed: string, stream: WriteStream): Promise<void> { - const rand = gen.create(seed); - const canvas = p.make(size, size); - const ctx = canvas.getContext('2d'); - - ctx.fillStyle = bg; - ctx.beginPath(); - ctx.fillRect(0, 0, size, size); - - ctx.fillStyle = colors[rand(colors.length)]; - - // side bitmap (filled by false) - const side: boolean[][] = new Array(sideN); - for (let i = 0; i < side.length; i++) { - side[i] = new Array(n).fill(false); - } - - // 1*n (filled by false) - const center: boolean[] = new Array(n).fill(false); - - // tslint:disable-next-line:prefer-for-of - for (let x = 0; x < side.length; x++) { - for (let y = 0; y < side[x].length; y++) { - side[x][y] = rand(3) === 0; - } - } - - for (let i = 0; i < center.length; i++) { - center[i] = rand(3) === 0; - } - - // Draw - for (let x = 0; x < n; x++) { - for (let y = 0; y < n; y++) { - const isXCenter = x === ((n - 1) / 2); - if (isXCenter && !center[y]) continue; - - const isLeftSide = x < ((n - 1) / 2); - if (isLeftSide && !side[x][y]) continue; - - const isRightSide = x > ((n - 1) / 2); - if (isRightSide && !side[sideN - (x - sideN)][y]) continue; - - const actualX = margin + (cellSize * x); - const actualY = margin + (cellSize * y); - ctx.beginPath(); - ctx.fillRect(actualX, actualY, cellSize, cellSize); - } - } - - return p.encodePNGToStream(canvas, stream); -} diff --git a/src/misc/gen-id.ts b/src/misc/gen-id.ts deleted file mode 100644 index b1b542dc4b..0000000000 --- a/src/misc/gen-id.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ulid } from 'ulid'; -import { genAid } from './id/aid'; -import { genMeid } from './id/meid'; -import { genMeidg } from './id/meidg'; -import { genObjectId } from './id/object-id'; -import config from '@/config/index'; - -const metohd = config.id.toLowerCase(); - -export function genId(date?: Date): string { - if (!date || (date > new Date())) date = new Date(); - - switch (metohd) { - case 'aid': return genAid(date); - case 'meid': return genMeid(date); - case 'meidg': return genMeidg(date); - case 'ulid': return ulid(date.getTime()); - case 'objectid': return genObjectId(date); - default: throw new Error('unrecognized id generation method'); - } -} diff --git a/src/misc/gen-key-pair.ts b/src/misc/gen-key-pair.ts deleted file mode 100644 index d4a8fa7534..0000000000 --- a/src/misc/gen-key-pair.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as crypto from 'crypto'; -import * as util from 'util'; - -const generateKeyPair = util.promisify(crypto.generateKeyPair); - -export async function genRsaKeyPair(modulusLength = 2048) { - return await generateKeyPair('rsa', { - modulusLength, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - cipher: undefined, - passphrase: undefined - } - }); -} - -export async function genEcKeyPair(namedCurve: 'prime256v1' | 'secp384r1' | 'secp521r1' | 'curve25519' = 'prime256v1') { - return await generateKeyPair('ec', { - namedCurve, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - cipher: undefined, - passphrase: undefined - } - }); -} diff --git a/src/misc/get-file-info.ts b/src/misc/get-file-info.ts deleted file mode 100644 index 39ba541395..0000000000 --- a/src/misc/get-file-info.ts +++ /dev/null @@ -1,196 +0,0 @@ -import * as fs from 'fs'; -import * as crypto from 'crypto'; -import * as stream from 'stream'; -import * as util from 'util'; -import * as fileType from 'file-type'; -import isSvg from 'is-svg'; -import * as probeImageSize from 'probe-image-size'; -import * as sharp from 'sharp'; -import { encode } from 'blurhash'; - -const pipeline = util.promisify(stream.pipeline); - -export type FileInfo = { - size: number; - md5: string; - type: { - mime: string; - ext: string | null; - }; - width?: number; - height?: number; - blurhash?: string; - warnings: string[]; -}; - -const TYPE_OCTET_STREAM = { - mime: 'application/octet-stream', - ext: null -}; - -const TYPE_SVG = { - mime: 'image/svg+xml', - ext: 'svg' -}; - -/** - * Get file information - */ -export async function getFileInfo(path: string): Promise<FileInfo> { - const warnings = [] as string[]; - - const size = await getFileSize(path); - const md5 = await calcHash(path); - - let type = await detectType(path); - - // image dimensions - let width: number | undefined; - let height: number | undefined; - - if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) { - const imageSize = await detectImageSize(path).catch(e => { - warnings.push(`detectImageSize failed: ${e}`); - return undefined; - }); - - // うまく判定できない画像は octet-stream にする - if (!imageSize) { - warnings.push(`cannot detect image dimensions`); - type = TYPE_OCTET_STREAM; - } else if (imageSize.wUnits === 'px') { - width = imageSize.width; - height = imageSize.height; - - // 制限を超えている画像は octet-stream にする - if (imageSize.width > 16383 || imageSize.height > 16383) { - warnings.push(`image dimensions exceeds limits`); - type = TYPE_OCTET_STREAM; - } - } else { - warnings.push(`unsupported unit type: ${imageSize.wUnits}`); - } - } - - let blurhash: string | undefined; - - if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) { - blurhash = await getBlurhash(path).catch(e => { - warnings.push(`getBlurhash failed: ${e}`); - return undefined; - }); - } - - return { - size, - md5, - type, - width, - height, - blurhash, - warnings, - }; -} - -/** - * Detect MIME Type and extension - */ -export async function detectType(path: string) { - // Check 0 byte - const fileSize = await getFileSize(path); - if (fileSize === 0) { - return TYPE_OCTET_STREAM; - } - - const type = await fileType.fromFile(path); - - if (type) { - // XMLはSVGかもしれない - if (type.mime === 'application/xml' && await checkSvg(path)) { - return TYPE_SVG; - } - - return { - mime: type.mime, - ext: type.ext - }; - } - - // 種類が不明でもSVGかもしれない - if (await checkSvg(path)) { - return TYPE_SVG; - } - - // それでも種類が不明なら application/octet-stream にする - return TYPE_OCTET_STREAM; -} - -/** - * Check the file is SVG or not - */ -export async function checkSvg(path: string) { - try { - const size = await getFileSize(path); - if (size > 1 * 1024 * 1024) return false; - return isSvg(fs.readFileSync(path)); - } catch { - return false; - } -} - -/** - * Get file size - */ -export async function getFileSize(path: string): Promise<number> { - const getStat = util.promisify(fs.stat); - return (await getStat(path)).size; -} - -/** - * Calculate MD5 hash - */ -async function calcHash(path: string): Promise<string> { - const hash = crypto.createHash('md5').setEncoding('hex'); - await pipeline(fs.createReadStream(path), hash); - return hash.read(); -} - -/** - * Detect dimensions of image - */ -async function detectImageSize(path: string): Promise<{ - width: number; - height: number; - wUnits: string; - hUnits: string; -}> { - const readable = fs.createReadStream(path); - const imageSize = await probeImageSize(readable); - readable.destroy(); - return imageSize; -} - -/** - * Calculate average color of image - */ -function getBlurhash(path: string): Promise<string> { - return new Promise((resolve, reject) => { - sharp(path) - .raw() - .ensureAlpha() - .resize(64, 64, { fit: 'inside' }) - .toBuffer((err, buffer, { width, height }) => { - if (err) return reject(err); - - let hash; - - try { - hash = encode(new Uint8ClampedArray(buffer), width, height, 7, 7); - } catch (e) { - return reject(e); - } - - resolve(hash); - }); - }); -} diff --git a/src/misc/get-note-summary.ts b/src/misc/get-note-summary.ts deleted file mode 100644 index a1866fb209..0000000000 --- a/src/misc/get-note-summary.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 投稿を表す文字列を取得します。 - * @param {*} note (packされた)投稿 - */ -export const getNoteSummary = (note: any, locale: any): string => { - if (note.deletedAt) { - return `(${locale['deletedNote']})`; - } - - if (note.isHidden) { - return `(${locale['invisibleNote']})`; - } - - let summary = ''; - - // 本文 - if (note.cw != null) { - summary += note.cw; - } else { - summary += note.text ? note.text : ''; - } - - // ファイルが添付されているとき - if ((note.files || []).length != 0) { - summary += ` (${locale['withNFiles'].replace('{n}', note.files.length)})`; - } - - // 投票が添付されているとき - if (note.poll) { - summary += ` (${locale['poll']})`; - } - - // 返信のとき - if (note.replyId) { - if (note.reply) { - summary += `\n\nRE: ${getNoteSummary(note.reply, locale)}`; - } else { - summary += '\n\nRE: ...'; - } - } - - // Renoteのとき - if (note.renoteId) { - if (note.renote) { - summary += `\n\nRN: ${getNoteSummary(note.renote, locale)}`; - } else { - summary += '\n\nRN: ...'; - } - } - - return summary.trim(); -}; diff --git a/src/misc/get-reaction-emoji.ts b/src/misc/get-reaction-emoji.ts deleted file mode 100644 index c2e0b98582..0000000000 --- a/src/misc/get-reaction-emoji.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default function(reaction: string): string { - switch (reaction) { - case 'like': return '👍'; - case 'love': return '❤️'; - case 'laugh': return '😆'; - case 'hmm': return '🤔'; - case 'surprise': return '😮'; - case 'congrats': return '🎉'; - case 'angry': return '💢'; - case 'confused': return '😥'; - case 'rip': return '😇'; - case 'pudding': return '🍮'; - case 'star': return '⭐'; - default: return reaction; - } -} diff --git a/src/misc/get-user-name.ts b/src/misc/get-user-name.ts deleted file mode 100644 index 3545e986e8..0000000000 --- a/src/misc/get-user-name.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { User } from '@/models/entities/user'; - -export default function(user: User): string { - return user.name || user.username; -} diff --git a/src/misc/hard-limits.ts b/src/misc/hard-limits.ts deleted file mode 100644 index 1039f7335a..0000000000 --- a/src/misc/hard-limits.ts +++ /dev/null @@ -1,14 +0,0 @@ - -// If you change DB_* values, you must also change the DB schema. - -/** - * Maximum note text length that can be stored in DB. - * Surrogate pairs count as one - */ -export const DB_MAX_NOTE_TEXT_LENGTH = 8192; - -/** - * Maximum image description length that can be stored in DB. - * Surrogate pairs count as one - */ -export const DB_MAX_IMAGE_COMMENT_LENGTH = 512; diff --git a/src/misc/i18n.ts b/src/misc/i18n.ts deleted file mode 100644 index 4fa398763a..0000000000 --- a/src/misc/i18n.ts +++ /dev/null @@ -1,29 +0,0 @@ -export class I18n<T extends Record<string, any>> { - public locale: T; - - constructor(locale: T) { - this.locale = locale; - - //#region BIND - this.t = this.t.bind(this); - //#endregion - } - - // string にしているのは、ドット区切りでのパス指定を許可するため - // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも - public t(key: string, args?: Record<string, any>): string { - try { - let str = key.split('.').reduce((o, i) => o[i], this.locale) as string; - - if (args) { - for (const [k, v] of Object.entries(args)) { - str = str.replace(`{${k}}`, v); - } - } - return str; - } catch (e) { - console.warn(`missing localization '${key}'`); - return key; - } - } -} diff --git a/src/misc/id/aid.ts b/src/misc/id/aid.ts deleted file mode 100644 index 2bcde90bff..0000000000 --- a/src/misc/id/aid.ts +++ /dev/null @@ -1,25 +0,0 @@ -// AID -// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列] - -import * as crypto from 'crypto'; - -const TIME2000 = 946684800000; -let counter = crypto.randomBytes(2).readUInt16LE(0); - -function getTime(time: number) { - time = time - TIME2000; - if (time < 0) time = 0; - - return time.toString(36).padStart(8, '0'); -} - -function getNoise() { - return counter.toString(36).padStart(2, '0').slice(-2); -} - -export function genAid(date: Date): string { - const t = date.getTime(); - if (isNaN(t)) throw 'Failed to create AID: Invalid Date'; - counter++; - return getTime(t) + getNoise(); -} diff --git a/src/misc/id/meid.ts b/src/misc/id/meid.ts deleted file mode 100644 index 30bbdf1698..0000000000 --- a/src/misc/id/meid.ts +++ /dev/null @@ -1,26 +0,0 @@ -const CHARS = '0123456789abcdef'; - -function getTime(time: number) { - if (time < 0) time = 0; - if (time === 0) { - return CHARS[0]; - } - - time += 0x800000000000; - - return time.toString(16).padStart(12, CHARS[0]); -} - -function getRandom() { - let str = ''; - - for (let i = 0; i < 12; i++) { - str += CHARS[Math.floor(Math.random() * CHARS.length)]; - } - - return str; -} - -export function genMeid(date: Date): string { - return getTime(date.getTime()) + getRandom(); -} diff --git a/src/misc/id/meidg.ts b/src/misc/id/meidg.ts deleted file mode 100644 index d4aaaea1ba..0000000000 --- a/src/misc/id/meidg.ts +++ /dev/null @@ -1,28 +0,0 @@ -const CHARS = '0123456789abcdef'; - -// 4bit Fixed hex value 'g' -// 44bit UNIX Time ms in Hex -// 48bit Random value in Hex - -function getTime(time: number) { - if (time < 0) time = 0; - if (time === 0) { - return CHARS[0]; - } - - return time.toString(16).padStart(11, CHARS[0]); -} - -function getRandom() { - let str = ''; - - for (let i = 0; i < 12; i++) { - str += CHARS[Math.floor(Math.random() * CHARS.length)]; - } - - return str; -} - -export function genMeidg(date: Date): string { - return 'g' + getTime(date.getTime()) + getRandom(); -} diff --git a/src/misc/id/object-id.ts b/src/misc/id/object-id.ts deleted file mode 100644 index 392ea43301..0000000000 --- a/src/misc/id/object-id.ts +++ /dev/null @@ -1,26 +0,0 @@ -const CHARS = '0123456789abcdef'; - -function getTime(time: number) { - if (time < 0) time = 0; - if (time === 0) { - return CHARS[0]; - } - - time = Math.floor(time / 1000); - - return time.toString(16).padStart(8, CHARS[0]); -} - -function getRandom() { - let str = ''; - - for (let i = 0; i < 16; i++) { - str += CHARS[Math.floor(Math.random() * CHARS.length)]; - } - - return str; -} - -export function genObjectId(date: Date): string { - return getTime(date.getTime()) + getRandom(); -} diff --git a/src/misc/identifiable-error.ts b/src/misc/identifiable-error.ts deleted file mode 100644 index 2d7c6bd0c6..0000000000 --- a/src/misc/identifiable-error.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * ID付きエラー - */ -export class IdentifiableError extends Error { - public message: string; - public id: string; - - constructor(id: string, message?: string) { - super(message); - this.message = message || ''; - this.id = id; - } -} diff --git a/src/misc/is-blocker-user-related.ts b/src/misc/is-blocker-user-related.ts deleted file mode 100644 index 8c0ebfad9b..0000000000 --- a/src/misc/is-blocker-user-related.ts +++ /dev/null @@ -1,15 +0,0 @@ -export function isBlockerUserRelated(note: any, blockerUserIds: Set<string>): boolean { - if (blockerUserIds.has(note.userId)) { - return true; - } - - if (note.reply != null && blockerUserIds.has(note.reply.userId)) { - return true; - } - - if (note.renote != null && blockerUserIds.has(note.renote.userId)) { - return true; - } - - return false; -} diff --git a/src/misc/is-duplicate-key-value-error.ts b/src/misc/is-duplicate-key-value-error.ts deleted file mode 100644 index 23d8ceb1b7..0000000000 --- a/src/misc/is-duplicate-key-value-error.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isDuplicateKeyValueError(e: Error): boolean { - return e.message.startsWith('duplicate key value'); -} diff --git a/src/misc/is-muted-user-related.ts b/src/misc/is-muted-user-related.ts deleted file mode 100644 index 2caa743f95..0000000000 --- a/src/misc/is-muted-user-related.ts +++ /dev/null @@ -1,15 +0,0 @@ -export function isMutedUserRelated(note: any, mutedUserIds: Set<string>): boolean { - if (mutedUserIds.has(note.userId)) { - return true; - } - - if (note.reply != null && mutedUserIds.has(note.reply.userId)) { - return true; - } - - if (note.renote != null && mutedUserIds.has(note.renote.userId)) { - return true; - } - - return false; -} diff --git a/src/misc/is-quote.ts b/src/misc/is-quote.ts deleted file mode 100644 index 2b57f036a2..0000000000 --- a/src/misc/is-quote.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Note } from '@/models/entities/note'; - -export default function(note: Note): boolean { - return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0)); -} diff --git a/src/misc/keypair-store.ts b/src/misc/keypair-store.ts deleted file mode 100644 index c018013b7b..0000000000 --- a/src/misc/keypair-store.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { UserKeypairs } from '@/models/index'; -import { User } from '@/models/entities/user'; -import { UserKeypair } from '@/models/entities/user-keypair'; -import { Cache } from './cache'; - -const cache = new Cache<UserKeypair>(Infinity); - -export async function getUserKeypair(userId: User['id']): Promise<UserKeypair> { - return await cache.fetch(userId, () => UserKeypairs.findOneOrFail(userId)); -} diff --git a/src/misc/license.ts b/src/misc/license.ts deleted file mode 100644 index 8b12923ca1..0000000000 --- a/src/misc/license.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as fs from 'fs'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; - -//const _filename = fileURLToPath(import.meta.url); -const _filename = __filename; -const _dirname = dirname(_filename); - -const license = fs.readFileSync(_dirname + '/../../LICENSE', 'utf-8'); - -const licenseHtml = license - .replace(/\r\n/g, '\n') - .replace(/(.)\n(.)/g, '$1 $2') - .replace(/(^|\n)(.*?)($|\n)/g, '<p>$2</p>'); - -export { - license, - licenseHtml -}; diff --git a/src/misc/normalize-for-search.ts b/src/misc/normalize-for-search.ts deleted file mode 100644 index 200540566e..0000000000 --- a/src/misc/normalize-for-search.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function normalizeForSearch(tag: string): string { - // ref. - // - https://analytics-note.xyz/programming/unicode-normalization-forms/ - // - https://maku77.github.io/js/string/normalize.html - return tag.normalize('NFKC').toLowerCase(); -} diff --git a/src/misc/nyaize.ts b/src/misc/nyaize.ts deleted file mode 100644 index 500d1db2cb..0000000000 --- a/src/misc/nyaize.ts +++ /dev/null @@ -1,15 +0,0 @@ -export function nyaize(text: string): string { - return text - // ja-JP - .replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ') - // en-US - .replace(/(?<=n)a/gi, x => x === 'A' ? 'YA' : 'ya') - .replace(/(?<=morn)ing/gi, x => x === 'ING' ? 'YAN' : 'yan') - .replace(/(?<=every)one/gi, x => x === 'ONE' ? 'NYAN' : 'nyan') - // ko-KR - .replace(/[나-낳]/g, match => String.fromCharCode( - match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0) - )) - .replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥') - .replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥'); -} diff --git a/src/misc/populate-emojis.ts b/src/misc/populate-emojis.ts deleted file mode 100644 index f0a8bde31e..0000000000 --- a/src/misc/populate-emojis.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { In } from 'typeorm'; -import { Emojis } from '@/models/index'; -import { Emoji } from '@/models/entities/emoji'; -import { Note } from '@/models/entities/note'; -import { Cache } from './cache'; -import { isSelfHost, toPunyNullable } from './convert-host'; -import { decodeReaction } from './reaction-lib'; -import config from '@/config/index'; -import { query } from '@/prelude/url'; - -const cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12); - -/** - * 添付用絵文字情報 - */ -type PopulatedEmoji = { - name: string; - url: string; -}; - -function normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { - // クエリに使うホスト - let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ) - : src === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない) - : isSelfHost(src) ? null // 自ホスト指定 - : (src || noteUserHost); // 指定されたホスト || ノートなどの所有者のホスト (こっちがリアクションにマッチすることはない) - - host = toPunyNullable(host); - - return host; -} - -function parseEmojiStr(emojiName: string, noteUserHost: string | null) { - const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); - if (!match) return { name: null, host: null }; - - const name = match[1]; - - // ホスト正規化 - const host = toPunyNullable(normalizeHost(match[2], noteUserHost)); - - return { name, host }; -} - -/** - * 添付用絵文字情報を解決する - * @param emojiName ノートやユーザープロフィールに添付された、またはリアクションのカスタム絵文字名 (:は含めない, リアクションでローカルホストの場合は@.を付ける (これはdecodeReactionで可能)) - * @param noteUserHost ノートやユーザープロフィールの所有者のホスト - * @returns 絵文字情報, nullは未マッチを意味する - */ -export async function populateEmoji(emojiName: string, noteUserHost: string | null): Promise<PopulatedEmoji | null> { - const { name, host } = parseEmojiStr(emojiName, noteUserHost); - if (name == null) return null; - - const queryOrNull = async () => (await Emojis.findOne({ - name, - host - })) || null; - - const emoji = await cache.fetch(`${name} ${host}`, queryOrNull); - - if (emoji == null) return null; - - const isLocal = emoji.host == null; - const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({url: emoji.url})}`; - - return { - name: emojiName, - url, - }; -} - -/** - * 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される) - */ -export async function populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<PopulatedEmoji[]> { - const emojis = await Promise.all(emojiNames.map(x => populateEmoji(x, noteUserHost))); - return emojis.filter((x): x is PopulatedEmoji => x != null); -} - -export function aggregateNoteEmojis(notes: Note[]) { - let emojis: { name: string | null; host: string | null; }[] = []; - for (const note of notes) { - emojis = emojis.concat(note.emojis - .map(e => parseEmojiStr(e, note.userHost))); - if (note.renote) { - emojis = emojis.concat(note.renote.emojis - .map(e => parseEmojiStr(e, note.renote!.userHost))); - if (note.renote.user) { - emojis = emojis.concat(note.renote.user.emojis - .map(e => parseEmojiStr(e, note.renote!.userHost))); - } - } - const customReactions = Object.keys(note.reactions).map(x => decodeReaction(x)).filter(x => x.name != null) as typeof emojis; - emojis = emojis.concat(customReactions); - if (note.user) { - emojis = emojis.concat(note.user.emojis - .map(e => parseEmojiStr(e, note.userHost))); - } - } - return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[]; -} - -/** - * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します - */ -export async function prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> { - const notCachedEmojis = emojis.filter(emoji => cache.get(`${emoji.name} ${emoji.host}`) == null); - const emojisQuery: any[] = []; - const hosts = new Set(notCachedEmojis.map(e => e.host)); - for (const host of hosts) { - emojisQuery.push({ - name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)), - host: host - }); - } - const _emojis = emojisQuery.length > 0 ? await Emojis.find({ - where: emojisQuery, - select: ['name', 'host', 'url'] - }) : []; - for (const emoji of _emojis) { - cache.set(`${emoji.name} ${emoji.host}`, emoji); - } -} diff --git a/src/misc/reaction-lib.ts b/src/misc/reaction-lib.ts deleted file mode 100644 index 46dedfa24b..0000000000 --- a/src/misc/reaction-lib.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { emojiRegex } from './emoji-regex'; -import { fetchMeta } from './fetch-meta'; -import { Emojis } from '@/models/index'; -import { toPunyNullable } from './convert-host'; - -const legacies: Record<string, string> = { - 'like': '👍', - 'love': '❤', // ここに記述する場合は異体字セレクタを入れない - 'laugh': '😆', - 'hmm': '🤔', - 'surprise': '😮', - 'congrats': '🎉', - 'angry': '💢', - 'confused': '😥', - 'rip': '😇', - 'pudding': '🍮', - 'star': '⭐', -}; - -export async function getFallbackReaction(): Promise<string> { - const meta = await fetchMeta(); - return meta.useStarForReactionFallback ? '⭐' : '👍'; -} - -export function convertLegacyReactions(reactions: Record<string, number>) { - const _reactions = {} as Record<string, number>; - - for (const reaction of Object.keys(reactions)) { - if (reactions[reaction] <= 0) continue; - - if (Object.keys(legacies).includes(reaction)) { - if (_reactions[legacies[reaction]]) { - _reactions[legacies[reaction]] += reactions[reaction]; - } else { - _reactions[legacies[reaction]] = reactions[reaction]; - } - } else { - if (_reactions[reaction]) { - _reactions[reaction] += reactions[reaction]; - } else { - _reactions[reaction] = reactions[reaction]; - } - } - } - - const _reactions2 = {} as Record<string, number>; - - for (const reaction of Object.keys(_reactions)) { - _reactions2[decodeReaction(reaction).reaction] = _reactions[reaction]; - } - - return _reactions2; -} - -export async function toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> { - if (reaction == null) return await getFallbackReaction(); - - reacterHost = toPunyNullable(reacterHost); - - // 文字列タイプのリアクションを絵文字に変換 - if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; - - // Unicode絵文字 - const match = emojiRegex.exec(reaction); - if (match) { - // 合字を含む1つの絵文字 - const unicode = match[0]; - - // 異体字セレクタ除去 - return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, ''); - } - - const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/); - if (custom) { - const name = custom[1]; - const emoji = await Emojis.findOne({ - host: reacterHost || null, - name, - }); - - if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`; - } - - return await getFallbackReaction(); -} - -type DecodedReaction = { - /** - * リアクション名 (Unicode Emoji or ':name@hostname' or ':name@.') - */ - reaction: string; - - /** - * name (カスタム絵文字の場合name, Emojiクエリに使う) - */ - name?: string; - - /** - * host (カスタム絵文字の場合host, Emojiクエリに使う) - */ - host?: string | null; -}; - -export function decodeReaction(str: string): DecodedReaction { - const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/); - - if (custom) { - const name = custom[1]; - const host = custom[2] || null; - - return { - reaction: `:${name}@${host || '.'}:`, // ローカル分は@以降を省略するのではなく.にする - name, - host - }; - } - - return { - reaction: str, - name: undefined, - host: undefined - }; -} - -export function convertLegacyReaction(reaction: string): string { - reaction = decodeReaction(reaction).reaction; - if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; - return reaction; -} diff --git a/src/misc/safe-for-sql.ts b/src/misc/safe-for-sql.ts deleted file mode 100644 index 02eb7f0a26..0000000000 --- a/src/misc/safe-for-sql.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function safeForSql(text: string): boolean { - return !/[\0\x08\x09\x1a\n\r"'\\\%]/g.test(text); -} diff --git a/src/misc/schema.ts b/src/misc/schema.ts deleted file mode 100644 index 4131875ef7..0000000000 --- a/src/misc/schema.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { SimpleObj, SimpleSchema } from './simple-schema'; -import { packedUserSchema } from '@/models/repositories/user'; -import { packedNoteSchema } from '@/models/repositories/note'; -import { packedUserListSchema } from '@/models/repositories/user-list'; -import { packedAppSchema } from '@/models/repositories/app'; -import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message'; -import { packedNotificationSchema } from '@/models/repositories/notification'; -import { packedDriveFileSchema } from '@/models/repositories/drive-file'; -import { packedDriveFolderSchema } from '@/models/repositories/drive-folder'; -import { packedFollowingSchema } from '@/models/repositories/following'; -import { packedMutingSchema } from '@/models/repositories/muting'; -import { packedBlockingSchema } from '@/models/repositories/blocking'; -import { packedNoteReactionSchema } from '@/models/repositories/note-reaction'; -import { packedHashtagSchema } from '@/models/repositories/hashtag'; -import { packedPageSchema } from '@/models/repositories/page'; -import { packedUserGroupSchema } from '@/models/repositories/user-group'; -import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite'; -import { packedChannelSchema } from '@/models/repositories/channel'; -import { packedAntennaSchema } from '@/models/repositories/antenna'; -import { packedClipSchema } from '@/models/repositories/clip'; -import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance'; -import { packedQueueCountSchema } from '@/models/repositories/queue'; -import { packedGalleryPostSchema } from '@/models/repositories/gallery-post'; -import { packedEmojiSchema } from '@/models/repositories/emoji'; -import { packedReversiGameSchema } from '@/models/repositories/games/reversi/game'; -import { packedReversiMatchingSchema } from '@/models/repositories/games/reversi/matching'; - -export const refs = { - User: packedUserSchema, - UserList: packedUserListSchema, - UserGroup: packedUserGroupSchema, - App: packedAppSchema, - MessagingMessage: packedMessagingMessageSchema, - Note: packedNoteSchema, - NoteReaction: packedNoteReactionSchema, - NoteFavorite: packedNoteFavoriteSchema, - Notification: packedNotificationSchema, - DriveFile: packedDriveFileSchema, - DriveFolder: packedDriveFolderSchema, - Following: packedFollowingSchema, - Muting: packedMutingSchema, - Blocking: packedBlockingSchema, - Hashtag: packedHashtagSchema, - Page: packedPageSchema, - Channel: packedChannelSchema, - QueueCount: packedQueueCountSchema, - Antenna: packedAntennaSchema, - Clip: packedClipSchema, - FederationInstance: packedFederationInstanceSchema, - GalleryPost: packedGalleryPostSchema, - Emoji: packedEmojiSchema, - ReversiGame: packedReversiGameSchema, - ReversiMatching: packedReversiMatchingSchema, -}; - -export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>; - -export interface Schema extends SimpleSchema { - items?: Schema; - properties?: Obj; - ref?: keyof typeof refs; -} - -type NonUndefinedPropertyNames<T extends Obj> = { - [K in keyof T]: T[K]['optional'] extends true ? never : K -}[keyof T]; - -type UndefinedPropertyNames<T extends Obj> = { - [K in keyof T]: T[K]['optional'] extends true ? K : never -}[keyof T]; - -type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>; -type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>; - -export interface Obj extends SimpleObj { [key: string]: Schema; } - -export type ObjType<s extends Obj> = - { [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } & - { [P in keyof OnlyRequired<s>]: SchemaType<s[P]> }; - -// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2 -type MyType<T extends Schema> = { - 0: any; - 1: SchemaType<T>; -}[T extends Schema ? 1 : 0]; - -type NullOrUndefined<p extends Schema, T> = - p['nullable'] extends true - ? p['optional'] extends true - ? (T | null | undefined) - : (T | null) - : p['optional'] extends true - ? (T | undefined) - : T; - -export type SchemaType<p extends Schema> = - p['type'] extends 'number' ? NullOrUndefined<p, number> : - p['type'] extends 'string' ? NullOrUndefined<p, string> : - p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> : - p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> : - p['type'] extends 'object' ? ( - p['ref'] extends keyof typeof refs - ? NullOrUndefined<p, Packed<p['ref']>> - : NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> - ) : - p['type'] extends 'any' ? NullOrUndefined<p, any> : - any; diff --git a/src/misc/secure-rndstr.ts b/src/misc/secure-rndstr.ts deleted file mode 100644 index 76ee1225eb..0000000000 --- a/src/misc/secure-rndstr.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as crypto from 'crypto'; - -const L_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'; -const LU_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - -export function secureRndstr(length = 32, useLU = true): string { - const chars = useLU ? LU_CHARS : L_CHARS; - const chars_len = chars.length; - - let str = ''; - - for (let i = 0; i < length; i++) { - let rand = Math.floor((crypto.randomBytes(1).readUInt8(0) / 0xFF) * chars_len); - if (rand === chars_len) { - rand = chars_len - 1; - } - str += chars.charAt(rand); - } - - return str; -} diff --git a/src/misc/show-machine-info.ts b/src/misc/show-machine-info.ts deleted file mode 100644 index 58747c1152..0000000000 --- a/src/misc/show-machine-info.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as os from 'os'; -import * as sysUtils from 'systeminformation'; -import Logger from '@/services/logger'; - -export async function showMachineInfo(parentLogger: Logger) { - const logger = parentLogger.createSubLogger('machine'); - logger.debug(`Hostname: ${os.hostname()}`); - logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`); - const mem = await sysUtils.mem(); - const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1); - const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1); - logger.debug(`CPU: ${os.cpus().length} core MEM: ${totalmem}GB (available: ${availmem}GB)`); -} diff --git a/src/misc/simple-schema.ts b/src/misc/simple-schema.ts deleted file mode 100644 index abbb348e24..0000000000 --- a/src/misc/simple-schema.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface SimpleSchema { - type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; - nullable: boolean; - optional: boolean; - items?: SimpleSchema; - properties?: SimpleObj; - description?: string; - example?: any; - format?: string; - ref?: string; - enum?: string[]; - default?: boolean | null; -} - -export interface SimpleObj { [key: string]: SimpleSchema; } diff --git a/src/misc/truncate.ts b/src/misc/truncate.ts deleted file mode 100644 index cb120331a1..0000000000 --- a/src/misc/truncate.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { substring } from 'stringz'; - -export function truncate(input: string, size: number): string; -export function truncate(input: string | undefined, size: number): string | undefined; -export function truncate(input: string | undefined, size: number): string | undefined { - if (!input) { - return input; - } else { - return substring(input, 0, size); - } -} diff --git a/src/misc/twemoji-base.ts b/src/misc/twemoji-base.ts deleted file mode 100644 index cd50311b15..0000000000 --- a/src/misc/twemoji-base.ts +++ /dev/null @@ -1 +0,0 @@ -export const twemojiSvgBase = '/twemoji'; |