Merge f4fb82ba23 into 6718a54f6f
This commit is contained in:
commit
860275d116
8 changed files with 83 additions and 4 deletions
|
|
@ -219,3 +219,7 @@ signToActivityPubGet: true
|
|||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
defaultTag:
|
||||
tag: null
|
||||
append: true
|
||||
|
|
|
|||
|
|
@ -321,3 +321,7 @@ signToActivityPubGet: true
|
|||
|
||||
# PID File of master process
|
||||
#pidFile: /tmp/misskey.pid
|
||||
|
||||
defaultTag:
|
||||
tag: null
|
||||
append: true
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ type Source = {
|
|||
};
|
||||
sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; };
|
||||
sentryForFrontend?: { options: Partial<Sentry.NodeOptions> };
|
||||
defaultTag: {
|
||||
tag: string;
|
||||
append: boolean;
|
||||
};
|
||||
|
||||
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
|
||||
|
||||
|
|
@ -132,6 +136,10 @@ export type Config = {
|
|||
index: string;
|
||||
scope?: 'local' | 'global' | string[];
|
||||
} | undefined;
|
||||
defaultTag: {
|
||||
tag: string;
|
||||
append: boolean;
|
||||
};
|
||||
proxy: string | undefined;
|
||||
proxySmtp: string | undefined;
|
||||
proxyBypassHosts: string[] | undefined;
|
||||
|
|
@ -293,6 +301,7 @@ export function loadConfig(): Config {
|
|||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
|
||||
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
||||
pidFile: config.pidFile,
|
||||
defaultTag: config.defaultTag,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -330,6 +330,16 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
data.localOnly = true;
|
||||
}
|
||||
|
||||
// デフォルトハッシュタグを本文末尾に書き足す
|
||||
if (this.config.defaultTag?.append && ['public', 'home'].includes(data.visibility)) {
|
||||
if (this.config.defaultTag?.tag != null) {
|
||||
const tag = `#${this.config.defaultTag?.tag}`;
|
||||
if (String(data.text).match(tag)) {
|
||||
data.text = `${data.text}\n\n${tag}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.text) {
|
||||
if (data.text.length > DB_MAX_NOTE_TEXT_LENGTH) {
|
||||
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
||||
|
|
@ -944,6 +954,18 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
// デフォルトハッシュタグを含む投稿は、リモートであってもローカルタイムラインに含める
|
||||
if (this.config.defaultTag?.tag != null) {
|
||||
const noteTags = note.tags ? note.tags.map((t: string) => t.toLowerCase()) : [];
|
||||
if (note.visibility === 'public' && noteTags.includes(normalizeForSearch(this.config.defaultTag?.tag))) {
|
||||
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 ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import { UserFollowingService } from '@/core/UserFollowingService.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'],
|
||||
|
|
@ -207,10 +209,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();
|
||||
const defaultTag: string | null = config.defaultTag?.tag;
|
||||
if (defaultTag == null) {
|
||||
qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
|
||||
} else {
|
||||
qb.orWhere(`(note.visibility = 'public') AND (:t <@ note.tags`, { t: normalizeForSearch(defaultTag) });
|
||||
}
|
||||
}))
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import { QueryService } from '@/core/QueryService.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'],
|
||||
|
|
@ -146,9 +148,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
withFiles: boolean,
|
||||
withReplies: boolean,
|
||||
}, me: MiLocalUser | null) {
|
||||
const config = loadConfig();
|
||||
const defaultTag: string | null = config.defaultTag?.tag;
|
||||
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(new Brackets(qb => {
|
||||
qb.andWhere('note.visibility = \'public\'');
|
||||
qb.andWhere('note.channelId IS NULL');
|
||||
if (defaultTag == null) {
|
||||
qb.andWhere('note.userHost IS NULL');
|
||||
} else {
|
||||
qb.andWhere(`:t <@ note.tags`, { t: normalizeForSearch(defaultTag) });
|
||||
}
|
||||
}))
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('note.reply', 'reply')
|
||||
.leftJoinAndSelect('note.renote', 'renote')
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
import type { JsonObject } from '@/misc/json-value.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';
|
||||
|
|
@ -21,6 +23,7 @@ class HybridTimelineChannel extends Channel {
|
|||
private withRenotes: boolean;
|
||||
private withReplies: boolean;
|
||||
private withFiles: boolean;
|
||||
private defaultTag: string | null;
|
||||
|
||||
constructor(
|
||||
private metaService: MetaService,
|
||||
|
|
@ -42,6 +45,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.defaultTag?.tag;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on('notesStream', this.onNote);
|
||||
|
|
@ -49,6 +54,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;
|
||||
|
|
@ -60,7 +70,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 { RoleService } from '@/core/RoleService.js';
|
|||
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
|
||||
import type { JsonObject } from '@/misc/json-value.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 | null;
|
||||
|
||||
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.defaultTag?.tag;
|
||||
|
||||
// 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…
Add table
Add a link
Reference in a new issue