mirror of
https://github.com/misskey-dev/misskey.git
synced 2025-01-25 03:10:57 +09:00
デフォルトハッシュタグタイムライン
This commit is contained in:
parent
15727088be
commit
594193ba8a
@ -106,7 +106,7 @@ redis:
|
||||
# ┌───────────────────────────┐
|
||||
#───┘ MeiliSearch configuration └─────────────────────────────
|
||||
|
||||
# You can set scope to local (default value) or global
|
||||
# You can set scope to local (default value) or global
|
||||
# (include notes from remote).
|
||||
|
||||
#meilisearch:
|
||||
@ -185,7 +185,7 @@ proxyRemoteFiles: true
|
||||
signToActivityPubGet: true
|
||||
|
||||
# For security reasons, uploading attachments from the intranet is prohibited,
|
||||
# but exceptions can be made from the following settings. Default value is "undefined".
|
||||
# but exceptions can be made from the following settings. Default value is "undefined".
|
||||
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
|
||||
#allowedPrivateNetworks: [
|
||||
# '127.0.0.1/32'
|
||||
@ -193,3 +193,6 @@ signToActivityPubGet: true
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
tagging:
|
||||
defaultTag: null
|
||||
|
@ -118,7 +118,7 @@ redis:
|
||||
# ┌───────────────────────────┐
|
||||
#───┘ MeiliSearch configuration └─────────────────────────────
|
||||
|
||||
# You can set scope to local (default value) or global
|
||||
# You can set scope to local (default value) or global
|
||||
# (include notes from remote).
|
||||
|
||||
#meilisearch:
|
||||
@ -214,7 +214,7 @@ proxyRemoteFiles: true
|
||||
signToActivityPubGet: true
|
||||
|
||||
# For security reasons, uploading attachments from the intranet is prohibited,
|
||||
# but exceptions can be made from the following settings. Default value is "undefined".
|
||||
# but exceptions can be made from the following settings. Default value is "undefined".
|
||||
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
|
||||
#allowedPrivateNetworks: [
|
||||
# '127.0.0.1/32'
|
||||
@ -225,3 +225,6 @@ signToActivityPubGet: true
|
||||
|
||||
# PID File of master process
|
||||
#pidFile: /tmp/misskey.pid
|
||||
|
||||
tagging:
|
||||
defaultTag: null
|
||||
|
@ -56,6 +56,9 @@ type Source = {
|
||||
index: string;
|
||||
scope?: 'local' | 'global' | string[];
|
||||
};
|
||||
tagging: {
|
||||
defaultTag: string;
|
||||
};
|
||||
|
||||
proxy?: string;
|
||||
proxySmtp?: string;
|
||||
@ -124,6 +127,9 @@ export type Config = {
|
||||
index: string;
|
||||
scope?: 'local' | 'global' | string[];
|
||||
} | undefined;
|
||||
tagging: {
|
||||
defaultTag: string;
|
||||
};
|
||||
proxy: string | undefined;
|
||||
proxySmtp: string | undefined;
|
||||
proxyBypassHosts: string[] | undefined;
|
||||
@ -261,6 +267,7 @@ export function loadConfig(): Config {
|
||||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
|
||||
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
||||
pidFile: config.pidFile,
|
||||
tagging: config.tagging,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,7 @@ import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
import { isReply } from '@/misc/is-reply.js';
|
||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||
import { loadConfig } from '@/config.js';
|
||||
|
||||
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)) {
|
||||
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 { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import { loadConfig } from '@/config.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
@ -194,10 +196,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
if (followees.length > 0) {
|
||||
const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
|
||||
qb.where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
|
||||
qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
|
||||
} else {
|
||||
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)');
|
||||
} else {
|
||||
qb.orWhere(`(note.visibility = 'public') AND ('${normalizeForSearch(defaultTag)}' = any(note.tags)`);
|
||||
}
|
||||
}))
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
|
@ -18,6 +18,8 @@ import { MetaService } from '@/core/MetaService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import { loadConfig } from '@/config.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
@ -149,9 +151,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
withFiles: boolean,
|
||||
withReplies: boolean,
|
||||
}, me: MiLocalUser | null) {
|
||||
const config = loadConfig();
|
||||
let defaultTag:string | null = config.tagging.defaultTag;
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
||||
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')
|
||||
.leftJoinAndSelect('note.reply', 'reply')
|
||||
.leftJoinAndSelect('note.renote', 'renote')
|
||||
|
@ -13,6 +13,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.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 {
|
||||
public readonly chName = 'hybridTimeline';
|
||||
@ -22,6 +24,7 @@ class HybridTimelineChannel extends Channel {
|
||||
private withRenotes: boolean;
|
||||
private withReplies: boolean;
|
||||
private withFiles: boolean;
|
||||
private defaultTag: string;
|
||||
|
||||
constructor(
|
||||
private metaService: MetaService,
|
||||
@ -43,6 +46,8 @@ class HybridTimelineChannel extends Channel {
|
||||
this.withRenotes = params.withRenotes ?? true;
|
||||
this.withReplies = params.withReplies ?? false;
|
||||
this.withFiles = params.withFiles ?? false;
|
||||
const config = loadConfig();
|
||||
this.defaultTag = config.tagging.defaultTag;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on('notesStream', this.onNote);
|
||||
@ -50,6 +55,11 @@ class HybridTimelineChannel extends Channel {
|
||||
|
||||
@bindThis
|
||||
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;
|
||||
|
||||
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
|
||||
@ -61,7 +71,7 @@ class HybridTimelineChannel extends Channel {
|
||||
if (!(
|
||||
(note.channelId == null && isMe) ||
|
||||
(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))
|
||||
)) return;
|
||||
|
||||
|
@ -12,6 +12,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.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 {
|
||||
public readonly chName = 'localTimeline';
|
||||
@ -20,6 +22,7 @@ class LocalTimelineChannel extends Channel {
|
||||
private withRenotes: boolean;
|
||||
private withReplies: boolean;
|
||||
private withFiles: boolean;
|
||||
private defaultTag: string;
|
||||
|
||||
constructor(
|
||||
private metaService: MetaService,
|
||||
@ -41,6 +44,8 @@ class LocalTimelineChannel extends Channel {
|
||||
this.withRenotes = params.withRenotes ?? true;
|
||||
this.withReplies = params.withReplies ?? false;
|
||||
this.withFiles = params.withFiles ?? false;
|
||||
const config = loadConfig();
|
||||
this.defaultTag = config.tagging.defaultTag;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on('notesStream', this.onNote);
|
||||
@ -50,7 +55,12 @@ class LocalTimelineChannel extends Channel {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
|
||||
|
||||
if (note.user.host !== null) return;
|
||||
if (this.defaultTag == null) {
|
||||
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.channelId != null) return;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user