using BotSharp.Abstraction.Plugins.Models; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using System.Drawing; using System.IO; using System.Reflection; using System.Xml; using BotSharp.Abstraction.Repositories; namespace BotSharp.Core.Plugins; public class PluginLoader { private readonly IServiceCollection _services; private readonly IConfiguration _config; private readonly PluginSettings _settings; private static List _modules = new List(); private static List _plugins = new List(); private static string _executingDir; public PluginLoader(IServiceCollection services, IConfiguration config, PluginSettings settings) { _services = services; _config = config; _settings = settings; } public void Load(Action loaded, string? plugin = null) { _executingDir = Directory.GetParent(Assembly.GetEntryAssembly().Location).FullName; _settings.Assemblies.ToList().ForEach(assemblyName => { if (plugin != null && plugin != assemblyName) { return; } var assemblyPath = Path.Combine(_executingDir, assemblyName + ".dll"); if (File.Exists(assemblyPath)) { var assembly = Assembly.Load(assemblyName); var modules = assembly.GetTypes() .Where(x => x.GetInterface(nameof(IBotSharpPlugin)) != null) .Select(x => Activator.CreateInstance(x) as IBotSharpPlugin) .ToList(); foreach (var module in modules) { if (_plugins.Exists(x => x.Id == module.Id)) { continue; } // Solve plugin dependency var attr = module.GetType().GetCustomAttribute(); if (attr != null) { foreach (var plugin in attr.PluginNames) { if (!_plugins.Any(x => x.Assembly == plugin)) { Load(loaded, plugin); } if (!_plugins.Any(x => x.Assembly == plugin)) { Console.WriteLine($"Load dependent plugin {plugin} failed by {module.Name}.", Color.Red); } } } InitModule(assemblyName, module); } loaded(assembly); } else { Console.WriteLine($"Can't find assemble {assemblyPath}.", Color.Red); } }); } private void InitModule(string assembly, IBotSharpPlugin module) { module.RegisterDI(_services, _config); // string classSummary = GetSummaryComment(module.GetType()); var name = string.IsNullOrEmpty(module.Name) ? module.GetType().Name : module.Name; _modules.Add(module); _plugins.Add(new PluginDef { Id = module.Id, Name = name, Module = module, Description = module.Description, Assembly = assembly, IconUrl = module.IconUrl, AgentIds = module.AgentIds }); Console.Write($"Loaded plugin "); Console.Write(name, Color.Green); Console.WriteLine($" from {assembly}."); if (!string.IsNullOrEmpty(module.Description)) { Console.WriteLine(module.Description); } } public List GetPlugins(IServiceProvider services) { // Apply user configurations var db = services.GetRequiredService(); var config = db.GetPluginConfig(); foreach (var plugin in _plugins) { plugin.Enabled = plugin.IsCore || config.EnabledPlugins.Contains(plugin.Id); } return _plugins; } public PagedItems GetPagedPlugins(IServiceProvider services, PluginFilter filter) { var plugins = GetPlugins(services); var pager = filter?.Pager ?? new Pagination(); return new PagedItems { Items = plugins.Skip(pager.Offset).Take(pager.Size), Count = plugins.Count() }; } public PluginDef UpdatePluginStatus(IServiceProvider services, string id, bool enable) { var plugin = _plugins.First(x => x.Id == id); plugin.Enabled = enable; // save to config var db = services.GetRequiredService(); var config = db.GetPluginConfig(); if (enable) { var dependentPlugins = new HashSet(); FindPluginDependency(id, enable, ref dependentPlugins); var missingPlugins = dependentPlugins.Where(x => !config.EnabledPlugins.Contains(x)).ToList(); if (!missingPlugins.IsNullOrEmpty()) { config.EnabledPlugins.AddRange(missingPlugins); db.SavePluginConfig(config); } // enable agents var agentService = services.GetRequiredService(); foreach (var agentId in plugin.AgentIds) { var agent = agentService.LoadAgent(agentId).Result; agent.Disabled = false; agentService.UpdateAgent(agent, AgentField.Disabled); if (agent.InheritAgentId != null) { agent = agentService.LoadAgent(agent.InheritAgentId).Result; agent.Disabled = false; agentService.UpdateAgent(agent, AgentField.Disabled); } } } else { if (config.EnabledPlugins.Exists(x => x == id)) { config.EnabledPlugins.Remove(id); db.SavePluginConfig(config); } // disable agents var agentService = services.GetRequiredService(); foreach (var agentId in plugin.AgentIds) { var agent = agentService.LoadAgent(agentId).Result; agent.Disabled = true; agentService.UpdateAgent(agent, AgentField.Disabled); } } return plugin; } private void FindPluginDependency(string pluginId, bool enabled, ref HashSet dependentPlugins) { var pluginDef = _plugins.FirstOrDefault(x => x.Id == pluginId); if (pluginDef == null) return; if (!pluginDef.IsCore) { pluginDef.Enabled = enabled; dependentPlugins.Add(pluginId); } var foundPlugin = _modules.FirstOrDefault(x => x.Id == pluginId); if (foundPlugin == null) return; var attr = foundPlugin.GetType().GetCustomAttribute(); if (attr != null && !attr.PluginNames.IsNullOrEmpty()) { foreach (var name in attr.PluginNames) { var plugins = _plugins.Where(x => x.Assembly == name).ToList(); if (plugins.IsNullOrEmpty()) return; foreach (var plugin in plugins) { FindPluginDependency(plugin.Id, enabled, ref dependentPlugins); } } } } public string GetSummaryComment(Type member) { string summary = string.Empty; XmlDocument xmlDoc = new XmlDocument(); // Load the XML documentation file var xmlFile = Path.Combine(_executingDir, $"{member.Module.Assembly.FullName.Split(',')[0]}.xml"); if (!File.Exists(xmlFile)) { return ""; } xmlDoc.Load(xmlFile); // Replace with your actual XML documentation file name // Construct the XPath query to find the summary comment string memberName = $"T:{member.FullName}"; string xpath = $"/doc/members/member[@name='{memberName}']/summary"; // Find the summary comment using the XPath query XmlNode summaryNode = xmlDoc.SelectSingleNode(xpath); if (summaryNode != null) { summary = summaryNode.InnerXml.Trim(); } return summary; } public void Configure(IApplicationBuilder app) { if (_modules.Count == 0) { Console.WriteLine($"No plugin loaded. Please check whether the Load() method is called.", Color.Yellow); } _modules.ForEach(module => { if (module.GetType().GetInterface(nameof(IBotSharpAppPlugin)) != null) { (module as IBotSharpAppPlugin).Configure(app); } }); } }