wip masto api

co-authored-by: cutls <web-pro@cutls.com>
This commit is contained in:
cutestnekoaqua 2023-02-07 22:56:07 +01:00
parent 9ca850be06
commit c68c01a09e
No known key found for this signature in database
GPG Key ID: 6BF0964A5069C1E0
8 changed files with 219 additions and 12 deletions

View File

@ -20,6 +20,7 @@
"gulp": "gulp build", "gulp": "gulp build",
"watch": "pnpm run dev", "watch": "pnpm run dev",
"dev": "pnpm node ./scripts/dev.js", "dev": "pnpm node ./scripts/dev.js",
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
"lint": "pnpm -r run lint", "lint": "pnpm -r run lint",
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts", "cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "cypress run", "cy:run": "cypress run",

View File

@ -81,6 +81,7 @@
"koa-send": "5.0.1", "koa-send": "5.0.1",
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "7.0.2", "koa-views": "7.0.2",
"@cutls/megalodon": "^5.1.1",
"mfm-js": "0.23.2", "mfm-js": "0.23.2",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"mocha": "10.2.0", "mocha": "10.2.0",

View File

@ -765,17 +765,17 @@ export interface IEndpointMeta {
} }
export interface IEndpoint { export interface IEndpoint {
name: string; name: string,
exec: any; exec: any, // TODO: may be obosolete @ThatOneCalculator
meta: IEndpointMeta; meta: IEndpointMeta,
params: Schema; params: Schema,
} }
const endpoints: IEndpoint[] = eps.map(([name, ep]) => { const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => {
return { return {
name: name, name: name,
exec: ep.default, exec: ep.default,
meta: ep.meta || {}, meta: ep.meta ?? {},
params: ep.paramDef, params: ep.paramDef,
}; };
}); });

View File

@ -7,6 +7,7 @@ import Router from "@koa/router";
import multer from "@koa/multer"; import multer from "@koa/multer";
import bodyParser from "koa-bodyparser"; import bodyParser from "koa-bodyparser";
import cors from "@koa/cors"; import cors from "@koa/cors";
import { apiMastodonCompatible } from './mastodon/ApiMastodonCompatibleService.js';
import { Instances, AccessTokens, Users } from "@/models/index.js"; import { Instances, AccessTokens, Users } from "@/models/index.js";
import config from "@/config/index.js"; import config from "@/config/index.js";
import endpoints from "./endpoints.js"; import endpoints from "./endpoints.js";
@ -57,6 +58,8 @@ const upload = multer({
// Init router // Init router
const router = new Router(); const router = new Router();
apiMastodonCompatible(router);
/** /**
* Register endpoint handlers * Register endpoint handlers
*/ */

View File

@ -0,0 +1,111 @@
import Router from "@koa/router";
import megalodon, { MegalodonInterface } from 'megalodon';
function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface {
const accessTokenArr = authorization?.split(' ') ?? [null];
const accessToken = accessTokenArr[accessTokenArr.length - 1];
const generator = (megalodon as any).default
const client = generator('misskey', BASE_URL, accessToken) as MegalodonInterface;
return client
}
const readScope = [
'read:account',
'read:drive',
'read:blocks',
'read:favorites',
'read:following',
'read:messaging',
'read:mutes',
'read:notifications',
'read:reactions',
'read:pages',
'read:page-likes',
'read:user-groups',
'read:channels',
'read:gallery',
'read:gallery-likes'
]
const writeScope = [
'write:account',
'write:drive',
'write:blocks',
'write:favorites',
'write:following',
'write:messaging',
'write:mutes',
'write:notes',
'write:notifications',
'write:reactions',
'write:votes',
'write:pages',
'write:page-likes',
'write:user-groups',
'write:channels',
'write:gallery',
'write:gallery-likes'
]
export function apiMastodonCompatible(router: Router): void {
router.post('/v1/apps', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.req;
try {
let scope = body.scopes
if (typeof scope === 'string') scope = scope.split(' ')
const pushScope: string[] = []
for (const s of scope) {
if (s.match(/^read/)) for (const r of readScope) pushScope.push(r)
if (s.match(/^write/)) for (const r of writeScope) pushScope.push(r)
}
let red = body.redirect_uris
if (red === 'urn:ietf:wg:oauth:2.0:oob') {
red = 'https://thedesk.top/hello.html'
}
const appData = await client.registerApp(body.client_name, { scopes: pushScope, redirect_uris: red, website: body.website });
ctx.body = {
id: appData.id,
name: appData.name,
website: appData.website,
redirect_uri: appData.redirectUri,
client_id: Buffer.from(appData.url || '').toString('base64'),
client_secret: appData.clientSecret,
}
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.get('/v1/accounts/verify_credentials', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.verifyAccountCredentials();
ctx.body = data.data;
} catch (e: any) {
console.error(e)
reply.code(401);
return e.response.data;
}
});
router.get('/v1/custom_emojis', async (ctx) => {
const BASE_URL = request.protocol + '://' + request.hostname;
const accessTokens = request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getInstanceCustomEmojis();
return data.data;
} catch (e: any) {
console.error(e)
reply.code(401);
return e.response.data;
}
});
}

View File

@ -20,6 +20,7 @@ import { createTemp } from "@/misc/create-temp.js";
import { publishMainStream } from "@/services/stream.js"; import { publishMainStream } from "@/services/stream.js";
import * as Acct from "@/misc/acct.js"; import * as Acct from "@/misc/acct.js";
import { envOption } from "@/env.js"; import { envOption } from "@/env.js";
import megalodon, { MegalodonInterface } from 'megalodon';
import activityPub from "./activitypub.js"; import activityPub from "./activitypub.js";
import nodeinfo from "./nodeinfo.js"; import nodeinfo from "./nodeinfo.js";
import wellKnown from "./well-known.js"; import wellKnown from "./well-known.js";
@ -133,6 +134,26 @@ router.get("/verify-email/:code", async (ctx) => {
} }
}); });
router.get("/oauth/authorize", async (ctx) => {
const client_id = ctx.request.query.client_id;
console.log(ctx.request.req);
ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString());
});
router.get("/oauth/token", async (ctx) => {
const body: any = ctx.request.body
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const generator = (megalodon as any).default;
const client = generator('misskey', BASE_URL, null) as MegalodonInterface;
try {
ctx.body = await client.fetchAccessToken(null, body.client_secret, body.code);
} catch (err: any) {
console.error(err);
ctx.status = 401;
ctx.body = err.response.data;
}
});
// Register router // Register router
app.use(router.routes()); app.use(router.routes());

