mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-31 08:34:10 +02:00
Add tdp:// scheme for plugins (with 'root/' to access root files)
This commit is contained in:
parent
464e758b94
commit
458eeeccda
@ -16,6 +16,7 @@
|
|||||||
using TweetDuck.Dialogs.Settings;
|
using TweetDuck.Dialogs.Settings;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
using TweetDuck.Management.Analytics;
|
using TweetDuck.Management.Analytics;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
using TweetDuck.Updates;
|
using TweetDuck.Updates;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
@ -65,7 +66,7 @@ public bool IsWaiting{
|
|||||||
private VideoPlayer videoPlayer;
|
private VideoPlayer videoPlayer;
|
||||||
private AnalyticsManager analytics;
|
private AnalyticsManager analytics;
|
||||||
|
|
||||||
public FormBrowser(){
|
public FormBrowser(PluginSchemeFactory pluginScheme){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName;
|
Text = Program.BrandName;
|
||||||
@ -74,6 +75,7 @@ public FormBrowser(){
|
|||||||
this.plugins.Reloaded += plugins_Reloaded;
|
this.plugins.Reloaded += plugins_Reloaded;
|
||||||
this.plugins.Executed += plugins_Executed;
|
this.plugins.Executed += plugins_Executed;
|
||||||
this.plugins.Reload();
|
this.plugins.Reload();
|
||||||
|
pluginScheme.Setup(plugins);
|
||||||
|
|
||||||
this.notification = new FormNotificationTweet(this, plugins);
|
this.notification = new FormNotificationTweet(this, plugins);
|
||||||
this.notification.Show();
|
this.notification.Show();
|
||||||
|
@ -114,6 +114,8 @@ private void browser_LoadingStateChanged(object sender, LoadingStateChangedEvent
|
|||||||
browser.AddWordToDictionary(word);
|
browser.AddWordToDictionary(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cef.AddCrossOriginWhitelistEntry(TwitterUrls.TweetDeck, PluginSchemeFactory.Name, "", true);
|
||||||
|
|
||||||
browser.BeginInvoke(new Action(OnBrowserReady));
|
browser.BeginInvoke(new Action(OnBrowserReady));
|
||||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||||
}
|
}
|
||||||
|
35
Plugins/PluginSchemeFactory.cs
Normal file
35
Plugins/PluginSchemeFactory.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System.Net;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Core.Browser;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
|
||||||
|
namespace TweetDuck.Plugins{
|
||||||
|
sealed class PluginSchemeFactory : ISchemeHandlerFactory{
|
||||||
|
public const string Name = PluginSchemeHandler<IResourceHandler>.Name;
|
||||||
|
|
||||||
|
private readonly PluginSchemeHandler<IResourceHandler> handler = new PluginSchemeHandler<IResourceHandler>(new ResourceProvider());
|
||||||
|
|
||||||
|
internal void Setup(PluginManager plugins){
|
||||||
|
handler.Setup(plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request){
|
||||||
|
return handler.Process(request.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ResourceProvider : IResourceProvider<IResourceHandler>{
|
||||||
|
public IResourceHandler Status(HttpStatusCode code, string message){
|
||||||
|
return ResourceHandler.ForErrorMessage(message, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResourceHandler File(byte[] bytes, string extension){
|
||||||
|
if (bytes.Length == 0){
|
||||||
|
return Status(HttpStatusCode.NoContent, "File is empty."); // FromByteArray crashes CEF internals with no contents
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return ResourceHandler.FromByteArray(bytes, ResourceHandler.GetMimeType(extension));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
Program.cs
14
Program.cs
@ -11,6 +11,7 @@
|
|||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
using TweetDuck.Resources;
|
using TweetDuck.Resources;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
@ -168,6 +169,17 @@ private static void Main(){
|
|||||||
LogSeverity = Arguments.HasFlag(Arguments.ArgLogging) ? LogSeverity.Info : LogSeverity.Disable
|
LogSeverity = Arguments.HasFlag(Arguments.ArgLogging) ? LogSeverity.Info : LogSeverity.Disable
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var pluginScheme = new PluginSchemeFactory();
|
||||||
|
|
||||||
|
settings.RegisterScheme(new CefCustomScheme{
|
||||||
|
SchemeName = PluginSchemeFactory.Name,
|
||||||
|
IsStandard = false,
|
||||||
|
IsSecure = true,
|
||||||
|
IsCorsEnabled = true,
|
||||||
|
IsCSPBypassing = true,
|
||||||
|
SchemeHandlerFactory = pluginScheme
|
||||||
|
});
|
||||||
|
|
||||||
CommandLineArgs.ReadCefArguments(Config.User.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
CommandLineArgs.ReadCefArguments(Config.User.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
||||||
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
||||||
@ -176,7 +188,7 @@ private static void Main(){
|
|||||||
|
|
||||||
Win.Application.ApplicationExit += (sender, args) => ExitCleanup();
|
Win.Application.ApplicationExit += (sender, args) => ExitCleanup();
|
||||||
|
|
||||||
FormBrowser mainForm = new FormBrowser();
|
FormBrowser mainForm = new FormBrowser(pluginScheme);
|
||||||
Resources.Initialize(mainForm);
|
Resources.Initialize(mainForm);
|
||||||
Win.Application.Run(mainForm);
|
Win.Application.Run(mainForm);
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Plugins\PluginSchemeFactory.cs" />
|
||||||
<Compile Include="Version.cs" />
|
<Compile Include="Version.cs" />
|
||||||
<Compile Include="Configuration\Arguments.cs" />
|
<Compile Include="Configuration\Arguments.cs" />
|
||||||
<Compile Include="Configuration\ConfigManager.cs" />
|
<Compile Include="Configuration\ConfigManager.cs" />
|
||||||
|
8
lib/TweetLib.Core/Browser/IResourceProvider.cs
Normal file
8
lib/TweetLib.Core/Browser/IResourceProvider.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace TweetLib.Core.Browser{
|
||||||
|
public interface IResourceProvider<T>{
|
||||||
|
T Status(HttpStatusCode code, string message);
|
||||||
|
T File(byte[] bytes, string extension);
|
||||||
|
}
|
||||||
|
}
|
@ -47,7 +47,7 @@ internal int GetTokenFromPlugin(Plugin plugin){
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Plugin? GetPluginFromToken(int token){
|
internal Plugin? GetPluginFromToken(int token){
|
||||||
return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null;
|
return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,14 +16,14 @@ public sealed class PluginManager{
|
|||||||
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
|
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
|
||||||
|
|
||||||
public IPluginConfig Config { get; }
|
public IPluginConfig Config { get; }
|
||||||
|
|
||||||
public event EventHandler<PluginErrorEventArgs>? Reloaded;
|
public event EventHandler<PluginErrorEventArgs>? Reloaded;
|
||||||
public event EventHandler<PluginErrorEventArgs>? Executed;
|
public event EventHandler<PluginErrorEventArgs>? Executed;
|
||||||
|
|
||||||
private readonly string pluginFolder;
|
private readonly string pluginFolder;
|
||||||
private readonly string pluginDataFolder;
|
private readonly string pluginDataFolder;
|
||||||
|
|
||||||
private readonly PluginBridge bridge;
|
internal readonly PluginBridge bridge;
|
||||||
private IScriptExecutor? browserExecutor;
|
private IScriptExecutor? browserExecutor;
|
||||||
|
|
||||||
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
||||||
|
69
lib/TweetLib.Core/Features/Plugins/PluginSchemeHandler.cs
Normal file
69
lib/TweetLib.Core/Features/Plugins/PluginSchemeHandler.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using TweetLib.Core.Browser;
|
||||||
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
|
||||||
|
namespace TweetLib.Core.Features.Plugins{
|
||||||
|
public sealed class PluginSchemeHandler<T> where T : class{
|
||||||
|
public const string Name = "tdp";
|
||||||
|
|
||||||
|
private readonly IResourceProvider<T> resourceProvider;
|
||||||
|
private PluginBridge? bridge = null;
|
||||||
|
|
||||||
|
public PluginSchemeHandler(IResourceProvider<T> resourceProvider){
|
||||||
|
this.resourceProvider = resourceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Setup(PluginManager plugins){
|
||||||
|
if (this.bridge != null){
|
||||||
|
throw new InvalidOperationException("Plugin scheme handler is already setup.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bridge = plugins.bridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? Process(string url){
|
||||||
|
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Scheme != Name || !int.TryParse(uri.Authority, out var identifier)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var segments = uri.Segments.Select(segment => segment.TrimEnd('/')).Where(segment => !string.IsNullOrEmpty(segment)).ToArray();
|
||||||
|
|
||||||
|
if (segments.Length > 0){
|
||||||
|
var handler = segments[0] switch{
|
||||||
|
"root" => DoReadRootFile(identifier, segments),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (handler != null){
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceProvider.Status(HttpStatusCode.BadRequest, "Bad URL path: " + uri.AbsolutePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private T? DoReadRootFile(int identifier, string[] segments){
|
||||||
|
string path = string.Join("/", segments, 1, segments.Length - 1);
|
||||||
|
|
||||||
|
Plugin? plugin = bridge?.GetPluginFromToken(identifier);
|
||||||
|
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(PluginFolder.Root, path);
|
||||||
|
|
||||||
|
if (fullPath.Length == 0){
|
||||||
|
return resourceProvider.Status(HttpStatusCode.Forbidden, "File path has to be relative to the plugin root folder.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
return resourceProvider.File(File.ReadAllBytes(fullPath), Path.GetExtension(path));
|
||||||
|
}catch(FileNotFoundException){
|
||||||
|
return resourceProvider.Status(HttpStatusCode.NotFound, "File not found.");
|
||||||
|
}catch(DirectoryNotFoundException){
|
||||||
|
return resourceProvider.Status(HttpStatusCode.NotFound, "Directory not found.");
|
||||||
|
}catch(Exception e){
|
||||||
|
return resourceProvider.Status(HttpStatusCode.InternalServerError, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user