Feature parity with v0
363
v1/app.go
|
@ -7,9 +7,11 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/tylertravisty/rum-goggles/v1/internal/chatbot"
|
||||||
"github.com/tylertravisty/rum-goggles/v1/internal/config"
|
"github.com/tylertravisty/rum-goggles/v1/internal/config"
|
||||||
"github.com/tylertravisty/rum-goggles/v1/internal/events"
|
"github.com/tylertravisty/rum-goggles/v1/internal/events"
|
||||||
"github.com/tylertravisty/rum-goggles/v1/internal/models"
|
"github.com/tylertravisty/rum-goggles/v1/internal/models"
|
||||||
|
@ -47,6 +49,7 @@ func (p *Page) staticLiveStreamUrl() string {
|
||||||
// App struct
|
// App struct
|
||||||
type App struct {
|
type App struct {
|
||||||
cancelProc context.CancelFunc
|
cancelProc context.CancelFunc
|
||||||
|
chatbot *chatbot.Chatbot
|
||||||
clients map[string]*rumblelivestreamlib.Client
|
clients map[string]*rumblelivestreamlib.Client
|
||||||
clientsMu sync.Mutex
|
clientsMu sync.Mutex
|
||||||
displaying string
|
displaying string
|
||||||
|
@ -101,24 +104,34 @@ func (a *App) process(ctx context.Context) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case apiE := <-a.producers.ApiP.Ch:
|
case apiE := <-a.producers.ApiP.Ch:
|
||||||
err := a.processApi(apiE)
|
a.processApi(apiE)
|
||||||
if err != nil {
|
|
||||||
a.logError.Println("error handling API event:", err)
|
|
||||||
}
|
|
||||||
case chatE := <-a.producers.ChatP.Ch:
|
case chatE := <-a.producers.ChatP.Ch:
|
||||||
err := a.processChat(chatE)
|
a.processChat(chatE)
|
||||||
if err != nil {
|
|
||||||
a.logError.Println("error handling chat event:", err)
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) processApi(event events.Api) error {
|
type apiProcessor func(event events.Api)
|
||||||
|
|
||||||
|
func (a *App) runApiProcessors(event events.Api, procs ...apiProcessor) {
|
||||||
|
for _, proc := range procs {
|
||||||
|
proc(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) processApi(event events.Api) {
|
||||||
|
a.runApiProcessors(
|
||||||
|
event,
|
||||||
|
a.pageApiProcessor,
|
||||||
|
a.chatbotApiProcessor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) pageApiProcessor(event events.Api) {
|
||||||
if event.Name == "" {
|
if event.Name == "" {
|
||||||
return fmt.Errorf("event name is empty")
|
a.logError.Println("page cannot process API: event name is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
a.pagesMu.Lock()
|
a.pagesMu.Lock()
|
||||||
|
@ -138,7 +151,7 @@ func (a *App) processApi(event events.Api) error {
|
||||||
|
|
||||||
if event.Stop {
|
if event.Stop {
|
||||||
runtime.EventsEmit(a.wails, "ApiActive-"+page.name, false)
|
runtime.EventsEmit(a.wails, "ApiActive-"+page.name, false)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.EventsEmit(a.wails, "ApiActive-"+page.name, true)
|
runtime.EventsEmit(a.wails, "ApiActive-"+page.name, true)
|
||||||
|
@ -153,12 +166,39 @@ func (a *App) processApi(event events.Api) error {
|
||||||
page.apiSt.respMu.Unlock()
|
page.apiSt.respMu.Unlock()
|
||||||
|
|
||||||
a.updatePage(page)
|
a.updatePage(page)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) processChat(event events.Chat) error {
|
type chatProcessor func(event events.Chat)
|
||||||
return nil
|
|
||||||
|
func (a *App) runChatProcessors(event events.Chat, procs ...chatProcessor) {
|
||||||
|
for _, proc := range procs {
|
||||||
|
proc(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) processChat(event events.Chat) {
|
||||||
|
if event.Stop {
|
||||||
|
runtime.EventsEmit(a.wails, "ChatStreamActive-"+event.Url, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.runChatProcessors(
|
||||||
|
event,
|
||||||
|
a.chatbotChatProcessor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement this
|
||||||
|
func (a *App) chatbotApiProcessor(event events.Api) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) chatbotChatProcessor(event events.Chat) {
|
||||||
|
if event.Message.Type == rumblelivestreamlib.ChatTypeInit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.chatbot.HandleChat(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) shutdown(ctx context.Context) {
|
func (a *App) shutdown(ctx context.Context) {
|
||||||
|
@ -212,6 +252,14 @@ func (a *App) Start() (bool, error) {
|
||||||
}
|
}
|
||||||
runtime.EventsEmit(a.wails, "StartupMessage", "Initializing event producers complete.")
|
runtime.EventsEmit(a.wails, "StartupMessage", "Initializing event producers complete.")
|
||||||
|
|
||||||
|
runtime.EventsEmit(a.wails, "StartupMessage", "Initializing chat bot...")
|
||||||
|
err = a.initChatbot()
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error initializing chat bot:", err)
|
||||||
|
return false, fmt.Errorf("Error starting Rum Goggles. Try restarting.")
|
||||||
|
}
|
||||||
|
runtime.EventsEmit(a.wails, "StartupMessage", "Initializing chat bot complete.")
|
||||||
|
|
||||||
// TODO: check for update - if available, pop up window
|
// TODO: check for update - if available, pop up window
|
||||||
// runtime.EventsEmit(a.ctx, "StartupMessage", "Checking for updates...")
|
// runtime.EventsEmit(a.ctx, "StartupMessage", "Checking for updates...")
|
||||||
// update, err = a.checkForUpdate()
|
// update, err = a.checkForUpdate()
|
||||||
|
@ -229,6 +277,13 @@ func (a *App) Start() (bool, error) {
|
||||||
return signin, nil
|
return signin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) initChatbot() error {
|
||||||
|
cb := chatbot.New(a.services.AccountS, a.services.ChatbotS, a.logError, a.wails)
|
||||||
|
a.chatbot = cb
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) initProducers() error {
|
func (a *App) initProducers() error {
|
||||||
producers, err := events.NewProducers(
|
producers, err := events.NewProducers(
|
||||||
events.WithLoggers(a.logError, a.logInfo),
|
events.WithLoggers(a.logError, a.logInfo),
|
||||||
|
@ -261,6 +316,7 @@ func (a *App) initServices() error {
|
||||||
models.WithChannelService(),
|
models.WithChannelService(),
|
||||||
models.WithAccountChannelService(),
|
models.WithAccountChannelService(),
|
||||||
models.WithChatbotService(),
|
models.WithChatbotService(),
|
||||||
|
models.WithChatbotRuleService(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error initializing services: %v", err)
|
return fmt.Errorf("error initializing services: %v", err)
|
||||||
|
@ -1076,6 +1132,12 @@ func (a *App) DeleteChatbot(chatbot *models.Chatbot) error {
|
||||||
return fmt.Errorf("Invalid chatbot. Try again.")
|
return fmt.Errorf("Invalid chatbot. Try again.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := a.StopChatbotRules(chatbot.ID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error stopping chatbot rules before deleting chatbot")
|
||||||
|
return fmt.Errorf("Error deleting chatbot. Could not stop running rules. Try Again.")
|
||||||
|
}
|
||||||
|
|
||||||
cb, err := a.services.ChatbotS.ByID(*chatbot.ID)
|
cb, err := a.services.ChatbotS.ByID(*chatbot.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logError.Println("error getting chatbot by ID:", err)
|
a.logError.Println("error getting chatbot by ID:", err)
|
||||||
|
@ -1085,6 +1147,20 @@ func (a *App) DeleteChatbot(chatbot *models.Chatbot) error {
|
||||||
return fmt.Errorf("Chatbot does not exist.")
|
return fmt.Errorf("Chatbot does not exist.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rules, err := a.services.ChatbotRuleS.ByChatbotID(*chatbot.ID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error getting chatbot rules by chatbot ID:", err)
|
||||||
|
return fmt.Errorf("Error deleting chatbot. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range rules {
|
||||||
|
err = a.services.ChatbotRuleS.Delete(&rule)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error deleting chatbot rule:", err)
|
||||||
|
return fmt.Errorf("Error deleting chatbot. Try again.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = a.services.ChatbotS.Delete(chatbot)
|
err = a.services.ChatbotS.Delete(chatbot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logError.Println("error deleting chatbot:", err)
|
a.logError.Println("error deleting chatbot:", err)
|
||||||
|
@ -1189,60 +1265,241 @@ func (a *App) chatbotList() ([]models.Chatbot, error) {
|
||||||
return list, err
|
return list, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRule struct {
|
func (a *App) ChatbotRules(chatbot *models.Chatbot) ([]chatbot.Rule, error) {
|
||||||
Message *ChatbotRuleMessage `json:"message"`
|
if chatbot == nil || chatbot.ID == nil {
|
||||||
SendAs *ChatbotRuleSender `json:"send_as"`
|
return nil, fmt.Errorf("Invalid chatbot. Try again.")
|
||||||
Trigger *ChatbotRuleTrigger `json:"trigger"`
|
}
|
||||||
|
|
||||||
|
rules, err := a.chatbotRules(*chatbot.ID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error getting chatbot rules:", err)
|
||||||
|
return nil, fmt.Errorf("Error getting chatbot rules. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleMessage struct {
|
func (a *App) chatbotRules(chatbotID int64) ([]chatbot.Rule, error) {
|
||||||
FromFile *ChatbotRuleMessageFile `json:"from_file"`
|
modelsRules, err := a.services.ChatbotRuleS.ByChatbotID(chatbotID)
|
||||||
FromText string `json:"from_text"`
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error querying chatbot rules: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rules := []chatbot.Rule{}
|
||||||
|
for _, modelsRule := range modelsRules {
|
||||||
|
rule := chatbot.Rule{
|
||||||
|
ID: modelsRule.ID,
|
||||||
|
ChatbotID: modelsRule.ChatbotID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if modelsRule.Parameters != nil {
|
||||||
|
var params chatbot.RuleParameters
|
||||||
|
err = json.Unmarshal([]byte(*modelsRule.Parameters), ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error un-marshaling chatbot rule parameters from json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.Parameters = ¶ms
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.Running = a.chatbot.Running(*rule.ChatbotID, *rule.ID)
|
||||||
|
|
||||||
|
rule.Display = rule.Parameters.Message.FromText
|
||||||
|
if rule.Parameters.Message.FromFile != nil {
|
||||||
|
rule.Display = filepath.Base(rule.Parameters.Message.FromFile.Filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatbot.SortRules(rules)
|
||||||
|
|
||||||
|
return rules, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleMessageFile struct {
|
func (a *App) DeleteChatbotRule(rule *chatbot.Rule) error {
|
||||||
Filepath string `json:"filepath"`
|
if rule == nil || rule.ID == nil || rule.ChatbotID == nil {
|
||||||
RandomRead bool `json:"random_read"`
|
return fmt.Errorf("Invalid chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
mRule, err := rule.ToModelsChatbotRule()
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error converting chatbot.Rule into models.ChatbotRule:", err)
|
||||||
|
return fmt.Errorf("Error deleting chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.chatbot.Stop(rule)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error stopping chatbot rule:", err)
|
||||||
|
return fmt.Errorf("Error deleting chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.services.ChatbotRuleS.Delete(mRule)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error deleting chatbot rule:", err)
|
||||||
|
return fmt.Errorf("Error deleting chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, err := a.chatbotRules(*rule.ChatbotID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error getting chatbot rules:", err)
|
||||||
|
return fmt.Errorf("Error deleting chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
runtime.EventsEmit(a.wails, "ChatbotRules", rules)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleSender struct {
|
func (a *App) NewChatbotRule(rule *chatbot.Rule) error {
|
||||||
Username string `json:"username"`
|
if rule == nil || rule.ChatbotID == nil || rule.Parameters == nil {
|
||||||
ChannelID *int `json:"channel_id"`
|
return fmt.Errorf("Invalid chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
mRule, err := rule.ToModelsChatbotRule()
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error converting chatbot.Rule into models.ChatbotRule:", err)
|
||||||
|
return fmt.Errorf("Error creating chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = a.services.ChatbotRuleS.Create(mRule)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error creating chatbot rule:", err)
|
||||||
|
return fmt.Errorf("Error creating chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, err := a.chatbotRules(*rule.ChatbotID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error getting chatbot rules:", err)
|
||||||
|
return fmt.Errorf("Error creating chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
runtime.EventsEmit(a.wails, "ChatbotRules", rules)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleTrigger struct {
|
func (a *App) RunChatbotRule(rule *chatbot.Rule) error {
|
||||||
OnCommand *ChatbotRuleTriggerCommand `json:"on_command"`
|
if rule == nil || rule.ChatbotID == nil {
|
||||||
OnEvent *ChatbotRuleTriggerEvent `json:"on_event"`
|
return fmt.Errorf("Invalid chatbot rule. Try again.")
|
||||||
OnTimer *time.Duration `json:"on_timer"`
|
}
|
||||||
|
|
||||||
|
mChatbot, err := a.services.ChatbotS.ByID(*rule.ChatbotID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error getting chatbot by ID:", err)
|
||||||
|
return fmt.Errorf("Error running chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
if mChatbot == nil {
|
||||||
|
return fmt.Errorf("Chatbot does not exist. Try again.")
|
||||||
|
}
|
||||||
|
if mChatbot.Url == nil {
|
||||||
|
a.logError.Println("chatbot url is nil")
|
||||||
|
return fmt.Errorf("Chatbot url is not set. Update url and try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = a.producers.ChatP.Start(*mChatbot.Url)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error starting chat producer:", err)
|
||||||
|
// TODO: send error to UI that chatbot URL could not be started
|
||||||
|
//runtime.EventsEmit("Ch")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.chatbot.Run(rule, *mChatbot.Url)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error running chat bot rule:", err)
|
||||||
|
return fmt.Errorf("Error running chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleTriggerCommand struct {
|
func (a *App) StopChatbotRule(rule *chatbot.Rule) error {
|
||||||
Command string `json:"command"`
|
err := a.chatbot.Stop(rule)
|
||||||
Restrict *ChatbotRuleTriggerCommandRestriction `json:"restrict"`
|
if err != nil {
|
||||||
Timeout time.Duration `json:"timeout"`
|
a.logError.Println("error stopping chat bot rule:", err)
|
||||||
|
return fmt.Errorf("Error stopping chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleTriggerCommandRestriction struct {
|
func (a *App) UpdateChatbotRule(rule *chatbot.Rule) error {
|
||||||
Bypass *ChatbotRuleTriggerCommandRestrictionBypass `json:"bypass"`
|
if rule == nil || rule.ID == nil || rule.ChatbotID == nil {
|
||||||
ToAdmin bool `json:"to_admin"`
|
return fmt.Errorf("Invalid chatbot rule. Try again.")
|
||||||
ToFollower bool `json:"to_follower"`
|
}
|
||||||
ToMod bool `json:"to_mod"`
|
|
||||||
ToStreamer bool `json:"to_streamer"`
|
mRule, err := rule.ToModelsChatbotRule()
|
||||||
ToSubscriber bool `json:"to_subscriber"`
|
if err != nil {
|
||||||
ToRant int `json:"to_rant"`
|
a.logError.Println("error converting chatbot.Rule into models.ChatbotRule:", err)
|
||||||
|
return fmt.Errorf("Error updating chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.chatbot.Stop(rule)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error stopping chatbot rule:", err)
|
||||||
|
return fmt.Errorf("Error updating chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.services.ChatbotRuleS.Update(mRule)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error updating chatbot rule:", err)
|
||||||
|
return fmt.Errorf("Error updating chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, err := a.chatbotRules(*rule.ChatbotID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error getting chatbot rules:", err)
|
||||||
|
return fmt.Errorf("Error updating chatbot rule. Try again.")
|
||||||
|
}
|
||||||
|
runtime.EventsEmit(a.wails, "ChatbotRules", rules)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleTriggerCommandRestrictionBypass struct {
|
func (a *App) RunChatbotRules(chatbotID *int64) error {
|
||||||
IfAdmin bool `json:"if_admin"`
|
if chatbotID == nil {
|
||||||
IfMod bool `json:"if_mod"`
|
return fmt.Errorf("Invalid chatbot. Try again.")
|
||||||
IfStreamer bool `json:"if_streamer"`
|
}
|
||||||
|
|
||||||
|
rules, err := a.chatbotRules(*chatbotID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error getting chatbot rules:", err)
|
||||||
|
return fmt.Errorf("Error running chatbot rules. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var errored bool
|
||||||
|
for _, rule := range rules {
|
||||||
|
if err = a.RunChatbotRule(&rule); err != nil {
|
||||||
|
errored = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errored {
|
||||||
|
return fmt.Errorf("An error occurred while running rules. Check error log for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleTriggerEvent struct {
|
func (a *App) StopChatbotRules(chatbotID *int64) error {
|
||||||
OnFollow bool `json:"on_follow"`
|
if chatbotID == nil {
|
||||||
OnSubscribe bool `json:"on_subscribe"`
|
return fmt.Errorf("Invalid chatbot. Try again.")
|
||||||
OnRaid bool `json:"on_raid"`
|
}
|
||||||
OnRant int `json:"on_rant"`
|
|
||||||
|
rules, err := a.chatbotRules(*chatbotID)
|
||||||
|
if err != nil {
|
||||||
|
a.logError.Println("error getting chatbot rules:", err)
|
||||||
|
return fmt.Errorf("Error stopping chatbot rules. Try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var errored bool
|
||||||
|
for _, rule := range rules {
|
||||||
|
if err = a.StopChatbotRule(&rule); err != nil {
|
||||||
|
errored = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errored {
|
||||||
|
return fmt.Errorf("An error occurred while stopping rules. Check error log for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) OpenFileDialog() (string, error) {
|
func (a *App) OpenFileDialog() (string, error) {
|
||||||
|
|
BIN
v1/frontend/src/assets/icons/twbs/gear-fill-white.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
v1/frontend/src/assets/icons/twbs/play-fill-green.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
v1/frontend/src/assets/icons/twbs/stop-fill-red.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
|
@ -7,12 +7,17 @@ import eye from './icons/twbs/eye.png';
|
||||||
import eye_red from './icons/twbs/eye-red.png';
|
import eye_red from './icons/twbs/eye-red.png';
|
||||||
import eye_slash from './icons/twbs/eye-slash.png';
|
import eye_slash from './icons/twbs/eye-slash.png';
|
||||||
import gear_fill from './icons/twbs/gear-fill.png';
|
import gear_fill from './icons/twbs/gear-fill.png';
|
||||||
|
import gear_fill_white from './icons/twbs/gear-fill-white.png';
|
||||||
import heart from './icons/twbs/heart-fill.png';
|
import heart from './icons/twbs/heart-fill.png';
|
||||||
import pause from './icons/twbs/pause-circle-green.png';
|
import pause from './icons/twbs/pause-circle-green.png';
|
||||||
|
import pause_big from './icons/twbs/pause-fill.png';
|
||||||
import play from './icons/twbs/play-circle-green.png';
|
import play from './icons/twbs/play-circle-green.png';
|
||||||
|
import play_big from './icons/twbs/play-fill.png';
|
||||||
|
import play_big_green from './icons/twbs/play-fill-green.png';
|
||||||
import plus_circle from './icons/twbs/plus-circle-fill.png';
|
import plus_circle from './icons/twbs/plus-circle-fill.png';
|
||||||
import robot from './icons/Font-Awesome/robot.png';
|
import robot from './icons/Font-Awesome/robot.png';
|
||||||
import star from './icons/twbs/star-fill.png';
|
import star from './icons/twbs/star-fill.png';
|
||||||
|
import stop_big_red from './icons/twbs/stop-fill-red.png';
|
||||||
import thumbs_down from './icons/twbs/hand-thumbs-down-fill.png';
|
import thumbs_down from './icons/twbs/hand-thumbs-down-fill.png';
|
||||||
import thumbs_up from './icons/twbs/hand-thumbs-up-fill.png';
|
import thumbs_up from './icons/twbs/hand-thumbs-up-fill.png';
|
||||||
import x_lg from './icons/twbs/x-lg.png';
|
import x_lg from './icons/twbs/x-lg.png';
|
||||||
|
@ -27,13 +32,18 @@ export const Eye = eye;
|
||||||
export const EyeRed = eye_red;
|
export const EyeRed = eye_red;
|
||||||
export const EyeSlash = eye_slash;
|
export const EyeSlash = eye_slash;
|
||||||
export const Gear = gear_fill;
|
export const Gear = gear_fill;
|
||||||
|
export const GearWhite = gear_fill_white;
|
||||||
export const Heart = heart;
|
export const Heart = heart;
|
||||||
export const Logo = logo;
|
export const Logo = logo;
|
||||||
export const Pause = pause;
|
export const Pause = pause;
|
||||||
|
export const PauseBig = pause_big;
|
||||||
export const Play = play;
|
export const Play = play;
|
||||||
|
export const PlayBig = play_big;
|
||||||
|
export const PlayBigGreen = play_big_green;
|
||||||
export const PlusCircle = plus_circle;
|
export const PlusCircle = plus_circle;
|
||||||
export const Robot = robot;
|
export const Robot = robot;
|
||||||
export const Star = star;
|
export const Star = star;
|
||||||
|
export const StopBigRed = stop_big_red;
|
||||||
export const ThumbsDown = thumbs_down;
|
export const ThumbsDown = thumbs_down;
|
||||||
export const ThumbsUp = thumbs_up;
|
export const ThumbsUp = thumbs_up;
|
||||||
export const XLg = x_lg;
|
export const XLg = x_lg;
|
|
@ -84,10 +84,10 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0px 10px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatbot-list-button {
|
.chatbot-list-item-button {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #344453;
|
background-color: #344453;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatbot-list-button:hover {
|
.chatbot-list-item-button:hover {
|
||||||
background-color: #415568;
|
background-color: #415568;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,7 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
padding: 0px 10px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
/* width: 100%; */
|
/* width: 100%; */
|
||||||
|
@ -221,6 +222,16 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatbot-modal-review {
|
||||||
|
color: #eee;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 350px;
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: scroll;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.chatbot-modal-setting {
|
.chatbot-modal-setting {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -286,6 +297,88 @@
|
||||||
transition: .4s;
|
transition: .4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatbot-rules {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
.chatbot-rule {
|
||||||
|
border-bottom: 1px solid #1f2e3c;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-family: sans-serif;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-rule-header {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-rule-output {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-x: scroll;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-rule-buttons {
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
padding-left: 10px;
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-rule-button {
|
||||||
|
align-items: center;
|
||||||
|
background-color: #344453;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-rule-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-rule-button-icon {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-rule-sender {
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
overflow-x: scroll;
|
||||||
|
padding-left: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-rule-trigger {
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
overflow-x: scroll;
|
||||||
|
padding-left: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
.choose-file {
|
.choose-file {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -338,12 +431,42 @@ input:checked + .chatbot-modal-toggle-slider:before {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.command-input {
|
||||||
|
border: none;
|
||||||
|
border-radius: 34px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-rant-amount {
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-rant-amount-symbol {
|
||||||
|
color: #eee;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 20px;
|
||||||
|
padding-right: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.timer-input {
|
.timer-input {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 34px;
|
border-radius: 34px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 24px;
|
font-size: 16px;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 5px 10px 5px 10px;
|
padding: 5px 10px 5px 10px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
|
@ -200,6 +200,7 @@
|
||||||
.small-modal-message {
|
.small-modal-message {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small-modal-title {
|
.small-modal-title {
|
||||||
|
|
|
@ -6,8 +6,8 @@ toolchain go1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.22
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
github.com/tylertravisty/rumble-livestream-lib-go v0.5.1
|
github.com/tylertravisty/rumble-livestream-lib-go v0.7.2
|
||||||
github.com/wailsapp/wails/v2 v2.8.0
|
github.com/wailsapp/wails/v2 v2.8.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
|
@ -60,8 +60,8 @@ github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQ
|
||||||
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||||
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g=
|
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g=
|
||||||
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk=
|
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk=
|
||||||
github.com/tylertravisty/rumble-livestream-lib-go v0.5.1 h1:vq65n/8MOvvg6tHiaHFFfYf25w7yuR1viSoBCjY2DSg=
|
github.com/tylertravisty/rumble-livestream-lib-go v0.7.2 h1:TRGTKhxB+uK0gnIC+rXbRxfFjMJxPHhjZzbsjDSpK+o=
|
||||||
github.com/tylertravisty/rumble-livestream-lib-go v0.5.1/go.mod h1:Odkqvsn+2eoWV3ePcj257Ga0bdOqV4JBTfOJcQ+Sqf8=
|
github.com/tylertravisty/rumble-livestream-lib-go v0.7.2/go.mod h1:Odkqvsn+2eoWV3ePcj257Ga0bdOqV4JBTfOJcQ+Sqf8=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
@ -71,8 +71,8 @@ github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhy
|
||||||
github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
|
github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
|
||||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||||
github.com/wailsapp/wails/v2 v2.8.0 h1:b2NNn99uGPiN6P5bDsnPwOJZWtAOUhNLv7Vl+YxMTr4=
|
github.com/wailsapp/wails/v2 v2.8.1 h1:KAudNjlFaiXnDfFEfSNoLoibJ1ovoutSrJ8poerTPW0=
|
||||||
github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0=
|
github.com/wailsapp/wails/v2 v2.8.1/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
|
|
447
v1/internal/chatbot/chatbot.go
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
package chatbot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tylertravisty/rum-goggles/v1/internal/events"
|
||||||
|
"github.com/tylertravisty/rum-goggles/v1/internal/models"
|
||||||
|
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type user struct {
|
||||||
|
livestreams map[string]*rumblelivestreamlib.Client
|
||||||
|
livestreamsMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *user) byLivestream(url string) *rumblelivestreamlib.Client {
|
||||||
|
client, _ := u.livestreams[url]
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
type clients map[string]*user
|
||||||
|
|
||||||
|
func (c clients) byUsername(username string) *user {
|
||||||
|
user, _ := c[username]
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clients) byUsernameLivestream(username string, url string) *rumblelivestreamlib.Client {
|
||||||
|
user := c.byUsername(username)
|
||||||
|
if user == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.byLivestream(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
type receiver struct {
|
||||||
|
onCommand map[string]map[int64]chan events.Chat
|
||||||
|
onCommandMu sync.Mutex
|
||||||
|
//onFollow []chan ???
|
||||||
|
//onRant []chan events.Chat
|
||||||
|
//onSubscribe []chan events.Chat
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bot struct {
|
||||||
|
runners map[int64]*Runner
|
||||||
|
runnersMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type Chatbot struct {
|
||||||
|
accountS models.AccountService
|
||||||
|
bots map[int64]*Bot
|
||||||
|
botsMu sync.Mutex
|
||||||
|
chatbotS models.ChatbotService
|
||||||
|
clients clients
|
||||||
|
clientsMu sync.Mutex
|
||||||
|
logError *log.Logger
|
||||||
|
receivers map[string]*receiver
|
||||||
|
receiversMu sync.Mutex
|
||||||
|
//runners map[int64]*Runner
|
||||||
|
// runnersMu sync.Mutex
|
||||||
|
wails context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(accountS models.AccountService, chatbotS models.ChatbotService, logError *log.Logger, wails context.Context) *Chatbot {
|
||||||
|
return &Chatbot{
|
||||||
|
accountS: accountS,
|
||||||
|
bots: map[int64]*Bot{},
|
||||||
|
chatbotS: chatbotS,
|
||||||
|
clients: map[string]*user{},
|
||||||
|
logError: logError,
|
||||||
|
receivers: map[string]*receiver{},
|
||||||
|
// runners: map[int64]*Runner{},
|
||||||
|
wails: wails,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resetClient/updateClient
|
||||||
|
func (cb *Chatbot) addClient(username string, livestreamUrl string) (*rumblelivestreamlib.Client, error) {
|
||||||
|
cb.clientsMu.Lock()
|
||||||
|
defer cb.clientsMu.Unlock()
|
||||||
|
|
||||||
|
u := cb.clients.byUsername(username)
|
||||||
|
if u == nil {
|
||||||
|
u = &user{
|
||||||
|
livestreams: map[string]*rumblelivestreamlib.Client{},
|
||||||
|
}
|
||||||
|
cb.clients[username] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
client := u.byLivestream(livestreamUrl)
|
||||||
|
if client != nil {
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := cb.accountS.ByUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error querying account by username: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookies []*http.Cookie
|
||||||
|
err = json.Unmarshal([]byte(*account.Cookies), &cookies)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error un-marshaling cookie string: %v", err)
|
||||||
|
}
|
||||||
|
client, err = rumblelivestreamlib.NewClient(rumblelivestreamlib.NewClientOptions{Cookies: cookies, LiveStreamUrl: livestreamUrl})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating new client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.ChatInfo(true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting chat info for client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.livestreamsMu.Lock()
|
||||||
|
defer u.livestreamsMu.Unlock()
|
||||||
|
u.livestreams[livestreamUrl] = client
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) Run(rule *Rule, url string) error {
|
||||||
|
if rule == nil ||
|
||||||
|
rule.ChatbotID == nil ||
|
||||||
|
rule.ID == nil ||
|
||||||
|
rule.Parameters == nil ||
|
||||||
|
rule.Parameters.SendAs == nil {
|
||||||
|
return pkgErr("", fmt.Errorf("invalid rule"))
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped := cb.stopRunner(*rule.ChatbotID, *rule.ID)
|
||||||
|
if stopped {
|
||||||
|
// TODO: figure out better way to determine when running rule is cleaned up.
|
||||||
|
// If rule was stopped, wait for everything to complete before running again.
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client := cb.clients.byUsernameLivestream(rule.Parameters.SendAs.Username, url)
|
||||||
|
if client == nil {
|
||||||
|
client, err = cb.addClient(rule.Parameters.SendAs.Username, url)
|
||||||
|
if err != nil {
|
||||||
|
return pkgErr("error adding client", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
runner := &Runner{
|
||||||
|
cancel: cancel,
|
||||||
|
client: client,
|
||||||
|
rule: *rule,
|
||||||
|
wails: cb.wails,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cb.initRunner(runner)
|
||||||
|
if err != nil {
|
||||||
|
return pkgErr("error initializing runner", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go cb.run(ctx, runner)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) initRunner(runner *Runner) error {
|
||||||
|
if runner == nil || runner.rule.ID == nil || runner.rule.ChatbotID == nil || runner.rule.Parameters == nil || runner.rule.Parameters.Trigger == nil {
|
||||||
|
return fmt.Errorf("invalid runner")
|
||||||
|
}
|
||||||
|
|
||||||
|
channelID, err := runner.rule.Parameters.SendAs.ChannelIDInt()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting channel ID to int: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runner.channelIDMu.Lock()
|
||||||
|
runner.channelID = channelID
|
||||||
|
runner.channelIDMu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case runner.rule.Parameters.Trigger.OnTimer != nil:
|
||||||
|
runner.run = runner.runOnTimer
|
||||||
|
case runner.rule.Parameters.Trigger.OnCommand != nil:
|
||||||
|
err = cb.initRunnerCommand(runner)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error initializing command: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cb.runnersMu.Lock()
|
||||||
|
// defer cb.runnersMu.Unlock()
|
||||||
|
// cb.runners[*runner.rule.ID] = runner
|
||||||
|
|
||||||
|
cb.botsMu.Lock()
|
||||||
|
defer cb.botsMu.Unlock()
|
||||||
|
bot, exists := cb.bots[*runner.rule.ChatbotID]
|
||||||
|
if !exists {
|
||||||
|
bot = &Bot{
|
||||||
|
runners: map[int64]*Runner{},
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.bots[*runner.rule.ChatbotID] = bot
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.runnersMu.Lock()
|
||||||
|
defer bot.runnersMu.Unlock()
|
||||||
|
bot.runners[*runner.rule.ID] = runner
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) initRunnerCommand(runner *Runner) error {
|
||||||
|
runner.run = runner.runOnCommand
|
||||||
|
|
||||||
|
cmd := runner.rule.Parameters.Trigger.OnCommand.Command
|
||||||
|
if cmd == "" || cmd[0] != '!' {
|
||||||
|
return fmt.Errorf("invalid command")
|
||||||
|
}
|
||||||
|
|
||||||
|
chatCh := make(chan events.Chat, 10)
|
||||||
|
runner.chatCh = chatCh
|
||||||
|
|
||||||
|
cb.receiversMu.Lock()
|
||||||
|
defer cb.receiversMu.Unlock()
|
||||||
|
rcvr, exists := cb.receivers[runner.client.LiveStreamUrl]
|
||||||
|
if !exists {
|
||||||
|
rcvr = &receiver{
|
||||||
|
onCommand: map[string]map[int64]chan events.Chat{},
|
||||||
|
}
|
||||||
|
cb.receivers[runner.client.LiveStreamUrl] = rcvr
|
||||||
|
}
|
||||||
|
|
||||||
|
chans, exists := rcvr.onCommand[cmd]
|
||||||
|
if !exists {
|
||||||
|
chans = map[int64]chan events.Chat{}
|
||||||
|
rcvr.onCommand[cmd] = chans
|
||||||
|
}
|
||||||
|
chans[*runner.rule.ID] = chatCh
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) run(ctx context.Context, runner *Runner) {
|
||||||
|
if runner == nil || runner.rule.ID == nil || runner.run == nil {
|
||||||
|
cb.logError.Println("invalid runner")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.EventsEmit(cb.wails, fmt.Sprintf("ChatbotRuleActive-%d", *runner.rule.ID), true)
|
||||||
|
err := runner.run(ctx)
|
||||||
|
if err != nil {
|
||||||
|
prefix := fmt.Sprintf("chatbot runner for rule %d returned error:", *runner.rule.ID)
|
||||||
|
cb.logError.Println(prefix, err)
|
||||||
|
runtime.EventsEmit(cb.wails, fmt.Sprintf("ChatbotRuleError-%d", *runner.rule.ID), "Chatbot encountered an error while running this rule.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cb.stop(&runner.rule)
|
||||||
|
if err != nil {
|
||||||
|
prefix := fmt.Sprintf("error stopping rule %d after runner returns:", *runner.rule.ID)
|
||||||
|
cb.logError.Println(prefix, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.EventsEmit(cb.wails, fmt.Sprintf("ChatbotRuleActive-%d", *runner.rule.ID), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) Running(chatbotID int64, ruleID int64) bool {
|
||||||
|
// cb.runnersMu.Lock()
|
||||||
|
// defer cb.runnersMu.Unlock()
|
||||||
|
// _, exists := cb.runners[id]
|
||||||
|
// return exists
|
||||||
|
|
||||||
|
cb.botsMu.Lock()
|
||||||
|
defer cb.botsMu.Unlock()
|
||||||
|
bot, exists := cb.bots[chatbotID]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.runnersMu.Lock()
|
||||||
|
defer bot.runnersMu.Unlock()
|
||||||
|
_, exists = bot.runners[ruleID]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) Stop(rule *Rule) error {
|
||||||
|
err := cb.stop(rule)
|
||||||
|
if err != nil {
|
||||||
|
return pkgErr("", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) stop(rule *Rule) error {
|
||||||
|
if rule == nil || rule.ID == nil || rule.ChatbotID == nil {
|
||||||
|
return fmt.Errorf("invalid rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.stopRunner(*rule.ChatbotID, *rule.ID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) stopRunner(chatbotID int64, ruleID int64) bool {
|
||||||
|
// cb.runnersMu.Lock()
|
||||||
|
// defer cb.runnersMu.Unlock()
|
||||||
|
// runner, exists := cb.runners[id]
|
||||||
|
// if !exists {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
cb.botsMu.Lock()
|
||||||
|
defer cb.botsMu.Unlock()
|
||||||
|
bot, exists := cb.bots[chatbotID]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.runnersMu.Lock()
|
||||||
|
defer bot.runnersMu.Unlock()
|
||||||
|
runner, exists := bot.runners[ruleID]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped := true
|
||||||
|
runner.stop()
|
||||||
|
// delete(cb.runners, id)
|
||||||
|
delete(bot.runners, ruleID)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case runner.rule.Parameters.Trigger.OnCommand != nil:
|
||||||
|
err := cb.closeRunnerCommand(runner)
|
||||||
|
if err != nil {
|
||||||
|
cb.logError.Println("error closing runner command:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) closeRunnerCommand(runner *Runner) error {
|
||||||
|
if runner == nil || runner.rule.ID == nil || runner.rule.Parameters == nil || runner.rule.Parameters.Trigger == nil || runner.rule.Parameters.Trigger.OnCommand == nil {
|
||||||
|
return fmt.Errorf("invalid runner command")
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.receiversMu.Lock()
|
||||||
|
defer cb.receiversMu.Unlock()
|
||||||
|
|
||||||
|
rcvr, exists := cb.receivers[runner.client.LiveStreamUrl]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("receiver for runner does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := runner.rule.Parameters.Trigger.OnCommand.Command
|
||||||
|
chans, exists := rcvr.onCommand[cmd]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("channel map for runner does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, exists := chans[*runner.rule.ID]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("channel for runner does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ch)
|
||||||
|
delete(chans, *runner.rule.ID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) HandleChat(event events.Chat) {
|
||||||
|
|
||||||
|
switch event.Message.Type {
|
||||||
|
case rumblelivestreamlib.ChatTypeMessages:
|
||||||
|
cb.handleMessage(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) handleMessage(event events.Chat) {
|
||||||
|
errs := cb.runMessageFuncs(
|
||||||
|
event,
|
||||||
|
cb.handleMessageCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
cb.logError.Println("chatbot: error handling message:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Chatbot) runMessageFuncs(event events.Chat, fns ...messageFunc) []error {
|
||||||
|
// TODO: validate message
|
||||||
|
|
||||||
|
errs := []error{}
|
||||||
|
for _, fn := range fns {
|
||||||
|
err := fn(event)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
type messageFunc func(event events.Chat) error
|
||||||
|
|
||||||
|
func (cb *Chatbot) handleMessageCommand(event events.Chat) error {
|
||||||
|
if strings.Index(event.Message.Text, "!") != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
words := strings.Split(event.Message.Text, " ")
|
||||||
|
cmd := words[0]
|
||||||
|
|
||||||
|
cb.receiversMu.Lock()
|
||||||
|
defer cb.receiversMu.Unlock()
|
||||||
|
|
||||||
|
receiver, exists := cb.receivers[event.Livestream]
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if receiver == nil {
|
||||||
|
return fmt.Errorf("receiver is nil for livestream: %s", event.Livestream)
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver.onCommandMu.Lock()
|
||||||
|
defer receiver.onCommandMu.Unlock()
|
||||||
|
runners, exist := receiver.onCommand[cmd]
|
||||||
|
if !exist {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, runner := range runners {
|
||||||
|
runner <- event
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
14
v1/internal/chatbot/error.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package chatbot
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const pkgName = "chatbot"
|
||||||
|
|
||||||
|
func pkgErr(prefix string, err error) error {
|
||||||
|
pkgErr := pkgName
|
||||||
|
if prefix != "" {
|
||||||
|
pkgErr = fmt.Sprintf("%s: %s", pkgErr, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%s: %v", pkgErr, err)
|
||||||
|
}
|
180
v1/internal/chatbot/rule.go
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
package chatbot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"cmp"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tylertravisty/rum-goggles/v1/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SortRules(rules []Rule) {
|
||||||
|
slices.SortFunc(rules, func(a, b Rule) int {
|
||||||
|
return cmp.Compare(strings.ToLower(a.Display), strings.ToLower(b.Display))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule struct {
|
||||||
|
ID *int64 `json:"id"`
|
||||||
|
ChatbotID *int64 `json:"chatbot_id"`
|
||||||
|
Display string `json:"display"`
|
||||||
|
Parameters *RuleParameters `json:"parameters"`
|
||||||
|
Running bool `json:"running"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleParameters struct {
|
||||||
|
Message *RuleMessage `json:"message"`
|
||||||
|
SendAs *RuleSender `json:"send_as"`
|
||||||
|
Trigger *RuleTrigger `json:"trigger"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleMessage struct {
|
||||||
|
FromFile *RuleMessageFile `json:"from_file"`
|
||||||
|
FromText string `json:"from_text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *RuleMessage) String() (string, error) {
|
||||||
|
if rm.FromFile == nil {
|
||||||
|
return rm.FromText, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := rm.FromFile.string()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error reading from file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rmf *RuleMessageFile) string() (string, error) {
|
||||||
|
if rmf.Filepath == "" {
|
||||||
|
return "", fmt.Errorf("filepath is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rmf.lines) == 0 {
|
||||||
|
file, err := os.Open(rmf.Filepath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error opening file: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rmf.lines = append(rmf.lines, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rmf.lines) == 0 {
|
||||||
|
return "", fmt.Errorf("no lines read")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rmf.RandomRead {
|
||||||
|
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(rmf.lines))))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error generating random line number: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rmf.lines[n.Int64()], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
line := rmf.lines[rmf.lineNum]
|
||||||
|
rmf.lineNum = rmf.lineNum + 1
|
||||||
|
if rmf.lineNum >= len(rmf.lines) {
|
||||||
|
rmf.lineNum = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleMessageFile struct {
|
||||||
|
Filepath string `json:"filepath"`
|
||||||
|
RandomRead bool `json:"random_read"`
|
||||||
|
lines []string
|
||||||
|
lineNum int
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleSender struct {
|
||||||
|
ChannelID *string `json:"channel_id"`
|
||||||
|
Display string `json:"display"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *RuleSender) ChannelIDInt() (*int, error) {
|
||||||
|
if rs.ChannelID == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
i64, err := strconv.ParseInt(*rs.ChannelID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, pkgErr("error parsing channel ID", err)
|
||||||
|
}
|
||||||
|
i := int(i64)
|
||||||
|
|
||||||
|
return &i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleTrigger struct {
|
||||||
|
OnCommand *RuleTriggerCommand `json:"on_command"`
|
||||||
|
OnEvent *RuleTriggerEvent `json:"on_event"`
|
||||||
|
OnTimer *time.Duration `json:"on_timer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleTriggerCommand struct {
|
||||||
|
Command string `json:"command"`
|
||||||
|
Restrict *RuleTriggerCommandRestriction `json:"restrict"`
|
||||||
|
Timeout time.Duration `json:"timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleTriggerCommandRestriction struct {
|
||||||
|
Bypass *RuleTriggerCommandRestrictionBypass `json:"bypass"`
|
||||||
|
ToAdmin bool `json:"to_admin"`
|
||||||
|
ToFollower bool `json:"to_follower"`
|
||||||
|
ToMod bool `json:"to_mod"`
|
||||||
|
ToStreamer bool `json:"to_streamer"`
|
||||||
|
ToSubscriber bool `json:"to_subscriber"`
|
||||||
|
ToRant int `json:"to_rant"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleTriggerCommandRestrictionBypass struct {
|
||||||
|
IfAdmin bool `json:"if_admin"`
|
||||||
|
IfMod bool `json:"if_mod"`
|
||||||
|
IfStreamer bool `json:"if_streamer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleTriggerEvent struct {
|
||||||
|
OnFollow bool `json:"on_follow"`
|
||||||
|
OnSubscribe bool `json:"on_subscribe"`
|
||||||
|
OnRaid bool `json:"on_raid"`
|
||||||
|
OnRant int `json:"on_rant"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rule *Rule) ToModelsChatbotRule() (*models.ChatbotRule, error) {
|
||||||
|
modelsRule := &models.ChatbotRule{
|
||||||
|
ID: rule.ID,
|
||||||
|
ChatbotID: rule.ChatbotID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.Parameters != nil {
|
||||||
|
paramsB, err := json.Marshal(rule.Parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshaling parameters into json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsS := string(paramsB)
|
||||||
|
modelsRule.Parameters = ¶msS
|
||||||
|
}
|
||||||
|
|
||||||
|
return modelsRule, nil
|
||||||
|
}
|
206
v1/internal/chatbot/runner.go
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
package chatbot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tylertravisty/rum-goggles/v1/internal/events"
|
||||||
|
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Runner struct {
|
||||||
|
apiCh chan events.Api
|
||||||
|
cancel context.CancelFunc
|
||||||
|
cancelMu sync.Mutex
|
||||||
|
channelID *int
|
||||||
|
channelIDMu sync.Mutex
|
||||||
|
chatCh chan events.Chat
|
||||||
|
client *rumblelivestreamlib.Client
|
||||||
|
rule Rule
|
||||||
|
run runFunc
|
||||||
|
wails context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
type chatFields struct {
|
||||||
|
ChannelName string
|
||||||
|
DisplayName string
|
||||||
|
Username string
|
||||||
|
Rant int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) chat(fields *chatFields) error {
|
||||||
|
msg, err := r.rule.Parameters.Message.String()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting message string: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fields != nil {
|
||||||
|
tmpl, err := template.New("chat").Parse(msg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgB bytes.Buffer
|
||||||
|
err = tmpl.Execute(&msgB, fields)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error executing template: %v", err)
|
||||||
|
}
|
||||||
|
msg = msgB.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.client.Chat(msg, r.channelID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending chat: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) init() error {
|
||||||
|
if r.rule.Parameters == nil || r.rule.Parameters.Trigger == nil {
|
||||||
|
return fmt.Errorf("invalid rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
channelID, err := r.rule.Parameters.SendAs.ChannelIDInt()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting channel ID to int: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.channelIDMu.Lock()
|
||||||
|
r.channelID = channelID
|
||||||
|
r.channelIDMu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.rule.Parameters.Trigger.OnTimer != nil:
|
||||||
|
r.run = r.runOnTimer
|
||||||
|
case r.rule.Parameters.Trigger.OnCommand != nil:
|
||||||
|
r.run = r.runOnCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type runFunc func(ctx context.Context) error
|
||||||
|
|
||||||
|
func (r *Runner) runOnCommand(ctx context.Context) error {
|
||||||
|
if r.rule.ID == nil || r.rule.Parameters == nil || r.rule.Parameters.Trigger == nil {
|
||||||
|
return fmt.Errorf("invalid rule")
|
||||||
|
}
|
||||||
|
if r.rule.Parameters.Trigger.OnCommand == nil {
|
||||||
|
return fmt.Errorf("command is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
var prev time.Time
|
||||||
|
for {
|
||||||
|
runtime.EventsEmit(r.wails, fmt.Sprintf("ChatbotRuleActive-%d", *r.rule.ID), true)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
case event := <-r.chatCh:
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(prev) < r.rule.Parameters.Trigger.OnCommand.Timeout*time.Second {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if block := r.blockCommand(event); block {
|
||||||
|
// if bypass := r.bypassCommand(event); !bypass {break}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.handleCommand(event)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error handling command: %v", err)
|
||||||
|
}
|
||||||
|
prev = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) blockCommand(event events.Chat) bool {
|
||||||
|
if r.rule.Parameters.Trigger.OnCommand.Restrict == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.rule.Parameters.Trigger.OnCommand.Restrict.ToFollower &&
|
||||||
|
!event.Message.IsFollower {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriber := false
|
||||||
|
for _, badge := range event.Message.Badges {
|
||||||
|
if badge == rumblelivestreamlib.ChatBadgeLocalsSupporter || badge == rumblelivestreamlib.ChatBadgeRecurringSubscription {
|
||||||
|
subscriber = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.rule.Parameters.Trigger.OnCommand.Restrict.ToSubscriber &&
|
||||||
|
!subscriber {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Message.Rant < r.rule.Parameters.Trigger.OnCommand.Restrict.ToRant*100 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) handleCommand(event events.Chat) error {
|
||||||
|
displayName := event.Message.Username
|
||||||
|
if event.Message.ChannelName != "" {
|
||||||
|
displayName = event.Message.ChannelName
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := &chatFields{
|
||||||
|
ChannelName: event.Message.ChannelName,
|
||||||
|
DisplayName: displayName,
|
||||||
|
Username: event.Message.Username,
|
||||||
|
Rant: event.Message.Rant / 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.chat(fields)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending chat: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) runOnTimer(ctx context.Context) error {
|
||||||
|
if r.rule.ID == nil || r.rule.Parameters == nil || r.rule.Parameters.Trigger == nil {
|
||||||
|
return fmt.Errorf("invalid rule")
|
||||||
|
}
|
||||||
|
if r.rule.Parameters.Trigger.OnTimer == nil {
|
||||||
|
return fmt.Errorf("timer is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
runtime.EventsEmit(r.wails, fmt.Sprintf("ChatbotRuleActive-%d", *r.rule.ID), true)
|
||||||
|
err := r.chat(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending chat: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger := time.NewTimer(*r.rule.Parameters.Trigger.OnTimer * time.Second)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
trigger.Stop()
|
||||||
|
return nil
|
||||||
|
case <-trigger.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) stop() {
|
||||||
|
r.cancelMu.Lock()
|
||||||
|
if r.cancel != nil {
|
||||||
|
r.cancel()
|
||||||
|
}
|
||||||
|
r.cancelMu.Unlock()
|
||||||
|
}
|
|
@ -11,16 +11,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Chat struct {
|
type Chat struct {
|
||||||
Message rumblelivestreamlib.ChatView
|
Livestream string
|
||||||
Stop bool
|
Message rumblelivestreamlib.ChatView
|
||||||
Url string
|
Stop bool
|
||||||
|
Url string
|
||||||
}
|
}
|
||||||
|
|
||||||
type chatProducer struct {
|
type chatProducer struct {
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
cancelMu sync.Mutex
|
cancelMu sync.Mutex
|
||||||
client *rumblelivestreamlib.Client
|
client *rumblelivestreamlib.Client
|
||||||
url string
|
livestream string
|
||||||
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
type chatProducerValFunc func(*chatProducer) error
|
type chatProducerValFunc func(*chatProducer) error
|
||||||
|
@ -82,6 +84,12 @@ func (cp *ChatProducer) Start(liveStreamUrl string) (string, error) {
|
||||||
return "", pkgErr("", fmt.Errorf("url is empty"))
|
return "", pkgErr("", fmt.Errorf("url is empty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cp.producersMu.Lock()
|
||||||
|
defer cp.producersMu.Unlock()
|
||||||
|
if producer, active := cp.producers[liveStreamUrl]; active {
|
||||||
|
return producer.url, nil
|
||||||
|
}
|
||||||
|
|
||||||
client, err := rumblelivestreamlib.NewClient(rumblelivestreamlib.NewClientOptions{LiveStreamUrl: liveStreamUrl})
|
client, err := rumblelivestreamlib.NewClient(rumblelivestreamlib.NewClientOptions{LiveStreamUrl: liveStreamUrl})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", pkgErr("error creating new rumble client", err)
|
return "", pkgErr("error creating new rumble client", err)
|
||||||
|
@ -93,19 +101,21 @@ func (cp *ChatProducer) Start(liveStreamUrl string) (string, error) {
|
||||||
}
|
}
|
||||||
chatStreamUrl := chatInfo.StreamUrl()
|
chatStreamUrl := chatInfo.StreamUrl()
|
||||||
|
|
||||||
cp.producersMu.Lock()
|
// cp.producersMu.Lock()
|
||||||
defer cp.producersMu.Unlock()
|
// defer cp.producersMu.Unlock()
|
||||||
if _, active := cp.producers[chatStreamUrl]; active {
|
// if _, active := cp.producers[chatStreamUrl]; active {
|
||||||
return chatStreamUrl, nil
|
// return chatStreamUrl, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
producer := &chatProducer{
|
producer := &chatProducer{
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
client: client,
|
client: client,
|
||||||
url: chatStreamUrl,
|
livestream: liveStreamUrl,
|
||||||
|
url: chatStreamUrl,
|
||||||
}
|
}
|
||||||
cp.producers[chatStreamUrl] = producer
|
// cp.producers[chatStreamUrl] = producer
|
||||||
|
cp.producers[liveStreamUrl] = producer
|
||||||
go cp.run(ctx, producer)
|
go cp.run(ctx, producer)
|
||||||
|
|
||||||
return chatStreamUrl, nil
|
return chatStreamUrl, nil
|
||||||
|
@ -138,6 +148,8 @@ func (cp *ChatProducer) run(ctx context.Context, producer *chatProducer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: handle the case when restarting stream with possibly missing messages
|
||||||
|
// Start new stream, make sure it's running, close old stream
|
||||||
for {
|
for {
|
||||||
err = producer.client.StartChatStream(cp.handleChat(producer), cp.handleError(producer))
|
err = producer.client.StartChatStream(cp.handleChat(producer), cp.handleError(producer))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -154,6 +166,7 @@ func (cp *ChatProducer) run(ctx context.Context, producer *chatProducer) {
|
||||||
cp.stop(producer)
|
cp.stop(producer)
|
||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
|
producer.client.StopChatStream()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +177,7 @@ func (cp *ChatProducer) handleChat(p *chatProducer) func(cv rumblelivestreamlib.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cp.Ch <- Chat{Message: cv, Url: p.url}
|
cp.Ch <- Chat{Livestream: p.livestream, Message: cv, Url: p.url}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +197,7 @@ func (cp *ChatProducer) stop(p *chatProducer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cp.Ch <- Chat{Stop: true, Url: p.url}
|
cp.Ch <- Chat{Livestream: p.livestream, Stop: true, Url: p.url}
|
||||||
|
|
||||||
cp.producersMu.Lock()
|
cp.producersMu.Lock()
|
||||||
delete(cp.producers, p.url)
|
delete(cp.producers, p.url)
|
||||||
|
|
|
@ -6,18 +6,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chatbotRuleColumns = "id, chatbot_id, name, rule"
|
chatbotRuleColumns = "id, chatbot_id, parameters"
|
||||||
chatbotRuleTable = "chatbot_rule"
|
chatbotRuleTable = "chatbot_rule"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChatbotRule struct {
|
type ChatbotRule struct {
|
||||||
ID *int64 `json:"id"`
|
ID *int64 `json:"id"`
|
||||||
ChatbotID *int64 `json:"chatbot_id"`
|
ChatbotID *int64 `json:"chatbot_id"`
|
||||||
Rule *string `json:"rule"`
|
Parameters *string `json:"parameters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatbotRule) values() []any {
|
func (c *ChatbotRule) values() []any {
|
||||||
return []any{c.ID, c.ChatbotID, c.Rule}
|
return []any{c.ID, c.ChatbotID, c.Parameters}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatbotRule) valuesNoID() []any {
|
func (c *ChatbotRule) valuesNoID() []any {
|
||||||
|
@ -30,26 +30,27 @@ func (c *ChatbotRule) valuesEndID() []any {
|
||||||
}
|
}
|
||||||
|
|
||||||
type sqlChatbotRule struct {
|
type sqlChatbotRule struct {
|
||||||
id sql.NullInt64
|
id sql.NullInt64
|
||||||
chatbotID sql.NullInt64
|
chatbotID sql.NullInt64
|
||||||
rule sql.NullString
|
parameters sql.NullString
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *sqlChatbotRule) scan(r Row) error {
|
func (sc *sqlChatbotRule) scan(r Row) error {
|
||||||
return r.Scan(&sc.id, &sc.chatbotID, &sc.rule)
|
return r.Scan(&sc.id, &sc.chatbotID, &sc.parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc sqlChatbotRule) toChatbotRule() *ChatbotRule {
|
func (sc sqlChatbotRule) toChatbotRule() *ChatbotRule {
|
||||||
var c ChatbotRule
|
var c ChatbotRule
|
||||||
c.ID = toInt64(sc.id)
|
c.ID = toInt64(sc.id)
|
||||||
c.ChatbotID = toInt64(sc.chatbotID)
|
c.ChatbotID = toInt64(sc.chatbotID)
|
||||||
c.Rule = toString(sc.rule)
|
c.Parameters = toString(sc.parameters)
|
||||||
|
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatbotRuleService interface {
|
type ChatbotRuleService interface {
|
||||||
AutoMigrate() error
|
AutoMigrate() error
|
||||||
|
ByChatbotID(cid int64) ([]ChatbotRule, error)
|
||||||
Create(c *ChatbotRule) (int64, error)
|
Create(c *ChatbotRule) (int64, error)
|
||||||
Delete(c *ChatbotRule) error
|
Delete(c *ChatbotRule) error
|
||||||
DestructiveReset() error
|
DestructiveReset() error
|
||||||
|
@ -82,7 +83,7 @@ func (cs *chatbotRuleService) createChatbotRuleTable() error {
|
||||||
CREATE TABLE IF NOT EXISTS "%s" (
|
CREATE TABLE IF NOT EXISTS "%s" (
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
chatbot_id INTEGER NOT NULL,
|
chatbot_id INTEGER NOT NULL,
|
||||||
rule TEXT NOT NULL
|
parameters TEXT NOT NULL,
|
||||||
FOREIGN KEY (chatbot_id) REFERENCES "%s" (id)
|
FOREIGN KEY (chatbot_id) REFERENCES "%s" (id)
|
||||||
)
|
)
|
||||||
`, chatbotRuleTable, chatbotTable)
|
`, chatbotRuleTable, chatbotTable)
|
||||||
|
@ -95,10 +96,42 @@ func (cs *chatbotRuleService) createChatbotRuleTable() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cs *chatbotRuleService) ByChatbotID(cid int64) ([]ChatbotRule, error) {
|
||||||
|
selectQ := fmt.Sprintf(`
|
||||||
|
SELECT %s
|
||||||
|
FROM "%s"
|
||||||
|
WHERE chatbot_id=?
|
||||||
|
`, chatbotRuleColumns, chatbotRuleTable)
|
||||||
|
|
||||||
|
rows, err := cs.Database.Query(selectQ, cid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, pkgErr("error executing select query", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
rules := []ChatbotRule{}
|
||||||
|
for rows.Next() {
|
||||||
|
scr := &sqlChatbotRule{}
|
||||||
|
|
||||||
|
err = scr.scan(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, pkgErr("error scanning row", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, *scr.toChatbotRule())
|
||||||
|
}
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
return nil, pkgErr("error iterating over rows", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cs *chatbotRuleService) Create(c *ChatbotRule) (int64, error) {
|
func (cs *chatbotRuleService) Create(c *ChatbotRule) (int64, error) {
|
||||||
err := runChatbotRuleValFuncs(
|
err := runChatbotRuleValFuncs(
|
||||||
c,
|
c,
|
||||||
chatbotRuleRequireRule,
|
chatbotRuleRequireParameters,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, pkgErr("invalid chat rule", err)
|
return -1, pkgErr("invalid chat rule", err)
|
||||||
|
@ -169,7 +202,7 @@ func (cs *chatbotRuleService) Update(c *ChatbotRule) error {
|
||||||
err := runChatbotRuleValFuncs(
|
err := runChatbotRuleValFuncs(
|
||||||
c,
|
c,
|
||||||
chatbotRuleRequireID,
|
chatbotRuleRequireID,
|
||||||
chatbotRuleRequireRule,
|
chatbotRuleRequireParameters,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pkgErr("invalid chat rule", err)
|
return pkgErr("invalid chat rule", err)
|
||||||
|
@ -215,9 +248,9 @@ func chatbotRuleRequireID(c *ChatbotRule) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatbotRuleRequireRule(c *ChatbotRule) error {
|
func chatbotRuleRequireParameters(c *ChatbotRule) error {
|
||||||
if c.Rule == nil || *c.Rule == "" {
|
if c.Parameters == nil || *c.Parameters == "" {
|
||||||
return ErrChatbotRuleInvalidRule
|
return ErrChatbotRuleInvalidParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -17,8 +17,8 @@ const (
|
||||||
ErrChatbotInvalidID ValidatorError = "invalid chatbot id"
|
ErrChatbotInvalidID ValidatorError = "invalid chatbot id"
|
||||||
ErrChatbotInvalidName ValidatorError = "invalid chatbot name"
|
ErrChatbotInvalidName ValidatorError = "invalid chatbot name"
|
||||||
|
|
||||||
ErrChatbotRuleInvalidID ValidatorError = "invalid chatbot rule id"
|
ErrChatbotRuleInvalidID ValidatorError = "invalid chatbot rule id"
|
||||||
ErrChatbotRuleInvalidRule ValidatorError = "invalid chatbot rule rule"
|
ErrChatbotRuleInvalidParameters ValidatorError = "invalid chatbot rule parameters"
|
||||||
)
|
)
|
||||||
|
|
||||||
func pkgErr(prefix string, err error) error {
|
func pkgErr(prefix string, err error) error {
|
||||||
|
|
|
@ -18,6 +18,7 @@ type Services struct {
|
||||||
AccountChannelS AccountChannelService
|
AccountChannelS AccountChannelService
|
||||||
ChannelS ChannelService
|
ChannelS ChannelService
|
||||||
ChatbotS ChatbotService
|
ChatbotS ChatbotService
|
||||||
|
ChatbotRuleS ChatbotRuleService
|
||||||
Database *sql.DB
|
Database *sql.DB
|
||||||
tables []table
|
tables []table
|
||||||
}
|
}
|
||||||
|
@ -116,3 +117,12 @@ func WithChatbotService() ServicesInit {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithChatbotRuleService() ServicesInit {
|
||||||
|
return func(s *Services) error {
|
||||||
|
s.ChatbotRuleS = NewChatbotRuleService(s.Database)
|
||||||
|
s.tables = append(s.tables, table{chatbotRuleTable, s.ChatbotRuleS.AutoMigrate, s.ChatbotRuleS.DestructiveReset})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
67
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/chat.go
generated
vendored
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/csv"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -19,6 +18,20 @@ import (
|
||||||
"gopkg.in/cenkalti/backoff.v1"
|
"gopkg.in/cenkalti/backoff.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChatBadgeRecurringSubscription = "recurring_subscription"
|
||||||
|
ChatBadgeLocalsSupporter = "locals_supporter"
|
||||||
|
|
||||||
|
ChatTypeInit = "init"
|
||||||
|
ChatTypeMessages = "messages"
|
||||||
|
ChatTypeMuteUsers = "mute_users"
|
||||||
|
ChatTypeDeleteMessages = "delete_messages"
|
||||||
|
ChatTypeSubscriber = "locals_supporter"
|
||||||
|
ChatTypeRaiding = "raid_confirmed"
|
||||||
|
ChatTypePinMessage = "pin_message"
|
||||||
|
ChatTypeUnpinMessage = "unpin_message"
|
||||||
|
)
|
||||||
|
|
||||||
type ChatInfo struct {
|
type ChatInfo struct {
|
||||||
ChannelID int
|
ChannelID int
|
||||||
ChatID string
|
ChatID string
|
||||||
|
@ -74,21 +87,12 @@ func (c *Client) getChatInfo() (*ChatInfo, error) {
|
||||||
if end == -1 {
|
if end == -1 {
|
||||||
return nil, fmt.Errorf("error finding end of chat function in webpage")
|
return nil, fmt.Errorf("error finding end of chat function in webpage")
|
||||||
}
|
}
|
||||||
argsS := strings.ReplaceAll(lineS[start:start+end], ", ", ",")
|
args := parseRumbleChatArgs(lineS[start : start+end])
|
||||||
argsS = strings.Replace(argsS, "[", "\"[", 1)
|
channelID, err := strconv.Atoi(args[5])
|
||||||
n := strings.LastIndex(argsS, "]")
|
|
||||||
argsS = argsS[:n] + "]\"" + argsS[n+1:]
|
|
||||||
c := csv.NewReader(strings.NewReader(argsS))
|
|
||||||
args, err := c.ReadAll()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error parsing csv: %v", err)
|
|
||||||
}
|
|
||||||
info := args[0]
|
|
||||||
channelID, err := strconv.Atoi(info[5])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error converting channel ID argument string to int: %v", err)
|
return nil, fmt.Errorf("error converting channel ID argument string to int: %v", err)
|
||||||
}
|
}
|
||||||
chatInfo = &ChatInfo{ChannelID: channelID, ChatID: info[1], UrlPrefix: info[0]}
|
chatInfo = &ChatInfo{ChannelID: channelID, ChatID: args[1], UrlPrefix: args[0]}
|
||||||
} else if strings.Contains(lineS, "media-by--a") && strings.Contains(lineS, "author") {
|
} else if strings.Contains(lineS, "media-by--a") && strings.Contains(lineS, "author") {
|
||||||
r := strings.NewReader(lineS)
|
r := strings.NewReader(lineS)
|
||||||
node, err := html.Parse(r)
|
node, err := html.Parse(r)
|
||||||
|
@ -117,6 +121,37 @@ func (c *Client) getChatInfo() (*ChatInfo, error) {
|
||||||
return chatInfo, nil
|
return chatInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseRumbleChatArgs(argsS string) []string {
|
||||||
|
open := 0
|
||||||
|
|
||||||
|
args := []string{}
|
||||||
|
arg := []rune{}
|
||||||
|
for _, c := range argsS {
|
||||||
|
if c == ',' && open == 0 {
|
||||||
|
args = append(args, trimRumbleChatArg(string(arg)))
|
||||||
|
arg = []rune{}
|
||||||
|
} else {
|
||||||
|
if c == '[' {
|
||||||
|
open = open + 1
|
||||||
|
}
|
||||||
|
if c == ']' {
|
||||||
|
open = open - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
arg = append(arg, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(arg) > 0 {
|
||||||
|
args = append(args, trimRumbleChatArg(string(arg)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimRumbleChatArg(arg string) string {
|
||||||
|
return strings.Trim(strings.TrimSpace(arg), "\"")
|
||||||
|
}
|
||||||
|
|
||||||
type ChatMessage struct {
|
type ChatMessage struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
|
@ -359,6 +394,7 @@ type ChatView struct {
|
||||||
IsFollower bool
|
IsFollower bool
|
||||||
Rant int
|
Rant int
|
||||||
Text string
|
Text string
|
||||||
|
Time time.Time
|
||||||
Type string
|
Type string
|
||||||
Username string
|
Username string
|
||||||
}
|
}
|
||||||
|
@ -424,6 +460,11 @@ func parseMessages(eventType string, messages []ChatEventMessage, users map[stri
|
||||||
view.Rant = message.Rant.PriceCents
|
view.Rant = message.Rant.PriceCents
|
||||||
}
|
}
|
||||||
view.Text = message.Text
|
view.Text = message.Text
|
||||||
|
t, err := time.Parse(time.RFC3339, message.Time)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing message time: %v", err)
|
||||||
|
}
|
||||||
|
view.Time = t
|
||||||
view.Type = eventType
|
view.Type = eventType
|
||||||
view.Username = user.Username
|
view.Username = user.Username
|
||||||
|
|
||||||
|
|
2
v1/vendor/github.com/wailsapp/wails/v2/internal/binding/reflect.go
generated
vendored
|
@ -19,7 +19,7 @@ func isFunction(value interface{}) bool {
|
||||||
return reflect.ValueOf(value).Kind() == reflect.Func
|
return reflect.ValueOf(value).Kind() == reflect.Func
|
||||||
}
|
}
|
||||||
|
|
||||||
// isStructPtr returns true if the value given is a struct
|
// isStruct returns true if the value given is a struct
|
||||||
func isStruct(value interface{}) bool {
|
func isStruct(value interface{}) bool {
|
||||||
return reflect.ValueOf(value).Kind() == reflect.Struct
|
return reflect.ValueOf(value).Kind() == reflect.Struct
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
#define WindowStartsMinimised 2
|
#define WindowStartsMinimised 2
|
||||||
#define WindowStartsFullscreen 3
|
#define WindowStartsFullscreen 3
|
||||||
|
|
||||||
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceEnabled, const char* singleInstanceUniqueId);
|
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int zoomable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceEnabled, const char* singleInstanceUniqueId);
|
||||||
void Run(void*, const char* url);
|
void Run(void*, const char* url);
|
||||||
|
|
||||||
void SetTitle(void* ctx, const char *title);
|
void SetTitle(void* ctx, const char *title);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#import "WailsMenu.h"
|
#import "WailsMenu.h"
|
||||||
#import "WailsMenuItem.h"
|
#import "WailsMenuItem.h"
|
||||||
|
|
||||||
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId) {
|
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int zoomable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId) {
|
||||||
|
|
||||||
[NSApplication sharedApplication];
|
[NSApplication sharedApplication];
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ WailsContext* Create(const char* title, int width, int height, int frameless, in
|
||||||
fullscreen = 1;
|
fullscreen = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[result CreateWindow:width :height :frameless :resizable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent :minWidth :minHeight :maxWidth :maxHeight :fraudulentWebsiteWarningEnabled :preferences];
|
[result CreateWindow:width :height :frameless :resizable :zoomable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent :minWidth :minHeight :maxWidth :maxHeight :fraudulentWebsiteWarningEnabled :preferences];
|
||||||
[result SetTitle:safeInit(title)];
|
[result SetTitle:safeInit(title)];
|
||||||
[result Center];
|
[result Center];
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ struct Preferences {
|
||||||
bool *fullscreenEnabled;
|
bool *fullscreenEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences;
|
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences;
|
||||||
- (void) SetSize:(int)width :(int)height;
|
- (void) SetSize:(int)width :(int)height;
|
||||||
- (void) SetPosition:(int)x :(int) y;
|
- (void) SetPosition:(int)x :(int) y;
|
||||||
- (void) SetMinSize:(int)minWidth :(int)minHeight;
|
- (void) SetMinSize:(int)minWidth :(int)minHeight;
|
||||||
|
|
|
@ -136,7 +136,7 @@ typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences {
|
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences {
|
||||||
NSWindowStyleMask styleMask = 0;
|
NSWindowStyleMask styleMask = 0;
|
||||||
|
|
||||||
if( !frameless ) {
|
if( !frameless ) {
|
||||||
|
@ -158,7 +158,6 @@ typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
||||||
|
|
||||||
self.mainWindow = [[WailsWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height)
|
self.mainWindow = [[WailsWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height)
|
||||||
styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
|
styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
|
||||||
|
|
||||||
if (!frameless && useToolbar) {
|
if (!frameless && useToolbar) {
|
||||||
id toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"];
|
id toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"];
|
||||||
[toolbar autorelease];
|
[toolbar autorelease];
|
||||||
|
@ -188,6 +187,10 @@ typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
||||||
[self.mainWindow setAppearance:nsAppearance];
|
[self.mainWindow setAppearance:nsAppearance];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!zoomable && resizable) {
|
||||||
|
NSButton *button = [self.mainWindow standardWindowButton:NSWindowZoomButton];
|
||||||
|
[button setEnabled: NO];
|
||||||
|
}
|
||||||
|
|
||||||
NSSize minSize = { minWidth, minHeight };
|
NSSize minSize = { minWidth, minHeight };
|
||||||
NSSize maxSize = { maxWidth, maxHeight };
|
NSSize maxSize = { maxWidth, maxHeight };
|
||||||
|
|
4
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/browser.go
generated
vendored
|
@ -10,5 +10,7 @@ import (
|
||||||
// BrowserOpenURL Use the default browser to open the url
|
// BrowserOpenURL Use the default browser to open the url
|
||||||
func (f *Frontend) BrowserOpenURL(url string) {
|
func (f *Frontend) BrowserOpenURL(url string) {
|
||||||
// Specific method implementation
|
// Specific method implementation
|
||||||
_ = browser.OpenURL(url)
|
if err := browser.OpenURL(url); err != nil {
|
||||||
|
f.logger.Error("Unable to open default system browser")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
3
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/main.m
generated
vendored
|
@ -203,6 +203,7 @@ int main(int argc, const char * argv[]) {
|
||||||
// insert code here...
|
// insert code here...
|
||||||
int frameless = 0;
|
int frameless = 0;
|
||||||
int resizable = 1;
|
int resizable = 1;
|
||||||
|
int zoomable = 0;
|
||||||
int fullscreen = 1;
|
int fullscreen = 1;
|
||||||
int fullSizeContent = 1;
|
int fullSizeContent = 1;
|
||||||
int hideTitleBar = 0;
|
int hideTitleBar = 0;
|
||||||
|
@ -219,7 +220,7 @@ int main(int argc, const char * argv[]) {
|
||||||
int defaultContextMenuEnabled = 1;
|
int defaultContextMenuEnabled = 1;
|
||||||
int windowStartState = 0;
|
int windowStartState = 0;
|
||||||
int startsHidden = 0;
|
int startsHidden = 0;
|
||||||
WailsContext *result = Create("OI OI!",400,400, frameless, resizable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, devtoolsEnabled, defaultContextMenuEnabled, windowStartState,
|
WailsContext *result = Create("OI OI!",400,400, frameless, resizable, zoomable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, devtoolsEnabled, defaultContextMenuEnabled, windowStartState,
|
||||||
startsHidden, 400, 400, 600, 600, false);
|
startsHidden, 400, 400, 600, 600, false);
|
||||||
SetBackgroundColour(result, 255, 0, 0, 255);
|
SetBackgroundColour(result, 255, 0, 0, 255);
|
||||||
void *m = NewMenu("");
|
void *m = NewMenu("");
|
||||||
|
|
6
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/window.go
generated
vendored
|
@ -60,7 +60,7 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window
|
||||||
defaultContextMenuEnabled := bool2Cint(debug || frontendOptions.EnableDefaultContextMenu)
|
defaultContextMenuEnabled := bool2Cint(debug || frontendOptions.EnableDefaultContextMenu)
|
||||||
singleInstanceEnabled := bool2Cint(frontendOptions.SingleInstanceLock != nil)
|
singleInstanceEnabled := bool2Cint(frontendOptions.SingleInstanceLock != nil)
|
||||||
|
|
||||||
var fullSizeContent, hideTitleBar, hideTitle, useToolbar, webviewIsTransparent C.int
|
var fullSizeContent, hideTitleBar, zoomable, hideTitle, useToolbar, webviewIsTransparent C.int
|
||||||
var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent C.int
|
var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent C.int
|
||||||
var appearance, title *C.char
|
var appearance, title *C.char
|
||||||
var preferences C.struct_Preferences
|
var preferences C.struct_Preferences
|
||||||
|
@ -108,12 +108,14 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoomable = bool2Cint(!frontendOptions.Mac.DisableZoom)
|
||||||
|
|
||||||
windowIsTranslucent = bool2Cint(mac.WindowIsTranslucent)
|
windowIsTranslucent = bool2Cint(mac.WindowIsTranslucent)
|
||||||
webviewIsTransparent = bool2Cint(mac.WebviewIsTransparent)
|
webviewIsTransparent = bool2Cint(mac.WebviewIsTransparent)
|
||||||
|
|
||||||
appearance = c.String(string(mac.Appearance))
|
appearance = c.String(string(mac.Appearance))
|
||||||
}
|
}
|
||||||
var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, fullscreen, fullSizeContent,
|
var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, zoomable, fullscreen, fullSizeContent,
|
||||||
hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent,
|
hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent,
|
||||||
alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, devtoolsEnabled, defaultContextMenuEnabled,
|
alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, devtoolsEnabled, defaultContextMenuEnabled,
|
||||||
windowStartState, startsHidden, minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings,
|
windowStartState, startsHidden, minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings,
|
||||||
|
|
4
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/browser.go
generated
vendored
|
@ -8,5 +8,7 @@ import "github.com/pkg/browser"
|
||||||
// BrowserOpenURL Use the default browser to open the url
|
// BrowserOpenURL Use the default browser to open the url
|
||||||
func (f *Frontend) BrowserOpenURL(url string) {
|
func (f *Frontend) BrowserOpenURL(url string) {
|
||||||
// Specific method implementation
|
// Specific method implementation
|
||||||
_ = browser.OpenURL(url)
|
if err := browser.OpenURL(url); err != nil {
|
||||||
|
f.logger.Error("Unable to open default system browser")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
4
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/linux/window.c
generated
vendored
|
@ -245,7 +245,7 @@ void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_wid
|
||||||
gtk_window_set_geometry_hints(window, NULL, &size, flags);
|
gtk_window_set_geometry_hints(window, NULL, &size, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function to disable the context menu but propogate the event
|
// function to disable the context menu but propagate the event
|
||||||
static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer data)
|
static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer data)
|
||||||
{
|
{
|
||||||
// return true to disable the context menu
|
// return true to disable the context menu
|
||||||
|
@ -254,7 +254,7 @@ static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context
|
||||||
|
|
||||||
void DisableContextMenu(void *webview)
|
void DisableContextMenu(void *webview)
|
||||||
{
|
{
|
||||||
// Disable the context menu but propogate the event
|
// Disable the context menu but propagate the event
|
||||||
g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL);
|
g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/browser.go
generated
vendored
|
@ -5,10 +5,31 @@ package windows
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pkg/browser"
|
"github.com/pkg/browser"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var fallbackBrowserPaths = []string{
|
||||||
|
`\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`,
|
||||||
|
`\Program Files\Google\Chrome\Application\chrome.exe`,
|
||||||
|
`\Program Files (x86)\Google\Chrome\Application\chrome.exe`,
|
||||||
|
`\Program Files\Mozilla Firefox\firefox.exe`,
|
||||||
|
}
|
||||||
|
|
||||||
// BrowserOpenURL Use the default browser to open the url
|
// BrowserOpenURL Use the default browser to open the url
|
||||||
func (f *Frontend) BrowserOpenURL(url string) {
|
func (f *Frontend) BrowserOpenURL(url string) {
|
||||||
// Specific method implementation
|
// Specific method implementation
|
||||||
_ = browser.OpenURL(url)
|
err := browser.OpenURL(url)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, fallback := range fallbackBrowserPaths {
|
||||||
|
if err := openBrowser(fallback, url); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.logger.Error("Unable to open default system browser")
|
||||||
|
}
|
||||||
|
|
||||||
|
func openBrowser(path, url string) error {
|
||||||
|
return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(path), windows.StringToUTF16Ptr(url), nil, windows.SW_SHOWNORMAL)
|
||||||
}
|
}
|
||||||
|
|
2
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/app.go
generated
vendored
|
@ -37,7 +37,7 @@ func init() {
|
||||||
w32.InitCommonControlsEx(&initCtrls)
|
w32.InitCommonControlsEx(&initCtrls)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAppIconID sets recource icon ID for the apps windows.
|
// SetAppIcon sets resource icon ID for the apps windows.
|
||||||
func SetAppIcon(appIconID int) {
|
func SetAppIcon(appIconID int) {
|
||||||
AppIconID = appIconID
|
AppIconID = appIconID
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ func (cb *ComboBox) OnSelectedChange() *EventManager {
|
||||||
return &cb.onSelectedChange
|
return &cb.onSelectedChange
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message processer
|
// Message processor
|
||||||
func (cb *ComboBox) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
func (cb *ComboBox) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
switch msg {
|
switch msg {
|
||||||
case w32.WM_COMMAND:
|
case w32.WM_COMMAND:
|
||||||
|
|
|
@ -438,7 +438,7 @@ func (lv *ListView) OnEndScroll() *EventManager {
|
||||||
return &lv.onEndScroll
|
return &lv.onEndScroll
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message processer
|
// Message processor
|
||||||
func (lv *ListView) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
func (lv *ListView) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
switch msg {
|
switch msg {
|
||||||
/*case w32.WM_ERASEBKGND:
|
/*case w32.WM_ERASEBKGND:
|
||||||
|
|
|
@ -248,7 +248,7 @@ func (tv *TreeView) OnViewChange() *EventManager {
|
||||||
return &tv.onViewChange
|
return &tv.onViewChange
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message processer
|
// Message processor
|
||||||
func (tv *TreeView) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
func (tv *TreeView) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||||
switch msg {
|
switch msg {
|
||||||
case w32.WM_NOTIFY:
|
case w32.WM_NOTIFY:
|
||||||
|
|
8
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/runtime/ipc_websocket.js
generated
vendored
64
v1/vendor/github.com/wailsapp/wails/v2/internal/frontend/runtime/package-lock.json
generated
vendored
|
@ -784,9 +784,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
@ -1514,9 +1514,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "2.78.1",
|
"version": "2.79.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||||
"integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==",
|
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
|
@ -1541,9 +1541,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "5.7.1",
|
"version": "5.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
|
@ -1865,15 +1865,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "3.1.8",
|
"version": "3.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.10.tgz",
|
||||||
"integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==",
|
"integrity": "sha512-Dx3olBo/ODNiMVk/cA5Yft9Ws+snLOXrhLtrI3F4XLt4syz2Yg8fayZMWScPKoz12v5BUv7VEmQHnsfpY80fYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.15.9",
|
"esbuild": "^0.15.9",
|
||||||
"postcss": "^8.4.16",
|
"postcss": "^8.4.18",
|
||||||
"resolve": "^1.22.1",
|
"resolve": "^1.22.1",
|
||||||
"rollup": "~2.78.0"
|
"rollup": "^2.79.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
|
@ -1885,12 +1885,17 @@
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@types/node": ">= 14",
|
||||||
"less": "*",
|
"less": "*",
|
||||||
"sass": "*",
|
"sass": "*",
|
||||||
"stylus": "*",
|
"stylus": "*",
|
||||||
|
"sugarss": "*",
|
||||||
"terser": "^5.4.0"
|
"terser": "^5.4.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"less": {
|
"less": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
@ -1900,6 +1905,9 @@
|
||||||
"stylus": {
|
"stylus": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"sugarss": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"terser": {
|
"terser": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
|
@ -2528,9 +2536,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fsevents": {
|
"fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
@ -3046,9 +3054,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rollup": {
|
"rollup": {
|
||||||
"version": "2.78.1",
|
"version": "2.79.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||||
"integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==",
|
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
|
@ -3067,9 +3075,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.7.1",
|
"version": "5.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
|
@ -3330,16 +3338,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vite": {
|
"vite": {
|
||||||
"version": "3.1.8",
|
"version": "3.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.10.tgz",
|
||||||
"integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==",
|
"integrity": "sha512-Dx3olBo/ODNiMVk/cA5Yft9Ws+snLOXrhLtrI3F4XLt4syz2Yg8fayZMWScPKoz12v5BUv7VEmQHnsfpY80fYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"esbuild": "^0.15.9",
|
"esbuild": "^0.15.9",
|
||||||
"fsevents": "~2.3.2",
|
"fsevents": "~2.3.2",
|
||||||
"postcss": "^8.4.16",
|
"postcss": "^8.4.18",
|
||||||
"resolve": "^1.22.1",
|
"resolve": "^1.22.1",
|
||||||
"rollup": "~2.78.0"
|
"rollup": "^2.79.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vitest": {
|
"vitest": {
|
||||||
|
|
2
v1/vendor/github.com/wailsapp/wails/v2/internal/goversion/min.go
generated
vendored
|
@ -1,3 +1,3 @@
|
||||||
package goversion
|
package goversion
|
||||||
|
|
||||||
const MinRequirement string = "1.18"
|
const MinRequirement string = "1.20"
|
||||||
|
|
2
v1/vendor/github.com/wailsapp/wails/v2/pkg/assetserver/assethandler.go
generated
vendored
|
@ -110,7 +110,7 @@ func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveFile will try to load the file from the fs.FS and write it to the response
|
// serveFSFile will try to load the file from the fs.FS and write it to the response
|
||||||
func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error {
|
func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error {
|
||||||
if d.fs == nil {
|
if d.fs == nil {
|
||||||
return os.ErrNotExist
|
return os.ErrNotExist
|
||||||
|
|
5
v1/vendor/github.com/wailsapp/wails/v2/pkg/assetserver/assetserver.go
generated
vendored
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
|
"html/template"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||||
|
@ -67,9 +68,11 @@ func NewAssetServer(bindingsJSON string, options assetserver.Options, servingFro
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAssetServerWithHandler(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
|
func NewAssetServerWithHandler(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
if bindingsJSON != "" {
|
if bindingsJSON != "" {
|
||||||
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
|
escapedBindingsJSON := template.JSEscapeString(bindingsJSON)
|
||||||
|
buffer.WriteString(`window.wailsbindings='` + escapedBindingsJSON + `';` + "\n")
|
||||||
}
|
}
|
||||||
buffer.Write(runtime.RuntimeDesktopJS())
|
buffer.Write(runtime.RuntimeDesktopJS())
|
||||||
|
|
||||||
|
|
2
v1/vendor/github.com/wailsapp/wails/v2/pkg/assetserver/assetserver_webview.go
generated
vendored
|
@ -60,7 +60,7 @@ func (d *AssetServer) processWebViewRequest(r webview.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server.
|
// processWebViewRequestInternal processes the HTTP Request by faking a golang HTTP Server.
|
||||||
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
||||||
func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
|
func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
|
||||||
uri := "unknown"
|
uri := "unknown"
|
||||||
|
|
1
v1/vendor/github.com/wailsapp/wails/v2/pkg/options/mac/mac.go
generated
vendored
|
@ -21,6 +21,7 @@ type Options struct {
|
||||||
WebviewIsTransparent bool
|
WebviewIsTransparent bool
|
||||||
WindowIsTranslucent bool
|
WindowIsTranslucent bool
|
||||||
Preferences *Preferences
|
Preferences *Preferences
|
||||||
|
DisableZoom bool
|
||||||
// ActivationPolicy ActivationPolicy
|
// ActivationPolicy ActivationPolicy
|
||||||
About *AboutInfo
|
About *AboutInfo
|
||||||
OnFileOpen func(filePath string) `json:"-"`
|
OnFileOpen func(filePath string) `json:"-"`
|
||||||
|
|
2
v1/vendor/github.com/wailsapp/wails/v2/pkg/runtime/screen.go
generated
vendored
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
type Screen = frontend.Screen
|
type Screen = frontend.Screen
|
||||||
|
|
||||||
// ScreenGetAllScreens returns all screens
|
// ScreenGetAll returns all screens
|
||||||
func ScreenGetAll(ctx context.Context) ([]Screen, error) {
|
func ScreenGetAll(ctx context.Context) ([]Screen, error) {
|
||||||
appFrontend := getFrontend(ctx)
|
appFrontend := getFrontend(ctx)
|
||||||
return appFrontend.ScreenGetAll()
|
return appFrontend.ScreenGetAll()
|
||||||
|
|
4
v1/vendor/modules.txt
vendored
|
@ -77,7 +77,7 @@ github.com/tkrajina/go-reflector/reflector
|
||||||
# github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909
|
# github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/tylertravisty/go-utils/random
|
github.com/tylertravisty/go-utils/random
|
||||||
# github.com/tylertravisty/rumble-livestream-lib-go v0.5.1
|
# github.com/tylertravisty/rumble-livestream-lib-go v0.7.2
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/tylertravisty/rumble-livestream-lib-go
|
github.com/tylertravisty/rumble-livestream-lib-go
|
||||||
# github.com/valyala/bytebufferpool v1.0.0
|
# github.com/valyala/bytebufferpool v1.0.0
|
||||||
|
@ -98,7 +98,7 @@ github.com/wailsapp/mimetype
|
||||||
github.com/wailsapp/mimetype/internal/charset
|
github.com/wailsapp/mimetype/internal/charset
|
||||||
github.com/wailsapp/mimetype/internal/json
|
github.com/wailsapp/mimetype/internal/json
|
||||||
github.com/wailsapp/mimetype/internal/magic
|
github.com/wailsapp/mimetype/internal/magic
|
||||||
# github.com/wailsapp/wails/v2 v2.8.0
|
# github.com/wailsapp/wails/v2 v2.8.1
|
||||||
## explicit; go 1.20
|
## explicit; go 1.20
|
||||||
github.com/wailsapp/wails/v2
|
github.com/wailsapp/wails/v2
|
||||||
github.com/wailsapp/wails/v2/internal/app
|
github.com/wailsapp/wails/v2/internal/app
|
||||||
|
|