This commit is contained in:
rxdn 2022-08-30 14:42:23 +01:00
parent c2239ef882
commit d745f26dd8
13 changed files with 271 additions and 27 deletions

View File

@ -182,7 +182,7 @@ func GetSettingsHandler(ctx *gin.Context) {
LanguageNames map[i18n.Language]string `json:"language_names"`
}{
Settings: settings,
Languages: i18n.LanguagesAlphabetical,
Languages: i18n.LanguagesAlphabetical[:],
LanguageNames: i18n.FullNames,
})
}

View File

@ -114,13 +114,17 @@ func (s *Settings) Validate(guildId uint64, premiumTier premium.PremiumTier) err
return errors.New("Must be able to view channel to type")
}
if s.Settings.UseThreads {
return fmt.Errorf("threads are disabled")
if s.Settings.UseThreads && s.TicketNotificationChannel == nil {
return errors.New("You must select a ticket notification channel")
}
if !s.Settings.UseThreads {
s.TicketNotificationChannel = nil
}
if s.Language != nil {
if _, ok := i18n.FullNames[*s.Language]; !ok {
return fmt.Errorf("invalid language")
return errors.New("Invalid language")
}
}
@ -222,6 +226,25 @@ func (s *Settings) Validate(guildId uint64, premiumTier premium.PremiumTier) err
return nil
})
group.Go(func() error {
if s.Settings.TicketNotificationChannel != nil {
ch, ok := cache.Instance.GetChannel(*s.Settings.TicketNotificationChannel)
if !ok {
return fmt.Errorf("Invalid ticket notification channel")
}
if ch.GuildId != guildId {
return fmt.Errorf("Ticket notification channel guild ID does not match")
}
if ch.Type != channel.ChannelTypeGuildText {
return fmt.Errorf("Ticket notification channel is not a text channel")
}
}
return nil
})
return group.Wait()
}

View File

