using Discord.Overrides; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.Loader; using System.Text; using System.Threading.Tasks; namespace Discord { /// /// Represents an override that can be loaded. /// public sealed class Override { /// /// Gets the ID of the override. /// public Guid Id { get; internal set; } /// /// Gets the name of the override. /// public string Name { get; internal set; } /// /// Gets the description of the override. /// public string Description { get; internal set; } /// /// Gets the date this override was created. /// public DateTimeOffset CreatedAt { get; internal set; } /// /// Gets the date the override was last modified. /// public DateTimeOffset LastUpdated { get; internal set; } internal static Override FromJson(string json) { var result = new Override(); using (var textReader = new StringReader(json)) using (var reader = new JsonTextReader(textReader)) { var obj = JObject.ReadFrom(reader); result.Id = obj["id"].ToObject(); result.Name = obj["name"].ToObject(); result.Description = obj["description"].ToObject(); result.CreatedAt = obj["created_at"].ToObject(); result.LastUpdated = obj["last_updated"].ToObject(); } return result; } } /// /// Represents a loaded override instance. /// public sealed class LoadedOverride { /// /// Gets the assembly containing the overrides definition. /// public Assembly Assembly { get; internal set; } /// /// Gets an instance of the override. /// public IOverride Instance { get; internal set; } /// /// Gets the overrides type. /// public Type Type { get; internal set; } } public sealed class BuildOverrides { /// /// Fired when an override logs a message. /// public static event Func Log { add => _logEvents.Add(value); remove => _logEvents.Remove(value); } /// /// Gets a read-only dictionary containing the currently loaded overrides. /// public IReadOnlyDictionary> LoadedOverrides => _loadedOverrides.Select(x => new KeyValuePair>(x.Key, x.Value)).ToDictionary(x => x.Key, x => x.Value); private static AssemblyLoadContext _overrideDomain; private static List> _logEvents = new(); private static ConcurrentDictionary> _loadedOverrides = new ConcurrentDictionary>(); private const string ApiUrl = "https://overrides.discordnet.dev"; static BuildOverrides() { _overrideDomain = new AssemblyLoadContext("Discord.Net.Overrides.Runtime"); _overrideDomain.Resolving += _overrideDomain_Resolving; } /// /// Gets details about a specific override. /// /// /// Note: This method does not load an override, it simply retrieves the info about it. /// /// The name of the override to get. /// /// A task representing the asynchronous get operation. The tasks result is an /// if it exists; otherwise . /// public static async Task GetOverrideAsync(string name) { using (var client = new HttpClient()) { var result = await client.GetAsync($"{ApiUrl}/overrides/{name}"); if (result.IsSuccessStatusCode) { var content = await result.Content.ReadAsStringAsync(); return Override.FromJson(content); } else return null; } } /// /// Adds an override to the current Discord.Net instance. /// /// /// The override initialization is non-blocking, any errors that occur within /// the overrides initialization procedure will be sent in the event. /// /// The name of the override to add. /// /// A task representing the asynchronous add operation. The tasks result is a boolean /// determining if the add operation was successful. /// public static async Task AddOverrideAsync(string name) { var ovrride = await GetOverrideAsync(name); if (ovrride == null) return false; return await AddOverrideAsync(ovrride); } /// /// Adds an override to the current Discord.Net instance. /// /// /// The override initialization is non-blocking, any errors that occur within /// the overrides initialization procedure will be sent in the event. /// /// The override to add. /// /// A task representing the asynchronous add operation. The tasks result is a boolean /// determining if the add operation was successful. /// public static async Task AddOverrideAsync(Override ovrride) { // download it var ms = new MemoryStream(); using (var client = new HttpClient()) { var result = await client.GetAsync($"{ApiUrl}/overrides/download/{ovrride.Id}"); if (!result.IsSuccessStatusCode) return false; await (await result.Content.ReadAsStreamAsync()).CopyToAsync(ms); } ms.Position = 0; // load the assembly //var test = Assembly.Load(ms.ToArray()); var asm = _overrideDomain.LoadFromStream(ms); // find out IOverride var overrides = asm.GetTypes().Where(x => x.GetInterfaces().Any(x => x == typeof(IOverride))); List loaded = new(); var context = new OverrideContext((m) => HandleLog(ovrride, m), ovrride); foreach (var ovr in overrides) { var inst = (IOverride)Activator.CreateInstance(ovr); inst.RegisterPackageLookupHandler((s) => { return GetDependencyAsync(ovrride.Id, s); }); _ = Task.Run(async () => { try { await inst.InitializeAsync(context); } catch (Exception x) { HandleLog(ovrride, $"Failed to initialize build override: {x}"); } }); loaded.Add(new LoadedOverride() { Assembly = asm, Instance = inst, Type = ovr }); } return _loadedOverrides.AddOrUpdate(ovrride, loaded, (_, __) => loaded) != null; } internal static void HandleLog(Override ovr, string msg) { _ = Task.Run(async () => { foreach (var item in _logEvents) { await item.Invoke(ovr, msg).ConfigureAwait(false); } }); } private static Assembly _overrideDomain_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) { // resolve the override id var v = _loadedOverrides.FirstOrDefault(x => x.Value.Any(x => x.Assembly.FullName == arg1.Assemblies.First().FullName)); return GetDependencyAsync(v.Key.Id, $"{arg2}").GetAwaiter().GetResult(); } private static async Task GetDependencyAsync(Guid id, string name) { using (var client = new HttpClient()) { var result = await client.PostAsync($"{ApiUrl}/overrides/{id}/dependency", new StringContent($"{{ \"info\": \"{name}\"}}", Encoding.UTF8, "application/json")); if (!result.IsSuccessStatusCode) throw new Exception("Failed to get dependency"); using (var ms = new MemoryStream()) { var innerStream = await result.Content.ReadAsStreamAsync(); await innerStream.CopyToAsync(ms); ms.Position = 0; return _overrideDomain.LoadFromStream(ms); } } } } }