chore: formatting

This commit is contained in:
ThatOneCalculator 2023-06-05 16:40:48 -07:00
parent 5b73351e80
commit 36283b9a35
No known key found for this signature in database
GPG Key ID: 8703CACD01000000
31 changed files with 366 additions and 300 deletions

View File

@ -1,12 +1,12 @@
import { defineConfig } from 'cypress' import { defineConfig } from "cypress";
export default defineConfig({ export default defineConfig({
e2e: { e2e: {
// We've imported your old cypress plugins here. // We've imported your old cypress plugins here.
// You may want to clean this up later by importing these. // You may want to clean this up later by importing these.
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config) return require("./cypress/plugins/index.js")(on, config);
}, },
baseUrl: 'http://localhost:61812', baseUrl: "http://localhost:61812",
}, },
}) });

View File

@ -1,4 +1,4 @@
describe('Before setup instance', () => { describe("Before setup instance", () => {
beforeEach(() => { beforeEach(() => {
cy.resetState(); cy.resetState();
}); });
@ -9,31 +9,31 @@ describe('Before setup instance', () => {
cy.wait(1000); cy.wait(1000);
}); });
it('successfully loads', () => { it("successfully loads", () => {
cy.visit('/'); cy.visit("/");
}); });
it('setup instance', () => { it("setup instance", () => {
cy.visit('/'); cy.visit("/");
cy.intercept('POST', '/api/admin/accounts/create').as('signup'); cy.intercept("POST", "/api/admin/accounts/create").as("signup");
cy.get('[data-cy-admin-username] input').type('admin'); cy.get("[data-cy-admin-username] input").type("admin");
cy.get('[data-cy-admin-password] input').type('admin1234'); cy.get("[data-cy-admin-password] input").type("admin1234");
cy.get('[data-cy-admin-ok]').click(); cy.get("[data-cy-admin-ok]").click();
// なぜか動かない // なぜか動かない
//cy.wait('@signup').should('have.property', 'response.statusCode'); //cy.wait('@signup').should('have.property', 'response.statusCode');
cy.wait('@signup'); cy.wait("@signup");
}); });
}); });
describe('After setup instance', () => { describe("After setup instance", () => {
beforeEach(() => { beforeEach(() => {
cy.resetState(); cy.resetState();
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.registerUser('admin', 'pass', true); cy.registerUser("admin", "pass", true);
}); });
afterEach(() => { afterEach(() => {
@ -42,34 +42,34 @@ describe('After setup instance', () => {
cy.wait(1000); cy.wait(1000);
}); });
it('successfully loads', () => { it("successfully loads", () => {
cy.visit('/'); cy.visit("/");
}); });
it('signup', () => { it("signup", () => {
cy.visit('/'); cy.visit("/");
cy.intercept('POST', '/api/signup').as('signup'); cy.intercept("POST", "/api/signup").as("signup");
cy.get('[data-cy-signup]').click(); cy.get("[data-cy-signup]").click();
cy.get('[data-cy-signup-username] input').type('alice'); cy.get("[data-cy-signup-username] input").type("alice");
cy.get('[data-cy-signup-password] input').type('alice1234'); cy.get("[data-cy-signup-password] input").type("alice1234");
cy.get('[data-cy-signup-password-retype] input').type('alice1234'); cy.get("[data-cy-signup-password-retype] input").type("alice1234");
cy.get('[data-cy-signup-submit]').click(); cy.get("[data-cy-signup-submit]").click();
cy.wait('@signup'); cy.wait("@signup");
}); });
}); });
describe('After user signup', () => { describe("After user signup", () => {
beforeEach(() => { beforeEach(() => {
cy.resetState(); cy.resetState();
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.registerUser('admin', 'pass', true); cy.registerUser("admin", "pass", true);
// ユーザー作成 // ユーザー作成
cy.registerUser('alice', 'alice1234'); cy.registerUser("alice", "alice1234");
}); });
afterEach(() => { afterEach(() => {
@ -78,51 +78,53 @@ describe('After user signup', () => {
cy.wait(1000); cy.wait(1000);
}); });
it('successfully loads', () => { it("successfully loads", () => {
cy.visit('/'); cy.visit("/");
}); });
it('signin', () => { it("signin", () => {
cy.visit('/'); cy.visit("/");
cy.intercept('POST', '/api/signin').as('signin'); cy.intercept("POST", "/api/signin").as("signin");
cy.get('[data-cy-signin]').click(); cy.get("[data-cy-signin]").click();
cy.get('[data-cy-signin-username] input').type('alice'); cy.get("[data-cy-signin-username] input").type("alice");
// Enterキーでサインインできるかの確認も兼ねる // Enterキーでサインインできるかの確認も兼ねる
cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); cy.get("[data-cy-signin-password] input").type("alice1234{enter}");
cy.wait('@signin'); cy.wait("@signin");
}); });
it('suspend', function() { it("suspend", function () {
cy.request('POST', '/api/admin/suspend-user', { cy.request("POST", "/api/admin/suspend-user", {
i: this.admin.token, i: this.admin.token,
userId: this.alice.id, userId: this.alice.id,
}); });
cy.visit('/'); cy.visit("/");
cy.get('[data-cy-signin]').click(); cy.get("[data-cy-signin]").click();
cy.get('[data-cy-signin-username] input').type('alice'); cy.get("[data-cy-signin-username] input").type("alice");
cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); cy.get("[data-cy-signin-password] input").type("alice1234{enter}");
// TODO: cypressにブラウザの言語指定できる機能が実装され次第英語のみテストするようにする // TODO: cypressにブラウザの言語指定できる機能が実装され次第英語のみテストするようにする
cy.contains(/アカウントが凍結されています|This account has been suspended due to/gi); cy.contains(
/アカウントが凍結されています|This account has been suspended due to/gi,
);
}); });
}); });
describe('After user singed in', () => { describe("After user singed in", () => {
beforeEach(() => { beforeEach(() => {
cy.resetState(); cy.resetState();
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.registerUser('admin', 'pass', true); cy.registerUser("admin", "pass", true);
// ユーザー作成 // ユーザー作成
cy.registerUser('alice', 'alice1234'); cy.registerUser("alice", "alice1234");
cy.login('alice', 'alice1234'); cy.login("alice", "alice1234");
}); });
afterEach(() => { afterEach(() => {
@ -131,17 +133,17 @@ describe('After user singed in', () => {
cy.wait(1000); cy.wait(1000);
}); });
it('successfully loads', () => { it("successfully loads", () => {
cy.get('[data-cy-open-post-form]').should('be.visible'); cy.get("[data-cy-open-post-form]").should("be.visible");
}); });
it('note', () => { it("note", () => {
cy.get('[data-cy-open-post-form]').click(); cy.get("[data-cy-open-post-form]").click();
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!'); cy.get("[data-cy-post-form-text]").type("Hello, Misskey!");
cy.get('[data-cy-open-post-form-submit]').click(); cy.get("[data-cy-open-post-form-submit]").click();
cy.contains('Hello, Misskey!'); cy.contains("Hello, Misskey!");
}); });
}); });
// TODO: 投稿フォームの公開範囲指定のテスト // TODO: 投稿フォームの公開範囲指定のテスト

View File

@ -1,14 +1,14 @@
describe('After user signed in', () => { describe("After user signed in", () => {
beforeEach(() => { beforeEach(() => {
cy.resetState(); cy.resetState();
cy.viewport('macbook-16'); cy.viewport("macbook-16");
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.registerUser('admin', 'pass', true); cy.registerUser("admin", "pass", true);
// ユーザー作成 // ユーザー作成
cy.registerUser('alice', 'alice1234'); cy.registerUser("alice", "alice1234");
cy.login('alice', 'alice1234'); cy.login("alice", "alice1234");
}); });
afterEach(() => { afterEach(() => {
@ -17,47 +17,47 @@ describe('After user signed in', () => {
cy.wait(1000); cy.wait(1000);
}); });
it('widget edit toggle is visible', () => { it("widget edit toggle is visible", () => {
cy.get('.mk-widget-edit').should('be.visible'); cy.get(".mk-widget-edit").should("be.visible");
}); });
it('widget select should be visible in edit mode', () => { it("widget select should be visible in edit mode", () => {
cy.get('.mk-widget-edit').click(); cy.get(".mk-widget-edit").click();
cy.get('.mk-widget-select').should('be.visible'); cy.get(".mk-widget-select").should("be.visible");
}); });
it('first widget should be removed', () => { it("first widget should be removed", () => {
cy.get('.mk-widget-edit').click(); cy.get(".mk-widget-edit").click();
cy.get('.customize-container:first-child .remove._button').click(); cy.get(".customize-container:first-child .remove._button").click();
cy.get('.customize-container').should('have.length', 2); cy.get(".customize-container").should("have.length", 2);
}); });
function buildWidgetTest(widgetName) { function buildWidgetTest(widgetName) {
it(`${widgetName} widget should get added`, () => { it(`${widgetName} widget should get added`, () => {
cy.get('.mk-widget-edit').click(); cy.get(".mk-widget-edit").click();
cy.get('.mk-widget-select select').select(widgetName, { force: true }); cy.get(".mk-widget-select select").select(widgetName, { force: true });
cy.get('.bg._modalBg.transparent').click({ multiple: true, force: true }); cy.get(".bg._modalBg.transparent").click({ multiple: true, force: true });
cy.get('.mk-widget-add').click({ force: true }); cy.get(".mk-widget-add").click({ force: true });
cy.get(`.mkw-${widgetName}`).should('exist'); cy.get(`.mkw-${widgetName}`).should("exist");
}); });
} }
buildWidgetTest('memo'); buildWidgetTest("memo");
buildWidgetTest('notifications'); buildWidgetTest("notifications");
buildWidgetTest('timeline'); buildWidgetTest("timeline");
buildWidgetTest('calendar'); buildWidgetTest("calendar");
buildWidgetTest('rss'); buildWidgetTest("rss");
buildWidgetTest('trends'); buildWidgetTest("trends");
buildWidgetTest('clock'); buildWidgetTest("clock");
buildWidgetTest('activity'); buildWidgetTest("activity");
buildWidgetTest('photos'); buildWidgetTest("photos");
buildWidgetTest('digitalClock'); buildWidgetTest("digitalClock");
buildWidgetTest('federation'); buildWidgetTest("federation");
buildWidgetTest('postForm'); buildWidgetTest("postForm");
buildWidgetTest('slideshow'); buildWidgetTest("slideshow");
buildWidgetTest('serverMetric'); buildWidgetTest("serverMetric");
buildWidgetTest('onlineUsers'); buildWidgetTest("onlineUsers");
buildWidgetTest('jobQueue'); buildWidgetTest("jobQueue");
buildWidgetTest('button'); buildWidgetTest("button");
buildWidgetTest('aiscript'); buildWidgetTest("aiscript");
}); });

View File

@ -16,6 +16,6 @@
* @type {Cypress.PluginConfig} * @type {Cypress.PluginConfig}
*/ */
module.exports = (on, config) => { module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits // `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config // `config` is the resolved Cypress config
} };

View File

@ -24,32 +24,34 @@
// -- This will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
Cypress.Commands.add('resetState', () => { Cypress.Commands.add("resetState", () => {
cy.window(win => { cy.window((win) => {
win.indexedDB.deleteDatabase('keyval-store'); win.indexedDB.deleteDatabase("keyval-store");
}); });
cy.request('POST', '/api/reset-db').as('reset'); cy.request("POST", "/api/reset-db").as("reset");
cy.get('@reset').its('status').should('equal', 204); cy.get("@reset").its("status").should("equal", 204);
cy.reload(true); cy.reload(true);
}); });
Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => { Cypress.Commands.add("registerUser", (username, password, isAdmin = false) => {
const route = isAdmin ? '/api/admin/accounts/create' : '/api/signup'; const route = isAdmin ? "/api/admin/accounts/create" : "/api/signup";
cy.request('POST', route, { cy.request("POST", route, {
username: username, username: username,
password: password, password: password,
}).its('body').as(username); })
.its("body")
.as(username);
}); });
Cypress.Commands.add('login', (username, password) => { Cypress.Commands.add("login", (username, password) => {
cy.visit('/'); cy.visit("/");
cy.intercept('POST', '/api/signin').as('signin'); cy.intercept("POST", "/api/signin").as("signin");
cy.get('[data-cy-signin]').click(); cy.get("[data-cy-signin]").click();
cy.get('[data-cy-signin-username] input').type(username); cy.get("[data-cy-signin-username] input").type(username);
cy.get('[data-cy-signin-password] input').type(`${password}{enter}`); cy.get("[data-cy-signin-password] input").type(`${password}{enter}`);
cy.wait('@signin').as('signedIn'); cy.wait("@signin").as("signedIn");
}); });

View File

@ -14,19 +14,21 @@
// *********************************************************** // ***********************************************************
// Import commands.js using ES2015 syntax: // Import commands.js using ES2015 syntax:
import './commands' import "./commands";
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')
Cypress.on('uncaught:exception', (err, runnable) => { Cypress.on("uncaught:exception", (err, runnable) => {
if ([ if (
// Chrome [
'ResizeObserver loop limit exceeded', // Chrome
"ResizeObserver loop limit exceeded",
// Firefox // Firefox
'ResizeObserver loop completed with undelivered notifications', "ResizeObserver loop completed with undelivered notifications",
].some(msg => err.message.includes(msg))) { ].some((msg) => err.message.includes(msg))
) {
return false; return false;
} }
}); });

View File

@ -2,59 +2,90 @@
* Languages Loader * Languages Loader
*/ */
const fs = require('fs'); const fs = require("fs");
const yaml = require('js-yaml'); const yaml = require("js-yaml");
let languages = [] let languages = [];
let languages_custom = [] let languages_custom = [];
const merge = (...args) => args.reduce((a, c) => ({
...a,
...c,
...Object.entries(a)
.filter(([k]) => c && typeof c[k] === 'object')
.reduce((a, [k, v]) => (a[k] = merge(v, c[k]), a), {})
}), {});
const merge = (...args) =>
args.reduce(
(a, c) => ({
...a,
...c,
...Object.entries(a)
.filter(([k]) => c && typeof c[k] === "object")
.reduce((a, [k, v]) => ((a[k] = merge(v, c[k])), a), {}),
}),
{},
);
fs.readdirSync(__dirname).forEach((file) => { fs.readdirSync(__dirname).forEach((file) => {
if (file.includes('.yml')){ if (file.includes(".yml")) {
file = file.slice(0, file.indexOf('.')) file = file.slice(0, file.indexOf("."));
languages.push(file); languages.push(file);
} }
}) });
fs.readdirSync(__dirname + '/../custom/locales').forEach((file) => { fs.readdirSync(__dirname + "/../custom/locales").forEach((file) => {
if (file.includes('.yml')){ if (file.includes(".yml")) {
file = file.slice(0, file.indexOf('.')) file = file.slice(0, file.indexOf("."));
languages_custom.push(file); languages_custom.push(file);
} }
}) });
const primaries = { const primaries = {
'en': 'US', en: "US",
'ja': 'JP', ja: "JP",
'zh': 'CN', zh: "CN",
}; };
// 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く // 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く
const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); const clean = (text) =>
text.replace(new RegExp(String.fromCodePoint(0x08), "g"), "");
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/${c}.yml`, 'utf-8'))) || {}, a), {}); const locales = languages.reduce(
const locales_custom = languages_custom.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/../custom/locales/${c}.yml`, 'utf-8'))) || {}, a), {}); (a, c) => (
Object.assign(locales, locales_custom) (a[c] =
yaml.load(clean(fs.readFileSync(`${__dirname}/${c}.yml`, "utf-8"))) ||
{}),
a
),
{},
);
const locales_custom = languages_custom.reduce(
(a, c) => (
(a[c] =
yaml.load(
clean(
fs.readFileSync(`${__dirname}/../custom/locales/${c}.yml`, "utf-8"),
),
) || {}),
a
),
{},
);
Object.assign(locales, locales_custom);
module.exports = Object.entries(locales) module.exports = Object.entries(locales).reduce(
.reduce((a, [k ,v]) => (a[k] = (() => { (a, [k, v]) => (
const [lang] = k.split('-'); (a[k] = (() => {
switch (k) { const [lang] = k.split("-");
case 'ja-JP': return v; switch (k) {
case 'ja-KS': case "ja-JP":
case 'en-US': return merge(locales['ja-JP'], v); return v;
default: return merge( case "ja-KS":
locales['ja-JP'], case "en-US":
locales['en-US'], return merge(locales["ja-JP"], v);
locales[`${lang}-${primaries[lang]}`] || {}, default:
v return merge(
); locales["ja-JP"],
} locales["en-US"],
})(), a), {}); locales[`${lang}-${primaries[lang]}`] || {},
v,
);
}
})()),
a
),
{},
);

