Fixed 267U.pre2
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
import { Followings, Hashtags, Notes, Users } from "../../../../models/index.js";
|
||||
import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
|
||||
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
|
||||
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
|
||||
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
||||
import { PaginationHelpers } from "./pagination.js";
|
||||
import { Brackets, IsNull } from "typeorm";
|
||||
import { awaitAll } from "../../../../prelude/await-all.js";
|
||||
import { NoteConverter } from "../converters/note.js";
|
||||
import Resolver from "../../../../remote/activitypub/resolver.js";
|
||||
import { getApId, isActor, isPost } from "../../../../remote/activitypub/type.js";
|
||||
import DbResolver from "../../../../remote/activitypub/db-resolver.js";
|
||||
import { createPerson } from "../../../../remote/activitypub/models/person.js";
|
||||
import { UserConverter } from "../converters/user.js";
|
||||
import { resolveUser } from "../../../../remote/resolve-user.js";
|
||||
import { createNote } from "../../../../remote/activitypub/models/note.js";
|
||||
import config from "../../../../config/index.js";
|
||||
import { logger } from "../index.js";
|
||||
import { generateFtsQuery } from "../../common/generate-fts-query.js";
|
||||
export class SearchHelpers {
|
||||
static async search(q, type, resolve = false, following = false, accountId, excludeUnreviewed = false, maxId, minId, limit = 20, offset, ctx) {
|
||||
if (q === undefined || q.trim().length === 0) throw new Error('Search query cannot be empty');
|
||||
if (limit > 40) limit = 40;
|
||||
const user = ctx.user;
|
||||
const notes = type === 'statuses' || !type ? this.searchNotes(q, resolve, following, accountId, maxId, minId, limit, offset, ctx) : [];
|
||||
const users = type === 'accounts' || !type ? this.searchUsers(q, resolve, following, maxId, minId, limit, offset, ctx) : [];
|
||||
const tags = type === 'hashtags' || !type ? this.searchTags(q, excludeUnreviewed, limit, offset) : [];
|
||||
const result = {
|
||||
statuses: Promise.resolve(notes).then((p)=>NoteConverter.encodeMany(p, ctx)),
|
||||
accounts: Promise.resolve(users).then((p)=>UserConverter.encodeMany(p, ctx)),
|
||||
hashtags: Promise.resolve(tags)
|
||||
};
|
||||
return awaitAll(result);
|
||||
}
|
||||
static async searchUsers(q, resolve, following, maxId, minId, limit, offset, ctx) {
|
||||
const user = ctx.user;
|
||||
if (resolve) {
|
||||
try {
|
||||
if (q.startsWith('https://') || q.startsWith('http://')) {
|
||||
// try resolving locally first
|
||||
const dbResolver = new DbResolver();
|
||||
const dbResult = await dbResolver.getUserFromApId(q);
|
||||
if (dbResult) return [
|
||||
dbResult
|
||||
];
|
||||
// ask remote
|
||||
const resolver = new Resolver();
|
||||
resolver.setUser(user);
|
||||
const object = await resolver.resolve(q);
|
||||
if (q !== object.id) {
|
||||
const result = await dbResolver.getUserFromApId(getApId(object));
|
||||
if (result) return [
|
||||
result
|
||||
];
|
||||
}
|
||||
return isActor(object) ? Promise.all([
|
||||
createPerson(getApId(object), resolver.reset())
|
||||
]) : [];
|
||||
} else {
|
||||
let match = q.match(/^@?(?<user>[a-zA-Z0-9_]+)@(?<host>[a-zA-Z0-9-.]+\.[a-zA-Z0-9-]+)$/);
|
||||
if (!match) match = q.match(/^@(?<user>[a-zA-Z0-9_]+)$/);
|
||||
if (match) {
|
||||
// check if user is already in database
|
||||
const dbResult = await Users.findOneBy({
|
||||
usernameLower: match.groups.user.toLowerCase(),
|
||||
host: match.groups?.host ?? IsNull()
|
||||
});
|
||||
if (dbResult) return [
|
||||
dbResult
|
||||
];
|
||||
const result = await resolveUser(match.groups.user.toLowerCase(), match.groups?.host ?? null);
|
||||
if (result) return [
|
||||
result
|
||||
];
|
||||
// no matches found
|
||||
return [];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`[mastodon-client] resolve user '${q}' failed: ${e.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
const query = PaginationHelpers.makePaginationQuery(Users.createQueryBuilder("user"), undefined, minId, maxId);
|
||||
if (following) {
|
||||
const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
|
||||
followerId: user.id
|
||||
});
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
qb.where(`user.id IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, {
|
||||
meId: user.id
|
||||
});
|
||||
}));
|
||||
}
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
qb.where("user.name ILIKE :q", {
|
||||
q: `%${sqlLikeEscape(q)}%`
|
||||
});
|
||||
qb.orWhere("concat_ws('@', user.usernameLower, user.host) ILIKE :q", {
|
||||
q: `%${sqlLikeEscape(q)}%`
|
||||
});
|
||||
}));
|
||||
query.orderBy({
|
||||
'user.notesCount': 'DESC'
|
||||
});
|
||||
return query.skip(offset ?? 0).take(limit).getMany().then((p)=>minId ? p.reverse() : p);
|
||||
}
|
||||
static async searchNotes(q, resolve, following, accountId, maxId, minId, limit, offset, ctx) {
|
||||
if (accountId && following) throw new Error("The 'following' and 'accountId' parameters cannot be used simultaneously");
|
||||
const user = ctx.user;
|
||||
if (resolve) {
|
||||
try {
|
||||
if (q.startsWith('https://') || q.startsWith('http://')) {
|
||||
// try resolving locally first
|
||||
const dbResolver = new DbResolver();
|
||||
const dbResult = await dbResolver.getNoteFromApId(q);
|
||||
if (dbResult) return [
|
||||
dbResult
|
||||
];
|
||||
// ask remote
|
||||
const resolver = new Resolver();
|
||||
resolver.setUser(user);
|
||||
const object = await resolver.resolve(q);
|
||||
if (q !== object.id) {
|
||||
const result = await dbResolver.getNoteFromApId(getApId(object));
|
||||
if (result) return [
|
||||
result
|
||||
];
|
||||
}
|
||||
return isPost(object) ? createNote(getApId(object), resolver.reset(), true).then((p)=>p ? [
|
||||
p
|
||||
] : []) : [];
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn(`Resolving note '${q}' failed: ${e.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), undefined, minId, maxId);
|
||||
if (accountId) {
|
||||
query.andWhere("note.userId = :userId", {
|
||||
userId: accountId
|
||||
});
|
||||
}
|
||||
if (following) {
|
||||
const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
|
||||
followerId: user.id
|
||||
});
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
qb.where(`note.userId IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, {
|
||||
meId: user.id
|
||||
});
|
||||
}));
|
||||
}
|
||||
query.leftJoinAndSelect("note.renote", "renote");
|
||||
generateFtsQuery(query, q);
|
||||
generateVisibilityQuery(query, user);
|
||||
if (!accountId) {
|
||||
generateMutedUserQuery(query, user);
|
||||
generateBlockedUserQuery(query, user);
|
||||
}
|
||||
query.setParameter("meId", user.id);
|
||||
return query.skip(offset ?? 0).take(limit).getMany().then((p)=>minId ? p.reverse() : p);
|
||||
}
|
||||
static async searchTags(q, excludeUnreviewed, limit, offset) {
|
||||
const tags = Hashtags.createQueryBuilder('tag').select('tag.name').distinctOn([
|
||||
'tag.name'
|
||||
]).where("tag.name ILIKE :q", {
|
||||
q: `%${sqlLikeEscape(q)}%`
|
||||
}).orderBy({
|
||||
'tag.name': 'ASC'
|
||||
}).skip(offset ?? 0).take(limit).getMany();
|
||||
return tags.then((p)=>p.map((tag)=>{
|
||||
return {
|
||||
name: tag.name,
|
||||
url: `${config.url}/tags/${tag.name}`,
|
||||
history: null
|
||||
};
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user