mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-11-21 22:07:06 +01:00
wip hashtags
This commit is contained in:
parent
41250d997b
commit
c454a44785
8 changed files with 164 additions and 173 deletions
|
@ -4,44 +4,17 @@ import type { HashtagsRepository } from '@/models/index.js';
|
|||
import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'Hashtag',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
attachedToUserOnly: { type: 'boolean', default: false },
|
||||
attachedToLocalUserOnly: { type: 'boolean', default: false },
|
||||
attachedToRemoteUserOnly: { type: 'boolean', default: false },
|
||||
sort: { type: 'string', enum: ['+mentionedUsers', '-mentionedUsers', '+mentionedLocalUsers', '-mentionedLocalUsers', '+mentionedRemoteUsers', '-mentionedRemoteUsers', '+attachedUsers', '-attachedUsers', '+attachedLocalUsers', '-attachedLocalUsers', '+attachedRemoteUsers', '-attachedRemoteUsers'] },
|
||||
},
|
||||
required: ['sort'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
export default class extends Endpoint<'hashtags/list'> {
|
||||
name = 'hashtags/list' as const;
|
||||
constructor(
|
||||
@Inject(DI.hashtagsRepository)
|
||||
private hashtagsRepository: HashtagsRepository,
|
||||
|
||||
private hashtagEntityService: HashtagEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
super(async (ps, me) => {
|
||||
const query = this.hashtagsRepository.createQueryBuilder('tag');
|
||||
|
||||
if (ps.attachedToUserOnly) query.andWhere('tag.attachedUsersCount != 0');
|
||||
|
|
|
@ -4,39 +4,15 @@ import type { HashtagsRepository } from '@/models/index.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
query: { type: 'string' },
|
||||
offset: { type: 'integer', default: 0 },
|
||||
},
|
||||
required: ['query'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
export default class extends Endpoint<'hashtags/search'> {
|
||||
name = 'hashtags/search' as const;
|
||||
constructor(
|
||||
@Inject(DI.hashtagsRepository)
|
||||
private hashtagsRepository: HashtagsRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
super(async (ps, me) => {
|
||||
const hashtags = await this.hashtagsRepository.createQueryBuilder('tag')
|
||||
.where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' })
|
||||
.orderBy('tag.count', 'DESC')
|
||||
|
|
|
@ -6,47 +6,20 @@ import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'Hashtag',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchHashtag: {
|
||||
message: 'No such hashtag.',
|
||||
code: 'NO_SUCH_HASHTAG',
|
||||
id: '110ee688-193e-4a3a-9ecf-c167b2e6981e',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tag: { type: 'string' },
|
||||
},
|
||||
required: ['tag'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
export default class extends Endpoint<'hashtags/show'> {
|
||||
name = 'hashtags/show' as const;
|
||||
constructor(
|
||||
@Inject(DI.hashtagsRepository)
|
||||
private hashtagsRepository: HashtagsRepository,
|
||||
|
||||
private hashtagEntityService: HashtagEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
super(async (ps, me) => {
|
||||
const hashtag = await this.hashtagsRepository.findOneBy({ name: normalizeForSearch(ps.tag) });
|
||||
if (hashtag == null) {
|
||||
throw new ApiError(meta.errors.noSuchHashtag);
|
||||
throw new ApiError(this.meta.errors.noSuchHashtag);
|
||||
}
|
||||
|
||||
return await this.hashtagEntityService.pack(hashtag);
|
||||
|
|
|
@ -22,57 +22,17 @@ const rangeA = 1000 * 60 * 60; // 60分
|
|||
|
||||
const max = 5;
|
||||
|
||||
export const meta = {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
allowGet: true,
|
||||
cacheSec: 60 * 1,
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
tag: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
chart: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
usersCount: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
export default class extends Endpoint<'hashtags/trend'> {
|
||||
name = 'hashtags/trend' as const;
|
||||
constructor(
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
super(meta, paramDef, async () => {
|
||||
super(async () => {
|
||||
const instance = await this.metaService.fetch(true);
|
||||
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
|
||||
|
||||
|
@ -95,9 +55,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
}
|
||||
|
||||
const tags: {
|
||||
name: string;
|
||||
users: Note['userId'][];
|
||||
}[] = [];
|
||||
name: string;
|
||||
users: Note['userId'][];
|
||||
}[] = [];
|
||||
|
||||
for (const note of tagNotes) {
|
||||
for (const tag of note.tags) {
|
||||
|
|
|
@ -5,44 +5,17 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: false,
|
||||
|
||||
tags: ['hashtags', 'users'],
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserDetailed',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tag: { type: 'string' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
|
||||
state: { type: 'string', enum: ['all', 'alive'], default: 'all' },
|
||||
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
|
||||
},
|
||||
required: ['tag', 'sort'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
export default class extends Endpoint<'hashtags/users'> {
|
||||
name = 'hashtags/users' as const;
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
super(async (ps, me) => {
|
||||
const query = this.usersRepository.createQueryBuilder('user')
|
||||
.where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) })
|
||||
.andWhere('user.isSuspended = FALSE');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { JSONSchema7 } from 'schema-type';
|
||||
import { IEndpointMeta } from './endpoints.types.js';
|
||||
import { localUsernameSchema, passwordSchema } from './schemas/user.js';
|
||||
import { localUsernameSchema, passwordSchema, userOriginSchema, userSortingSchema } from './schemas/user.js';
|
||||
import ms from 'ms';
|
||||
import { chartSchemaToJSONSchema } from './schemas.js';
|
||||
import { chartsSchemas } from './schemas/charts.js';
|
||||
|
@ -5217,6 +5217,137 @@ export const endpoints = {
|
|||
}],
|
||||
},
|
||||
//#endregion
|
||||
|
||||
//#region hashtags
|
||||
'hashtags/list': {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
defines: [{
|
||||
req: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
attachedToUserOnly: { type: 'boolean', default: false },
|
||||
attachedToLocalUserOnly: { type: 'boolean', default: false },
|
||||
attachedToRemoteUserOnly: { type: 'boolean', default: false },
|
||||
sort: { type: 'string', enum: ['+mentionedUsers', '-mentionedUsers', '+mentionedLocalUsers', '-mentionedLocalUsers', '+mentionedRemoteUsers', '-mentionedRemoteUsers', '+attachedUsers', '-attachedUsers', '+attachedLocalUsers', '-attachedLocalUsers', '+attachedRemoteUsers', '-attachedRemoteUsers'] },
|
||||
},
|
||||
required: ['sort'],
|
||||
},
|
||||
res: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: 'https://misskey-hub.net/api/schemas/Hashtag',
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
'hashtags/search': {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
defines: [{
|
||||
req: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
query: { type: 'string' },
|
||||
offset: { type: 'integer', default: 0 },
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
res: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
'hashtags/show': {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
errors: {
|
||||
noSuchHashtag: {
|
||||
message: 'No such hashtag.',
|
||||
code: 'NO_SUCH_HASHTAG',
|
||||
id: '110ee688-193e-4a3a-9ecf-c167b2e6981e',
|
||||
},
|
||||
},
|
||||
|
||||
defines: [{
|
||||
req: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tag: { type: 'string' },
|
||||
},
|
||||
required: ['tag'],
|
||||
},
|
||||
res: {
|
||||
$ref: 'https://misskey-hub.net/api/schemas/Hashtag',
|
||||
},
|
||||
}],
|
||||
},
|
||||
'hashtags/trend': {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
allowGet: true,
|
||||
cacheSec: 60 * 1,
|
||||
|
||||
defines: [{
|
||||
req: undefined,
|
||||
res: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tag: { type: 'string' },
|
||||
chart: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
},
|
||||
usersCount: { type: 'number' },
|
||||
},
|
||||
required: ['tag', 'chart', 'usersCount'],
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
'hashtags/users': {
|
||||
requireCredential: false,
|
||||
|
||||
tags: ['hashtags', 'users'],
|
||||
|
||||
defines: [{
|
||||
req: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tag: { type: 'string' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sort: userSortingSchema,
|
||||
state: { type: 'string', enum: ['all', 'alive'], default: 'all' },
|
||||
origin: {
|
||||
...userOriginSchema,
|
||||
default: 'local',
|
||||
},
|
||||
},
|
||||
required: ['tag', 'sort'],
|
||||
},
|
||||
res: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: 'https://misskey-hub.net/api/schemas/UserDetailed',
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
//#endregion
|
||||
} as const satisfies { [x: string]: IEndpointMeta; };
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { SchemaType } from "schema-type";
|
||||
import { Packed } from "./schemas.js";
|
||||
import type { userOriginSchema, userSortingSchema } from "./schemas/user.js";
|
||||
|
||||
export type ID = Packed<'Id'>;
|
||||
export type DateString = string;
|
||||
|
@ -163,13 +165,8 @@ export type Instance = {
|
|||
|
||||
export type Signin = Packed<'SignIn'>;
|
||||
|
||||
export type UserSorting =
|
||||
| '+follower'
|
||||
| '-follower'
|
||||
| '+createdAt'
|
||||
| '-createdAt'
|
||||
| '+updatedAt'
|
||||
| '-updatedAt';
|
||||
export type OriginType = 'combined' | 'local' | 'remote';
|
||||
export type UserSorting = SchemaType<typeof userSortingSchema, []>;
|
||||
|
||||
export type OriginType = SchemaType<typeof userOriginSchema, []>;
|
||||
|
||||
export type MeSignup = TODO;
|
||||
|
|
|
@ -495,3 +495,11 @@ export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as con
|
|||
export const descriptionSchema = { type: 'string', minLength: 1, maxLength: 1500 } as const satisfies JSONSchema7;
|
||||
export const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const satisfies JSONSchema7;
|
||||
export const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const satisfies JSONSchema7;
|
||||
|
||||
export const userSortingSchema = {
|
||||
enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'],
|
||||
} as const satisfies JSONSchema7;
|
||||
|
||||
export const userOriginSchema = {
|
||||
enum: ['combined', 'local', 'remote'],
|
||||
} as const satisfies JSONSchema7;
|
||||
|
|
Loading…
Reference in a new issue