Add welcome (#17)
Reviewed-on: #17 Co-authored-by: jolheiser <john.olheiser@gmail.com> Co-committed-by: jolheiser <john.olheiser@gmail.com>track
parent
a15bb13ba5
commit
449f99cf69
24
Makefile
24
Makefile
|
@ -1,24 +0,0 @@
|
||||||
GO ?= go
|
|
||||||
|
|
||||||
.PHONY: fmt
|
|
||||||
fmt:
|
|
||||||
$(GO) fmt ./...
|
|
||||||
|
|
||||||
.PHONY: imp
|
|
||||||
imp:
|
|
||||||
imp -w
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test:
|
|
||||||
$(GO) test -race ./...
|
|
||||||
|
|
||||||
.PHONY: vet
|
|
||||||
vet:
|
|
||||||
$(GO) vet ./...
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
$(GO) build
|
|
||||||
|
|
||||||
.PHONY: check
|
|
||||||
check: generate imp fmt test vet build
|
|
|
@ -25,6 +25,34 @@ meme_rate = "0"
|
||||||
# Imgur Client ID
|
# Imgur Client ID
|
||||||
imgur_client_id = ""
|
imgur_client_id = ""
|
||||||
|
|
||||||
|
# Welcome new users
|
||||||
|
[welcome]
|
||||||
|
|
||||||
|
# Channel ID of the welcome channel
|
||||||
|
channel = "0"
|
||||||
|
|
||||||
|
# Message to send in welcome channel
|
||||||
|
message = """\
|
||||||
|
**Hey ${user}, welcome to The Canopy 🎉 !**
|
||||||
|
|
||||||
|
If you are new, please read #rules and fill out an application here: https://canopymc.net/apply.
|
||||||
|
|
||||||
|
You will need to join our creative server at `creative.canopymc.net` to be verified.
|
||||||
|
|
||||||
|
**After** applying and joining the server, please run the `!register <MC username>` (without the brackets) command in this channel to join the Discord!
|
||||||
|
"""
|
||||||
|
|
||||||
|
# DM to send to user
|
||||||
|
dm = """\
|
||||||
|
**Hey ${user}, welcome to The Canopy 🎉 !**
|
||||||
|
|
||||||
|
If you are new, please read #rules and fill out an application here: https://canopymc.net/apply.
|
||||||
|
|
||||||
|
You will need to join our survival server at `creative.canopymc.net` to be verified.
|
||||||
|
|
||||||
|
After doing that please run the `!register <MC username>` (without the brackets) command in the welcome channel to join the Discord!
|
||||||
|
"""
|
||||||
|
|
||||||
[register]
|
[register]
|
||||||
# role is the role to assign to a user after registering
|
# role is the role to assign to a user after registering
|
||||||
role = "0"
|
role = "0"
|
||||||
|
|
|
@ -58,6 +58,11 @@ type Config struct {
|
||||||
Nouns []string `toml:"nouns"`
|
Nouns []string `toml:"nouns"`
|
||||||
MinorThings []string `toml:"minor_things"`
|
MinorThings []string `toml:"minor_things"`
|
||||||
} `toml:"compliment"`
|
} `toml:"compliment"`
|
||||||
|
Welcome struct {
|
||||||
|
Channel string `toml:"channel"`
|
||||||
|
Message string `toml:"message"`
|
||||||
|
DM string `toml:"dm"`
|
||||||
|
} `toml:"welcome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageRole struct {
|
type MessageRole struct {
|
||||||
|
@ -65,6 +70,7 @@ type MessageRole struct {
|
||||||
MessageID string `toml:"message_id"`
|
MessageID string `toml:"message_id"`
|
||||||
Reactions []MessageReaction `toml:"reactions"`
|
Reactions []MessageReaction `toml:"reactions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageReaction struct {
|
type MessageReaction struct {
|
||||||
Emoji string `toml:"emoji"`
|
Emoji string `toml:"emoji"`
|
||||||
RoleID string `toml:"role_id"`
|
RoleID string `toml:"role_id"`
|
||||||
|
|
|
@ -19,7 +19,7 @@ type Database struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(dbPath string) (*Database, error) {
|
func Load(dbPath string) (*Database, error) {
|
||||||
db, err := bbolt.Open(dbPath, 0600, bbolt.DefaultOptions)
|
db, err := bbolt.Open(dbPath, 0o600, bbolt.DefaultOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/go-serverapi"
|
"git.jojodev.com/Minecraft/go-serverapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/falseknees"
|
"git.jojodev.com/Minecraft/canopeas/falseknees"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/go-serverapi"
|
"git.jojodev.com/Minecraft/go-serverapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"go.jolheiser.com/beaver"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -33,7 +33,7 @@ func init() {
|
||||||
|
|
||||||
dj, err := newDadJoke()
|
dj, err := newDadJoke()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Warnf("error getting new dad joke: %v", err)
|
log.Warn().Msgf("error getting new dad joke: %v", err)
|
||||||
return dadJokeErr, nil
|
return dadJokeErr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,20 @@ package discord
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/canopeas/config"
|
"github.com/rs/zerolog/log"
|
||||||
"git.canopymc.net/Etzelia/canopeas/database"
|
|
||||||
|
"git.jojodev.com/Minecraft/go-serverapi"
|
||||||
|
|
||||||
|
"git.jojodev.com/Minecraft/canopeas/config"
|
||||||
|
"git.jojodev.com/Minecraft/canopeas/database"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/go-serverapi"
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/dghubble/go-twitter/twitter"
|
"github.com/dghubble/go-twitter/twitter"
|
||||||
"github.com/dghubble/oauth1"
|
"github.com/dghubble/oauth1"
|
||||||
"go.jolheiser.com/beaver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Register commands to this map
|
// Register commands to this map
|
||||||
|
@ -88,7 +91,7 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error)
|
||||||
Echo(cfg)
|
Echo(cfg)
|
||||||
for _, c := range commands {
|
for _, c := range commands {
|
||||||
if c.name == "" {
|
if c.name == "" {
|
||||||
beaver.Errorf("command is missing a name: %s", c.help)
|
log.Error().Msgf("command is missing a name: %s", c.help)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
commandMap[c.name] = c
|
commandMap[c.name] = c
|
||||||
|
@ -98,6 +101,7 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.AddHandler(readyHandler())
|
bot.AddHandler(readyHandler())
|
||||||
|
bot.AddHandler(joinHandler(cfg))
|
||||||
bot.AddHandler(leaveHandler(cfg))
|
bot.AddHandler(leaveHandler(cfg))
|
||||||
bot.AddHandler(commandHandler(cfg, db, sapi, twitterClient))
|
bot.AddHandler(commandHandler(cfg, db, sapi, twitterClient))
|
||||||
bot.AddHandler(messageHandler(cfg, db))
|
bot.AddHandler(messageHandler(cfg, db))
|
||||||
|
@ -119,7 +123,7 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error)
|
||||||
|
|
||||||
func sendTyping(s *discordgo.Session, channelID string) {
|
func sendTyping(s *discordgo.Session, channelID string) {
|
||||||
if err := s.ChannelTyping(channelID); err != nil {
|
if err := s.ChannelTyping(channelID); err != nil {
|
||||||
beaver.Errorf("could not send typing status: %v", err)
|
log.Error().Msgf("could not send typing status: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +139,7 @@ func sendMessage(s *discordgo.Session, channelID, content string, scrub bool) *d
|
||||||
msg, err = s.ChannelMessageSend(channelID, content)
|
msg, err = s.ChannelMessageSend(channelID, content)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Errorf("could not send message: %v", err)
|
log.Error().Msgf("could not send message: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return msg
|
return msg
|
||||||
|
@ -145,7 +149,7 @@ func sendEmbed(s *discordgo.Session, channelID string, embed *discordgo.MessageE
|
||||||
embed.Color = embedColor
|
embed.Color = embedColor
|
||||||
msg, err := s.ChannelMessageSendEmbed(channelID, embed)
|
msg, err := s.ChannelMessageSendEmbed(channelID, embed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Errorf("could not send embed: %v", err)
|
log.Error().Msgf("could not send embed: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return msg
|
return msg
|
||||||
|
@ -164,7 +168,7 @@ func isStaff(authorRoleIDs, staffRoleIDs []string) bool {
|
||||||
|
|
||||||
func readyHandler() func(s *discordgo.Session, m *discordgo.Ready) {
|
func readyHandler() func(s *discordgo.Session, m *discordgo.Ready) {
|
||||||
return func(s *discordgo.Session, r *discordgo.Ready) {
|
return func(s *discordgo.Session, r *discordgo.Ready) {
|
||||||
beaver.Infof("https://discord.com/api/oauth2/authorize?client_id=%s&permissions=0&redirect_uri=https://birbmc.com&scope=bot", r.User.ID)
|
log.Info().Msgf("https://discord.com/api/oauth2/authorize?client_id=%s&permissions=0&redirect_uri=https://birbmc.com&scope=bot", r.User.ID)
|
||||||
|
|
||||||
// Init status changer
|
// Init status changer
|
||||||
go updateStatus(s)
|
go updateStatus(s)
|
||||||
|
@ -214,13 +218,13 @@ func commandHandler(cfg *config.Config, db *database.Database, sapi *serverapi.C
|
||||||
}
|
}
|
||||||
if cmd.deleteInvocation {
|
if cmd.deleteInvocation {
|
||||||
if err := s.ChannelMessageDelete(m.Message.ChannelID, m.Message.ID); err != nil {
|
if err := s.ChannelMessageDelete(m.Message.ChannelID, m.Message.ID); err != nil {
|
||||||
beaver.Warnf("could not remove invocation for %s: %v", m.Content, err)
|
log.Warn().Msgf("could not remove invocation for %s: %v", m.Content, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
feedback, err := cmd.run(cmdInit)
|
feedback, err := cmd.run(cmdInit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
feedback = "Internal error"
|
feedback = "Internal error"
|
||||||
beaver.Errorf("error while running %s: %v", cmdArg, err)
|
log.Error().Msgf("error while running %s: %v", cmdArg, err)
|
||||||
}
|
}
|
||||||
if len(feedback) > 0 {
|
if len(feedback) > 0 {
|
||||||
sendMessage(s, m.ChannelID, feedback, false)
|
sendMessage(s, m.ChannelID, feedback, false)
|
||||||
|
@ -239,7 +243,7 @@ func messageHandler(cfg *config.Config, db *database.Database) func(s *discordgo
|
||||||
for _, role := range m.MentionRoles {
|
for _, role := range m.MentionRoles {
|
||||||
if cfg.FiredRole == role {
|
if cfg.FiredRole == role {
|
||||||
if err := db.IncrementPing(cfg.FiredRole); err != nil {
|
if err := db.IncrementPing(cfg.FiredRole); err != nil {
|
||||||
beaver.Errorf("could not increment ping for %s: %v", cfg.FiredRole, err)
|
log.Error().Msgf("could not increment ping for %s: %v", cfg.FiredRole, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,7 +272,7 @@ func reactionHandler(add bool, s *discordgo.Session, m *discordgo.MessageReactio
|
||||||
roleCmd = s.GuildMemberRoleRemove
|
roleCmd = s.GuildMemberRoleRemove
|
||||||
}
|
}
|
||||||
if err := roleCmd(m.GuildID, m.UserID, r); err != nil {
|
if err := roleCmd(m.GuildID, m.UserID, r); err != nil {
|
||||||
beaver.Errorf("could not modify role %s for user %s", r, m.UserID)
|
log.Error().Msgf("could not modify role %s for user %s", r, m.UserID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,10 +289,39 @@ func updateStatus(s *discordgo.Session) {
|
||||||
for {
|
for {
|
||||||
dj, err := newDadJoke()
|
dj, err := newDadJoke()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Warnf("could not get new dad joke: %v", err)
|
log.Warn().Msgf("could not get new dad joke: %v", err)
|
||||||
} else if err := s.UpdateStatus(1, dj.Joke); err != nil {
|
} else if err := s.UpdateStatus(1, dj.Joke); err != nil {
|
||||||
beaver.Warnf("could not update status: %v", err)
|
log.Warn().Msgf("could not update status: %v", err)
|
||||||
}
|
}
|
||||||
<-ticker.C
|
<-ticker.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func joinHandler(cfg *config.Config) func(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
|
||||||
|
return func(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
|
||||||
|
sendMessage(s, cfg.Welcome.Channel, os.Expand(cfg.Welcome.Message, func(s string) string {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "user":
|
||||||
|
return m.Mention()
|
||||||
|
default:
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}), false)
|
||||||
|
|
||||||
|
if cfg.Welcome.DM != "" {
|
||||||
|
dm, err := s.UserChannelCreate(m.User.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msgf("could not create DM with %s", m.User.Username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendMessage(s, dm.ID, os.Expand(cfg.Welcome.DM, func(s string) string {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "user":
|
||||||
|
return m.Mention()
|
||||||
|
default:
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/canopeas/config"
|
"git.jojodev.com/Minecraft/canopeas/config"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,10 +5,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"gitea.com/jolheiser/gojang"
|
"gitea.com/jolheiser/gojang"
|
||||||
"gitea.com/jolheiser/gojang/rate"
|
"gitea.com/jolheiser/gojang/rate"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"go.jolheiser.com/beaver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -44,13 +45,13 @@ func init() {
|
||||||
if rate.IsRateLimitExceededError(err) {
|
if rate.IsRateLimitExceededError(err) {
|
||||||
return "Rate limited by Mojang, slow down!", nil
|
return "Rate limited by Mojang, slow down!", nil
|
||||||
}
|
}
|
||||||
beaver.Errorf("Profile: %v", err)
|
log.Error().Msgf("Profile: %v", err)
|
||||||
return "Could not contact the Mojang API.", nil
|
return "Could not contact the Mojang API.", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
names, err := client.UUIDToNameHistory(profile.UUID)
|
names, err := client.UUIDToNameHistory(profile.UUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Errorf("UUIDToNameHistory: %v", err)
|
log.Error().Msgf("UUIDToNameHistory: %v", err)
|
||||||
return "Could not contact the Mojang API.", nil
|
return "Could not contact the Mojang API.", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/canopeas/config"
|
"git.jojodev.com/Minecraft/canopeas/config"
|
||||||
"git.canopymc.net/Etzelia/canopeas/imgur"
|
"git.jojodev.com/Minecraft/canopeas/imgur"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package discord
|
package discord
|
||||||
|
|
||||||
import "git.canopymc.net/Etzelia/inspiro"
|
import "git.jojodev.com/Minecraft/canopeas/inspiro"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
commands = append(commands, &command{
|
commands = append(commands, &command{
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/canopeas/config"
|
"git.jojodev.com/Minecraft/go-mcm"
|
||||||
|
"git.jojodev.com/Minecraft/go-mcm/model/django"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/go-mcm"
|
"git.jojodev.com/Minecraft/canopeas/config"
|
||||||
"git.canopymc.net/Etzelia/go-mcm/model/django"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const bannedPlayersFile = "banned-players.json"
|
const bannedPlayersFile = "banned-players.json"
|
||||||
|
|
|
@ -25,7 +25,6 @@ func init() {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if resp.StatusCode%100 != 2 {
|
if resp.StatusCode%100 != 2 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("https://twitter.com/%d/status/%d", tweet.User.ID, tweet.ID), nil
|
return fmt.Sprintf("https://twitter.com/%d/status/%d", tweet.User.ID, tweet.ID), nil
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/canopeas/database"
|
"git.jojodev.com/Minecraft/canopeas/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -5,10 +5,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/canopeas/database"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/go-serverapi"
|
"git.jojodev.com/Minecraft/go-serverapi"
|
||||||
"go.jolheiser.com/beaver"
|
|
||||||
|
"git.jojodev.com/Minecraft/canopeas/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rateLimit struct {
|
type rateLimit struct {
|
||||||
|
@ -60,23 +61,23 @@ func (u *unbanSchedule) Run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unbanSchedule) check() {
|
func (u *unbanSchedule) check() {
|
||||||
beaver.Debug("Running unban schedule")
|
log.Debug().Msg("Running unban schedule")
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
for _, record := range u.db.ListUnbans() {
|
for _, record := range u.db.ListUnbans() {
|
||||||
if now.After(record.Expiration) {
|
if now.After(record.Expiration) {
|
||||||
beaver.Infof("Unbanning %s", record.Username)
|
log.Info().Msgf("Unbanning %s", record.Username)
|
||||||
unban := serverapi.Unban{
|
unban := serverapi.Unban{
|
||||||
Target: record.Username,
|
Target: record.Username,
|
||||||
}
|
}
|
||||||
status, err := u.sapi.Unban(unban)
|
status, err := u.sapi.Unban(unban)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Error(err)
|
log.Err(err).Msg("")
|
||||||
}
|
}
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
beaver.Errorf("ServerAPI returned status %d when trying to ban %s", status, record.Username)
|
log.Error().Msgf("ServerAPI returned status %d when trying to ban %s", status, record.Username)
|
||||||
}
|
}
|
||||||
if err := u.db.RemoveUnban(record.Username); err != nil {
|
if err := u.db.RemoveUnban(record.Username); err != nil {
|
||||||
beaver.Errorf("could not remove unban for %s in database", record.Username)
|
log.Error().Msgf("could not remove unban for %s in database", record.Username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package falseknees
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Client is a FalseKnees client
|
||||||
|
type Client struct {
|
||||||
|
http *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Client
|
||||||
|
func New(opts ...ClientOption) *Client {
|
||||||
|
c := &Client{
|
||||||
|
http: http.DefaultClient,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientOption is options for a Client
|
||||||
|
type ClientOption func(*Client)
|
||||||
|
|
||||||
|
// WithHTTP is a ClientOption for using a different http.Client
|
||||||
|
func WithHTTP(client *http.Client) ClientOption {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.http = client
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package falseknees
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
updateInterval = time.Minute * 30
|
||||||
|
baseURL = "https://www.falseknees.com/"
|
||||||
|
currentRe = regexp.MustCompile(`window\.location\.href.+"(.+)\.html"`)
|
||||||
|
imageRe = regexp.MustCompile(`src="(imgs.+\.png)".+title="(.+)"`)
|
||||||
|
|
||||||
|
current int
|
||||||
|
lastUpdate time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
// Comic is a FalseKnees comic
|
||||||
|
type Comic struct {
|
||||||
|
Num int
|
||||||
|
Title string
|
||||||
|
Img string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comic returns a specific Comic
|
||||||
|
func (c *Client) Comic(ctx context.Context, num int) (*Comic, error) {
|
||||||
|
return c.comic(ctx, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current returns the current Comic
|
||||||
|
func (c *Client) Current(ctx context.Context) (*Comic, error) {
|
||||||
|
if err := c.updateCurrent(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.Comic(ctx, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random returns a random Comic
|
||||||
|
func (c *Client) Random(ctx context.Context) (*Comic, error) {
|
||||||
|
if err := c.updateCurrent(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
return c.Comic(ctx, rand.Intn(current)+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) updateCurrent(ctx context.Context) error {
|
||||||
|
now := time.Now()
|
||||||
|
if !lastUpdate.IsZero() && lastUpdate.After(now.Add(-updateInterval)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lastUpdate = now
|
||||||
|
|
||||||
|
u := fmt.Sprintf("%sindex.html", baseURL)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.http.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("could not get page for index: %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
match := currentRe.FindStringSubmatch(string(body))
|
||||||
|
if len(match) == 0 {
|
||||||
|
return errors.New("could not find current comic")
|
||||||
|
}
|
||||||
|
|
||||||
|
curr, err := strconv.Atoi(match[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
current = curr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) comic(ctx context.Context, num int) (*Comic, error) {
|
||||||
|
u := fmt.Sprintf("%s%d.html", baseURL, num)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.http.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("could not get page for comic %d: %s", num, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
match := imageRe.FindStringSubmatch(string(body))
|
||||||
|
if len(match) == 0 {
|
||||||
|
return nil, fmt.Errorf("could not find comic #%d", num)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Comic{
|
||||||
|
Num: num,
|
||||||
|
Title: match[2],
|
||||||
|
Img: fmt.Sprintf("%s%s", baseURL, match[1]),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,483 @@
|
||||||
|
package falseknees
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
server *httptest.Server
|
||||||
|
client *Client
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/index.html" {
|
||||||
|
_, _ = w.Write(indexHTML)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.Path == "/389.html" {
|
||||||
|
_, _ = w.Write(currentHTML)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.Path == "/252.html" {
|
||||||
|
_, _ = w.Write(bunnyHTML)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
server = httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
baseURL = server.URL + "/"
|
||||||
|
currentComic.Img = fmt.Sprintf("%simgs/389.png", baseURL)
|
||||||
|
bunnyComic.Img = fmt.Sprintf("%simgs/252.png", baseURL)
|
||||||
|
client = New()
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCurrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
comic, err := client.Current(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("could not get current comic: %v\n", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if *comic != currentComic {
|
||||||
|
t.Log("comic does not match test data")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
comic, err := client.Comic(context.Background(), 252)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("could not get comic 252: %v\n", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if *comic != bunnyComic {
|
||||||
|
t.Log("comic does not match test data")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
currentComic = Comic{
|
||||||
|
Num: 389,
|
||||||
|
Title: "that's the good stuff",
|
||||||
|
}
|
||||||
|
bunnyComic = Comic{
|
||||||
|
Num: 252,
|
||||||
|
Title: "Spring is the fucking greatest shit",
|
||||||
|
}
|
||||||
|
|
||||||
|
indexHTML = []byte(`<!DOCTYPE html
|
||||||
|
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="description"
|
||||||
|
content="False Knees is a webcomic written by Joshua Barkman. All silly nonsense is my own." />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
|
||||||
|
<meta http-equiv="refresh" content="0; URL=389.html" />
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.location.href = "389.html"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="imgs/favicon.png" sizes="96x96">
|
||||||
|
<!-- Facebook Meta tags -->
|
||||||
|
<meta property="og:title" content="False Knees" />
|
||||||
|
<meta property="og:type" content="blog" />
|
||||||
|
<meta property="og:url" content="http://www.falseknees.com/index.html" />
|
||||||
|
<meta property="og:image" content="http://www.falseknees.com/imgs/389.png" />
|
||||||
|
<meta property="og:site_name" content="False Knees" />
|
||||||
|
<meta property="fb:admins" content="1646220005" />
|
||||||
|
|
||||||
|
<link rel="image_src" href="imgs/389.png" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="stylesheet.css" />
|
||||||
|
<title>Page Redirection</title>
|
||||||
|
|
||||||
|
<!-- Google Analytics -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
var _gaq = _gaq || [];
|
||||||
|
var pluginUrl =
|
||||||
|
'//www.google-analytics.com/plugins/ga/inpage_linkid.js';
|
||||||
|
_gaq.push(['_require', 'inpage_linkid', pluginUrl]);
|
||||||
|
_gaq.push(['_setAccount', 'UA-37345913-1']);
|
||||||
|
_gaq.push(['_setDomainName', 'falseknees.com']);
|
||||||
|
_gaq.push(['_trackPageview']);
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||||
|
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||||
|
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="fb-root"></div>
|
||||||
|
<script>
|
||||||
|
(function(d, s, id) {
|
||||||
|
var js, fjs = d.getElementsByTagName(s)[0];
|
||||||
|
if (d.getElementById(id)) return;
|
||||||
|
js = d.createElement(s); js.id = id;
|
||||||
|
js.src = "//connect.facebook.net/en_GB/sdk.js#xfbml=1&version=v2.0";
|
||||||
|
fjs.parentNode.insertBefore(js, fjs);
|
||||||
|
}(document, 'script', 'facebook-jssdk'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Title Image -->
|
||||||
|
<table align="center">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div id="title">
|
||||||
|
<a href="index.html"><img src="imgs/falseknees.png" width="800" /></a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- New Button Placement -->
|
||||||
|
<div align="center">
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><a href="about.html"
|
||||||
|
title=""><img src="imgs/aboutoff.png" height="50" alt="About" onmouseover="this.src='imgs/abouton.png'" onmouseout="this.src='imgs/aboutoff.png'" /></a>
|
||||||
|
</td>
|
||||||
|
<td><a title=""><img src="imgs/stara.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="https://false-knees.myshopify.com/"
|
||||||
|
title=""><img src="imgs/store.png" height="50" alt="Store" onmouseover="this.src='imgs/storeon.png'" onmouseout="this.src='imgs/store.png'" /></a>
|
||||||
|
</td>
|
||||||
|
<td><a title=""><img src="imgs/starb.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="https://www.patreon.com/falseknees?ty=h"><img src="imgs/patron.png" height="50" onmouseover="this.src='imgs/patroff.png'" onmouseout="this.src='imgs/patron.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a title=""><img src="imgs/starc.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="book.html"
|
||||||
|
title=""><img src="imgs/book.png" height="40" alt="About" onmouseover="this.src='imgs/bookon.png'" onmouseout="this.src='imgs/book.png'" /></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comic -->
|
||||||
|
<div>
|
||||||
|
<img src="imgs/389.png" width="600" title="that's the good stuff" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descriptive Text Box -->
|
||||||
|
<!-- Descriptive Text Box -->
|
||||||
|
<div align="center">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<div align="center">
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><a href="1.html"
|
||||||
|
title=""><img src="imgs/first.png" height="60" alt="First" onmouseover="this.src='imgs/firston.png'" onmouseout="this.src='imgs/first.png'" /></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="388.html"
|
||||||
|
title=""><img src="imgs/previous.png" height="60" alt="Previous" onmouseover="this.src='imgs/previouson.png'" onmouseout="this.src='imgs/previous.png'" /></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="archive.html"
|
||||||
|
title=""><img src="imgs/archive.png" height="60" alt="Archive" onmouseover="this.src='imgs/archive2.png'" onmouseout="this.src='imgs/archive.png'" /></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="index.html"
|
||||||
|
title=""><img src="imgs/next.png" height="60" alt="Next" onmouseover="this.src='imgs/nexton.png'" onmouseout="this.src='imgs/next.png'" /></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="index.html"
|
||||||
|
title=""><img src="imgs/last.png" height="60" alt="Last" onmouseover="this.src='imgs/laston.png'" onmouseout="this.src='imgs/last.png'" /></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social Media -->
|
||||||
|
<table class="social" align="center">
|
||||||
|
<tr>
|
||||||
|
<td><a href="http://falseknees.tumblr.com/"><img src="imgs/TumblrButton.png" width="60" onmouseover="this.src='imgs/TumblrButtonOn.png'" onmouseout="this.src='imgs/TumblrButton.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://instagram.com/FalseKnees"><img src="imgs/instagram.png" width="60" onmouseover="this.src='imgs/instagramon.png'" onmouseout="this.src='imgs/instagram.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://www.facebook.com/FalseKnees?ref=hl"><img src="imgs/FacebookButton.png" width="60" onmouseover="this.src='imgs/FacebookButtonOn.png'" onmouseout="this.src='imgs/FacebookButton.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="http://www.webtoons.com/en/challenge/false-knees/list?title_no=79544"><img src="imgs/webtooff.png" width="60" onmouseover="this.src='imgs/webtoon.png'" onmouseout="this.src='imgs/webtooff.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://tapas.io/series/FalseKnees"><img src="imgs/tap.png" width="60" onmouseover="this.src='imgs/tapon.png'" onmouseout="this.src='imgs/tap.png'"/></a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Copyright -->
|
||||||
|
<div>
|
||||||
|
<p>False Knees © 2013-whenever Joshua Barkman</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>`)
|
||||||
|
|
||||||
|
currentHTML = []byte(`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta name="description" content="False Knees is a webcomic written by Joshua Barkman. All silly nonsense is my own." />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="imgs/favicon.png" sizes="96x96">
|
||||||
|
<!-- Facebook Meta tags -->
|
||||||
|
<meta property="og:title" content="False Knees" />
|
||||||
|
<meta property="og:type" content="blog" />
|
||||||
|
<meta property="og:url" content="http://www.falseknees.com/389.html" />
|
||||||
|
<meta property="og:image" content="http://www.falseknees.com/imgs/389.png" />
|
||||||
|
<meta property="og:site_name" content="False Knees" />
|
||||||
|
<meta property="fb:admins" content="1646220005" />
|
||||||
|
|
||||||
|
<link rel="image_src" href="imgs/389.png" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="stylesheet.css" />
|
||||||
|
<title>False Knees</title>
|
||||||
|
|
||||||
|
<!-- Google Analytics -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
var _gaq = _gaq || [];
|
||||||
|
var pluginUrl =
|
||||||
|
'//www.google-analytics.com/plugins/ga/inpage_linkid.js';
|
||||||
|
_gaq.push(['_require', 'inpage_linkid', pluginUrl]);
|
||||||
|
_gaq.push(['_setAccount', 'UA-37345913-1']);
|
||||||
|
_gaq.push(['_setDomainName', 'falseknees.com']);
|
||||||
|
_gaq.push(['_trackPageview']);
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||||
|
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||||
|
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="fb-root"></div>
|
||||||
|
<script>(function(d, s, id) {
|
||||||
|
var js, fjs = d.getElementsByTagName(s)[0];
|
||||||
|
if (d.getElementById(id)) return;
|
||||||
|
js = d.createElement(s); js.id = id;
|
||||||
|
js.src = "//connect.facebook.net/en_GB/sdk.js#xfbml=1&version=v2.0";
|
||||||
|
fjs.parentNode.insertBefore(js, fjs);
|
||||||
|
}(document, 'script', 'facebook-jssdk'));</script>
|
||||||
|
|
||||||
|
<!-- Title Image -->
|
||||||
|
<table align="center">
|
||||||
|
<tr>
|
||||||
|
<td><div id="title">
|
||||||
|
<a href="index.html"><img src="imgs/falseknees.png" width="800" /></a>
|
||||||
|
</div></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- New Button Placement -->
|
||||||
|
<div align="center">
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><a href="about.html" title=""><img src="imgs/aboutoff.png" height="50" alt="About" onmouseover="this.src='imgs/abouton.png'" onmouseout="this.src='imgs/aboutoff.png'" /></a></td>
|
||||||
|
<td><a title=""><img src="imgs/stara.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="https://false-knees.myshopify.com/" title=""><img src="imgs/store.png" height="50" alt="Store" onmouseover="this.src='imgs/storeon.png'" onmouseout="this.src='imgs/store.png'" /></a></td>
|
||||||
|
<td><a title=""><img src="imgs/starb.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="https://www.patreon.com/falseknees?ty=h"><img src="imgs/patron.png" height="50" onmouseover="this.src='imgs/patroff.png'" onmouseout="this.src='imgs/patron.png'"/></a></td>
|
||||||
|
<td><a title=""><img src="imgs/starc.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="book.html" title=""><img src="imgs/book.png" height="40" alt="About" onmouseover="this.src='imgs/bookon.png'" onmouseout="this.src='imgs/book.png'" /></a></td>
|
||||||
|
</tr>
|
||||||
|
</table></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comic -->
|
||||||
|
<div>
|
||||||
|
<img src="imgs/389.png" width="600" title="that's the good stuff" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descriptive Text Box -->
|
||||||
|
<!-- Descriptive Text Box -->
|
||||||
|
<div align="center">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<div align="center">
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><a href="1.html" title=""><img src="imgs/first.png" height="60" alt="First" onmouseover="this.src='imgs/firston.png'" onmouseout="this.src='imgs/first.png'" /></a></td>
|
||||||
|
<td><a href="388.html" title=""><img src="imgs/previous.png" height="60" alt="Previous" onmouseover="this.src='imgs/previouson.png'" onmouseout="this.src='imgs/previous.png'" /></a></td>
|
||||||
|
<td><a href="archive.html" title=""><img src="imgs/archive.png" height="60" alt="Archive" onmouseover="this.src='imgs/archive2.png'" onmouseout="this.src='imgs/archive.png'" /></a></td>
|
||||||
|
<td><a href="389.html" title=""><img src="imgs/next.png" height="60" alt="Next" onmouseover="this.src='imgs/nexton.png'" onmouseout="this.src='imgs/next.png'" /></a></td>
|
||||||
|
<td><a href="index.html" title=""><img src="imgs/last.png" height="60" alt="Last" onmouseover="this.src='imgs/laston.png'" onmouseout="this.src='imgs/last.png'" /></a></td>
|
||||||
|
</tr>
|
||||||
|
</table></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social Media -->
|
||||||
|
<table class="social" align="center">
|
||||||
|
<tr>
|
||||||
|
<td><a href="http://falseknees.tumblr.com/"><img src="imgs/TumblrButton.png" width="60" onmouseover="this.src='imgs/TumblrButtonOn.png'" onmouseout="this.src='imgs/TumblrButton.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://instagram.com/FalseKnees"><img src="imgs/instagram.png" width="60" onmouseover="this.src='imgs/instagramon.png'" onmouseout="this.src='imgs/instagram.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://www.facebook.com/FalseKnees?ref=hl"><img src="imgs/FacebookButton.png" width="60" onmouseover="this.src='imgs/FacebookButtonOn.png'" onmouseout="this.src='imgs/FacebookButton.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="http://www.webtoons.com/en/challenge/false-knees/list?title_no=79544"><img src="imgs/webtooff.png" width="60" onmouseover="this.src='imgs/webtoon.png'" onmouseout="this.src='imgs/webtooff.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://tapas.io/series/FalseKnees"><img src="imgs/tap.png" width="60" onmouseover="this.src='imgs/tapon.png'" onmouseout="this.src='imgs/tap.png'"/></a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Copyright -->
|
||||||
|
<div>
|
||||||
|
<p>False Knees © 2013-whenever Joshua Barkman</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`)
|
||||||
|
|
||||||
|
bunnyHTML = []byte(`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta name="description" content="False Knees is a webcomic written by Joshua Barkman. All silly nonsense is my own." />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="imgs/favicon.png" sizes="96x96">
|
||||||
|
<!-- Facebook Meta tags -->
|
||||||
|
<meta property="og:title" content="False Knees" />
|
||||||
|
<meta property="og:type" content="blog" />
|
||||||
|
<meta property="og:url" content="http://www.falseknees.com/252.html" />
|
||||||
|
<meta property="og:image" content="http://www.falseknees.com/imgs/252.png" />
|
||||||
|
<meta property="og:site_name" content="False Knees" />
|
||||||
|
<meta property="fb:admins" content="1646220005" />
|
||||||
|
|
||||||
|
<link rel="image_src" href="imgs/252.png" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="stylesheet.css" />
|
||||||
|
<title>False Knees</title>
|
||||||
|
|
||||||
|
<!-- Google Analytics -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
var _gaq = _gaq || [];
|
||||||
|
var pluginUrl =
|
||||||
|
'//www.google-analytics.com/plugins/ga/inpage_linkid.js';
|
||||||
|
_gaq.push(['_require', 'inpage_linkid', pluginUrl]);
|
||||||
|
_gaq.push(['_setAccount', 'UA-37345913-1']);
|
||||||
|
_gaq.push(['_setDomainName', 'falseknees.com']);
|
||||||
|
_gaq.push(['_trackPageview']);
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||||
|
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||||
|
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="fb-root"></div>
|
||||||
|
<script>(function(d, s, id) {
|
||||||
|
var js, fjs = d.getElementsByTagName(s)[0];
|
||||||
|
if (d.getElementById(id)) return;
|
||||||
|
js = d.createElement(s); js.id = id;
|
||||||
|
js.src = "//connect.facebook.net/en_GB/sdk.js#xfbml=1&version=v2.0";
|
||||||
|
fjs.parentNode.insertBefore(js, fjs);
|
||||||
|
}(document, 'script', 'facebook-jssdk'));</script>
|
||||||
|
|
||||||
|
<!-- Title Image -->
|
||||||
|
<table align="center">
|
||||||
|
<tr>
|
||||||
|
<td><div id="title">
|
||||||
|
<a href="index.html"><img src="imgs/falseknees.png" width="800" /></a>
|
||||||
|
</div></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- New Button Placement -->
|
||||||
|
<div align="center">
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><a href="about.html" title=""><img src="imgs/aboutoff.png" height="50" alt="About" onmouseover="this.src='imgs/abouton.png'" onmouseout="this.src='imgs/aboutoff.png'" /></a></td>
|
||||||
|
<td><a title=""><img src="imgs/stara.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="https://false-knees.myshopify.com/collections/comic-prints" title=""><img src="imgs/store.png" height="50" alt="Store" onmouseover="this.src='imgs/storeon.png'" onmouseout="this.src='imgs/store.png'" /></a></td>
|
||||||
|
<td><a title=""><img src="imgs/starb.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="https://www.patreon.com/falseknees?ty=h"><img src="imgs/patron.png" height="50" onmouseover="this.src='imgs/patroff.png'" onmouseout="this.src='imgs/patron.png'"/></a></td>
|
||||||
|
<td><a title=""><img src="imgs/starc.png" height="50" alt="" /></a></td>
|
||||||
|
<td><a href="book.html" title=""><img src="imgs/book.png" height="40" alt="About" onmouseover="this.src='imgs/bookon.png'" onmouseout="this.src='imgs/book.png'" /></a></td>
|
||||||
|
</tr>
|
||||||
|
</table></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comic -->
|
||||||
|
<div>
|
||||||
|
<img src="imgs/252.png" width="600" title="Spring is the fucking greatest shit" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descriptive Text Box -->
|
||||||
|
<!-- Descriptive Text Box -->
|
||||||
|
<div align="center">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<div align="center">
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><a href="1.html" title=""><img src="imgs/first.png" height="60" alt="First" onmouseover="this.src='imgs/firston.png'" onmouseout="this.src='imgs/first.png'" /></a></td>
|
||||||
|
<td><a href="251.html" title=""><img src="imgs/previous.png" height="60" alt="Previous" onmouseover="this.src='imgs/previouson.png'" onmouseout="this.src='imgs/previous.png'" /></a></td>
|
||||||
|
<td><a href="archive.html" title=""><img src="imgs/archive.png" height="60" alt="Archive" onmouseover="this.src='imgs/archive2.png'" onmouseout="this.src='imgs/archive.png'" /></a></td>
|
||||||
|
<td><a href="253.html" title=""><img src="imgs/next.png" height="60" alt="Next" onmouseover="this.src='imgs/nexton.png'" onmouseout="this.src='imgs/next.png'" /></a></td>
|
||||||
|
<td><a href="index.html" title=""><img src="imgs/last.png" height="60" alt="Last" onmouseover="this.src='imgs/laston.png'" onmouseout="this.src='imgs/last.png'" /></a></td>
|
||||||
|
</tr>
|
||||||
|
</table></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social Media -->
|
||||||
|
<table class="social" align="center">
|
||||||
|
<tr>
|
||||||
|
<td><a href="http://falseknees.tumblr.com/"><img src="imgs/TumblrButton.png" width="60" onmouseover="this.src='imgs/TumblrButtonOn.png'" onmouseout="this.src='imgs/TumblrButton.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://instagram.com/FalseKnees"><img src="imgs/instagram.png" width="60" onmouseover="this.src='imgs/instagramon.png'" onmouseout="this.src='imgs/instagram.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://www.facebook.com/FalseKnees?ref=hl"><img src="imgs/FacebookButton.png" width="60" onmouseover="this.src='imgs/FacebookButtonOn.png'" onmouseout="this.src='imgs/FacebookButton.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="http://www.webtoons.com/en/challenge/false-knees/list?title_no=79544"><img src="imgs/webtooff.png" width="60" onmouseover="this.src='imgs/webtoon.png'" onmouseout="this.src='imgs/webtooff.png'"/></a>
|
||||||
|
</td>
|
||||||
|
<td><a href="https://tapas.io/series/FalseKnees"><img src="imgs/tap.png" width="60" onmouseover="this.src='imgs/tapon.png'" onmouseout="this.src='imgs/tap.png'"/></a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Copyright -->
|
||||||
|
<div>
|
||||||
|
<p>False Knees © 2013-whenever Joshua Barkman</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`)
|
||||||
|
)
|
12
go.mod
12
go.mod
|
@ -1,12 +1,10 @@
|
||||||
module git.canopymc.net/Etzelia/canopeas
|
module git.jojodev.com/Minecraft/canopeas
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.canopymc.net/Etzelia/falseknees v0.0.0-20210713232726-7325698e2451
|
git.jojodev.com/Minecraft/go-mcm v0.0.1
|
||||||
git.canopymc.net/Etzelia/go-mcm v0.0.0-20210713232816-d2b27d7edff0
|
git.jojodev.com/Minecraft/go-serverapi v0.0.1
|
||||||
git.canopymc.net/Etzelia/go-serverapi v0.0.0-20210713233104-94e800dbb304
|
|
||||||
git.canopymc.net/Etzelia/inspiro v0.0.3-0.20210713233035-ffd88077147f
|
|
||||||
gitea.com/jolheiser/gojang v0.0.7
|
gitea.com/jolheiser/gojang v0.0.7
|
||||||
gitea.com/jolheiser/xkcd v0.0.2
|
gitea.com/jolheiser/xkcd v0.0.2
|
||||||
github.com/bwmarrin/discordgo v0.22.0
|
github.com/bwmarrin/discordgo v0.22.0
|
||||||
|
@ -14,8 +12,6 @@ require (
|
||||||
github.com/dghubble/oauth1 v0.7.0
|
github.com/dghubble/oauth1 v0.7.0
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
github.com/pelletier/go-toml v1.8.1
|
github.com/pelletier/go-toml v1.8.1
|
||||||
|
github.com/rs/zerolog v1.26.1
|
||||||
go.etcd.io/bbolt v1.3.4
|
go.etcd.io/bbolt v1.3.4
|
||||||
go.jolheiser.com/beaver v1.0.2
|
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
48
go.sum
48
go.sum
|
@ -1,11 +1,7 @@
|
||||||
git.canopymc.net/Etzelia/falseknees v0.0.0-20210713232726-7325698e2451 h1:EbxWDS7sOyxv8einE7ps8WsywvlFqyKp3vdvk4PYVw4=
|
git.jojodev.com/Minecraft/go-mcm v0.0.1 h1:3nfjCz3wA4l44QWYMRC7DLwU4GneWC5e1CRub7Tt1S0=
|
||||||
git.canopymc.net/Etzelia/falseknees v0.0.0-20210713232726-7325698e2451/go.mod h1:bgGHtcoYFmNIFgcU4P2LwqANZsJxoygnLI0C6OWE/U4=
|
git.jojodev.com/Minecraft/go-mcm v0.0.1/go.mod h1:0VAA1b5ZgM1leeYOqMhBnEfdonZccdMdeQrsF+50s04=
|
||||||
git.canopymc.net/Etzelia/go-mcm v0.0.0-20210713232816-d2b27d7edff0 h1:UrwR0Ap4sjoRDfbi/ow76OeBAR9pI+BFKMYU6Jj9EtU=
|
git.jojodev.com/Minecraft/go-serverapi v0.0.1 h1:sn594cScthq0W/ntLVhWfMfNcPXG9IyyqMatcEjcYIQ=
|
||||||
git.canopymc.net/Etzelia/go-mcm v0.0.0-20210713232816-d2b27d7edff0/go.mod h1:M9yjY5mBSK5vGVPru7RG6K5bUfoRH7dTtyQ+MCuJ33g=
|
git.jojodev.com/Minecraft/go-serverapi v0.0.1/go.mod h1:a3e4OnMNJnd2OzwRG62Fe/k70SelNDoAxYW9SYmkDf4=
|
||||||
git.canopymc.net/Etzelia/go-serverapi v0.0.0-20210713233104-94e800dbb304 h1:gBzoEToJCO1nKbfhfzhGMgSWY6szwDbA8doVmPr3SIY=
|
|
||||||
git.canopymc.net/Etzelia/go-serverapi v0.0.0-20210713233104-94e800dbb304/go.mod h1:U0H8WgtAzR+L+65odnpUH1lT6z7ylcG6R9keOOTG+fk=
|
|
||||||
git.canopymc.net/Etzelia/inspiro v0.0.3-0.20210713233035-ffd88077147f h1:CupD+6/4Vrx0fGDIFf+cu8ponr19or3bCzkPPQXmRJk=
|
|
||||||
git.canopymc.net/Etzelia/inspiro v0.0.3-0.20210713233035-ffd88077147f/go.mod h1:7zYT6obYO7/a3v+gV+uNfNlWK1dJz6Mz7lY9FeRSGOU=
|
|
||||||
gitea.com/jolheiser/gojang v0.0.7 h1:Q4cG7QYiKQsJtUWgXXiolAH9DCLRoaQ4olaO9OV628U=
|
gitea.com/jolheiser/gojang v0.0.7 h1:Q4cG7QYiKQsJtUWgXXiolAH9DCLRoaQ4olaO9OV628U=
|
||||||
gitea.com/jolheiser/gojang v0.0.7/go.mod h1:r9kj2wv/21Da7VpWz+qmxLexH85o2BAM4NMxeYgQlcY=
|
gitea.com/jolheiser/gojang v0.0.7/go.mod h1:r9kj2wv/21Da7VpWz+qmxLexH85o2BAM4NMxeYgQlcY=
|
||||||
gitea.com/jolheiser/xkcd v0.0.2 h1:HJP83YwSKxSYcoNfpb1ZpAfBvkUAnN+YgeukraXtfrc=
|
gitea.com/jolheiser/xkcd v0.0.2 h1:HJP83YwSKxSYcoNfpb1ZpAfBvkUAnN+YgeukraXtfrc=
|
||||||
|
@ -14,6 +10,7 @@ github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIx
|
||||||
github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||||
github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
|
github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
|
||||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -23,6 +20,7 @@ github.com/dghubble/oauth1 v0.7.0 h1:AlpZdbRiJM4XGHIlQ8BuJ/wlpGwFEJNnB4Mc+78tA/w
|
||||||
github.com/dghubble/oauth1 v0.7.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk=
|
github.com/dghubble/oauth1 v0.7.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk=
|
||||||
github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU=
|
github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU=
|
||||||
github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY=
|
github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
@ -30,26 +28,46 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
||||||
|
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk=
|
|
||||||
go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g=
|
|
||||||
go.jolheiser.com/gql v0.0.1 h1:y3LGHcJUZI9otTCcMn8TVdF3aEzNX0FW6m0YUamlLto=
|
go.jolheiser.com/gql v0.0.1 h1:y3LGHcJUZI9otTCcMn8TVdF3aEzNX0FW6m0YUamlLto=
|
||||||
go.jolheiser.com/gql v0.0.1/go.mod h1:74eYqVRIxsOFxtVl0RYGKNyYQgJYQaxOCgar7LP71Hw=
|
go.jolheiser.com/gql v0.0.1/go.mod h1:74eYqVRIxsOFxtVl0RYGKNyYQgJYQaxOCgar7LP71Hw=
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU=
|
||||||
|
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 h1:iCaAy5bMeEvwANu3YnJfWwI0kWAGkEa2RXPdweI/ysk=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
|
||||||
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -30,7 +30,6 @@ type Image struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(clientID, albumID string) ([]*Image, error) {
|
func Get(clientID, albumID string) ([]*Image, error) {
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://api.imgur.com/3/album/%s/images", albumID), nil)
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://api.imgur.com/3/album/%s/images", albumID), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package inspiro
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const api = "https://inspirobot.me/api?generate=true"
|
||||||
|
|
||||||
|
func Generate() (string, error) {
|
||||||
|
resp, err := http.Get(api)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return string(body), nil
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package inspiro
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
if _, err := Generate(); err != nil {
|
||||||
|
t.Logf("could not generate image: %v", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
31
main.go
31
main.go
|
@ -6,54 +6,49 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.canopymc.net/Etzelia/canopeas/config"
|
"github.com/rs/zerolog"
|
||||||
"git.canopymc.net/Etzelia/canopeas/database"
|
"github.com/rs/zerolog/log"
|
||||||
"git.canopymc.net/Etzelia/canopeas/discord"
|
|
||||||
|
|
||||||
"go.jolheiser.com/beaver"
|
"git.jojodev.com/Minecraft/canopeas/config"
|
||||||
|
"git.jojodev.com/Minecraft/canopeas/database"
|
||||||
|
"git.jojodev.com/Minecraft/canopeas/discord"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configFlag string
|
var configFlag string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
flag.StringVar(&configFlag, "config", "canopeas.toml", "Set config path")
|
flag.StringVar(&configFlag, "config", "canopeas.toml", "Set config path")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
beaver.Console.Format = beaver.FormatOptions{
|
|
||||||
TimePrefix: true,
|
|
||||||
StackPrefix: true,
|
|
||||||
StackLimit: 15,
|
|
||||||
LevelPrefix: true,
|
|
||||||
LevelColor: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := config.Load(configFlag)
|
cfg, err := config.Load(configFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Fatalf("could not load config: %v", err)
|
log.Fatal().Msgf("could not load config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := database.Load(cfg.DBPath)
|
db, err := database.Load(cfg.DBPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Fatalf("could not load database: %v", err)
|
log.Fatal().Msgf("could not load database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bot, err := discord.Bot(cfg, db)
|
bot, err := discord.Bot(cfg, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
beaver.Fatalf("could not start Discord bot: %v", err)
|
log.Fatal().Msgf("could not start Discord bot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := bot.Open(); err != nil {
|
if err := bot.Open(); err != nil {
|
||||||
beaver.Errorf("error running Discord bot: %v", err)
|
log.Error().Msgf("error running Discord bot: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
beaver.Info("Bot is now running. Press CTRL-C to exit.")
|
log.Info().Msg("Bot is now running. Press CTRL-C to exit.")
|
||||||
sc := make(chan os.Signal, 1)
|
sc := make(chan os.Signal, 1)
|
||||||
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
|
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
|
||||||
<-sc
|
<-sc
|
||||||
|
|
||||||
if err := bot.Close(); err != nil {
|
if err := bot.Close(); err != nil {
|
||||||
beaver.Errorf("error closing Discord bot: %v", err)
|
log.Error().Msgf("error closing Discord bot: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue