Trash integrations lmao

This commit is contained in:
Michcio 2022-09-20 13:17:09 +02:00
parent 6aa52ecbfa
commit 58002cac58
39 changed files with 0 additions and 1363 deletions

View File

@ -324,9 +324,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "الصفحات"
integration: "التكامل"
connectService: "اتصل"
disconnectService: "اقطع الاتصال"
enableLocalTimeline: "تفعيل الخيط المحلي"
enableGlobalTimeline: "تفعيل الخيط الزمني الشامل"
disablingTimelinesInfo: "سيتمكن المديرون والمشرفون من الوصول إلى كل الخيوط الزمنية\

View File

@ -339,9 +339,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "পৃষ্ঠা"
integration: "ইন্টিগ্রেশন"
connectService: "সংযুক্ত করুন"
disconnectService: "সংযোগ বিচ্ছিন্ন করুন"
enableLocalTimeline: "স্থানীয় টাইমলাইন চালু করুন"
enableGlobalTimeline: "গ্লোবাল টাইমলাইন চালু করুন"
disablingTimelinesInfo: "আপনি এই টাইমলাইনগুলি বন্ধ করলেও প্রশাসক এবং মডারেটররা এই\

View File

@ -307,9 +307,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Stránky"
integration: "Integrace"
connectService: "Připojit"
disconnectService: "Odpojit"
enableLocalTimeline: "Povolit lokální čas"
enableGlobalTimeline: "Povolit globální čas"
registration: "Registrace"

View File

@ -350,9 +350,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Seiten"
integration: "Integration"
connectService: "Verbinden"
disconnectService: "Trennen"
enableLocalTimeline: "Lokale Chronik aktivieren"
enableGlobalTimeline: "Globale Chronik aktivieren"
disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle\

View File

@ -340,9 +340,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Pages"
integration: "Integration"
connectService: "Connect"
disconnectService: "Disconnect"
enableLocalTimeline: "Enable local timeline"
enableGlobalTimeline: "Enable global timeline"
disablingTimelinesInfo: "Adminstrators and Moderators will always have access to all\
@ -1511,13 +1508,3 @@ _deck:
list: "List"
mentions: "Mentions"
direct: "Direct notes"
_services:
_discord:
connected: "Discord: @{username}#{discriminator} connected to FoundKey: @{mkUsername}!"
disconnected: "Discord linkage has been removed."
_twitter:
connected: "Twitter: @{twitterUserName} connected to FoundKey: @{userName}!"
disconnected: "Twitter linkage has been removed."
_github:
connected: "GitHub: @{login} connected to FoundKey: @{userName}!"
disconnected: "GitHub linkage has been removed."

View File

@ -342,9 +342,6 @@ dayX: "Día {day}"
monthX: "Mes {month}"
yearX: "Año {year}"
pages: "Páginas"
integration: "Integración"
connectService: "Conectar"
disconnectService: "Desconectar"
enableLocalTimeline: "Habilitar linea de tiempo local"
enableGlobalTimeline: "Habilitar linea de tiempo global"
disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conveniencia\

View File

@ -342,9 +342,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Pages"
integration: "Intégrations"
connectService: "Connexion"
disconnectService: "Déconnexion"
enableLocalTimeline: "Activer le fil local"
enableGlobalTimeline: "Activer le fil global"
disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s\

View File

@ -341,9 +341,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Halaman"
integration: "Integrasi"
connectService: "Sambungkan"
disconnectService: "Putuskan"
enableLocalTimeline: "Nyalakan linimasa lokal"
enableGlobalTimeline: "Nyalakan linimasa global"
disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua linimasa\

View File

@ -335,9 +335,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Pagine"
integration: "App collegate"
connectService: "Connessione"
disconnectService: "Disconnessione "
enableLocalTimeline: "Abilita Timeline locale"
enableGlobalTimeline: "Abilita Timeline federata"
disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori e\

View File

@ -317,9 +317,6 @@ dayX: "{day}日"
monthX: "{month}月"
yearX: "{year}年"
pages: "ページ"
integration: "連携"
connectService: "接続する"
disconnectService: "切断する"
enableLocalTimeline: "ローカルタイムラインを有効にする"
enableGlobalTimeline: "グローバルタイムラインを有効にする"
disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。"
@ -1449,13 +1446,3 @@ _deck:
list: "リスト"
mentions: "あなた宛て"
direct: "ダイレクト"
_services:
_discord:
connected: "Discord: @{username}#{discriminator} を、FoundKey: @{mkUsername} に接続しました!"
disconnected: "Discordの連携を解除しました :v:"
_twitter:
connected: "Twitter: @{twitterUserName} を、FoundKey: @{userName} に接続しました!"
disconnected: "Twitterの連携を解除しました :v:"
_github:
connected: "GitHub: @{login} を、FoundKey: @{userName} に接続しました!"
disconnected: "GitHubの連携を解除しました :v:"

View File

@ -319,7 +319,6 @@ dayX: "{day}日"
monthX: "{month}月"
yearX: "{year}年"
pages: "ページ"
integration: "連携"
enableLocalTimeline: "ローカルタイムラインを使えるようにする"
enableGlobalTimeline: "グローバルタイムラインを使えるようにする"
disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。"

View File

@ -315,9 +315,6 @@ dayX: "{day}일"
monthX: "{month}월"
yearX: "{year}년"
pages: "페이지"
integration: "연동"
connectService: "계정 연동"
disconnectService: "계정 연동 해제"
enableLocalTimeline: "로컬 타임라인 활성화"
enableGlobalTimeline: "글로벌 타임라인 활성화"
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."

View File

@ -329,9 +329,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Strony"
integration: "Integracja"
connectService: "Połącz"
disconnectService: "Rozłącz"
enableLocalTimeline: "Włącz lokalną oś czasu"
enableGlobalTimeline: "Włącz globalną oś czasu"
disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dostęp do\

View File

@ -342,9 +342,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Pagini"
integration: "Integrare"
connectService: "Conectează"
disconnectService: "Deconectează"
enableLocalTimeline: "Activează cronologia locală"
enableGlobalTimeline: "Activeaza cronologia globală"
disablingTimelinesInfo: "Administratorii și Moderatorii vor avea mereu access la toate\

View File

@ -335,9 +335,6 @@ dayX: "{day} день"
monthX: "{month} месяц"
yearX: "{year} год"
pages: "Страницы"
integration: "Интеграция"
connectService: "Подключиться"
disconnectService: "Отключиться"
enableLocalTimeline: "Включить локальную ленту"
enableGlobalTimeline: "Включить глобальную ленту"
disablingTimelinesInfo: "У администраторов и модераторов есть доступ ко всем лентам,\

View File

@ -336,9 +336,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Stránky"
integration: "Integrácia"
connectService: "Pripojiť"
disconnectService: "Odpojiť"
enableLocalTimeline: "Povoliť lokálnu časovú os"
enableGlobalTimeline: "Povoliť globálnu časovú os"
disablingTimelinesInfo: "Administrátori a moderátori majú vždy prístup ku všetkým\

View File

@ -336,9 +336,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Сторінки"
integration: "Інтеграція"
connectService: "Під’єднати"
disconnectService: "Відключитися"
enableLocalTimeline: "Увімкнути локальну стрічку"
enableGlobalTimeline: "Увімкнути глобальну стрічку"
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх\

View File

@ -336,9 +336,6 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Trang"
integration: "Tương tác"
connectService: "Kết nối"
disconnectService: "Ngắt kết nối"
enableLocalTimeline: "Bật bảng tin máy chủ"
enableGlobalTimeline: "Bật bảng tin liên hợp"
disablingTimelinesInfo: "Quản trị viên và Kiểm duyệt viên luôn có quyền truy cập mọi\

View File

@ -315,9 +315,6 @@ dayX: "{day}日"
monthX: "{month}月"
yearX: "{year}年"
pages: "页面"
integration: "关联"
connectService: "连接"
disconnectService: "断开连接"
enableLocalTimeline: "启用本地时间线功能"
enableGlobalTimeline: "启用全局时间线"
disablingTimelinesInfo: "即使时间线功能被禁用,出于方便,管理员和数据图表也可以继续使用。"

View File

@ -315,9 +315,6 @@ dayX: "{day}日"
monthX: "{month}月"
yearX: "{year}年"
pages: "頁面"
integration: "整合"
connectService: "己連結"
disconnectService: "己斷開 "
enableLocalTimeline: "開啟本地時間軸"
enableGlobalTimeline: "啟用公開時間軸"
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調人仍可以繼續使用,以方便您。"

View File

@ -29,7 +29,6 @@
"ajv": "8.11.0",
"archiver": "5.3.1",
"autobind-decorator": "2.4.0",
"autwh": "0.1.0",
"aws-sdk": "2.1165.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.5",
@ -150,7 +149,6 @@
"@types/node": "18.7.16",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.5",
"@types/oauth": "^0.9.1",
"@types/opentype.js": "^1.3.4",
"@types/pg": "^8.6.5",
"@types/pug": "2.0.6",

View File

