Custom naming schemes

This commit is contained in:
rxdn 2022-06-18 20:00:44 +01:00
parent b8d2bf03ff
commit d5f8e58b3a
11 changed files with 233 additions and 23 deletions

View File

@ -23,6 +23,11 @@ import (
const freePanelLimit = 3 const freePanelLimit = 3
var (
placeholderPattern = regexp.MustCompile(`%(\w+)%`)
channelNamePattern = regexp.MustCompile(`^[\w\d-_]+$`)
)
type panelBody struct { type panelBody struct {
ChannelId uint64 `json:"channel_id,string"` ChannelId uint64 `json:"channel_id,string"`
MessageId uint64 `json:"message_id,string"` MessageId uint64 `json:"message_id,string"`
@ -40,6 +45,7 @@ type panelBody struct {
ButtonStyle component.ButtonStyle `json:"button_style,string"` ButtonStyle component.ButtonStyle `json:"button_style,string"`
ButtonLabel string `json:"button_label"` ButtonLabel string `json:"button_label"`
FormId *int `json:"form_id"` FormId *int `json:"form_id"`
NamingScheme *string `json:"naming_scheme"`
} }
func (p *panelBody) IntoPanelMessageData(customId string, isPremium bool) panelMessageData { func (p *panelBody) IntoPanelMessageData(customId string, isPremium bool) panelMessageData {
@ -153,6 +159,7 @@ func CreatePanel(ctx *gin.Context) {
ButtonStyle: int(data.ButtonStyle), ButtonStyle: int(data.ButtonStyle),
ButtonLabel: data.ButtonLabel, ButtonLabel: data.ButtonLabel,
FormId: data.FormId, FormId: data.FormId,
NamingScheme: data.NamingScheme,
} }
panelId, err := dbclient.Client.Panel.Create(panel) panelId, err := dbclient.Client.Panel.Create(panel)
@ -305,6 +312,11 @@ func (p *panelBody) doValidations(ctx *gin.Context, guildId uint64) bool {
} }
} }
if !p.verifyNamingScheme() {
ctx.JSON(400, utils.ErrorStr("Invalid naming scheme: ensure that the naming scheme is less than 100 characters and the placeholders you have used are valid"))
return false
}
return true return true
} }
@ -459,6 +471,47 @@ func (p *panelBody) verifyTeams(guildId uint64) (bool, error) {
return dbclient.Client.SupportTeam.AllTeamsExistForGuild(guildId, p.Teams) return dbclient.Client.SupportTeam.AllTeamsExistForGuild(guildId, p.Teams)
} }
func (p *panelBody) verifyNamingScheme() bool {
if p.NamingScheme == nil {
return true
}
if len(*p.NamingScheme) == 0 {
p.NamingScheme = nil
return true
}
// Substitute out {} users may use by mistake, spaces for dashes and convert to lowercase
p.NamingScheme = utils.Ptr(strings.ReplaceAll(*p.NamingScheme, "{", "%"))
p.NamingScheme = utils.Ptr(strings.ReplaceAll(*p.NamingScheme, "}", "%"))
p.NamingScheme = utils.Ptr(strings.ReplaceAll(*p.NamingScheme, " ", "-"))
p.NamingScheme = utils.Ptr(strings.ToLower(*p.NamingScheme))
if len(*p.NamingScheme) > 100 {
return false
}
// We must remove all placeholders from the string to check whether the rest of the string is legal
noPlaceholders := *p.NamingScheme
// Validate placeholders used
validPlaceholders := []string{"id", "username"}
for _, match := range placeholderPattern.FindAllStringSubmatch(*p.NamingScheme, -1) {
if len(match) < 2 { // Infallible
return false
}
placeholder := match[1]
if !utils.Contains(validPlaceholders, placeholder) {
return false
}
noPlaceholders = strings.Replace(noPlaceholders, match[0], "", -1) // match[0] = "%placeholder%"
}
return channelNamePattern.MatchString(noPlaceholders)
}
func getRoleHashSet(guildId uint64) (*collections.Set[uint64], error) { func getRoleHashSet(guildId uint64) (*collections.Set[uint64], error) {
ctx, err := botcontext.ContextForGuild(guildId) ctx, err := botcontext.ContextForGuild(guildId)
if err != nil { if err != nil {

View File

@ -17,6 +17,7 @@ func ListPanels(ctx *gin.Context) {
Emoji types.Emoji `json:"emote"` Emoji types.Emoji `json:"emote"`
Mentions []string `json:"mentions"` Mentions []string `json:"mentions"`
Teams []int `json:"teams"` Teams []int `json:"teams"`
UseServerDefaultNamingScheme bool `json:"use_server_default_naming_scheme"`
} }
guildId := ctx.Keys["guildid"].(uint64) guildId := ctx.Keys["guildid"].(uint64)
@ -79,6 +80,7 @@ func ListPanels(ctx *gin.Context) {
Emoji: types.NewEmoji(p.EmojiName, p.EmojiId), Emoji: types.NewEmoji(p.EmojiName, p.EmojiId),
Mentions: mentions, Mentions: mentions,
Teams: teamIds, Teams: teamIds,
UseServerDefaultNamingScheme: p.NamingScheme == nil,
} }
return nil return nil

View File

@ -125,6 +125,7 @@ func UpdatePanel(ctx *gin.Context) {
ButtonStyle: int(data.ButtonStyle), ButtonStyle: int(data.ButtonStyle),
ButtonLabel: data.ButtonLabel, ButtonLabel: data.ButtonLabel,
FormId: data.FormId, FormId: data.FormId,
NamingScheme: data.NamingScheme,
} }
if err = dbclient.Client.Panel.Update(panel); err != nil { if err = dbclient.Client.Panel.Update(panel); err != nil {

View File

@ -12,7 +12,8 @@
"sirv-cli": "^1.0.0", "sirv-cli": "^1.0.0",
"svelte-emoji-selector": "^1.0.1", "svelte-emoji-selector": "^1.0.1",
"svelte-router-spa": "^6.0.2", "svelte-router-spa": "^6.0.2",
"svelte-select": "^3.17.0" "svelte-select": "^3.17.0",
"svelte-tooltip": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.6", "@babel/core": "^7.14.6",
@ -3176,6 +3177,51 @@
"integrity": "sha512-2gzDDMDhM+ImDaLEZVlnlHVY1340Y368tT4Qk5IwLnCeRJ4zV3cVwliVGacoHy7iCDukcGXzKwDzG/hTTcaljg==", "integrity": "sha512-2gzDDMDhM+ImDaLEZVlnlHVY1340Y368tT4Qk5IwLnCeRJ4zV3cVwliVGacoHy7iCDukcGXzKwDzG/hTTcaljg==",
"dev": true "dev": true
}, },
"node_modules/svelte-tooltip": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/svelte-tooltip/-/svelte-tooltip-1.2.0.tgz",
"integrity": "sha512-FmaiNoCGkXBqF5AAscv+NdLtVIhywpqcaKw7mP1IksLJPMW3QaOPB8G23CHPQWde0J3A0NdCDQ3DI2isWGsDbg==",
"dependencies": {
"sirv-cli": "^0.4.4"
}
},
"node_modules/svelte-tooltip/node_modules/@polka/url": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz",
"integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw=="
},
"node_modules/svelte-tooltip/node_modules/sirv": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-0.4.6.tgz",
"integrity": "sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ==",
"dependencies": {
"@polka/url": "^0.5.0",
"mime": "^2.3.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/svelte-tooltip/node_modules/sirv-cli": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-0.4.6.tgz",
"integrity": "sha512-/Vj85/kBvPL+n9ibgX6FicLE8VjidC1BhlX67PYPBfbBAphzR6i0k0HtU5c2arejfU3uzq8l3SYPCwl1x7z6Ww==",
"dependencies": {
"console-clear": "^1.1.0",
"get-port": "^3.2.0",
"kleur": "^3.0.0",
"local-access": "^1.0.1",
"sade": "^1.4.0",
"sirv": "^0.4.6",
"tinydate": "^1.0.0"
},
"bin": {
"sirv": "index.js"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/terser": { "node_modules/terser": {
"version": "5.7.0", "version": "5.7.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz",
@ -5606,6 +5652,44 @@
"integrity": "sha512-2gzDDMDhM+ImDaLEZVlnlHVY1340Y368tT4Qk5IwLnCeRJ4zV3cVwliVGacoHy7iCDukcGXzKwDzG/hTTcaljg==", "integrity": "sha512-2gzDDMDhM+ImDaLEZVlnlHVY1340Y368tT4Qk5IwLnCeRJ4zV3cVwliVGacoHy7iCDukcGXzKwDzG/hTTcaljg==",
"dev": true "dev": true
}, },
"svelte-tooltip": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/svelte-tooltip/-/svelte-tooltip-1.2.0.tgz",
"integrity": "sha512-FmaiNoCGkXBqF5AAscv+NdLtVIhywpqcaKw7mP1IksLJPMW3QaOPB8G23CHPQWde0J3A0NdCDQ3DI2isWGsDbg==",
"requires": {
"sirv-cli": "^0.4.4"
},
"dependencies": {
"@polka/url": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz",
"integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw=="
},
"sirv": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-0.4.6.tgz",
"integrity": "sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ==",
"requires": {
"@polka/url": "^0.5.0",
"mime": "^2.3.1"
}
},
"sirv-cli": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-0.4.6.tgz",
"integrity": "sha512-/Vj85/kBvPL+n9ibgX6FicLE8VjidC1BhlX67PYPBfbBAphzR6i0k0HtU5c2arejfU3uzq8l3SYPCwl1x7z6Ww==",
"requires": {
"console-clear": "^1.1.0",
"get-port": "^3.2.0",
"kleur": "^3.0.0",
"local-access": "^1.0.1",
"sade": "^1.4.0",
"sirv": "^0.4.6",
"tinydate": "^1.0.0"
}
}
}
},
"terser": { "terser": {
"version": "5.7.0", "version": "5.7.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz",

View File

@ -28,6 +28,7 @@
"sirv-cli": "^1.0.0", "sirv-cli": "^1.0.0",
"svelte-emoji-selector": "^1.0.1", "svelte-emoji-selector": "^1.0.1",
"svelte-router-spa": "^6.0.2", "svelte-router-spa": "^6.0.2",
"svelte-select": "^3.17.0" "svelte-select": "^3.17.0",
"svelte-tooltip": "^1.2.0"
} }
} }