View File

@ -1,6 +1,6 @@
{ {
"name": "calckey", "name": "calckey",
"version": "14.0.0-dev40", "version": "14.0.0-dev44",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
@ -27,7 +27,7 @@
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run", "e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
"mocha": "pnpm --filter backend run mocha", "mocha": "pnpm --filter backend run mocha",
"test": "pnpm run mocha", "test": "pnpm run mocha",
"format": "pnpm rome format packages/**/* --write && pnpm -r run format", "format": "pnpm -r run format",
"clean": "pnpm node ./scripts/clean.js", "clean": "pnpm node ./scripts/clean.js",
"clean-all": "pnpm node ./scripts/clean-all.js", "clean-all": "pnpm node ./scripts/clean-all.js",
"cleanall": "pnpm run clean-all" "cleanall": "pnpm run clean-all"

View File

@ -47,6 +47,6 @@ export class noteRepliesFunction1658656633972 {
} }
async down(queryRunner) { async down(queryRunner) {
await queryRunner.query(`DROP FUNCTION note_replies`); await queryRunner.query("DROP FUNCTION note_replies");
} }
} }

View File

@ -12,7 +12,7 @@ export class addFkAbuseUserReportTargetUserIdToUserId1671199573000 {
async down(queryRunner) { async down(queryRunner) {
await queryRunner.query( await queryRunner.query(
`ALTER TABLE abuse_user_report DROP CONSTRAINT fk_7f4e851a35d81b64dda28eee0`, "ALTER TABLE abuse_user_report DROP CONSTRAINT fk_7f4e851a35d81b64dda28eee0",
); );
} }
} }

