public static List <TwitchEmote> GetThirdPartyEmotes(int streamerId, string cacheFolder, Emotes embededEmotes = null, bool bttv = true, bool ffz = true, bool stv = true) { List <TwitchEmote> returnList = new List <TwitchEmote>(); List <string> alreadyAdded = new List <string>(); string bttvFolder = Path.Combine(cacheFolder, "bttv"); string ffzFolder = Path.Combine(cacheFolder, "ffz"); string stvFolder = Path.Combine(cacheFolder, "stv"); if (embededEmotes != null) { foreach (ThirdPartyEmoteData emoteData in embededEmotes.thirdParty) { try { MemoryStream ms = new MemoryStream(emoteData.data); SKCodec codec = SKCodec.Create(ms); TwitchEmote newEmote = new TwitchEmote(new List <SKBitmap>() { SKBitmap.Decode(emoteData.data) }, codec, emoteData.name, codec.FrameCount == 0 ? "png" : "gif", "", emoteData.imageScale, emoteData.data); returnList.Add(newEmote); alreadyAdded.Add(emoteData.name); } catch { } } } using (WebClient client = new WebClient()) { if (bttv) { if (!Directory.Exists(bttvFolder)) { TwitchHelper.CreateDirectory(bttvFolder); } //Global BTTV Emotes JArray BBTV = JArray.Parse(client.DownloadString("https://api.betterttv.net/3/cached/emotes/global")); foreach (var emote in BBTV) { string id = emote["id"].ToString(); string name = emote["code"].ToString(); if (alreadyAdded.Contains(name)) { continue; } string fileName = Path.Combine(bttvFolder, id + "_2x.png"); string url = String.Format("https://cdn.betterttv.net/emote/{0}/2x", id); TwitchEmote newEmote = GetTwitchEmote(fileName, url, name, emote["imageType"].ToString(), id, 2); if (newEmote != null) { returnList.Add(newEmote); alreadyAdded.Add(name); } } //Channel specific BTTV emotes try { JObject BBTV_channel = JObject.Parse(client.DownloadString("https://api.betterttv.net/3/cached/users/twitch/" + streamerId)); foreach (var emote in BBTV_channel["sharedEmotes"]) { string id = emote["id"].ToString(); string name = emote["code"].ToString(); string mime = emote["imageType"].ToString(); if (alreadyAdded.Contains(name)) { continue; } string fileName = Path.Combine(bttvFolder, id + "_2x." + mime); string url = String.Format("https://cdn.betterttv.net/emote/{0}/2x", id); TwitchEmote newEmote = GetTwitchEmote(fileName, url, name, mime, id, 2); if (newEmote != null) { returnList.Add(newEmote); alreadyAdded.Add(name); } } foreach (var emote in BBTV_channel["channelEmotes"]) { string id = emote["id"].ToString(); string name = emote["code"].ToString(); string mime = emote["imageType"].ToString(); if (alreadyAdded.Contains(name)) { continue; } string fileName = Path.Combine(bttvFolder, id + "_2x." + mime); string url = String.Format("https://cdn.betterttv.net/emote/{0}/2x", id); TwitchEmote newEmote = GetTwitchEmote(fileName, url, name, mime, id, 2); if (newEmote != null) { returnList.Add(newEmote); alreadyAdded.Add(name); } } } catch { } } if (ffz) { if (!Directory.Exists(ffzFolder)) { TwitchHelper.CreateDirectory(ffzFolder); } //Global FFZ emotes JArray FFZ = JArray.Parse(client.DownloadString("https://api.betterttv.net/3/cached/frankerfacez/emotes/global")); foreach (var emote in FFZ) { string id = emote["id"].ToString(); string name = emote["code"].ToString(); string mime = emote["imageType"].ToString(); if (alreadyAdded.Contains(name)) { continue; } string fileName = Path.Combine(ffzFolder, id + "_1x." + mime); string url = String.Format("https://cdn.betterttv.net/frankerfacez_emote/{0}/1", id); TwitchEmote newEmote = GetTwitchEmote(fileName, url, name, mime, id, 2); if (newEmote != null) { returnList.Add(newEmote); alreadyAdded.Add(name); } } //Channel specific FFZ emotes try { JArray FFZ_channel = JArray.Parse(client.DownloadString("https://api.betterttv.net/3/cached/frankerfacez/users/twitch/" + streamerId)); foreach (var emote in FFZ_channel) { string id = emote["id"].ToString(); string name = emote["code"].ToString(); string mime = emote["imageType"].ToString(); if (alreadyAdded.Contains(name)) { continue; } string fileName = Path.Combine(ffzFolder, id + "_2x." + mime); string fileNameLow = Path.Combine(ffzFolder, id + "_1x" + mime); TwitchEmote newEmote = null; if (File.Exists(fileNameLow)) { newEmote = GetTwitchEmote(fileName, String.Format("https://cdn.betterttv.net/frankerfacez_emote/{0}/1", id), name, mime, id, 1); } if (newEmote == null) { newEmote = GetTwitchEmote(fileName, String.Format("https://cdn.betterttv.net/frankerfacez_emote/{0}/2", id), name, mime, id, 2); if (newEmote == null) { newEmote = GetTwitchEmote(fileName, String.Format("https://cdn.betterttv.net/frankerfacez_emote/{0}/1", id), name, mime, id, 1); } } if (newEmote != null) { returnList.Add(newEmote); alreadyAdded.Add(name); } } } catch { } } if (stv) { if (!Directory.Exists(stvFolder)) { TwitchHelper.CreateDirectory(stvFolder); } //Global 7tv Emotes JArray STV = JArray.Parse(client.DownloadString("https://api.7tv.app/v2/emotes/global")); foreach (var emote in STV) { string id = emote["id"].ToString(); string name = emote["name"].ToString(); string mime = emote["mime"].ToString().Split('/')[1]; string url2x = emote["urls"][1][1].ToString(); // 2x if (alreadyAdded.Contains(name)) { continue; } byte[] bytes; string fileName = Path.Combine(stvFolder, id + "_2x." + mime); if (File.Exists(fileName)) { bytes = File.ReadAllBytes(fileName); } else { bytes = client.DownloadData(url2x); File.WriteAllBytes(fileName, bytes); } MemoryStream ms = new MemoryStream(bytes); returnList.Add(new TwitchEmote(new List <SKBitmap>() { SKBitmap.Decode(bytes) }, SKCodec.Create(ms), name, mime, id, 2, bytes)); alreadyAdded.Add(name); } //Channel specific 7tv emotes try { JArray STV_channel = JArray.Parse(client.DownloadString(String.Format("https://api.7tv.app/v2/users/{0}/emotes", streamerId))); foreach (var emote in STV_channel) { string id = emote["id"].ToString(); string name = emote["name"].ToString(); string mime = emote["mime"].ToString().Split('/')[1]; string url2x = emote["urls"][1][1].ToString(); // 2x if (alreadyAdded.Contains(name)) { continue; } byte[] bytes; string fileName = Path.Combine(stvFolder, id + "_2x." + mime); if (File.Exists(fileName)) { bytes = File.ReadAllBytes(fileName); } else { bytes = client.DownloadData(url2x); File.WriteAllBytes(fileName, bytes); } MemoryStream ms = new MemoryStream(bytes); returnList.Add(new TwitchEmote(new List <SKBitmap>() { SKBitmap.Decode(bytes) }, SKCodec.Create(ms), name, mime, id, 2, bytes)); alreadyAdded.Add(name); } } catch { } } } return(returnList); }
public static List <TwitchEmote> GetEmotes(List <Comment> comments, string cacheFolder, Emotes embededEmotes = null, bool deepSearch = false) { List <TwitchEmote> returnList = new List <TwitchEmote>(); List <string> alreadyAdded = new List <string>(); List <string> failedEmotes = new List <string>(); string emoteFolder = Path.Combine(cacheFolder, "emotes"); if (!Directory.Exists(emoteFolder)) { TwitchHelper.CreateDirectory(emoteFolder); } if (embededEmotes != null) { foreach (FirstPartyEmoteData emoteData in embededEmotes.firstParty) { try { MemoryStream ms = new MemoryStream(emoteData.data); SKCodec codec = SKCodec.Create(ms); TwitchEmote newEmote = new TwitchEmote(new List <SKBitmap>() { SKBitmap.Decode(emoteData.data) }, codec, emoteData.id, codec.FrameCount == 0 ? "png" : "gif", emoteData.id, emoteData.imageScale, emoteData.data); returnList.Add(newEmote); alreadyAdded.Add(emoteData.id); } catch { } } } using (WebClient client = new WebClient()) { foreach (var comment in comments) { if (comment.message.fragments == null) { continue; } foreach (var fragment in comment.message.fragments) { if (fragment.emoticon != null) { string id = fragment.emoticon.emoticon_id; if (!alreadyAdded.Contains(id) && !failedEmotes.Contains(id)) { try { string filePath = ""; if (File.Exists(Path.Combine(emoteFolder, id + "_1x.gif"))) { filePath = Path.Combine(emoteFolder, id + "_1x.gif"); } else if (File.Exists(Path.Combine(emoteFolder, id + "_1x.png")) && !id.Contains("emotesv2_")) { filePath = Path.Combine(emoteFolder, id + "_1x.png"); } if (File.Exists(filePath)) { SKBitmap emoteImage = SKBitmap.Decode(filePath); if (emoteImage == null) { try { File.Delete(filePath); } catch { } } else { try { byte[] bytes = File.ReadAllBytes(filePath); MemoryStream ms = new MemoryStream(bytes); SKCodec codec = SKCodec.Create(ms); returnList.Add(new TwitchEmote(new List <SKBitmap>() { SKBitmap.Decode(bytes) }, codec, id, codec.FrameCount == 0 ? "png" : "gif", id, 1, bytes)); alreadyAdded.Add(id); } catch { } } } if (!alreadyAdded.Contains(id)) { byte[] bytes = client.DownloadData(String.Format("https://static-cdn.jtvnw.net/emoticons/v2/{0}/default/dark/1.0", id)); alreadyAdded.Add(id); MemoryStream ms = new MemoryStream(bytes); SKCodec codec = SKCodec.Create(ms); TwitchEmote newEmote = new TwitchEmote(new List <SKBitmap>() { SKBitmap.Decode(bytes) }, codec, id, codec.FrameCount == 0 ? "png" : "gif", id, 1, bytes); returnList.Add(newEmote); try { File.WriteAllBytes(Path.Combine(emoteFolder, newEmote.id + "_1x." + newEmote.imageType), bytes); } catch { } } } catch (WebException) { failedEmotes.Add(id); } } } } } } return(returnList); }
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; JObject taskInfo = await TwitchHelper.GetVideoInfo(Int32.Parse(videoId)); chatRoot.streamer.name = taskInfo["channel"]["display_name"].ToString(); chatRoot.streamer.id = taskInfo["channel"]["_id"].ToObject <int>(); videoStart = downloadOptions.CropBeginning ? downloadOptions.CropBeginningTime : 0.0; videoEnd = downloadOptions.CropEnding ? downloadOptions.CropEndingTime : taskInfo["length"].ToObject <double>(); } else { JObject taskInfo = await TwitchHelper.GetClipInfo(downloadOptions.Id); videoId = taskInfo["vod"]["id"].ToString(); downloadOptions.CropBeginning = true; downloadOptions.CropBeginningTime = taskInfo["vod"]["offset"].ToObject <int>(); downloadOptions.CropEnding = true; downloadOptions.CropEndingTime = downloadOptions.CropBeginningTime + taskInfo["duration"].ToObject <double>(); chatRoot.streamer.name = taskInfo["broadcaster"]["display_name"].ToString(); chatRoot.streamer.id = taskInfo["broadcaster"]["id"].ToObject <int>(); videoStart = taskInfo["vod"]["offset"].ToObject <double>(); videoEnd = taskInfo["vod"]["offset"].ToObject <double>() + taskInfo["duration"].ToObject <double>(); } 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(); } }