public async Task ReceiveInternalAsync(YtInitialData ytInitialData, YtCfg ytCfg, CookieContainer cc, ILoginState loginInfo) { var dataToPost = new DataToPost(ytCfg); string initialContinuation; if (_siteOptions.IsAllChat) { initialContinuation = ytInitialData.ChatContinuation().AllChatContinuation; } else { initialContinuation = ytInitialData.ChatContinuation().JouiChatContinuation; } dataToPost.SetContinuation(initialContinuation); while (!_cts.IsCancellationRequested) { //例外はここではcatchしない。 var s = await Tools.GetGetLiveChat(dataToPost, ytCfg.InnerTubeApiKey, cc, loginInfo); var actions = s.GetActions(); var continuation = s.GetContinuation(); if (continuation is ReloadContinuation reload) { throw new ReloadException(); } else if (continuation is UnknownContinuation unknown) { throw new SpecChangedException(unknown.Raw); } dataToPost.SetContinuation(continuation.Continuation); var count = actions.Count; var timeoutMs = Math.Max(continuation.TimeoutMs, 1000); var waitTime = count > 0 ? timeoutMs / count : 1000; foreach (var action in actions) { ProcessAction(action); try { await Task.Delay(waitTime, _cts.Token); } catch (TaskCanceledException) { return; } } } }
/// <summary> /// /// </summary> /// <param name="vid"></param> /// <param name="ytCfg"></param> /// <param name="cc"></param> /// <param name="loginInfo"></param> /// <returns></returns> /// <exception cref="ReloadException"></exception> /// <exception cref="SpecChangedException"></exception> public async Task ReceiveAsync(string vid, YtInitialData ytInitialData1, YtCfg ytCfg, CookieContainer cc, ILoginState loginInfo) { if (_cts != null) { throw new InvalidOperationException("receiving"); } _cts = new CancellationTokenSource(); try { await ReceiveInternalAsync(ytInitialData1, ytCfg, cc, loginInfo); } finally { _cts = null; } }
public DataToPost(YtCfg ytCfg) { dynamic d = JsonConvert.DeserializeObject("{\"context\":{}}", new JsonSerializerSettings { Formatting = Formatting.None }); dynamic context = JsonConvert.DeserializeObject(ytCfg.InnerTubeContext, new JsonSerializerSettings { Formatting = Formatting.None }); d.context = context; //2021/01/16 //userが最初から設定されている場合があった //ただ、その要素は //user //+gaiaId //+userId //+lockedSafetyMode //onBehalfOfUserは含まれていなかった。上記の要素だけで十分なのかは不明。 if (!d.ContainsKey("user")) { if (ytCfg.DelegatedSessionId != null)//未ログインの場合は設定されない { dynamic user = JsonConvert.DeserializeObject("{\"onBehalfOfUser\":" + "\"" + ytCfg.DelegatedSessionId + "\"" + "}", new JsonSerializerSettings { Formatting = Formatting.None }); d.context.user = user; } else { dynamic user = JsonConvert.DeserializeObject("{}", new JsonSerializerSettings { Formatting = Formatting.None }); d.context.user = user; } } _d = d; }
public async Task ReceiveInternalAsync(YtInitialData ytInitialData1, YtCfg ytCfg, CookieContainer cc, ILoginState loginInfo) { var dataToPost = new DataToPost(ytCfg); string initialContinuation; if (_siteOptions.IsAllChat) { initialContinuation = ytInitialData1.AllChatContinuation;// ytInitialData.ChatContinuation().AllChatContinuation; } else { initialContinuation = ytInitialData1.JouiChatContinuation;// ytInitialData.ChatContinuation().JouiChatContinuation; } dataToPost.SetContinuation(initialContinuation); while (!_cts.IsCancellationRequested) { //例外はここではcatchしない。 var getLiveChat = await Tools.GetGetLiveChat(dataToPost, ytCfg.InnertubeApiKey, cc, loginInfo, _logger); var actions = getLiveChat.Actions; var continuation = getLiveChat.Continuation;// s.GetContinuation(); if (continuation is null) { throw new ContinuationNotExistsException(); } if (continuation is ReloadContinuationData reload) { throw new ReloadException(); } else if (continuation is TimedContinuationData timed) { dataToPost.SetContinuation(timed.Continaution); await ProcessAction(actions, timed.TimeoutMs); } else if (continuation is InvalidationContinuationData invalid) { dataToPost.SetContinuation(invalid.Continaution); await ProcessAction(actions, invalid.TimeoutMs); } else if (continuation is UnknownContinuationData unknown) { throw new SpecChangedException(unknown.Raw); } else { //ここには来ない予定 throw new SpecChangedException(""); } //if (continuation is ReloadContinuation reload) //{ // throw new ReloadException(); //} //else if (continuation is UnknownContinuation unknown) //{ // throw new SpecChangedException(unknown.Raw); //} //dataToPost.SetContinuation(continuation.Continuation); //var timeoutMs = Math.Max(continuation.TimeoutMs, 1000); //if (actions.Count > 0) //{ // var waitTime = timeoutMs / actions.Count; // foreach (var action in actions) // { // ProcessAction(action); // try // { // await Task.Delay(waitTime, _cts.Token); // } // catch (TaskCanceledException) // { // return; // } // } //} //else //{ // var waitTime = timeoutMs; // try // { // await Task.Delay(waitTime, _cts.Token); // } // catch (TaskCanceledException) // { // return; // } //} } }
private async Task ConnectInternalAsync(string input, IBrowserProfile browserProfile) { var resolver = new VidResolver(); var vidResult = await resolver.GetVid(_server, input); string vid; switch (vidResult) { case VidResult single: vid = single.Vid; break; case MultiVidsResult multi: SendSystemInfo("このチャンネルでは複数のライブが配信中です。", InfoType.Notice); foreach (var v in multi.Vids) { SendSystemInfo(v, InfoType.Notice); //titleも欲しい } return; case NoVidResult no: SendSystemInfo("このチャンネルでは生放送をしていないようです", InfoType.Error); return; default: throw new NotImplementedException(); } _cc = CreateCookieContainer(browserProfile); await InitElapsedTimer(vid); _chatProvider = new ChatProvider2(_siteOptions); _chatProvider.MessageReceived += ChatProvider_MessageReceived; _chatProvider.LoggedInStateChanged += _chatProvider_LoggedInStateChanged; _chatProvider.InfoReceived += ChatProvider_InfoReceived; var metaProvider = new MetaDataYoutubeiProvider(_server, _logger); metaProvider.InfoReceived += MetaProvider_InfoReceived; metaProvider.MetadataReceived += MetaProvider_MetadataReceived; reload: var liveChatHtml = await GetLiveChat(vid, _cc); var ytCfgStr = Tools.ExtractYtCfg(liveChatHtml); var ytCfg = new YtCfg(ytCfgStr); var ytInitialData = Tools.ExtractYtInitialData(liveChatHtml); if (!ytInitialData.CanChat) { SendSystemInfo("このライブストリームではチャットは無効です。", InfoType.Notice); return; } var loginInfo = Tools.CreateLoginInfo(ytInitialData.IsLoggedIn); SetLoggedInState(ytInitialData.IsLoggedIn); _postCommentCoodinator = new DataCreator(ytInitialData, ytCfg.InnerTubeApiKey, _cc); var initialActions = ytInitialData.GetActions(); foreach (var action in initialActions) { OnMessageReceived(action, true); } var chatTask = _chatProvider.ReceiveAsync(vid, ytInitialData, ytCfg, _cc, loginInfo); var metaTask = metaProvider.ReceiveAsync(ytCfg, vid, _cc); var tasks = new List <Task> { chatTask, metaTask }; while (tasks.Count > 0) { var t = await Task.WhenAny(tasks); if (t == chatTask) { metaProvider.Disconnect(); try { await metaTask; } catch (Exception ex) { _logger.LogException(ex); } tasks.Remove(metaTask); try { await chatTask; } catch (ChatUnavailableException) { _isDisconnectedExpected = true; SendSystemInfo("配信が終了したか、チャットが無効です。", InfoType.Notice); } catch (ReloadException) { } catch (SpecChangedException ex) { SendSystemInfo("YouTubeの仕様変更に未対応のためコメント取得を継続できません", InfoType.Error); _logger.LogException(ex); _isDisconnectedExpected = true; } catch (Exception ex) { SendSystemInfo(ex.Message, InfoType.Error); //意図しない切断 //ただし、サーバーからReloadメッセージが来た場合と違って、単純にリロードすれば済む問題ではない。 _logger.LogException(ex); await Task.Delay(1000); } tasks.Remove(chatTask); if (_isDisconnectedExpected == false) { //何らかの原因で意図しない切断が発生した。 SendSystemInfo("エラーが発生したためサーバーとの接続が切断されましたが、自動的に再接続します", InfoType.Notice); goto reload; } } else { try { await metaTask; } catch (Exception ex) { _logger.LogException(ex); } tasks.Remove(metaTask); } } _chatProvider.MessageReceived -= ChatProvider_MessageReceived; _chatProvider.LoggedInStateChanged -= _chatProvider_LoggedInStateChanged; _chatProvider.InfoReceived -= ChatProvider_InfoReceived; metaProvider.InfoReceived -= MetaProvider_InfoReceived; metaProvider.MetadataReceived -= MetaProvider_MetadataReceived; }