mirror of
https://iceshrimp.dev/iceshrimp/iceshrimp
synced 2025-01-21 21:12:53 +09:00
[mastodon-client] GET /v2/suggestions
This commit is contained in:
parent
44b72a2ecc
commit
5f0d140bbe
@ -52,6 +52,14 @@ export function unique<T>(xs: T[]): T[] {
|
|||||||
return [...new Set(xs)];
|
return [...new Set(xs)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uniqBy<T>(a: T[], key: Function): T[] {
|
||||||
|
const seen = new Set<any>();
|
||||||
|
return a.filter(function(item) {
|
||||||
|
const k = key(item);
|
||||||
|
return seen.has(k) ? false : seen.add(k);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function sum(xs: number[]): number {
|
export function sum(xs: number[]): number {
|
||||||
return xs.reduce((a, b) => a + b, 0);
|
return xs.reduce((a, b) => a + b, 0);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,11 @@ export function convertListId(list: MastodonEntity.List) {
|
|||||||
return simpleConvertId(list);
|
return simpleConvertId(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertSuggestionIds(suggestion: MastodonEntity.SuggestedAccount) {
|
||||||
|
suggestion.account = convertAccountId(suggestion.account)
|
||||||
|
return suggestion
|
||||||
|
}
|
||||||
|
|
||||||
export function convertNotificationIds(notification: MastodonEntity.Notification) {
|
export function convertNotificationIds(notification: MastodonEntity.Notification) {
|
||||||
notification.account = convertAccountId(notification.account);
|
notification.account = convertAccountId(notification.account);
|
||||||
notification.id = convertId(notification.id, IdType.MastodonId);
|
notification.id = convertId(notification.id, IdType.MastodonId);
|
||||||
|
@ -2,9 +2,9 @@ import Router from "@koa/router";
|
|||||||
import { getClient } from "@/server/api/mastodon/index.js";
|
import { getClient } from "@/server/api/mastodon/index.js";
|
||||||
import { MiscHelpers } from "@/server/api/mastodon/helpers/misc.js";
|
import { MiscHelpers } from "@/server/api/mastodon/helpers/misc.js";
|
||||||
import authenticate from "@/server/api/authenticate.js";
|
import authenticate from "@/server/api/authenticate.js";
|
||||||
import { argsToBools } from "@/server/api/mastodon/endpoints/timeline.js";
|
import { argsToBools, limitToInt } from "@/server/api/mastodon/endpoints/timeline.js";
|
||||||
import { Announcements } from "@/models/index.js";
|
import { Announcements } from "@/models/index.js";
|
||||||
import { convertAnnouncementId } from "@/server/api/mastodon/converters.js";
|
import { convertAnnouncementId, convertSuggestionIds } from "@/server/api/mastodon/converters.js";
|
||||||
import { convertId, IdType } from "@/misc/convert-id.js";
|
import { convertId, IdType } from "@/misc/convert-id.js";
|
||||||
|
|
||||||
export function setupEndpointsMisc(router: Router): void {
|
export function setupEndpointsMisc(router: Router): void {
|
||||||
@ -109,4 +109,23 @@ export function setupEndpointsMisc(router: Router): void {
|
|||||||
ctx.body = e.response.data;
|
ctx.body = e.response.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/v2/suggestions", async (ctx) => {
|
||||||
|
try {
|
||||||
|
const auth = await authenticate(ctx.headers.authorization, null);
|
||||||
|
const user = auth[0] ?? undefined;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
ctx.status = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = limitToInt(ctx.query);
|
||||||
|
ctx.body = await MiscHelpers.getFollowSuggestions(user, args.limit)
|
||||||
|
.then(p => p.map(x => convertSuggestionIds(x)));
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.status = 500;
|
||||||
|
ctx.body = { error: e.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { convertAccountId, convertSearchIds, convertStatusIds } from "../convert
|
|||||||
import authenticate from "@/server/api/authenticate.js";
|
import authenticate from "@/server/api/authenticate.js";
|
||||||
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
||||||
import { SearchHelpers } from "@/server/api/mastodon/helpers/search.js";
|
import { SearchHelpers } from "@/server/api/mastodon/helpers/search.js";
|
||||||
|
import { MiscHelpers } from "@/server/api/mastodon/helpers/misc.js";
|
||||||
|
|
||||||
export function setupEndpointsSearch(router: Router): void {
|
export function setupEndpointsSearch(router: Router): void {
|
||||||
router.get("/v1/search", async (ctx) => {
|
router.get("/v1/search", async (ctx) => {
|
||||||
@ -69,29 +70,6 @@ export function setupEndpointsSearch(router: Router): void {
|
|||||||
ctx.body = e.response.data;
|
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;
|
|
||||||
let data = await getFeaturedUser(
|
|
||||||
BASE_URL,
|
|
||||||
ctx.request.hostname,
|
|
||||||
accessTokens,
|
|
||||||
query.limit || 20,
|
|
||||||
);
|
|
||||||
data = data.map((suggestion) => {
|
|
||||||
suggestion.account = convertAccountId(suggestion.account);
|
|
||||||
return suggestion;
|
|
||||||
});
|
|
||||||
console.log(data);
|
|
||||||
ctx.body = data;
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
ctx.status = 401;
|
|
||||||
ctx.body = e.response.data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getHighlight(
|
async function getHighlight(
|
||||||
@ -113,34 +91,3 @@ async function getHighlight(
|
|||||||
return [];
|
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: new Converter(BASE_URL).userDetail(u, host),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (e: any) {
|
|
||||||
console.log(e);
|
|
||||||
console.log(e.response.data);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -29,4 +29,9 @@ namespace MastodonEntity {
|
|||||||
export type MutedAccount = Account | {
|
export type MutedAccount = Account | {
|
||||||
mute_expires_at: string | null;
|
mute_expires_at: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SuggestedAccount = {
|
||||||
|
source: "staff" | "past_interactions" | "global",
|
||||||
|
account: Account
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,12 @@ import { Announcement } from "@/models/entities/announcement.js";
|
|||||||
import { ILocalUser } from "@/models/entities/user.js";
|
import { ILocalUser } from "@/models/entities/user.js";
|
||||||
import { AnnouncementConverter } from "@/server/api/mastodon/converters/announcement.js";
|
import { AnnouncementConverter } from "@/server/api/mastodon/converters/announcement.js";
|
||||||
import { genId } from "@/misc/gen-id.js";
|
import { genId } from "@/misc/gen-id.js";
|
||||||
|
import * as Acct from "@/misc/acct.js";
|
||||||
|
import { User } from "@/models/entities/user.js";
|
||||||
|
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
||||||
|
import { generateMutedUserQueryForUsers } from "@/server/api/common/generate-muted-user-query.js";
|
||||||
|
import { generateBlockQueryForUsers } from "@/server/api/common/generate-block-query.js";
|
||||||
|
import { uniqBy } from "@/prelude/array.js";
|
||||||
|
|
||||||
export class MiscHelpers {
|
export class MiscHelpers {
|
||||||
public static async getInstance(): Promise<MastodonEntity.Instance> {
|
public static async getInstance(): Promise<MastodonEntity.Instance> {
|
||||||
@ -123,4 +129,50 @@ export class MiscHelpers {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getFollowSuggestions(user: ILocalUser, limit: number): Promise<MastodonEntity.SuggestedAccount[]> {
|
||||||
|
const cache = UserHelpers.getFreshAccountCache();
|
||||||
|
const results: Promise<MastodonEntity.SuggestedAccount[]>[] = [];
|
||||||
|
|
||||||
|
const pinned = fetchMeta().then(meta => Promise.all(
|
||||||
|
meta.pinnedUsers
|
||||||
|
.map((acct) => Acct.parse(acct))
|
||||||
|
.map((acct) =>
|
||||||
|
Users.findOneBy({
|
||||||
|
usernameLower: acct.username.toLowerCase(),
|
||||||
|
host: acct.host ?? IsNull(),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.then(p => p.filter(x => !!x) as User[])
|
||||||
|
.then(p => UserConverter.encodeMany(p, cache))
|
||||||
|
.then(p => p.map(x => {
|
||||||
|
return {source: "staff", account: x} as MastodonEntity.SuggestedAccount
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const query = Users.createQueryBuilder("user")
|
||||||
|
.where("user.isExplorable = TRUE")
|
||||||
|
.andWhere("user.host IS NULL")
|
||||||
|
.orderBy("user.followersCount", "DESC")
|
||||||
|
.andWhere("user.updatedAt > :date", {
|
||||||
|
date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5),
|
||||||
|
});
|
||||||
|
|
||||||
|
generateMutedUserQueryForUsers(query, user);
|
||||||
|
generateBlockQueryForUsers(query, user);
|
||||||
|
|
||||||
|
const global = query
|
||||||
|
.take(limit)
|
||||||
|
.getMany()
|
||||||
|
.then(p => UserConverter.encodeMany(p, cache))
|
||||||
|
.then(p => p.map(x => {
|
||||||
|
return {source: "global", account: x} as MastodonEntity.SuggestedAccount
|
||||||
|
}));
|
||||||
|
|
||||||
|
results.push(pinned);
|
||||||
|
results.push(global);
|
||||||
|
|
||||||
|
|
||||||
|
return Promise.all(results).then(p => uniqBy(p.flat(), (x: MastodonEntity.SuggestedAccount) => x.account.id).slice(0, limit));
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user