View File

@ -4,22 +4,22 @@ export class CleanCharts1680375641101 {
} }
async up(queryRunner) { async up(queryRunner) {
await queryRunner.query( await queryRunner.query(
`delete from __chart__hashtag where ___local_users = 0 and ___remote_users = 0;`, "delete from __chart__hashtag where ___local_users = 0 and ___remote_users = 0;",
); );
await queryRunner.query( await queryRunner.query(
`delete from __chart_day__hashtag where ___local_users = 0 and ___remote_users = 0;`, "delete from __chart_day__hashtag where ___local_users = 0 and ___remote_users = 0;",
); );
await queryRunner.query(`COMMIT;`); await queryRunner.query("COMMIT;");
await queryRunner.query(`vacuum __chart__hashtag;`); await queryRunner.query("vacuum __chart__hashtag;");
await queryRunner.query(`vacuum __chart_day__hashtag;`); await queryRunner.query("vacuum __chart_day__hashtag;");
await queryRunner.query(`COMMIT;`); await queryRunner.query("COMMIT;");
} }
async down(queryRunner) { async down(queryRunner) {
await queryRunner.query( await queryRunner.query(
`delete from __chart__hashtag where ___local_users = 0 and ___remote_users = 0;`, "delete from __chart__hashtag where ___local_users = 0 and ___remote_users = 0;",
); );
await queryRunner.query( await queryRunner.query(
`delete from __chart_day__hashtag where ___local_users = 0 and ___remote_users = 0;`, "delete from __chart_day__hashtag where ___local_users = 0 and ___remote_users = 0;",
); );
} }
} }

