Example #1
0
        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();
            }
        }
Example #3
0
        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);
            }
        }