using CefSharp; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Windows.Forms; using TweetDuck.Core.Controls; using TweetDuck.Core.Utils; using TweetDuck.Resources; using TweetLib.Core.Data; using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins.Config; using TweetLib.Core.Features.Plugins.Enums; using TweetLib.Core.Features.Plugins.Events; namespace TweetDuck.Plugins{ sealed class PluginManager : IPluginManager{ private const string SetupScriptPrefix = "plugins."; public string PathCustomPlugins => Path.Combine(pluginFolder, PluginGroup.Custom.GetSubFolder()); public IEnumerable<Plugin> Plugins => plugins; public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections; public IPluginConfig Config { get; } public event EventHandler<PluginErrorEventArgs> Reloaded; public event EventHandler<PluginErrorEventArgs> Executed; private readonly string pluginFolder; private readonly string pluginDataFolder; private readonly Control sync; private readonly PluginBridge bridge; private readonly HashSet<Plugin> plugins = new HashSet<Plugin>(); private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>(); private readonly Random rand = new Random(); private IWebBrowser mainBrowser; public PluginManager(Control sync, IPluginConfig config, string pluginFolder, string pluginDataFolder){ this.Config = config; this.Config.PluginChangedState += Config_PluginChangedState; this.pluginFolder = pluginFolder; this.pluginDataFolder = pluginDataFolder; this.sync = sync; this.bridge = new PluginBridge(this); } public void Register(IWebBrowser browser, PluginEnvironment environment, bool asMainBrowser = false){ browser.FrameLoadEnd += (sender, args) => { IFrame frame = args.Frame; if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){ ExecutePlugins(frame, environment); } }; browser.RegisterAsyncJsObject("$TDP", bridge); if (asMainBrowser){ mainBrowser = browser; } } private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){ mainBrowser?.ExecuteScriptAsync("TDPF_setPluginState", e.Plugin, e.IsEnabled); } public bool IsPluginInstalled(string identifier){ return plugins.Any(plugin => plugin.Identifier.Equals(identifier)); } public bool HasAnyPlugin(PluginEnvironment environment){ return plugins.Any(plugin => plugin.Environments.HasFlag(environment)); } public bool IsPluginConfigurable(Plugin plugin){ return plugin.HasConfig || bridge.WithConfigureFunction.Contains(plugin); } public void ConfigurePlugin(Plugin plugin){ if (bridge.WithConfigureFunction.Contains(plugin)){ mainBrowser?.ExecuteScriptAsync("TDPF_configurePlugin", plugin); } else if (plugin.HasConfig){ if (File.Exists(plugin.ConfigPath)){ using(Process.Start("explorer.exe", "/select,\"" + plugin.ConfigPath.Replace('/', '\\') + "\"")){} } else{ using(Process.Start("explorer.exe", '"' + plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\') + '"')){} } } } public int GetTokenFromPlugin(Plugin plugin){ foreach(KeyValuePair<int, Plugin> kvp in tokens){ if (kvp.Value.Equals(plugin)){ return kvp.Key; } } int token, attempts = 1000; do{ token = rand.Next(); }while(tokens.ContainsKey(token) && --attempts >= 0); if (attempts < 0){ token = -tokens.Count - 1; } tokens[token] = plugin; return token; } public Plugin GetPluginFromToken(int token){ return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null; } public void Reload(){ plugins.Clear(); tokens.Clear(); List<string> loadErrors = new List<string>(1); foreach(var result in PluginGroupExtensions.Values.SelectMany(group => PluginLoader.AllInFolder(pluginFolder, pluginDataFolder, group))){ if (result.HasValue){ plugins.Add(result.Value); } else{ loadErrors.Add(result.Exception.Message); } } Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors)); } private void ExecutePlugins(IFrame frame, PluginEnvironment environment){ if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, SetupScriptPrefix + environment.GetPluginScriptFile(), sync)){ return; } bool includeDisabled = environment.IncludesDisabledPlugins(); if (includeDisabled){ ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig"); } List<string> failedPlugins = new List<string>(1); foreach(Plugin plugin in Plugins){ string path = plugin.GetScriptPath(environment); if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun){ continue; } string script; try{ script = File.ReadAllText(path); }catch(Exception e){ failedPlugins.Add($"{plugin.Identifier} ({Path.GetFileName(path)}): {e.Message}"); continue; } ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), $"plugin:{plugin}"); } sync.InvokeAsyncSafe(() => { Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins)); }); } } }