@ -101,18 +101,6 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
enableTwitterIntegration: {
type: 'boolean',
optional: false, nullable: false,
},
enableGithubIntegration: {
type: 'boolean',
optional: false, nullable: false,
},
enableDiscordIntegration: {
type: 'boolean',
optional: false, nullable: false,
},
enableServiceWorker: {
type: 'boolean',
optional: false, nullable: false,
@ -166,30 +154,6 @@ export const meta = {
optional: true, nullable: true,
format: 'id',
},
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, nullable: true,
@ -314,9 +278,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,
enableServiceWorker: instance.enableServiceWorker,
translatorAvailable: instance.deeplAuthKey != null,
pinnedPages: instance.pinnedPages,
@ -330,12 +291,6 @@ export default define(meta, paramDef, async (ps, me) => {
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
proxyAccountId: instance.proxyAccountId,
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

@ -58,15 +58,6 @@ export const paramDef = {
summalyProxy: { type: 'string', nullable: true },
deeplAuthKey: { type: 'string', nullable: true },
deeplIsPro: { type: 'boolean' },
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 },
enableDiscordIntegration: { type: 'boolean' },
discordClientId: { type: 'string', nullable: true },
discordClientSecret: { type: 'string', nullable: true },
enableEmail: { type: 'boolean' },
email: { type: 'string', nullable: true },
smtpSecure: { type: 'boolean' },
@ -231,42 +222,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;
}
if (ps.githubClientId !== undefined) {
set.githubClientId = ps.githubClientId;
}
if (ps.githubClientSecret !== undefined) {
set.githubClientSecret = ps.githubClientSecret;
}
if (ps.enableDiscordIntegration !== undefined) {
set.enableDiscordIntegration = ps.enableDiscordIntegration;
}
if (ps.discordClientId !== undefined) {
set.discordClientId = ps.discordClientId;
}
if (ps.discordClientSecret !== undefined) {
set.discordClientSecret = ps.discordClientSecret;
}
if (ps.enableEmail !== undefined) {
set.enableEmail = ps.enableEmail;
}

View File

@ -170,18 +170,6 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
enableTwitterIntegration: {
type: 'boolean',
optional: false, nullable: false,
},
enableGithubIntegration: {
type: 'boolean',
optional: false, nullable: false,
},
enableDiscordIntegration: {
type: 'boolean',
optional: false, nullable: false,
},
enableServiceWorker: {
type: 'boolean',
optional: false, nullable: false,
@ -226,18 +214,6 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
twitter: {
type: 'boolean',
optional: false, nullable: false,
},
github: {
type: 'boolean',
optional: false, nullable: false,
},
discord: {
type: 'boolean',
optional: false, nullable: false,
},
serviceWorker: {
type: 'boolean',
optional: false, nullable: false,
@ -317,10 +293,6 @@ export default define(meta, paramDef, async (ps, me) => {
defaultDarkTheme: instance.defaultDarkTheme,
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,
enableGithubIntegration: instance.enableGithubIntegration,
enableDiscordIntegration: instance.enableDiscordIntegration,
enableServiceWorker: instance.enableServiceWorker,
translatorAvailable: instance.deeplAuthKey != null,
@ -343,9 +315,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: instance.enableServiceWorker,
miauth: true,
},

View File

@ -15,9 +15,6 @@ import handler from './api-handler.js';
import signup from './private/signup.js';
import signin from './private/signin.js';
import signupPending from './private/signup-pending.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();
@ -81,10 +78,6 @@ router.post('/signup', signup);
router.post('/signin', signin);
router.post('/signup-pending', signupPending);
router.use(discord.routes());
router.use(github.routes());
router.use(twitter.routes());
router.get('/v1/instance/peers', async ctx => {
const instances = await Instances.find({
select: ['host'],

View File

@ -1,298 +0,0 @@
import Koa from 'koa';
import Router from '@koa/router';
import { OAuth2 } from 'oauth';
import { v4 as uuid } from 'uuid';
import { IsNull } from 'typeorm';
import { getJson } from '@/misc/fetch.js';
import config from '@/config/index.js';
import { publishMainStream } from '@/services/stream.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Users, UserProfiles } from '@/models/index.js';
import { ILocalUser } from '@/models/entities/user.js';
import { redisClient } from '@/db/redis.js';
import { I18n } from '@/misc/i18n.js';
import signin from '../common/signin.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 ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
}
const referer = ctx.headers['referer'];
return (normalizeUrl(referer) === normalizeUrl(config.url));
}
const locales = await import('../../../../../../locales/index.js').then(mod => mod.default);
// Init router
const router = new Router();
router.get('/disconnect/discord', async ctx => {
if (!compareOrigin(ctx)) {
ctx.throw(400, 'invalid origin');
return;
}
const userToken = getUserToken(ctx);
if (!userToken) {
ctx.throw(400, 'signin required');
return;
}
const user = await Users.findOneByOrFail({
host: IsNull(),
token: userToken,
});
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
const locale = locales[profile.lang || 'en-US'];
const i18n = new I18n(locale);
delete profile.integrations.discord;
await UserProfiles.update(user.id, {
integrations: profile.integrations,
});
ctx.body = i18n.t('_services._discord.disconnected');
// Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
detail: true,
includeSecrets: true,
}));
});
async function getOAuth2() {
const meta = await fetchMeta(true);
if (meta.enableDiscordIntegration) {
return new OAuth2(
meta.discordClientId!,
meta.discordClientSecret!,
'https://discord.com/',
'api/oauth2/authorize',
'api/oauth2/token');
} else {
return null;
}
}
router.get('/connect/discord', async ctx => {
if (!compareOrigin(ctx)) {
ctx.throw(400, 'invalid origin');
return;
}
const userToken = getUserToken(ctx);
if (!userToken) {
ctx.throw(400, 'signin required');
return;
}
const params = {
redirect_uri: `${config.url}/api/dc/cb`,
scope: ['identify'],
state: uuid(),
response_type: 'code',
};
redisClient.set(userToken, JSON.stringify(params));
const oauth2 = await getOAuth2();
ctx.redirect(oauth2!.getAuthorizeUrl(params));
});
router.get('/signin/discord', async ctx => {
const sessid = uuid();
const params = {
redirect_uri: `${config.url}/api/dc/cb`,
scope: ['identify'],
state: uuid(),
response_type: 'code',
};
ctx.cookies.set('signin_with_discord_sid', sessid, {
path: '/',
secure: config.url.startsWith('https'),
httpOnly: true,
});
redisClient.set(sessid, JSON.stringify(params));
const oauth2 = await getOAuth2();
ctx.redirect(oauth2!.getAuthorizeUrl(params));
});
router.get('/dc/cb', async ctx => {
const userToken = getUserToken(ctx);
const oauth2 = await getOAuth2();
if (!userToken) {
const sessid = ctx.cookies.get('signin_with_discord_sid');
if (!sessid) {
ctx.throw(400, 'invalid session');
return;
}
const code = ctx.query.code;
if (!code || typeof code !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
redisClient.get(sessid, async (_, state) => {
res(JSON.parse(state));
});
});
if (ctx.query.state !== state) {
ctx.throw(400, 'invalid session');
return;
}
const { accessToken, refreshToken, expiresDate } = await new Promise<any>((res, rej) =>
oauth2!.getOAuthAccessToken(code, {
grant_type: 'authorization_code',
redirect_uri,
}, (err, accessToken, refreshToken, result) => {
if (err) {
rej(err);
} else if (result.error) {
rej(result.error);
} else {
res({
accessToken,
refreshToken,
expiresDate: Date.now() + Number(result.expires_in) * 1000,
});
}
}));
const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
'Authorization': `Bearer ${accessToken}`,
})) as Record<string, unknown>;
if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const profile = await UserProfiles.createQueryBuilder()
.where('"integrations"->\'discord\'->>\'id\' = :id', { id })
.andWhere('"userHost" IS NULL')
.getOne();
if (profile == null) {
ctx.throw(404, `There were no FoundKey accounts linked to @${username}#${discriminator}...`);
return;
}
await UserProfiles.update(profile.userId, {
integrations: {
...profile.integrations,
discord: {
id,
accessToken,
refreshToken,
expiresDate,
username,
discriminator,
},
},
});
signin(ctx, await Users.findOneBy({ id: profile.userId }) as ILocalUser, true);
} else {
const code = ctx.query.code;
if (!code || typeof code !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
redisClient.get(userToken, async (_, state) => {
res(JSON.parse(state));
});
});
if (ctx.query.state !== state) {
ctx.throw(400, 'invalid session');
return;
}
const { accessToken, refreshToken, expiresDate } = await new Promise<any>((res, rej) =>
oauth2!.getOAuthAccessToken(code, {
grant_type: 'authorization_code',
redirect_uri,
}, (err, accessToken, refreshToken, result) => {
if (err) {
rej(err);
} else if (result.error) {
rej(result.error);
} else {
res({
accessToken,
refreshToken,
expiresDate: Date.now() + Number(result.expires_in) * 1000,
});
}
}));
const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
'Authorization': `Bearer ${accessToken}`,
})) as Record<string, unknown>;
if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const user = await Users.findOneByOrFail({
host: IsNull(),
token: userToken,
});
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
const locale = locales[profile.lang || 'en-US'];
const i18n = new I18n(locale);
await UserProfiles.update(user.id, {
integrations: {
...profile.integrations,
discord: {
accessToken,
refreshToken,
expiresDate,
id,
username,
discriminator,
},
},
});
ctx.body = i18n.t('_services._discord.connected', {
username,
discriminator,
mkUsername: user.username,
});
// Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
detail: true,
includeSecrets: true,
}));
}
});
export default router;

