2021-10-23 16:49:45 +00:00
import Alamofire
import Defaults
import Foundation
import Logging
import SwiftyJSON
final class SponsorBlockAPI : ObservableObject {
2024-04-23 09:00:27 +00:00
static let categories = [ " sponsor " , " selfpromo " , " interaction " , " intro " , " outro " , " preview " , " filler " , " music_offtopic " ]
2021-10-23 16:49:45 +00:00
2021-12-17 19:55:52 +00:00
let logger = Logger ( label : " stream.yattee.app.sb " )
2021-10-24 09:16:04 +00:00
2021-10-23 16:49:45 +00:00
@ Published var videoID : String ?
@ Published var segments = [ Segment ] ( )
static func categoryDescription ( _ name : String ) -> String ? {
2023-09-23 13:07:27 +00:00
guard categories . contains ( name ) else {
2021-10-23 16:49:45 +00:00
return nil
}
switch name {
2022-09-04 15:28:30 +00:00
case " sponsor " :
return " Sponsor " . localized ( )
2021-10-23 16:49:45 +00:00
case " selfpromo " :
2024-04-23 09:00:27 +00:00
return " Unpaid/Self Promotion " . localized ( )
case " interaction " :
return " Interaction Reminder (Subscribe) " . localized ( )
2022-09-04 15:28:30 +00:00
case " intro " :
2024-04-23 09:00:27 +00:00
return " Intermission/Intro Animation " . localized ( )
2022-09-04 15:28:30 +00:00
case " outro " :
2024-04-23 09:00:27 +00:00
return " Endcards/Credits " . localized ( )
case " preview " :
return " Preview/Recap/Hook " . localized ( )
case " filler " :
return " Filler Tangent/Jokes " . localized ( )
2021-10-23 16:49:45 +00:00
case " music_offtopic " :
2024-04-23 09:00:27 +00:00
return " Music: Non-Music Section " . localized ( )
2021-10-23 16:49:45 +00:00
default :
return name . capitalized
}
}
2022-01-07 18:46:47 +00:00
static func categoryDetails ( _ name : String ) -> String ? {
2023-09-23 13:07:27 +00:00
guard categories . contains ( name ) else {
2022-01-07 18:46:47 +00:00
return nil
}
switch name {
case " sponsor " :
2022-09-04 15:28:30 +00:00
return ( " Part of a video promoting a product or service not directly related to the creator. " +
" The creator will receive payment or compensation in the form of money or free products. " ) . localized ( )
2022-01-07 18:46:47 +00:00
case " selfpromo " :
2024-04-23 09:00:27 +00:00
return ( " The creator will not receive any payment in exchange for this promotion. " +
" This includes charity drives or free shout outs for products or other people they like. \n \n " +
" Promoting a product or service that is directly related to the creator themselves. " +
2022-09-04 15:28:30 +00:00
" This usually includes merchandise or promotion of monetized platforms. " ) . localized ( )
2022-01-07 18:46:47 +00:00
2024-04-23 09:00:27 +00:00
case " interaction " :
return " Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video). " . localized ( )
2022-01-07 18:46:47 +00:00
case " intro " :
2022-09-04 15:28:30 +00:00
return ( " Segments typically found at the start of a video that include an animation, " +
" still frame or clip which are also seen in other videos by the same creator. " ) . localized ( )
2022-01-07 18:46:47 +00:00
case " outro " :
2023-04-22 08:56:18 +00:00
return " Typically near or at the end of the video when the credits pop up and/or endcards are shown. " . localized ( )
2022-01-07 18:46:47 +00:00
2024-04-23 09:00:27 +00:00
case " preview " :
return " Collection of clips that show what is coming up in in this video or other videos in a series where all information is repeated later in the video " . localized ( )
case " filler " :
return " Filler Tangent/ Jokes is only for tangential scenes added only for filler or humor that are not required to understand the main content of the video. " . localized ( )
2022-01-07 18:46:47 +00:00
case " music_offtopic " :
2023-04-22 08:56:18 +00:00
return " For videos which feature music as the primary content. " . localized ( )
2022-01-07 18:46:47 +00:00
default :
return nil
}
}
2021-12-17 19:55:52 +00:00
func loadSegments ( videoID : String , categories : Set < String > , completionHandler : @ escaping ( ) -> Void = { } ) {
2021-10-23 16:49:45 +00:00
guard ! skipSegmentsURL . isNil , self . videoID != videoID else {
2021-12-17 19:55:52 +00:00
completionHandler ( )
2021-10-23 16:49:45 +00:00
return
}
self . videoID = videoID
2023-05-26 21:24:00 +00:00
DispatchQueue . main . async { [ weak self ] in
2021-12-29 18:55:41 +00:00
self ? . requestSegments ( categories : categories , completionHandler : completionHandler )
}
2021-10-23 16:49:45 +00:00
}
2022-12-03 23:35:07 +00:00
func reset ( ) {
videoID = nil
segments = [ ]
}
2021-12-17 19:55:52 +00:00
private func requestSegments ( categories : Set < String > , completionHandler : @ escaping ( ) -> Void = { } ) {
2021-10-28 19:32:03 +00:00
guard let url = skipSegmentsURL , ! categories . isEmpty else {
2021-10-23 16:49:45 +00:00
return
}
2022-01-06 23:00:55 +00:00
AF . request ( url , parameters : parameters ( categories : categories ) ) . responseDecodable ( of : JSON . self ) { [ weak self ] response in
2022-09-28 14:27:01 +00:00
guard let self else {
2021-12-17 19:55:52 +00:00
return
}
2021-10-23 16:49:45 +00:00
switch response . result {
case let . success ( value ) :
self . segments = JSON ( value ) . arrayValue . map ( SponsorBlockSegment . init ) . sorted { $0 . end < $1 . end }
self . logger . info ( " loaded \( self . segments . count ) SponsorBlock segments " )
2024-04-23 09:00:27 +00:00
for segment in self . segments {
self . logger . info ( " \( segment . start ) -> \( segment . end ) " )
2021-10-23 16:49:45 +00:00
}
case let . failure ( error ) :
self . segments = [ ]
self . logger . error ( " failed to load SponsorBlock segments: \( error . localizedDescription ) " )
}
2021-12-17 19:55:52 +00:00
completionHandler ( )
2021-10-23 16:49:45 +00:00
}
}
private var skipSegmentsURL : String ? {
let url = Defaults [ . sponsorBlockInstance ]
return url . isEmpty ? nil : " \( url ) /api/skipSegments "
}
2021-10-28 19:32:03 +00:00
private func parameters ( categories : Set < String > ) -> [ String : String ] {
2021-10-23 16:49:45 +00:00
[
" videoID " : videoID ! ,
2021-10-28 19:32:03 +00:00
" categories " : JSON ( Array ( categories ) ) . rawString ( String . Encoding . utf8 ) !
2021-10-23 16:49:45 +00:00
]
}
}