Skip to content

Commit

Permalink
Merge branch 'release/3.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
j4rv committed Nov 11, 2024
2 parents ffa5f04 + eba9593 commit be02451
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 103 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# J4RV's Discord bot

[Add it to your server!](https://discord.com/api/oauth2/authorize?client_id=901475699699875880&permissions=412384290880&scope=bot)
[Add it to your server!](https://discord.com/oauth2/authorize?client_id=901475699699875880)

## How to run:

Expand Down
13 changes: 11 additions & 2 deletions cmd/jarvbot/bunker.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,21 @@ func answerShoot(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context
timeoutRole, err := getTimeoutRole(ds, mc.GuildID)
notifyIfErr("answerShoot: get timeout role", err, ds)
if err != nil {
ds.ChannelMessageSend(mc.ChannelID, "Could not find the Timeout Role, maybe I'm missing permissions or it does not exist :(")
return false
}

shooter, err := ds.GuildMember(mc.GuildID, mc.Author.ID)
notifyIfErr("answerShoot: get shooter member", err, ds)
if err != nil {
ds.ChannelMessageSend(mc.ChannelID, "Could not find you in this server, maybe I'm missing permissions u_u")
return false
}

target, err := ds.GuildMember(mc.GuildID, match[1])
notifyIfErr("answerShoot: get target member", err, ds)
if err != nil {
ds.ChannelMessageSend(mc.ChannelID, "Couldn't find member with user ID: "+match[1]+", maybe I'm missing permissions u_u")
return false
}

Expand Down Expand Up @@ -140,8 +143,14 @@ func shoot(ds *discordgo.Session, channelID string, guildID string, shooter *dis
if member.User.ID == ds.State.User.ID {
continue
}
ds.GuildMemberRoleAdd(guildID, member.User.ID, timeoutRoleID)
removeRoleAfterDuration(ds, guildID, member.User.ID, timeoutRoleID, timeoutDurationWhenNuclearCatastrophe)
if rand.Float32() <= nuclearCatastropheDeathRatio {
ds.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{
Content: fmt.Sprintf("%s died in the explosion!", member.User.Mention()),
AllowedMentions: &discordgo.MessageAllowedMentions{},
})
ds.GuildMemberRoleAdd(guildID, member.User.ID, timeoutRoleID)
removeRoleAfterDuration(ds, guildID, member.User.ID, timeoutRoleID, timeoutDurationWhenNuclearCatastrophe)
}
}
return nil
}
Expand Down
162 changes: 137 additions & 25 deletions cmd/jarvbot/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ const roleEveryone = "@everyone"
const globalGuildID = ""

var ogNonRootTwitterLinkRegex = regexp.MustCompile(`\b(?:https?://)?(?:www\.)?(?:twitter|x)\.com/\S+\b`)
var ogTwitterLinkRegex = regexp.MustCompile(`\b(?:https?://)?(?:www\.)?(?:twitter|x)\.com\b`)
var ogNonRootPixivLinkRegex = regexp.MustCompile(`\b(?:https?://)?(?:www\.)?pixiv\.net/\S+\b`)
var commandPrefixRegex = regexp.MustCompile(`^!\w+\s*`)
var commandWithTwoArguments = regexp.MustCompile(`^!\w+\s*(\(.{1,36}\))\s*(\(.{1,36}\))`)
var commandWithMention = regexp.MustCompile(`^!\w+\s*<@!?(\d+)>`)

var badEmbedLinkReplacements = map[*regexp.Regexp]string{
regexp.MustCompile(`\b(?:https?://)?(?:www\.)?(?:twitter|x)\.com\b`): "https://fxtwitter.com",
regexp.MustCompile(`\b(?:https?://)?(?:www\.)?pixiv\.net\b`): "https://phixiv.net",
}

var messageLinkFixToOgAuthorId = map[*discordgo.Message]string{}

type command func(*discordgo.Session, *discordgo.MessageCreate, context.Context) bool

func onMessageCreated(ctx context.Context) func(ds *discordgo.Session, mc *discordgo.MessageCreate) {
Expand All @@ -47,8 +54,9 @@ func onMessageCreated(ctx context.Context) func(ds *discordgo.Session, mc *disco
}

// Twitter links replacement
if ogNonRootTwitterLinkRegex.MatchString(mc.Content) {
processMessageWithTwitterLinks(ds, mc, ctx)
if ogNonRootTwitterLinkRegex.MatchString(mc.Content) ||
ogNonRootPixivLinkRegex.MatchString(mc.Content) {
processMessageWithBadEmbedLinks(ds, mc, ctx)
return
}
}
Expand All @@ -57,7 +65,7 @@ func onMessageCreated(ctx context.Context) func(ds *discordgo.Session, mc *disco
// the command key must be lowercased
var commands = map[string]command{
// public
"!version": simpleTextResponse("v3.6.2"),
"!version": simpleTextResponse("v3.7.0"),
"!source": simpleTextResponse("Source code: https://github.com/j4rv/discord-bot"),
"!genshindailycheckin": answerGenshinDailyCheckIn,
"!genshindailycheckinstop": answerGenshinDailyCheckInStop,
Expand All @@ -71,29 +79,33 @@ var commands = map[string]command{
"!randomdomainrun": notSpammable(answerRandomDomainRun),
"!remindme": notSpammable(answerRemindme),
"!roll": notSpammable(answerRoll),
"!shoot": notSpammable(answerShoot),
"!pp": notSpammable(answerPP),
// hidden or easter eggs
"!hello": notSpammable(answerHello),
"!liquid": notSpammable(answerLiquid),
"!don": notSpammable(answerDon),
"!shoot": notSpammable(answerShoot),
"!sniper_shoot": notSpammable(answerSniperShoot),
"!pp": notSpammable(answerPP),
// only available for discord mods
"!roleids": guildOnly((answerRoleIDs)),
"!react4roles": guildOnly((answerMakeReact4RolesMsg)),
"!addcommand": guildOnly((answerAddCommand)),
"!removecommand": guildOnly((answerRemoveCommand)),
"!deletecommand": guildOnly((answerRemoveCommand)),
"!commandcreator": guildOnly((answerCommandCreator)),
"!listcommands": modOnly(answerListCommands),
"!allowspamming": guildOnly(modOnly(answerAllowSpamming)),
"!preventspamming": guildOnly(modOnly(answerPreventSpamming)),
"!setcustomtimeoutrole": guildOnly(modOnly(answerSetCustomTimeoutRole)),
"!announcehere": guildOnly(modOnly(answerAnnounceHere)),
"!fixtwitterlinks": guildOnly(modOnly(answerFixTwitterLinks)),
"!fixbadembedlinks": guildOnly(modOnly(answerFixBadEmbedLinks)),
"!messagelogs": guildOnly(modOnly(answerMessageLogs)),
"!commandstats": guildOnly(modOnly(answerCommandStats)),
// only available for the bot owner
"!guildlist": adminOnly(answerGuildList),
"!addglobalcommand": adminOnly(answerAddGlobalCommand),
"!removeglobalcommand": adminOnly(answerRemoveGlobalCommand),
"!deleteglobalcommand": adminOnly(answerRemoveGlobalCommand),
"!announce": adminOnly(answerAnnounce),
"!dbbackup": adminOnly(answerDbBackup),
"!runtimestats": adminOnly(answerRuntimeStats),
Expand Down Expand Up @@ -160,11 +172,11 @@ func answerHello(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context

func answerPP(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
seed, err := strconv.ParseInt(mc.Author.ID, 10, 64)
seed *= unixDay()
notifyIfErr("answerPP: parsing user id: "+mc.Author.ID, err, ds)
if err != nil {
return false
}
seed *= unixDay()
pp := ppgen.NewPenisWithSeed(seed)
_, err = ds.ChannelMessageSend(mc.ChannelID, fmt.Sprintf("%s's penis: %s", mc.Author.Mention(), pp))
return err == nil
Expand Down Expand Up @@ -241,40 +253,93 @@ func answerAnnounceHere(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx
return err == nil
}

func answerFixTwitterLinks(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
currSetting, _ := serverDS.getServerProperty(mc.GuildID, serverPropFixTwitterLinks)
func answerFixBadEmbedLinks(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
currSetting, _ := serverDS.getServerProperty(mc.GuildID, serverPropFixBadEmbedLinks)
newSetting := serverPropYes
if currSetting == serverPropYes {
newSetting = serverPropNo
}
err := serverDS.setServerProperty(mc.GuildID, serverPropFixTwitterLinks, newSetting)
err := serverDS.setServerProperty(mc.GuildID, serverPropFixBadEmbedLinks, newSetting)
if err == nil && newSetting == serverPropYes {
ds.ChannelMessageSend(mc.ChannelID, "Okay! Will fix twitter links")
ds.ChannelMessageSend(mc.ChannelID, "Okay! Will fix bad embed links")
} else if err == nil && newSetting == serverPropNo {
ds.ChannelMessageSend(mc.ChannelID, "Okay! Will not fix twitter links")
ds.ChannelMessageSend(mc.ChannelID, "Okay! Will not fix bad embed links")
}
return err == nil
}

func processMessageWithTwitterLinks(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) {
currSetting, _ := serverDS.getServerProperty(mc.GuildID, serverPropFixTwitterLinks)
func processMessageWithBadEmbedLinks(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) {
currSetting, _ := serverDS.getServerProperty(mc.GuildID, serverPropFixBadEmbedLinks)
if currSetting != serverPropYes {
return
}

messageContent := ogTwitterLinkRegex.ReplaceAllString(mc.Content, "https://fxtwitter.com")
_, err := sendAsUser(ds, mc.Author, mc.ChannelID, messageContent)
messageContent := mc.Content
for rgx, rpl := range badEmbedLinkReplacements {
messageContent = rgx.ReplaceAllString(messageContent, rpl)
}
fixedMsg, err := sendAsUser(ds, mc.Author, mc.ChannelID, messageContent, mc.ReferencedMessage)
if err != nil {
notifyIfErr("processMessageWithTwitterLinks::sendAsUser", err, ds)
notifyIfErr("processMessageWithBadEmbedLinks::sendAsUser", err, ds)
return
}
messageLinkFixToOgAuthorId[fixedMsg] = mc.Author.ID

ds.State.MessageRemove(mc.Message)
err = ds.ChannelMessageDelete(mc.ChannelID, mc.ID)
if err != nil {
notifyIfErr("processMessageWithTwitterLinks::ds.ChannelMessageDelete", err, ds)
notifyIfErr("processMessageWithBadEmbedLinks::ds.ChannelMessageDelete", err, ds)
return
}
}

func answerDeleteLinkFixMessage(ds *discordgo.Session, ic *discordgo.InteractionCreate) {
interactionUserId := interactionUser(ic).ID
ogAuthorId, ok := messageLinkFixToOgAuthorId[ic.Message]
if !ok && !isAdmin(interactionUserId) {
ds.InteractionRespond(ic.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Could not find original author, only a mod can delete that message",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return
}

// Check if the user can delete the message
if interactionUserId != ogAuthorId || !isAdmin(interactionUserId) {
ds.InteractionRespond(ic.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "You did not send that message",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return
}

// Try to delete and respond if it was successful
fixedMsgID := ic.ApplicationCommandData().TargetID
err := ds.ChannelMessageDelete(ic.ChannelID, fixedMsgID)
notifyIfErr("answerDeleteLinkFixMessage::ChannelMessageDelete", err, ds)
if err != nil {
ds.InteractionRespond(ic.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Sorry, I could not delete the message u_u",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return
}
ds.InteractionRespond(ic.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Message deleted ^w^",
Flags: discordgo.MessageFlagsEphemeral,
},
})
}

func answerMessageLogs(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
Expand Down Expand Up @@ -303,17 +368,35 @@ func answerCommandStats(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx
return err == nil
}

func answerGuildList(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
guilds, err := ds.UserGuilds(100, "", "", true)
if err != nil {
notifyIfErr("answerGuildList", err, ds)
return false
}
guildsMsg := ""
for _, g := range guilds {
guildsMsg += fmt.Sprintf("%s [%s] - Member count %d - Presence count %d\n", g.Name, g.ID, g.ApproximateMemberCount, g.ApproximatePresenceCount)
}
_, err = ds.ChannelMessageSend(mc.ChannelID, guildsMsg)
return err == nil
}

// ---------- Simple command stuff ----------

func answerAddCommand(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
commandBody := commandPrefixRegex.ReplaceAllString(mc.Content, "")
key := strings.TrimSpace(commandPrefixRegex.FindString(commandBody))
if key == "" {
ds.ChannelMessageSend(mc.ChannelID, diff("Could not get the key from the command body", "- "))
ds.ChannelMessageSend(mc.ChannelID, markdownDiffBlock("Could not get the key from the command body", "- "))
return false
}
response := commandPrefixRegex.ReplaceAllString(commandBody, "")
err := commandDS.addSimpleCommand(key, response, mc.GuildID)
if response == "" {
ds.ChannelMessageSend(mc.ChannelID, markdownDiffBlock("Could not get the response from the command body", "- "))
return false
}
err := commandDS.addSimpleCommand(key, response, mc.GuildID, mc.Author.ID)
notifyIfErr("addSimpleCommand", err, ds)
if err == nil {
ds.ChannelMessageSend(mc.ChannelID, commandSuccessMessage)
Expand All @@ -325,11 +408,15 @@ func answerAddGlobalCommand(ds *discordgo.Session, mc *discordgo.MessageCreate,
commandBody := commandPrefixRegex.ReplaceAllString(mc.Content, "")
key := strings.TrimSpace(commandPrefixRegex.FindString(commandBody))
if key == "" {
ds.ChannelMessageSend(mc.ChannelID, diff("Could not get the key from the command body", "- "))
ds.ChannelMessageSend(mc.ChannelID, markdownDiffBlock("Could not get the key from the command body", "- "))
return false
}
response := commandPrefixRegex.ReplaceAllString(commandBody, "")
err := commandDS.addSimpleCommand(key, response, globalGuildID)
if response == "" {
ds.ChannelMessageSend(mc.ChannelID, markdownDiffBlock("Could not get the response from the command body", "- "))
return false
}
err := commandDS.addSimpleCommand(key, response, globalGuildID, mc.Author.ID)
notifyIfErr("addGlobalCommand", err, ds)
if err == nil {
ds.ChannelMessageSend(mc.ChannelID, commandSuccessMessage)
Expand All @@ -340,16 +427,43 @@ func answerAddGlobalCommand(ds *discordgo.Session, mc *discordgo.MessageCreate,
func answerRemoveCommand(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
commandBody := strings.TrimSpace(commandPrefixRegex.ReplaceAllString(mc.Content, ""))
err := commandDS.removeSimpleCommand(commandBody, mc.GuildID)
if err == errZeroRowsAffected {
ds.ChannelMessageSend(mc.ChannelID, "I could not find that command! sowwy u_u")
}
notifyIfErr("removeSimpleCommand", err, ds)
if err == nil {
ds.ChannelMessageSend(mc.ChannelID, commandSuccessMessage)
}
return err == nil
}

func answerCommandCreator(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
commandBody := strings.TrimSpace(commandPrefixRegex.ReplaceAllString(mc.Content, ""))
if commandBody == "" {
return false
}
if commandBody[0] != '!' {
commandBody = "!" + commandBody
}

creator, err := commandDS.getCommandCreator(commandBody, mc.GuildID)
if err != nil {
ds.ChannelMessageSend(mc.ChannelID, "Could not find command creator. I'm sowwy u_u")
return false
}
ds.ChannelMessageSendComplex(mc.ChannelID, &discordgo.MessageSend{
Content: fmt.Sprintf("Command creator: <@%s>", creator),
AllowedMentions: &discordgo.MessageAllowedMentions{},
})
return true
}

func answerRemoveGlobalCommand(ds *discordgo.Session, mc *discordgo.MessageCreate, ctx context.Context) bool {
commandBody := strings.TrimSpace(commandPrefixRegex.ReplaceAllString(mc.Content, ""))
err := commandDS.removeSimpleCommand(commandBody, globalGuildID)
if err == errZeroRowsAffected {
ds.ChannelMessageSend(mc.ChannelID, "I could not find that command! sowwy u_u")
}
notifyIfErr("removeGlobalCommand", err, ds)
if err == nil {
ds.ChannelMessageSend(mc.ChannelID, commandSuccessMessage)
Expand Down Expand Up @@ -495,8 +609,6 @@ func notSpammable(wrapped command) command {

var lastUserCommandTime = map[string]time.Time{}

const commandCooldown = time.Minute * 15

func resetUserCooldown(userID string) {
lastUserCommandTime[userID] = time.Now()
}
Expand Down
Loading

0 comments on commit be02451

Please sign in to comment.