View File

@ -1,269 +0,0 @@
import Koa from 'koa';
import Router from '@koa/router';
import { OAuth2 } from 'oauth';
import { v4 as uuid } from 'uuid';
import { IsNull } from 'typeorm';
import { getJson } from '@/misc/fetch.js';
import config from '@/config/index.js';
import { publishMainStream } from '@/services/stream.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Users, UserProfiles } from '@/models/index.js';
import { ILocalUser } from '@/models/entities/user.js';
import { redisClient } from '@/db/redis.js';
import signin from '../common/signin.js';
import { I18n } from '@/misc/i18n.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 ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
}
const referer = ctx.headers['referer'];
return (normalizeUrl(referer) === normalizeUrl(config.url));
}
const locales = await import('../../../../../../locales/index.js').then(mod => mod.default);
// Init router
const router = new Router();
router.get('/disconnect/github', async ctx => {
if (!compareOrigin(ctx)) {
ctx.throw(400, 'invalid origin');
return;
}
const userToken = getUserToken(ctx);
if (!userToken) {
ctx.throw(400, 'signin required');
return;
}
const user = await Users.findOneByOrFail({
host: IsNull(),
token: userToken,
});
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
const locale = locales[profile.lang || 'en-US'];
const i18n = new I18n(locale);
delete profile.integrations.github;
await UserProfiles.update(user.id, {
integrations: profile.integrations,
});
ctx.body = i18n.t('_services._github.disconnected');
// Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
detail: true,
includeSecrets: true,
}));
});
async function getOath2() {
const meta = await fetchMeta(true);
if (meta.enableGithubIntegration && meta.githubClientId && meta.githubClientSecret) {
return new OAuth2(
meta.githubClientId,
meta.githubClientSecret,
'https://github.com/',
'login/oauth/authorize',
'login/oauth/access_token');
} else {
return null;
}
}
router.get('/connect/github', async ctx => {
if (!compareOrigin(ctx)) {
ctx.throw(400, 'invalid origin');
return;
}
const userToken = getUserToken(ctx);
if (!userToken) {
ctx.throw(400, 'signin required');
return;
}
const params = {
redirect_uri: `${config.url}/api/gh/cb`,
scope: ['read:user'],
state: uuid(),
};
redisClient.set(userToken, JSON.stringify(params));
const oauth2 = await getOath2();
ctx.redirect(oauth2!.getAuthorizeUrl(params));
});
router.get('/signin/github', async ctx => {
const sessid = uuid();
const params = {
redirect_uri: `${config.url}/api/gh/cb`,
scope: ['read:user'],
state: uuid(),
};
ctx.cookies.set('signin_with_github_sid', sessid, {
path: '/',
secure: config.url.startsWith('https'),
httpOnly: true,
});
redisClient.set(sessid, JSON.stringify(params));
const oauth2 = await getOath2();
ctx.redirect(oauth2!.getAuthorizeUrl(params));
});
router.get('/gh/cb', async ctx => {
const userToken = getUserToken(ctx);
const oauth2 = await getOath2();
if (!userToken) {
const sessid = ctx.cookies.get('signin_with_github_sid');
if (!sessid) {
ctx.throw(400, 'invalid session');
return;
}
const code = ctx.query.code;
if (!code || typeof code !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
redisClient.get(sessid, async (_, state) => {
res(JSON.parse(state));
});
});
if (ctx.query.state !== state) {
ctx.throw(400, 'invalid session');
return;
}
const { accessToken } = await new Promise<any>((res, rej) =>
oauth2!.getOAuthAccessToken(code, {
redirect_uri,
}, (err, accessToken, refresh, result) => {
if (err) {
rej(err);
} else if (result.error) {
rej(result.error);
} else {
res({ accessToken });
}
}));
const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
'Authorization': `bearer ${accessToken}`,
})) as Record<string, unknown>;
if (typeof login !== 'string' || typeof id !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const link = await UserProfiles.createQueryBuilder()
.where('"integrations"->\'github\'->>\'id\' = :id', { id })
.andWhere('"userHost" IS NULL')
.getOne();
if (link == null) {
ctx.throw(404, `There were no FoundKey accounts linked to @${login}...`);
return;
}
signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true);
} else {
const code = ctx.query.code;
if (!code || typeof code !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
redisClient.get(userToken, async (_, state) => {
res(JSON.parse(state));
});
});
if (ctx.query.state !== state) {
ctx.throw(400, 'invalid session');
return;
}
const { accessToken } = await new Promise<any>((res, rej) =>
oauth2!.getOAuthAccessToken(
code,
{ redirect_uri },
(err, accessToken, refresh, result) => {
if (err) {
rej(err);
} else if (result.error) {
rej(result.error);
} else {
res({ accessToken });
}
}));
const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
'Authorization': `bearer ${accessToken}`,
})) as Record<string, unknown>;
if (typeof login !== 'string' || typeof id !== 'string') {
ctx.throw(400, 'invalid session');
return;
}
const user = await Users.findOneByOrFail({
host: IsNull(),
token: userToken,
});
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
const locale = locales[profile.lang || 'en-US'];
const i18n = new I18n(locale);
await UserProfiles.update(user.id, {
integrations: {
...profile.integrations,
github: {
accessToken,
id,
login,
},
},
});
ctx.body = i18n.t('_services._github.connected', {
login,
userName: user.username,
});
// Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
detail: true,
includeSecrets: true,
}));
}
});
export default router;

