[feat] Remove Twitter Integration

This commit is contained in:
Pyrox 2023-11-27 18:17:34 -05:00 committed by Laura Hausmann
parent 042e8c552d
commit 60f7e2cf6a
22 changed files with 370 additions and 729 deletions

View File

@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class RemoveTwitterIntegration1701069578019 implements MigrationInterface {
name = "RemoveTwitterIntegration1701069578019";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN IF EXISTS "enableTwitterIntegration"`,
);
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN IF EXISTS "twitterConsumerKey"`,
);
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN IF EXISTS "twitterConsumerSecret"`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "enableTwitterIntegration" boolean NOT NULL DEFAULT false`,
);
await queryRunner.query(
`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "twitterConsumerKey" character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "twitterConsumerSecret" character varying(128)`,
);
}
}

View File

@ -310,23 +310,6 @@ export class Meta {
})
public swPrivateKey: string;
@Column("boolean", {
default: false,
})
public enableTwitterIntegration: boolean;
@Column("varchar", {
length: 128,
nullable: true,
})
public twitterConsumerKey: string | null;
@Column("varchar", {
length: 128,
nullable: true,
})
public twitterConsumerSecret: string | null;
@Column("boolean", {
default: false,
})

View File

@ -687,10 +687,6 @@ export async function resolvePerson(
const services: {
[x: string]: (id: string, username: string) => any;
} = {
"misskey:authentication:twitter": (userId, screenName) => ({
userId,
screenName,
}),
"misskey:authentication:github": (id, login) => ({ id, login }),
"misskey:authentication:discord": (id, name) => $discord(id, name),
};

View File

@ -140,11 +140,6 @@ export const meta = {
optional: false,
nullable: false,
},
enableTwitterIntegration: {
type: "boolean",
optional: false,
nullable: false,
},
enableGithubIntegration: {
type: "boolean",
optional: false,
@ -260,36 +255,6 @@ export const meta = {
optional: true,
nullable: true,
},
twitterConsumerKey: {
type: "string",
optional: true,
nullable: true,
},
twitterConsumerSecret: {
type: "string",
optional: true,
nullable: true,
},
githubClientId: {
type: "string",
optional: true,
nullable: true,
},
githubClientSecret: {
type: "string",
optional: true,
nullable: true,
},
discordClientId: {
type: "string",
optional: true,
nullable: true,
},
discordClientSecret: {
type: "string",
optional: true,
nullable: true,
},
summaryProxy: {
type: "string",
optional: true,
@ -483,9 +448,6 @@ export default define(meta, paramDef, async (ps, me) => {
defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme,
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,
enableGithubIntegration: instance.enableGithubIntegration,
enableDiscordIntegration: instance.enableDiscordIntegration,
translatorAvailable:
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
pinnedPages: instance.pinnedPages,
@ -504,12 +466,6 @@ export default define(meta, paramDef, async (ps, me) => {
secureMode: instance.secureMode,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
twitterConsumerKey: instance.twitterConsumerKey,
twitterConsumerSecret: instance.twitterConsumerSecret,
githubClientId: instance.githubClientId,
githubClientSecret: instance.githubClientSecret,
discordClientId: instance.discordClientId,
discordClientSecret: instance.discordClientSecret,
summalyProxy: instance.summalyProxy,
email: instance.email,
smtpSecure: instance.smtpSecure,

View File

@ -124,9 +124,6 @@ export const paramDef = {
deeplIsPro: { type: "boolean" },
libreTranslateApiUrl: { type: "string", nullable: true },
libreTranslateApiKey: { type: "string", nullable: true },
enableTwitterIntegration: { type: "boolean" },
twitterConsumerKey: { type: "string", nullable: true },
twitterConsumerSecret: { type: "string", nullable: true },
enableGithubIntegration: { type: "boolean" },
githubClientId: { type: "string", nullable: true },
githubClientSecret: { type: "string", nullable: true },
@ -363,17 +360,6 @@ export default define(meta, paramDef, async (ps, me) => {
set.summalyProxy = ps.summalyProxy;
}
if (ps.enableTwitterIntegration !== undefined) {
set.enableTwitterIntegration = ps.enableTwitterIntegration;
}
if (ps.twitterConsumerKey !== undefined) {
set.twitterConsumerKey = ps.twitterConsumerKey;
}
if (ps.twitterConsumerSecret !== undefined) {
set.twitterConsumerSecret = ps.twitterConsumerSecret;
}
if (ps.enableGithubIntegration !== undefined) {
set.enableGithubIntegration = ps.enableGithubIntegration;

View File

@ -251,11 +251,6 @@ export const meta = {
optional: false,
nullable: false,
},
enableTwitterIntegration: {
type: "boolean",
optional: false,
nullable: false,
},
enableGithubIntegration: {
type: "boolean",
optional: false,
@ -320,11 +315,6 @@ export const meta = {
optional: false,
nullable: false,
},
twitter: {
type: "boolean",
optional: false,
nullable: false,
},
github: {
type: "boolean",
optional: false,
@ -453,7 +443,6 @@ export default define(meta, paramDef, async (ps, me) => {
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,
enableGithubIntegration: instance.enableGithubIntegration,
enableDiscordIntegration: instance.enableDiscordIntegration,
@ -487,7 +476,6 @@ export default define(meta, paramDef, async (ps, me) => {
hcaptcha: instance.enableHcaptcha,
recaptcha: instance.enableRecaptcha,
objectStorage: instance.useObjectStorage,
twitter: instance.enableTwitterIntegration,
github: instance.enableGithubIntegration,
discord: instance.enableDiscordIntegration,
serviceWorker: true,

View File

@ -19,7 +19,6 @@ import signupPending from "./private/signup-pending.js";
import verifyEmail from "./private/verify-email.js";
import discord from "./service/discord.js";
import github from "./service/github.js";
import twitter from "./service/twitter.js";
// Init app
const app = new Koa();
@ -112,8 +111,6 @@ router.post("/verify-email", verifyEmail);
router.use(discord.routes());
router.use(github.routes());
router.use(twitter.routes());
router.post("/miauth/:session/check", async (ctx) => {
const token = await AccessTokens.findOneBy({
session: ctx.params.session,

View File

@ -1,226 +0,0 @@
import type Koa from "koa";
import Router from "@koa/router";
import { v4 as uuid } from "uuid";
import autwh from "autwh";
import { IsNull } from "typeorm";
import { publishMainStream } from "@/services/stream.js";
import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { Users, UserProfiles } from "@/models/index.js";
import type { ILocalUser } from "@/models/entities/user.js";
import signin from "../common/signin.js";
import { redisClient } from "../../../db/redis.js";
function getUserToken(ctx: Koa.BaseContext): string | null {
return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1];
}
function compareOrigin(ctx: Koa.BaseContext): boolean {
function normalizeUrl(url?: string): string {
return url == null
? ""
: url.endsWith("/")
? url.substr(0, url.length - 1)
: url;
}
const referer = ctx.headers["referer"];
return normalizeUrl(referer) === normalizeUrl(config.url);
}
// Init router
const router = new Router();
router.get("/disconnect/twitter", async (ctx) => {
if (!compareOrigin(ctx)) {
ctx.throw(400, "invalid origin");
return;
}
const userToken = getUserToken(ctx);
if (userToken == null) {
ctx.throw(400, "signin required");
return;
}
const user = await Users.findOneByOrFail({
host: IsNull(),
token: userToken,
});
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
profile.integrations.twitter = undefined;
await UserProfiles.update(user.id, {
integrations: profile.integrations,
});
ctx.body = "Twitterの連携を解除しました :v:";
// Publish i updated event
publishMainStream(
user.id,
"meUpdated",
await Users.pack(user, user, {
detail: true,
includeSecrets: true,
}),
);
});
async function getTwAuth() {
const meta = await fetchMeta(true);
if (
meta.enableTwitterIntegration &&
meta.twitterConsumerKey &&
meta.twitterConsumerSecret
) {
return autwh({
consumerKey: meta.twitterConsumerKey,
consumerSecret: meta.twitterConsumerSecret,
callbackUrl: `${config.url}/api/tw/cb`,
});
} else {
return null;
}
}
router.get("/connect/twitter", async (ctx) => {
if (!compareOrigin(ctx)) {
ctx.throw(400, "invalid origin");
return;
}
const userToken = getUserToken(ctx);
if (userToken == null) {
ctx.throw(400, "signin required");
return;
}
const twAuth = await getTwAuth();
const twCtx = await twAuth!.begin();
redisClient.set(userToken, JSON.stringify(twCtx));
ctx.redirect(twCtx.url);
});
router.get("/signin/twitter", async (ctx) => {
const twAuth = await getTwAuth();
const twCtx = await twAuth!.begin();
const sessid = uuid();
redisClient.set(sessid, JSON.stringify(twCtx));
ctx.cookies.set("signin_with_twitter_sid", sessid, {
path: "/",
secure: config.url.startsWith("https"),
httpOnly: true,
});
ctx.redirect(twCtx.url);
});
router.get("/tw/cb", async (ctx) => {
const userToken = getUserToken(ctx);
const twAuth = await getTwAuth();
if (userToken == null) {
const sessid = ctx.cookies.get("signin_with_twitter_sid");
if (sessid == null) {
ctx.throw(400, "invalid session");
return;
}
const get = new Promise<any>((res, rej) => {
redisClient.get(sessid, async (_, twCtx) => {
res(twCtx);
});
});
const twCtx = await get;
const verifier = ctx.query.oauth_verifier;
if (!verifier || typeof verifier !== "string") {
ctx.throw(400, "invalid session");
return;
}
const result = await twAuth!.done(JSON.parse(twCtx), verifier);
const link = await UserProfiles.createQueryBuilder()
.where("\"integrations\"->'twitter'->>'userId' = :id", {
id: result.userId,
})
.andWhere('"userHost" IS NULL')
.getOne();
if (link == null) {
ctx.throw(
404,
`@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`,
);
return;
}
signin(
ctx,
(await Users.findOneBy({ id: link.userId })) as ILocalUser,
true,
);
} else {
const verifier = ctx.query.oauth_verifier;
if (!verifier || typeof verifier !== "string") {
ctx.throw(400, "invalid session");
return;
}
const get = new Promise<any>((res, rej) => {
redisClient.get(userToken, async (_, twCtx) => {
res(twCtx);
});
});
const twCtx = await get;
const result = await twAuth!.done(JSON.parse(twCtx), verifier);
const user = await Users.findOneByOrFail({
host: IsNull(),
token: userToken,
});
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
await UserProfiles.update(user.id, {
integrations: {
...profile.integrations,
twitter: {
accessToken: result.accessToken,
accessTokenSecret: result.accessTokenSecret,
userId: result.userId,
screenName: result.screenName,
},
},
});
ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`;
// Publish i updated event
publishMainStream(
user.id,
"meUpdated",
await Users.pack(user, user, {
detail: true,
includeSecrets: true,
}),
);
}
});
export default router;

View File

@ -84,7 +84,6 @@ const nodeinfo2 = async () => {
enableRecaptcha: meta.enableRecaptcha,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
enableTwitterIntegration: meta.enableTwitterIntegration,
enableGithubIntegration: meta.enableGithubIntegration,
enableDiscordIntegration: meta.enableDiscordIntegration,
enableEmail: meta.enableEmail,

View File

@ -31,7 +31,3 @@ block meta
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
meta(name='misskey:clip-id' content=clip.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)

View File

@ -31,9 +31,5 @@ block meta
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
if !user.host
link(rel='alternate' href=url type='application/activity+json')

View File

@ -44,10 +44,6 @@ block meta
meta(name='misskey:user-id' content=user.id)
meta(name='misskey:note-id' content=note.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
if note.prev
link(rel='prev' href=`${config.url}/notes/${note.prev}`)
if note.next

View File

@ -31,7 +31,3 @@ block meta
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
meta(name='misskey:page-id' content=page.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)

View File

@ -31,9 +31,6 @@ block meta
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
if profile.twitter
meta(name='twitter:creator' content=`@${profile.twitter.screenName}`)
if !sub
if !user.host
link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json')

View File

@ -16,7 +16,10 @@
<MkInfo v-if="message">
{{ message }}
</MkInfo>
<div v-if="!totpLogin" class="normal-signin">
<div
v-if="!totpLogin"
class="normal-signin"
>
<MkInput
v-model="username"
class="_formBlock"
@ -43,18 +46,14 @@
required
data-cy-signin-password
>
<template #prefix
><i class="ph-lock ph-bold ph-lg"></i
></template>
<template #caption
><button
<template #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
<template #caption><button
class="_textButton"
type="button"
@click="resetPassword"
>
{{ i18n.ts.forgotPassword }}
</button></template
>
</button></template>
</MkInput>
<MkButton
class="_formBlock"
@ -62,8 +61,8 @@
primary
:disabled="signing"
style="margin: 1rem auto"
>{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton
>
>{{ signing
? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
</div>
<div
v-if="totpLogin"
@ -75,11 +74,17 @@
class="twofa-group tap-group"
>
<p>{{ i18n.ts.tapSecurityKey }}</p>
<MkButton v-if="!queryingKey" @click="queryKey">
<MkButton
v-if="!queryingKey"
@click="queryKey"
>
{{ i18n.ts.retry }}
</MkButton>
</div>
<div v-if="user && user.securityKeys" class="or-hr">
<div
v-if="user && user.securityKeys"
class="or-hr"
>
<p class="or-msg">{{ i18n.ts.or }}</p>
</div>
<div class="twofa-group totp-group">
@ -95,9 +100,7 @@
required
>
<template #label>{{ i18n.ts.password }}</template>
<template #prefix
><i class="ph-lock ph-bold ph-lg"></i
></template>
<template #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
</MkInput>
<MkInput
v-model="token"
@ -108,9 +111,7 @@
required
>
<template #label>{{ i18n.ts._2fa.token }}</template>
<template #prefix
><i class="ph-poker-chip ph-bold ph-lg"></i
></template>
<template #prefix><i class="ph-poker-chip ph-bold ph-lg"></i></template>
</MkInput>
<MkButton
type="submit"
@ -119,22 +120,11 @@
style="margin: 1rem auto auto"
>{{
signing ? i18n.ts.loggingIn : i18n.ts.login
}}</MkButton
>
}}</MkButton>
</div>
</div>
</div>
<div class="social _section">
<a
v-if="meta && meta.enableTwitterIntegration"
class="_borderButton _gap"
:href="`${apiUrl}/signin/twitter`"
><i
class="ph-twitter-logo ph-bold ph-lg"
style="margin-right: 4px"
></i
>{{ i18n.t("signinWith", { x: "Twitter" }) }}</a
>
<a
v-if="meta && meta.enableGithubIntegration"
class="_borderButton _gap"
@ -142,9 +132,9 @@
><i
class="ph-github-logo ph-bold ph-lg"
style="margin-right: 4px"
></i
>{{ i18n.t("signinWith", { x: "GitHub" }) }}</a
>
></i>{{ i18n.t("signinWith", {
x: "GitHub"
}) }}</a>
<a
v-if="meta && meta.enableDiscordIntegration"
class="_borderButton _gap"
@ -152,9 +142,10 @@
><i
class="ph-discord-logo ph-bold ph-lg"
style="margin-right: 4px"
></i
>{{ i18n.t("signinWith", { x: "Discord" }) }}</a
>
></i>{{ i18n.t("signinWith", {
x:
"Discord"
}) }}</a>
</div>
</form>
</template>

View File

@ -47,13 +47,11 @@ import * as os from "@/os";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
let enableTwitterIntegration: boolean = $ref(false);
let enableGithubIntegration: boolean = $ref(false);
let enableDiscordIntegration: boolean = $ref(false);
async function init() {
const meta = await os.api("admin/meta");
enableTwitterIntegration = meta.enableTwitterIntegration;
enableGithubIntegration = meta.enableGithubIntegration;
enableDiscordIntegration = meta.enableDiscordIntegration;
}

View File

@ -1,50 +1,29 @@
<template>
<div class="_formRoot">
<FormSection v-if="instance.enableDiscordIntegration">
<template #label
><i class="ph-discord-logo ph-bold ph-lg"></i> Discord</template
>
<template #label><i class="ph-discord-logo ph-bold ph-lg"></i> Discord</template>
<p v-if="integrations.discord">
{{ i18n.ts.connectedTo }}:
<a
:href="`https://discord.com/users/${integrations.discord.id}`"
rel="nofollow noopener"
target="_blank"
>@{{ integrations.discord.username }}#{{
<a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener"
target="_blank">@{{ integrations.discord.username }}#{{
integrations.discord.discriminator
}}</a
>
}}</a>
</p>
<MkButton
v-if="integrations.discord"
danger
@click="disconnectDiscord"
>{{ i18n.ts.disconnectService }}</MkButton
>
<MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ i18n.ts.disconnectService }}
</MkButton>
<MkButton v-else primary @click="connectDiscord">{{
i18n.ts.connectService
}}</MkButton>
</FormSection>
<FormSection v-if="instance.enableGithubIntegration">
<template #label
><i class="ph-github-logo ph-bold ph-lg"></i> GitHub</template
>
<template #label><i class="ph-github-logo ph-bold ph-lg"></i> GitHub</template>
<p v-if="integrations.github">
{{ i18n.ts.connectedTo }}:
<a
:href="`https://github.com/${integrations.github.login}`"
rel="nofollow noopener"
target="_blank"
>@{{ integrations.github.login }}</a
>
<a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{
integrations.github.login }}</a>
</p>
<MkButton
v-if="integrations.github"
danger
@click="disconnectGithub"
>{{ i18n.ts.disconnectService }}</MkButton
>
<MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ i18n.ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectGithub">{{
i18n.ts.connectService
}}</MkButton>
@ -62,7 +41,6 @@ import { instance } from "@/instance";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
const twitterForm = ref<Window | null>(null);
const discordForm = ref<Window | null>(null);
const githubForm = ref<Window | null>(null);
@ -76,14 +54,6 @@ function openWindow(service: string, type: string) {
);
}
function connectTwitter() {
twitterForm.value = openWindow("twitter", "connect");
}
function disconnectTwitter() {
openWindow("twitter", "disconnect");
}
function connectDiscord() {
discordForm.value = openWindow("discord", "connect");
}
@ -107,9 +77,6 @@ onMounted(() => {
(document.location.protocol.startsWith("https") ? " secure" : "");
watch(integrations, () => {
if (integrations.value.twitter) {
if (twitterForm.value) twitterForm.value.close();
}
if (integrations.value.discord) {
if (discordForm.value) discordForm.value.close();
}

View File

@ -7799,7 +7799,7 @@
},
{
"kind": "Content",
"text": "{\n\tmaintainerName: string | null;\n\tmaintainerEmail: string | null;\n\tversion: string;\n\tname: string | null;\n\turi: string;\n\tdescription: string | null;\n\ttosUrl: string | null;\n\tdisableRegistration: boolean;\n\tdisableLocalTimeline: boolean;\n\tdisableRecommendedTimeline: boolean;\n\tdisableGlobalTimeline: boolean;\n\tdriveCapacityPerLocalUserMb: number;\n\tdriveCapacityPerRemoteUserMb: number;\n\tenableHcaptcha: boolean;\n\thcaptchaSiteKey: string | null;\n\tenableRecaptcha: boolean;\n\trecaptchaSiteKey: string | null;\n\tswPublickey: string | null;\n\tmaxNoteTextLength: number;\n\tenableEmail: boolean;\n\tenableTwitterIntegration: boolean;\n\tenableGithubIntegration: boolean;\n\tenableDiscordIntegration: boolean;\n\tenableServiceWorker: boolean;\n\temojis: "
"text": "{\n\tmaintainerName: string | null;\n\tmaintainerEmail: string | null;\n\tversion: string;\n\tname: string | null;\n\turi: string;\n\tdescription: string | null;\n\ttosUrl: string | null;\n\tdisableRegistration: boolean;\n\tdisableLocalTimeline: boolean;\n\tdisableRecommendedTimeline: boolean;\n\tdisableGlobalTimeline: boolean;\n\tdriveCapacityPerLocalUserMb: number;\n\tdriveCapacityPerRemoteUserMb: number;\n\tenableHcaptcha: boolean;\n\thcaptchaSiteKey: string | null;\n\tenableRecaptcha: boolean;\n\trecaptchaSiteKey: string | null;\n\tswPublickey: string | null;\n\tmaxNoteTextLength: number;\n\tenableEmail: boolean;\n\tenableGithubIntegration: boolean;\n\tenableDiscordIntegration: boolean;\n\tenableServiceWorker: boolean;\n\temojis: "
},
{
"kind": "Reference",

View File

@ -2397,7 +2397,6 @@ type LiteInstanceMetadata = {
swPublickey: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
enableServiceWorker: boolean;

View File

@ -2295,7 +2295,6 @@ type LiteInstanceMetadata = {
swPublickey: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
enableServiceWorker: boolean;

View File

@ -28,7 +28,6 @@ export declare type LiteInstanceMetadata = {
swPublickey: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
enableServiceWorker: boolean;

View File

@ -297,7 +297,6 @@ export type LiteInstanceMetadata = {
swPublickey: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
searchEngine: string;