2021-09-22 13:36:45 +00:00
< html >
< title > Twitch-Bot: Config-Editor< / title >
< link rel = "stylesheet" href = "editor/bundle.css" >
< link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/css/all.min.css" >
< style >
[v-cloak] {
display: none;
}
.btn-twitch {
background-color: #6441a5;
}
< / style >
< div id = "app" v-cloak >
< b-navbar toggleable = "lg" type = "dark" variant = "primary" class = "mb-3" >
< b-navbar-brand href = "#" > < i class = "fas fa-fw fa-robot mr-1" > < / i > Twitch-Bot< / b-navbar-brand >
< b-navbar-toggle target = "nav-collapse" > < / b-navbar-toggle >
< b-collapse id = "nav-collapse" is-nav >
< b-navbar-nav v-if = "authToken" >
< b-nav-item :active = "editMode === 'general'" @ click = "editMode = 'general'" >
< i class = "fas fa-fw fa-cog mr-1" > < / i >
General
< / b-nav-item >
< b-nav-item :active = "editMode === 'automessages'" @ click = "editMode = 'automessages'" >
< i class = "fas fa-fw fa-envelope-open-text mr-1" > < / i >
Auto-Messages
< b-badge pill > {{ autoMessages.length }}
< / b-badge > < / b-nav-item >
< b-nav-item :active = "editMode === 'rules'" @ click = "editMode = 'rules'" >
< i class = "fas fa-fw fa-inbox mr-1" > < / i >
Rules
< b-badge pill > {{ rules.length }}< / b-badge >
< / b-nav-item >
< / b-navbar-nav >
< b-navbar-nav class = "ml-auto" >
< b-nav-text >
< span v-if = "configNotifySocketConnected" >
< i
class="fas fa-fw fa-ethernet mr-1 text-success"
title="Connected to Bot"
v-b-tooltip.hover
>< / i >
< / span >
< span v-else >
< i
class="fas fa-fw fa-ethernet mr-1 text-danger"
title="Disconnected from Bot"
v-b-tooltip.hover
>< / i >
< / span >
< / b-nav-text >
< / b-navbar-nav >
< / b-collapse >
< / b-navbar >
< b-container >
<!-- Error display -->
< b-row v-if = "error" >
< b-col >
< b-alert
dismissible
@dismissed="error = null"
show
variant="danger"
>
< i class = "fas fa-fw fa-exclamation-circle mr-1" > < / i > {{ error }}
< / b-alert >
< / b-col >
< / b-row >
<!-- Working display -->
< b-row v-if = "changePending" >
< b-col >
< b-alert
show
variant="info"
>
< i class = "fas fa-fw fa-spinner fa-pulse mr-1" > < / i >
Your change was submitted and is pending, please wait for config to be updated!
< / b-alert >
< / b-col >
< / b-row >
<!-- Logged - out state -->
< b-row
v-if="!authToken"
>
< b-col
class="text-center"
>
< b-button
:disabled="!vars.TwitchClientID"
:href="authURL"
variant="twitch"
>
< i class = "fab fa-fw fa-twitch mr-1" > < / i > Login with Twitch
< / b-button >
< / b-col >
< / b-row >
<!-- Logged - in state -->
< template v-else >
< b-row v-if = "editMode === 'general'" >
< b-col >
< b-card-group columns >
< b-card no-body >
< b-card-header >
< i class = "fas fa-fw fa-hashtag mr-1" > < / i > Channels
< / b-card-header >
< b-list-group flush >
< b-list-group-item
class="d-flex align-items-center align-middle"
:key="channel"
v-for="channel in sortedChannels"
>
< span class = "mr-auto" >
< i class = "fas fa-fw fa-hashtag mr-1" > < / i >
{{ channel }}
< / span >
< b-button
@click="removeChannel(channel)"
size="sm"
variant="danger"
>
< i class = "fas fa-fw fa-minus" > < / i >
< / b-button >
< / b-list-group-item >
< b-list-group-item >
< b-input-group >
< b-form-input @ keyup . enter = "addChannel" v-model = "models.addChannel" > < / b-form-input >
< b-input-group-append >
< b-button @ click = "addChannel" variant = "success" > < i class = "fas fa-fw fa-plus mr-1" > < / i > Add< / b-button >
< / b-input-group-append >
< / b-input-group >
< / b-list-group-item >
< / b-list-group >
< / b-card >
< b-card no-body >
< b-card-header >
< i class = "fas fa-fw fa-users mr-1" > < / i > Bot-Editors
< / b-card-header >
< b-list-group flush >
< b-list-group-item
class="d-flex align-items-center align-middle"
:key="editor"
v-for="editor in sortedEditors"
>
< b-avatar class = "mr-3" :src = "userProfiles[editor]?.profile_image_url" > < / b-avatar >
< span class = "mr-auto" > {{ userProfiles[editor] ? userProfiles[editor].display_name : editor }}< / span >
< b-button
@click="removeEditor(editor)"
size="sm"
variant="danger"
>
< i class = "fas fa-fw fa-minus" > < / i >
< / b-button >
< / b-list-group-item >
< b-list-group-item >
< b-input-group >
< b-form-input @ keyup . enter = "addEditor" v-model = "models.addEditor" > < / b-form-input >
< b-input-group-append >
< b-button @ click = "addEditor" variant = "success" > < i class = "fas fa-fw fa-plus mr-1" > < / i > Add< / b-button >
< / b-input-group-append >
< / b-input-group >
< / b-list-group-item >
< / b-list-group >
< / b-card >
< / b-card-group >
< / b-col >
< / b-row >
< b-row v-else-if = "editMode === 'automessages'" >
< b-col >
< b-table
:busy="!autoMessages"
:fields="autoMessageFields"
hover
:items="autoMessages"
key="autoMessagesTable"
striped
>
< template # cell ( actions ) = " data " >
< b-button-group size = "sm" >
< b-button @ click = "editAutoMessage(data.item)" > < i class = "fas fa-fw fa-pen" > < / i > < / b-button >
< b-button @ click = "deleteAutoMessage(data.item.uuid)" variant = "danger" > < i class = "fas fa-fw fa-minus" > < / i > < / b-button >
< / b-button-group >
< / template >
< template # cell ( channel ) = " data " >
< i class = "fas fa-fw fa-hashtag mr-1" > < / i >
{{ data.value }}
< / template >
< template # cell ( cron ) = " data " >
< code > {{ data.value }}< / code >
< / template >
< template # head ( actions ) = " data " >
< b-button-group size = "sm" >
< b-button @ click = "newAutoMessage" variant = "success" > < i class = "fas fa-fw fa-plus" > < / i > < / b-button >
< / b-button-group >
< / template >
< / b-table >
< / b-col >
< / b-row >
< b-row v-else-if = "editMode === 'rules'" >
< b-col >
< b-table
:busy="!rules"
:fields="rulesFields"
hover
:items="rules"
key="rulesTable"
striped
>
< template # cell ( _actions ) = " data " >
< b-button-group size = "sm" >
< b-button @ click = "editRule(data.item)" > < i class = "fas fa-fw fa-pen" > < / i > < / b-button >
< b-button @ click = "deleteRule(data.item.uuid)" variant = "danger" > < i class = "fas fa-fw fa-minus" > < / i > < / b-button >
< / b-button-group >
< / template >
< template # cell ( _match ) = " data " >
< b-badge
class="m-1 text-truncate text-left col-12"
style="max-width: 250px;"
v-for="badge in formatRuleMatch(data.item)"
>
< strong > {{ badge.key }}< / strong > < code class = "ml-2" > {{ badge.value }}< / code >
< / b-badge >
< / template >
< template # cell ( _description ) = " data " >
< template v-if = "data.item.description" > {{ data.item.description }}< br > < / template >
2021-09-22 15:04:43 +00:00
< b-badge class = "mt-1 mr-1" variant = "danger" v-if = "data.item.disable" > Disabled< / b-badge >
2021-09-22 13:36:45 +00:00
< b-badge
class="mt-1 mr-1"
v-for="badge in formatRuleActions(data.item)"
>
{{ badge }}
< / b-badge >
< / template >
< template # head ( _actions ) = " data " >
< b-button-group size = "sm" >
< b-button @ click = "newRule" variant = "success" > < i class = "fas fa-fw fa-plus" > < / i > < / b-button >
< / b-button-group >
< / template >
< / b-table >
< / b-col >
< / b-row >
< / template >
<!-- Auto - Message Editor -->
< b-modal
@hidden="showAutoMessageEditModal=false"
hide-header-close
@ok="saveAutoMessage"
:ok-disabled="!validateAutoMessage"
ok-title="Save"
size="lg"
:visible="showAutoMessageEditModal"
title="Edit Auto-Message"
v-if="showAutoMessageEditModal"
>
< b-row >
< b-col cols = "8" >
< b-form-group
label="Channel"
label-for="formAutoMessageChannel"
>
< b-input-group
prepend="#"
>
< b-form-input
id="formAutoMessageChannel"
:state="validateAutoMessageChannel"
type="text"
required
v-model="models.autoMessage.channel"
>< / b-form-input >
< / b-input-group >
< / b-form-group >
< hr >
< b-form-group
:description="`${models.autoMessage.message?.length || 0} / ${validateAutoMessageMessageLength}`"
label="Message"
label-for="formAutoMessageMessage"
>
< b-form-textarea
id="formAutoMessageMessage"
max-rows="6"
required
rows="3"
:state="models.autoMessage.message?.length < = validateAutoMessageMessageLength"
v-model="models.autoMessage.message"
>< / b-form-textarea >
< / b-form-group >
< b-form-group >
< b-form-checkbox
switch
v-model="models.autoMessage.use_action"
>
Send message as action (< code > /me< / code > )
< / b-form-checkbox >
< / b-form-group >
< hr >
< b-form-group
label="Sending Mode"
label-for="formAutoMessageSendMode"
>
< b-form-select
id="formAutoMessageSendMode"
:options="autoMessageSendModes"
v-model="models.autoMessage.sendMode"
>< / b-form-select >
< / b-form-group >
< b-form-group
label="Send at"
label-for="formAutoMessageCron"
v-if="models.autoMessage.sendMode === 'cron'"
>
< b-form-input
id="formAutoMessageCron"
v-model="models.autoMessage.cron"
:state="validateAutoMessageCron"
type="text"
>< / b-form-input >
< div slot = "description" >
< code > @every [time]< / code > or Cron syntax
< / div >
< / b-form-group >
< b-form-group
label="Send every"
label-for="formAutoMessageNLines"
v-if="models.autoMessage.sendMode === 'lines'"
>
< b-input-group
append="Lines"
>
< b-form-input
id="formAutoMessageNLines"
v-model="models.autoMessage.message_interval"
type="number"
>< / b-form-input >
< / b-input-group >
< / b-form-group >
< hr >
< b-form-group >
< b-form-checkbox
switch
v-model="models.autoMessage.only_on_live"
>
Send only when channel is live
< / b-form-checkbox >
< / b-form-group >
< b-form-group
label="Disable on Template"
label-for="formAutoMessageDisableOnTemplate"
>
< div slot = "description" >
Template expression resulting in < code > true< / code > to disable the rule or < code > false< / code > to enable it
< / div >
< b-form-textarea
id="formAutoMessageDisableOnTemplate"
max-rows="6"
required
rows="1"
v-model="models.autoMessage.disable_on_template"
>< / b-form-textarea >
< / b-form-group >
< / b-col >
< b-col cols = "4" >
< h6 > Getting Help< / h6 >
< p >
For information about available template functions and variables to use in the < strong > Message< / strong > see the < a href = "https://github.com/Luzifer/twitch-bot/wiki#templating" target = "_blank" > Templating< / a > section of the Wiki.
< / p >
< p >
For information about the < strong > Cron< / strong > syntax have a look at the < a href = "https://cron.help/" target = "_blank" > cron.help< / a > site. Aditionally you can use < code > @every [time]< / code > syntax. The < code > [time]< / code > part is in format < code > 1h30m20s< / code > . You can leave out every segment but need to specify the unit of every segment. So for example < code > @every 1h< / code > or < code > @every 10m< / code > would be a valid specification.
< / p >
< / b-col >
< / b-row >
< / b-modal >
<!-- Rule Editor -->
< b-modal
@hidden="showRuleEditModal=false"
hide-header-close
@ok="saveRule"
:ok-disabled="!validateRule"
ok-title="Save"
scrollable
size="xl"
:visible="showRuleEditModal"
title="Edit Rule"
v-if="showRuleEditModal"
>
< b-row >
< b-col cols = "6" >
< b-form-group
description="Human readable description for the rules list"
label="Description"
label-for="formRuleDescription"
>
< b-form-input
id="formRuleDescription"
type="text"
v-model="models.rule.description"
>< / b-form-input >
< / b-form-group >
< hr >
< b-tabs content-class = "mt-3" >
< b-tab >
< div slot = "title" >
Matcher < b-badge > {{ countRuleMatchers }}< / b-badge >
< / div >
< b-form-group
description="Channel with leading hash: #mychannel - matches all channels if none are given"
label="Match Channels"
label-for="formRuleMatchChannels"
>
< b-form-tags
id="formRuleMatchChannels"
no-add-on-enter
placeholder="Enter channels separated by space or comma"
remove-on-delete
separator=" ,"
:tag-validator="(tag) => Boolean(tag.match(/^#[a-zA-Z0-9_]{4,25}$/))"
v-model="models.rule.match_channels"
>< / b-form-tags >
< / b-form-group >
< b-form-group
description="Matches no events if not set"
label="Match Event"
label-for="formRuleMatchEvent"
>
< b-form-select
id="formRuleMatchEvent"
:options="availableEvents"
v-model="models.rule.match_event"
>< / b-form-select >
< / b-form-group >
< b-form-group
description="Regular expression to match the message, matches all messages when not set"
label="Match Message"
label-for="formRuleMatchMessage"
>
< b-form-input
id="formRuleMatchMessage"
:state="models.rule.match_message__validation"
type="text"
v-model="models.rule.match_message"
>< / b-form-input >
< / b-form-group >
< b-form-group
description="Matches all users if none are given"
label="Match Users"
label-for="formRuleMatchUsers"
>
< b-form-tags
id="formRuleMatchUsers"
no-add-on-enter
placeholder="Enter usernames separated by space or comma"
remove-on-delete
separator=" ,"
:tag-validator="(tag) => Boolean(tag.match(/^[a-z0-9_]{4,25}$/))"
v-model="models.rule.match_users"
>< / b-form-tags >
< / b-form-group >
< / b-tab >
< b-tab >
< div slot = "title" >
Cooldown < b-badge > {{ countRuleCooldowns }}< / b-badge >
< / div >
< b-row >
< b-col >
< b-form-group
label="Rule Cooldown"
label-for="formRuleRuleCooldown"
>
< b-form-input
id="formRuleRuleCooldown"
placeholder="No Cooldown"
:state="validateDuration(models.rule.cooldown, false)"
type="text"
v-model="models.rule.cooldown"
>< / b-form-input >
< / b-form-group >
< / b-col >
< b-col >
< b-form-group
label="Channel Cooldown"
label-for="formRuleChannelCooldown"
>
< b-form-input
id="formRuleChannelCooldown"
placeholder="No Cooldown"
:state="validateDuration(models.rule.channel_cooldown, false)"
type="text"
v-model="models.rule.channel_cooldown"
>< / b-form-input >
< / b-form-group >
< / b-col >
< b-col >
< b-form-group
label="User Cooldown"
label-for="formRuleUserCooldown"
>
< b-form-input
id="formRuleUserCooldown"
placeholder="No Cooldown"
:state="validateDuration(models.rule.user_cooldown, false)"
type="text"
v-model="models.rule.user_cooldown"
>< / b-form-input >
< / b-form-group >
< / b-row >
< b-form-group
:description="`Available badges: ${vars.IRCBadges?.join(', ')}`"
label="Skip Cooldown for"
label-for="formRuleSkipCooldown"
>
< b-form-tags
id="formRuleSkipCooldown"
no-add-on-enter
placeholder="Enter badges separated by space or comma"
remove-on-delete
separator=" ,"
:tag-validator="validateTwitchBadge"
v-model="models.rule.skip_cooldown_for"
>< / b-form-tags >
< / b-form-group >
< / b-tab >
< b-tab >
< div slot = "title" >
Conditions < b-badge > {{ countRuleConditions }}< / b-badge >
< / div >
< p > Disable rule… < / p >
< b-row >
< b-col >
< b-form-group >
< b-form-checkbox
switch
v-model="models.rule.disable"
>
completely
< / b-form-checkbox >
< / b-form-group >
< / b-col >
< b-col >
< b-form-group >
< b-form-checkbox
switch
v-model="models.rule.disable_on_offline"
>
when channel is offline
< / b-form-checkbox >
< / b-form-group >
< / b-col >
< b-col >
< b-form-group >
< b-form-checkbox
switch
v-model="models.rule.disable_on_permit"
>
when user has permit
< / b-form-checkbox >
< / b-form-group >
< / b-col >
< / b-row >
< b-form-group
:description="`Available badges: ${vars.IRCBadges?.join(', ')}`"
label="Disable Rule for"
label-for="formRuleDisableOn"
>
< b-form-tags
id="formRuleDisableOn"
no-add-on-enter
placeholder="Enter badges separated by space or comma"
remove-on-delete
separator=" ,"
:tag-validator="validateTwitchBadge"
v-model="models.rule.disable_on"
>< / b-form-tags >
< / b-form-group >
< b-form-group
:description="`Available badges: ${vars.IRCBadges?.join(', ')}`"
label="Enable Rule for"
label-for="formRuleEnableOn"
>
< b-form-tags
id="formRuleEnableOn"
no-add-on-enter
placeholder="Enter badges separated by space or comma"
remove-on-delete
separator=" ,"
:tag-validator="validateTwitchBadge"
v-model="models.rule.enable_on"
>< / b-form-tags >
< / b-form-group >
< b-form-group
label="Disable on Template"
label-for="formRuleDisableOnTemplate"
>
< div slot = "description" >
Template expression resulting in < code > true< / code > to disable the rule or < code > false< / code > to enable it
< / div >
< b-form-textarea
id="formRuleDisableOnTemplate"
max-rows="6"
required
rows="1"
v-model="models.rule.disable_on_template"
>< / b-form-textarea >
< / b-form-group >
< / b-tab >
< / b-tabs >
< / b-col >
< b-col cols = "6" >
< div class = "accordion" role = "tablist" >
< b-card
:key="`${models.rule.uuid}-action-${idx}`"
no-body
class="mb-1"
v-for="(action, idx) in models.rule.actions"
>
< b-card-header header-tag = "header" class = "p-1 d-flex" role = "tab" >
< b-button-group class = "flex-fill" >
< b-button
block
v-b-toggle="`${models.rule.uuid}-action-${idx}`"
variant="primary"
>
{{ getActionDefinitionByType(action.type).name }}
< i class = "fas fa-fw fa-exclamation-triangle text-danger" v-if = "actionHasValidationError(idx)" > < / i >
< / b-button >
< b-button
@click="moveAction(idx, -1)"
:disabled="idx === 0"
variant="secondary"
>< i class = "fas fa-fw fa-chevron-up" > < / i > < / b-button >
< b-button
@click="moveAction(idx, +1)"
:disabled="idx === models.rule.actions.length - 1"
variant="secondary"
>< i class = "fas fa-fw fa-chevron-down" > < / i > < / b-button >
< b-button
@click="removeAction(idx)"
variant="danger"
>< i class = "fas fa-fw fa-trash" > < / i > < / b-button >
< / b-button-group >
< / b-card-header >
< b-collapse
:id="`${models.rule.uuid}-action-${idx}`"
accordion="my-accordion"
role="tabpanel"
>
< b-card-body v-if = "getActionDefinitionByType(action.type).fields?.length > 0" >
< template
v-for="field in getActionDefinitionByType(action.type).fields"
>
< b-form-group
v-if="field.type === 'bool'"
>
< div slot = "description" >
< i
class="fas fa-fw fa-code mr-1 text-success"
title="Supports Templating"
v-if="field.support_template"
>< / i >
{{ field.description }}
< / div >
< b-form-checkbox
switch
v-model="models.rule.actions[idx].attributes[field.key]"
>
{{ field.name }}
< / b-form-checkbox >
< / b-form-group >
< b-form-group
:label="field.name"
:label-for="`${models.rule.uuid}-action-${idx}-${field.key}`"
v-else-if="field.type === 'stringslice'"
>
< div slot = "description" >
< i
class="fas fa-fw fa-code mr-1 text-success"
title="Supports Templating"
v-if="field.support_template"
>< / i >
{{ field.description }}
< / div >
< b-form-tags
:id="`${models.rule.uuid}-action-${idx}-${field.key}`"
:state="validateActionArgument(idx, field.key)"
placeholder="Enter elements and press enter to add the element"
remove-on-delete
v-model="models.rule.actions[idx].attributes[field.key]"
>< / b-form-tags >
< / b-form-group >
< b-form-group
:label="field.name"
:label-for="`${models.rule.uuid}-action-${idx}-${field.key}`"
v-else-if="field.type === 'string' & & field.long"
>
< div slot = "description" >
< i
class="fas fa-fw fa-code mr-1 text-success"
title="Supports Templating"
v-if="field.support_template"
>< / i >
{{ field.description }}
< / div >
< b-form-textarea
:id="`${models.rule.uuid}-action-${idx}-${field.key}`"
max-rows="6"
:required="!field.optional"
rows="3"
:state="validateActionArgument(idx, field.key)"
v-model="models.rule.actions[idx].attributes[field.key]"
>< / b-form-textarea >
< / b-form-group >
< b-form-group
:label="field.name"
:label-for="`${models.rule.uuid}-action-${idx}-${field.key}`"
v-else
>
< div slot = "description" >
< i
class="fas fa-fw fa-code mr-1 text-success"
title="Supports Templating"
v-if="field.support_template"
>< / i >
{{ field.description }}
< / div >
< b-form-input
:id="`${models.rule.uuid}-action-${idx}-${field.key}`"
:placeholder="field.default"
:required="!field.optional"
:state="validateActionArgument(idx, field.key)"
type="text"
v-model="models.rule.actions[idx].attributes[field.key]"
>< / b-form-input >
< / b-form-group >
< / template >
< / b-card-body >
< b-card-body v-else >
This action has no attributes.
< / b-card-body >
< / b-collapse >
< / b-card >
< hr >
< b-form-group
label="Add Action"
label-for="ruleAddAction"
>
< b-input-group >
< b-form-select
id="ruleAddAction"
:options="availableActionsForAdd"
v-model="models.addAction"
>< / b-form-select >
< b-input-group-append >
< b-button
@click="addAction"
:disabled="!models.addAction"
variant="success"
>< i class = "fas fa-fw fa-plus mr-1" > < / i > Add< / b-button >
< / b-input-group-append >
< / b-input-group >
< / b-form-group >
< div >
< / b-col >
< / b-row >
< / b-modal >
< / b-container >
< / div >
< script src = "editor/bundle.js" > < / script >
< script src = "editor/app.js" > < / script >
< / html >