View File

@ -1,211 +0,0 @@
import 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 { ILocalUser } from '@/models/entities/user.js';
import { redisClient } from '@/db/redis.js';
import signin from '../common/signin.js';
import { I18n } from '@/misc/i18n.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));
}
const locales = await import('../../../../../../locales/index.js').then(mod => mod.default);
// 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 });
const locale = locales[profile.lang || 'en-US'];
const i18n = new I18n(locale);
delete profile.integrations.twitter;
await UserProfiles.update(user.id, {
integrations: profile.integrations,
});
ctx.body = i18n.t('_services._twitter.disconnected');
// 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, `There were no FoundKey accounts linked to @${result.screenName}...`);
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 });
const locale = locales[profile.lang || 'en-US'];
const i18n = new I18n(locale);
await UserProfiles.update(user.id, {
integrations: {
...profile.integrations,
twitter: {
accessToken: result.accessToken,
accessTokenSecret: result.accessTokenSecret,
userId: result.userId,
screenName: result.screenName,
},
},
});
ctx.body = i18n.t('_services._twitter.connected', {
twitterUserName: result.screenName,
userName: user.username,
});
// Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
detail: true,
includeSecrets: true,
}));
}
});
export default router;

View File

@ -72,9 +72,6 @@ const nodeinfo2 = async () => {
enableHcaptcha: meta.enableHcaptcha,
enableRecaptcha: meta.enableRecaptcha,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
enableTwitterIntegration: meta.enableTwitterIntegration,
enableGithubIntegration: meta.enableGithubIntegration,
enableDiscordIntegration: meta.enableDiscordIntegration,
enableEmail: meta.enableEmail,
enableServiceWorker: meta.enableServiceWorker,
proxyAccountName: proxyAccount ? proxyAccount.username : null,

View File

@ -18,7 +18,6 @@
"abort-controller": "3.0.0",
"autobind-decorator": "2.4.0",
"autosize": "5.0.1",
"autwh": "0.1.0",
"blurhash": "1.1.5",
"broadcast-channel": "4.13.0",
"browser-image-resizer": "2.4.1",
@ -86,7 +85,6 @@
"@types/katex": "0.14.0",
"@types/matter-js": "0.17.7",
"@types/mocha": "9.1.1",
"@types/oauth": "0.9.1",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0",
"@types/seedrandom": "3.0.2",

View File

@ -40,11 +40,6 @@
</div>
</div>
</div>
<div class="social _section">
<a v-if="meta && meta.enableTwitterIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/twitter`"><i class="fab fa-twitter" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'Twitter' }) }}</a>
<a v-if="meta && meta.enableGithubIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/github`"><i class="fab fa-github" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'GitHub' }) }}</a>
<a v-if="meta && meta.enableDiscordIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/discord`"><i class="fab fa-discord" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'Discord' }) }}</a>
</div>
</form>
</template>

