Implemented chatbot event triggers on follow, raid, rant, and subscribe
This commit is contained in:
parent
1e87346086
commit
562b90ebf7
82
v1/app.go
82
v1/app.go
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -190,7 +191,7 @@ func (a *App) processChat(event events.Chat) {
|
|||
|
||||
// TODO: implement this
|
||||
func (a *App) chatbotApiProcessor(event events.Api) {
|
||||
return
|
||||
a.chatbot.HandleApi(event)
|
||||
}
|
||||
|
||||
func (a *App) chatbotChatProcessor(event events.Chat) {
|
||||
|
@ -356,9 +357,9 @@ func (a *App) verifyAccounts() (int, error) {
|
|||
}
|
||||
loggedIn, err := client.LoggedIn()
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error check if account is logged in: %v", err)
|
||||
return -1, fmt.Errorf("error checking if account is logged in: %v", err)
|
||||
}
|
||||
if loggedIn {
|
||||
if loggedIn.User.LoggedIn {
|
||||
a.clients[*account.Username] = client
|
||||
} else {
|
||||
account.Cookies = nil
|
||||
|
@ -493,7 +494,24 @@ func (a *App) Login(username string, password string) error {
|
|||
return fmt.Errorf("Error logging in. Try again.")
|
||||
}
|
||||
if acct == nil {
|
||||
acct = &models.Account{nil, nil, &username, &cookiesS, nil, nil}
|
||||
loggedIn, err := client.LoggedIn()
|
||||
if err != nil {
|
||||
a.logError.Println("error checking if account is logged in:", err)
|
||||
return fmt.Errorf("Error logging in. Try again.")
|
||||
}
|
||||
|
||||
uid, found := strings.CutPrefix(loggedIn.User.ID, "_u")
|
||||
if !found {
|
||||
a.logError.Println("did not find uid prefix '_u' in response after checking if accounts is logged in")
|
||||
return fmt.Errorf("Error logging in. Try again.")
|
||||
}
|
||||
rumbleUsername := loggedIn.Data.Username
|
||||
if rumbleUsername == "" {
|
||||
a.logError.Println("username is empty in response after checking if accounts is logged in")
|
||||
return fmt.Errorf("Error logging in. Try again.")
|
||||
}
|
||||
|
||||
acct = &models.Account{nil, &uid, &rumbleUsername, &cookiesS, nil, nil}
|
||||
id, err := a.services.AccountS.Create(acct)
|
||||
if err != nil {
|
||||
a.logError.Println("error creating account:", err)
|
||||
|
@ -789,6 +807,27 @@ func (a *App) ActivateChannel(id int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *App) startPageApi(pi PageInfo) error {
|
||||
name := pi.String()
|
||||
if name == nil {
|
||||
return fmt.Errorf("page name is nil")
|
||||
}
|
||||
url := pi.KeyUrl()
|
||||
if url == nil {
|
||||
return fmt.Errorf("page key url is nil")
|
||||
}
|
||||
|
||||
if !a.producers.ApiP.Active(*name) {
|
||||
err := a.producers.ApiP.Start(*name, *url, 10*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting api: %v", err)
|
||||
}
|
||||
runtime.EventsEmit(a.wails, "ApiActive-"+*name, true)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// If page is inactivate, activate.
|
||||
// If page is active, deactivate.
|
||||
func (a *App) activatePage(pi PageInfo) error {
|
||||
|
@ -1400,6 +1439,41 @@ func (a *App) RunChatbotRule(rule *chatbot.Rule) error {
|
|||
a.logError.Println("error starting chat producer:", err)
|
||||
// TODO: send error to UI that chatbot URL could not be started
|
||||
//runtime.EventsEmit("Ch")
|
||||
return fmt.Errorf("Error connecting to chat. Try again.")
|
||||
}
|
||||
|
||||
page := rule.Page()
|
||||
if page != nil {
|
||||
switch page.Prefix {
|
||||
case chatbot.PrefixAccount:
|
||||
acct, err := a.services.AccountS.ByUsername(page.Name)
|
||||
if err != nil {
|
||||
a.logError.Println("error getting account by username:", err)
|
||||
return fmt.Errorf("Error getting account to monitor. Check rule and try again.")
|
||||
}
|
||||
if acct == nil {
|
||||
return fmt.Errorf("Account to monitor does not exist. Check rule and try again.")
|
||||
}
|
||||
err = a.startPageApi(acct)
|
||||
if err != nil {
|
||||
a.logError.Println("error starting page api:", err)
|
||||
return fmt.Errorf("Error starting API for account in rule. Try again.")
|
||||
}
|
||||
case chatbot.PrefixChannel:
|
||||
channel, err := a.services.ChannelS.ByName(page.Name)
|
||||
if err != nil {
|
||||
a.logError.Println("error getting channel by name:", err)
|
||||
return fmt.Errorf("Error getting channel to monitor. Check rule and try again.")
|
||||
}
|
||||
if channel == nil {
|
||||
return fmt.Errorf("Channel to monitor does not exist. Check rule and try again.")
|
||||
}
|
||||
err = a.startPageApi(channel)
|
||||
if err != nil {
|
||||
a.logError.Println("error starting page api:", err)
|
||||
return fmt.Errorf("Error starting API for channel in rule. Try again.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = a.chatbot.Run(rule, *mChatbot.Url)
|
||||
|
|
BIN
v1/frontend/src/assets/icons/twbs/chevron-down.png
Normal file
BIN
v1/frontend/src/assets/icons/twbs/chevron-down.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
|
@ -1,4 +1,5 @@
|
|||
import chess_rook from './icons/Font-Awesome/chess-rook.png';
|
||||
import chevron_down from './icons/twbs/chevron-down.png';
|
||||
import chevron_left from './icons/twbs/chevron-left.png';
|
||||
import chevron_right from './icons/twbs/chevron-right.png';
|
||||
import circle_green_background from './icons/twbs/circle-green-background.png';
|
||||
|
@ -25,6 +26,7 @@ import logo from './logo/logo.png';
|
|||
|
||||
export const ChessRook = chess_rook;
|
||||
export const ChevronLeft = chevron_left;
|
||||
export const ChevronDown = chevron_down;
|
||||
export const ChevronRight = chevron_right;
|
||||
export const CircleGreenBackground = circle_green_background;
|
||||
export const CircleRedBackground = circle_red_background;
|
||||
|
|
|
@ -184,6 +184,100 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-event-body {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-event-body-bottom {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 50%;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-event-body-top {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 50%;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-event-setting {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-event-options {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
height: 158.5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-event-options-follow {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chatbot-modal-event-options-label {
|
||||
align-items: center;
|
||||
color: #eee;
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chatbot-modal-event-options-label-warning {
|
||||
align-items: center;
|
||||
color: #f23160;
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chatbot-modal-option-label {
|
||||
align-items: center;
|
||||
color: #eee;
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.chatbot-modal-option-label-warning {
|
||||
align-items: center;
|
||||
color: #f23160;
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.chatbot-modal-pages {
|
||||
background-color: white;
|
||||
/* border: 1px solid #D6E0EA; */
|
||||
|
@ -248,6 +342,13 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chatbot-modal-setting-description-warning {
|
||||
color: #f23160;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.chatbot-modal-textarea {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
|
@ -461,6 +562,10 @@ input:checked + .chatbot-modal-toggle-slider:before {
|
|||
padding-right: 1px;
|
||||
}
|
||||
|
||||
.dropdown-option-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timer-input {
|
||||
border: none;
|
||||
border-radius: 34px;
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
StopBigRed,
|
||||
} from '../assets';
|
||||
import './ChatBot.css';
|
||||
import { DropDown } from './DropDown';
|
||||
|
||||
function ChatBot(props) {
|
||||
const [chatbots, setChatbots] = useState([]);
|
||||
|
@ -326,12 +327,52 @@ function ChatbotRule(props) {
|
|||
return hours + 'h ' + minutes + 'm ' + seconds + 's';
|
||||
};
|
||||
|
||||
const printTriggerEvent = () => {
|
||||
const onEvent = props.rule.parameters.trigger.on_event;
|
||||
switch (true) {
|
||||
case onEvent.from_account !== undefined && onEvent.from_account !== null:
|
||||
const fromAccount = props.rule.parameters.trigger.on_event.from_account;
|
||||
switch (true) {
|
||||
case fromAccount.on_follow !== undefined && fromAccount.on_follow !== null:
|
||||
return 'Follow';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
break;
|
||||
case onEvent.from_channel !== undefined && onEvent.from_channel !== null:
|
||||
const fromChannel = props.rule.parameters.trigger.on_event.from_channel;
|
||||
switch (true) {
|
||||
case fromChannel.on_follow !== undefined && fromChannel.on_follow !== null:
|
||||
return 'Follow';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
break;
|
||||
case onEvent.from_live_stream !== undefined && onEvent.from_live_stream !== null:
|
||||
const fromLiveStream = props.rule.parameters.trigger.on_event.from_live_stream;
|
||||
switch (true) {
|
||||
case fromLiveStream.on_raid !== undefined && fromLiveStream.on_raid !== null:
|
||||
return 'Raid';
|
||||
case fromLiveStream.on_rant !== undefined && fromLiveStream.on_rant !== null:
|
||||
return 'Rant';
|
||||
case fromLiveStream.on_sub !== undefined && fromLiveStream.on_sub !== null:
|
||||
return 'Sub';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const printTrigger = () => {
|
||||
let trigger = props.rule.parameters.trigger;
|
||||
|
||||
switch (true) {
|
||||
case trigger.on_command !== undefined && trigger.on_command !== null:
|
||||
return trigger.on_command.command;
|
||||
case trigger.on_event !== undefined && trigger.on_event !== null:
|
||||
return printTriggerEvent();
|
||||
case trigger.on_timer !== undefined && trigger.on_timer !== null:
|
||||
return prettyTimer(props.rule.parameters.trigger.on_timer);
|
||||
}
|
||||
|
@ -372,6 +413,8 @@ function ChatbotRule(props) {
|
|||
switch (true) {
|
||||
case trigger.on_command !== undefined && trigger.on_command !== null:
|
||||
return 'on_command';
|
||||
case trigger.on_event !== undefined && trigger.on_event !== null:
|
||||
return 'on_event';
|
||||
case trigger.on_timer !== undefined && trigger.on_timer !== null:
|
||||
return 'on_timer';
|
||||
}
|
||||
|
@ -600,6 +643,7 @@ function ModalRule(props) {
|
|||
});
|
||||
};
|
||||
|
||||
console.log('back:', back);
|
||||
return (
|
||||
<>
|
||||
{error !== '' && (
|
||||
|
@ -613,6 +657,49 @@ function ModalRule(props) {
|
|||
onSubmit={() => setError('')}
|
||||
/>
|
||||
)}
|
||||
{stage === 'event-from_stream' && (
|
||||
<ModalRuleEventStream
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'message' && (
|
||||
<ModalRuleMessage
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'review' && (
|
||||
<ModalRuleReview
|
||||
edit={edit}
|
||||
setEdit={setEdit}
|
||||
new={props.new}
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
onDelete={props.onDelete}
|
||||
onSubmit={submit}
|
||||
rule={rule}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'sender' && (
|
||||
<ModalRuleSender
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'trigger' && (
|
||||
<ModalRuleTrigger
|
||||
onClose={props.onClose}
|
||||
|
@ -632,6 +719,16 @@ function ModalRule(props) {
|
|||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'trigger-on_event' && (
|
||||
<ModalRuleTriggerEvent
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'trigger-on_timer' && (
|
||||
<ModalRuleTriggerTimer
|
||||
onBack={goBack}
|
||||
|
@ -642,39 +739,6 @@ function ModalRule(props) {
|
|||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'message' && (
|
||||
<ModalRuleMessage
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'sender' && (
|
||||
<ModalRuleSender
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
rule={rule}
|
||||
setRule={setRule}
|
||||
setStage={updateStage}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
{stage === 'review' && (
|
||||
<ModalRuleReview
|
||||
edit={edit}
|
||||
setEdit={setEdit}
|
||||
new={props.new}
|
||||
onBack={goBack}
|
||||
onClose={props.onClose}
|
||||
onDelete={props.onDelete}
|
||||
onSubmit={submit}
|
||||
rule={rule}
|
||||
show={props.show}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -707,6 +771,20 @@ function ModalRuleTrigger(props) {
|
|||
next('trigger-on_command');
|
||||
};
|
||||
|
||||
const triggerOnEvent = () => {
|
||||
const rule = props.rule;
|
||||
if (rule.trigger == undefined || rule.trigger == null) {
|
||||
rule.trigger = {};
|
||||
}
|
||||
|
||||
rule.trigger.on_command = null;
|
||||
rule.trigger.on_timer = null;
|
||||
|
||||
props.setRule(rule);
|
||||
|
||||
next('trigger-on_event');
|
||||
};
|
||||
|
||||
const triggerOnTimer = () => {
|
||||
const rule = props.rule;
|
||||
if (rule.trigger == undefined || rule.trigger == null) {
|
||||
|
@ -739,18 +817,15 @@ function ModalRuleTrigger(props) {
|
|||
src={ChevronRight}
|
||||
/>
|
||||
</button>
|
||||
{/* <button
|
||||
className='modal-add-account-channel-button'
|
||||
onClick={() => next('trigger-stream_event')}
|
||||
>
|
||||
<button className='modal-add-account-channel-button' onClick={triggerOnEvent}>
|
||||
<div className='modal-add-account-channel-button-left'>
|
||||
<span>Stream Event</span>
|
||||
<span>Event</span>
|
||||
</div>
|
||||
<img
|
||||
className='modal-add-account-channel-button-right-icon'
|
||||
src={ChevronRight}
|
||||
/>
|
||||
</button> */}
|
||||
</button>
|
||||
<button className='modal-add-account-channel-button' onClick={triggerOnTimer}>
|
||||
<div className='modal-add-account-channel-button-left'>
|
||||
<span>Timer</span>
|
||||
|
@ -958,6 +1033,456 @@ function ModalRuleTriggerCommand(props) {
|
|||
);
|
||||
}
|
||||
|
||||
function ModalRuleTriggerEvent(props) {
|
||||
const [event, setEvent] = useState('');
|
||||
const [validEvent, setValidEvent] = useState(true);
|
||||
const updateEvent = (e) => {
|
||||
setEvent(e);
|
||||
if (e !== event) {
|
||||
setOptions({});
|
||||
setValidOptions(true);
|
||||
switch (e) {
|
||||
case 'Rant':
|
||||
setOptions({ min_amount: 0, max_amount: 0 });
|
||||
break;
|
||||
default:
|
||||
setOptions({});
|
||||
}
|
||||
}
|
||||
setValidEvent(true);
|
||||
};
|
||||
const [options, setOptions] = useState({});
|
||||
const [validOptions, setValidOptions] = useState(true);
|
||||
const [source, setSource] = useState('');
|
||||
const [validSource, setValidSource] = useState(true);
|
||||
const updateSource = (s) => {
|
||||
setSource(s);
|
||||
if (s !== source) {
|
||||
setEvent('');
|
||||
setValidOptions(true);
|
||||
}
|
||||
setValidSource(true);
|
||||
};
|
||||
const [parameters, setParameters] = useState({
|
||||
Account: { events: ['Follow'] },
|
||||
Channel: { events: ['Follow'] },
|
||||
'Live Stream': { events: ['Raid', 'Rant', 'Sub'] },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (props.rule.trigger.on_event === undefined || props.rule.trigger.on_event === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onEvent = props.rule.trigger.on_event;
|
||||
switch (true) {
|
||||
case onEvent.from_account !== undefined && onEvent.from_account !== null:
|
||||
setSource('Account');
|
||||
const fromAccount = props.rule.trigger.on_event.from_account;
|
||||
switch (true) {
|
||||
case fromAccount.on_follow !== undefined && fromAccount.on_follow !== null:
|
||||
setEvent('Follow');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case onEvent.from_channel !== undefined && onEvent.from_channel !== null:
|
||||
setSource('Channel');
|
||||
const fromChannel = props.rule.trigger.on_event.from_channel;
|
||||
switch (true) {
|
||||
case fromChannel.on_follow !== undefined && fromChannel.on_follow !== null:
|
||||
setEvent('Follow');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case onEvent.from_live_stream !== undefined && onEvent.from_live_stream !== null:
|
||||
setSource('Live Stream');
|
||||
const fromLiveStream = props.rule.trigger.on_event.from_live_stream;
|
||||
switch (true) {
|
||||
case fromLiveStream.on_raid !== undefined && fromLiveStream.on_raid !== null:
|
||||
setEvent('Raid');
|
||||
break;
|
||||
case fromLiveStream.on_rant !== undefined && fromLiveStream.on_rant !== null:
|
||||
setEvent('Rant');
|
||||
setOptions(props.rule.trigger.on_event.from_live_stream.on_rant);
|
||||
break;
|
||||
case fromLiveStream.on_sub !== undefined && fromLiveStream.on_sub !== null:
|
||||
setEvent('Sub');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const validRantOptions = () => {
|
||||
if (isNaN(options.min_amount) || isNaN(options.max_amount)) {
|
||||
setValidOptions(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.max_amount !== 0 && options.min_amount > options.max_amount) {
|
||||
setValidOptions(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const fromAccount = () => {
|
||||
let from_account = {};
|
||||
switch (event) {
|
||||
case 'Follow':
|
||||
from_account.name = options.page;
|
||||
from_account.on_follow = {};
|
||||
break;
|
||||
default:
|
||||
setValidEvent(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const rule = props.rule;
|
||||
if (rule.trigger.on_event == undefined || rule.trigger.on_event == null) {
|
||||
rule.trigger.on_event = {};
|
||||
}
|
||||
|
||||
rule.trigger.on_event.from_account = from_account;
|
||||
rule.trigger.on_event.from_channel = null;
|
||||
rule.trigger.on_event.from_live_stream = null;
|
||||
|
||||
props.setRule(rule);
|
||||
next('message');
|
||||
};
|
||||
|
||||
const fromChannel = () => {
|
||||
let from_channel = {};
|
||||
switch (event) {
|
||||
case 'Follow':
|
||||
from_channel.name = options.page;
|
||||
from_channel.on_follow = {};
|
||||
break;
|
||||
default:
|
||||
setValidEvent(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const rule = props.rule;
|
||||
if (rule.trigger.on_event == undefined || rule.trigger.on_event == null) {
|
||||
rule.trigger.on_event = {};
|
||||
}
|
||||
|
||||
rule.trigger.on_event.from_account = null;
|
||||
rule.trigger.on_event.from_channel = from_channel;
|
||||
rule.trigger.on_event.from_live_stream = null;
|
||||
|
||||
props.setRule(rule);
|
||||
next('message');
|
||||
};
|
||||
|
||||
const fromLiveStream = () => {
|
||||
let from_live_stream = {};
|
||||
switch (event) {
|
||||
case 'Raid':
|
||||
from_live_stream.on_raid = {};
|
||||
break;
|
||||
case 'Rant':
|
||||
if (!validRantOptions()) {
|
||||
return;
|
||||
}
|
||||
from_live_stream.on_rant = options;
|
||||
break;
|
||||
case 'Sub':
|
||||
from_live_stream.on_sub = {};
|
||||
break;
|
||||
default:
|
||||
setValidEvent(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const rule = props.rule;
|
||||
if (rule.trigger.on_event == undefined || rule.trigger.on_event == null) {
|
||||
rule.trigger.on_event = {};
|
||||
}
|
||||
|
||||
rule.trigger.on_event.from_account = null;
|
||||
rule.trigger.on_event.from_channel = null;
|
||||
rule.trigger.on_event.from_live_stream = from_live_stream;
|
||||
|
||||
props.setRule(rule);
|
||||
next('message');
|
||||
};
|
||||
|
||||
const back = () => {
|
||||
props.onBack();
|
||||
};
|
||||
|
||||
const next = (stage) => {
|
||||
props.setStage(stage);
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
switch (source) {
|
||||
case 'Account':
|
||||
fromAccount();
|
||||
break;
|
||||
case 'Channel':
|
||||
fromChannel();
|
||||
break;
|
||||
case 'Live Stream':
|
||||
fromLiveStream();
|
||||
break;
|
||||
default:
|
||||
setValidSource(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
cancelButton={'Back'}
|
||||
onCancel={back}
|
||||
onClose={props.onClose}
|
||||
show={props.show}
|
||||
submitButton={'Next'}
|
||||
onSubmit={submit}
|
||||
style={{ height: '480px', minHeight: '480px', width: '360px', minWidth: '360px' }}
|
||||
>
|
||||
<div className='modal-add-account-channel'>
|
||||
<span className='modal-add-account-channel-title'>Configure Event</span>
|
||||
<div className='chatbot-modal-event-body'>
|
||||
<div className='chatbot-modal-event-body-top'>
|
||||
<div className='chatbot-modal-event-setting'>
|
||||
<label
|
||||
className={
|
||||
validSource
|
||||
? 'chatbot-modal-option-label'
|
||||
: 'chatbot-modal-option-label-warning'
|
||||
}
|
||||
>
|
||||
Source{!validSource && '*'}
|
||||
</label>
|
||||
<div style={{ width: '250px' }}>
|
||||
<DropDown
|
||||
options={Object.keys(parameters)}
|
||||
select={updateSource}
|
||||
selected={source}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='chatbot-modal-event-setting'>
|
||||
<label
|
||||
className={
|
||||
validEvent
|
||||
? 'chatbot-modal-option-label'
|
||||
: 'chatbot-modal-option-label-warning'
|
||||
}
|
||||
>
|
||||
Event{!validEvent && '*'}
|
||||
</label>
|
||||
<div style={{ width: '250px' }}>
|
||||
{source !== '' && (
|
||||
<DropDown
|
||||
options={parameters[source].events}
|
||||
select={updateEvent}
|
||||
selected={event}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='chatbot-modal-event-body-bottom'>
|
||||
<label
|
||||
className={
|
||||
validOptions
|
||||
? 'chatbot-modal-event-options-label'
|
||||
: 'chatbot-modal-event-options-label-warning'
|
||||
}
|
||||
>
|
||||
{validOptions ? 'Options' : 'Verify Options'}
|
||||
</label>
|
||||
<div className='chatbot-modal-event-options'>
|
||||
{event === 'Rant' && (
|
||||
<EventOptionsRant options={options} setOptions={setOptions} />
|
||||
)}
|
||||
{event === 'Follow' && (
|
||||
<EventOptionsFollow
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
source={source}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function EventOptionsFollow(props) {
|
||||
const [accounts, setAccounts] = useState({});
|
||||
const [page, setPage] = useState(props.options.page === undefined ? '' : props.options.page);
|
||||
const updatePage = (name) => {
|
||||
setPage(name);
|
||||
props.setOptions({ page: name });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
AccountList()
|
||||
.then((response) => {
|
||||
setAccounts(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const sortChannels = (channels) => {
|
||||
let sorted = [...channels].sort((a, b) =>
|
||||
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
|
||||
);
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const sortAccounts = () => {
|
||||
let keys = Object.keys(accounts);
|
||||
|
||||
let sorted = [...keys].sort((a, b) =>
|
||||
accounts[a].account.username.toLowerCase() > accounts[b].account.username.toLowerCase()
|
||||
? 1
|
||||
: -1
|
||||
);
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const sortPages = () => {
|
||||
let pages = [];
|
||||
|
||||
const keys = sortAccounts();
|
||||
keys.forEach((key, i) => {
|
||||
const account = accounts[key];
|
||||
if (props.source === 'Account') {
|
||||
pages.push(account.account.username);
|
||||
}
|
||||
if (props.source === 'Channel') {
|
||||
const channels = sortChannels(account.channels);
|
||||
channels.forEach((channel, j) => {
|
||||
pages.push(channel.name);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='modal-add-account-channel-body' style={{ height: '90%' }}>
|
||||
<div className='chatbot-modal-pages'>
|
||||
{sortPages().map((option, index) => (
|
||||
<div className={'chatbot-modal-page'} key={index}>
|
||||
<button
|
||||
className='chatbot-modal-page-button'
|
||||
onClick={() => updatePage(option)}
|
||||
style={{
|
||||
backgroundColor: page === option ? '#85c742' : '',
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EventOptionsRant(props) {
|
||||
const [minAmount, setMinAmount] = useState(
|
||||
isNaN(props.options.min_amount) ? 0 : props.options.min_amount
|
||||
);
|
||||
const updateMinAmount = (event) => {
|
||||
let amount = parseInt(event.target.value);
|
||||
if (isNaN(amount)) {
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
if (maxAmount !== 0 && amount > maxAmount) {
|
||||
setValidMaxAmount(false);
|
||||
} else {
|
||||
setValidMaxAmount(true);
|
||||
}
|
||||
|
||||
setMinAmount(event.target.value);
|
||||
props.setOptions({ min_amount: amount, max_amount: maxAmount });
|
||||
};
|
||||
const [maxAmount, setMaxAmount] = useState(
|
||||
isNaN(props.options.max_amount) ? 0 : props.options.max_amount
|
||||
);
|
||||
const updateMaxAmount = (event) => {
|
||||
let amount = parseInt(event.target.value);
|
||||
if (isNaN(amount)) {
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
if (amount !== 0) {
|
||||
if (amount < minAmount) {
|
||||
setValidMaxAmount(false);
|
||||
} else {
|
||||
setValidMaxAmount(true);
|
||||
}
|
||||
} else {
|
||||
setValidMaxAmount(true);
|
||||
}
|
||||
|
||||
setMaxAmount(amount);
|
||||
props.setOptions({ min_amount: minAmount, max_amount: amount });
|
||||
};
|
||||
const [validMaxAmount, setValidMaxAmount] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='chatbot-modal-setting' style={{ paddingTop: '0px' }}>
|
||||
<label className='chatbot-modal-setting-description'>Min rant amount</label>
|
||||
<div>
|
||||
<span className='command-rant-amount-symbol'>$</span>
|
||||
<input
|
||||
className='command-rant-amount'
|
||||
onChange={updateMinAmount}
|
||||
placeholder='0'
|
||||
size='4'
|
||||
type='text'
|
||||
value={minAmount === 0 ? '' : minAmount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='chatbot-modal-setting' style={{ paddingTop: '0px' }}>
|
||||
<label
|
||||
className={
|
||||
validMaxAmount
|
||||
? 'chatbot-modal-setting-description'
|
||||
: 'chatbot-modal-setting-description-warning'
|
||||
}
|
||||
>
|
||||
Max rant amount{!validMaxAmount && ' (>= min)'}
|
||||
</label>
|
||||
<div>
|
||||
<span className='command-rant-amount-symbol'>$</span>
|
||||
<input
|
||||
className='command-rant-amount'
|
||||
onChange={updateMaxAmount}
|
||||
placeholder='0'
|
||||
size='4'
|
||||
type='text'
|
||||
value={maxAmount === 0 ? '' : maxAmount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalRuleTriggerTimer(props) {
|
||||
const prependZero = (value) => {
|
||||
if (value < 10) {
|
||||
|
|
80
v1/frontend/src/components/DropDown.css
Normal file
80
v1/frontend/src/components/DropDown.css
Normal file
|
@ -0,0 +1,80 @@
|
|||
.dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border: 1px solid #061726;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.dropdown-menu-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-menu-background {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.dropdown-menu-option {
|
||||
background-color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #061726;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-menu-option-selected {
|
||||
background-color: #77b23b;
|
||||
}
|
||||
|
||||
.dropdown-menu-option:hover {
|
||||
background-color: #77b23b;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border: 1px solid #061726;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-toggle-text {
|
||||
color: #061726;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dropdown-toggle-icon {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
97
v1/frontend/src/components/DropDown.jsx
Normal file
97
v1/frontend/src/components/DropDown.jsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { ChevronDown } from '../assets';
|
||||
import './DropDown.css';
|
||||
|
||||
export function DropDown(props) {
|
||||
const [options, setOptions] = useState(props.options !== undefined ? props.options : []);
|
||||
const [selected, setSelected] = useState(props.selected !== undefined ? props.selected : '');
|
||||
const [toggled, setToggled] = useState(false);
|
||||
const toggle = () => {
|
||||
setToggled(!toggled);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(props.selected !== undefined ? props.selected : '');
|
||||
}, [props.selected]);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(props.options !== undefined ? props.options : []);
|
||||
}, [props.options]);
|
||||
|
||||
const select = (option) => {
|
||||
props.select(option);
|
||||
setSelected(option);
|
||||
toggle();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='dropdown'>
|
||||
<button className='dropdown-toggle' onClick={toggle}>
|
||||
<div style={{ width: '20px' }}></div>
|
||||
<span className='dropdown-toggle-text'>{selected}</span>
|
||||
<img className='dropdown-toggle-icon' src={ChevronDown} />
|
||||
</button>
|
||||
{toggled && (
|
||||
<DropDownMenu
|
||||
options={options}
|
||||
select={select}
|
||||
selected={selected}
|
||||
toggle={toggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DropDownMenu(props) {
|
||||
const menuRef = useRef();
|
||||
const { width } = menuWidth(menuRef);
|
||||
|
||||
return (
|
||||
<div className='dropdown-menu-container' ref={menuRef}>
|
||||
{width !== undefined && (
|
||||
<div className='dropdown-menu' style={{ width: width + 'px' }}>
|
||||
{props.options.map((option, index) => (
|
||||
<button
|
||||
className={
|
||||
props.selected === option
|
||||
? 'dropdown-menu-option dropdown-menu-option-selected'
|
||||
: 'dropdown-menu-option'
|
||||
}
|
||||
key={index}
|
||||
onClick={() => props.select(option)}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className='dropdown-menu-background' onClick={props.toggle}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const menuWidth = (menuRef) => {
|
||||
const [width, setWidth] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const getWidth = () => ({ width: menuRef.current.offsetWidth });
|
||||
|
||||
const handleResize = () => {
|
||||
setWidth(getWidth());
|
||||
};
|
||||
|
||||
if (menuRef.current) {
|
||||
setWidth(getWidth());
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, [menuRef]);
|
||||
|
||||
return width;
|
||||
};
|
|
@ -190,6 +190,10 @@ function AccountIcon(props) {
|
|||
setUsername(props.account.username);
|
||||
}, [props.account.username]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoggedIn(props.account.cookies !== null);
|
||||
}, [props.account.cookies]);
|
||||
|
||||
useEffect(() => {
|
||||
if (username !== '') {
|
||||
PageStatus(pageName(username));
|
||||
|
|
|
@ -6,7 +6,7 @@ toolchain go1.22.0
|
|||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.7.2
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.9.0
|
||||
github.com/wailsapp/wails/v2 v2.8.1
|
||||
)
|
||||
|
||||
|
|
|
@ -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/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/rumble-livestream-lib-go v0.7.2 h1:TRGTKhxB+uK0gnIC+rXbRxfFjMJxPHhjZzbsjDSpK+o=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.7.2/go.mod h1:Odkqvsn+2eoWV3ePcj257Ga0bdOqV4JBTfOJcQ+Sqf8=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.9.0 h1:G1b/uac43dq7BG7NzcLeRLPOfOu8GyjViE9s48qhwhw=
|
||||
github.com/tylertravisty/rumble-livestream-lib-go v0.9.0/go.mod h1:Odkqvsn+2eoWV3ePcj257Ga0bdOqV4JBTfOJcQ+Sqf8=
|
||||
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/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
|
|
|
@ -43,12 +43,32 @@ func (c clients) byUsernameLivestream(username string, url string) *rumblelivest
|
|||
return user.byLivestream(url)
|
||||
}
|
||||
|
||||
type followReceiver struct {
|
||||
apiCh chan events.ApiFollower
|
||||
latest time.Time
|
||||
}
|
||||
|
||||
type receiver struct {
|
||||
onCommand map[string]map[int64]chan events.Chat
|
||||
onCommandMu sync.Mutex
|
||||
//onFollow []chan ???
|
||||
//onRant []chan events.Chat
|
||||
//onSubscribe []chan events.Chat
|
||||
onFollow map[int64]*followReceiver
|
||||
onFollowMu sync.Mutex
|
||||
onRaid map[int64]chan events.Chat
|
||||
onRaidMu sync.Mutex
|
||||
onRant map[int64]chan events.Chat
|
||||
onRantMu sync.Mutex
|
||||
onSub map[int64]chan events.Chat
|
||||
onSubMu sync.Mutex
|
||||
}
|
||||
|
||||
func newReceiver() *receiver {
|
||||
return &receiver{
|
||||
onCommand: map[string]map[int64]chan events.Chat{},
|
||||
onFollow: map[int64]*followReceiver{},
|
||||
onRaid: map[int64]chan events.Chat{},
|
||||
onRant: map[int64]chan events.Chat{},
|
||||
onSub: map[int64]chan events.Chat{},
|
||||
}
|
||||
}
|
||||
|
||||
type Bot struct {
|
||||
|
@ -107,6 +127,10 @@ func (cb *Chatbot) addClient(username string, livestreamUrl string) (*rumblelive
|
|||
return nil, fmt.Errorf("error querying account by username: %v", err)
|
||||
}
|
||||
|
||||
if account.Cookies == nil {
|
||||
return nil, fmt.Errorf("account cookies are nil")
|
||||
}
|
||||
|
||||
var cookies []*http.Cookie
|
||||
err = json.Unmarshal([]byte(*account.Cookies), &cookies)
|
||||
if err != nil {
|
||||
|
@ -154,10 +178,17 @@ func (cb *Chatbot) Run(rule *Rule, url string) error {
|
|||
}
|
||||
}
|
||||
|
||||
page := ""
|
||||
rulePage := rule.Page()
|
||||
if rulePage != nil {
|
||||
page = rulePage.Prefix + strings.ReplaceAll(rulePage.Name, " ", "")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
runner := &Runner{
|
||||
cancel: cancel,
|
||||
client: client,
|
||||
page: page,
|
||||
rule: *rule,
|
||||
wails: cb.wails,
|
||||
}
|
||||
|
@ -187,13 +218,18 @@ func (cb *Chatbot) initRunner(runner *Runner) error {
|
|||
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)
|
||||
}
|
||||
case runner.rule.Parameters.Trigger.OnEvent != nil:
|
||||
err = cb.initRunnerEvent(runner)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing event: %v", err)
|
||||
}
|
||||
case runner.rule.Parameters.Trigger.OnTimer != nil:
|
||||
runner.run = runner.runOnTimer
|
||||
}
|
||||
|
||||
// cb.runnersMu.Lock()
|
||||
|
@ -233,12 +269,12 @@ func (cb *Chatbot) initRunnerCommand(runner *Runner) error {
|
|||
defer cb.receiversMu.Unlock()
|
||||
rcvr, exists := cb.receivers[runner.client.LiveStreamUrl]
|
||||
if !exists {
|
||||
rcvr = &receiver{
|
||||
onCommand: map[string]map[int64]chan events.Chat{},
|
||||
}
|
||||
rcvr = newReceiver()
|
||||
cb.receivers[runner.client.LiveStreamUrl] = rcvr
|
||||
}
|
||||
|
||||
rcvr.onCommandMu.Lock()
|
||||
defer rcvr.onCommandMu.Unlock()
|
||||
chans, exists := rcvr.onCommand[cmd]
|
||||
if !exists {
|
||||
chans = map[int64]chan events.Chat{}
|
||||
|
@ -249,6 +285,164 @@ func (cb *Chatbot) initRunnerCommand(runner *Runner) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEvent(runner *Runner) error {
|
||||
event := runner.rule.Parameters.Trigger.OnEvent
|
||||
switch {
|
||||
case event.FromAccount != nil:
|
||||
return cb.initRunnerEventFromAccount(runner)
|
||||
case event.FromChannel != nil:
|
||||
return cb.initRunnerEventFromChannel(runner)
|
||||
case event.FromLiveStream != nil:
|
||||
return cb.initRunnerEventFromLiveStream(runner)
|
||||
}
|
||||
|
||||
return fmt.Errorf("runner event not supported")
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEventFromAccount(runner *Runner) error {
|
||||
fromAccount := runner.rule.Parameters.Trigger.OnEvent.FromAccount
|
||||
switch {
|
||||
case fromAccount.OnFollow != nil:
|
||||
return cb.initRunnerEventFromAccountOnFollow(runner)
|
||||
}
|
||||
|
||||
return fmt.Errorf("runner event not supported")
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEventFromAccountOnFollow(runner *Runner) error {
|
||||
runner.run = runner.runOnEventFromAccountOnFollow
|
||||
|
||||
apiCh := make(chan events.ApiFollower, 10)
|
||||
runner.apiCh = apiCh
|
||||
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
rcvr, exists := cb.receivers[runner.page]
|
||||
if !exists {
|
||||
rcvr = newReceiver()
|
||||
cb.receivers[runner.page] = rcvr
|
||||
}
|
||||
|
||||
// TODO: should I check if channel already exists, if so delete it?
|
||||
rcvr.onFollowMu.Lock()
|
||||
defer rcvr.onFollowMu.Unlock()
|
||||
rcvr.onFollow[*runner.rule.ID] = &followReceiver{apiCh, time.Now()}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEventFromChannel(runner *Runner) error {
|
||||
fromChannel := runner.rule.Parameters.Trigger.OnEvent.FromChannel
|
||||
switch {
|
||||
case fromChannel.OnFollow != nil:
|
||||
return cb.initRunnerEventFromChannelOnFollow(runner)
|
||||
}
|
||||
|
||||
return fmt.Errorf("runner event not supported")
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEventFromChannelOnFollow(runner *Runner) error {
|
||||
runner.run = runner.runOnEventFromChannelOnFollow
|
||||
|
||||
apiCh := make(chan events.ApiFollower, 10)
|
||||
runner.apiCh = apiCh
|
||||
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
rcvr, exists := cb.receivers[runner.page]
|
||||
if !exists {
|
||||
rcvr = newReceiver()
|
||||
cb.receivers[runner.page] = rcvr
|
||||
}
|
||||
|
||||
// TODO: should I check if channel already exists, if so delete it?
|
||||
rcvr.onFollowMu.Lock()
|
||||
defer rcvr.onFollowMu.Unlock()
|
||||
rcvr.onFollow[*runner.rule.ID] = &followReceiver{apiCh, time.Now()}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEventFromLiveStream(runner *Runner) error {
|
||||
fromLiveStream := runner.rule.Parameters.Trigger.OnEvent.FromLiveStream
|
||||
switch {
|
||||
case fromLiveStream.OnRaid != nil:
|
||||
return cb.initRunnerEventFromLiveStreamOnRaid(runner)
|
||||
case fromLiveStream.OnRant != nil:
|
||||
return cb.initRunnerEventFromLiveStreamOnRant(runner)
|
||||
case fromLiveStream.OnSub != nil:
|
||||
return cb.initRunnerEventFromLiveStreamOnSub(runner)
|
||||
}
|
||||
|
||||
return fmt.Errorf("runner event not supported")
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEventFromLiveStreamOnRaid(runner *Runner) error {
|
||||
runner.run = runner.runOnEventFromLiveStreamOnRaid
|
||||
|
||||
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 = newReceiver()
|
||||
cb.receivers[runner.client.LiveStreamUrl] = rcvr
|
||||
}
|
||||
|
||||
// TODO: should I check if channel already exists, if so delete it?
|
||||
rcvr.onRaidMu.Lock()
|
||||
defer rcvr.onRaidMu.Unlock()
|
||||
rcvr.onRaid[*runner.rule.ID] = chatCh
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEventFromLiveStreamOnRant(runner *Runner) error {
|
||||
runner.run = runner.runOnEventFromLiveStreamOnRant
|
||||
|
||||
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 = newReceiver()
|
||||
cb.receivers[runner.client.LiveStreamUrl] = rcvr
|
||||
}
|
||||
|
||||
// TODO: should I check if channel already exists, if so delete it?
|
||||
rcvr.onRantMu.Lock()
|
||||
defer rcvr.onRantMu.Unlock()
|
||||
rcvr.onRant[*runner.rule.ID] = chatCh
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) initRunnerEventFromLiveStreamOnSub(runner *Runner) error {
|
||||
runner.run = runner.runOnEventFromLiveStreamOnSub
|
||||
|
||||
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 = newReceiver()
|
||||
cb.receivers[runner.client.LiveStreamUrl] = rcvr
|
||||
}
|
||||
|
||||
// TODO: should I check if channel already exists, if so delete it?
|
||||
rcvr.onSubMu.Lock()
|
||||
defer rcvr.onSubMu.Unlock()
|
||||
rcvr.onSub[*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")
|
||||
|
@ -312,12 +506,6 @@ func (cb *Chatbot) stop(rule *Rule) error {
|
|||
}
|
||||
|
||||
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]
|
||||
|
@ -334,7 +522,6 @@ func (cb *Chatbot) stopRunner(chatbotID int64, ruleID int64) bool {
|
|||
|
||||
stopped := true
|
||||
runner.stop()
|
||||
// delete(cb.runners, id)
|
||||
delete(bot.runners, ruleID)
|
||||
|
||||
switch {
|
||||
|
@ -343,6 +530,11 @@ func (cb *Chatbot) stopRunner(chatbotID int64, ruleID int64) bool {
|
|||
if err != nil {
|
||||
cb.logError.Println("error closing runner command:", err)
|
||||
}
|
||||
case runner.rule.Parameters.Trigger.OnEvent != nil:
|
||||
err := cb.closeRunnerEvent(runner)
|
||||
if err != nil {
|
||||
cb.logError.Println("error closing runner event:", err)
|
||||
}
|
||||
}
|
||||
|
||||
return stopped
|
||||
|
@ -361,6 +553,9 @@ func (cb *Chatbot) closeRunnerCommand(runner *Runner) error {
|
|||
return fmt.Errorf("receiver for runner does not exist")
|
||||
}
|
||||
|
||||
rcvr.onCommandMu.Lock()
|
||||
defer rcvr.onCommandMu.Unlock()
|
||||
|
||||
cmd := runner.rule.Parameters.Trigger.OnCommand.Command
|
||||
chans, exists := rcvr.onCommand[cmd]
|
||||
if !exists {
|
||||
|
@ -378,8 +573,193 @@ func (cb *Chatbot) closeRunnerCommand(runner *Runner) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) HandleChat(event events.Chat) {
|
||||
func (cb *Chatbot) closeRunnerEvent(runner *Runner) error {
|
||||
if runner == nil || runner.rule.ID == nil || runner.rule.Parameters == nil || runner.rule.Parameters.Trigger == nil || runner.rule.Parameters.Trigger.OnEvent == nil {
|
||||
return fmt.Errorf("invalid runner event")
|
||||
}
|
||||
|
||||
switch {
|
||||
case runner.rule.Parameters.Trigger.OnEvent.FromAccount != nil:
|
||||
return cb.closeRunnerEventFromAccount(runner)
|
||||
case runner.rule.Parameters.Trigger.OnEvent.FromChannel != nil:
|
||||
return cb.closeRunnerEventFromChannel(runner)
|
||||
case runner.rule.Parameters.Trigger.OnEvent.FromLiveStream != nil:
|
||||
return cb.closeRunnerEventFromLiveStream(runner)
|
||||
}
|
||||
|
||||
return fmt.Errorf("runner event not supported")
|
||||
}
|
||||
|
||||
func (cb *Chatbot) closeRunnerEventFromAccount(runner *Runner) error {
|
||||
if runner == nil || runner.rule.ID == nil || runner.rule.Parameters == nil || runner.rule.Parameters.Trigger == nil || runner.rule.Parameters.Trigger.OnEvent == nil || runner.rule.Parameters.Trigger.OnEvent.FromAccount == nil {
|
||||
return fmt.Errorf("invalid runner event")
|
||||
}
|
||||
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
|
||||
rcvr, exists := cb.receivers[runner.page]
|
||||
if !exists {
|
||||
return fmt.Errorf("receiver for runner does not exist")
|
||||
}
|
||||
|
||||
fromAccount := runner.rule.Parameters.Trigger.OnEvent.FromAccount
|
||||
switch {
|
||||
case fromAccount.OnFollow != nil:
|
||||
rcvr.onFollowMu.Lock()
|
||||
defer rcvr.onFollowMu.Unlock()
|
||||
followR, exists := rcvr.onFollow[*runner.rule.ID]
|
||||
if !exists {
|
||||
return fmt.Errorf("channel for runner does not exist")
|
||||
}
|
||||
close(followR.apiCh)
|
||||
delete(rcvr.onFollow, *runner.rule.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) closeRunnerEventFromChannel(runner *Runner) error {
|
||||
if runner == nil || runner.rule.ID == nil || runner.rule.Parameters == nil || runner.rule.Parameters.Trigger == nil || runner.rule.Parameters.Trigger.OnEvent == nil || runner.rule.Parameters.Trigger.OnEvent.FromChannel == nil {
|
||||
return fmt.Errorf("invalid runner event")
|
||||
}
|
||||
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
|
||||
rcvr, exists := cb.receivers[runner.page]
|
||||
if !exists {
|
||||
return fmt.Errorf("receiver for runner does not exist")
|
||||
}
|
||||
|
||||
fromChannel := runner.rule.Parameters.Trigger.OnEvent.FromChannel
|
||||
switch {
|
||||
case fromChannel.OnFollow != nil:
|
||||
rcvr.onFollowMu.Lock()
|
||||
defer rcvr.onFollowMu.Unlock()
|
||||
followR, exists := rcvr.onFollow[*runner.rule.ID]
|
||||
if !exists {
|
||||
return fmt.Errorf("channel for runner does not exist")
|
||||
}
|
||||
close(followR.apiCh)
|
||||
delete(rcvr.onFollow, *runner.rule.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) closeRunnerEventFromLiveStream(runner *Runner) error {
|
||||
if runner == nil || runner.rule.ID == nil || runner.rule.Parameters == nil || runner.rule.Parameters.Trigger == nil || runner.rule.Parameters.Trigger.OnEvent == nil || runner.rule.Parameters.Trigger.OnEvent.FromLiveStream == nil {
|
||||
return fmt.Errorf("invalid runner event")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
fromLiveStream := runner.rule.Parameters.Trigger.OnEvent.FromLiveStream
|
||||
switch {
|
||||
case fromLiveStream.OnRaid != nil:
|
||||
rcvr.onRaidMu.Lock()
|
||||
defer rcvr.onRaidMu.Unlock()
|
||||
ch, exists := rcvr.onRaid[*runner.rule.ID]
|
||||
if !exists {
|
||||
return fmt.Errorf("channel for runner does not exist")
|
||||
}
|
||||
close(ch)
|
||||
delete(rcvr.onRaid, *runner.rule.ID)
|
||||
case fromLiveStream.OnRant != nil:
|
||||
rcvr.onRantMu.Lock()
|
||||
defer rcvr.onRantMu.Unlock()
|
||||
ch, exists := rcvr.onRant[*runner.rule.ID]
|
||||
if !exists {
|
||||
return fmt.Errorf("channel for runner does not exist")
|
||||
}
|
||||
close(ch)
|
||||
delete(rcvr.onRant, *runner.rule.ID)
|
||||
case fromLiveStream.OnSub != nil:
|
||||
rcvr.onSubMu.Lock()
|
||||
defer rcvr.onSubMu.Unlock()
|
||||
ch, exists := rcvr.onSub[*runner.rule.ID]
|
||||
if !exists {
|
||||
return fmt.Errorf("channel for runner does not exist")
|
||||
}
|
||||
close(ch)
|
||||
delete(rcvr.onSub, *runner.rule.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) HandleApi(event events.Api) {
|
||||
errs := cb.runApiFuncs(
|
||||
event,
|
||||
cb.handleApiFollow,
|
||||
)
|
||||
|
||||
for _, err := range errs {
|
||||
cb.logError.Println("chatbot: error handling api event:", err)
|
||||
}
|
||||
}
|
||||
|
||||
type apiFunc func(api events.Api) error
|
||||
|
||||
func (cb *Chatbot) runApiFuncs(api events.Api, fns ...apiFunc) []error {
|
||||
// TODO: validate api response?
|
||||
|
||||
errs := []error{}
|
||||
for _, fn := range fns {
|
||||
err := fn(api)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (cb *Chatbot) handleApiFollow(api events.Api) error {
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
|
||||
rcvr, exists := cb.receivers[api.Name]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
if rcvr == nil {
|
||||
return fmt.Errorf("receiver is nil for API: %s", api.Name)
|
||||
}
|
||||
|
||||
rcvr.onFollowMu.Lock()
|
||||
defer rcvr.onFollowMu.Unlock()
|
||||
|
||||
for _, runner := range rcvr.onFollow {
|
||||
latest := runner.latest
|
||||
for _, follower := range api.Resp.Followers.RecentFollowers {
|
||||
followedOn, err := time.Parse(time.RFC3339, follower.FollowedOn)
|
||||
// TODO: fix this in the API, not in the code
|
||||
followedOn = followedOn.Add(-4 * time.Hour)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing followed_on time: %v", err)
|
||||
}
|
||||
if followedOn.After(runner.latest) {
|
||||
if followedOn.After(latest) {
|
||||
latest = followedOn
|
||||
}
|
||||
runner.apiCh <- events.ApiFollower{Username: follower.Username}
|
||||
}
|
||||
}
|
||||
runner.latest = latest
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) HandleChat(event events.Chat) {
|
||||
switch event.Message.Type {
|
||||
case rumblelivestreamlib.ChatTypeMessages:
|
||||
cb.handleMessage(event)
|
||||
|
@ -390,6 +770,9 @@ func (cb *Chatbot) handleMessage(event events.Chat) {
|
|||
errs := cb.runMessageFuncs(
|
||||
event,
|
||||
cb.handleMessageCommand,
|
||||
cb.handleMessageEventRaid,
|
||||
cb.handleMessageEventRant,
|
||||
cb.handleMessageEventSub,
|
||||
)
|
||||
|
||||
for _, err := range errs {
|
||||
|
@ -397,12 +780,12 @@ func (cb *Chatbot) handleMessage(event events.Chat) {
|
|||
}
|
||||
}
|
||||
|
||||
func (cb *Chatbot) runMessageFuncs(event events.Chat, fns ...messageFunc) []error {
|
||||
func (cb *Chatbot) runMessageFuncs(chat events.Chat, fns ...messageFunc) []error {
|
||||
// TODO: validate message
|
||||
|
||||
errs := []error{}
|
||||
for _, fn := range fns {
|
||||
err := fn(event)
|
||||
err := fn(chat)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
@ -411,25 +794,25 @@ func (cb *Chatbot) runMessageFuncs(event events.Chat, fns ...messageFunc) []erro
|
|||
return errs
|
||||
}
|
||||
|
||||
type messageFunc func(event events.Chat) error
|
||||
type messageFunc func(chat events.Chat) error
|
||||
|
||||
func (cb *Chatbot) handleMessageCommand(event events.Chat) error {
|
||||
if strings.Index(event.Message.Text, "!") != 0 {
|
||||
func (cb *Chatbot) handleMessageCommand(chat events.Chat) error {
|
||||
if strings.Index(chat.Message.Text, "!") != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
words := strings.Split(event.Message.Text, " ")
|
||||
words := strings.Split(chat.Message.Text, " ")
|
||||
cmd := words[0]
|
||||
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
|
||||
receiver, exists := cb.receivers[event.Livestream]
|
||||
receiver, exists := cb.receivers[chat.Livestream]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
if receiver == nil {
|
||||
return fmt.Errorf("receiver is nil for livestream: %s", event.Livestream)
|
||||
return fmt.Errorf("receiver is nil for livestream: %s", chat.Livestream)
|
||||
}
|
||||
|
||||
receiver.onCommandMu.Lock()
|
||||
|
@ -440,7 +823,85 @@ func (cb *Chatbot) handleMessageCommand(event events.Chat) error {
|
|||
}
|
||||
|
||||
for _, runner := range runners {
|
||||
runner <- event
|
||||
runner <- chat
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) handleMessageEventRaid(chat events.Chat) error {
|
||||
if !chat.Message.Raid {
|
||||
return nil
|
||||
}
|
||||
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
|
||||
receiver, exists := cb.receivers[chat.Livestream]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
if receiver == nil {
|
||||
return fmt.Errorf("receiver is nil for livestream: %s", chat.Livestream)
|
||||
}
|
||||
|
||||
receiver.onRaidMu.Lock()
|
||||
defer receiver.onRaidMu.Unlock()
|
||||
|
||||
for _, runner := range receiver.onRaid {
|
||||
runner <- chat
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) handleMessageEventRant(chat events.Chat) error {
|
||||
if chat.Message.Rant == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
|
||||
receiver, exists := cb.receivers[chat.Livestream]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
if receiver == nil {
|
||||
return fmt.Errorf("receiver is nil for livestream: %s", chat.Livestream)
|
||||
}
|
||||
|
||||
receiver.onRantMu.Lock()
|
||||
defer receiver.onRantMu.Unlock()
|
||||
|
||||
for _, runner := range receiver.onRant {
|
||||
runner <- chat
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cb *Chatbot) handleMessageEventSub(chat events.Chat) error {
|
||||
if !chat.Message.Sub {
|
||||
return nil
|
||||
}
|
||||
|
||||
cb.receiversMu.Lock()
|
||||
defer cb.receiversMu.Unlock()
|
||||
|
||||
receiver, exists := cb.receivers[chat.Livestream]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
if receiver == nil {
|
||||
return fmt.Errorf("receiver is nil for livestream: %s", chat.Livestream)
|
||||
}
|
||||
|
||||
receiver.onSubMu.Lock()
|
||||
defer receiver.onSubMu.Unlock()
|
||||
|
||||
for _, runner := range receiver.onSub {
|
||||
runner <- chat
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -16,6 +16,11 @@ import (
|
|||
"github.com/tylertravisty/rum-goggles/v1/internal/models"
|
||||
)
|
||||
|
||||
const (
|
||||
PrefixAccount = "/user/"
|
||||
PrefixChannel = "/c/"
|
||||
)
|
||||
|
||||
func SortRules(rules []Rule) {
|
||||
slices.SortFunc(rules, func(a, b Rule) int {
|
||||
return cmp.Compare(strings.ToLower(a.Display), strings.ToLower(b.Display))
|
||||
|
@ -30,12 +35,33 @@ type Rule struct {
|
|||
Running bool `json:"running"`
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
Name string
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (r *Rule) Page() *Page {
|
||||
if r.Parameters != nil {
|
||||
return r.Parameters.Page()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RuleParameters struct {
|
||||
Message *RuleMessage `json:"message"`
|
||||
SendAs *RuleSender `json:"send_as"`
|
||||
Trigger *RuleTrigger `json:"trigger"`
|
||||
}
|
||||
|
||||
func (rp *RuleParameters) Page() *Page {
|
||||
if rp.Trigger != nil {
|
||||
return rp.Trigger.Page()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RuleMessage struct {
|
||||
FromFile *RuleMessageFile `json:"from_file"`
|
||||
FromText string `json:"from_text"`
|
||||
|
@ -131,6 +157,14 @@ type RuleTrigger struct {
|
|||
OnTimer *time.Duration `json:"on_timer"`
|
||||
}
|
||||
|
||||
func (rt *RuleTrigger) Page() *Page {
|
||||
if rt.OnEvent != nil {
|
||||
return rt.OnEvent.Page()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RuleTriggerCommand struct {
|
||||
Command string `json:"command"`
|
||||
Restrict *RuleTriggerCommandRestriction `json:"restrict"`
|
||||
|
@ -154,12 +188,49 @@ type RuleTriggerCommandRestrictionBypass struct {
|
|||
}
|
||||
|
||||
type RuleTriggerEvent struct {
|
||||
OnFollow bool `json:"on_follow"`
|
||||
OnSubscribe bool `json:"on_subscribe"`
|
||||
OnRaid bool `json:"on_raid"`
|
||||
OnRant int `json:"on_rant"`
|
||||
FromAccount *RuleTriggerEventAccount `json:"from_account"`
|
||||
FromChannel *RuleTriggerEventChannel `json:"from_channel"`
|
||||
FromLiveStream *RuleTriggerEventLiveStream `json:"from_live_stream"`
|
||||
}
|
||||
|
||||
func (rte *RuleTriggerEvent) Page() *Page {
|
||||
switch {
|
||||
case rte.FromAccount != nil:
|
||||
return &Page{rte.FromAccount.Name, PrefixAccount}
|
||||
case rte.FromChannel != nil:
|
||||
return &Page{rte.FromChannel.Name, PrefixChannel}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type RuleTriggerEventAccount struct {
|
||||
Name string `json:"name"`
|
||||
OnFollow *RuleTriggerEventAccountFollow `json:"on_follow"`
|
||||
}
|
||||
|
||||
type RuleTriggerEventAccountFollow struct{}
|
||||
|
||||
type RuleTriggerEventChannel struct {
|
||||
Name string `json:"name"`
|
||||
OnFollow *RuleTriggerEventChannelFollow `json:"on_follow"`
|
||||
}
|
||||
|
||||
type RuleTriggerEventChannelFollow struct{}
|
||||
|
||||
type RuleTriggerEventLiveStream struct {
|
||||
OnRaid *RuleTriggerEventLiveStreamRaid `json:"on_raid"`
|
||||
OnRant *RuleTriggerEventLiveStreamRant `json:"on_rant"`
|
||||
OnSub *RuleTriggerEventLiveStreamSub `json:"on_sub"`
|
||||
}
|
||||
|
||||
type RuleTriggerEventLiveStreamRaid struct{}
|
||||
type RuleTriggerEventLiveStreamRant struct {
|
||||
MinAmount int `json:"min_amount"`
|
||||
MaxAmount int `json:"max_amount"`
|
||||
}
|
||||
type RuleTriggerEventLiveStreamSub struct{}
|
||||
|
||||
func (rule *Rule) ToModelsChatbotRule() (*models.ChatbotRule, error) {
|
||||
modelsRule := &models.ChatbotRule{
|
||||
ID: rule.ID,
|
||||
|
|
|
@ -14,13 +14,14 @@ import (
|
|||
)
|
||||
|
||||
type Runner struct {
|
||||
apiCh chan events.Api
|
||||
apiCh chan events.ApiFollower
|
||||
cancel context.CancelFunc
|
||||
cancelMu sync.Mutex
|
||||
channelID *int
|
||||
channelIDMu sync.Mutex
|
||||
chatCh chan events.Chat
|
||||
client *rumblelivestreamlib.Client
|
||||
page string
|
||||
rule Rule
|
||||
run runFunc
|
||||
wails context.Context
|
||||
|
@ -61,29 +62,31 @@ func (r *Runner) chat(fields *chatFields) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) init() error {
|
||||
if r.rule.Parameters == nil || r.rule.Parameters.Trigger == nil {
|
||||
return fmt.Errorf("invalid rule")
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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()
|
||||
// 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
|
||||
}
|
||||
// switch {
|
||||
// case r.rule.Parameters.Trigger.OnTimer != nil:
|
||||
// r.run = r.runOnTimer
|
||||
// case r.rule.Parameters.Trigger.OnEvent != nil:
|
||||
// r.run = r.runOnEvent
|
||||
// case r.rule.Parameters.Trigger.OnCommand != nil:
|
||||
// r.run = r.runOnCommand
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
type runFunc func(ctx context.Context) error
|
||||
|
||||
|
@ -102,18 +105,18 @@ func (r *Runner) runOnCommand(ctx context.Context) error {
|
|||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case event := <-r.chatCh:
|
||||
case chat := <-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}
|
||||
if block := r.blockCommand(chat); block {
|
||||
// if bypass := r.bypassCommand(chat); !bypass {break}
|
||||
break
|
||||
}
|
||||
|
||||
err := r.handleCommand(event)
|
||||
err := r.handleCommand(chat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error handling command: %v", err)
|
||||
}
|
||||
|
@ -122,18 +125,18 @@ func (r *Runner) runOnCommand(ctx context.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *Runner) blockCommand(event events.Chat) bool {
|
||||
func (r *Runner) blockCommand(chat events.Chat) bool {
|
||||
if r.rule.Parameters.Trigger.OnCommand.Restrict == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if r.rule.Parameters.Trigger.OnCommand.Restrict.ToFollower &&
|
||||
!event.Message.IsFollower {
|
||||
!chat.Message.IsFollower {
|
||||
return true
|
||||
}
|
||||
|
||||
subscriber := false
|
||||
for _, badge := range event.Message.Badges {
|
||||
for _, badge := range chat.Message.Badges {
|
||||
if badge == rumblelivestreamlib.ChatBadgeLocalsSupporter || badge == rumblelivestreamlib.ChatBadgeRecurringSubscription {
|
||||
subscriber = true
|
||||
}
|
||||
|
@ -144,24 +147,238 @@ func (r *Runner) blockCommand(event events.Chat) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
if event.Message.Rant < r.rule.Parameters.Trigger.OnCommand.Restrict.ToRant*100 {
|
||||
if chat.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
|
||||
func (r *Runner) handleCommand(chat events.Chat) error {
|
||||
displayName := chat.Message.Username
|
||||
if chat.Message.ChannelName != "" {
|
||||
displayName = chat.Message.ChannelName
|
||||
}
|
||||
|
||||
fields := &chatFields{
|
||||
ChannelName: event.Message.ChannelName,
|
||||
ChannelName: chat.Message.ChannelName,
|
||||
DisplayName: displayName,
|
||||
Username: event.Message.Username,
|
||||
Rant: event.Message.Rant / 100,
|
||||
Username: chat.Message.Username,
|
||||
Rant: chat.Message.Rant / 100,
|
||||
}
|
||||
|
||||
err := r.chat(fields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending chat: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) runOnEventFromAccountOnFollow(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.OnEvent == nil || r.rule.Parameters.Trigger.OnEvent.FromAccount == nil || r.rule.Parameters.Trigger.OnEvent.FromAccount.OnFollow == nil {
|
||||
return fmt.Errorf("event is nil")
|
||||
}
|
||||
|
||||
for {
|
||||
runtime.EventsEmit(r.wails, fmt.Sprintf("ChatbotRuleActive-%d", *r.rule.ID), true)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case api := <-r.apiCh:
|
||||
err := r.handleEventOnFollow(api)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error handling event: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) runOnEventFromChannelOnFollow(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.OnEvent == nil || r.rule.Parameters.Trigger.OnEvent.FromChannel == nil || r.rule.Parameters.Trigger.OnEvent.FromChannel.OnFollow == nil {
|
||||
return fmt.Errorf("event is nil")
|
||||
}
|
||||
|
||||
for {
|
||||
runtime.EventsEmit(r.wails, fmt.Sprintf("ChatbotRuleActive-%d", *r.rule.ID), true)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case api := <-r.apiCh:
|
||||
err := r.handleEventOnFollow(api)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error handling event: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) handleEventOnFollow(follower events.ApiFollower) error {
|
||||
fields := &chatFields{
|
||||
DisplayName: follower.Username,
|
||||
Username: follower.Username,
|
||||
}
|
||||
|
||||
err := r.chat(fields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending chat: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) runOnEventFromLiveStreamOnRaid(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.OnEvent == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream.OnRaid == nil {
|
||||
return fmt.Errorf("event is nil")
|
||||
}
|
||||
|
||||
for {
|
||||
runtime.EventsEmit(r.wails, fmt.Sprintf("ChatbotRuleActive-%d", *r.rule.ID), true)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case chat := <-r.chatCh:
|
||||
err := r.handleEventFromLiveStreamOnRaid(chat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error handling event: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) handleEventFromLiveStreamOnRaid(chat events.Chat) error {
|
||||
if r.rule.Parameters == nil || r.rule.Parameters.Trigger == nil || r.rule.Parameters.Trigger.OnEvent == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream.OnRaid == nil {
|
||||
return fmt.Errorf("invalid rule")
|
||||
}
|
||||
|
||||
displayName := chat.Message.Username
|
||||
if chat.Message.ChannelName != "" {
|
||||
displayName = chat.Message.ChannelName
|
||||
}
|
||||
|
||||
fields := &chatFields{
|
||||
ChannelName: chat.Message.ChannelName,
|
||||
DisplayName: displayName,
|
||||
Username: chat.Message.Username,
|
||||
Rant: chat.Message.Rant / 100,
|
||||
}
|
||||
|
||||
err := r.chat(fields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending chat: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) runOnEventFromLiveStreamOnRant(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.OnEvent == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream.OnRant == nil {
|
||||
return fmt.Errorf("event is nil")
|
||||
}
|
||||
|
||||
for {
|
||||
runtime.EventsEmit(r.wails, fmt.Sprintf("ChatbotRuleActive-%d", *r.rule.ID), true)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case chat := <-r.chatCh:
|
||||
err := r.handleEventFromLiveStreamOnRant(chat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error handling event: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) handleEventFromLiveStreamOnRant(chat events.Chat) error {
|
||||
if r.rule.Parameters == nil || r.rule.Parameters.Trigger == nil || r.rule.Parameters.Trigger.OnEvent == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream.OnRant == nil {
|
||||
return fmt.Errorf("invalid rule")
|
||||
}
|
||||
|
||||
rant := chat.Message.Rant / 100
|
||||
minAmount := r.rule.Parameters.Trigger.OnEvent.FromLiveStream.OnRant.MinAmount
|
||||
maxAmount := r.rule.Parameters.Trigger.OnEvent.FromLiveStream.OnRant.MaxAmount
|
||||
if minAmount != 0 && rant < minAmount {
|
||||
return nil
|
||||
}
|
||||
if maxAmount != 0 && rant > maxAmount {
|
||||
return nil
|
||||
}
|
||||
|
||||
displayName := chat.Message.Username
|
||||
if chat.Message.ChannelName != "" {
|
||||
displayName = chat.Message.ChannelName
|
||||
}
|
||||
|
||||
fields := &chatFields{
|
||||
ChannelName: chat.Message.ChannelName,
|
||||
DisplayName: displayName,
|
||||
Username: chat.Message.Username,
|
||||
Rant: chat.Message.Rant / 100,
|
||||
}
|
||||
|
||||
err := r.chat(fields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending chat: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) runOnEventFromLiveStreamOnSub(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.OnEvent == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream.OnSub == nil {
|
||||
return fmt.Errorf("event is nil")
|
||||
}
|
||||
|
||||
for {
|
||||
runtime.EventsEmit(r.wails, fmt.Sprintf("ChatbotRuleActive-%d", *r.rule.ID), true)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case chat := <-r.chatCh:
|
||||
err := r.handleEventFromLiveStreamOnSub(chat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error handling event: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) handleEventFromLiveStreamOnSub(chat events.Chat) error {
|
||||
if r.rule.Parameters == nil || r.rule.Parameters.Trigger == nil || r.rule.Parameters.Trigger.OnEvent == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream == nil || r.rule.Parameters.Trigger.OnEvent.FromLiveStream.OnSub == nil {
|
||||
return fmt.Errorf("invalid rule")
|
||||
}
|
||||
|
||||
displayName := chat.Message.Username
|
||||
if chat.Message.ChannelName != "" {
|
||||
displayName = chat.Message.ChannelName
|
||||
}
|
||||
|
||||
fields := &chatFields{
|
||||
ChannelName: chat.Message.ChannelName,
|
||||
DisplayName: displayName,
|
||||
Username: chat.Message.Username,
|
||||
Rant: chat.Message.Rant / 100,
|
||||
}
|
||||
|
||||
err := r.chat(fields)
|
||||
|
|
|
@ -16,6 +16,10 @@ type Api struct {
|
|||
Stop bool
|
||||
}
|
||||
|
||||
type ApiFollower struct {
|
||||
Username string
|
||||
}
|
||||
|
||||
type apiProducer struct {
|
||||
cancel context.CancelFunc
|
||||
cancelMu sync.Mutex
|
||||
|
|
35
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/chat.go
generated
vendored
35
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/chat.go
generated
vendored
|
@ -258,6 +258,15 @@ type ChatEventBlock struct {
|
|||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ChatEventNotification struct {
|
||||
Badge string `json:"badge"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type ChatEventRaidNotification struct {
|
||||
StartTs int64 `json:"start_ts"`
|
||||
}
|
||||
|
||||
type ChatEventRant struct {
|
||||
Duration int `json:"duration"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
|
@ -265,13 +274,15 @@ type ChatEventRant struct {
|
|||
}
|
||||
|
||||
type ChatEventMessage struct {
|
||||
Blocks []ChatEventBlock `json:"blocks"`
|
||||
ChannelID *int64 `json:"channel_id"`
|
||||
ID string `json:"id"`
|
||||
Rant *ChatEventRant `json:"rant"`
|
||||
Text string `json:"text"`
|
||||
Time string `json:"time"`
|
||||
UserID string `json:"user_id"`
|
||||
Blocks []ChatEventBlock `json:"blocks"`
|
||||
ChannelID *int64 `json:"channel_id"`
|
||||
ID string `json:"id"`
|
||||
Notification *ChatEventNotification `json:"notification"`
|
||||
RaidNotification *ChatEventRaidNotification `json:"raid_notification"`
|
||||
Rant *ChatEventRant `json:"rant"`
|
||||
Text string `json:"text"`
|
||||
Time string `json:"time"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
type ChatEventUser struct {
|
||||
|
@ -392,7 +403,9 @@ type ChatView struct {
|
|||
ImageUrl string
|
||||
Init bool
|
||||
IsFollower bool
|
||||
Raid bool
|
||||
Rant int
|
||||
Sub bool
|
||||
Text string
|
||||
Time time.Time
|
||||
Type string
|
||||
|
@ -456,9 +469,17 @@ func parseMessages(eventType string, messages []ChatEventMessage, users map[stri
|
|||
view.Color = user.Color
|
||||
view.ImageUrl = user.Image1
|
||||
view.IsFollower = user.IsFollower
|
||||
if message.RaidNotification != nil {
|
||||
view.Raid = true
|
||||
}
|
||||
if message.Rant != nil {
|
||||
view.Rant = message.Rant.PriceCents
|
||||
}
|
||||
if message.Notification != nil {
|
||||
if message.Notification.Badge == ChatBadgeRecurringSubscription {
|
||||
view.Sub = true
|
||||
}
|
||||
}
|
||||
view.Text = message.Text
|
||||
t, err := time.Parse(time.RFC3339, message.Time)
|
||||
if err != nil {
|
||||
|
|
18
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/client.go
generated
vendored
18
v1/vendor/github.com/tylertravisty/rumble-livestream-lib-go/client.go
generated
vendored
|
@ -273,31 +273,37 @@ func (c *Client) userLogout() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type LoggedInResponseData struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type LoggedInResponseUser struct {
|
||||
LoggedIn bool `json:"logged_in"`
|
||||
ID string `json:"id"`
|
||||
LoggedIn bool `json:"logged_in"`
|
||||
}
|
||||
|
||||
type LoggedInResponse struct {
|
||||
Data LoggedInResponseData `json:"data"`
|
||||
User LoggedInResponseUser `json:"user"`
|
||||
}
|
||||
|
||||
func (c *Client) LoggedIn() (bool, error) {
|
||||
func (c *Client) LoggedIn() (*LoggedInResponse, error) {
|
||||
resp, err := c.httpClient.Get(urlUserLogin)
|
||||
if err != nil {
|
||||
return false, pkgErr("error getting login service", err)
|
||||
return nil, pkgErr("error getting login service", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bodyB, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, pkgErr("error reading body bytes", err)
|
||||
return nil, pkgErr("error reading body bytes", err)
|
||||
}
|
||||
|
||||
var lir LoggedInResponse
|
||||
err = json.NewDecoder(strings.NewReader(string(bodyB))).Decode(&lir)
|
||||
if err != nil {
|
||||
return false, pkgErr("error un-marshaling response body", err)
|
||||
return nil, pkgErr("error un-marshaling response body", err)
|
||||
}
|
||||
|
||||
return lir.User.LoggedIn, nil
|
||||
return &lir, nil
|
||||
}
|
||||
|
|
2
v1/vendor/modules.txt
vendored
2
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
|
||||
## explicit; go 1.16
|
||||
github.com/tylertravisty/go-utils/random
|
||||
# github.com/tylertravisty/rumble-livestream-lib-go v0.7.2
|
||||
# github.com/tylertravisty/rumble-livestream-lib-go v0.9.0
|
||||
## explicit; go 1.19
|
||||
github.com/tylertravisty/rumble-livestream-lib-go
|
||||
# github.com/valyala/bytebufferpool v1.0.0
|
||||
|
|
Loading…
Reference in a new issue