<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> <b-badge class="mt-1 mr-1" variant="danger" v-if="data.item.disable">Disabled</b-badge> <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 :description="addActionDescription" 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>