-
Notifications
You must be signed in to change notification settings - Fork 21
feat/enhanced-follow-list #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
245ed97
1077c76
c300df5
06d070a
b0abd0b
d7d436e
98fe469
a49b12f
06ffbb8
b8a2818
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,79 @@ | ||||||||||||
| import { HttpClient } from "$shared/http/http-client.ts"; | ||||||||||||
| import FollowSyncer from "$shared/module/shared-follows/follow-syncer.ts"; | ||||||||||||
| import type CommonDataService from "$shared/settings/common.service.ts"; | ||||||||||||
| import type CommonUtils from "$shared/utils/common.utils.ts"; | ||||||||||||
| import type { FollowedChannelsResponse } from "$types/platforms/kick/kick.api.types.ts"; | ||||||||||||
|
|
||||||||||||
| export default class KickFollowSyncer extends FollowSyncer { | ||||||||||||
| private readonly http: HttpClient = new HttpClient(this.logger); | ||||||||||||
| private syncInProgress = false; | ||||||||||||
|
|
||||||||||||
| constructor( | ||||||||||||
| private readonly commonDataService: CommonDataService, | ||||||||||||
| private readonly commonUtils: CommonUtils, | ||||||||||||
| ) { | ||||||||||||
| super("kick"); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| async getFollows() { | ||||||||||||
| if (this.syncInProgress) { | ||||||||||||
| this.logger.warn("Sync already in progress, skipping new request"); | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| this.syncInProgress = true; | ||||||||||||
| try { | ||||||||||||
| const followed = await this.fetchAllFollowed(); | ||||||||||||
| await this.commonDataService.updateCommonNestedKey("sharedFollows", "kick", followed); | ||||||||||||
| this.logger.info(`Synced ${followed.length} followed channels`); | ||||||||||||
| } catch (err) { | ||||||||||||
| this.logger.error("Failed to sync follows", err); | ||||||||||||
| } finally { | ||||||||||||
| this.syncInProgress = false; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| async clearFollows() { | ||||||||||||
| await this.commonDataService.updateCommonNestedKey("sharedFollows", "kick", []); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private async fetchAllFollowed(): Promise<string[]> { | ||||||||||||
| const collected = new Set<string>(); | ||||||||||||
| await this.fetchFollowedRecursive(0, collected); | ||||||||||||
| return Array.from(collected); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private async fetchFollowedRecursive(cursor: number, collected: Set<string>) { | ||||||||||||
| const authorization = this.getAuthHeader(); | ||||||||||||
| if (!authorization) return; | ||||||||||||
|
|
||||||||||||
| try { | ||||||||||||
| const url = new URL("https://kick.com/api/v2/channels/followed"); | ||||||||||||
| url.searchParams.set("cursor", String(cursor)); | ||||||||||||
|
|
||||||||||||
| const { data } = await this.http.request<FollowedChannelsResponse>(url.href, { | ||||||||||||
| method: "GET", | ||||||||||||
| headers: { Authorization: authorization }, | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| (data.channels ?? []).forEach((channel) => { | ||||||||||||
| const name = (channel.channel_slug || channel.user_username || "").toString().trim(); | ||||||||||||
| if (name) { | ||||||||||||
| collected.add(name.toLowerCase()); | ||||||||||||
| } | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| if (typeof data.nextCursor === "number") { | ||||||||||||
| await this.fetchFollowedRecursive(data.nextCursor, collected); | ||||||||||||
| } | ||||||||||||
|
Comment on lines
+45
to
+67
|
||||||||||||
| } catch (error) { | ||||||||||||
| this.logger.warn(`Failed to fetch followed channels at cursor ${cursor}`, error); | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private getAuthHeader(): string | undefined { | ||||||||||||
| const token = this.commonUtils.getCookie("session_token"); | ||||||||||||
| if (!token) return; | ||||||||||||
|
||||||||||||
| if (!token) return; | |
| if (!token) { | |
| this.logger.warn("Kick follow sync: 'session_token' cookie is missing; cannot fetch followed channels."); | |
| return; | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import KickModule from "$kick/kick.module.ts"; | ||
| import KickFollowSyncer from "$kick/modules/shared-follows/kick.follow-syncer.ts"; | ||
| import type { KickModuleConfig } from "$types/shared/module/module.types.ts"; | ||
|
|
||
| export default class SharedFollowsModule extends KickModule { | ||
| private readonly kickFollowsSyncer = new KickFollowSyncer(this.commonDataService(), this.commonUtils()); | ||
|
|
||
| config: KickModuleConfig = { | ||
| name: "shared-follows", | ||
| appliers: [ | ||
| { | ||
| type: "event", | ||
| event: "kick:settings:shareFollowsToOtherPlatforms", | ||
| callback: async (value) => { | ||
| if (value) await this.startSyncTimer(); | ||
| else { | ||
| await this.kickFollowsSyncer.clearFollows(); | ||
| this.stopSyncTimer(); | ||
| } | ||
| }, | ||
| key: "share-follows", | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| private syncFollowsTimer: NodeJS.Timeout | undefined; | ||
|
|
||
| async initialize(): Promise<void> { | ||
| const shareFollowsToOtherPlatforms = await this.settingsService().getSettingsKey("shareFollowsToOtherPlatforms"); | ||
| if (shareFollowsToOtherPlatforms) await this.startSyncTimer(); | ||
| } | ||
|
|
||
| private async startSyncTimer() { | ||
| this.stopSyncTimer(); | ||
| this.syncFollowsTimer = setInterval(() => this.kickFollowsSyncer.getFollows(), 120000); // 2 mins | ||
| await this.kickFollowsSyncer.getFollows(); | ||
| } | ||
|
|
||
| private stopSyncTimer() { | ||
| if (this.syncFollowsTimer) clearInterval(this.syncFollowsTimer); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default value for
showFollowsFromOtherPlatformsis set totrue, which means users will automatically see follows from other platforms without explicitly opting in. This could be surprising behavior for users. Consider setting this tofalseby default to make it an opt-in feature, especially sinceshareFollowsToOtherPlatformsis defaulted tofalse.