slash roleypoly yay
This commit is contained in:
parent
02f5075d3b
commit
607d7e121c
22 changed files with 394 additions and 66 deletions
|
@ -4,4 +4,6 @@ DISCORD_PUBLIC_KEY=cc
|
|||
DISCORD_BOT_TOKEN=dd
|
||||
|
||||
LISTEN_ADDR=:8169
|
||||
STORAGE_DIR=./.storage
|
||||
STORAGE_DIR=./.storage
|
||||
|
||||
PUBLIC_BASE_URL=http://localhost:8169
|
|
@ -13,6 +13,23 @@ import (
|
|||
|
||||
type DiscordClientMock struct {
|
||||
mock.Mock
|
||||
|
||||
BotToken string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
func NewDiscordClientMock() *DiscordClientMock {
|
||||
dcm := &DiscordClientMock{
|
||||
BotToken: "bot-token",
|
||||
ClientID: "client-id",
|
||||
ClientSecret: "client-secret",
|
||||
}
|
||||
|
||||
dcm.On("BotAuth", mock.AnythingOfType("*http.Request"))
|
||||
dcm.On("GetClientID").Return(dcm.ClientID)
|
||||
|
||||
return dcm
|
||||
}
|
||||
|
||||
func (c *DiscordClientMock) Do(req *http.Request) (*http.Response, error) {
|
||||
|
@ -40,3 +57,7 @@ func (c *DiscordClientMock) MockResponse(method, path string, statusCode int, da
|
|||
return req.Method == method && pathMatcher.MatchString(req.URL.Path)
|
||||
})).Return(r, nil)
|
||||
}
|
||||
|
||||
func (c *DiscordClientMock) GetClientID() string {
|
||||
return c.Called().String(0)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const DiscordBaseUrl = "https://discord.com/api/v10"
|
|||
type IDiscordClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
BotAuth(req *http.Request)
|
||||
GetClientID() string
|
||||
}
|
||||
|
||||
type DiscordClient struct {
|
||||
|
@ -64,3 +65,7 @@ func OutputResponse(resp *http.Response, dst any) error {
|
|||
// TODO: more checks?
|
||||
return json.NewDecoder(resp.Body).Decode(dst)
|
||||
}
|
||||
|
||||
func (d *DiscordClient) GetClientID() string {
|
||||
return d.ClientID
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/discord"
|
||||
"git.sapphic.engineer/roleypoly/v4/discord/clientmock"
|
||||
"git.sapphic.engineer/roleypoly/v4/types/fixtures"
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
)
|
||||
|
@ -17,7 +18,7 @@ var (
|
|||
)
|
||||
|
||||
func TestGetMember(t *testing.T) {
|
||||
dc := defaultMocks(t)
|
||||
dc := clientmock.NewDiscordClientMock()
|
||||
g := discord.Guild{
|
||||
Client: dc,
|
||||
DiscordGuild: fixtures.Guild,
|
||||
|
|
|
@ -6,11 +6,6 @@ import (
|
|||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
)
|
||||
|
||||
type IGuildService interface {
|
||||
Client() IDiscordClient
|
||||
GetGuild(guildID string) (IGuild, error)
|
||||
}
|
||||
|
||||
type GuildService struct {
|
||||
client IDiscordClient
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/discord"
|
||||
"git.sapphic.engineer/roleypoly/v4/discord/clientmock"
|
||||
"git.sapphic.engineer/roleypoly/v4/types/fixtures"
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -16,7 +17,7 @@ var (
|
|||
)
|
||||
|
||||
func TestGetGuild(t *testing.T) {
|
||||
dc := defaultMocks(t)
|
||||
dc := clientmock.NewDiscordClientMock()
|
||||
gs := discord.NewGuildService(dc)
|
||||
|
||||
dc.MockResponse("GET", utils.J("guilds", fixtures.Guild.ID), 200, fixtureGuild)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package discord_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/discord/clientmock"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func defaultMocks(t *testing.T) *clientmock.DiscordClientMock {
|
||||
dc := &clientmock.DiscordClientMock{}
|
||||
|
||||
dc.On("BotAuth", mock.AnythingOfType("*http.Request"))
|
||||
|
||||
return dc
|
||||
}
|
52
interactions/cmd_roleypoly.go
Normal file
52
interactions/cmd_roleypoly.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package interactions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
)
|
||||
|
||||
func (i *Interactions) CmdRoleypoly(ix Interaction) (InteractionResponse, error) {
|
||||
mentions := i.CommandsMap()
|
||||
hostname := "roleypoly.com"
|
||||
publicURL, _ := url.Parse(i.PublicBaseURL)
|
||||
if publicURL != nil {
|
||||
hostname = publicURL.Host
|
||||
}
|
||||
|
||||
return InteractionResponse{
|
||||
Type: ResponseChannelMessage,
|
||||
Data: InteractionResponseData{
|
||||
Flags: FlagEphemeral,
|
||||
Embeds: []Embed{{
|
||||
Color: utils.EmbedColor,
|
||||
Title: fmt.Sprintf(":beginner: Hey there, %s", ix.GetName()),
|
||||
Description: "Try these slash commands, or pick roles from a browser!",
|
||||
Fields: []EmbedField{
|
||||
{
|
||||
Name: "See all the roles",
|
||||
Value: utils.Or(mentions["pickable-roles"], "/pickable-roles"),
|
||||
},
|
||||
{
|
||||
Name: "Pick a role",
|
||||
Value: utils.Or(mentions["pick-role"], "/pick-role"),
|
||||
},
|
||||
{
|
||||
Name: "Remove a role",
|
||||
Value: utils.Or(mentions["remove-role"], "/remove-role"),
|
||||
},
|
||||
},
|
||||
}},
|
||||
Components: []ButtonComponent{
|
||||
{
|
||||
Type: 2, // Button
|
||||
Style: 5, // Link
|
||||
Label: fmt.Sprintf("Pick roles on %s", hostname),
|
||||
Emoji: utils.GetRandomEmoji(),
|
||||
URL: fmt.Sprintf("%s/s/%s", i.PublicBaseURL, ix.GuildID),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
66
interactions/cmd_roleypoly_test.go
Normal file
66
interactions/cmd_roleypoly_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package interactions_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/interactions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCmdRoleypoly(t *testing.T) {
|
||||
i, dcm, _ := makeInteractions(t)
|
||||
ix := mkInteraction("roleypoly")
|
||||
|
||||
commands := []interactions.InteractionCommand{
|
||||
{ID: "1", Name: "pick-role"},
|
||||
{ID: "2", Name: "pickable-roles"},
|
||||
{ID: "3", Name: "remove-role"},
|
||||
}
|
||||
dcm.MockResponse("GET", "/applications/*/commands", 200, commands)
|
||||
|
||||
ir, err := i.CmdRoleypoly(ix)
|
||||
assert.Nil(t, err)
|
||||
|
||||
embed := ir.Data.Embeds[0]
|
||||
|
||||
// test that the button is cool
|
||||
button := ir.Data.Components[0]
|
||||
assert.Equal(t, fmt.Sprintf("%s/s/guild-id", i.PublicBaseURL), button.URL)
|
||||
|
||||
// test the command mentions
|
||||
tests := map[string]string{
|
||||
"See all the roles": "</2:pickable-roles>",
|
||||
"Pick a role": "</1:pick-role>",
|
||||
"Remove a role": "</3:remove-role>",
|
||||
}
|
||||
|
||||
for _, field := range embed.Fields {
|
||||
assert.Equal(t, tests[field.Name], field.Value)
|
||||
}
|
||||
|
||||
// test weird name cases
|
||||
// not weird
|
||||
ix.Member.Nick = "Doll"
|
||||
ix.Member.User.GlobalName = "Dolly"
|
||||
ix.Member.User.Username = "41666."
|
||||
ir, err = i.CmdRoleypoly(ix)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ":beginner: Hey there, Doll", ir.Data.Embeds[0].Title)
|
||||
|
||||
// no nick
|
||||
ix.Member.Nick = ""
|
||||
ix.Member.User.GlobalName = "Dolly"
|
||||
ix.Member.User.Username = "41666."
|
||||
ir, err = i.CmdRoleypoly(ix)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ":beginner: Hey there, Dolly", ir.Data.Embeds[0].Title)
|
||||
|
||||
// no globalname
|
||||
ix.Member.Nick = ""
|
||||
ix.Member.User.GlobalName = ""
|
||||
ix.Member.User.Username = "41666."
|
||||
ir, err = i.CmdRoleypoly(ix)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ":beginner: Hey there, 41666.", ir.Data.Embeds[0].Title)
|
||||
}
|
|
@ -2,6 +2,7 @@ package interactions
|
|||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
|
@ -9,23 +10,30 @@ import (
|
|||
|
||||
"git.sapphic.engineer/roleypoly/v4/discord"
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
)
|
||||
|
||||
type Interactions struct {
|
||||
PublicKey ed25519.PublicKey
|
||||
Guilds *discord.GuildService
|
||||
Router *InteractionRouter
|
||||
PublicKey ed25519.PublicKey
|
||||
PublicBaseURL string
|
||||
Guilds *discord.GuildService
|
||||
Router *InteractionRouter
|
||||
|
||||
// if interactions are able to be used
|
||||
OK bool
|
||||
|
||||
commandsMap []InteractionCommand
|
||||
commandsMentions map[string]string
|
||||
}
|
||||
|
||||
func NewInteractions(publicKey string, guildsService *discord.GuildService) *Interactions {
|
||||
func NewInteractions(publicKey string, publicBaseURL string, guildsService *discord.GuildService) *Interactions {
|
||||
|
||||
return &Interactions{
|
||||
PublicKey: ed25519.PublicKey(publicKey),
|
||||
Guilds: guildsService,
|
||||
Router: NewInteractionRouter(),
|
||||
OK: len(publicKey) != 0,
|
||||
PublicKey: ed25519.PublicKey(publicKey),
|
||||
PublicBaseURL: publicBaseURL,
|
||||
Guilds: guildsService,
|
||||
Router: NewInteractionRouter(),
|
||||
OK: len(publicKey) != 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,6 +76,32 @@ func (i *Interactions) PostHandler(c fiber.Ctx) error {
|
|||
return types.NewAPIError(http.StatusNotImplemented, "not implemented").Send(c)
|
||||
}
|
||||
|
||||
// func (i *Interactions) Respond(ix Interaction, ir InteractionResponse) error {
|
||||
// i.Guilds
|
||||
// }
|
||||
func (i *Interactions) CommandsMap() map[string]string {
|
||||
if i.commandsMentions != nil {
|
||||
return i.commandsMentions
|
||||
}
|
||||
|
||||
dc := i.Guilds.Client()
|
||||
req := discord.NewRequest("GET", utils.J("applications", i.Guilds.Client().GetClientID(), "commands"))
|
||||
dc.BotAuth(req)
|
||||
|
||||
resp, err := dc.Do(req)
|
||||
if err != nil {
|
||||
log.Println("interactions: commands map fetch failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
i.commandsMap = []InteractionCommand{}
|
||||
err = discord.OutputResponse(resp, &i.commandsMap)
|
||||
if err != nil {
|
||||
log.Println("interactions: commands map parse failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
i.commandsMentions = map[string]string{}
|
||||
for _, command := range i.commandsMap {
|
||||
i.commandsMentions[command.Name] = fmt.Sprintf("</%s:%s>", command.ID, command.Name)
|
||||
}
|
||||
|
||||
return i.commandsMentions
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestNewInteractions(t *testing.T) {
|
|||
|
||||
gs := &discord.GuildService{}
|
||||
|
||||
i := interactions.NewInteractions(key, gs)
|
||||
i := interactions.NewInteractions(key, "http://roleypoly.local", gs)
|
||||
assert.True(t, i.OK, "interactions OK")
|
||||
assert.Equal(t, string(i.PublicKey), key, "public key accepted from args")
|
||||
}
|
||||
|
|
|
@ -12,6 +12,18 @@ type Interaction struct {
|
|||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (ix Interaction) GetName() string {
|
||||
if ix.Member.Nick != "" {
|
||||
return ix.Member.Nick
|
||||
}
|
||||
|
||||
if ix.Member.User.GlobalName != "" {
|
||||
return ix.Member.User.GlobalName
|
||||
}
|
||||
|
||||
return ix.Member.User.Username
|
||||
}
|
||||
|
||||
const (
|
||||
TypePing uint64 = 1
|
||||
TypeApplicationCommand uint64 = 2
|
||||
|
@ -43,9 +55,59 @@ const (
|
|||
)
|
||||
|
||||
type InteractionResponseData struct {
|
||||
Content string
|
||||
Embeds []Embed
|
||||
Flags int
|
||||
Content string `json:"content,omitempty"`
|
||||
Components []ButtonComponent `json:"components,omitempty"`
|
||||
Embeds []Embed `json:"embeds,omitempty"`
|
||||
Flags int `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
type Embed struct{}
|
||||
type ButtonComponent struct {
|
||||
Type int `json:"type,omitempty"`
|
||||
Style int `json:"style,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Emoji types.Emoji `json:"emoji,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type InteractionCommand struct {
|
||||
ID string `json:"id"`
|
||||
Type int `json:"type"`
|
||||
GuildID string `json:"guild_id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Options InteractionCommandOption `json:"options"`
|
||||
ChannelTypes []int `json:"channel_types,omitempty"`
|
||||
}
|
||||
|
||||
type InteractionCommandOption struct {
|
||||
Type int `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
}
|
||||
|
||||
type Embed struct {
|
||||
Author EmbedAuthor `json:"author"`
|
||||
Color uint32 `json:"color"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Fields []EmbedField `json:"fields"`
|
||||
Footer EmbedFooter `json:"footer"`
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type EmbedField struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Inline bool `json:"inline,omitempty"`
|
||||
}
|
||||
|
||||
type EmbedFooter struct {
|
||||
Text string `json:"text"`
|
||||
IconURL string `json:"icon_url,omitempty"`
|
||||
ProxyIconURL string `json:"proxy_icon_url,omitempty"`
|
||||
}
|
||||
|
||||
type EmbedAuthor struct {
|
||||
Name string `json:"name"`
|
||||
IconURL string `json:"icon_url"`
|
||||
}
|
||||
|
|
|
@ -10,28 +10,32 @@ import (
|
|||
"time"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/discord"
|
||||
"git.sapphic.engineer/roleypoly/v4/discord/clientmock"
|
||||
"git.sapphic.engineer/roleypoly/v4/interactions"
|
||||
"git.sapphic.engineer/roleypoly/v4/types/fixtures"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func makeInteractions(t *testing.T) (*interactions.Interactions, *discord.GuildService, ed25519.PrivateKey) {
|
||||
func makeInteractions(t *testing.T) (*interactions.Interactions, *clientmock.DiscordClientMock, ed25519.PrivateKey) {
|
||||
pub, priv, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Errorf("makeInteractions: failed to generate key: %v", err)
|
||||
}
|
||||
|
||||
gs := &discord.GuildService{}
|
||||
dcm := clientmock.NewDiscordClientMock()
|
||||
gs := discord.NewGuildService(dcm)
|
||||
|
||||
i := &interactions.Interactions{
|
||||
PublicKey: pub,
|
||||
Guilds: gs,
|
||||
Router: interactions.NewInteractionRouter(),
|
||||
OK: true,
|
||||
PublicKey: pub,
|
||||
PublicBaseURL: "http://roleypoly.local",
|
||||
Guilds: gs,
|
||||
Router: interactions.NewInteractionRouter(),
|
||||
OK: true,
|
||||
}
|
||||
|
||||
return i, gs, priv
|
||||
return i, dcm, priv
|
||||
}
|
||||
|
||||
func cmdHelloWorld(ix interactions.Interaction) (interactions.InteractionResponse, error) {
|
||||
|
@ -85,7 +89,9 @@ func sendInteraction(t *testing.T, i *interactions.Interactions, key ed25519.Pri
|
|||
|
||||
func mkInteraction(name string) interactions.Interaction {
|
||||
return interactions.Interaction{
|
||||
Type: interactions.TypeApplicationCommand,
|
||||
GuildID: "guild-id",
|
||||
Type: interactions.TypeApplicationCommand,
|
||||
Member: fixtures.Member,
|
||||
Data: interactions.InteractionData{
|
||||
Name: name,
|
||||
},
|
||||
|
|
26
main.go
26
main.go
|
@ -2,27 +2,27 @@ package main // import "git.sapphic.engineer/roleypoly/v4"
|
|||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/discord"
|
||||
"git.sapphic.engineer/roleypoly/v4/roleypoly"
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := roleypoly.CreateFiberApp()
|
||||
|
||||
clientID := os.Getenv("DISCORD_CLIENT_ID")
|
||||
clientSecret := os.Getenv("DISCORD_CLIENT_SECRET")
|
||||
botToken := os.Getenv("DISCORD_BOT_TOKEN")
|
||||
dc := discord.NewDiscordClient(clientID, clientSecret, botToken)
|
||||
dc := discord.NewDiscordClient(
|
||||
utils.DiscordClientID,
|
||||
utils.DiscordClientSecret,
|
||||
utils.DiscordBotToken,
|
||||
)
|
||||
|
||||
publicKey := os.Getenv("DISCORD_PUBLIC_KEY")
|
||||
roleypoly.SetupControllers(app, dc, publicKey)
|
||||
roleypoly.SetupControllers(
|
||||
app,
|
||||
dc,
|
||||
utils.DiscordPublicKey,
|
||||
utils.PublicBaseURL,
|
||||
)
|
||||
|
||||
listenAddr := os.Getenv("LISTEN_ADDR")
|
||||
if listenAddr == "" {
|
||||
listenAddr = ":8169"
|
||||
}
|
||||
|
||||
log.Fatal(app.Listen(listenAddr))
|
||||
log.Fatal(app.Listen(utils.ListenAddr))
|
||||
}
|
||||
|
|
|
@ -29,14 +29,14 @@ func CreateFiberApp() *fiber.App {
|
|||
return app
|
||||
}
|
||||
|
||||
func SetupControllers(app *fiber.App, dc *discord.DiscordClient, publicKey string) {
|
||||
func SetupControllers(app *fiber.App, dc *discord.DiscordClient, publicKey string, publicBaseURL string) {
|
||||
gs := discord.NewGuildService(dc)
|
||||
|
||||
(&testing.TestingController{
|
||||
Guilds: gs,
|
||||
}).Routes(app.Group("/testing"))
|
||||
|
||||
interactions.NewInteractions(publicKey, gs).Routes(app.Group("/interactions"))
|
||||
interactions.NewInteractions(publicKey, publicBaseURL, gs).Routes(app.Group("/interactions"))
|
||||
}
|
||||
|
||||
// func getStorageDirectory() string {
|
||||
|
|
7
types/discord.go
Normal file
7
types/discord.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package types
|
||||
|
||||
type Emoji struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Animated bool `json:"animated,omitempty"`
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package types
|
||||
|
||||
type DiscordUser struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Avatar string `json:"avatar"`
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Avatar string `json:"avatar"`
|
||||
GlobalName string `json:"globalName"`
|
||||
}
|
||||
|
|
5
utils/const.go
Normal file
5
utils/const.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package utils
|
||||
|
||||
const (
|
||||
EmbedColor = 0x453e3d
|
||||
)
|
61
utils/emojis.go
Normal file
61
utils/emojis.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
)
|
||||
|
||||
var (
|
||||
AllEmojis = []types.Emoji{
|
||||
// Repeated for probability reasons
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext", ID: "785201823945850942"},
|
||||
{Name: "roleypolynext_420", ID: "785201824101564466"},
|
||||
{Name: "roleypolynext_ace", ID: "785201823497715723"},
|
||||
{Name: "roleypolynext_ace", ID: "785201823497715723"},
|
||||
{Name: "roleypolynext_ace", ID: "785201823497715723"},
|
||||
{Name: "roleypolynext_ace", ID: "785201823497715723"},
|
||||
{Name: "roleypolynext_ace", ID: "785201823497715723"},
|
||||
{Name: "roleypolynext_ace", ID: "785201823497715723"},
|
||||
{Name: "roleypolynext_anim", ID: "785203654125682698", Animated: true},
|
||||
{Name: "roleypolynext_bi", ID: "785201823584616500"},
|
||||
{Name: "roleypolynext_bi", ID: "785201823584616500"},
|
||||
{Name: "roleypolynext_bi", ID: "785201823584616500"},
|
||||
{Name: "roleypolynext_bi", ID: "785201823584616500"},
|
||||
{Name: "roleypolynext_bi", ID: "785201823584616500"},
|
||||
{Name: "roleypolynext_bi", ID: "785201823584616500"},
|
||||
{Name: "roleypolynext_lesbian", ID: "785201823627476993"},
|
||||
{Name: "roleypolynext_lesbian", ID: "785201823627476993"},
|
||||
{Name: "roleypolynext_lesbian", ID: "785201823627476993"},
|
||||
{Name: "roleypolynext_lesbian", ID: "785201823627476993"},
|
||||
{Name: "roleypolynext_lesbian", ID: "785201823627476993"},
|
||||
{Name: "roleypolynext_lny", ID: "785201824092651560"},
|
||||
{Name: "roleypolynext_newyear", ID: "785201824277200956"},
|
||||
{Name: "roleypolynext_pride", ID: "785201823501385749"},
|
||||
{Name: "roleypolynext_pride", ID: "785201823501385749"},
|
||||
{Name: "roleypolynext_pride", ID: "785201823501385749"},
|
||||
{Name: "roleypolynext_pride", ID: "785201823501385749"},
|
||||
{Name: "roleypolynext_pride", ID: "785201823501385749"},
|
||||
{Name: "roleypolynext_spectrum", ID: "785201823526682666"},
|
||||
{Name: "roleypolynext_trans", ID: "785201823967215617"},
|
||||
{Name: "roleypolynext_trans", ID: "785201823967215617"},
|
||||
{Name: "roleypolynext_trans", ID: "785201823967215617"},
|
||||
{Name: "roleypolynext_trans", ID: "785201823967215617"},
|
||||
{Name: "roleypolynext_trans", ID: "785201823967215617"},
|
||||
}
|
||||
)
|
||||
|
||||
func GetRandomEmoji() types.Emoji {
|
||||
idx := rand.UintN(uint(len(AllEmojis)))
|
||||
return AllEmojis[idx]
|
||||
}
|
12
utils/env.go
Normal file
12
utils/env.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
var (
|
||||
DiscordBotToken = os.Getenv("DISCORD_BOT_TOKEN")
|
||||
DiscordClientID = os.Getenv("DISCORD_CLIENT_ID")
|
||||
DiscordClientSecret = os.Getenv("DISCORD_CLIENT_SECRET")
|
||||
DiscordPublicKey = os.Getenv("DISCORD_PUBLIC_KEY")
|
||||
ListenAddr = Or(os.Getenv("LISTEN_ADDR"), ":8169")
|
||||
PublicBaseURL = os.Getenv("PUBLIC_BASE_URL")
|
||||
)
|
|
@ -12,3 +12,11 @@ func HeadTitle(text string) string {
|
|||
func J(parts ...string) string {
|
||||
return "/" + strings.Join(parts, "/")
|
||||
}
|
||||
|
||||
func Or(input, fallback string) string {
|
||||
if input == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
|
|
@ -15,3 +15,8 @@ func TestHeadTitle(t *testing.T) {
|
|||
func TestJ(t *testing.T) {
|
||||
assert.Equal(t, "/a/b/c", utils.J("a", "b", "c"))
|
||||
}
|
||||
|
||||
func TestOr(t *testing.T) {
|
||||
assert.Equal(t, "a", utils.Or("a", "b"))
|
||||
assert.Equal(t, "b", utils.Or("", "b"))
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue