-
Notifications
You must be signed in to change notification settings - Fork 272
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ability to embed twitch badges and bit emotes (#437)
* add ability to embed twitch badges and bit emotes * render using the cached ones! * add --offline argument for CLI * script to update old chat json files * Renamed 'emotes' json property to 'embededData', renamed '--embed-emotes' to '--embed-images', update README, complete some TODOs, typos * Typos, added some comments * GetImage can fail, put in try catch * use status codes in the catch statements Co-authored-by: ScrubN <[email protected]> Co-authored-by: Lewis Pardo <[email protected]>
- Loading branch information
1 parent
7603ec9
commit ef143bb
Showing
19 changed files
with
573 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
TwitchDownloaderCLI/Modes/Arguments/ChatDownloadUpdaterArgs.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using CommandLine; | ||
|
||
namespace TwitchDownloaderCLI.Modes.Arguments | ||
{ | ||
|
||
[Verb("chatupdate", HelpText = "Updates the embeded emotes, badges, and bits of a chat download.")] | ||
public class ChatDownloadUpdaterArgs | ||
{ | ||
[Option('i', "input", Required = true, HelpText = "Path to input file. Valid extensions are json")] | ||
public string InputFile { get; set; } | ||
|
||
[Option('o', "output", Required = true, HelpText = "Path to output file. Extension should match the input.")] | ||
public string OutputFile { get; set; } | ||
|
||
[Option('E', "embed-missing", Default = true, HelpText = "Embed missing emotes, badges, and bits. Already embedded images will be untouched")] | ||
public bool EmbedMissing { get; set; } | ||
|
||
[Option('U', "update-old", Default = false, HelpText = "Update old emotes, badges, and bits to the current. All embedded images will be overwritten")] | ||
public bool UpdateOldEmbeds { get; set; } | ||
|
||
[Option("bttv", Default = true, HelpText = "Enable BTTV embedding in chat download.")] | ||
public bool BttvEmotes { get; set; } | ||
|
||
[Option("ffz", Default = true, HelpText = "Enable FFZ embedding in chat download.")] | ||
public bool FfzEmotes { get; set; } | ||
|
||
[Option("stv", Default = true, HelpText = "Enable 7tv embedding in chat download.")] | ||
public bool StvEmotes { get; set; } | ||
|
||
[Option("temp-path", Default = "", HelpText = "Path to temporary folder to use for cache.")] | ||
public string TempFolder { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
using TwitchDownloaderCLI.Modes.Arguments; | ||
using TwitchDownloaderCore; | ||
using TwitchDownloaderCore.Options; | ||
using TwitchDownloaderCore.TwitchObjects; | ||
|
||
namespace TwitchDownloaderCLI.Modes | ||
{ | ||
internal class DownloadChatUpdater | ||
{ | ||
internal static void Update(ChatDownloadUpdaterArgs inputOptions) | ||
{ | ||
DownloadFormat inFormat = Path.GetExtension(inputOptions.InputFile)!.ToLower() switch | ||
{ | ||
".json" => DownloadFormat.Json, | ||
".html" => DownloadFormat.Html, | ||
".htm" => DownloadFormat.Html, | ||
_ => DownloadFormat.Text | ||
}; | ||
DownloadFormat outFormat = Path.GetExtension(inputOptions.OutputFile)!.ToLower() switch | ||
{ | ||
".json" => DownloadFormat.Json, | ||
".html" => DownloadFormat.Html, | ||
".htm" => DownloadFormat.Html, | ||
_ => DownloadFormat.Text | ||
}; | ||
// Check that both input and output are json | ||
if (inFormat != DownloadFormat.Json || outFormat != DownloadFormat.Json) | ||
{ | ||
Console.WriteLine("[ERROR] - {0} format must be be json!", inFormat != DownloadFormat.Json ? "Input" : "Output"); | ||
Environment.Exit(1); | ||
} | ||
if (!File.Exists(inputOptions.InputFile)) | ||
{ | ||
Console.WriteLine("[ERROR] - Input file does not exist!"); | ||
Environment.Exit(1); | ||
} | ||
if (!inputOptions.EmbedMissing && !inputOptions.UpdateOldEmbeds) | ||
{ | ||
Console.WriteLine("[ERROR] - Please enable either EmbedMissingEmotes or UpdateOldEmotes"); | ||
Environment.Exit(1); | ||
} | ||
|
||
// Read in the old input file | ||
ChatRoot chatRoot = Task.Run(() => ChatRenderer.ParseJsonStatic(inputOptions.InputFile)).Result; | ||
if (chatRoot.streamer == null) | ||
{ | ||
chatRoot.streamer = new Streamer(); | ||
chatRoot.streamer.id = int.Parse(chatRoot.comments.First().channel_id); | ||
chatRoot.streamer.name = Task.Run(() => TwitchHelper.GetStreamerName(chatRoot.streamer.id)).Result; | ||
} | ||
if (chatRoot.embeddedData == null) | ||
{ | ||
chatRoot.embeddedData = new EmbeddedData(); | ||
} | ||
|
||
string cacheFolder = Path.Combine(string.IsNullOrWhiteSpace(inputOptions.TempFolder) ? Path.GetTempPath() : inputOptions.TempFolder, "TwitchDownloader", "chatupdatecache"); | ||
|
||
// Clear working directory if it already exists | ||
if (Directory.Exists(cacheFolder)) | ||
Directory.Delete(cacheFolder, true); | ||
|
||
// Thirdparty emotes | ||
if (chatRoot.embeddedData.thirdParty == null || inputOptions.UpdateOldEmbeds) | ||
{ | ||
chatRoot.embeddedData.thirdParty = new List<EmbedEmoteData>(); | ||
} | ||
Console.WriteLine("Input third party emote count: " + chatRoot.embeddedData.thirdParty.Count); | ||
List<TwitchEmote> thirdPartyEmotes = new List<TwitchEmote>(); | ||
thirdPartyEmotes = Task.Run(() => TwitchHelper.GetThirdPartyEmotes(chatRoot.streamer.id, cacheFolder, bttv: inputOptions.BttvEmotes, ffz: inputOptions.FfzEmotes, stv: inputOptions.StvEmotes, embeddedData: chatRoot.embeddedData)).Result; | ||
foreach (TwitchEmote emote in thirdPartyEmotes) | ||
{ | ||
EmbedEmoteData newEmote = new EmbedEmoteData(); | ||
newEmote.id = emote.Id; | ||
newEmote.imageScale = emote.ImageScale; | ||
newEmote.data = emote.ImageData; | ||
newEmote.name = emote.Name; | ||
newEmote.width = emote.Width / emote.ImageScale; | ||
newEmote.height = emote.Height / emote.ImageScale; | ||
chatRoot.embeddedData.thirdParty.Add(newEmote); | ||
} | ||
Console.WriteLine("Output third party emote count: " + chatRoot.embeddedData.thirdParty.Count); | ||
|
||
// Firstparty emotes | ||
if (chatRoot.embeddedData.firstParty == null || inputOptions.UpdateOldEmbeds) | ||
{ | ||
chatRoot.embeddedData.firstParty = new List<EmbedEmoteData>(); | ||
} | ||
Console.WriteLine("Input first party emote count: " + chatRoot.embeddedData.firstParty.Count); | ||
List<TwitchEmote> firstPartyEmotes = new List<TwitchEmote>(); | ||
firstPartyEmotes = Task.Run(() => TwitchHelper.GetEmotes(chatRoot.comments, cacheFolder, embeddedData: chatRoot.embeddedData)).Result; | ||
foreach (TwitchEmote emote in firstPartyEmotes) | ||
{ | ||
EmbedEmoteData newEmote = new EmbedEmoteData(); | ||
newEmote.id = emote.Id; | ||
newEmote.imageScale = emote.ImageScale; | ||
newEmote.data = emote.ImageData; | ||
newEmote.width = emote.Width / emote.ImageScale; | ||
newEmote.height = emote.Height / emote.ImageScale; | ||
chatRoot.embeddedData.firstParty.Add(newEmote); | ||
} | ||
Console.WriteLine("Output third party emote count: " + chatRoot.embeddedData.firstParty.Count); | ||
|
||
// Twitch badges | ||
if (chatRoot.embeddedData.twitchBadges == null || inputOptions.UpdateOldEmbeds) | ||
{ | ||
chatRoot.embeddedData.twitchBadges = new List<EmbedChatBadge>(); | ||
} | ||
Console.WriteLine("Input twitch badge count: " + chatRoot.embeddedData.twitchBadges.Count); | ||
List<ChatBadge> twitchBadges = new List<ChatBadge>(); | ||
twitchBadges = Task.Run(() => TwitchHelper.GetChatBadges(chatRoot.streamer.id, cacheFolder, embeddedData: chatRoot.embeddedData)).Result; | ||
foreach (ChatBadge badge in twitchBadges) | ||
{ | ||
EmbedChatBadge newBadge = new EmbedChatBadge(); | ||
newBadge.name = badge.Name; | ||
newBadge.versions = badge.VersionsData; | ||
chatRoot.embeddedData.twitchBadges.Add(newBadge); | ||
} | ||
Console.WriteLine("Output twitch badge count: " + chatRoot.embeddedData.twitchBadges.Count); | ||
|
||
// Twitch bits / cheers | ||
if (chatRoot.embeddedData.twitchBits == null || inputOptions.UpdateOldEmbeds) | ||
{ | ||
chatRoot.embeddedData.twitchBits = new List<EmbedCheerEmote>(); | ||
} | ||
Console.WriteLine("Input twitch bit count: " + chatRoot.embeddedData.twitchBits.Count); | ||
List<CheerEmote> twitchBits = new List<CheerEmote>(); | ||
twitchBits = Task.Run(() => TwitchHelper.GetBits(cacheFolder, chatRoot.streamer.id.ToString(), embeddedData: chatRoot.embeddedData)).Result; | ||
foreach (CheerEmote bit in twitchBits) | ||
{ | ||
EmbedCheerEmote newBit = new EmbedCheerEmote(); | ||
newBit.prefix = bit.prefix; | ||
newBit.tierList = new Dictionary<int, EmbedEmoteData>(); | ||
foreach (KeyValuePair<int, TwitchEmote> emotePair in bit.tierList) | ||
{ | ||
EmbedEmoteData newEmote = new EmbedEmoteData(); | ||
newEmote.id = emotePair.Value.Id; | ||
newEmote.imageScale = emotePair.Value.ImageScale; | ||
newEmote.data = emotePair.Value.ImageData; | ||
newEmote.name = emotePair.Value.Name; | ||
newEmote.width = emotePair.Value.Width / emotePair.Value.ImageScale; | ||
newEmote.height = emotePair.Value.Height / emotePair.Value.ImageScale; | ||
newBit.tierList.Add(emotePair.Key, newEmote); | ||
} | ||
chatRoot.embeddedData.twitchBits.Add(newBit); | ||
} | ||
Console.WriteLine("Input twitch bit count: " + chatRoot.embeddedData.twitchBits.Count); | ||
|
||
// Finally save the output to file! | ||
// TODO: maybe in the future we could also export as HTML here too? | ||
if (outFormat == DownloadFormat.Json) | ||
{ | ||
using (TextWriter writer = File.CreateText(inputOptions.OutputFile)) | ||
{ | ||
var serializer = new Newtonsoft.Json.JsonSerializer(); | ||
serializer.Serialize(writer, chatRoot); | ||
} | ||
} | ||
|
||
// Clear our working directory, it's highly unlikely we would reuse it anyways | ||
if (Directory.Exists(cacheFolder)) | ||
Directory.Delete(cacheFolder, true); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.