protected virtual async Task <string> GetMetadata(CookieContainer cc, string url, string data) { return(await _server.PostAsync(new HttpOptions { Url = url, Cc = cc, UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36", }, new StringContent(data, Encoding.UTF8, "application/x-www-form-urlencoded"))); }
protected virtual async Task <string> GetMetadata(CookieContainer cc, string url, string data) { return(await _server.PostAsync(new HttpOptions { Url = url, Cc = cc, UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0", }, new StringContent(data, Encoding.UTF8, "application/x-www-form-urlencoded"))); }
public async Task <bool> PostCommentAsync(string text) { var ret = false; if (CanPostComment) { try { var clientMessageId = PostCommentContext.ClientIdPrefix + _commentPostCount; var s = "{\"text_segments\":[{\"text\":\"" + text + "\"}]}"; var sej = PostCommentContext.Sej.Replace("\r\n", "").Replace("\t", "").Replace(" ", ""); var sessionToken = PostCommentContext.SessionToken; var data = new Dictionary <string, string> { { "client_message_id", clientMessageId }, { "rich_message", s }, { "sej", sej }, { "session_token", sessionToken }, }; var url = "https://www.youtube.com/service_ajax?name=sendLiveChatMessageEndpoint"; var res = await _server.PostAsync(url, data, _cc); Debug.WriteLine(res); var json = DynamicJson.Parse(res); //if (json.IsDefined("errors")) //{ // var k = string.Join("&", data.Select(kv => kv.Key + "=" + kv.Value)); // throw new PostingCommentFailedException("コメント投稿に失敗しました(" + json.errors[0] + ")",$"data={k}, res={res}"); //} if (json.IsDefined("code") && json.code == "SUCCESS") { if (json.IsDefined("data") && json.data.IsDefined("errorMessage")) { var k = string.Join("&", data.Select(kv => kv.Key + "=" + kv.Value)); var errorText = json.data.errorMessage.liveChatTextActionsErrorMessageRenderer.errorText.simpleText; throw new PostingCommentFailedException("コメント投稿に失敗しました(" + errorText + ")", $"data={k}, res={res}"); } else if (json.IsDefined("data") && json.data.IsDefined("actions")) { //多分成功 _commentPostCount++; ret = true; goto CommentPostsucceeded; } } var k0 = string.Join("&", data.Select(kv => kv.Key + "=" + kv.Value)); throw new UnknownResponseReceivedException($"data={k0}, res={res}"); } catch (UnknownResponseReceivedException ex) { _logger.LogException(ex); SendInfo(ex.Message, InfoType.Error); } catch (PostingCommentFailedException ex) { _logger.LogException(ex); SendInfo(ex.Message, InfoType.Error); } //catch (HttpException ex) //{ // //{\"errors\":[\"検証中にエラーが発生しました。\"]} statuscodeは分からないけど200以外 // _logger.LogException(ex, "", $"text={text},statuscode={ex.StatusCode}"); // SendInfo("コメント投稿時にエラーが発生", InfoType.Error); //} catch (HttpRequestException ex) { if (ex.InnerException is WebException webEx) { string response; using (var sr = new System.IO.StreamReader(webEx.Response.GetResponseStream())) { response = sr.ReadToEnd(); } var statuscode = (int)((HttpWebResponse)webEx.Response).StatusCode; //{\"errors\":[\"検証中にエラーが発生しました。\"]} statuscodeは分からないけど200以外 _logger.LogException(ex, "", $"text={text},statuscode={statuscode}"); SendInfo("コメント投稿時にエラーが発生", InfoType.Error); } else { _logger.LogException(ex, "", $"text={text}"); SendInfo("コメント投稿時にエラーが発生", InfoType.Error); } } catch (Exception ex) { _logger.LogException(ex); SendInfo("コメント投稿時にエラーが発生", InfoType.Error); } } CommentPostsucceeded: return(ret); }
protected virtual async Task <string> GetMetadata(CookieContainer cc, string url, string payload) { return(await _server.PostAsync(new HttpOptions { Url = url, Cc = cc }, new StringContent(payload, Encoding.UTF8, "application/json"))); }
public override async Task ReceiveAsync(string ytCfg, string vid, CookieContainer cc) { _cts = new CancellationTokenSource(); string innerTubeKey; try { var ytCfgJson = DynamicJson.Parse(ytCfg); innerTubeKey = ytCfgJson.INNERTUBE_API_KEY; }catch (Exception ex) { throw new FatalException("", ex); } var url = "https://www.youtube.com/youtubei/v1/updated_metadata?alt=json&key=" + innerTubeKey; var payload = "{\"context\":{\"client\":{\"hl\":\"ja\",\"gl\":\"JP\",\"clientName\":1,\"clientVersion\":\"1.20180224\",\"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 bytes = await wc.UploadDataTaskAsync(url, payloadBytes); //var res = Encoding.UTF8.GetString(bytes); var res = await _server.PostAsync(new HttpOptions { Url = url, Cc = cc }, new StringContent(payload, Encoding.UTF8, "application/json")); var json = DynamicJson.Parse(res); if (json.continuation.IsDefined("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 (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(string ytCfg, string vid, CookieContainer cc) { _cts = new CancellationTokenSource(); string token; try { var ytCfgJson = DynamicJson.Parse(ytCfg); token = ytCfgJson.XSRF_TOKEN; } catch (Exception ex) { //TODO:metadataが一切取れない致命的なエラーだということを表したい throw new FatalException("", ex); } //この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 _server.PostAsync(new HttpOptions { Url = url, Cc = cc, UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0", }, new StringContent(encodedContinuation + encodedData, Encoding.UTF8, "application/x-www-form-urlencoded")); var d = DynamicJson.Parse(res); string continuation; if (!d.IsDefined("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.IsDefined("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) { SendInfo($"メタデータの取得でエラーが発生 ({ex.Message})", InfoType.Notice); } catch (HttpException 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; }