From fc77c97547c6b3518e28db1f48bbc7908358d40b Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Sun, 15 Jun 2025 12:17:54 +0200 Subject: [PATCH 01/11] feat: add configuration options for instagram and twitter plugins --- CLAUDE.md | 5 ++++ docs/plugins.md | 4 +-- .../templates/channel_plugin_config.html | 20 +++++++++++++++ internal/plugin/social/instagram.go | 21 ++++++++++------ internal/plugin/social/twitter.go | 25 ++++++++++++------- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 35fc410..2191848 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,11 @@ When creating, modifying, or removing plugins: - Brief description of functionality - Usage instructions with examples - Any configuration requirements +3. **For plugins with configuration options:** + - Set `ConfigRequired: true` in the plugin's BasePlugin struct + - Add corresponding HTML form fields in `internal/admin/templates/channel_plugin_config.html` + - Use conditional template logic: `{{else if eq .ChannelPlugin.PluginID "plugin.id"}}` + - Include proper form labels, help text, and value binding ## Testing diff --git a/docs/plugins.md b/docs/plugins.md index d84aec5..3472a08 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -23,5 +23,5 @@ ### Social Media -- Twitter Link Expander: Automatically converts twitter.com and x.com links to fxtwitter.com links and removes tracking parameters. This allows for better media embedding in chat platforms. -- Instagram Link Expander: Automatically converts instagram.com links to ddinstagram.com links and removes tracking parameters. This allows for better media embedding in chat platforms. +- Twitter Link Expander: Automatically converts twitter.com and x.com links to alternative domain links and removes tracking parameters. This allows for better media embedding in chat platforms. Configure with `domain` option to set replacement domain (default: fxtwitter.com). +- Instagram Link Expander: Automatically converts instagram.com links to alternative domain links and removes tracking parameters. This allows for better media embedding in chat platforms. Configure with `domain` option to set replacement domain (default: ddinstagram.com). diff --git a/internal/admin/templates/channel_plugin_config.html b/internal/admin/templates/channel_plugin_config.html index decf1a2..2719004 100644 --- a/internal/admin/templates/channel_plugin_config.html +++ b/internal/admin/templates/channel_plugin_config.html @@ -19,6 +19,26 @@ Messages containing links to these domains will be blocked. + {{else if eq .ChannelPlugin.PluginID "social.instagram"}} +
+ + +
+ Enter the domain to replace instagram.com links with. Default is ddinstagram.com if left empty. +
+
+ {{else if eq .ChannelPlugin.PluginID "social.twitter"}} +
+ + +
+ Enter the domain to replace twitter.com and x.com links with. Default is fxtwitter.com if left empty. +
+
{{else}}
This plugin doesn't have specific configuration fields implemented yet. diff --git a/internal/plugin/social/instagram.go b/internal/plugin/social/instagram.go index 6b7aa4c..b423b45 100644 --- a/internal/plugin/social/instagram.go +++ b/internal/plugin/social/instagram.go @@ -18,9 +18,10 @@ type InstagramExpander struct { func NewInstagramExpander() *InstagramExpander { return &InstagramExpander{ BasePlugin: plugin.BasePlugin{ - ID: "social.instagram", - Name: "Instagram Link Expander", - Help: "Automatically converts instagram.com links to ddinstagram.com links and removes tracking parameters", + ID: "social.instagram", + Name: "Instagram Link Expander", + Help: "Automatically converts instagram.com links to alternative domain links and removes tracking parameters. Configure 'domain' option to set replacement domain (default: ddinstagram.com)", + ConfigRequired: true, }, } } @@ -32,6 +33,12 @@ func (p *InstagramExpander) OnMessage(msg *model.Message, config map[string]inte return nil } + // Get replacement domain from config, default to ddinstagram.com + replacementDomain := "ddinstagram.com" + if domain, ok := config["domain"].(string); ok && domain != "" { + replacementDomain = domain + } + // Regex to match instagram.com links // Match both http://instagram.com and https://instagram.com formats // Also match www.instagram.com @@ -42,7 +49,7 @@ func (p *InstagramExpander) OnMessage(msg *model.Message, config map[string]inte return nil } - // Replace instagram.com with ddinstagram.com in the message and clean query parameters + // Replace instagram.com with configured domain in the message and clean query parameters transformed := instagramRegex.ReplaceAllStringFunc(msg.Text, func(link string) string { // Parse the URL parsedURL, err := url.Parse(link) @@ -51,13 +58,13 @@ func (p *InstagramExpander) OnMessage(msg *model.Message, config map[string]inte return link } - // Ensure we don't change links that already come from ddinstagram.com + // Ensure we don't change links that already come from the replacement domain if parsedURL.Host != "instagram.com" && parsedURL.Host != "www.instagram.com" { return link } - // Change the host - parsedURL.Host = "d.ddinstagram.com" + // Change the host to the configured domain + parsedURL.Host = replacementDomain // Remove query parameters parsedURL.RawQuery = "" diff --git a/internal/plugin/social/twitter.go b/internal/plugin/social/twitter.go index 553bd07..d98eee6 100644 --- a/internal/plugin/social/twitter.go +++ b/internal/plugin/social/twitter.go @@ -18,9 +18,10 @@ type TwitterExpander struct { func NewTwitterExpander() *TwitterExpander { return &TwitterExpander{ BasePlugin: plugin.BasePlugin{ - ID: "social.twitter", - Name: "Twitter Link Expander", - Help: "Automatically converts twitter.com links to fxtwitter.com links and removes tracking parameters", + ID: "social.twitter", + Name: "Twitter Link Expander", + Help: "Automatically converts twitter.com and x.com links to alternative domain links and removes tracking parameters. Configure 'domain' option to set replacement domain (default: fxtwitter.com)", + ConfigRequired: true, }, } } @@ -32,6 +33,12 @@ func (p *TwitterExpander) OnMessage(msg *model.Message, config map[string]interf return nil } + // Get replacement domain from config, default to fxtwitter.com + replacementDomain := "fxtwitter.com" + if domain, ok := config["domain"].(string); ok && domain != "" { + replacementDomain = domain + } + // Regex to match twitter.com links // Match both http://twitter.com and https://twitter.com formats // Also match www.twitter.com @@ -42,22 +49,22 @@ func (p *TwitterExpander) OnMessage(msg *model.Message, config map[string]interf return nil } - // Replace twitter.com with fxtwitter.com in the message and clean query parameters + // Replace twitter.com/x.com with configured domain in the message and clean query parameters transformed := twitterRegex.ReplaceAllStringFunc(msg.Text, func(link string) string { // Parse the URL parsedURL, err := url.Parse(link) if err != nil { // If parsing fails, just do the simple replacement - link = strings.Replace(link, "twitter.com", "fxtwitter.com", 1) - link = strings.Replace(link, "x.com", "fxtwitter.com", 1) + link = strings.Replace(link, "twitter.com", replacementDomain, 1) + link = strings.Replace(link, "x.com", replacementDomain, 1) return link } - // Change the host + // Change the host to the configured domain if strings.Contains(parsedURL.Host, "twitter.com") { - parsedURL.Host = strings.Replace(parsedURL.Host, "twitter.com", "fxtwitter.com", 1) + parsedURL.Host = strings.Replace(parsedURL.Host, "twitter.com", replacementDomain, 1) } else if strings.Contains(parsedURL.Host, "x.com") { - parsedURL.Host = strings.Replace(parsedURL.Host, "x.com", "fxtwitter.com", 1) + parsedURL.Host = strings.Replace(parsedURL.Host, "x.com", replacementDomain, 1) } // Remove query parameters From 899ac49336e958d98500ad8c65f45d0bc602883f Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Sun, 15 Jun 2025 13:03:52 +0200 Subject: [PATCH 02/11] chore: split plugin configuration templates --- internal/admin/admin.go | 26 ++++++++++++++-- .../templates/channel_plugin_config.html | 31 ++----------------- .../plugins/security.domainblock.html | 12 +++++++ .../templates/plugins/social.instagram.html | 11 +++++++ .../templates/plugins/social.twitter.html | 11 +++++++ 5 files changed, 60 insertions(+), 31 deletions(-) create mode 100644 internal/admin/templates/plugins/security.domainblock.html create mode 100644 internal/admin/templates/plugins/social.instagram.html create mode 100644 internal/admin/templates/plugins/social.twitter.html diff --git a/internal/admin/admin.go b/internal/admin/admin.go index 2b41820..cfa2595 100644 --- a/internal/admin/admin.go +++ b/internal/admin/admin.go @@ -16,7 +16,7 @@ import ( "github.com/gorilla/sessions" ) -//go:embed templates/*.html +//go:embed templates/*.html templates/plugins/*.html var templateFS embed.FS const ( @@ -90,7 +90,7 @@ func New(cfg *config.Config, database *db.Database, version string) *Admin { } // Parse and register all templates - templateFiles := []string{ + mainTemplateFiles := []string{ "index.html", "login.html", "change_password.html", @@ -101,7 +101,13 @@ func New(cfg *config.Config, database *db.Database, version string) *Admin { "channel_plugin_config.html", } - for _, tf := range templateFiles { + pluginTemplateFiles := []string{ + "plugins/security.domainblock.html", + "plugins/social.instagram.html", + "plugins/social.twitter.html", + } + + for _, tf := range mainTemplateFiles { // Read template content from embedded filesystem content, err := templateFS.ReadFile("templates/" + tf) if err != nil { @@ -120,6 +126,20 @@ func New(cfg *config.Config, database *db.Database, version string) *Admin { panic(err) } + // If this is the channel_plugin_config template, also parse plugin templates + if tf == "channel_plugin_config.html" { + for _, pluginTf := range pluginTemplateFiles { + pluginContent, err := templateFS.ReadFile("templates/" + pluginTf) + if err != nil { + panic(err) + } + t, err = t.Parse(string(pluginContent)) + if err != nil { + panic(err) + } + } + } + templates[tf] = t } diff --git a/internal/admin/templates/channel_plugin_config.html b/internal/admin/templates/channel_plugin_config.html index 2719004..0f229f9 100644 --- a/internal/admin/templates/channel_plugin_config.html +++ b/internal/admin/templates/channel_plugin_config.html @@ -9,36 +9,11 @@
{{if eq .ChannelPlugin.PluginID "security.domainblock"}} -
- - -
- Enter comma-separated list of domains to block (e.g., example.com, evil.org). - Messages containing links to these domains will be blocked. -
-
+ {{template "plugins/security.domainblock.html" .}} {{else if eq .ChannelPlugin.PluginID "social.instagram"}} -
- - -
- Enter the domain to replace instagram.com links with. Default is ddinstagram.com if left empty. -
-
+ {{template "plugins/social.instagram.html" .}} {{else if eq .ChannelPlugin.PluginID "social.twitter"}} -
- - -
- Enter the domain to replace twitter.com and x.com links with. Default is fxtwitter.com if left empty. -
-
+ {{template "plugins/social.twitter.html" .}} {{else}}
This plugin doesn't have specific configuration fields implemented yet. diff --git a/internal/admin/templates/plugins/security.domainblock.html b/internal/admin/templates/plugins/security.domainblock.html new file mode 100644 index 0000000..7ffcc48 --- /dev/null +++ b/internal/admin/templates/plugins/security.domainblock.html @@ -0,0 +1,12 @@ +{{define "plugins/security.domainblock.html"}} +
+ + +
+ Enter comma-separated list of domains to block (e.g., example.com, evil.org). + Messages containing links to these domains will be blocked. +
+
+{{end}} \ No newline at end of file diff --git a/internal/admin/templates/plugins/social.instagram.html b/internal/admin/templates/plugins/social.instagram.html new file mode 100644 index 0000000..a83485d --- /dev/null +++ b/internal/admin/templates/plugins/social.instagram.html @@ -0,0 +1,11 @@ +{{define "plugins/social.instagram.html"}} +
+ + +
+ Enter the domain to replace instagram.com links with. Default is ddinstagram.com if left empty. +
+
+{{end}} \ No newline at end of file diff --git a/internal/admin/templates/plugins/social.twitter.html b/internal/admin/templates/plugins/social.twitter.html new file mode 100644 index 0000000..cb4885f --- /dev/null +++ b/internal/admin/templates/plugins/social.twitter.html @@ -0,0 +1,11 @@ +{{define "plugins/social.twitter.html"}} +
+ + +
+ Enter the domain to replace twitter.com and x.com links with. Default is fxtwitter.com if left empty. +
+
+{{end}} \ No newline at end of file From 3b09a9dd4783f18ab3b416e2357f1010c9423850 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:06:17 +0200 Subject: [PATCH 03/11] feat: allow enabling all plugins into a channel --- internal/admin/admin.go | 7 + internal/admin/templates/channel_detail.html | 9 + internal/app/app.go | 28 +- internal/db/db.go | 43 ++- internal/db/db_test.go | 203 ++++++++++++ internal/migration/migrations.go | 58 ++++ internal/model/message.go | 6 + internal/model/message_test.go | 234 +++++++++++++ internal/plugin/plugin.go | 13 + internal/plugin/plugin_test.go | 331 +++++++++++++++++++ 10 files changed, 915 insertions(+), 17 deletions(-) create mode 100644 internal/db/db_test.go create mode 100644 internal/model/message_test.go create mode 100644 internal/plugin/plugin_test.go diff --git a/internal/admin/admin.go b/internal/admin/admin.go index cfa2595..abefb72 100644 --- a/internal/admin/admin.go +++ b/internal/admin/admin.go @@ -564,6 +564,13 @@ func (a *Admin) handleChannelDetail(w http.ResponseWriter, r *http.Request) { return } + // Update enable_all_plugins + enableAllPlugins := r.FormValue("enable_all_plugins") == "true" + if err := a.db.UpdateChannelEnableAllPlugins(id, enableAllPlugins); err != nil { + http.Error(w, "Failed to update channel enable all plugins", http.StatusInternalServerError) + return + } + a.addFlash(w, r, "Channel updated", "success") http.Redirect(w, r, "/admin/channels/"+channelID, http.StatusSeeOther) return diff --git a/internal/admin/templates/channel_detail.html b/internal/admin/templates/channel_detail.html index 78909df..9f9a78d 100644 --- a/internal/admin/templates/channel_detail.html +++ b/internal/admin/templates/channel_detail.html @@ -27,6 +27,15 @@
+
+ +
+ When enabled, all registered plugins will be automatically enabled for this channel. Individual plugin settings will be ignored. +
+
-{{end}} \ No newline at end of file +{{end}} From 377b1723c31975c9cb43587a949c2014e2ae81ae Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Tue, 24 Jun 2025 08:10:56 +0200 Subject: [PATCH 11/11] fix: default parse mode to text --- internal/platform/telegram/telegram.go | 6 +++--- internal/plugin/social/twitter.go | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/platform/telegram/telegram.go b/internal/platform/telegram/telegram.go index d35bb6b..b015793 100644 --- a/internal/platform/telegram/telegram.go +++ b/internal/platform/telegram/telegram.go @@ -237,13 +237,13 @@ func (t *TelegramPlatform) SendMessage(msg *model.Message) error { "text": msg.Text, } - // Set parse_mode based on plugin preference or default to Markdown + // Set parse_mode based on plugin preference or default to empty string if msg.Raw != nil && msg.Raw["parse_mode"] != nil { // Plugin explicitly set parse_mode payload["parse_mode"] = msg.Raw["parse_mode"] } else { - // Default to Markdown for backward compatibility - payload["parse_mode"] = "Markdown" + // Default to empty string (no formatting) + payload["parse_mode"] = "" } // Add reply if needed diff --git a/internal/plugin/social/twitter.go b/internal/plugin/social/twitter.go index 69bd979..f2c6cc9 100644 --- a/internal/plugin/social/twitter.go +++ b/internal/plugin/social/twitter.go @@ -75,7 +75,6 @@ func (p *TwitterExpander) OnMessage(msg *model.Message, config map[string]interf Chat: msg.Chat, ReplyTo: msg.ID, Channel: msg.Channel, - Raw: map[string]interface{}{"parse_mode": ""}, } action := &model.MessageAction{