public override async Task ReceiveAsync(YtCfg ytCfg, string vid, CookieContainer cc) { if (_cts != null) { throw new InvalidOperationException("receiving"); } _cts = new CancellationTokenSource(); try { await ReceiveInternalAsync(ytCfg, vid, cc); } finally { _cts = null; } }
protected virtual Task CreateMetadataReceivingTask(ref IMetadataProvider metaProvider, BrowserType browserType, string vid, string liveChatHtml) { Task metaTask = null; YtCfg ytCfg = null; try { var ytCfgStr = Tools.ExtractYtcfg(liveChatHtml); ytCfg = new YtCfg(ytCfgStr); } catch (ParseException ex) { _logger.LogException(ex, "live_chatからのytcfgの抜き出しに失敗", liveChatHtml); } if (ytCfg != null) { //"service_ajax?name=updatedMetadataEndpoint"はIEには対応していないらしく、400が返って来てしまう。 //そこで、IEの場合のみ旧版の"youtubei"を使うようにした。 if (browserType == BrowserType.IE) { metaProvider = new MetaDataYoutubeiProvider(_server, _logger); } else { metaProvider = new MetadataProvider(_server, _logger); } metaProvider.MetadataReceived += (s, e) => { MetadataUpdated?.Invoke(this, e); }; metaProvider.InfoReceived += (s, e) => { SendInfo(e.Comment, e.Type); }; metaTask = metaProvider.ReceiveAsync(ytCfg: ytCfg, vid: vid, cc: _cc); } return(metaTask); }
public async Task ReceiveInternalAsync(YtCfg ytCfg, string vid, CookieContainer cc) { var url = "https://www.youtube.com/youtubei/v1/updated_metadata?alt=json&key=" + ytCfg.InnertubeApiKey; var payload = "{\"context\":{\"client\":{\"hl\":\"ja\",\"gl\":\"JP\",\"clientName\":1,\"clientVersion\":\"2.20210114.08.00\",\"screenDensityFloat\":\"1.25\"}},\"videoId\":\"" + vid + "\"}"; //var payloadBytes = Encoding.UTF8.GetBytes(payload); //var wc = new MyWebClient(cc); while (!_cts.IsCancellationRequested) { int timeoutMs = 0; //wc.Headers["origin"] = "https://www.youtube.com"; //wc.Headers[HttpRequestHeader.ContentType] = "application/json"; //wc.Headers[HttpRequestHeader.Accept] = "*/*"; try { var res = await GetMetadata(cc, url, payload); dynamic json = JsonConvert.DeserializeObject(res); if (!json.ContainsKey("continuation")) { SendInfo($"updated_metadataにcontinuationが無い", InfoType.Debug); throw new ContinuationNotExistsException(res); } if (json.continuation.ContainsKey("invalidationContinuationData")) { throw new ParseException(res); } else { timeoutMs = (int)json.continuation.timedContinuationData.timeoutMs; } var metadata = ActionsToMetadata(json.actions); metaReceived?.Invoke(this, metadata); } catch (TaskCanceledException) { break; } catch (WebException ex) when(ex.Status == WebExceptionStatus.ProtocolError) { var httpRes = (HttpWebResponse)ex.Response; var code = httpRes.StatusCode; _logger.LogException(ex, "", $"url={url}"); SendInfo($"メタデータの取得でエラーが発生 ({code})", InfoType.Notice); } catch (WebException ex) { SendInfo($"メタデータの取得でエラーが発生 ({ex.Status})", InfoType.Notice); Debug.WriteLine(ex.Message); } catch (ParseException ex) { _logger.LogException(ex); } catch (ContinuationNotExistsException ex) { _logger.LogException(ex); break; } catch (Exception ex) { var parseEx = new ParseException("", ex); _logger.LogException(parseEx); } timeoutMs = timeoutMs == 0 ? 1000 : timeoutMs; try { await Task.Delay(timeoutMs, _cts.Token); } catch (TaskCanceledException) { break; } } _cts = null; }
public override async Task ReceiveAsync(YtCfg ytCfg, string vid, CookieContainer cc) { _cts = new CancellationTokenSource(); var token = ytCfg.XsrfToken; //このAPIを呼び出すとき、Cookieに"YSC"と"VISITOR_INFO1_LIVE"が必須。 //2つとも配信ページか $"https://www.youtube.com/live_chat?v={vid}&is_popout=1"にアクセスした時にCookieをセットしなければもらえる。 //コメビュではtokenとかを取得するために"/live_chat"に必ずアクセスする必要があるため、未ログイン時にはそれで取得した値を使えば良い。 var url = "https://www.youtube.com/service_ajax?name=updatedMetadataEndpoint"; //この値接続毎に固定っぽい。continuationも変わらない気がする。 var sej = "{\"clickTrackingParams\":\"CIQBEMyrARgAIhMIrdnBx6fm2wIVQ5VYCh14dQoMKPgd\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/service_ajax\",\"sendPost\":true}},\"updatedMetadataEndpoint\":{\"videoId\":\"" + vid + "\"}}"; var data = new Dictionary <string, string> { { "sej", sej }, //{"csn", "E90sW4DVBdaP4ALlho8Q" }, { "session_token", token }, }; var encodedData = string.Join("&", data.Select(kv => kv.Key + "=" + HttpUtility.UrlEncode(kv.Value))); //HttpUtility.UrlEncode()の戻り値は小文字だけど問題ない。 string encodedContinuation = ""; bool isFatalError = false; //続行不能なエラーが出た場合にtrueにする。 while (!_cts.IsCancellationRequested) { int timeoutMs = 0; try { var res = await GetMetadata(cc, url, encodedContinuation + encodedData); dynamic d = JsonConvert.DeserializeObject(res); string continuation; if (!d.ContainsKey("code") || d.code != "SUCCESS") { if (res == "{\"errors\":[\"Invalid Request\"]}") { isFatalError = true; } var v = string.Join("&", data.Select(kv => kv.Key + "=" + kv.Value)); //{"code":"ERROR","error":"不明なエラーです。"} //{"code":"ERROR","error":"この機能は現在利用できません。しばらくしてからもう一度お試しください。"} throw new ParseException($"res={res},ytCfg={ytCfg},encoded={v}"); } if (d.data.continuation.ContainsKey("invalidationContinuationData")) { throw new ParseException(res); } else { continuation = d.data.continuation.timedContinuationData.continuation; //if (!data.ContainsKey("continuation")) //{ // data.Add("continuation", continuation); //} //else //{ // data["continuation"] = continuation; //} encodedContinuation = "continuation=" + HttpUtility.UrlEncode(continuation) + "&"; timeoutMs = (int)d.data.continuation.timedContinuationData.timeoutMs; } try { var metadata = ActionsToMetadata(d.data.actions); metaReceived?.Invoke(this, metadata); } catch (Exception ex) { throw new ParseException(res, ex); } } catch (HttpRequestException ex) { _logger.LogException(ex); SendInfo($"メタデータの取得でエラーが発生 ({ex.Message})", InfoType.Notice); break; } catch (HttpException ex) { _logger.LogException(ex); SendInfo($"メタデータの取得でエラーが発生 ({ex.Message})", InfoType.Notice); break; } catch (ParseException ex) { _logger.LogException(ex); if (isFatalError) { SendInfo("メタデータの取得がキャンセルされました(Invalid Request)", InfoType.Notice); break; } } catch (TaskCanceledException ex) { SendInfo("メタデータの取得がキャンセルされました(" + ex.Message + ")", InfoType.Notice); break; } catch (Exception ex) { var parseEx = new ParseException("", ex); _logger.LogException(parseEx); } timeoutMs = timeoutMs == 0 ? 1000 : timeoutMs; try { await Task.Delay(timeoutMs, _cts.Token); } catch (TaskCanceledException) { break; } } _cts = null; return; }
public abstract Task ReceiveAsync(YtCfg ytCfg, string vid, CookieContainer cc);