View File

@ -157,11 +157,6 @@ const menuDef = $computed(() => [{
text: i18n.ts.relays,
to: '/admin/relays',
active: props.initialPage === 'relays',
}, {
icon: 'fas fa-share-alt',
text: i18n.ts.integration,
to: '/admin/integrations',
active: props.initialPage === 'integrations',
}, {
icon: 'fas fa-ban',
text: i18n.ts.instanceBlocking,
@ -199,7 +194,6 @@ const component = $computed(() => {
case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue'));
case 'security': return defineAsyncComponent(() => import('./security.vue'));
case 'relays': return defineAsyncComponent(() => import('./relays.vue'));
case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
default: return null;

View File

@ -1,59 +0,0 @@
<template>
<FormSuspense :p="init">
<div class="_formRoot">
<FormSwitch v-model="enableDiscordIntegration" class="_formBlock">
<template #label>{{ i18n.ts.enable }}</template>
</FormSwitch>
<template v-if="enableDiscordIntegration">
<FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo>
<FormInput v-model="discordClientId" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Client ID</template>
</FormInput>
<FormInput v-model="discordClientSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Client Secret</template>
</FormInput>
</template>
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
</div>
</FormSuspense>
</template>
<script lang="ts" setup>
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
let uri: string = $ref('');
let enableDiscordIntegration: boolean = $ref(false);
let discordClientId: string | null = $ref(null);
let discordClientSecret: string | null = $ref(null);
async function init() {
const meta = await os.api('admin/meta');
uri = meta.uri;
enableDiscordIntegration = meta.enableDiscordIntegration;
discordClientId = meta.discordClientId;
discordClientSecret = meta.discordClientSecret;
}
function save() {
os.apiWithDialog('admin/update-meta', {
enableDiscordIntegration,
discordClientId,
discordClientSecret,
}).then(() => {
fetchInstance();
});
}
</script>

View File

@ -1,59 +0,0 @@
<template>
<FormSuspense :p="init">
<div class="_formRoot">
<FormSwitch v-model="enableGithubIntegration" class="_formBlock">
<template #label>{{ i18n.ts.enable }}</template>
</FormSwitch>
<template v-if="enableGithubIntegration">
<FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo>
<FormInput v-model="githubClientId" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Client ID</template>
</FormInput>
<FormInput v-model="githubClientSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Client Secret</template>
</FormInput>
</template>
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
</div>
</FormSuspense>
</template>
<script lang="ts" setup>
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
let uri: string = $ref('');
let enableGithubIntegration: boolean = $ref(false);
let githubClientId: string | null = $ref(null);
let githubClientSecret: string | null = $ref(null);
async function init() {
const meta = await os.api('admin/meta');
uri = meta.uri;
enableGithubIntegration = meta.enableGithubIntegration;
githubClientId = meta.githubClientId;
githubClientSecret = meta.githubClientSecret;
}
function save() {
os.apiWithDialog('admin/update-meta', {
enableGithubIntegration,
githubClientId,
githubClientSecret,
}).then(() => {
fetchInstance();
});
}
</script>

View File

@ -1,59 +0,0 @@
<template>
<FormSuspense :p="init">
<div class="_formRoot">
<FormSwitch v-model="enableTwitterIntegration" class="_formBlock">
<template #label>{{ i18n.ts.enable }}</template>
</FormSwitch>
<template v-if="enableTwitterIntegration">
<FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo>
<FormInput v-model="twitterConsumerKey" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Consumer Key</template>
</FormInput>
<FormInput v-model="twitterConsumerSecret" class="_formBlock">
<template #prefix><i class="fas fa-key"></i></template>
<template #label>Consumer Secret</template>
</FormInput>
</template>
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
</div>
</FormSuspense>
</template>
<script lang="ts" setup>
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/ui/button.vue';
import FormInfo from '@/components/ui/info.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
let uri: string = $ref('');
let enableTwitterIntegration: boolean = $ref(false);
let twitterConsumerKey: string | null = $ref(null);
let twitterConsumerSecret: string | null = $ref(null);
async function init() {
const meta = await os.api('admin/meta');
uri = meta.uri;
enableTwitterIntegration = meta.enableTwitterIntegration;
twitterConsumerKey = meta.twitterConsumerKey;
twitterConsumerSecret = meta.twitterConsumerSecret;
}
function save() {
os.apiWithDialog('admin/update-meta', {
enableTwitterIntegration,
twitterConsumerKey,
twitterConsumerSecret,
}).then(() => {
fetchInstance();
});
}
</script>

View File

@ -1,54 +0,0 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader/></template>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<FormFolder class="_formBlock">
<template #icon><i class="fab fa-twitter"></i></template>
<template #label>Twitter</template>
<template #suffix>{{ enableTwitterIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
<XTwitter/>
</FormFolder>
<FormFolder class="_formBlock">
<template #icon><i class="fab fa-github"></i></template>
<template #label>GitHub</template>
<template #suffix>{{ enableGithubIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
<XGithub/>
</FormFolder>
<FormFolder class="_formBlock">
<template #icon><i class="fab fa-discord"></i></template>
<template #label>Discord</template>
<template #suffix>{{ enableDiscordIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
<XDiscord/>
</FormFolder>
</FormSuspense>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import XTwitter from './integrations.twitter.vue';
import XGithub from './integrations.github.vue';
import XDiscord from './integrations.discord.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormFolder from '@/components/form/folder.vue';
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;
}
definePageMetadata({
title: i18n.ts.integration,
icon: 'fas fa-share-alt',
});
</script>

View File

@ -89,11 +89,6 @@ const menuDef = computed(() => [{
text: i18n.ts.email,
to: '/settings/email',
active: props.initialPage === 'email',
}, {
icon: 'fas fa-share-alt',
text: i18n.ts.integration,
to: '/settings/integration',
active: props.initialPage === 'integration',
}, {
icon: 'fas fa-lock',
text: i18n.ts.security,
@ -200,7 +195,6 @@ const component = computed(() => {
case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue'));
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
case 'instance-mute': return defineAsyncComponent(() => import('./instance-mute.vue'));
case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
case 'security': return defineAsyncComponent(() => import('./security.vue'));
case '2fa': return defineAsyncComponent(() => import('./2fa.vue'));
case 'api': return defineAsyncComponent(() => import('./api.vue'));

View File

@ -1,95 +0,0 @@
<template>
<div class="_formRoot">
<FormSection v-if="instance.enableTwitterIntegration">
<template #label><i class="fab fa-twitter"></i> Twitter</template>
<p v-if="integrations.twitter">{{ i18n.ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
<MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ i18n.ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectTwitter">{{ i18n.ts.connectService }}</MkButton>
</FormSection>
<FormSection v-if="instance.enableDiscordIntegration">
<template #label><i class="fab fa-discord"></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 }}#{{ integrations.discord.discriminator }}</a></p>
<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="fab fa-github"></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></p>
<MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ i18n.ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectGithub">{{ i18n.ts.connectService }}</MkButton>
</FormSection>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue';
import { apiUrl } from '@/config';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/ui/button.vue';
import { $i } from '@/account';
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);
const integrations = computed(() => $i!.integrations);
function openWindow(service: string, type: string) {
return window.open(`${apiUrl}/${type}/${service}`,
`${service}_${type}_window`,
'height=570, width=520',
);
}
function connectTwitter() {
twitterForm.value = openWindow('twitter', 'connect');
}
function disconnectTwitter() {
openWindow('twitter', 'disconnect');
}
function connectDiscord() {
discordForm.value = openWindow('discord', 'connect');
}
function disconnectDiscord() {
openWindow('discord', 'disconnect');
}
function connectGithub() {
githubForm.value = openWindow('github', 'connect');
}
function disconnectGithub() {
openWindow('github', 'disconnect');
}
onMounted(() => {
document.cookie = `igi=${$i!.token}; path=/;` +
' max-age=31536000;' +
(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();
}
if (integrations.value.github) {
if (githubForm.value) githubForm.value.close();
}
});
});
definePageMetadata({
title: i18n.ts.integration,
icon: 'fas fa-share-alt',
});
</script>

View File

@ -2172,15 +2172,6 @@ __metadata:
languageName: node
linkType: hard
"@types/oauth@npm:0.9.1, @types/oauth@npm:^0.9.1":
version: 0.9.1
resolution: "@types/oauth@npm:0.9.1"
dependencies:
"@types/node": "*"
checksum: 5c079611b455eff58fba6358e028b191a1e65475600f8ed8d98c1696fedcfb0290aa6c6a19cf50f21a9e2d816ecb43a19f910900d91f8ba3727e33c48f97d7f3
languageName: node
linkType: hard
"@types/opentype.js@npm:^1.3.4":
version: 1.3.4
resolution: "@types/opentype.js@npm:1.3.4"
@ -3400,15 +3391,6 @@ __metadata:
languageName: node
linkType: hard
"autwh@npm:0.1.0":
version: 0.1.0
resolution: "autwh@npm:0.1.0"
dependencies:
oauth: 0.9.15
checksum: 5ca904d43421e7475de29adfda65ca769105cbf597c9c43fef934f20b0daa331621157cdf34513eae5f492e1b4fc6443f37438da08bf2ba1c4e3e7d6d3f3f738
languageName: node
linkType: hard
"aws-sdk@npm:2.1165.0":
version: 2.1165.0
resolution: "aws-sdk@npm:2.1165.0"
@ -3591,7 +3573,6 @@ __metadata:
"@types/node": 18.7.16
"@types/node-fetch": 3.0.3
"@types/nodemailer": 6.4.5
"@types/oauth": ^0.9.1
"@types/opentype.js": ^1.3.4
"@types/pg": ^8.6.5
"@types/pug": 2.0.6
@ -3622,7 +3603,6 @@ __metadata:
ajv: 8.11.0
archiver: 5.3.1
autobind-decorator: 2.4.0
autwh: 0.1.0
aws-sdk: 2.1165.0
bcryptjs: 2.4.3
blurhash: 1.1.5
@ -4563,7 +4543,6 @@ __metadata:
"@types/katex": 0.14.0
"@types/matter-js": 0.17.7
"@types/mocha": 9.1.1
"@types/oauth": 0.9.1
"@types/punycode": 2.1.0
"@types/qrcode": 1.5.0
"@types/seedrandom": 3.0.2
@ -4578,7 +4557,6 @@ __metadata:
abort-controller: 3.0.0
autobind-decorator: 2.4.0
autosize: 5.0.1
autwh: 0.1.0
blurhash: 1.1.5
broadcast-channel: 4.13.0
browser-image-resizer: 2.4.1
@ -12135,13 +12113,6 @@ __metadata:
languageName: node
linkType: hard
"oauth@npm:0.9.15":
version: 0.9.15
resolution: "oauth@npm:0.9.15"
checksum: 957c0d8d85300398dcb0e293953650c0fc3facc795bee8228238414f19f59cef5fd4ee8d17a972c142924c10c5f6ec50ef80f77f4a6cc6e3c98f9d22c027801c
languageName: node
linkType: hard
"object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1":
version: 4.1.1
resolution: "object-assign@npm:4.1.1"