diff --git a/packages/backend/src/queue/processors/webhook-deliver.ts b/packages/backend/src/queue/processors/webhook-deliver.ts index a130fcd382..2edf4f6960 100644 --- a/packages/backend/src/queue/processors/webhook-deliver.ts +++ b/packages/backend/src/queue/processors/webhook-deliver.ts @@ -20,6 +20,7 @@ export default async (job: Bull.Job) => { "X-Calckey-Host": config.host, "X-Calckey-Hook-Id": job.data.webhookId, "X-Calckey-Hook-Secret": job.data.secret, + 'Content-Type': 'application/json' }, body: JSON.stringify({ hookId: job.data.webhookId, diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 96b03a4f66..4eb87a6145 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -22,6 +22,35 @@ import github from "./service/github.js"; import twitter from "./service/twitter.js"; import { koaBody } from "koa-body"; +export enum IdType { + CalckeyId, + MastodonId +}; + +export function convertId(idIn: string, idConvertTo: IdType ) { + let idArray = [] + switch (idConvertTo) { + case IdType.MastodonId: + idArray = [...idIn].map(item => item.charCodeAt(0)); + idArray = idArray.map(item => { + if (item.toString().length < 3) { + return `0${item.toString()}` + } + else return item.toString() + }); + return idArray.join(''); + case IdType.CalckeyId: + for (let i = 0; i < idIn.length; i += 3) { + if ((idIn.length % 3) !== 0) { + idIn = `0${idIn}` + } + idArray.push(idIn.slice(i, i+3)); + } + idArray = idArray.map(item => String.fromCharCode(item)); + return idArray.join(''); + } +}; + // Init app const app = new Koa(); diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 312e381359..1c5e31fe8d 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -4,8 +4,9 @@ import Router from "@koa/router"; import { FindOptionsWhere, IsNull } from "typeorm"; import { getClient } from "../ApiMastodonCompatibleService.js"; import { argsToBools, limitToInt } from "./timeline.js"; +import { convertId, IdType } from "../../index.js"; -const relationshopModel = { +const relationshipModel = { id: "", following: false, followed_by: false, @@ -29,7 +30,8 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.verifyAccountCredentials(); - const acct = data.data; + let acct = data.data; + acct.id = convertId(acct.id, IdType.MastodonId); acct.url = `${BASE_URL}/@${acct.url}`; acct.note = ""; acct.avatar_static = acct.avatar; @@ -42,6 +44,7 @@ export function apiAccountMastodon(router: Router): void { sensitive: false, language: "", }; + console.log(acct); ctx.body = acct; } catch (e: any) { console.error(e); @@ -58,7 +61,9 @@ export function apiAccountMastodon(router: Router): void { const data = await client.updateCredentials( (ctx.request as any).body as any, ); - ctx.body = data.data; + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -71,8 +76,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.search((request.query as any).acct, 'accounts'); - ctx.body = data.data.accounts[0]; + const data = await client.search((ctx.request.query as any).acct, 'accounts'); + let resp = data.data.accounts[0]; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -87,8 +94,11 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getAccount(ctx.params.id); - ctx.body = data.data; + const calcId = convertId(ctx.params.id, IdType.CalckeyId); + const data = await client.getAccount(calcId); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -105,10 +115,20 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getAccountStatuses( - ctx.params.id, + convertId(ctx.params.id, IdType.CalckeyId), argsToBools(limitToInt(ctx.query as any)), ); - ctx.body = data.data; + let resp = data.data; + for (let statIdx = 0; statIdx < resp.length; statIdx++) { + resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); + resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null; + resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null; + let mentions = resp[statIdx].mentions + for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { + resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId); + } + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -125,10 +145,14 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getAccountFollowers( - ctx.params.id, - ctx.query as any, + convertId(ctx.params.id, IdType.CalckeyId), + limitToInt(ctx.query as any), ); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -145,10 +169,14 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getAccountFollowing( - ctx.params.id, - ctx.query as any, + convertId(ctx.params.id, IdType.CalckeyId), + limitToInt(ctx.query as any), ); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -181,10 +209,11 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.followAccount(ctx.params.id); - const acct = data.data; + const data = await client.followAccount(convertId(ctx.params.id, IdType.CalckeyId)); + let acct = data.data; acct.following = true; - ctx.body = data.data; + acct.id = convertId(acct.id, IdType.MastodonId); + ctx.body = acct; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -200,10 +229,11 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unfollowAccount(ctx.params.id); - const acct = data.data; + const data = await client.unfollowAccount(convertId(ctx.params.id, IdType.CalckeyId)); + let acct = data.data; + acct.id = convertId(acct.id, IdType.MastodonId); acct.following = false; - ctx.body = data.data; + ctx.body = acct; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -219,8 +249,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.blockAccount(ctx.params.id); - ctx.body = data.data; + const data = await client.blockAccount(convertId(ctx.params.id, IdType.CalckeyId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -236,8 +268,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unblockAccount(ctx.params.id); - ctx.body = data.data; + const data = await client.unblockAccount(convertId(ctx.params.id, IdType.MastodonId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -254,10 +288,12 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.muteAccount( - ctx.params.id, + convertId(ctx.params.id, IdType.CalckeyId), (ctx.request as any).body as any, ); - ctx.body = data.data; + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -273,8 +309,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unmuteAccount(ctx.params.id); - ctx.body = data.data; + const data = await client.unmuteAccount(convertId(ctx.params.id, IdType.CalckeyId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -290,16 +328,23 @@ export function apiAccountMastodon(router: Router): void { let users; try { // TODO: this should be body - const idsRaw = ctx.request.query ? ctx.request.query["id[]"] : null; - const ids = typeof idsRaw === "string" ? [idsRaw] : idsRaw; + let ids = ctx.request.query ? ctx.request.query["id[]"] : null; + if (typeof ids === "string") { + ids = [ids]; + } users = ids; - relationshopModel.id = idsRaw?.toString() || "1"; - if (!idsRaw) { - ctx.body = [relationshopModel]; + relationshipModel.id = ids?.toString() || "1"; + if (!ids) { + ctx.body = [relationshipModel]; return; } + const data = await client.getRelationships(ids); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); let data = e.response.data; @@ -315,7 +360,17 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = (await client.getBookmarks(ctx.query as any)) as any; - ctx.body = data.data; + let resp = data.data; + for (let statIdx = 0; statIdx < resp.length; statIdx++) { + resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); + resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null; + resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null; + let mentions = resp[statIdx].mentions + for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { + resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId); + } + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -329,7 +384,17 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getFavourites(ctx.query as any); - ctx.body = data.data; + let resp = data.data; + for (let statIdx = 0; statIdx < resp.length; statIdx++) { + resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); + resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null; + resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null; + let mentions = resp[statIdx].mentions + for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { + resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId); + } + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -343,7 +408,11 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getMutes(ctx.query as any); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -357,7 +426,11 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getBlocks(ctx.query as any); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -365,7 +438,7 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get("/v1/follow_ctxs", async (ctx) => { + router.get("/v1/follow_requests", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -373,7 +446,11 @@ export function apiAccountMastodon(router: Router): void { const data = await client.getFollowRequests( ((ctx.query as any) || { limit: 20 }).limit, ); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -382,14 +459,16 @@ export function apiAccountMastodon(router: Router): void { } }); router.post<{ Params: { id: string } }>( - "/v1/follow_ctxs/:id/authorize", + "/v1/follow_requests/:id/authorize", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.acceptFollowRequest(ctx.params.id); - ctx.body = data.data; + const data = await client.acceptFollowRequest(convertId(ctx.params.id, IdType.CalckeyId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -399,14 +478,16 @@ export function apiAccountMastodon(router: Router): void { }, ); router.post<{ Params: { id: string } }>( - "/v1/follow_ctxs/:id/reject", + "/v1/follow_requests/:id/reject", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.rejectFollowRequest(ctx.params.id); - ctx.body = data.data; + const data = await client.rejectFollowRequest(convertId(ctx.params.id, IdType.CalckeyId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 6f7dcfd390..174aaf222f 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -31,6 +31,7 @@ import webServer from "./web/index.js"; import { initializeStreamingServer } from "./api/streaming.js"; import { koaBody } from "koa-body"; import removeTrailingSlash from "koa-remove-trailing-slashes"; +import {v4 as uuid} from "uuid"; export const serverLogger = new Logger("server", "gray", false); @@ -78,6 +79,7 @@ const mastoRouter = new Router(); mastoRouter.use( koaBody({ urlencoded: true, + multipart: true, }), ); @@ -170,21 +172,33 @@ mastoRouter.get("/oauth/authorize", async (ctx) => { mastoRouter.post("/oauth/token", async (ctx) => { const body: any = ctx.request.body || ctx.request.query; - console.log('token-request', body) - let client_id: any = ctx.request.query.client_id; + console.log('token-request', body); + console.log('token-query', ctx.request.query); + if (body.redirect_uri.startsWith('com.tapbots') && body.grant_type === 'client_credentials') { + const ret = { + access_token: uuid(), + token_type: "Bearer", + scope: "read", + created_at: Math.floor(new Date().getTime() / 1000), + }; + ctx.body = ret; + return; + } + let client_id: any = body.client_id; const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; const generator = (megalodon as any).default; const client = generator("misskey", BASE_URL, null) as MegalodonInterface; let m = null; let token = null; if (body.code) { - m = body.code.match(/^([a-zA-Z0-9]{8})([a-zA-Z0-9]{4})([a-zA-Z0-9]{4})([a-zA-Z0-9]{4})([a-zA-Z0-9]{12})/); - if (!m.length) { - ctx.body = { error: "Invalid code" }; - return; - } - token = `${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}` + //m = body.code.match(/^([a-zA-Z0-9]{8})([a-zA-Z0-9]{4})([a-zA-Z0-9]{4})([a-zA-Z0-9]{4})([a-zA-Z0-9]{12})/); + //if (!m.length) { + // ctx.body = { error: "Invalid code" }; + // return; + //} + //token = `${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}` console.log(body.code, token) + token = body.code } if (client_id instanceof Array) { client_id = client_id.toString(); diff --git a/packages/client/src/pages/auth.vue b/packages/client/src/pages/auth.vue index b7ea082c64..4ad7d08c66 100644 --- a/packages/client/src/pages/auth.vue +++ b/packages/client/src/pages/auth.vue @@ -88,11 +88,8 @@ export default defineComponent({ const getUrlParams = () => window.location.search.substring(1).split('&').reduce((result, query) => { const [k, v] = query.split('='); result[k] = decodeURI(v); return result; }, {}); const isMastodon = !!getUrlParams().mastodon if (this.session.app.callbackUrl && isMastodon) { - const state = getUrlParams().state - const stateParam = `&state=${state}` - const tokenRaw = this.session.token - const token = tokenRaw.replaceAll('-', '') - location.href = `${this.session.app.callbackUrl}?code=${token}${stateParam}`; + const stateParam = !!getUrlParams().state ? `&state=$(getUrlParams().state)` : ''; + location.href = `${this.session.app.callbackUrl}?code=${this.session.token}${stateParam}`; } else if (this.session.app.callbackUrl) { const url = new URL(this.session.app.callbackUrl); if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');