/// <summary> /// XML文字列を取得する /// </summary> /// <param name="info"></param> /// <returns></returns> public string GetXmlContent(IDmcInfo info) { var data = new Xml::GetThumbInfoApiResponse(); data.Thumb.VideoId = info.Id; data.Thumb.Title = info.Title; data.Thumb.Description = info.Description; data.Thumb.ThumbnailUrl = info.ThumbInfo.GetSpecifiedThumbnail(ThumbSize.Large); data.Thumb.FirstRetrieve = new DateTimeOffset(info.UploadedOn, TimeSpan.FromHours(9)).ToString("yyyy-MM-ddTHH:mm:sszzz"); data.Thumb.Length = $"{Math.Floor((double)info.Duration / 60).ToString().PadLeft(2, '0')}:{info.Duration % 60}"; data.Thumb.ViewCounter = info.ViewCount; data.Thumb.CommentNum = info.CommentCount; data.Thumb.LikeCounter = info.LikeCount; data.Thumb.MylistCounter = info.MylistCount; data.Thumb.WatchUrl = Const::NetConstant.NiconicoWatchUrl + info.Id; data.Thumb.Tags.Tag.AddRange(info.Tags.Select(t => new Xml::Tag() { Text = t })); data.Thumb.UserId = info.ChannelName.IsNullOrEmpty() ? info.OwnerID.ToString() : null; data.Thumb.UserNickname = info.ChannelName.IsNullOrEmpty() ? info.Owner : null; data.Thumb.ChName = info.ChannelName.IsNullOrEmpty() ? null : info.ChannelName; data.Thumb.ChId = info.ChannelName.IsNullOrEmpty() ? null : info.ChannelID; return(Xmlparser.Serialize(data)); }
/// <summary> /// 動画情報本文を取得する /// </summary> /// <param name="info"></param> /// <returns></returns> public string GetContent(IDmcInfo info) { var ownerNameTitle = info.ChannelName.IsNullOrEmpty() ? "[owner_nickname]" : "[channel_name]"; var ownerIDTitle = info.ChannelName.IsNullOrEmpty() ? "[owner_id]" : "[channel_id]"; var ownerName = info.ChannelName.IsNullOrEmpty() ? info.Owner : info.ChannelName; var ownerID = info.ChannelName.IsNullOrEmpty() ? info.OwnerID.ToString() : info.ChannelID; var list = new List <string>() { "[name]", info.Id + Environment.NewLine, "[post]", info.UploadedOn.ToString("yyyy/MM/dd HH:mm:ss") + Environment.NewLine, "[title]", info.Title + Environment.NewLine, "[comment]", info.Description + Environment.NewLine, "[tags]", String.Join(Environment.NewLine, info.Tags) + Environment.NewLine, "[view_counter]", info.ViewCount.ToString() + Environment.NewLine, "[comment_num]", info.CommentCount.ToString() + Environment.NewLine, "[mylist_counter]", info.MylistCount.ToString() + Environment.NewLine, "[length]", $"{Math.Floor((double)(info.Duration/60))}分{info.Duration%60}秒" + Environment.NewLine, ownerIDTitle, ownerID + Environment.NewLine, ownerNameTitle, ownerName }; return(string.Join(Environment.NewLine, list)); }
/// <summary> /// コメントを取得する /// </summary> /// <param name="dmcInfo"></param> /// <param name="settings"></param> /// <param name="when"></param> /// <returns></returns> private async Task <List <Response::Comment> > GetCommentsAsync(IDmcInfo dmcInfo, IDownloadSettings settings, long?when = null) { var option = new CommentOptions() { NoEasyComment = !settings.DownloadEasy, OwnerComment = settings.DownloadOwner, When = when ?? 0 }; var request = await this.requestBuilder.GetRequestDataAsync(dmcInfo, option); string server = dmcInfo.CommentThreads.FirstOrDefault()?.Server ?? @"https://nmsg.nicovideo.jp/api.json"; if (!server.EndsWith("/api.json")) { server += "/api.json"; } var res = await this.http.PostAsync(new Uri(server), new StringContent(request)); if (!res.IsSuccessStatusCode) { throw new HttpRequestException($"コメントの取得に失敗しました。(status_code:{(int)res.StatusCode}, reason_phrase:{res.ReasonPhrase})"); } string content = await res.Content.ReadAsStringAsync(); var data = JsonParser.DeSerialize <List <Response::Comment> >(content); return(data); }
public async Task <IAttemptResult <string> > BuildRequestAsync(IDmcInfo dmcInfo, ICommentFetchOption option, string key) { IAttemptResult <List <Request::RequestRoot> > result; try { result = await this.BuildRequestAsyncInternal(dmcInfo, option, key); } catch (Exception ex) { this._logger.Error("リクエストの構築中にエラーが発生しました。", ex); return(AttemptResult <string> .Fail($"リクエストの構築中にエラーが発生しました。(詳細:{ex.Message})")); } if (!result.IsSucceeded || result.Data is null) { return(AttemptResult <string> .Fail(result.Message)); } string json; try { json = JsonParser.Serialize(result.Data); } catch (Exception ex) { this._logger.Error("リクエストのシリアライズ中にエラーが発生しました。", ex); return(AttemptResult <string> .Fail($"リクエストのシリアライズ中にエラーが発生しました。(詳細:{ex.Message})")); } return(AttemptResult <string> .Succeeded(json)); }
public string GetFilePath(string format, IDmcInfo dmcInfo, string extension, string folderName, bool replaceStricted, bool overWrite, string?suffix = null) { var filename = this.niconicoUtils.GetFileName(format, dmcInfo, extension, replaceStricted, suffix); var filePath = Path.Combine(folderName, filename); if (!overWrite) { filePath = IOUtils.CheclFileExistsAndReturnNewFilename(filePath); } return(filePath); }
private (long, long) GetDefaultPosyTarget(IDmcInfo dmcInfo) { foreach (var thread in dmcInfo.CommentThreads) { if (thread.IsDefaultPostTarget) { return(thread.ID, thread.Fork); } } return(-1, -1); }
/// <summary> /// JSON文字列を取得する /// </summary> /// <param name="info"></param> /// <returns></returns> public string GetJsonContent(IDmcInfo info) { var data = new VideoInfoJson(info); var options = new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), WriteIndented = true }; return(JsonParser.Serialize(data, options)); }
/// <summary> /// セッション情報からファイル名を取得する /// </summary> /// <param name="format"></param> /// <param name="session"></param> /// <returns></returns> public string GetFileName(string format, IDmcInfo dmcInfo, string extension, bool replaceStricted, string?suffix = null) { var info = new VideoInfoForPath() { Title = dmcInfo.Title, OwnerName = dmcInfo.Owner, NiconicoID = dmcInfo.Id, UploadedOn = dmcInfo.UploadedOn, DownloadStartedOn = dmcInfo.DownloadStartedOn, OwnerID = dmcInfo.OwnerID.ToString(), Duration = dmcInfo.Duration, }; return(this.GetFilenameInternal(format, info, extension, replaceStricted, suffix)); }
public VideoInfoJson(IDmcInfo info) { this.Title = info.Title; this.Post = info.UploadedOn.ToString("yyyy/MM/ddTHH:mm:ss"); this.ID = info.Id; this.Description = info.Description; this.Tags = new List <string>(); this.Tags.AddRange(info.Tags); this.ViewCount = info.ViewCount; this.MylistCount = info.MylistCount; this.CommentCount = info.CommentCount; this.LikeCount = info.LikeCount; this.Owner = info.ChannelName.IsNullOrEmpty() ? info.Owner : null; this.OwnerID = info.ChannelName.IsNullOrEmpty() ? info.OwnerID.ToString() : null; this.ChannelName = info.ChannelName.IsNullOrEmpty() ? null : info.ChannelName; this.ChannelID = info.ChannelName.IsNullOrEmpty() ? null : info.ChannelID; }
/// <summary> /// コメントをダウンロードする /// </summary> /// <param name="dmcInfo"></param> /// <param name="settings"></param> /// <returns></returns> public async Task <ICommentCollection> DownloadCommentAsync(IDmcInfo dmcInfo, IDownloadSettings settings, Action <string> onMessage, IDownloadContext context, CancellationToken token) { var(dThread, dFork) = this.GetDefaultPosyTarget(dmcInfo); if (dThread == -1 || dFork == -1) { throw new InvalidOperationException("DefaultPostTargetが見つかりません。"); } var comments = CommentCollection.GetInstance(settings.CommentOffset, dThread, dFork, settings.EnableUnsafeCommentHandle, settings.EnableExperimentalCommentSafetySystem); Response::Chat?first = null; int index = 0; long lastNo = 0; do { int count = comments.Count; if (index > 0) { if (settings.CommentFetchWaitSpan > 0) { onMessage($"待機中...({settings.CommentFetchWaitSpan}ms)"); try { await Task.Delay(settings.CommentFetchWaitSpan, token); } catch { } } onMessage($"過去ログをダウンロード中({index + 1}件目・{count}コメ)"); } long?when = count == 0 ? 0 : first?.Date - 1; List <Response::Comment> retlieved = await this.GetCommentsAsync(dmcInfo, settings, when); comments.Add(retlieved); if (index > 0) { this.logger.Log($"過去ログをダウンロードしました。({index}個目, {retlieved.Count}コメント, {context.GetLogContent()})"); } else { this.logger.Log($"現行スレッドをダウンロードしました。({retlieved.Count}コメント, {context.GetLogContent()})"); } first = comments.GetFirstComment(false); if ((first?.No ?? 0) == lastNo) { break; } else { lastNo = first?.No ?? 0; } if (settings.MaxCommentsCount > 0 && comments.Count > settings.MaxCommentsCount) { var rmCount = comments.Count - settings.MaxCommentsCount; comments.RemoveFor(rmCount); break; } if (!settings.DownloadLog) { break; } else if (index == 0) { onMessage("過去ログのダウンロードを開始します。"); } ++index; } while (first?.No > 1 && !token.IsCancellationRequested); if (token.IsCancellationRequested) { throw new TaskCanceledException("コメントのダウンロードがキャンセルされました。"); } onMessage($"コメントの正規化処理を開始します。"); comments.Distinct(); onMessage($"コメントの正規化処理が完了しました。"); this.logger.Log($"コメントのダウンロードが完了しました。({comments.Count}コメント, {context.GetLogContent()})"); return(comments); }
public async Task <IAttemptResult> DownloadCommentAsync(IDmcInfo dmcInfo, IDownloadSettings settings, IDownloadContext context, CancellationToken token) { //コメント追記処理 var origination = DateTime.Now; var originationSpecidied = false; var oldComments = new List <Core::IComment>(); if (settings.AppendingToLocalComment && this._commentLoader.CommentExists(settings.FolderPath, settings.NiconicoId)) { IAttemptResult <Local::LocalCommentInfo> localResult = this._commentLoader.LoadComment(settings.FolderPath, settings.NiconicoId); if (localResult.IsSucceeded && localResult.Data is not null) { origination = localResult.Data.LastUpdatedTime; originationSpecidied = true; oldComments.AddRange(localResult.Data.Comments); } } //キャンセル処理 token.ThrowIfCancellationRequested(); //コメント取得処理 var dlOption = new Fetch::CommentClientOption(originationSpecidied, origination); IAttemptResult <(Core::ICommentCollection, Core::IThreadInfo)> dlResult = await this._commentClient.DownloadCommentAsync(dmcInfo, settings, dlOption, context, token); if (!dlResult.IsSucceeded) { return(AttemptResult.Fail(dlResult.Message)); } //キャンセル処理 token.ThrowIfCancellationRequested(); //コメント統合処理 var(collection, threadInfo) = dlResult.Data; if (oldComments.Count > 0) { foreach (var comment in oldComments) { collection.Add(comment); } } //コメント書き込み処理 string path = this._path.GetFilePath(settings.FileNameFormat, dmcInfo, ".xml", settings.FolderPath, settings.IsReplaceStrictedEnable, settings.Overwrite); var writerOption = new Local::CommentWriterOption(path, settings.OmittingXmlDeclaration, dmcInfo.Id); IAttemptResult writeResult = this._commentWriter.WriteComment(collection.Comments, threadInfo, writerOption); if (!writeResult.IsSucceeded) { return(writeResult); } //キャンセル処理 token.ThrowIfCancellationRequested(); //コメ数記録 context.CommentCount = collection.Count; return(AttemptResult.Succeeded()); }
public async Task <IAttemptResult <List <Request::RequestRoot> > > BuildRequestAsyncInternalForTest(IDmcInfo dmcInfo, ICommentFetchOption option, string key) { return(await this.BuildRequestAsyncInternal(dmcInfo, option, key)); }
/// <summary> /// ThreadLeaves要求を取得 /// </summary> /// <param name="thread"></param> /// <param name="dmcInfo"></param> /// <param name="option"></param> /// <param name="wayBackKey"></param> /// <returns></returns> private IAttemptResult <Request::ThreadLeaves> GetThreadLeaves(IThread thread, IDmcInfo dmcInfo, ICommentFetchOption option, string?wayBackKey) { var leaves = new Request::ThreadLeaves() { ThreadNo = thread.ID.ToString(), UserID = dmcInfo.UserId, Fork = thread.Fork, Language = 0, Scores = 1, Nicoru = 3, }; //force184? if (thread.Is184Forced) { leaves.Force184 = "1"; } //公式動画を判別 if (thread.IsThreadkeyRequired) { leaves.ThreadKey = thread.Threadkey; } else { leaves.UserKey = dmcInfo.Userkey; } //過去ログ if (option.DownloadLog) { leaves.When = option.When; leaves.WayBackKey = wayBackKey; } int flooredDuration; try { flooredDuration = (int)Math.Floor(dmcInfo.Duration / 60f); } catch (Exception ex) { this._logger.Error($"動画再生時間の計算に失敗しました。", ex); return(AttemptResult <Request::ThreadLeaves> .Fail($"動画再生時間の計算に失敗しました。(詳細:{ex.Message})")); } //leaves.Content = $"0-{flooredDuration}:100,1000:nicoru:100"; leaves.Content = "1000"; return(AttemptResult <Request::ThreadLeaves> .Succeeded(leaves)); }
/// <summary> /// Thread要求を構築する /// </summary> /// <param name="threadInfo"></param> /// <param name="dmcInfo"></param> /// <param name="option"></param> /// <returns></returns> private async Task <IAttemptResult <Request::Thread> > GetThreadAsync(IThread threadInfo, IDmcInfo dmcInfo, ICommentFetchOption option) { var thread = new Request::Thread() { ThreadNo = threadInfo.ID.ToString(), UserID = dmcInfo.UserId, Fork = threadInfo.Fork, Language = 0, WithGlobal = 1, Scores = 1, Nicoru = 3, Force184 = threadInfo.Is184Forced ? "1" : null, }; //投コメを判定 if (threadInfo.IsOwnerThread) { thread.ResFrom = -1000; thread.Version = "20061206"; } else { thread.Version = "20090904"; } //Force184? if (threadInfo.Is184Forced) { thread.Force184 = "1"; } //公式動画を判別 if (threadInfo.IsThreadkeyRequired) { thread.ThreadKey = threadInfo.Threadkey; } else { thread.UserKey = dmcInfo.Userkey; } //過去ログ if (option.DownloadLog) { IAttemptResult <WayBackKey> wResult = await this._officialCommentHandler.GetWayBackKeyAsync(threadInfo.ID.ToString()); if (!wResult.IsSucceeded || wResult.Data is null) { return(AttemptResult <Request::Thread> .Fail(wResult.Message)); } thread.When = option.When; thread.WayBackKey = wResult.Data.Key; } return(AttemptResult <Request::Thread> .Succeeded(thread)); }
/// <summary> /// 内部的にリクエストを構築 /// </summary> /// <param name="dmcInfo"></param> /// <param name="option"></param> /// <param name="key"></param> /// <returns></returns> private async Task <IAttemptResult <List <Request::RequestRoot> > > BuildRequestAsyncInternal(IDmcInfo dmcInfo, ICommentFetchOption option, string key) { if (!this.CheckKey(key)) { return(AttemptResult <List <Request::RequestRoot> > .Fail("Keyが違います。")); } var request = new List <Request::RequestRoot>(); //リクエスト開始Ping要求を挿入 request.Add(new Request::RequestRoot() { Ping = this.GetPingContent(PingType.StartRequest, this.requestIndex) }); //ループ foreach (IThread thread in dmcInfo.CommentThreads) { //かんたんコメント判定 if (!option.DownloadEasy && thread.IsEasyCommentPostTarget) { continue; } //投コメ判定 if (!option.DownloadOwner && thread.IsOwnerThread) { continue; } //Thread要求を挿入 IAttemptResult <Request::Thread> tResult = await this.GetThreadAsync(thread, dmcInfo, option); if (!tResult.IsSucceeded || tResult.Data is null) { return(AttemptResult <List <Request::RequestRoot> > .Fail(tResult.Message)); } request.Add(new Request::RequestRoot() { Ping = this.GetPingContent(PingType.StartCommand, this.commandIndex) }); request.Add(new Request::RequestRoot() { Thread = tResult.Data }); request.Add(new Request::RequestRoot() { Ping = this.GetPingContent(PingType.StartCommand, this.commandIndex) }); this.commandIndex++; //ThreadLeaves要求を挿入 if (thread.IsLeafRequired) { IAttemptResult <Request::ThreadLeaves> lResult = this.GetThreadLeaves(thread, dmcInfo, option, tResult.Data.WayBackKey); if (!lResult.IsSucceeded || lResult.Data is null) { return(AttemptResult <List <Request::RequestRoot> > .Fail(lResult.Message)); } request.Add(new Request::RequestRoot() { Ping = this.GetPingContent(PingType.StartCommand, this.commandIndex) }); request.Add(new Request::RequestRoot() { ThreadLeaves = lResult.Data }); request.Add(new Request::RequestRoot() { Ping = this.GetPingContent(PingType.EndCommand, this.commandIndex) }); this.commandIndex++; } } //リクエスト終了Ping要求を挿入 request.Add(new Request::RequestRoot() { Ping = this.GetPingContent(PingType.EndRequest, this.requestIndex) }); this.requestIndex++; return(AttemptResult <List <Request::RequestRoot> > .Succeeded(request)); }