View File

@ -79,7 +79,7 @@ export default defineComponent({
accepted() { accepted() {
this.state = 'accepted'; this.state = 'accepted';
if (this.session.app.callbackUrl) { if (this.session.app.callbackUrl) {
location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`; location.href = `${this.session.app.callbackUrl}?token=${this.session.token}&code=${this.session.token}`;
} }
}, onLogin(res) { }, onLogin(res) {
login(res.i); login(res.i);

View File

@ -163,6 +163,7 @@ importers:
koa-send: 5.0.1 koa-send: 5.0.1
koa-slow: 2.1.0 koa-slow: 2.1.0
koa-views: 7.0.2 koa-views: 7.0.2
megalodon: ^5.1.1
mfm-js: 0.23.2 mfm-js: 0.23.2
mime-types: 2.1.35 mime-types: 2.1.35
mocha: 10.2.0 mocha: 10.2.0
@ -278,6 +279,7 @@ importers:
koa-send: 5.0.1 koa-send: 5.0.1
koa-slow: 2.1.0 koa-slow: 2.1.0
koa-views: 7.0.2_6tybghmia4wsnt33xeid7y4rby koa-views: 7.0.2_6tybghmia4wsnt33xeid7y4rby
megalodon: 5.1.1
mfm-js: 0.23.2 mfm-js: 0.23.2
mime-types: 2.1.35 mime-types: 2.1.35
mocha: 10.2.0 mocha: 10.2.0
@ -2348,7 +2350,6 @@ packages:
resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==} resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==}
dependencies: dependencies:
'@types/node': 18.11.18 '@types/node': 18.11.18
dev: true
/@types/offscreencanvas/2019.3.0: /@types/offscreencanvas/2019.3.0:
resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==} resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
@ -2529,6 +2530,12 @@ packages:
'@types/node': 18.11.18 '@types/node': 18.11.18
dev: true dev: true
/@types/ws/8.5.4:
resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
dependencies:
'@types/node': 18.11.18
dev: false
/@types/yauzl/2.10.0: /@types/yauzl/2.10.0:
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
requiresBuild: true requiresBuild: true
@ -3246,7 +3253,7 @@ packages:
/axios/0.24.0: /axios/0.24.0:
resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
dependencies: dependencies:
follow-redirects: 1.15.2 follow-redirects: 1.15.2_debug@4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: false dev: false
@ -3254,11 +3261,21 @@ packages:
/axios/0.25.0_debug@4.3.4: /axios/0.25.0_debug@4.3.4:
resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
dependencies: dependencies:
follow-redirects: 1.15.2 follow-redirects: 1.15.2_debug@4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: true dev: true
/axios/1.2.2:
resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==}
dependencies:
follow-redirects: 1.15.2_debug@4.3.4
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
/babel-eslint/10.1.0_eslint@8.31.0: /babel-eslint/10.1.0_eslint@8.31.0:
resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==} resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -4940,7 +4957,6 @@ packages:
/dayjs/1.11.7: /dayjs/1.11.7:
resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
dev: true
/debug/2.6.9: /debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
@ -6197,7 +6213,7 @@ packages:
readable-stream: 2.3.7 readable-stream: 2.3.7
dev: false dev: false
/follow-redirects/1.15.2: /follow-redirects/1.15.2_debug@4.3.4:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
peerDependencies: peerDependencies:
@ -6205,6 +6221,8 @@ packages:
peerDependenciesMeta: peerDependenciesMeta:
debug: debug:
optional: true optional: true
dependencies:
debug: 4.3.4
/for-each/0.3.3: /for-each/0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@ -8654,6 +8672,30 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/megalodon/5.1.1:
resolution: {integrity: sha512-zsYzzmogmk9lnXzGk3kKv58LUmZVFMebiya/1CZqZYnBVxq18Ep8l1AU41o+INANMqYxG+hAQvJhE+Z5dcUabQ==}
engines: {node: '>=15.0.0'}
dependencies:
'@types/oauth': 0.9.1
'@types/ws': 8.5.4
axios: 1.2.2
dayjs: 1.11.7
form-data: 4.0.0
https-proxy-agent: 5.0.1
oauth: 0.10.0
object-assign-deep: 0.4.0
parse-link-header: 2.0.0
socks-proxy-agent: 7.0.0
typescript: 4.9.4
uuid: 9.0.0
ws: 8.12.0
transitivePeerDependencies:
- bufferutil
- debug
- supports-color
- utf-8-validate
dev: false
/merge-stream/2.0.0: /merge-stream/2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@ -9321,6 +9363,11 @@ packages:
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
dev: false dev: false
/object-assign-deep/0.4.0:
resolution: {integrity: sha512-54Uvn3s+4A/cMWx9tlRez1qtc7pN7pbQ+Yi7mjLjcBpWLlP+XbSHiHbQW6CElDiV4OvuzqnMrBdkgxI1mT8V/Q==}
engines: {node: '>=6'}
dev: false
/object-assign/4.1.1: /object-assign/4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -9613,6 +9660,12 @@ packages:
error-ex: 1.3.2 error-ex: 1.3.2
dev: false dev: false
/parse-link-header/2.0.0:
resolution: {integrity: sha512-xjU87V0VyHZybn2RrCX5TIFGxTVZE6zqqZWMPlIKiSKuWh/X5WZdt+w1Ki1nXB+8L/KtL+nZ4iq+sfI6MrhhMw==}
dependencies:
xtend: 4.0.2
dev: false
/parse-node-version/1.0.1: /parse-node-version/1.0.1:
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
@ -10254,6 +10307,10 @@ packages:
resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==}
dev: true dev: true
/proxy-from-env/1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
/ps-tree/1.2.0: /ps-tree/1.2.0:
resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
@ -12943,6 +13000,19 @@ packages:
optional: true optional: true
dev: false dev: false
/ws/8.12.0:
resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/xev/3.0.2: /xev/3.0.2:
resolution: {integrity: sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==} resolution: {integrity: sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==}
dev: false dev: false