more mastodon work
This commit is contained in:
parent
74903846b6
commit
05acb51da2
|
@ -98,6 +98,7 @@
|
||||||
"punycode": "2.1.1",
|
"punycode": "2.1.1",
|
||||||
"pureimage": "0.3.15",
|
"pureimage": "0.3.15",
|
||||||
"qrcode": "1.5.1",
|
"qrcode": "1.5.1",
|
||||||
|
"qs": "6.9.7",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.18.0",
|
"re2": "1.18.0",
|
||||||
|
@ -158,6 +159,7 @@
|
||||||
"@types/pug": "2.0.6",
|
"@types/pug": "2.0.6",
|
||||||
"@types/punycode": "2.1.0",
|
"@types/punycode": "2.1.0",
|
||||||
"@types/qrcode": "1.5.0",
|
"@types/qrcode": "1.5.0",
|
||||||
|
"@types/qs": "6.9.7",
|
||||||
"@types/random-seed": "0.3.3",
|
"@types/random-seed": "0.3.3",
|
||||||
"@types/ratelimiter": "3.4.4",
|
"@types/ratelimiter": "3.4.4",
|
||||||
"@types/redis": "4.0.11",
|
"@types/redis": "4.0.11",
|
||||||
|
|
|
@ -71,26 +71,8 @@ export function apiAccountMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
let userArray = ctx.query.acct?.toString().split("@");
|
const data = await client.search((request.query as any).acct, 'accounts');
|
||||||
let userid;
|
ctx.body = data.data.accounts[0];
|
||||||
if (userArray === undefined) {
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = { error: "no user specified" };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (userArray.length === 1) {
|
|
||||||
const q: FindOptionsWhere<User> = {
|
|
||||||
usernameLower: userArray[0].toLowerCase(),
|
|
||||||
host: IsNull(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const user = await Users.findOneBy(q);
|
|
||||||
userid = user?.id;
|
|
||||||
} else {
|
|
||||||
userid = (await resolveUser(userArray[0], userArray[1])).id;
|
|
||||||
}
|
|
||||||
const data = await client.getAccount(userid ? userid : "");
|
|
||||||
ctx.body = data.data;
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error(e.response.data);
|
console.error(e.response.data);
|
||||||
|
|
|
@ -44,12 +44,10 @@ const writeScope = [
|
||||||
export function apiAuthMastodon(router: Router): void {
|
export function apiAuthMastodon(router: Router): void {
|
||||||
router.post("/v1/apps", async (ctx) => {
|
router.post("/v1/apps", async (ctx) => {
|
||||||
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
||||||
const accessTokens = ctx.request.headers.authorization;
|
const client = getClient(BASE_URL, '');
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const body: any = ctx.request.body || ctx.request.query;
|
||||||
const body: any = ctx.request.body;
|
|
||||||
try {
|
try {
|
||||||
let scope = body.scopes;
|
let scope = body.scopes;
|
||||||
console.log(body);
|
|
||||||
if (typeof scope === "string") scope = scope.split(" ");
|
if (typeof scope === "string") scope = scope.split(" ");
|
||||||
const pushScope = new Set<string>();
|
const pushScope = new Set<string>();
|
||||||
for (const s of scope) {
|
for (const s of scope) {
|
||||||
|
@ -64,14 +62,16 @@ export function apiAuthMastodon(router: Router): void {
|
||||||
redirect_uris: red,
|
redirect_uris: red,
|
||||||
website: body.website,
|
website: body.website,
|
||||||
});
|
});
|
||||||
ctx.body = {
|
const returns = {
|
||||||
id: appData.id,
|
id: Math.floor(Math.random() * 100).toString(),
|
||||||
name: appData.name,
|
name: appData.name,
|
||||||
website: appData.website,
|
website: body.website,
|
||||||
redirect_uri: red,
|
redirect_uri: red,
|
||||||
client_id: Buffer.from(appData.url || "").toString("base64"),
|
client_id: Buffer.from(appData.url || "").toString("base64"),
|
||||||
client_secret: appData.clientSecret,
|
client_secret: appData.clientSecret
|
||||||
};
|
};
|
||||||
|
console.log(returns)
|
||||||
|
ctx.body = returns;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
|
|
|
@ -10,18 +10,18 @@ export async function getInstance(response: Entity.Instance) {
|
||||||
const totalStatuses = Notes.count({ where: { userHost: IsNull() } });
|
const totalStatuses = Notes.count({ where: { userHost: IsNull() } });
|
||||||
return {
|
return {
|
||||||
uri: response.uri,
|
uri: response.uri,
|
||||||
title: response.title || "",
|
title: response.title || "Calckey",
|
||||||
short_description: response.description || "",
|
short_description: response.description.substring(0, 50) || "See real server website",
|
||||||
description: response.description || "",
|
description: response.description || "This is a vanilla Calckey Instance. It doesnt seem to have a description. BTW you are using the Mastodon api to access this server :)",
|
||||||
email: response.email || "",
|
email: response.email || "",
|
||||||
version: "3.0.0 compatible (Calckey)",
|
version: "3.0.0 compatible (3.5+ Calckey)", //I hope this version string is correct, we will need to test it.
|
||||||
urls: response.urls,
|
urls: response.urls,
|
||||||
stats: {
|
stats: {
|
||||||
user_count: (await totalUsers),
|
user_count: (await totalUsers),
|
||||||
status_count: (await totalStatuses),
|
status_count: (await totalStatuses),
|
||||||
domain_count: response.stats.domain_count
|
domain_count: response.stats.domain_count
|
||||||
},
|
},
|
||||||
thumbnail: response.thumbnail || "",
|
thumbnail: response.thumbnail || 'https://http.cat/404',
|
||||||
languages: meta.langs,
|
languages: meta.langs,
|
||||||
registrations: !meta.disableRegistration || response.registrations,
|
registrations: !meta.disableRegistration || response.registrations,
|
||||||
approval_required: !response.registrations,
|
approval_required: !response.registrations,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import megalodon, { MegalodonInterface } from "@calckey/megalodon";
|
import megalodon, { MegalodonInterface } from "@calckey/megalodon";
|
||||||
import Router from "@koa/router";
|
import Router from "@koa/router";
|
||||||
import { getClient } from "../ApiMastodonCompatibleService.js";
|
import { getClient } from "../ApiMastodonCompatibleService.js";
|
||||||
|
import axios from "axios";
|
||||||
|
import { Converter } from "@calckey/megalodon";
|
||||||
|
import { limitToInt } from "./timeline.js";
|
||||||
|
|
||||||
export function apiSearchMastodon(router: Router): void {
|
export function apiSearchMastodon(router: Router): void {
|
||||||
router.get("/v1/search", async (ctx) => {
|
router.get("/v1/search", async (ctx) => {
|
||||||
|
@ -9,7 +12,7 @@ export function apiSearchMastodon(router: Router): void {
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
const body: any = ctx.request.body;
|
const body: any = ctx.request.body;
|
||||||
try {
|
try {
|
||||||
const query: any = ctx.query;
|
const query: any = limitToInt(ctx.query);
|
||||||
const type = query.type || "";
|
const type = query.type || "";
|
||||||
const data = await client.search(query.q, type, query);
|
const data = await client.search(query.q, type, query);
|
||||||
ctx.body = data.data;
|
ctx.body = data.data;
|
||||||
|
@ -19,4 +22,110 @@ export function apiSearchMastodon(router: Router): void {
|
||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
router.get("/v2/search", async (ctx) => {
|
||||||
|
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
||||||
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
|
try {
|
||||||
|
const query: any = limitToInt(ctx.query);
|
||||||
|
const type = query.type;
|
||||||
|
if (type) {
|
||||||
|
const data = await client.search(query.q, type, query);
|
||||||
|
ctx.body = data.data;
|
||||||
|
} else {
|
||||||
|
const acct = await client.search(query.q, "accounts", query);
|
||||||
|
const stat = await client.search(query.q, "statuses", query);
|
||||||
|
const tags = await client.search(query.q, "hashtags", query);
|
||||||
|
ctx.body = {
|
||||||
|
accounts: acct.data.accounts,
|
||||||
|
statuses: stat.data.statuses,
|
||||||
|
hashtags: tags.data.hashtags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
ctx.status = (401);
|
||||||
|
ctx.body e.response.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.get("/v1/trends/statuses", async (ctx) => {
|
||||||
|
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
||||||
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
try {
|
||||||
|
const data = await getHighlight(BASE_URL, ctx.request.hostname, accessTokens);
|
||||||
|
ctx.body = data;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
ctx.status = (401);
|
||||||
|
ctx.body = e.response.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.get("/v2/suggestions", async (ctx) => {
|
||||||
|
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
||||||
|
const accessTokens = ctx.headers.authorization;
|
||||||
|
try {
|
||||||
|
const query: any = ctx.query;
|
||||||
|
const data = await getFeaturedUser(
|
||||||
|
BASE_URL,
|
||||||
|
ctx.request.hostname,
|
||||||
|
accessTokens,
|
||||||
|
query.limit || 20,
|
||||||
|
);
|
||||||
|
console.log(data);
|
||||||
|
ctx.body = data;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
ctx.status = (401);
|
||||||
|
ctx.body = e.response.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function getHighlight(
|
||||||
|
BASE_URL: string,
|
||||||
|
domain: string,
|
||||||
|
accessTokens: string | undefined,
|
||||||
|
) {
|
||||||
|
const accessTokenArr = accessTokens?.split(" ") ?? [null];
|
||||||
|
const accessToken = accessTokenArr[accessTokenArr.length - 1];
|
||||||
|
try {
|
||||||
|
const api = await axios.post(`${BASE_URL}/api/notes/featured`, {
|
||||||
|
i: accessToken,
|
||||||
|
});
|
||||||
|
const data: MisskeyEntity.Note[] = api.data;
|
||||||
|
return data.map((note) => Converter.note(note, domain));
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(e);
|
||||||
|
console.log(e.response.data);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function getFeaturedUser(
|
||||||
|
BASE_URL: string,
|
||||||
|
host: string,
|
||||||
|
accessTokens: string | undefined,
|
||||||
|
limit: number,
|
||||||
|
) {
|
||||||
|
const accessTokenArr = accessTokens?.split(" ") ?? [null];
|
||||||
|
const accessToken = accessTokenArr[accessTokenArr.length - 1];
|
||||||
|
try {
|
||||||
|
const api = await axios.post(`${BASE_URL}/api/users`, {
|
||||||
|
i: accessToken,
|
||||||
|
limit,
|
||||||
|
origin: "local",
|
||||||
|
sort: "+follower",
|
||||||
|
state: "alive",
|
||||||
|
});
|
||||||
|
const data: MisskeyEntity.UserDetail[] = api.data;
|
||||||
|
console.log(data);
|
||||||
|
return data.map((u) => {
|
||||||
|
return {
|
||||||
|
source: "past_interactions",
|
||||||
|
account: Converter.userDetail(u, host),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(e);
|
||||||
|
console.log(e.response.data);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,12 @@ import Router from "@koa/router";
|
||||||
import { getClient } from "../ApiMastodonCompatibleService.js";
|
import { getClient } from "../ApiMastodonCompatibleService.js";
|
||||||
import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
|
import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import querystring from 'node:querystring'
|
||||||
|
import qs from 'qs'
|
||||||
|
function normalizeQuery(data: any) {
|
||||||
|
const str = querystring.stringify(data);
|
||||||
|
return qs.parse(str);
|
||||||
|
}
|
||||||
|
|
||||||
export function apiStatusMastodon(router: Router): void {
|
export function apiStatusMastodon(router: Router): void {
|
||||||
router.post("/v1/statuses", async (ctx) => {
|
router.post("/v1/statuses", async (ctx) => {
|
||||||
|
@ -9,9 +15,12 @@ export function apiStatusMastodon(router: Router): void {
|
||||||
const accessTokens = ctx.headers.authorization;
|
const accessTokens = ctx.headers.authorization;
|
||||||
const client = getClient(BASE_URL, accessTokens);
|
const client = getClient(BASE_URL, accessTokens);
|
||||||
try {
|
try {
|
||||||
const body: any = ctx.request.body;
|
let body: any = ctx.request.body;
|
||||||
|
if ((!body.poll && body['poll[options][]']) || (!body.media_ids && body['media_ids[]'])) {
|
||||||
|
body = normalizeQuery(body)
|
||||||
|
}
|
||||||
const text = body.status;
|
const text = body.status;
|
||||||
const removed = text.replace(/@\S+/g, "").replaceAll(" ", "");
|
const removed = text.replace(/@\S+/g, "").replace(/\s|/g, '')
|
||||||
const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
|
const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
|
||||||
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
|
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
|
||||||
if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
|
if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
|
||||||
|
@ -36,6 +45,8 @@ export function apiStatusMastodon(router: Router): void {
|
||||||
}
|
}
|
||||||
if (!body.media_ids) body.media_ids = undefined;
|
if (!body.media_ids) body.media_ids = undefined;
|
||||||
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
|
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
|
||||||
|
const { sensitive } = body
|
||||||
|
body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive
|
||||||
const data = await client.postStatus(text, body);
|
const data = await client.postStatus(text, body);
|
||||||
ctx.body = data.data;
|
ctx.body = data.data;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
@ -70,7 +81,7 @@ export function apiStatusMastodon(router: Router): void {
|
||||||
const data = await client.deleteStatus(ctx.params.id);
|
const data = await client.deleteStatus(ctx.params.id);
|
||||||
ctx.body = data.data;
|
ctx.body = data.data;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e.response.data, request.params.id);
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
|
@ -430,6 +441,6 @@ export function statusModel(
|
||||||
pinned: false,
|
pinned: false,
|
||||||
emoji_reactions: [],
|
emoji_reactions: [],
|
||||||
bookmarked: false,
|
bookmarked: false,
|
||||||
quote: false,
|
quote: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ export function limitToInt(q: ParsedUrlQuery) {
|
||||||
let object: any = q;
|
let object: any = q;
|
||||||
if (q.limit)
|
if (q.limit)
|
||||||
if (typeof q.limit === "string") object.limit = parseInt(q.limit, 10);
|
if (typeof q.limit === "string") object.limit = parseInt(q.limit, 10);
|
||||||
return q;
|
if (q.offset)
|
||||||
|
if (typeof q.offset === "string") object.offset = parseInt(q.offset, 10);
|
||||||
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function argsToBools(q: ParsedUrlQuery) {
|
export function argsToBools(q: ParsedUrlQuery) {
|
||||||
|
@ -26,12 +28,29 @@ export function argsToBools(q: ParsedUrlQuery) {
|
||||||
export function toTextWithReaction(status: Entity.Status[], host: string) {
|
export function toTextWithReaction(status: Entity.Status[], host: string) {
|
||||||
return status.map((t) => {
|
return status.map((t) => {
|
||||||
if (!t) return statusModel(null, null, [], "no content");
|
if (!t) return statusModel(null, null, [], "no content");
|
||||||
|
t.quote = null as any;
|
||||||
if (!t.emoji_reactions) return t;
|
if (!t.emoji_reactions) return t;
|
||||||
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
|
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
|
||||||
const reactions = t.emoji_reactions.map(
|
const reactions = t.emoji_reactions.map((r) => {
|
||||||
(r) => `${r.name.replace("@.", "")} (${r.count}${r.me ? "* " : ""})`,
|
const emojiNotation = r.url ? `:${r.name.replace('@.', '')}:` : r.name
|
||||||
);
|
return `${emojiNotation} (${r.count}${r.me ? `* ` : ''})`
|
||||||
//t.emojis = getEmoji(t.content, host)
|
});
|
||||||
|
const reaction = t.emoji_reactions as Entity.Reaction[];
|
||||||
|
const emoji = t.emojis || []
|
||||||
|
for (const r of reaction) {
|
||||||
|
if (!r.url) continue
|
||||||
|
emoji.push({
|
||||||
|
'shortcode': r.name,
|
||||||
|
'url': r.url,
|
||||||
|
'static_url': r.url,
|
||||||
|
'visible_in_picker': true,
|
||||||
|
},)
|
||||||
|
}
|
||||||
|
const isMe = reaction.findIndex((r) => r.me) > -1;
|
||||||
|
const total = reaction.reduce((sum, reaction) => sum + reaction.count, 0);
|
||||||
|
t.favourited = isMe;
|
||||||
|
t.favourites_count = total;
|
||||||
|
t.emojis = emoji
|
||||||
t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(
|
t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(
|
||||||
", ",
|
", ",
|
||||||
)}</p>`;
|
)}</p>`;
|
||||||
|
@ -103,7 +122,7 @@ export function apiTimelineMastodon(router: Router): void {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
router.get<{ Params: { hashtag: string } }>(
|
router.get(
|
||||||
"/v1/timelines/home",
|
"/v1/timelines/home",
|
||||||
async (ctx, reply) => {
|
async (ctx, reply) => {
|
||||||
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
|
||||||
|
|
|
@ -414,12 +414,13 @@ export default class Connection {
|
||||||
const client = getClient(this.host, this.accessToken);
|
const client = getClient(this.host, this.accessToken);
|
||||||
client.getStatus(payload.id).then((data) => {
|
client.getStatus(payload.id).then((data) => {
|
||||||
const newPost = toTextWithReaction([data.data], this.host);
|
const newPost = toTextWithReaction([data.data], this.host);
|
||||||
|
const targetPost = newPost[0]
|
||||||
for (const stream of this.currentSubscribe) {
|
for (const stream of this.currentSubscribe) {
|
||||||
this.wsConnection.send(
|
this.wsConnection.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
stream,
|
stream,
|
||||||
event: "status.update",
|
event: "status.update",
|
||||||
payload: JSON.stringify(newPost[0]),
|
payload: JSON.stringify(targetPost),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,24 +154,29 @@ router.get("/verify-email/:code", async (ctx) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
mastoRouter.get("/oauth/authorize", async (ctx) => {
|
mastoRouter.get("/oauth/authorize", async (ctx) => {
|
||||||
const client_id = ctx.request.query.client_id;
|
const { client_id, state, redirect_uri } = ctx.request.query.client_id;
|
||||||
console.log(ctx.request.req);
|
console.log(ctx.request.req);
|
||||||
ctx.redirect(Buffer.from(client_id?.toString() || "", "base64").toString());
|
const param = state ? `state=${state}&mastodon=true` : "mastodon=true";
|
||||||
|
ctx.redirect(`${Buffer.from(client_id || '', 'base64').toString()}?${param}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
mastoRouter.post("/oauth/token", async (ctx) => {
|
mastoRouter.post("/oauth/token", async (ctx) => {
|
||||||
const body: any = ctx.request.body;
|
const body: any = ctx.request.body || ctx.request.query;
|
||||||
|
console.log('token-request', body)
|
||||||
let client_id: any = ctx.request.query.client_id;
|
let client_id: any = ctx.request.query.client_id;
|
||||||
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
|
||||||
const generator = (megalodon as any).default;
|
const generator = (megalodon as any).default;
|
||||||
const client = generator("misskey", BASE_URL, null) as MegalodonInterface;
|
const client = generator("misskey", BASE_URL, null) as MegalodonInterface;
|
||||||
let m = null;
|
let m = null;
|
||||||
|
let token = null;
|
||||||
if (body.code) {
|
if (body.code) {
|
||||||
m = body.code.match(/^[a-zA-Z0-9-]+/);
|
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) {
|
if (!m.length) {
|
||||||
ctx.body = { error: "Invalid code" };
|
ctx.body = { error: "Invalid code" };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
token = `${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}`
|
||||||
|
console.log(body.code, token)
|
||||||
}
|
}
|
||||||
if (client_id instanceof Array) {
|
if (client_id instanceof Array) {
|
||||||
client_id = client_id.toString();
|
client_id = client_id.toString();
|
||||||
|
@ -182,14 +187,16 @@ mastoRouter.post("/oauth/token", async (ctx) => {
|
||||||
const atData = await client.fetchAccessToken(
|
const atData = await client.fetchAccessToken(
|
||||||
client_id,
|
client_id,
|
||||||
body.client_secret,
|
body.client_secret,
|
||||||
m ? m[0] : "",
|
token ? token : "",
|
||||||
);
|
);
|
||||||
ctx.body = {
|
const ret = {
|
||||||
access_token: atData.accessToken,
|
access_token: atData.accessToken,
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
scope: "read write follow",
|
scope: body.scope || 'read write follow push',
|
||||||
created_at: Math.floor(new Date().getTime() / 1000),
|
created_at: Math.floor(new Date().getTime() / 1000),
|
||||||
};
|
};
|
||||||
|
console.log('token-response', ret)
|
||||||
|
ctx.body = ret;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
|
|
|
@ -86,7 +86,14 @@ export default defineComponent({
|
||||||
accepted() {
|
accepted() {
|
||||||
this.state = 'accepted';
|
this.state = 'accepted';
|
||||||
const getUrlParams = () => window.location.search.substring(1).split('&').reduce((result, query) => { const [k, v] = query.split('='); result[k] = decodeURI(v); return result; }, {});
|
const getUrlParams = () => window.location.search.substring(1).split('&').reduce((result, query) => { const [k, v] = query.split('='); result[k] = decodeURI(v); return result; }, {});
|
||||||
if (this.session.app.callbackUrl) {
|
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}`;
|
||||||
|
} else if (this.session.app.callbackUrl) {
|
||||||
const url = new URL(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');
|
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
|
||||||
if (this.session.app.callbackUrl === "urn:ietf:wg:oauth:2.0:oob") {
|
if (this.session.app.callbackUrl === "urn:ietf:wg:oauth:2.0:oob") {
|
||||||
|
|
|
@ -100,6 +100,7 @@ importers:
|
||||||
'@types/pug': 2.0.6
|
'@types/pug': 2.0.6
|
||||||
'@types/punycode': 2.1.0
|
'@types/punycode': 2.1.0
|
||||||
'@types/qrcode': 1.5.0
|
'@types/qrcode': 1.5.0
|
||||||
|
'@types/qs': 6.9.7
|
||||||
'@types/random-seed': 0.3.3
|
'@types/random-seed': 0.3.3
|
||||||
'@types/ratelimiter': 3.4.4
|
'@types/ratelimiter': 3.4.4
|
||||||
'@types/redis': 4.0.11
|
'@types/redis': 4.0.11
|
||||||
|
@ -183,6 +184,7 @@ importers:
|
||||||
punycode: 2.1.1
|
punycode: 2.1.1
|
||||||
pureimage: 0.3.15
|
pureimage: 0.3.15
|
||||||
qrcode: 1.5.1
|
qrcode: 1.5.1
|
||||||
|
qs: 6.9.7
|
||||||
random-seed: 0.3.0
|
random-seed: 0.3.0
|
||||||
ratelimiter: 3.4.1
|
ratelimiter: 3.4.1
|
||||||
re2: 1.18.0
|
re2: 1.18.0
|
||||||
|
@ -295,6 +297,7 @@ importers:
|
||||||
punycode: 2.1.1
|
punycode: 2.1.1
|
||||||
pureimage: 0.3.15
|
pureimage: 0.3.15
|
||||||
qrcode: 1.5.1
|
qrcode: 1.5.1
|
||||||
|
qs: 6.9.7
|
||||||
random-seed: 0.3.0
|
random-seed: 0.3.0
|
||||||
ratelimiter: 3.4.1
|
ratelimiter: 3.4.1
|
||||||
re2: 1.18.0
|
re2: 1.18.0
|
||||||
|
@ -357,6 +360,7 @@ importers:
|
||||||
'@types/pug': 2.0.6
|
'@types/pug': 2.0.6
|
||||||
'@types/punycode': 2.1.0
|
'@types/punycode': 2.1.0
|
||||||
'@types/qrcode': 1.5.0
|
'@types/qrcode': 1.5.0
|
||||||
|
'@types/qs': 6.9.7
|
||||||
'@types/random-seed': 0.3.3
|
'@types/random-seed': 0.3.3
|
||||||
'@types/ratelimiter': 3.4.4
|
'@types/ratelimiter': 3.4.4
|
||||||
'@types/redis': 4.0.11
|
'@types/redis': 4.0.11
|
||||||
|
@ -4264,7 +4268,7 @@ packages:
|
||||||
resolution: {integrity: sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==}
|
resolution: {integrity: sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
inflation: 2.0.0
|
inflation: 2.0.0
|
||||||
qs: 6.11.0
|
qs: 6.9.7
|
||||||
raw-body: 2.5.1
|
raw-body: 2.5.1
|
||||||
type-is: 1.6.18
|
type-is: 1.6.18
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -4273,7 +4277,7 @@ packages:
|
||||||
resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==}
|
resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
inflation: 2.0.0
|
inflation: 2.0.0
|
||||||
qs: 6.11.0
|
qs: 6.9.7
|
||||||
raw-body: 2.5.1
|
raw-body: 2.5.1
|
||||||
type-is: 1.6.18
|
type-is: 1.6.18
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -10691,6 +10695,11 @@ packages:
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/qs/6.9.7:
|
||||||
|
resolution: {integrity: sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/query-string/4.3.4:
|
/query-string/4.3.4:
|
||||||
resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
|
resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
Loading…
Reference in New Issue