public static async Task <GqlVideoResponse> GetGqlVideos(string channelName, string cursor = "", int limit = 50) { using (WebClient client = new WebClient()) { client.Encoding = Encoding.UTF8; client.Headers.Add("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko"); client.Headers[HttpRequestHeader.ContentType] = "application/json"; string response = await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "{\"query\":\"query{user(login:\\\"" + channelName + "\\\"){videos(first: " + limit + "" + (cursor == "" ? "" : ",after:\\\"" + cursor + "\\\"") + ") { edges { node { title, id, lengthSeconds, previewThumbnailURL(height: 180, width: 320), createdAt, viewCount }, cursor }, pageInfo { hasNextPage, hasPreviousPage }, totalCount }}}\",\"variables\":{}}"); GqlVideoResponse result = JsonConvert.DeserializeObject <GqlVideoResponse>(response); return(result); } }
public async Task DownloadAsync(IProgress <ProgressReport> progress, CancellationToken cancellationToken) { using (WebClient client = new WebClient()) { client.Encoding = Encoding.UTF8; client.Headers.Add("Accept", "application/vnd.twitchtv.v5+json; charset=UTF-8"); client.Headers.Add("Client-Id", "kimne78kx3ncx6brgo4mv6wki5h1ko"); DownloadType downloadType = downloadOptions.Id.All(x => Char.IsDigit(x)) ? DownloadType.Video : DownloadType.Clip; string videoId = ""; List <Comment> comments = new List <Comment>(); ChatRoot chatRoot = new ChatRoot() { streamer = new Streamer(), video = new VideoTime(), comments = comments }; double videoStart = 0.0; double videoEnd = 0.0; double videoDuration = 0.0; int errorCount = 0; if (downloadType == DownloadType.Video) { videoId = downloadOptions.Id; GqlVideoResponse taskInfo = await TwitchHelper.GetVideoInfo(Int32.Parse(videoId)); chatRoot.streamer.name = taskInfo.data.video.owner.displayName; chatRoot.streamer.id = int.Parse(taskInfo.data.video.owner.id); videoStart = downloadOptions.CropBeginning ? downloadOptions.CropBeginningTime : 0.0; videoEnd = downloadOptions.CropEnding ? downloadOptions.CropEndingTime : taskInfo.data.video.lengthSeconds; } else { GqlClipResponse taskInfo = await TwitchHelper.GetClipInfo(downloadOptions.Id); if (taskInfo.data.clip.video == null || taskInfo.data.clip.videoOffsetSeconds == null) { throw new Exception("Invalid VOD for clip, deleted/expired VOD possibly?"); } videoId = taskInfo.data.clip.video.id; downloadOptions.CropBeginning = true; downloadOptions.CropBeginningTime = (int)taskInfo.data.clip.videoOffsetSeconds; downloadOptions.CropEnding = true; downloadOptions.CropEndingTime = downloadOptions.CropBeginningTime + taskInfo.data.clip.durationSeconds; chatRoot.streamer.name = taskInfo.data.clip.broadcaster.displayName; chatRoot.streamer.id = int.Parse(taskInfo.data.clip.broadcaster.id); videoStart = (int)taskInfo.data.clip.videoOffsetSeconds; videoEnd = (int)taskInfo.data.clip.videoOffsetSeconds + taskInfo.data.clip.durationSeconds; } chatRoot.video.start = videoStart; chatRoot.video.end = videoEnd; videoDuration = videoEnd - videoStart; double latestMessage = videoStart - 1; bool isFirst = true; string cursor = ""; while (latestMessage < videoEnd) { string response; try { if (isFirst) { response = await client.DownloadStringTaskAsync(String.Format("https://api.twitch.tv/v5/videos/{0}/comments?content_offset_seconds={1}", videoId, videoStart)); } else { response = await client.DownloadStringTaskAsync(String.Format("https://api.twitch.tv/v5/videos/{0}/comments?cursor={1}", videoId, cursor)); } errorCount = 0; } catch (WebException ex) { await Task.Delay(1000 *errorCount); errorCount++; if (errorCount >= 10) { throw ex; } continue; } CommentResponse commentResponse = JsonConvert.DeserializeObject <CommentResponse>(response); foreach (var comment in commentResponse.comments) { if (latestMessage < videoEnd && comment.content_offset_seconds > videoStart) { comments.Add(comment); } latestMessage = comment.content_offset_seconds; } if (commentResponse._next == null) { break; } else { cursor = commentResponse._next; } int percent = (int)Math.Floor((latestMessage - videoStart) / videoDuration * 100); progress.Report(new ProgressReport() { reportType = ReportType.Percent, data = percent }); progress.Report(new ProgressReport() { reportType = ReportType.MessageInfo, data = $"Downloading {percent}%" }); cancellationToken.ThrowIfCancellationRequested(); if (isFirst) { isFirst = false; } } if (downloadOptions.EmbedEmotes && downloadOptions.IsJson) { progress.Report(new ProgressReport() { reportType = ReportType.Message, data = "Downloading + Embedding Emotes" }); chatRoot.emotes = new Emotes(); List <FirstPartyEmoteData> firstParty = new List <FirstPartyEmoteData>(); List <ThirdPartyEmoteData> thirdParty = new List <ThirdPartyEmoteData>(); string cacheFolder = Path.Combine(Path.GetTempPath(), "TwitchDownloader", "cache"); List <TwitchEmote> thirdPartyEmotes = new List <TwitchEmote>(); List <TwitchEmote> firstPartyEmotes = new List <TwitchEmote>(); await Task.Run(() => { thirdPartyEmotes = TwitchHelper.GetThirdPartyEmotes(chatRoot.streamer.id, cacheFolder); firstPartyEmotes = TwitchHelper.GetEmotes(comments, cacheFolder).ToList(); }); foreach (TwitchEmote emote in thirdPartyEmotes) { ThirdPartyEmoteData newEmote = new ThirdPartyEmoteData(); newEmote.id = emote.id; newEmote.imageScale = emote.imageScale; newEmote.data = emote.imageData; newEmote.name = emote.name; thirdParty.Add(newEmote); } foreach (TwitchEmote emote in firstPartyEmotes) { FirstPartyEmoteData newEmote = new FirstPartyEmoteData(); newEmote.id = emote.id; newEmote.imageScale = 1; newEmote.data = emote.imageData; firstParty.Add(newEmote); } chatRoot.emotes.thirdParty = thirdParty; chatRoot.emotes.firstParty = firstParty; } if (downloadOptions.IsJson) { using (TextWriter writer = File.CreateText(downloadOptions.Filename)) { var serializer = new JsonSerializer(); serializer.Serialize(writer, chatRoot); } } else { using (StreamWriter sw = new StreamWriter(downloadOptions.Filename)) { foreach (var comment in chatRoot.comments) { string username = comment.commenter.display_name; string message = comment.message.body; if (downloadOptions.TimeFormat == TimestampFormat.Utc) { string timestamp = comment.created_at.ToString("u").Replace("Z", " UTC"); sw.WriteLine(String.Format("[{0}] {1}: {2}", timestamp, username, message)); } else if (downloadOptions.TimeFormat == TimestampFormat.Relative) { TimeSpan time = new TimeSpan(0, 0, (int)comment.content_offset_seconds); string timestamp = time.ToString(@"h\:mm\:ss"); sw.WriteLine(String.Format("[{0}] {1}: {2}", timestamp, username, message)); } else if (downloadOptions.TimeFormat == TimestampFormat.None) { sw.WriteLine(String.Format("{0}: {1}", username, message)); } } sw.Flush(); sw.Close(); } } chatRoot = null; GC.Collect(); } }
private async void btnQueue_Click(object sender, RoutedEventArgs e) { btnQueue.IsEnabled = false; List <string> idList = new List <string>(); List <string> urlList = new List <string>(textList.Text.Split('\n').Where(x => x.Trim() != "")); List <string> invalidList = new List <string>(); List <string> errorList = new List <string>(); List <TaskData> dataList = new List <TaskData>(); Dictionary <string, string> idDict = new Dictionary <string, string>(); foreach (var url in urlList) { string id = PageChatDownload.ValidateUrl(url); if (id == "") { invalidList.Add(url); } else { idList.Add(id); idDict[id] = url; } } if (invalidList.Count > 0) { MessageBox.Show("Please double check the VOD/Clip link", "Unable to parse inputs\n" + String.Join("\n", invalidList.ToArray()), MessageBoxButton.OK, MessageBoxImage.Error); return; } Dictionary <int, string> taskDict = new Dictionary <int, string>(); List <Task <GqlVideoResponse> > taskVideoList = new List <Task <GqlVideoResponse> >(); List <Task <GqlClipResponse> > taskClipList = new List <Task <GqlClipResponse> >(); foreach (var id in idList) { if (id.All(Char.IsDigit)) { Task <GqlVideoResponse> task = TwitchHelper.GetVideoInfo(int.Parse(id)); taskVideoList.Add(task); taskDict[task.Id] = id; } else { Task <GqlClipResponse> task = TwitchHelper.GetClipInfo(id); taskClipList.Add(task); taskDict[task.Id] = id; } } try { await Task.WhenAll(taskVideoList.ToArray()); } catch { } try { await Task.WhenAll(taskClipList.ToArray()); } catch { } for (int i = 0; i < taskVideoList.Count; i++) { if (taskVideoList[i].IsCompleted) { string id = taskDict[taskVideoList[i].Id]; if (!taskVideoList[i].IsFaulted) { GqlVideoResponse data = taskVideoList[i].Result; TaskData newData = new TaskData(); newData.Id = id; try { string thumbUrl = data.data.video.thumbnailURLs.FirstOrDefault(); var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.UriSource = new Uri(thumbUrl); bitmapImage.EndInit(); newData.Thumbnail = bitmapImage; } catch { } newData.Title = data.data.video.title; newData.Streamer = data.data.video.owner.displayName; newData.Time = data.data.video.createdAt.ToLocalTime(); dataList.Add(newData); } else { errorList.Add(idDict[id]); } } } for (int i = 0; i < taskClipList.Count; i++) { if (taskClipList[i].IsCompleted) { string id = taskDict[taskClipList[i].Id]; if (!taskClipList[i].IsFaulted) { GqlClipResponse data = taskClipList[i].Result; TaskData newData = new TaskData(); newData.Id = id; try { string thumbUrl = data.data.clip.thumbnailURL; var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.UriSource = new Uri(thumbUrl); bitmapImage.EndInit(); newData.Thumbnail = bitmapImage; } catch { } newData.Title = data.data.clip.title; newData.Streamer = data.data.clip.broadcaster.displayName; newData.Time = data.data.clip.createdAt.ToLocalTime(); dataList.Add(newData); } else { errorList.Add(idDict[id]); } } } if (errorList.Count > 0) { MessageBox.Show("Error getting VOD/Clip information", "Unable to get info for these VODs/Clips\n" + String.Join("\n", errorList.ToArray()), MessageBoxButton.OK, MessageBoxImage.Error); return; } WindowQueueOptions queue = new WindowQueueOptions(dataList); bool?queued = queue.ShowDialog(); if (queued != null && (bool)queued) { this.Close(); } btnQueue.IsEnabled = true; }
private async Task UpdateList() { if (type == DownloadType.Video) { string currentCursor = ""; if (cursorList.Count > 0 && cursorIndex >= 0) { currentCursor = cursorList[cursorIndex]; } GqlVideoResponse res = await TwitchHelper.GetGqlVideos(currnetChannel, currentCursor, 100); videoList.Clear(); if (res.data.user != null) { foreach (var video in res.data.user.videos.edges) { TaskData data = new TaskData(); data.Title = video.node.title; data.Length = video.node.lengthSeconds; data.Id = video.node.id; data.Time = video.node.createdAt; data.Views = video.node.viewCount; try { var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.UriSource = new Uri(video.node.previewThumbnailURL); bitmapImage.EndInit(); data.Thumbnail = bitmapImage; } catch { } videoList.Add(data); } if (res.data.user.videos.pageInfo.hasNextPage) { btnNext.IsEnabled = true; } else { btnNext.IsEnabled = false; } if (res.data.user.videos.pageInfo.hasPreviousPage) { btnPrev.IsEnabled = true; } else { btnPrev.IsEnabled = false; } if (res.data.user.videos.pageInfo.hasNextPage) { string newCursor = res.data.user.videos.edges[0].cursor; if (!cursorList.Contains(newCursor)) { cursorList.Add(newCursor); } } } } else { string currentCursor = ""; if (cursorList.Count > 0 && cursorIndex >= 0) { currentCursor = cursorList[cursorIndex]; } GqlClipResponse res = await TwitchHelper.GetGqlClips(currnetChannel, period, currentCursor, 50); videoList.Clear(); if (res.data.user != null) { foreach (var clip in res.data.user.clips.edges) { TaskData data = new TaskData(); data.Title = clip.node.title; data.Length = clip.node.durationSeconds; data.Id = clip.node.slug; data.Time = clip.node.createdAt; data.Views = clip.node.viewCount; try { var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.UriSource = new Uri(clip.node.thumbnailURL); bitmapImage.EndInit(); data.Thumbnail = bitmapImage; } catch { } videoList.Add(data); } if (res.data.user.clips.pageInfo.hasNextPage) { btnNext.IsEnabled = true; } else { btnNext.IsEnabled = false; } if (cursorIndex >= 0) { btnPrev.IsEnabled = true; } else { btnPrev.IsEnabled = false; } if (res.data.user.clips.pageInfo.hasNextPage) { string newCursor = res.data.user.clips.edges.Where(x => x.cursor != null).First().cursor; if (!cursorList.Contains(newCursor)) { cursorList.Add(newCursor); } } } } }
private async void btnGetInfo_Click(object sender, RoutedEventArgs e) { string id = ValidateUrl(textUrl.Text); if (id != "") { btnGetInfo.IsEnabled = false; downloadId = id; if (id.All(Char.IsDigit)) { downloadType = DownloadType.Video; } else { downloadType = DownloadType.Clip; } try { if (downloadType == DownloadType.Video) { GqlVideoResponse taskInfo = await TwitchHelper.GetVideoInfo(Int32.Parse(downloadId)); string thumbUrl = taskInfo.data.video.thumbnailURLs.FirstOrDefault(); Task <BitmapImage> taskThumb = InfoHelper.GetThumb(thumbUrl); try { await taskThumb; } catch { AppendLog("ERROR: Unable to find thumbnail"); } if (!taskThumb.IsFaulted) { imgThumbnail.Source = taskThumb.Result; } textTitle.Text = taskInfo.data.video.title; textStreamer.Text = taskInfo.data.video.owner.displayName; textCreatedAt.Text = taskInfo.data.video.createdAt.ToString(); currentVideoTime = taskInfo.data.video.createdAt.ToLocalTime(); streamerId = int.Parse(taskInfo.data.video.owner.id); SetEnabled(true, false); } else if (downloadType == DownloadType.Clip) { string clipId = downloadId; GqlClipResponse taskInfo = await TwitchHelper.GetClipInfo(clipId); string thumbUrl = taskInfo.data.clip.thumbnailURL; Task <BitmapImage> taskThumb = InfoHelper.GetThumb(thumbUrl); await Task.WhenAll(taskThumb); imgThumbnail.Source = taskThumb.Result; textStreamer.Text = taskInfo.data.clip.broadcaster.displayName; textCreatedAt.Text = taskInfo.data.clip.createdAt.ToString(); currentVideoTime = taskInfo.data.clip.createdAt.ToLocalTime(); textTitle.Text = taskInfo.data.clip.title; streamerId = int.Parse(taskInfo.data.clip.broadcaster.id); SetEnabled(true, false); SetEnabled(false, true); } btnGetInfo.IsEnabled = true; } catch (Exception ex) { MessageBox.Show("Unable to get Clip/Video information. Please double check your link and try again", "Unable to get info", MessageBoxButton.OK, MessageBoxImage.Error); AppendLog("ERROR: " + ex.Message); btnGetInfo.IsEnabled = true; } } else { MessageBox.Show("Please double check the VOD/Clip link", "Unable to parse input", MessageBoxButton.OK, MessageBoxImage.Error); } }