View File

@ -1,17 +1,39 @@
<div class:col-1={col1} class:col-2={col2} class:col-3={col3} class:col-4={col4}> <div class:col-1={col1} class:col-2={col2} class:col-3={col3} class:col-4={col4}>
{#if label !== undefined} {#if label !== undefined}
<label for="input" class="form-label">{label}</label> <div class="label-wrapper">
<label for="input" class="form-label">
{label}
</label>
{#if tooltipText !== undefined}
<div style="margin-bottom: 5px">
<Tooltip tip={tooltipText} top color="#121212">
{#if tooltipLink !== undefined}
<a href={tooltipLink} target="_blank">
<i class="fas fa-circle-info form-label tooltip-icon"></i>
</a>
{:else}
<i class="fas fa-circle-info form-label tooltip-icon"></i>
{/if}
</Tooltip>
</div>
{/if}
</div>
{/if} {/if}
<input id="input" class="form-input" placeholder="{placeholder}" disabled="{disabled}" on:input on:change <input id="input" class="form-input" placeholder="{placeholder}" disabled="{disabled}" on:input on:change
bind:value={value}> bind:value={value}>
</div> </div>
<script> <script>
import Tooltip from 'svelte-tooltip';
export let value; export let value;
export let label; export let label;
export let placeholder; export let placeholder;
export let disabled = false; export let disabled = false;
export let tooltipText = undefined;
export let tooltipLink = undefined;
export let col1 = false; export let col1 = false;
export let col2 = false; export let col2 = false;
export let col3 = false; export let col3 = false;
@ -22,4 +44,15 @@
input { input {
width: 100%; width: 100%;
} }
.label-wrapper {
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
}
.tooltip-icon {
cursor: pointer;
}
</style> </style>

View File

@ -41,8 +41,6 @@
on:toggle={handleEmojiTypeChange} /> on:toggle={handleEmojiTypeChange} />
</div> </div>
{#if data.use_custom_emoji} {#if data.use_custom_emoji}
<!--bind:selectedValue={selectedMentions}
on:select={updateMentions}-->
<div class="multiselect-super"> <div class="multiselect-super">
<Select items={emojis} <Select items={emojis}
Item={EmojiItem} Item={EmojiItem}
@ -106,6 +104,29 @@
<Input col2={true} label="Large Image URL" bind:value={data.image_url} placeholder="https://example.com/image.png" /> <Input col2={true} label="Large Image URL" bind:value={data.image_url} placeholder="https://example.com/image.png" />
<Input col2={true} label="Small Image URL" bind:value={data.thumbnail_url} placeholder="https://example.com/image.png" /> <Input col2={true} label="Small Image URL" bind:value={data.thumbnail_url} placeholder="https://example.com/image.png" />
</div> </div>
<div class="row">
<div class="col-2">
<label for="naming-scheme-wrapper" class="form-label">Naming Scheme</label>
<div class="row" id="naming-scheme-wrapper">
<div>
<label class="form-label">Use Server Default</label>
<Toggle hideLabel
toggledColor="#66bb6a"
untoggledColor="#ccc"
bind:toggled={data.use_server_default_naming_scheme} />
</div>
<div class="col-fill">
{#if !data.use_server_default_naming_scheme}
<Input label="Naming Scheme"
bind:value={data.naming_scheme}
placeholder="ticket-%id%"
tooltipText="Click here for the full placeholder list"
tooltipLink="https://docs.ticketsbot.net" />
{/if}
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</form> </form>
@ -126,6 +147,7 @@
import Dropdown from "../form/Dropdown.svelte"; import Dropdown from "../form/Dropdown.svelte";
import Checkbox from "../form/Checkbox.svelte"; import Checkbox from "../form/Checkbox.svelte";
import Toggle from "svelte-toggle"; import Toggle from "svelte-toggle";
import Slider from "../form/Slider.svelte";
export let guildId; export let guildId;
export let seedDefault = true; export let seedDefault = true;
@ -247,8 +269,7 @@
.forEach((mention) => selectedMentions.push(mention)); .forEach((mention) => selectedMentions.push(mention));
} }
$: data.emote = data.emote; data.emote = data.emote;
console.log(data.emote)
tempColour = intToColour(data.colour); tempColour = intToColour(data.colour);
} }
@ -271,7 +292,8 @@
button_style: "1", button_style: "1",
form_id: "null", form_id: "null",
channel_id: channels.find((c) => c.type === 0).id, channel_id: channels.find((c) => c.type === 0).id,
category_id: channels.find((c) => c.type === 4).id category_id: channels.find((c) => c.type === 4).id,
use_server_default_naming_scheme: true,
}; };
} else { } else {
applyOverrides(); applyOverrides();
@ -295,6 +317,12 @@
height: 100%; height: 100%;
} }
.col-fill {
display: flex;
flex-direction: column;
flex-grow: 1;
}
:global(.col-1-3) { :global(.col-1-3) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -359,6 +387,10 @@
position: absolute; position: absolute;
} }
#naming-scheme-wrapper {
gap: 10px;
}
:global(.multiselect-super) { :global(.multiselect-super) {
display: flex; display: flex;
width: 100%; width: 100%;

View File

@ -13,7 +13,7 @@
<!--<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">--> <!--<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">-->
<!-- Icons --> <!-- Icons -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.1.1/css/all.css">
<!-- GA --> <!-- GA -->
<script async src='https://www.google-analytics.com/analytics.js' <script async src='https://www.google-analytics.com/analytics.js'

View File

@ -38,7 +38,7 @@
export let currentRoute; export let currentRoute;
export let params = {}; export let params = {};
let guildId = currentRoute.namedParams.id let guildId = currentRoute.namedParams.id;
import Head from '../includes/Head.svelte' import Head from '../includes/Head.svelte'
import LoadingScreen from '../includes/LoadingScreen.svelte' import LoadingScreen from '../includes/LoadingScreen.svelte'

4
go.mod
View File

@ -81,3 +81,7 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
nhooyr.io/websocket v1.8.4 // indirect nhooyr.io/websocket v1.8.4 // indirect
) )
replace (
github.com/TicketsBot/database => "../database"
)

View File

@ -4,9 +4,9 @@ import (
"github.com/rxdn/gdl/objects/channel/message" "github.com/rxdn/gdl/objects/channel/message"
) )
func ContainsString(slice []string, target string) bool { func Contains[T comparable](slice []T, value T) bool {
for _, elem := range slice { for _, elem := range slice {
if elem == target { if elem == value {
return true return true
} }
} }