View File

@ -17,7 +17,8 @@
"watch": "pnpm swc src -d built -D -w", "watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check \"src/**/*.ts\"", "lint": "pnpm rome check \"src/**/*.ts\"",
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", "mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"test": "pnpm run mocha" "test": "pnpm run mocha",
"format": "pnpm rome format * --write && pnpm rome check --apply-suggested *"
}, },
"resolutions": { "resolutions": {
"chokidar": "^3.3.1" "chokidar": "^3.3.1"

View File

@ -93,7 +93,7 @@ export async function masterMain() {
true, true,
); );
if (!envOption.noDaemons && !config.onlyQueueProcessor) { if (!(envOption.noDaemons || config.onlyQueueProcessor)) {
import("../daemons/server-stats.js").then((x) => x.default()); import("../daemons/server-stats.js").then((x) => x.default());
import("../daemons/queue-stats.js").then((x) => x.default()); import("../daemons/queue-stats.js").then((x) => x.default());
import("../daemons/janitor.js").then((x) => x.default()); import("../daemons/janitor.js").then((x) => x.default());

View File

@ -70,13 +70,13 @@ export class LdSignature {
...options, ...options,
"@context": "https://w3id.org/identity/v1", "@context": "https://w3id.org/identity/v1",
}; };
delete transformedOptions["type"]; transformedOptions["type"] = undefined;
delete transformedOptions["id"]; transformedOptions["id"] = undefined;
delete transformedOptions["signatureValue"]; transformedOptions["signatureValue"] = undefined;
const canonizedOptions = await this.normalize(transformedOptions); const canonizedOptions = await this.normalize(transformedOptions);
const optionsHash = this.sha256(canonizedOptions); const optionsHash = this.sha256(canonizedOptions);
const transformedData = { ...data }; const transformedData = { ...data };
delete transformedData["signature"]; transformedData["signature"] = undefined;
const cannonidedData = await this.normalize(transformedData); const cannonidedData = await this.normalize(transformedData);
if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`);
const documentHash = this.sha256(cannonidedData); const documentHash = this.sha256(cannonidedData);

View File

@ -29,8 +29,8 @@ export async function createImage(
throw new Error("invalid image: url not privided"); throw new Error("invalid image: url not privided");
} }
if (!image.url.startsWith("https://") && !image.url.startsWith("http://")) { if (!(image.url.startsWith("https://") || image.url.startsWith("http://"))) {
throw new Error("invalid image: unexpected shcema of url: " + image.url); throw new Error(`invalid image: unexpected shcema of url: ${image.url}`);
} }
logger.info(`Creating the Image: ${image.url}`); logger.info(`Creating the Image: ${image.url}`);

View File

@ -529,8 +529,8 @@ export default define(meta, paramDef, async (ps, me) => {
github: instance.enableGithubIntegration, github: instance.enableGithubIntegration,
discord: instance.enableDiscordIntegration, discord: instance.enableDiscordIntegration,
serviceWorker: instance.enableServiceWorker, serviceWorker: instance.enableServiceWorker,
postEditing: instance.experimentalFeatures?.postEditing || false, postEditing: instance.experimentalFeatures?.postEditing,
postImports: instance.experimentalFeatures?.postImports || false, postImports: instance.experimentalFeatures?.postImports,
miauth: true, miauth: true,
}; };
} }

View File

@ -614,13 +614,13 @@ export default define(meta, paramDef, async (ps, user) => {
} }
// Post is a reply and remote user is the contributor of the original post // Post is a reply and remote user is the contributor of the original post
if (note.reply && note.reply.userHost !== null) { if (note.reply?.userHost !== null) {
const u = await Users.findOneBy({ id: note.reply.userId }); const u = await Users.findOneBy({ id: note.reply.userId });
if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u);
} }
// Post is a renote and remote user is the contributor of the original post // Post is a renote and remote user is the contributor of the original post
if (note.renote && note.renote.userHost !== null) { if (note.renote?.userHost !== null) {
const u = await Users.findOneBy({ id: note.renote.userId }); const u = await Users.findOneBy({ id: note.renote.userId });
if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u);
} }

View File

@ -49,7 +49,7 @@ export function toTextWithReaction(status: Entity.Status[], host: string) {
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0]; if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
const reactions = t.emoji_reactions.map((r) => { const reactions = t.emoji_reactions.map((r) => {
const emojiNotation = r.url ? `:${r.name.replace("@.", "")}:` : r.name; const emojiNotation = r.url ? `:${r.name.replace("@.", "")}:` : r.name;
return `${emojiNotation} (${r.count}${r.me ? `* ` : ""})`; return `${emojiNotation} (${r.count}${r.me ? "* " : ""})`;
}); });
const reaction = t.emoji_reactions as Entity.Reaction[]; const reaction = t.emoji_reactions as Entity.Reaction[];
const emoji = t.emojis || []; const emoji = t.emojis || [];

View File

@ -97,7 +97,7 @@
const fontSize = localStorage.getItem("fontSize"); const fontSize = localStorage.getItem("fontSize");
if (fontSize) { if (fontSize) {
document.documentElement.classList.add("f-" + fontSize); document.documentElement.classList.add(`f-${fontSize}`);
} }
const useSystemFont = localStorage.getItem("useSystemFont"); const useSystemFont = localStorage.getItem("useSystemFont");
@ -292,7 +292,7 @@
const meta = await res.json(); const meta = await res.json();
if (meta.version != v) { if (meta.version !== v) {
localStorage.setItem("v", meta.version); localStorage.setItem("v", meta.version);
refresh(); refresh();
} }

View File

@ -564,13 +564,13 @@ export default async (
} }
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
if (data.reply && data.reply.userHost !== null) { if (data.reply?.userHost !== null) {
const u = await Users.findOneBy({ id: data.reply.userId }); const u = await Users.findOneBy({ id: data.reply.userId });
if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u);
} }
// 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送
if (data.renote && data.renote.userHost !== null) { if (data.renote?.userHost !== null) {
const u = await Users.findOneBy({ id: data.renote.userId }); const u = await Users.findOneBy({ id: data.renote.userId });
if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u);
} }
@ -671,7 +671,7 @@ async function insertNote(
tags: tags.map((tag) => normalizeForSearch(tag)), tags: tags.map((tag) => normalizeForSearch(tag)),
emojis, emojis,
userId: user.id, userId: user.id,
localOnly: data.localOnly || false, localOnly: data.localOnly,
visibility: data.visibility as any, visibility: data.visibility as any,
visibleUserIds: visibleUserIds:
data.visibility === "specified" data.visibility === "specified"

View File

@ -462,21 +462,21 @@ describe("API visibility", () => {
it("[HTL] public-post が 自分が見れる", async(async () => { it("[HTL] public-post が 自分が見れる", async(async () => {
const res = await request("/notes/timeline", { limit: 100 }, alice); const res = await request("/notes/timeline", { limit: 100 }, alice);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const notes = res.body.filter((n: any) => n.id == pub.id); const notes = res.body.filter((n: any) => n.id === pub.id);
assert.strictEqual(notes[0].text, "x"); assert.strictEqual(notes[0].text, "x");
})); }));
it("[HTL] public-post が 非フォロワーから見れない", async(async () => { it("[HTL] public-post が 非フォロワーから見れない", async(async () => {
const res = await request("/notes/timeline", { limit: 100 }, other); const res = await request("/notes/timeline", { limit: 100 }, other);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const notes = res.body.filter((n: any) => n.id == pub.id); const notes = res.body.filter((n: any) => n.id === pub.id);
assert.strictEqual(notes.length, 0); assert.strictEqual(notes.length, 0);
})); }));
it("[HTL] followers-post が フォロワーから見れる", async(async () => { it("[HTL] followers-post が フォロワーから見れる", async(async () => {
const res = await request("/notes/timeline", { limit: 100 }, follower); const res = await request("/notes/timeline", { limit: 100 }, follower);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const notes = res.body.filter((n: any) => n.id == fol.id); const notes = res.body.filter((n: any) => n.id === fol.id);
assert.strictEqual(notes[0].text, "x"); assert.strictEqual(notes[0].text, "x");
})); }));
//#endregion //#endregion
@ -489,7 +489,7 @@ describe("API visibility", () => {
follower, follower,
); );
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const notes = res.body.filter((n: any) => n.id == folR.id); const notes = res.body.filter((n: any) => n.id === folR.id);
assert.strictEqual(notes[0].text, "x"); assert.strictEqual(notes[0].text, "x");
})); }));
@ -500,7 +500,7 @@ describe("API visibility", () => {
other, other,
); );
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const notes = res.body.filter((n: any) => n.id == folR.id); const notes = res.body.filter((n: any) => n.id === folR.id);
assert.strictEqual(notes.length, 0); assert.strictEqual(notes.length, 0);
})); }));
@ -511,7 +511,7 @@ describe("API visibility", () => {
target, target,
); );
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const notes = res.body.filter((n: any) => n.id == folR.id); const notes = res.body.filter((n: any) => n.id === folR.id);
assert.strictEqual(notes[0].text, "x"); assert.strictEqual(notes[0].text, "x");
})); }));
//#endregion //#endregion
@ -520,14 +520,14 @@ describe("API visibility", () => {
it("[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる", async(async () => { it("[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる", async(async () => {
const res = await request("/notes/mentions", { limit: 100 }, target); const res = await request("/notes/mentions", { limit: 100 }, target);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const notes = res.body.filter((n: any) => n.id == folR.id); const notes = res.body.filter((n: any) => n.id === folR.id);
assert.strictEqual(notes[0].text, "x"); assert.strictEqual(notes[0].text, "x");
})); }));
it("[mentions] followers-mention が 非フォロワー (メンション先である) から見れる", async(async () => { it("[mentions] followers-mention が 非フォロワー (メンション先である) から見れる", async(async () => {
const res = await request("/notes/mentions", { limit: 100 }, target); const res = await request("/notes/mentions", { limit: 100 }, target);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const notes = res.body.filter((n: any) => n.id == folM.id); const notes = res.body.filter((n: any) => n.id === folM.id);
assert.strictEqual(notes[0].text, "@target x"); assert.strictEqual(notes[0].text, "@target x");
})); }));
//#endregion //#endregion

View File

@ -13,10 +13,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 0, size: 0,
md5: "d41d8cd98f00b204e9800998ecf8427e", md5: "d41d8cd98f00b204e9800998ecf8427e",
@ -35,10 +35,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 25360, size: 25360,
md5: "091b3f259662aa31e2ffef4519951168", md5: "091b3f259662aa31e2ffef4519951168",
@ -57,10 +57,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 1868, size: 1868,
md5: "08189c607bea3b952704676bb3c979e0", md5: "08189c607bea3b952704676bb3c979e0",
@ -79,10 +79,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 2248, size: 2248,
md5: "32c47a11555675d9267aee1a86571e7e", md5: "32c47a11555675d9267aee1a86571e7e",
@ -101,10 +101,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 3772, size: 3772,
md5: "f73535c3e1e27508885b69b10cf6e991", md5: "f73535c3e1e27508885b69b10cf6e991",
@ -123,10 +123,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 505, size: 505,
md5: "b6f52b4b021e7b92cdd04509c7267965", md5: "b6f52b4b021e7b92cdd04509c7267965",
@ -146,10 +146,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 544, size: 544,
md5: "4b7a346cde9ccbeb267e812567e33397", md5: "4b7a346cde9ccbeb267e812567e33397",
@ -168,10 +168,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 75933, size: 75933,
md5: "268c5dde99e17cf8fe09f1ab3f97df56", md5: "268c5dde99e17cf8fe09f1ab3f97df56",
@ -190,10 +190,10 @@ describe("Get file info", () => {
const info = (await getFileInfo(path, { const info = (await getFileInfo(path, {
skipSensitiveDetection: true, skipSensitiveDetection: true,
})) as any; })) as any;
delete info.warnings; info.warnings = undefined;
delete info.blurhash; info.blurhash = undefined;
delete info.sensitive; info.sensitive = undefined;
delete info.porn; info.porn = undefined;
assert.deepStrictEqual(info, { assert.deepStrictEqual(info, {
size: 12624, size: 12624,
md5: "68d5b2d8d1d1acbbce99203e3ec3857e", md5: "68d5b2d8d1d1acbbce99203e3ec3857e",

View File

@ -599,7 +599,7 @@ describe("Streaming", () => {
chitose, chitose,
"hashtag", "hashtag",
({ type, body }) => { ({ type, body }) => {
if (type == "note") { if (type === "note") {
assert.deepStrictEqual(body.text, "#foo"); assert.deepStrictEqual(body.text, "#foo");
ws.close(); ws.close();
done(); done();
@ -625,7 +625,7 @@ describe("Streaming", () => {
chitose, chitose,
"hashtag", "hashtag",
({ type, body }) => { ({ type, body }) => {
if (type == "note") { if (type === "note") {
if (body.text === "#foo") fooCount++; if (body.text === "#foo") fooCount++;
if (body.text === "#bar") barCount++; if (body.text === "#bar") barCount++;
if (body.text === "#foo #bar") fooBarCount++; if (body.text === "#foo #bar") fooBarCount++;
@ -668,7 +668,7 @@ describe("Streaming", () => {
chitose, chitose,
"hashtag", "hashtag",
({ type, body }) => { ({ type, body }) => {
if (type == "note") { if (type === "note") {
if (body.text === "#foo") fooCount++; if (body.text === "#foo") fooCount++;
if (body.text === "#bar") barCount++; if (body.text === "#bar") barCount++;
if (body.text === "#foo #bar") fooBarCount++; if (body.text === "#foo #bar") fooBarCount++;
@ -718,7 +718,7 @@ describe("Streaming", () => {
chitose, chitose,
"hashtag", "hashtag",
({ type, body }) => { ({ type, body }) => {
if (type == "note") { if (type === "note") {
if (body.text === "#foo") fooCount++; if (body.text === "#foo") fooCount++;
if (body.text === "#bar") barCount++; if (body.text === "#bar") barCount++;
if (body.text === "#foo #bar") fooBarCount++; if (body.text === "#foo #bar") fooBarCount++;

View File

@ -53,7 +53,7 @@ export const api = async (endpoint: string, params: any, me?: any) => {
beforeError: [ beforeError: [
(error) => { (error) => {
const { response } = error; const { response } = error;
if (response && response.body) console.warn(response.body); if (response?.body) console.warn(response.body);
return error; return error;
}, },
], ],
@ -316,7 +316,7 @@ export function launchServer(
moreProcess: () => Promise<void> = async () => {}, moreProcess: () => Promise<void> = async () => {},
) { ) {
return (done: (err?: Error) => any) => { return (done: (err?: Error) => any) => {
const p = childProcess.spawn("node", [_dirname + "/../index.js"], { const p = childProcess.spawn("node", [`${_dirname}/../index.js`], {
stdio: ["inherit", "inherit", "inherit", "ipc"], stdio: ["inherit", "inherit", "inherit", "ipc"],
env: { NODE_ENV: "test", PATH: process.env.PATH }, env: { NODE_ENV: "test", PATH: process.env.PATH },
}); });
@ -340,8 +340,8 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
username: config.db.user, username: config.db.user,
password: config.db.pass, password: config.db.pass,
database: config.db.db, database: config.db.db,
synchronize: true && !justBorrow, synchronize: !justBorrow,
dropSchema: true && !justBorrow, dropSchema: !justBorrow,
entities: initEntities || entities, entities: initEntities || entities,
}); });
@ -359,7 +359,7 @@ export function startServer(
rej("timeout to start"); rej("timeout to start");
}, timeout); }, timeout);
const p = childProcess.spawn("node", [_dirname + "/../built/index.js"], { const p = childProcess.spawn("node", [`${_dirname}/../built/index.js`], {
stdio: ["inherit", "inherit", "inherit", "ipc"], stdio: ["inherit", "inherit", "inherit", "ipc"],
env: { NODE_ENV: "test", PATH: process.env.PATH }, env: { NODE_ENV: "test", PATH: process.env.PATH },
}); });

View File

@ -42,7 +42,7 @@
isLong, isLong,
manyImages: note.files.length > 4, manyImages: note.files.length > 4,
showContent: note.cw && !showContent, showContent: note.cw && !showContent,
animatedMfm: !disableMfm animatedMfm: !disableMfm,
}" }"
> >
<XShowMoreButton <XShowMoreButton

View File

@ -9,7 +9,7 @@
class="mfm-object" class="mfm-object"
:class="{ :class="{
nowrap, nowrap,
advancedMfm: defaultStore.state.advancedMfm advancedMfm: defaultStore.state.advancedMfm,
}" }"
/> />
</template> </template>

View File

@ -4,7 +4,8 @@
"scripts": { "scripts": {
"build": "webpack", "build": "webpack",
"watch": "pnpm swc src -d built -D -w", "watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check \"src/**/*.ts\"" "lint": "pnpm rome check \"src/**/*.ts\"",
"format": "pnpm rome format * --write && pnpm rome check --apply-suggested *"
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",

View File

@ -1,21 +1,39 @@
const fs = require('fs'); const fs = require("fs");
const execa = require('execa'); const execa = require("execa");
(async () => { (async () => {
fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true }); fs.rmSync(`${__dirname}/../packages/backend/built`, {
fs.rmSync(__dirname + '/../packages/backend/node_modules', { recursive: true, force: true }); recursive: true,
force: true,
});
fs.rmSync(`${__dirname}/../packages/backend/node_modules`, {
recursive: true,
force: true,
});
fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true }); fs.rmSync(`${__dirname}/../packages/client/built`, {
fs.rmSync(__dirname + '/../packages/client/node_modules', { recursive: true, force: true }); recursive: true,
force: true,
});
fs.rmSync(`${__dirname}/../packages/client/node_modules`, {
recursive: true,
force: true,
});
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); fs.rmSync(`${__dirname}/../packages/sw/built`, {
fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true }); recursive: true,
force: true,
});
fs.rmSync(`${__dirname}/../packages/sw/node_modules`, {
recursive: true,
force: true,
});
fs.rmSync(__dirname + '/../built', { recursive: true, force: true }); fs.rmSync(`${__dirname}/../built`, { recursive: true, force: true });
fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true }); fs.rmSync(`${__dirname}/../node_modules`, { recursive: true, force: true });
execa('pnpm', ['store', 'prune'], { execa("pnpm", ["store", "prune"], {
cwd: __dirname + '/../', cwd: `${__dirname}/../`,
stdio: 'inherit' stdio: "inherit",
}); });
})(); })();

View File

@ -1,8 +1,17 @@
const fs = require('fs'); const fs = require("fs");
(async () => { (async () => {
fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true }); fs.rmSync(`${__dirname}/../packages/backend/built`, {
fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true }); recursive: true,
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); force: true,
fs.rmSync(__dirname + '/../built', { recursive: true, force: true }); });
fs.rmSync(`${__dirname}/../packages/client/built`, {
recursive: true,
force: true,
});
fs.rmSync(`${__dirname}/../packages/sw/built`, {
recursive: true,
force: true,
});
fs.rmSync(`${__dirname}/../built`, { recursive: true, force: true });
})(); })();

View File

@ -1,45 +1,45 @@
const execa = require('execa'); const execa = require("execa");
(async () => { (async () => {
await execa('pnpm', ['clean'], { await execa("pnpm", ["clean"], {
cwd: __dirname + '/../', cwd: `${__dirname}/../`,
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}); });
execa('pnpm', ['dlx', 'gulp', 'watch'], { execa("pnpm", ["dlx", "gulp", "watch"], {
cwd: __dirname + '/../', cwd: `${__dirname}/../`,
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}); });
execa('pnpm', ['--filter', 'backend', 'watch'], { execa("pnpm", ["--filter", "backend", "watch"], {
cwd: __dirname + '/../', cwd: `${__dirname}/../`,
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}); });
execa('pnpm', ['--filter', 'client', 'watch'], { execa("pnpm", ["--filter", "client", "watch"], {
cwd: __dirname + '/../', cwd: `${__dirname}/../`,
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}); });
execa('pnpm', ['--filter', 'sw', 'watch'], { execa("pnpm", ["--filter", "sw", "watch"], {
cwd: __dirname + '/../', cwd: `${__dirname}/../`,
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}); });
const start = async () => { const start = async () => {
try { try {
await execa('pnpm', ['start'], { await execa("pnpm", ["start"], {
cwd: __dirname + '/../', cwd: `${__dirname}/../`,
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}); });
} catch (e) { } catch (e) {
await new Promise(resolve => setTimeout(resolve, 3000)); await new Promise((resolve) => setTimeout(resolve, 3000));
start(); start();
} }
}; };