Music_Player3/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift

992 lines
47 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MPNetWorkManager.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/4/11.
//
import UIKit
import Network
import Alamofire
import AVFoundation
///
typealias BrowseRequestStateBlock = (_ browse:[MPPositive_BrowseModuleListViewModel], _ isCompeleted:Bool) -> Void
////
typealias ListRequestResultBlock = (_ list:MPPositive_ListAlbumListViewModel) -> Void
///
class MP_NetWorkManager: NSObject {
//
static let shared = MP_NetWorkManager()
//MARK: - API
///
private let header:String = "https://music.youtube.com"
///
private let point:String = "/youtubei/v1"
///
private let browse:String = "/browse"
///
private let next:String = "/next"
///
private let player:String = "/player"
///
private let suggestions:String = "/music/get_search_suggestions"
///
private let search = "/search"
//MARK: -
//访
private var visitorData:String?
//
private var continuationAndItct:(String?,String?){
willSet{
//
guard let continuation = newValue.0, let itct = newValue.1, browseQueque != nil, let url = URL(string: header+point+browse) else {
//线
browseQueque = nil
return
}
//
let parameters:[String:Any] = [
"ctoken":continuation,
"continuation":continuation,
"type":"next",
"itct":itct,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
//
browseQueque?.async {
[weak self] in
guard let self = self else { return }
requestPostHomeBrowse(url, parameters: parameters)
}
}
}
//MARK: -
//-
private var browseQueque:DispatchQueue?
//MARK: -
///
var browseRequestStateBlock:BrowseRequestStateBlock?
///
var listRequestResultBlock:ListRequestResultBlock?
//
private override init() {
super.init()
}
//MARK: -
///
func requestNetworkPermission(oberve:UIViewController, completeHanlder:ActionBlock?) {
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
switch path.status {
case .satisfied:
DispatchQueue.main.async {
guard completeHanlder != nil else {
return
}
completeHanlder!()
}
default://
DispatchQueue.main.async {
//
let alertController = UIAlertController(title: "Access network request", message: "”Musicoo“ needs to be loaded via a network request. Please click “Settings” to allow this application to gain access to the network.", preferredStyle: .alert)
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in
}
let OKAction = UIAlertAction(title: "Settings", style: .default) { (action) in
let url = URL(string: UIApplication.openSettingsURLString)
if let url = url,UIApplication.shared.canOpenURL(url){
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:]) { (success) in
}
}else{
UIApplication.shared.canOpenURL(url)
}
}
}
alertController.addAction(CancelAction)
alertController.addAction(OKAction)
oberve.present(alertController, animated: true, completion: nil)
}
}
}
let queue = DispatchQueue(label: "MPNetWorkManager")
monitor.start(queue: queue)
}
}
//MARK: - API
extension MP_NetWorkManager {
//MARK: -
///YouTubemusic/
func requestBrowseDatas() {
//continuationcontinuation,
//
browseQueque = DispatchQueue(label: "com.request.browseQueque")
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
browseQueque?.async {
[weak self] in
//IDcontinuation
guard let self = self else { return }
let parameters:[String:Any] = [
"browseId": "FEmusic_home",
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
requestPostHomeBrowse(url, parameters: parameters)
}
}
//
private func requestPostHomeBrowse(_ url:URL, parameters:Parameters) {
//postRootBrowses
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonBrowses.self) { [weak self] (response) in
guard let self = self else {return}
if let task = response.request {
print("URL: \(task.url!)")
}
switch response.result {
case .success(let value):
if value.responseContext?.visitorData != nil {
self.visitorData = value.responseContext?.visitorData
}
//
let tab = value.contents?.singleColumnBrowseResultsRenderer?.tabs?[0]
if let content = tab?.tabRenderer?.content {
parsingBrowseContents(content)
}else if let continuationContents = value.continuationContents {
parsingBrowseContinuationContents(continuationContents)
}else {
print("Failed to parse browses content")
}
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
//MARK: -
/// YouTubemusic/browse
/// - Parameters:
/// - item:
func requestAlbumOrListDatas(_ item: MPPositive_BrowseItemViewModel) {
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//browseIdparams
let parameters:[String:Any] = [
"browseId":(item.browseItem.browseContent.browseId ?? ""),
"params":(item.browseItem.browseContent.params ?? ""),
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
requestPostAlbumOrList(url, parameters: parameters)
}
///
private func requestPostAlbumOrList(_ url:URL, parameters:Parameters) {
//postRootBrowses
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonListOrAlbum.self) { [weak self] (response) in
guard let self = self else {return}
if let task = response.request {
print("URL: \(task.url!)")
}
switch response.result {
case .success(let value):
let contents = value.contents
//ViewModel
let list = MPPositive_ListAlbumListViewModel()
if let content = contents?.singleColumnBrowseResultsRenderer?.tabs?.first?.tabRenderer?.content {
list.items = parsingListContents(content)
}
if let header = value.header {
list.header = .init(parsingListHeaders(header))
}
//
guard listRequestResultBlock != nil else {
return
}
listRequestResultBlock!(list)
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
//MARK: -
///NextPlayer
/// - Parameter item:
func requestNextList(_ item: MPPositive_BrowseItemViewModel, completion:@escaping(([MPPositive_SongItemModel]) -> Void)) {
//next
let path = header+point+next
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//videoIdparams
let parameters:[String:Any] = [
"playlistId":(item.browseItem.musicVideo.playListId ?? ""),
"videoId":(item.browseItem.musicVideo.videoId ?? ""),
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
//next
requestPostNextList(url, parameters: parameters) { listSongs in
//
completion(listSongs)
}
}
//next
private func requestPostNextList(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SongItemModel]) -> Void)) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
//list
let listSongs:[MPPositive_SongItemModel] = parsingNextList(value)
completion(listSongs)
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
///Next/
/// - Parameter item:
func requestNextLyricsAndRelated(_ item: MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) {
//next
let path = header+point+next
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//videoIdparams
let parameters:[String:Any] = [
"videoId":(item.videoId ?? ""),
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
//next/
requestPostNextLyricsAndRelated(url, parameters: parameters) { result in
completion(result)
}
}
//Next/
private func requestPostNextLyricsAndRelated(_ url:URL, parameters:Parameters, completion:@escaping(((String?,String?)) -> Void)) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonNext.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
let result = parsingNextLyricsAndRelated(value)
//
completion(result)
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
//MARK: - player
/// Player(/)
/// - Parameter item:
func requestPlayer(_ item: MPPositive_SongItemModel, completion:@escaping (([String]?, [String]?) -> Void)){
//player
let path = header+point+player
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//videoIdparams
let parameters:[String:Any] = [
// "playlistId":(item.browseItem.musicVideo.playListId ?? ""),
"videoId":(item.videoId ?? ""),
"prettyPrint":"false",
"context":[
"client":[
//访
"clientName": "WEB_REMIX",
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00"
]
],
"playbackContext": [
"contentPlaybackContext": [
"signatureTimestamp": MP_WebWork.shared.signatureTimestamp ?? 0
]
]
]
requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in
completion(resourceUlrs, coverUrls)
}
}
///
private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping(([String]?, [String]?) -> Void)) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonPlayer.self) { [weak self] (response) in
guard let self = self else {return}
if let task = response.request {
print("URL: \(task.url!)")
}
switch response.result {
case .success(let value):
parsingPlayer(value) { resourceUlrs, coverUrls in
completion(resourceUlrs, coverUrls)
}
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
//MARK: -
///
/// - Parameter lyricId: id
func requestLyric(_ lyricId:String, completion:@escaping((String) -> Void)) {
//browse
let path = header+point+browse
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//browseIdparams
let parameters:[String:Any] = [
"browseId":lyricId,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
requestPostLyric(url, parameters: parameters) { lyrics in
completion(lyrics)
}
}
//
private func requestPostLyric(_ url:URL, parameters:Parameters, completion:@escaping((String) -> Void)) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonLyrics.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
completion(parsingLyrics(value) ?? "")
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
//MARK: -
///
/// - Parameter content:
func requestSearchSuggestions(_ content:String, completion:@escaping(([[MPPositive_SearchSuggestionItemModel]]) -> Void)) {
//
let path = header+point+suggestions
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
let parameters:[String:Any] = [
"input":content,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
requestPostSearchSuggestions(url, parameters: parameters) { result in
completion(result)
}
}
//
private func requestPostSearchSuggestions(_ url:URL, parameters:Parameters, completion:@escaping(([[MPPositive_SearchSuggestionItemModel]]) -> Void)) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchSuggestions.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
parsingSearchSuggestions(value) { results in
completion(results)
}
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
///
/// - Parameter text:
func requestSearchResults(_ text:String) {
//
let path = header+point+search
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
let parameters:[String:Any] = [
"query":text,
"prettyPrint":"false",
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
//
}
//
private func requestPostSearchResults(_ url:URL, parameters:Parameters) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchResults.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
if let contents = value.contents?.tabbedSearchResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents {
parsingSearchResults(contents)
}
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
}
//MARK: -
extension MP_NetWorkManager {
//MARK: -
///_Contents
private func parsingBrowseContents(_ content:JsonBrowses.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) {
//:idid
var browses:[MPPositive_BrowseModuleListViewModel] = []
var continuation:String?
var itct:String?
//
content.sectionListRenderer?.contents?.forEach({ content in
//
let browse = MPPositive_BrowseModuleListViewModel()
browse.title = content.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text
//
content.musicCarouselShelfRenderer?.contents?.forEach({ content in
///
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
browse.items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
browse.items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
})
browses.append(browse)
})
if let nextContinuationData = content.sectionListRenderer?.continuations?.first {
continuation = nextContinuationData.nextContinuationData?.continuation
itct = nextContinuationData.nextContinuationData?.clickTrackingParams
}
guard browseRequestStateBlock != nil else {
return
}
//
self.browseRequestStateBlock!(browses, (continuation == nil))
//
self.continuationAndItct = (continuation,itct)
}
///_ContinuationContents
private func parsingBrowseContinuationContents(_ continuationContents:JsonBrowses.ContinuationContents) {
//:idid
var browses:[MPPositive_BrowseModuleListViewModel] = []
var continuation:String?
var itct:String?
//
continuationContents.sectionListContinuation?.contents?.forEach({ content in
//
let browse = MPPositive_BrowseModuleListViewModel()
browse.title = content.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.first?.text
//
content.musicCarouselShelfRenderer?.contents?.forEach({ content in
///
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
browse.items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
browse.items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
})
browses.append(browse)
})
if let nextContinuationData = continuationContents.sectionListContinuation?.continuations?.first {
continuation = nextContinuationData.nextContinuationData?.continuation
itct = nextContinuationData.nextContinuationData?.clickTrackingParams
}
guard browseRequestStateBlock != nil else {
return
}
//
self.browseRequestStateBlock!(browses, (continuation == nil))
//
self.continuationAndItct = (continuation,itct)
}
///_Contents
private func parsingListContents(_ content:JsonListOrAlbum.Contents.SingleColumnBrowseResultsRenderer.Tab.TabRenderer.Content) -> [MPPositive_BrowseItemViewModel] {
var items:[MPPositive_BrowseItemViewModel] = []
//
content.sectionListRenderer?.contents?.forEach({ content in
//
if let musicPlaylistShelfRenderer = content.musicPlaylistShelfRenderer {
musicPlaylistShelfRenderer.contents?.forEach({ content in
///
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
})
}else if let musicShelfRenderer = content.musicShelfRenderer{
musicShelfRenderer.contents?.forEach({ content in
///
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
items.append(.init(parsingMusicResponsiveListItemRenderer(musicResponsiveListItemRenderer)))
}else if let musicTwoRowItemRenderer = content.musicTwoRowItemRenderer {
items.append(.init(parsingMusicTwoRowItemRenderer(musicTwoRowItemRenderer)))
}
})
}
})
return items
}
///_Header
private func parsingListHeaders(_ header:JsonListOrAlbum.Header) -> MPPositive_ListHeaderModel {
let listHeader = MPPositive_ListHeaderModel()
if let musicDetailHeaderRenderer = header.musicDetailHeaderRenderer {
//
listHeader.maintitle = musicDetailHeaderRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
//
listHeader.subtitle = musicDetailHeaderRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
//
listHeader.thirdtitle = musicDetailHeaderRenderer.secondSubtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
///
listHeader.forDescription = musicDetailHeaderRenderer.description?.runs?.reduce("", { $0 + ($1.text ?? "")})
///
listHeader.coverUrl = musicDetailHeaderRenderer.thumbnail?.croppedSquareThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
}else if let musicImmersiveHeaderRenderer = header.musicImmersiveHeaderRenderer {
listHeader.maintitle = musicImmersiveHeaderRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
listHeader.forDescription = musicImmersiveHeaderRenderer.description?.runs?.reduce("", { $0 + ($1.text ?? "")})
listHeader.coverUrl = musicImmersiveHeaderRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
}
return listHeader
}
///_Next_
private func parsingNextList(_ next:JsonNext) -> [MPPositive_SongItemModel] {
var array:[MPPositive_SongItemModel] = []
if let tabs = next.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs {
if let tab = tabs.first {
//
for (index, content) in (tab.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents ?? []).enumerated() {
if let playlistPanelVideoRenderer = content.playlistPanelVideoRenderer {
//
let song = MPPositive_SongItemModel()
song.index = index
song.title = playlistPanelVideoRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.longBylineText = playlistPanelVideoRenderer.longBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.lengthText = playlistPanelVideoRenderer.lengthText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.shortBylineText = playlistPanelVideoRenderer.shortBylineText?.runs?.reduce("", { $0 + ($1.text ?? "")})
song.reviewUrls = playlistPanelVideoRenderer.thumbnail?.thumbnails?.map({$0.url ?? ""})
song.videoId = playlistPanelVideoRenderer.videoId
array.append(song)
}
}
}
}
return array
}
/// _Next_IDIDIDbrwose
/// - Parameter next: next
/// - Returns: 0ID1ID
private func parsingNextLyricsAndRelated(_ next:JsonNext) -> (String?,String?){
if let tabs = next.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs {
if tabs.count == 3 {
//ID
let lyrcisId = tabs[1].tabRenderer?.endpoint?.browseEndpoint?.browseId
//ID
let relatedID = tabs[2].tabRenderer?.endpoint?.browseEndpoint?.browseId
return (lyrcisId, relatedID)
}else if tabs.count == 2 {
//ID
let lyrcisId = tabs[1].tabRenderer?.endpoint?.browseEndpoint?.browseId
return (lyrcisId, nil)
}else {
return (nil,nil)
}
}else {
return (nil,nil)
}
}
/// _Player
/// - Parameters:
/// - player: player
/// - completion:
private func parsingPlayer(_ player:JsonPlayer, completion:@escaping(([String]?, [String]?) -> Void)){
var infos:[String]?
//player
if let videoDetails = player.videoDetails {
infos = parsingPlayerVideoDetails(videoDetails)
}
if let streamingData = player.streamingData {
parsingPlayerStreamingData(streamingData){ urls in
completion(urls,infos)
}
}
}
/// _StreamingData
/// - Parameter streamingData:
private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String]) -> Void)) {
var group:DispatchGroup? = DispatchGroup()
var array:[String] = []
let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? [])
for format in allFormats {
if let signatureCipher = format.signatureCipher {
// DispatchGroup
group?.enter()
//
parsingPlayerSignatureCipher(signatureCipher) { result in
array.append(result)
// DispatchGroup
group?.leave()
}
}
}
group?.notify(queue: .main) {
completion(array)
group = nil
}
}
///_SignatureCipher
private func parsingPlayerSignatureCipher(_ signatureCipher:String, completion:@escaping((String) -> Void)) {
// print("Resources-SignatureCipher:\(signatureCipher)")
//
let originalURLString = seperatorOff(String(signatureCipher))
//
guard let sRange = originalURLString.range(of: "s=") else {
return
}
guard let spSigRange = originalURLString.range(of: "&sp=sig", range: sRange.upperBound..<originalURLString.endIndex) else {
return
}
//url
guard let urlRange = originalURLString.range(of: "&url=")?.lowerBound else {
return
}
let urlStartIndex = originalURLString.index(urlRange, offsetBy: 5) // "&url=" 5
//URl
let urlSubstring = originalURLString[urlStartIndex...] // &url=
let signString = String(originalURLString[sRange.upperBound..<spSigRange.lowerBound])
//
MP_WebWork.shared.excuteJavaScript(signString) { result in
//
let abString = urlSubstring + "&sig=" + result
// print("Resources-SignatureDecryption:\(abString)")
completion(abString)
}
}
/// _VideoDetails
/// - Parameter videoDetails:
/// - Returns: videoIdtitleauthorurls
private func parsingPlayerVideoDetails(_ videoDetails:JsonPlayer.VideoDetails) -> [String]? {
var urls:[String]?
videoDetails.thumbnail?.thumbnails?.forEach({ item in
if item.url != nil {
if urls != nil {
urls!.append(item.url!)
}else {
urls = []
urls!.append(item.url!)
}
}
})
return urls
}
///_Lyrics
private func parsingLyrics(_ lyrics:JsonLyrics) -> String? {
if let first = lyrics.contents?.sectionListRenderer?.contents?.first {
return first.musicDescriptionShelfRenderer?.description?.runs?.first?.text
}
return ""
}
/// _SearchSuggestions
/// - Parameters:
/// - searchSuggestions:
/// - completion:
private func parsingSearchSuggestions(_ searchSuggestions:JsonSearchSuggestions, completion:@escaping([[MPPositive_SearchSuggestionItemModel]]) -> Void) {
if let contents = searchSuggestions.contents {
var sections:[[MPPositive_SearchSuggestionItemModel]] = []
contents.forEach { section in
var suggestions:[MPPositive_SearchSuggestionItemModel] = []
section.searchSuggestionsSectionRenderer?.contents?.forEach({ content in
//
let item = MPPositive_SearchSuggestionItemModel()
if let searchSuggestionRenderer = content.searchSuggestionRenderer {
//
item.title = searchSuggestionRenderer.suggestion?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
var reviewUrls:[String] = []
//
musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.forEach({ thumbnail in
reviewUrls.append(thumbnail.url ?? "")
})
item.reviewUrls = reviewUrls
if let flexColumns = musicResponsiveListItemRenderer.flexColumns {
for (index,flexColumn) in flexColumns.enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
}
}
suggestions.append(item)
})
sections.append(suggestions)
}
completion(sections)
}
}
///_SearchResults
private func parsingSearchResults(_ contents:[JsonSearchResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) {
contents.forEach { content in
//
if let musicCardShelfRenderer = content.musicCardShelfRenderer {
//
}else {
//
}
}
}
//MARK: -
//musicResponsiveListItemRenderer/
private func parsingMusicResponsiveListItemRenderer(_ musicResponsiveListItemRenderer: RootMusicResponsiveListItemRenderer) -> MPPositive_BrowseItemModel {
//
let item = MPPositive_BrowseItemModel()
item.itemType = .single
//
item.coverUrl = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
if let flexColumns = musicResponsiveListItemRenderer.flexColumns {
for (index,flexColumn) in flexColumns.enumerated() {
if index == 0 {
item.maintitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else if index == 1 {
item.subtitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
item.thirdtitle = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}
var browseContent = BrowseItemContent()
flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.forEach({ run in
if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
//ID
item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId
}
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" {
//
browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
}
})
item.browseContent = browseContent
}
}
//idid
if let watch = musicResponsiveListItemRenderer.overlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer?.playNavigationEndpoint?.watchEndpoint {
var musicVideo = BrowseItemMusicVideo()
musicVideo.videoId = watch.videoId
musicVideo.playListId = watch.playlistId
musicVideo.musicVideoType = watch.watchEndpointMusicSupportedConfigs?.watchEndpointMusicConfig?.musicVideoType
item.musicVideo = musicVideo
}
return item
}
//musicResponsiveListItemRendererlist
private func parsingMusicTwoRowItemRenderer(_ musicTwoRowItemRenderer: RootMusicTwoRowItemRenderer) -> MPPositive_BrowseItemModel {
//
let item = MPPositive_BrowseItemModel()
item.itemType = .list
//
item.coverUrl = musicTwoRowItemRenderer.thumbnailRenderer?.musicThumbnailRenderer?.thumbnail?.thumbnails?.last?.url
//
item.maintitle = musicTwoRowItemRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
//
item.subtitle = musicTwoRowItemRenderer.subtitle?.runs?.reduce("", { $0 + ($1.text ?? "")})
var browseContent = BrowseItemContent()
musicTwoRowItemRenderer.title?.runs?.forEach({ run in
if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
//ID
item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId
}
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" {
//
browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
}
})
musicTwoRowItemRenderer.subtitle?.runs?.forEach({ run in
if run.navigationEndpoint?.browseEndpoint?.browseId != nil {
//ID
item.artistsId = run.navigationEndpoint?.browseEndpoint?.browseId
}
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != nil && run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType != "MUSIC_PAGE_TYPE_ARTIST" {
//
browseContent.pageType = run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType
browseContent.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
browseContent.params = run.navigationEndpoint?.browseEndpoint?.params
}
})
item.browseContent = browseContent
if let playListId = musicTwoRowItemRenderer.thumbnailOverlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer?.playNavigationEndpoint?.watchPlaylistEndpoint?.playlistId {
//ID
var musicVideo = BrowseItemMusicVideo()
musicVideo.playListId = playListId
item.musicVideo = musicVideo
}
if let videoId = musicTwoRowItemRenderer.navigationEndpoint?.watchEndpoint?.videoId {
//
var musicVideo = BrowseItemMusicVideo()
musicVideo.videoId = videoId
item.musicVideo = musicVideo
item.itemType = .single
}
return item
}
}
//MARK: -
extension MP_NetWorkManager {
///
private func seperatorOff(_ encodedString: String) -> String {
guard let decodedString = encodedString.removingPercentEncoding else {
print("百分比解码失败")
return ""
}
return decodedString
}
}