Skip to content

Commit

Permalink
Update holodex for twitter broadcast
Browse files Browse the repository at this point in the history
  • Loading branch information
HitomaruKonpaku committed Nov 12, 2023
1 parent 180855a commit 13f2c17
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 111 deletions.
2 changes: 2 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ holodex:
active: false
space:
active: false
broadcast:
active: false
4 changes: 4 additions & 0 deletions src/module/config/config/holodex.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ export const holodexConfig = {
space: {
active: false,
},

broadcast: {
active: false,
},
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum HolodexExternalStreamType {
TWITTER_SPACE = 'twitter_space',
TWITTER_BROADCAST = 'twitter_broadcast',
}
10 changes: 9 additions & 1 deletion src/module/holodex/holodex.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'
import { ConfigModule } from '../config/config.module'
import { DiscordModule } from '../discord/discord.module'
import { TrackModule } from '../track/track.module'
import { TwitterBroadcast } from '../twitter/model/twitter-broadcast.entity'
import { TwitterSpace } from '../twitter/model/twitter-space.entity'
import { TwitterModule } from '../twitter/twitter.module'
import { YoutubeModule } from '../youtube/youtube.module'
Expand All @@ -13,11 +14,13 @@ import { HolodexVideo } from './model/holodex-video.entity'
import { HolodexApiService } from './service/api/holodex-api.service'
import { HolodexChannelControllerService } from './service/controller/holodex-channel-controller.service'
import { HolodexVideoControllerService } from './service/controller/holodex-video-controller.service'
import { HolodexBroadcastCronService } from './service/cron/holodex-broadcast-cron.service'
import { HolodexSpaceCronService } from './service/cron/holodex-space-cron.service'
import { HolodexBroadcastService } from './service/data/external/holodex-broadcast.service'
import { HolodexSpaceService } from './service/data/external/holodex-space.service'
import { HolodexChannelAccountService } from './service/data/holodex-channel-account.service'
import { HolodexChannelService } from './service/data/holodex-channel.service'
import { HolodexExternalStreamService } from './service/data/holodex-external-stream.service'
import { HolodexSpaceService } from './service/data/holodex-space.service'
import { HolodexVideoService } from './service/data/holodex-video.service'
import { HolodexService } from './service/holodex.service'
import { HolodexTweetTrackingService } from './service/tracking/holodex-tweet-tracking.service'
Expand All @@ -29,7 +32,9 @@ import { HolodexTweetTrackingService } from './service/tracking/holodex-tweet-tr
HolodexChannelAccount,
HolodexVideo,
HolodexExternalStream,

TwitterSpace,
TwitterBroadcast,
]),
ConfigModule,
TrackModule,
Expand All @@ -47,13 +52,15 @@ import { HolodexTweetTrackingService } from './service/tracking/holodex-tweet-tr
HolodexExternalStreamService,

HolodexSpaceService,
HolodexBroadcastService,

HolodexChannelControllerService,
HolodexVideoControllerService,

HolodexTweetTrackingService,