@ -1,10 +1,14 @@
package api
import (
"fmt"
"github.com/TicketsBot/GoPanel/botcontext"
dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/database"
"github.com/gin-gonic/gin"
"github.com/rxdn/gdl/rest"
"github.com/rxdn/gdl/rest/request"
"strconv"
)
@ -92,11 +96,61 @@ func removeDefaultMember(ctx *gin.Context, guildId, selfId, snowflake uint64, en
return
}
// Remove on-call role
metadata, err := dbclient.Client.GuildMetadata.Get(guildId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
if metadata.OnCallRole != nil {
botContext, err := botcontext.ContextForGuild(guildId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
if entityType == entityTypeUser {
// If the member is not in the guild we do not have to worry
member, err := botContext.GetGuildMember(guildId, snowflake)
if err == nil {
if member.HasRole(*metadata.OnCallRole) {
// Attempt to remove role but ignore failure
_ = botContext.RemoveGuildMemberRole(guildId, snowflake, *metadata.OnCallRole)
}
} else {
if err, ok := err.(request.RestError); !ok || err.StatusCode != 404 {
ctx.JSON(500, utils.ErrorJson(err))
return
}
}
} else if entityType == entityTypeRole {
// Recreate role
if err := dbclient.Client.GuildMetadata.SetOnCallRole(guildId, nil); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
if err := botContext.DeleteGuildRole(guildId, *metadata.OnCallRole); err != nil && !isUnknownRoleError(err) {
ctx.JSON(500, utils.ErrorJson(err))
return
}
if _, err := createOnCallRole(botContext, guildId, nil); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
} else {
ctx.JSON(500, utils.ErrorStr("Infallible"))
return
}
}
ctx.JSON(200, utils.SuccessResponse)
}
func removeTeamMember(ctx *gin.Context, teamId int, guildId, snowflake uint64, entityType entityType) {
exists, err := dbclient.Client.SupportTeam.Exists(teamId, guildId)
team, exists, err := dbclient.Client.SupportTeam.GetById(guildId, teamId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
@ -107,6 +161,7 @@ func removeTeamMember(ctx *gin.Context, teamId int, guildId, snowflake uint64, e
return
}
// Remove from DB
switch entityType {
case entityTypeUser:
err = dbclient.Client.SupportTeamMembers.Delete(teamId, snowflake)
@ -119,5 +174,89 @@ func removeTeamMember(ctx *gin.Context, teamId int, guildId, snowflake uint64, e
return
}
// Remove on-call role
if team.OnCallRole != nil {
botContext, err := botcontext.ContextForGuild(guildId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
if entityType == entityTypeUser {
// If the member is not in the guild we do not have to worry
member, err := botContext.GetGuildMember(guildId, snowflake)
if err == nil {
if member.HasRole(*team.OnCallRole) {
// Attempt to remove role but ignore failure
_ = botContext.RemoveGuildMemberRole(guildId, snowflake, *team.OnCallRole)
}
} else {
if err, ok := err.(request.RestError); !ok || err.StatusCode != 404 {
ctx.JSON(500, utils.ErrorJson(err))
return
}
}
_ = botContext.RemoveGuildMemberRole(guildId, snowflake, *team.OnCallRole)
} else if entityType == entityTypeRole {
// Recreate role
if err := dbclient.Client.SupportTeam.SetOnCallRole(teamId, nil); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
if err := botContext.DeleteGuildRole(guildId, *team.OnCallRole); err != nil && !isUnknownRoleError(err) {
ctx.JSON(500, utils.ErrorJson(err))
return
}
if _, err := createOnCallRole(botContext, guildId, &team); err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
} else {
ctx.JSON(500, utils.ErrorStr("Infallible"))
}
}
ctx.JSON(200, utils.SuccessResponse)
}
}
func createOnCallRole(botContext botcontext.BotContext, guildId uint64, team *database.SupportTeam) (uint64, error) {
var roleName string
if team == nil {
roleName = "On Call" // TODO: Translate
} else {
roleName = utils.StringMax(fmt.Sprintf("On Call - %s", team.Name), 100)
}
data := rest.GuildRoleData{
Name: roleName,
Hoist: utils.Ptr(false),
Mentionable: utils.Ptr(false),
}
role, err := botContext.CreateGuildRole(guildId, data)
if err != nil {
return 0, err
}
if team == nil {
if err := dbclient.Client.GuildMetadata.SetOnCallRole(guildId, &role.Id); err != nil {
return 0, err
}
} else {
if err := dbclient.Client.SupportTeam.SetOnCallRole(team.Id, &role.Id); err != nil {
return 0, err
}
}
return role.Id, nil
}
func isUnknownRoleError(err error) bool {
if err, ok := err.(request.RestError); ok && err.ApiError.Message == "Unknown Role" {
return true
}
return false
}

View File

@ -95,6 +95,21 @@ func (ctx BotContext) GetGuildMember(guildId, userId uint64) (m member.Member, e
return
}
func (ctx BotContext) RemoveGuildMemberRole(guildId, userId, roleId uint64) (err error) {
err = rest.RemoveGuildMemberRole(ctx.Token, ctx.RateLimiter, guildId, userId, roleId)
return
}
func (ctx BotContext) CreateGuildRole(guildId uint64, data rest.GuildRoleData) (role guild.Role, err error) {
role, err = rest.CreateGuildRole(ctx.Token, ctx.RateLimiter, guildId, data)
return
}
func (ctx BotContext) DeleteGuildRole(guildId, roleId uint64) (err error) {
err = rest.DeleteGuildRole(ctx.Token, ctx.RateLimiter, guildId, roleId)
return
}
func (ctx BotContext) GetUser(userId uint64) (u user.User, err error) {
u, err = rest.GetUser(ctx.Token, ctx.RateLimiter, userId)
if err == nil {

View File

@ -1,4 +1,4 @@
<Dropdown {col1} {col2} {col3} {col4} bind:value label={label}>
<Dropdown {col1} {col2} {col3} {col4} bind:value {label} {disabled} >
{#if withNull}
<option value=null>
{nullLabel}
@ -18,6 +18,7 @@
export let value;
export let label;
export let disabled = false;
export let channels = [];
export let withNull = false;
export let nullLabel = "Disabled";

View File

@ -11,7 +11,14 @@
{#if tooltip !== undefined}
<div style="">
<Tooltip tip={tooltip} top color="#121212">
<i class="fas fa-circle-info form-label tooltip-icon"></i>
{#if tooltipUrl !== undefined}
<a href={tooltipUrl} 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}
@ -35,6 +42,7 @@
export let defaultOpen = false;
export let tooltip;
export let tooltipUrl;
let expanded = false;
let showOverflow = true;

View File

@ -0,0 +1,27 @@
<Badge>
<div class="inner">
{#if icon !== undefined}
<i class="{icon}"></i>
{/if}
<span class="text">
<slot />
</span>
</div>
</Badge>
<script>
import Badge from "./Badge.svelte";
export let text;
export let icon;
</script>
<style>
.inner {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
color: white;
}
</style>

View File

@ -1,5 +1,9 @@
<div class:col-1={col1} class:col-2={col2} class:col-3={col3} class:col-4={col4}>
<label for="input" class="form-label">{label}</label>
<slot name="label">
<label for="input" class="form-label">
{label}
</label>
</slot>
<input id="input" class="form-checkbox" type=checkbox bind:checked={value} on:change {disabled}>
</div>

View File

@ -2,7 +2,7 @@
{#if label !== undefined}
<label for="input" class="form-label">{label}</label>
{/if}
<select id="input" class="form-input" on:change bind:value={value}>
<select id="input" class="form-input" on:change bind:value={value} {disabled}>
<slot />
</select>
</div>
@ -16,6 +16,7 @@
<script>
export let value;
export let label;
export let disabled = false;
export let col1 = false;
export let col2 = false;

View File

@ -27,13 +27,27 @@
</div>
</Collapsible>
<Collapsible defaultOpen tooltip="Click here to find out more about thread mode" tooltipUrl="https://docs.ticketsbot.net">
<span slot="header" class="header">
Thread Mode
<IconBadge icon="fas fa-flask">Beta</IconBadge>
</span>
<div slot="content" class="col-1">
<div class="row">
<Checkbox label="Enabled" bind:value={data.use_threads}/>
<ChannelDropdown label="Ticket Notification Channel" col4 {channels} disabled={!data.use_threads}
bind:value={data.ticket_notification_channel}/>
</div>
</div>
</Collapsible>
<Collapsible defaultOpen>
<span slot="header">Tickets</span>
<div slot="content" class="col-1">
<div class="row">
<ChannelDropdown label="Archive Channel" col3=true channels={channels} withNull={true}
<ChannelDropdown label="Archive Channel" col4 channels={channels} withNull={true}
bind:value={data.archive_channel}/>
<Dropdown label="Overflow Category" col3=true bind:value={data.overflow_category_id}>
<Dropdown label="Overflow Category" col4 bind:value={data.overflow_category_id}>
<option value=-1>Disabled</option>
<option value=-2>Uncategorised (Appears at top of channel list)</option>
{#each channels as channel}
@ -197,6 +211,7 @@
import PremiumBadge from "../PremiumBadge.svelte";
import {toDays, toHours, toMinutes} from "../../js/timeutil";
import Toggle from "../form/Toggle.svelte";
import IconBadge from "../IconBadge.svelte";
export let guildId;
@ -476,6 +491,6 @@
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
gap: 5px;
}
</style>

7
go.mod
View File

@ -6,9 +6,9 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/TicketsBot/archiverclient v0.0.0-20220326163414-558fd52746dc
github.com/TicketsBot/common v0.0.0-20220703211704-f792aa9f0c42
github.com/TicketsBot/database v0.0.0-20220804213011-190281e8f797
github.com/TicketsBot/database v0.0.0-20220830131231-b5540b57f6cb
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c
github.com/TicketsBot/worker v0.0.0-20220802140902-30ca73aea6b8
github.com/TicketsBot/worker v0.0.0-20220830131837-12d85aca5c71
github.com/apex/log v1.1.2
github.com/getsentry/sentry-go v0.13.0
github.com/gin-gonic/contrib v0.0.0-20191209060500-d6e26eeaa607
@ -25,7 +25,7 @@ require (
github.com/jackc/pgx/v4 v4.7.1
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
github.com/pkg/errors v0.9.1
github.com/rxdn/gdl v0.0.0-20220726141522-788c9ce67ad0
github.com/rxdn/gdl v0.0.0-20220830131333-09a2e5819976
github.com/sirupsen/logrus v1.6.0
github.com/weppos/publicsuffix-go v0.20.0
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
@ -80,6 +80,7 @@ require (
github.com/ugorji/go/codec v1.2.6 // indirect
go.uber.org/atomic v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect

20
go.sum
View File

@ -39,14 +39,14 @@ github.com/TicketsBot/archiverclient v0.0.0-20220326163414-558fd52746dc h1:n15W8
github.com/TicketsBot/archiverclient v0.0.0-20220326163414-558fd52746dc/go.mod h1:2KcfHS0JnSsgcxZBs3NyWMXNQzEo67mBSGOyzHPWOCc=
github.com/TicketsBot/common v0.0.0-20220703211704-f792aa9f0c42 h1:3/qnbrEfL8gqSbjJ4o7WKkdoPngmhjAGEXFwteEjpqs=
github.com/TicketsBot/common v0.0.0-20220703211704-f792aa9f0c42/go.mod h1:WxHh6bY7KhIqdayeOp5f0Zj2NNi/7QqCQfMEqHnpdAM=
github.com/TicketsBot/database v0.0.0-20220804213011-190281e8f797 h1:1RIBwjPJqTmVrBztWQxE6OHKZjwTqxfXMRQrPuMaQzk=
github.com/TicketsBot/database v0.0.0-20220804213011-190281e8f797/go.mod h1:gAtOoQKZfCkQ4AoNWQUSl51Fnlqk+odzD/hZ1e1sXyI=
github.com/TicketsBot/database v0.0.0-20220830131231-b5540b57f6cb h1:mXBn09KBzbVRmg+k1JC67DL8YXisCO74D9nIW/xi9Vg=
github.com/TicketsBot/database v0.0.0-20220830131231-b5540b57f6cb/go.mod h1:gAtOoQKZfCkQ4AoNWQUSl51Fnlqk+odzD/hZ1e1sXyI=
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c h1:OqGjFH6mbE6gd+NqI2ARJdtH3UUvhiAkD0r0fhGJK2s=
github.com/TicketsBot/logarchiver v0.0.0-20220326162808-cdf0310f5e1c/go.mod h1:jgi2OXQKsd5nUnTIRkwvPmeuD/i7OhN68LKMssuQY1c=
github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261 h1:NHD5GB6cjlkpZFjC76Yli2S63/J2nhr8MuE6KlYJpQM=
github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261/go.mod h1:2zPxDAN2TAPpxUPjxszjs3QFKreKrQh5al/R3cMXmYk=
github.com/TicketsBot/worker v0.0.0-20220802140902-30ca73aea6b8 h1:/hxUoNMsAD1HeG66iPDn0G5zQ8RNltaiL2h50LNplaA=
github.com/TicketsBot/worker v0.0.0-20220802140902-30ca73aea6b8/go.mod h1:E8y+9Xu8el7QzHALhR/IFvITcJJkDeAxfsBFIEEWJuo=
github.com/TicketsBot/worker v0.0.0-20220830131837-12d85aca5c71 h1:9cXKmN0wDrafXpLhbaOgFCBZlP903nFDm6sPo9UPHwA=
github.com/TicketsBot/worker v0.0.0-20220830131837-12d85aca5c71/go.mod h1:1lgdQCRso/tfis5E5bb2CyKPMbSDJpigM/LfmubgKHE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -196,8 +196,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -405,8 +405,10 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/rxdn/gdl v0.0.0-20220726141522-788c9ce67ad0 h1:i2WqGZFCpow9d9ZfdVbEExeCkAnw969CIMyOtebqGVY=
github.com/rxdn/gdl v0.0.0-20220726141522-788c9ce67ad0/go.mod h1:HtxfLp4OaoPoDJHQ4JOx/QeLH2d40VgT3wNOf7ETsRE=
github.com/rxdn/gdl v0.0.0-20220816151714-b119c7b939a9 h1:X4kT2+HgTHIfBFWdDbk4nzqxhfeq/mdkr/WUPs2R5LE=
github.com/rxdn/gdl v0.0.0-20220816151714-b119c7b939a9/go.mod h1:HtxfLp4OaoPoDJHQ4JOx/QeLH2d40VgT3wNOf7ETsRE=
github.com/rxdn/gdl v0.0.0-20220830131333-09a2e5819976 h1:8txEQWvEVqHZfrD5WFIj4NMC+waEPYBf9abZjOP+rkQ=
github.com/rxdn/gdl v0.0.0-20220830131333-09a2e5819976/go.mod h1:HtxfLp4OaoPoDJHQ4JOx/QeLH2d40VgT3wNOf7ETsRE=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/progressbar/v3 v3.8.2 h1:2kZJwZCpb+E/V79kGO7daeq+hUwUJW0A5QD1Wv455dA=
github.com/schollz/progressbar/v3 v3.8.2/go.mod h1:9KHLdyuXczIsyStQwzvW8xiELskmX7fQMaZdN23nAv8=
@ -493,6 +495,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -681,8 +685,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -4,6 +4,7 @@ import (
"encoding/base64"
"math/rand"
"strconv"
"strings"
)
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
@ -22,7 +23,8 @@ func IsInt(str string) bool {
}
func Base64Decode(s string) string {
b, err := base64.StdEncoding.DecodeString(s); if err != nil {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return ""
}
return string(b)
@ -32,6 +34,10 @@ func Base64Encode(s string) string {
return base64.StdEncoding.EncodeToString([]byte(s))
}
func StrPtr(s string) *string {
return &s
func StringMax(str string, max int, suffix ...string) string {
if len(str) > max {
return str[:max] + strings.Join(suffix, "")
}
return str
}