mirror of
https://github.com/misskey-dev/misskey.git
synced 2025-02-05 04:47:04 +09:00
デフォルトハッシュタグタイムライン
This commit is contained in:
parent
15727088be
commit
594193ba8a
@ -193,3 +193,6 @@ signToActivityPubGet: true
|
|||||||
|
|
||||||
# Upload or download file size limits (bytes)
|
# Upload or download file size limits (bytes)
|
||||||
#maxFileSize: 262144000
|
#maxFileSize: 262144000
|
||||||
|
|
||||||
|
tagging:
|
||||||
|
defaultTag: null
|
||||||
|
@ -225,3 +225,6 @@ signToActivityPubGet: true
|
|||||||
|
|
||||||
# PID File of master process
|
# PID File of master process
|
||||||
#pidFile: /tmp/misskey.pid
|
#pidFile: /tmp/misskey.pid
|
||||||
|
|
||||||
|
tagging:
|
||||||
|
defaultTag: null
|
||||||
|
@ -56,6 +56,9 @@ type Source = {
|
|||||||
index: string;
|
index: string;
|
||||||
scope?: 'local' | 'global' | string[];
|
scope?: 'local' | 'global' | string[];
|
||||||
};
|
};
|
||||||
|
tagging: {
|
||||||
|
defaultTag: string;
|
||||||
|
};
|
||||||
|
|
||||||
proxy?: string;
|
proxy?: string;
|
||||||
proxySmtp?: string;
|
proxySmtp?: string;
|
||||||
@ -124,6 +127,9 @@ export type Config = {
|
|||||||
index: string;
|
index: string;
|
||||||
scope?: 'local' | 'global' | string[];
|
scope?: 'local' | 'global' | string[];
|
||||||
} | undefined;
|
} | undefined;
|
||||||
|
tagging: {
|
||||||
|
defaultTag: string;
|
||||||
|
};
|
||||||
proxy: string | undefined;
|
proxy: string | undefined;
|
||||||
proxySmtp: string | undefined;
|
proxySmtp: string | undefined;
|
||||||
proxyBypassHosts: string[] | undefined;
|
proxyBypassHosts: string[] | undefined;
|
||||||
@ -261,6 +267,7 @@ export function loadConfig(): Config {
|
|||||||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
|
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
|
||||||
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
||||||
pidFile: config.pidFile,
|
pidFile: config.pidFile,
|
||||||
|
tagging: config.tagging,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
import { isReply } from '@/misc/is-reply.js';
|
import { isReply } from '@/misc/is-reply.js';
|
||||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
|
import { loadConfig } from '@/config.js';
|
||||||
|
|
||||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||||
|
|
||||||
@ -913,6 +914,16 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// デフォルトハッシュタグ
|
||||||
|
const config = loadConfig();
|
||||||
|
if (note.visibility === 'public' && note.tags.includes(String(config.tagging.defaultTag))) {
|
||||||
|
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
||||||
|
this.fanoutTimelineService.push('localTimeline', note.id, 1000, r);
|
||||||
|
if (note.fileIds.length > 0) {
|
||||||
|
this.fanoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 自分自身以外への返信
|
// 自分自身以外への返信
|
||||||
if (isReply(note)) {
|
if (isReply(note)) {
|
||||||
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||||
|
@ -20,6 +20,8 @@ import { MetaService } from '@/core/MetaService.js';
|
|||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
|
import { loadConfig } from '@/config.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
@ -194,10 +196,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
if (followees.length > 0) {
|
if (followees.length > 0) {
|
||||||
const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
|
const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
|
||||||
qb.where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
|
qb.where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
|
||||||
qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
|
|
||||||
} else {
|
} else {
|
||||||
qb.where('note.userId = :meId', { meId: me.id });
|
qb.where('note.userId = :meId', { meId: me.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
let defaultTag:string | null = config.tagging.defaultTag;
|
||||||
|
if (defaultTag == null) {
|
||||||
qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
|
qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
|
||||||
|
} else {
|
||||||
|
qb.orWhere(`(note.visibility = 'public') AND ('${normalizeForSearch(defaultTag)}' = any(note.tags)`);
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
.innerJoinAndSelect('note.user', 'user')
|
||||||
|
@ -18,6 +18,8 @@ import { MetaService } from '@/core/MetaService.js';
|
|||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
|
import { loadConfig } from '@/config.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
@ -149,9 +151,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
withFiles: boolean,
|
withFiles: boolean,
|
||||||
withReplies: boolean,
|
withReplies: boolean,
|
||||||
}, me: MiLocalUser | null) {
|
}, me: MiLocalUser | null) {
|
||||||
|
const config = loadConfig();
|
||||||
|
let defaultTag:string | null = config.tagging.defaultTag;
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
||||||
ps.sinceId, ps.untilId)
|
ps.sinceId, ps.untilId)
|
||||||
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL) AND (note.channelId IS NULL)')
|
.andWhere(
|
||||||
|
(defaultTag == null)
|
||||||
|
? '(note.visibility = \'public\') AND (note.userHost IS NULL) AND (note.channelId IS NULL)'
|
||||||
|
: `(note.visibility = 'public') AND ('${normalizeForSearch(defaultTag)}' = any(note.tags) AND (note.channelId IS NULL)`
|
||||||
|
)
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
.innerJoinAndSelect('note.user', 'user')
|
||||||
.leftJoinAndSelect('note.reply', 'reply')
|
.leftJoinAndSelect('note.reply', 'reply')
|
||||||
.leftJoinAndSelect('note.renote', 'renote')
|
.leftJoinAndSelect('note.renote', 'renote')
|
||||||
|
@ -13,6 +13,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
|
import { loadConfig } from '@/config.js';
|
||||||
|
|
||||||
class HybridTimelineChannel extends Channel {
|
class HybridTimelineChannel extends Channel {
|
||||||
public readonly chName = 'hybridTimeline';
|
public readonly chName = 'hybridTimeline';
|
||||||
@ -22,6 +24,7 @@ class HybridTimelineChannel extends Channel {
|
|||||||
private withRenotes: boolean;
|
private withRenotes: boolean;
|
||||||
private withReplies: boolean;
|
private withReplies: boolean;
|
||||||
private withFiles: boolean;
|
private withFiles: boolean;
|
||||||
|
private defaultTag: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
@ -43,6 +46,8 @@ class HybridTimelineChannel extends Channel {
|
|||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = params.withRenotes ?? true;
|
||||||
this.withReplies = params.withReplies ?? false;
|
this.withReplies = params.withReplies ?? false;
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.withFiles = params.withFiles ?? false;
|
||||||
|
const config = loadConfig();
|
||||||
|
this.defaultTag = config.tagging.defaultTag;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
@ -50,6 +55,11 @@ class HybridTimelineChannel extends Channel {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async onNote(note: Packed<'Note'>) {
|
private async onNote(note: Packed<'Note'>) {
|
||||||
|
let matched = false;
|
||||||
|
if (this.defaultTag != null) {
|
||||||
|
const noteTags = note.tags ? note.tags.map((t: string) => t.toLowerCase()) : [];
|
||||||
|
matched = noteTags.includes(normalizeForSearch(this.defaultTag));
|
||||||
|
}
|
||||||
const isMe = this.user!.id === note.userId;
|
const isMe = this.user!.id === note.userId;
|
||||||
|
|
||||||
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
|
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
|
||||||
@ -61,7 +71,7 @@ class HybridTimelineChannel extends Channel {
|
|||||||
if (!(
|
if (!(
|
||||||
(note.channelId == null && isMe) ||
|
(note.channelId == null && isMe) ||
|
||||||
(note.channelId == null && Object.hasOwn(this.following, note.userId)) ||
|
(note.channelId == null && Object.hasOwn(this.following, note.userId)) ||
|
||||||
(note.channelId == null && (note.user.host == null && note.visibility === 'public')) ||
|
(note.channelId == null && ((note.user.host == null || matched) && note.visibility === 'public')) ||
|
||||||
(note.channelId != null && this.followingChannels.has(note.channelId))
|
(note.channelId != null && this.followingChannels.has(note.channelId))
|
||||||
)) return;
|
)) return;
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
|
import { loadConfig } from '@/config.js';
|
||||||
|
|
||||||
class LocalTimelineChannel extends Channel {
|
class LocalTimelineChannel extends Channel {
|
||||||
public readonly chName = 'localTimeline';
|
public readonly chName = 'localTimeline';
|
||||||
@ -20,6 +22,7 @@ class LocalTimelineChannel extends Channel {
|
|||||||
private withRenotes: boolean;
|
private withRenotes: boolean;
|
||||||
private withReplies: boolean;
|
private withReplies: boolean;
|
||||||
private withFiles: boolean;
|
private withFiles: boolean;
|
||||||
|
private defaultTag: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
@ -41,6 +44,8 @@ class LocalTimelineChannel extends Channel {
|
|||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = params.withRenotes ?? true;
|
||||||
this.withReplies = params.withReplies ?? false;
|
this.withReplies = params.withReplies ?? false;
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.withFiles = params.withFiles ?? false;
|
||||||
|
const config = loadConfig();
|
||||||
|
this.defaultTag = config.tagging.defaultTag;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
@ -50,7 +55,12 @@ class LocalTimelineChannel extends Channel {
|
|||||||
private async onNote(note: Packed<'Note'>) {
|
private async onNote(note: Packed<'Note'>) {
|
||||||
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
|
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
|
||||||
|
|
||||||
|
if (this.defaultTag == null) {
|
||||||
if (note.user.host !== null) return;
|
if (note.user.host !== null) return;
|
||||||
|
} else {
|
||||||
|
const noteTags = note.tags ? note.tags.map((t: string) => t.toLowerCase()) : [];
|
||||||
|
if (!noteTags.includes(normalizeForSearch(this.defaultTag))) return;
|
||||||
|
}
|
||||||
if (note.visibility !== 'public') return;
|
if (note.visibility !== 'public') return;
|
||||||
if (note.channelId != null) return;
|
if (note.channelId != null) return;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user