Skip to content
This repository has been archived by the owner on Jan 31, 2024. It is now read-only.

Commit

Permalink
add: Bubble timeline
Browse files Browse the repository at this point in the history
Closes  #154
  • Loading branch information
Mar0xy committed Dec 4, 2023
1 parent d586d1e commit 2f99c7e
Show file tree
Hide file tree
Showing 27 changed files with 387 additions and 4 deletions.
1 change: 1 addition & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,7 @@ _role:
high: "High"
_options:
gtlAvailable: "Can view the global timeline"
btlAvailable: "Can view the bubble timeline"
ltlAvailable: "Can view the local timeline"
canPublicNote: "Can send public notes"
canImportNotes: "Can import notes"
Expand Down
1 change: 1 addition & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1698,6 +1698,7 @@ export interface Locale {
};
"_options": {
"gtlAvailable": string;
"btlAvailable": string;
"ltlAvailable": string;
"canPublicNote": string;
"canImportNotes": string;
Expand Down
1 change: 1 addition & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,7 @@ _role:
high: ""
_options:
gtlAvailable: "グローバルタイムラインの閲覧"
btlAvailable: "バブルのタイムラインを見ることができる"
ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可"
canImportNotes: "ノートのインポートが可能"
Expand Down
11 changes: 11 additions & 0 deletions packages/backend/migration/1701647674000-BubbleInstances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class BubbleInstances1701647674000 {
name = 'BubbleInstances1701647674000'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "bubbleInstances" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bubbleInstances"`);
}
}
3 changes: 3 additions & 0 deletions packages/backend/src/core/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type { OnApplicationShutdown } from '@nestjs/common';
export type RolePolicies = {
gtlAvailable: boolean;
ltlAvailable: boolean;
btlAvailable: boolean;
canPublicNote: boolean;
canInvite: boolean;
inviteLimit: number;
Expand Down Expand Up @@ -53,6 +54,7 @@ export type RolePolicies = {
export const DEFAULT_POLICIES: RolePolicies = {
gtlAvailable: true,
ltlAvailable: true,
btlAvailable: false,
canPublicNote: true,
canInvite: false,
inviteLimit: 0,
Expand Down Expand Up @@ -303,6 +305,7 @@ export class RoleService implements OnApplicationShutdown {

return {
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
btlAvailable: calc('btlAvailable', vs => vs.some(v => v === true)),
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,4 +544,9 @@ export class MiMeta {
nullable: true,
})
public defaultLike: string | null;

@Column('varchar', {
length: 256, array: true, default: '{}',
})
public bubbleInstances: string[];
}
1 change: 1 addition & 0 deletions packages/backend/src/server/NodeinfoServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export class NodeinfoServerService {
disableRegistration: meta.disableRegistration,
disableLocalTimeline: !basePolicies.ltlAvailable,
disableGlobalTimeline: !basePolicies.gtlAvailable,
disableBubbleTimeline: !basePolicies.btlAvailable,
emailRequiredForSignup: meta.emailRequiredForSignup,
enableHcaptcha: meta.enableHcaptcha,
enableRecaptcha: meta.enableRecaptcha,
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/server/ServerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { AntennaChannelService } from './api/stream/channels/antenna.js';
import { ChannelChannelService } from './api/stream/channels/channel.js';
import { DriveChannelService } from './api/stream/channels/drive.js';
import { GlobalTimelineChannelService } from './api/stream/channels/global-timeline.js';
import { BubbleTimelineChannelService } from './api/stream/channels/bubble-timeline.js';
import { HashtagChannelService } from './api/stream/channels/hashtag.js';
import { HomeTimelineChannelService } from './api/stream/channels/home-timeline.js';
import { HybridTimelineChannelService } from './api/stream/channels/hybrid-timeline.js';
Expand Down Expand Up @@ -77,6 +78,7 @@ import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
ChannelChannelService,
DriveChannelService,
GlobalTimelineChannelService,
BubbleTimelineChannelService,
HashtagChannelService,
RoleTimelineChannelService,
HomeTimelineChannelService,
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/server/api/EndpointsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ import * as ep___notes_favorites_create from './endpoints/notes/favorites/create
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
import * as ep___notes_featured from './endpoints/notes/featured.js';
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
Expand Down Expand Up @@ -648,6 +649,7 @@ const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create'
const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
const $notes_bubbleTimeline: Provider = { provide: 'ep:notes/bubble-timeline', useClass: ep___notes_bubbleTimeline.default };
const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default };
const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
Expand Down Expand Up @@ -1023,6 +1025,7 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
$notes_favorites_delete,
$notes_featured,
$notes_globalTimeline,
$notes_bubbleTimeline,
$notes_hybridTimeline,
$notes_localTimeline,
$notes_mentions,
Expand Down Expand Up @@ -1392,6 +1395,7 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
$notes_favorites_delete,
$notes_featured,
$notes_globalTimeline,
$notes_bubbleTimeline,
$notes_hybridTimeline,
$notes_localTimeline,
$notes_mentions,
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ import * as ep___notes_favorites_create from './endpoints/notes/favorites/create
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
import * as ep___notes_featured from './endpoints/notes/featured.js';
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
Expand Down Expand Up @@ -646,6 +647,7 @@ const eps = [
['notes/favorites/delete', ep___notes_favorites_delete],
['notes/featured', ep___notes_featured],
['notes/global-timeline', ep___notes_globalTimeline],
['notes/bubble-timeline', ep___notes_bubbleTimeline],
['notes/hybrid-timeline', ep___notes_hybridTimeline],
['notes/local-timeline', ep___notes_localTimeline],
['notes/mentions', ep___notes_mentions],
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ export const meta = {
type: 'string',
},
},
bubbleInstances: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
},
},
hcaptchaSecretKey: {
type: 'string',
optional: false, nullable: true,
Expand Down Expand Up @@ -402,6 +409,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
silencedHosts: instance.silencedHosts,
sensitiveWords: instance.sensitiveWords,
preservedUsernames: instance.preservedUsernames,
bubbleInstances: instance.bubbleInstances,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
turnstileSecretKey: instance.turnstileSecretKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const paramDef = {
enableIdenticonGeneration: { type: 'boolean' },
serverRules: { type: 'array', items: { type: 'string' } },
preservedUsernames: { type: 'array', items: { type: 'string' } },
bubbleInstances: { type: 'array', items: { type: 'string' } },
manifestJsonOverride: { type: 'string' },
enableFanoutTimeline: { type: 'boolean' },
enableFanoutTimelineDbFallback: { type: 'boolean' },
Expand Down Expand Up @@ -482,6 +483,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.preservedUsernames = ps.preservedUsernames;
}

if (ps.bubbleInstances !== undefined) {
set.bubbleInstances = ps.bubbleInstances;
}

if (ps.manifestJsonOverride !== undefined) {
set.manifestJsonOverride = ps.manifestJsonOverride;
}
Expand Down
130 changes: 130 additions & 0 deletions packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm';
import type { NotesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js';
import { CacheService } from '@/core/CacheService.js';
import { MetaService } from '@/core/MetaService.js';

export const meta = {
tags: ['notes'],

res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'Note',
},
},

errors: {
btlDisabled: {
message: 'Bubble timeline has been disabled.',
code: 'BTL_DISABLED',
id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6c',
},
},
} as const;

export const paramDef = {
type: 'object',
properties: {
withFiles: { type: 'boolean', default: false },
withBots: { type: 'boolean', default: true },
withRenotes: { type: 'boolean', default: true },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
required: [],
} as const;

@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,

private noteEntityService: NoteEntityService,
private queryService: QueryService,
private roleService: RoleService,
private activeUsersChart: ActiveUsersChart,
private cacheService: CacheService,
private metaService: MetaService,
) {
super(meta, paramDef, async (ps, me) => {
const policies = await this.roleService.getUserPolicies(me ? me.id : null);
const instance = await this.metaService.fetch();
if (!policies.btlAvailable) {
throw new ApiError(meta.errors.btlDisabled);
}

const [
followings,
] = me ? await Promise.all([
this.cacheService.userFollowingsCache.fetch(me.id),
]) : [undefined];

//#region Construct query
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.visibility = \'public\'')
.andWhere('note.channelId IS NULL')
.andWhere(
`(note.userHost = ANY ('{"${instance.bubbleInstances.join('","')}"}'))`,
)
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');

if (me) {
this.queryService.generateMutedUserQuery(query, me);
this.queryService.generateBlockedUserQuery(query, me);
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
}

if (ps.withFiles) {
query.andWhere('note.fileIds != \'{}\'');
}

if (!ps.withBots) query.andWhere('user.isBot = FALSE');

if (ps.withRenotes === false) {
query.andWhere(new Brackets(qb => {
qb.where('note.renoteId IS NULL');
qb.orWhere(new Brackets(qb => {
qb.where('note.text IS NOT NULL');
qb.orWhere('note.fileIds != \'{}\'');
}));
}));
}
//#endregion

let timeline = await query.limit(ps.limit).getMany();

timeline = timeline.filter(note => {
if (note.user?.isSilenced && me && followings && note.userId !== me.id && !followings[note.userId]) return false;
return true;
});

process.nextTick(() => {
if (me) {
this.activeUsersChart.read(me);
}
});

return await this.noteEntityService.packMany(timeline, me);
});
}
}
3 changes: 3 additions & 0 deletions packages/backend/src/server/api/stream/ChannelsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { bindThis } from '@/decorators.js';
import { HybridTimelineChannelService } from './channels/hybrid-timeline.js';
import { LocalTimelineChannelService } from './channels/local-timeline.js';
import { HomeTimelineChannelService } from './channels/home-timeline.js';
import { BubbleTimelineChannelService } from './channels/bubble-timeline.js';
import { GlobalTimelineChannelService } from './channels/global-timeline.js';
import { MainChannelService } from './channels/main.js';
import { ChannelChannelService } from './channels/channel.js';
Expand All @@ -28,6 +29,7 @@ export class ChannelsService {
private localTimelineChannelService: LocalTimelineChannelService,
private hybridTimelineChannelService: HybridTimelineChannelService,
private globalTimelineChannelService: GlobalTimelineChannelService,
private bubbleTimelineChannelService: BubbleTimelineChannelService,
private userListChannelService: UserListChannelService,
private hashtagChannelService: HashtagChannelService,
private roleTimelineChannelService: RoleTimelineChannelService,
Expand All @@ -48,6 +50,7 @@ export class ChannelsService {
case 'localTimeline': return this.localTimelineChannelService;
case 'hybridTimeline': return this.hybridTimelineChannelService;
case 'globalTimeline': return this.globalTimelineChannelService;
case 'bubbleTimeline': return this.bubbleTimelineChannelService;
case 'userList': return this.userListChannelService;
case 'hashtag': return this.hashtagChannelService;
case 'roleTimeline': return this.roleTimelineChannelService;
Expand Down
Loading

0 comments on commit 2f99c7e

Please sign in to comment.