mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-17 11:34:09 +02:00
181 lines
6.4 KiB
C#
181 lines
6.4 KiB
C#
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));
|
|
});
|
|
}
|
|
}
|
|
}
|