HolodexSpaceCronService,
HolodexBroadcastCronService,
],
exports: [
HolodexService,
Expand All @@ -65,6 +72,7 @@ import { HolodexTweetTrackingService } from './service/tracking/holodex-tweet-tr
HolodexExternalStreamService,

HolodexSpaceService,
HolodexBroadcastService,

HolodexChannelControllerService,
HolodexVideoControllerService,
Expand Down
8 changes: 8 additions & 0 deletions src/module/holodex/interface/holodex-broadcast.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { TwitterBroadcast } from '../../twitter/model/twitter-broadcast.entity'
import { HolodexChannelAccount } from '../model/holodex-channel_account.entity'
import { HolodexExternalStream } from '../model/holodex-external-stream.entity'

export interface HolodexBroadcast extends TwitterBroadcast {
holodexChannelAccount?: HolodexChannelAccount
holodexExternalStream?: HolodexExternalStream
}
7 changes: 7 additions & 0 deletions src/module/holodex/interface/holodex.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { HolodexChannelAccount } from '../model/holodex-channel_account.entity'
import { HolodexExternalStream } from '../model/holodex-external-stream.entity'

export interface HolodexItem {
holodexChannelAccount?: HolodexChannelAccount
holodexExternalStream?: HolodexExternalStream
}
134 changes: 134 additions & 0 deletions src/module/holodex/service/cron/base/holodex-base-cron.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import Bottleneck from 'bottleneck'
import { BaseCronService } from '../../../../../shared/service/base-cron.service'
import { BaseExternalEntity } from '../../../../database/model/base-external.entity'
import { DiscordService } from '../../../../discord/service/discord.service'
import { HolodexExternalStreamType } from '../../../enum/holodex-external-stream-type.enum'
import { HolodexItem } from '../../../interface/holodex.interface'
import { HolodexApiService } from '../../api/holodex-api.service'
import { HolodexExternalStreamService } from '../../data/holodex-external-stream.service'
import { HolodexVideoService } from '../../data/holodex-video.service'

export abstract class HolodexBaseCronService<T extends BaseExternalEntity> extends BaseCronService {
protected readonly TITLE_MIN_LENGTH = 4
protected readonly TITLE_MAX_LENGTH = 120

protected extraDurationSec = 120

constructor(
protected readonly holodexApiService: HolodexApiService,
protected readonly holodexVideoService: HolodexVideoService,
protected readonly holodexExternalStreamService: HolodexExternalStreamService,
protected readonly discordService: DiscordService,
) {
super()
}

public abstract getType(): HolodexExternalStreamType

public abstract getItems(): Promise<T[]>

protected abstract getItemTitle(item: T): string

protected abstract getItemUrl(item: T): string

protected abstract getItemThumbnailUrl(item: T): string

protected abstract getItemLiveTime(item: T): string

protected abstract getItemDuration(item: T): number

protected abstract getVideoCreatedAt(item: T): number

protected async onTick() {
try {
const items = await this.getItems()
if (!items?.length) {
return
}
this.logger.debug('onTick', { count: items.length, ids: items.map((v) => v.id) })
const limiter = new Bottleneck({ maxConcurrent: 1 })
await Promise.allSettled(items.map((v) => limiter.schedule(() => this.notifyItem(v))))
} catch (error) {
this.logger.error(`onTick: ${error.message}`)
}
}

protected async notifyItem(item: T & HolodexItem) {
try {
const owner = await this.discordService.getOwner()
const body = {
id: item.holodexExternalStream?.id,
channel_id: item.holodexChannelAccount.channelId,
title: {
name: this.getItemTitle(item),
link: this.getItemUrl(item),
thumbnail: this.getItemThumbnailUrl(item),
placeholderType: 'external-stream',
certainty: 'certain',
credits: {
bot: {
link: 'https://discord.gg/A24AbzgvRJ',
name: owner.username,
user: owner.username,
},
},
},
liveTime: this.getItemLiveTime(item),
duration: this.getItemDuration(item),
}

debugger
debugger
debugger
debugger
debugger

const { data } = await this.holodexApiService.postVideoPlaceholder(body)
if (data.error) {
const { placeholder } = data
if (placeholder) {
this.logger.error(`notifyItem#placeholder: ${data.error}`, {
id: item.id,
placeholder: {
id: placeholder.id,
link: placeholder.link,
},
})

body.id = placeholder.id
await this.holodexApiService.postVideoPlaceholder(body)
await this.saveVideo(placeholder, item)
return
}

this.logger.error(`notifyItem#response: ${data.error}`, { id: item.id })
return
}

if (!item.holodexExternalStream) {
const limiter = new Bottleneck({ maxConcurrent: 1 })
await Promise.allSettled(data.map((v) => limiter.schedule(() => this.saveVideo(v, item))))
}
} catch (error) {
const msg = [error.message, error.response?.data]
.filter((v) => v)
.join('. ')
this.logger.error(`notifyItem: ${msg}`, { id: item.id })
}
}

protected async saveVideo(data: any, item: T) {
await this.holodexVideoService.save({
id: data.id,
createdAt: this.getVideoCreatedAt(item),
channelId: data.channel_id,
type: data.type,
})
await this.holodexExternalStreamService.add({
id: data.id,
createdAt: this.getVideoCreatedAt(item),
type: this.getType(),
sourceId: item.id,
})
}
}
78 changes: 78 additions & 0 deletions src/module/holodex/service/cron/holodex-broadcast-cron.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint-disable class-methods-use-this */

import { Inject, Injectable, forwardRef } from '@nestjs/common'
import { baseLogger } from '../../../../logger'
import { DiscordService } from '../../../discord/service/discord.service'
import { TwitterUtil } from '../../../twitter/util/twitter.util'
import { HolodexExternalStreamType } from '../../enum/holodex-external-stream-type.enum'
import { HolodexBroadcast } from '../../interface/holodex-broadcast.interface'
import { HolodexApiService } from '../api/holodex-api.service'
import { HolodexBroadcastService } from '../data/external/holodex-broadcast.service'
import { HolodexExternalStreamService } from '../data/holodex-external-stream.service'
import { HolodexVideoService } from '../data/holodex-video.service'
import { HolodexBaseCronService } from './base/holodex-base-cron.service'

@Injectable()
export class HolodexBroadcastCronService extends HolodexBaseCronService<HolodexBroadcast> {
protected readonly logger = baseLogger.child({ context: HolodexBroadcastCronService.name })

protected cronTime = '0 */1 * * * *'
// protected cronRunOnInit = true

constructor(
@Inject(HolodexApiService)
protected readonly holodexApiService: HolodexApiService,
@Inject(HolodexVideoService)
protected readonly holodexVideoService: HolodexVideoService,
@Inject(HolodexExternalStreamService)
protected readonly holodexExternalStreamService: HolodexExternalStreamService,
@Inject(forwardRef(() => DiscordService))
protected readonly discordService: DiscordService,
@Inject(HolodexBroadcastService)
private readonly holodexBroadcastService: HolodexBroadcastService,
) {
super(holodexApiService, holodexVideoService, holodexExternalStreamService, discordService)
}

public getType(): HolodexExternalStreamType {
throw HolodexExternalStreamType.TWITTER_BROADCAST
}

public async getItems(): Promise<HolodexBroadcast[]> {
const items = await this.holodexBroadcastService.getManyLive()
return items
}

protected getItemTitle(item: HolodexBroadcast): string {
let title = item.title || `${item.user.username}'s Broadcast`
if (title.length < this.TITLE_MIN_LENGTH) {
title = `Broadcast: ${item.title}`
}
title = title.slice(0, this.TITLE_MAX_LENGTH)
return title
}

protected getItemUrl(item: HolodexBroadcast): string {
const url = TwitterUtil.getBroadcastUrl(item.id)
return url
}

protected getItemThumbnailUrl(item: HolodexBroadcast): string {
const url = item.imageUrl || item.user.profileImageUrl
return url
}

protected getItemLiveTime(item: HolodexBroadcast): string {
const time = new Date(item.startedAt).toISOString()
return time
}

protected getItemDuration(item: HolodexBroadcast): number {
const duration = Math.floor((Date.now() - item.startedAt) / 1000) + this.extraDurationSec
return duration
}

protected getVideoCreatedAt(item: HolodexBroadcast): number {
return item.startedAt
}
}
Loading

0 comments on commit 13f2c17

Please sign in to comment.