public static Input.IInput ParseInput(string input) { if (string.IsNullOrEmpty(input)) { throw new ArgumentNullException(nameof(input)); } if (VidResolver.IsChannel(input)) { return(new Input.ChannelUrl(input)); } else if (VidResolver.IsCustomChannel(input)) { return(new Input.CustomChannelUrl(input)); } else if (VidResolver.IsStudio(input)) { return(new Input.StudioUrl(input)); } else if (VidResolver.IsUser(input)) { return(new Input.UserUrl(input)); } else if (VidResolver.IsVid(input)) { return(new Input.Vid(input)); } else if (VidResolver.IsWatch(input)) { return(new Input.WatchUrl(input)); } return(new Input.InvalidInput(input)); }
public WatchUrl(string watchUrl) { Raw = watchUrl; if (!VidResolver.TryWatch(watchUrl, out var vid)) { throw new ArgumentException(nameof(vid)); } Vid = vid; }
public async Task 短縮URLに対応() { var s = new VidResolver(); var serverMock = new Mock <IYouTubeLibeServer>(); var result1 = await s.GetVid(serverMock.Object, "https://youtu.be/bexmlC2nD0U"); Assert.IsTrue(result1 is IVidResult); Assert.AreEqual("bexmlC2nD0U", ((VidResult)result1).Vid); }
public async Task ResolveVidFromChannelUrl() { var sample = Tools.GetSampleData("Channel_some_archives.txt"); var s = new VidResolver(); var serverMock = new Mock <IYouTubeLibeServer>(); serverMock.Setup(k => k.GetEnAsync("https://www.youtube.com/channel/UCkDuaqQxw3k7Aa816kngUYg/videos?flow=list&view=2")).Returns(Task.FromResult(sample)); var result1 = await s.GetVid(serverMock.Object, "https://www.youtube.com/channel/UCkDuaqQxw3k7Aa816kngUYg"); Assert.IsTrue(result1 is NoVidResult); }
public async Task GetVidsFromChannelId_CC_label_Test() { var html = Tools.GetSampleData("Channel_some_archives_with_cc_label.txt"); var server = new Mock <IYouTubeLibeServer>(); var channelId = "UCBL9Blq9GDhPGAQbfUJIYXQ"; server.Setup(s => s.GetEnAsync("https://www.youtube.com/channel/" + channelId + "/videos?flow=list&view=0")).Returns(Task.FromResult(html)); var resolver = new VidResolver(); var vids = await resolver.GetVidsFromChannelId(server.Object, channelId); Assert.IsTrue(vids.Count == 0); }
public async Task ResolveVidFromChanneLivelUrl() { var sample = Tools.GetSampleData("Channel_live.txt"); var s = new VidResolver(); var serverMock = new Mock <IYouTubeLibeServer>(); serverMock.Setup(k => k.GetEnAsync(It.IsAny <string>())).Returns(Task.FromResult(sample)); var result1 = await s.GetVid(serverMock.Object, "https://www.youtube.com/channel/UCkDuaqQxw3k7Aa816kngUYg") as VidResult; Assert.IsTrue(result1 is VidResult); Assert.AreEqual("klvzbBP7zM8", ((VidResult)result1).Vid); }
public async Task GetVidsFromChannelId_on_air_Test() { var html = Tools.GetSampleData("Channel_on_air.txt"); var server = new Mock <IYouTubeLibeServer>(); var channelId = "UCBL9Blq9GDhPGAQbfUJIYXQ"; server.Setup(s => s.GetEnAsync("https://www.youtube.com/channel/" + channelId + "/videos?flow=list&view=0")).Returns(Task.FromResult(html)); var resolver = new VidResolver(); var vids = await resolver.GetVidsFromChannelId(server.Object, channelId); Assert.IsTrue(vids.Count == 1); Assert.AreEqual("AuFOOUtIyUY", vids[0]); }
public async Task ResolveVidFromWatchUrl() { var s = new VidResolver(); var serverMock = new Mock <IYouTubeLibeServer>(); var result1 = await s.GetVid(serverMock.Object, "https://www.youtube.com/watch?v=Rs-WxTGgVus"); Assert.IsTrue(result1 is IVidResult); Assert.AreEqual("Rs-WxTGgVus", ((VidResult)result1).Vid); var result2 = await s.GetVid(serverMock.Object, "https://www.youtube.com/watch?v=Rs-WxTGgVus&feature=test"); Assert.IsTrue(result2 is IVidResult); Assert.AreEqual("Rs-WxTGgVus", ((VidResult)result2).Vid); }
public UserUrl(string userUrl) { Raw = userUrl; UserId = VidResolver.ExtractUserId(userUrl); }
public ChannelUrl(string channelUrl) { Raw = channelUrl; ChannelId = VidResolver.ExtractChannelId(channelUrl); }
private async Task ConnectInternalAsync(IInput 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, _logger); _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 liveChat = LiveChat.Parse(liveChatHtml); var ytCfgStr = Tools.ExtractYtCfg(liveChatHtml); //var ytCfg = new YtCfgOld(ytCfgStr); var ytCfg = liveChat.YtCfg; var ytInitialData = Tools.ExtractYtInitialData(liveChatHtml); if (!ytInitialData.CanChat) { SendSystemInfo("このライブストリームではチャットは無効です。", InfoType.Notice); return; } var loginInfo = Tools.CreateLoginInfo(liveChat.YtCfg.IsLoggedIn); //ログイン済みユーザの正常にコメントが取得できるようになったら以下のコードは不要 //---ここから--- if (loginInfo is LoggedIn) { var k = Tools.GetSapiSid(_cc); if (k == null) { //SIDが無い。ログイン済み判定なのにSIDが無い場合が散見されるが原因不明。強制的に未ログインにする。 var cookies = Tools.ExtractCookies(_cc); var cver = ytInitialData.Cver; var keys = string.Join(",", cookies.Select(c => c.Name)); _logger.LogException(new Exception(), "", $"cver={cver},keys={keys}"); _cc = new CookieContainer(); goto reload; } } //---ここまで--- SetLoggedInState(liveChat.YtCfg.IsLoggedIn); _postCommentCoodinator = new DataCreator(ytInitialData, liveChat.YtCfg.InnertubeApiKey, liveChat.YtCfg.DelegatedSessionId, _cc); foreach (var action in liveChat.YtInitialData.Actions) { OnMessageReceived(action, true); } //var initialActions = ytInitialData.GetActions(); //foreach (var action in initialActions) //{ // OnMessageReceived(action, true); //} var chatTask = _chatProvider.ReceiveAsync(vid, liveChat.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 (GetLiveChatException ex) { _isDisconnectedExpected = true; string html; try { html = await GetLiveChat(vid, _cc); } catch { html = ""; } _logger.LogException(ex, "", $"input={input.Raw},html={html}"); SendSystemInfo($"エラーが発生したため、これ以上コメントを取得できません{Environment.NewLine}{ex.Message}", InfoType.Notice); } catch (ContinuationNotExistsException) { break; } catch (ChatUnavailableException ex) { _isDisconnectedExpected = true; _logger.LogException(ex); 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; }
public CustomChannelUrl(string customChannelUrl) { Raw = customChannelUrl; CustomChannelId = VidResolver.ExtractCustomChannelId(customChannelUrl); }
public StudioUrl(string studioUrl) { Raw = studioUrl; Vid = VidResolver.ExtractVidFromStudioUrl(studioUrl); }
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; }