discord.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "runtime"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/AvraamMavridis/randomcolor"
  10. "github.com/aidarkhanov/nanoid/v2"
  11. "github.com/bwmarrin/discordgo"
  12. "github.com/fatih/color"
  13. "github.com/hako/durafmt"
  14. "github.com/teris-io/shortid"
  15. )
  16. const (
  17. discordEpoch = 1420070400000
  18. )
  19. //TODO: Clean these two
  20. func discordTimestampToSnowflake(format string, timestamp string) string {
  21. t, err := time.Parse(format, timestamp)
  22. if err == nil {
  23. return fmt.Sprint(((t.Local().UnixNano() / int64(time.Millisecond)) - discordEpoch) << 22)
  24. }
  25. log.Println(color.HiRedString("Failed to convert timestamp to discord snowflake... Format: '%s', Timestamp: '%s' - Error:\t%s",
  26. format, timestamp, err),
  27. )
  28. return ""
  29. }
  30. func discordSnowflakeToTimestamp(snowflake string, format string) string {
  31. i, err := strconv.ParseInt(snowflake, 10, 64)
  32. if err != nil {
  33. return ""
  34. }
  35. t := time.Unix(0, ((i>>22)+discordEpoch)*1000000)
  36. return t.Local().Format(format)
  37. }
  38. func getAllRegisteredChannels() []string {
  39. var channels []string
  40. if config.All != nil { // ALL MODE
  41. for _, guild := range bot.State.Guilds {
  42. for _, channel := range guild.Channels {
  43. if hasPerms(channel.ID, discordgo.PermissionReadMessages) && hasPerms(channel.ID, discordgo.PermissionReadMessageHistory) {
  44. channels = append(channels, channel.ID)
  45. }
  46. }
  47. }
  48. } else { // STANDARD MODE
  49. // Compile all config channels
  50. for _, channel := range config.Channels {
  51. if channel.ChannelIDs != nil {
  52. for _, subchannel := range *channel.ChannelIDs {
  53. channels = append(channels, subchannel)
  54. }
  55. } else if isNumeric(channel.ChannelID) {
  56. channels = append(channels, channel.ChannelID)
  57. }
  58. }
  59. // Compile all channels sourced from config servers
  60. for _, server := range config.Servers {
  61. if server.ServerIDs != nil {
  62. for _, subserver := range *server.ServerIDs {
  63. guild, err := bot.State.Guild(subserver)
  64. if err == nil {
  65. for _, channel := range guild.Channels {
  66. if hasPerms(channel.ID, discordgo.PermissionReadMessageHistory) {
  67. channels = append(channels, channel.ID)
  68. }
  69. }
  70. }
  71. }
  72. } else if isNumeric(server.ServerID) {
  73. guild, err := bot.State.Guild(server.ServerID)
  74. if err == nil {
  75. for _, channel := range guild.Channels {
  76. if hasPerms(channel.ID, discordgo.PermissionReadMessageHistory) {
  77. channels = append(channels, channel.ID)
  78. }
  79. }
  80. }
  81. }
  82. }
  83. }
  84. return channels
  85. }
  86. //#region Presence
  87. func dataKeyReplacement(input string) string {
  88. //TODO: Case-insensitive key replacement. -- If no streamlined way to do it, convert to lower to find substring location but replace normally
  89. if strings.Contains(input, "{{") && strings.Contains(input, "}}") {
  90. countInt := int64(dbDownloadCount()) + *config.InflateCount
  91. timeNow := time.Now()
  92. keys := [][]string{
  93. {"{{dgVersion}}", discordgo.VERSION},
  94. {"{{ddgVersion}}", projectVersion},
  95. {"{{apiVersion}}", discordgo.APIVersion},
  96. {"{{countNoCommas}}", fmt.Sprint(countInt)},
  97. {"{{count}}", formatNumber(countInt)},
  98. {"{{countShort}}", formatNumberShort(countInt)},
  99. {"{{numServers}}", fmt.Sprint(len(bot.State.Guilds))},
  100. {"{{numBoundChannels}}", fmt.Sprint(getBoundChannelsCount())},
  101. {"{{numBoundServers}}", fmt.Sprint(getBoundServersCount())},
  102. {"{{numAdminChannels}}", fmt.Sprint(len(config.AdminChannels))},
  103. {"{{numAdmins}}", fmt.Sprint(len(config.Admins))},
  104. {"{{timeSavedShort}}", timeLastUpdated.Format("3:04pm")},
  105. {"{{timeSavedShortTZ}}", timeLastUpdated.Format("3:04pm MST")},
  106. {"{{timeSavedMid}}", timeLastUpdated.Format("3:04pm MST 1/2/2006")},
  107. {"{{timeSavedLong}}", timeLastUpdated.Format("3:04:05pm MST - January 2, 2006")},
  108. {"{{timeSavedShort24}}", timeLastUpdated.Format("15:04")},
  109. {"{{timeSavedShortTZ24}}", timeLastUpdated.Format("15:04 MST")},
  110. {"{{timeSavedMid24}}", timeLastUpdated.Format("15:04 MST 2/1/2006")},
  111. {"{{timeSavedLong24}}", timeLastUpdated.Format("15:04:05 MST - 2 January, 2006")},
  112. {"{{timeNowShort}}", timeNow.Format("3:04pm")},
  113. {"{{timeNowShortTZ}}", timeNow.Format("3:04pm MST")},
  114. {"{{timeNowMid}}", timeNow.Format("3:04pm MST 1/2/2006")},
  115. {"{{timeNowLong}}", timeNow.Format("3:04:05pm MST - January 2, 2006")},
  116. {"{{timeNowShort24}}", timeNow.Format("15:04")},
  117. {"{{timeNowShortTZ24}}", timeNow.Format("15:04 MST")},
  118. {"{{timeNowMid24}}", timeNow.Format("15:04 MST 2/1/2006")},
  119. {"{{timeNowLong24}}", timeNow.Format("15:04:05 MST - 2 January, 2006")},
  120. {"{{uptime}}", durafmt.ParseShort(time.Since(startTime)).String()},
  121. }
  122. for _, key := range keys {
  123. if strings.Contains(input, key[0]) {
  124. input = strings.ReplaceAll(input, key[0], key[1])
  125. }
  126. }
  127. }
  128. return input
  129. }
  130. func filenameKeyReplacement(channelConfig configurationChannel, download downloadRequestStruct) string {
  131. //TODO: same as dataKeyReplacement
  132. ret := config.FilenameFormat
  133. if channelConfig.OverwriteFilenameFormat != nil {
  134. if *channelConfig.OverwriteFilenameFormat != "" {
  135. ret = *channelConfig.OverwriteFilenameFormat
  136. }
  137. }
  138. if strings.Contains(ret, "{{") && strings.Contains(ret, "}}") {
  139. // Format Filename Date
  140. filenameDateFormat := config.FilenameDateFormat
  141. if channelConfig.OverwriteFilenameDateFormat != nil {
  142. if *channelConfig.OverwriteFilenameDateFormat != "" {
  143. filenameDateFormat = *channelConfig.OverwriteFilenameDateFormat
  144. }
  145. }
  146. messageTime := download.Message.Timestamp
  147. shortID, err := shortid.Generate()
  148. if err != nil && config.DebugOutput {
  149. log.Println(logPrefixDebug, color.HiCyanString("Error when generating a shortID %s", err))
  150. }
  151. nanoID, err := nanoid.New()
  152. if err != nil && config.DebugOutput {
  153. log.Println(logPrefixDebug, color.HiCyanString("Error when creating a nanoID %s", err))
  154. }
  155. userID := ""
  156. username := ""
  157. if download.Message.Author != nil {
  158. userID = download.Message.Author.ID
  159. username = download.Message.Author.Username
  160. }
  161. keys := [][]string{
  162. {"{{date}}", messageTime.Format(filenameDateFormat)},
  163. {"{{file}}", download.Filename},
  164. {"{{fileType}}", download.FileExtension},
  165. {"{{messageID}}", download.Message.ID},
  166. {"{{userID}}", userID},
  167. {"{{username}}", username},
  168. {"{{channelID}}", download.Message.ChannelID},
  169. {"{{serverID}}", download.Message.GuildID},
  170. {"{{message}}", clearPath(download.Message.Content)},
  171. {"{{nanoID}}", nanoID},
  172. {"{{shortID}}", shortID},
  173. }
  174. for _, key := range keys {
  175. if strings.Contains(ret, key[0]) {
  176. ret = strings.ReplaceAll(ret, key[0], key[1])
  177. }
  178. }
  179. }
  180. return ret
  181. }
  182. func updateDiscordPresence() {
  183. if config.PresenceEnabled {
  184. // Vars
  185. countInt := int64(dbDownloadCount()) + *config.InflateCount
  186. count := formatNumber(countInt)
  187. countShort := formatNumberShort(countInt)
  188. timeShort := timeLastUpdated.Format("3:04pm")
  189. timeLong := timeLastUpdated.Format("3:04:05pm MST - January 2, 2006")
  190. // Defaults
  191. status := fmt.Sprintf("%s - %s files", timeShort, countShort)
  192. statusDetails := timeLong
  193. statusState := fmt.Sprintf("%s files total", count)
  194. // Overwrite Presence
  195. if config.PresenceOverwrite != nil {
  196. status = *config.PresenceOverwrite
  197. if status != "" {
  198. status = dataKeyReplacement(status)
  199. }
  200. }
  201. // Overwrite Details
  202. if config.PresenceOverwriteDetails != nil {
  203. statusDetails = *config.PresenceOverwriteDetails
  204. if statusDetails != "" {
  205. statusDetails = dataKeyReplacement(statusDetails)
  206. }
  207. }
  208. // Overwrite State
  209. if config.PresenceOverwriteState != nil {
  210. statusState = *config.PresenceOverwriteState
  211. if statusState != "" {
  212. statusState = dataKeyReplacement(statusState)
  213. }
  214. }
  215. // Update
  216. bot.UpdateStatusComplex(discordgo.UpdateStatusData{
  217. Game: &discordgo.Game{
  218. Name: status,
  219. Type: config.PresenceType,
  220. Details: statusDetails, // Only visible if real user
  221. State: statusState, // Only visible if real user
  222. },
  223. Status: config.PresenceStatus,
  224. })
  225. } else if config.PresenceStatus != string(discordgo.StatusOnline) {
  226. bot.UpdateStatusComplex(discordgo.UpdateStatusData{
  227. Status: config.PresenceStatus,
  228. })
  229. }
  230. }
  231. //#endregion
  232. //#region Embeds
  233. func getEmbedColor(channelID string) int {
  234. var err error
  235. var color *string
  236. var channelInfo *discordgo.Channel
  237. // Assign Defined Color
  238. if config.EmbedColor != nil {
  239. if *config.EmbedColor != "" {
  240. color = config.EmbedColor
  241. }
  242. }
  243. // Overwrite with Defined Color for Channel
  244. /*var msg *discordgo.Message
  245. msg.ChannelID = channelID
  246. if channelRegistered(msg) {
  247. channelConfig := getChannelConfig(channelID)
  248. if channelConfig.OverwriteEmbedColor != nil {
  249. if *channelConfig.OverwriteEmbedColor != "" {
  250. color = channelConfig.OverwriteEmbedColor
  251. }
  252. }
  253. }*/
  254. // Use Defined Color
  255. if color != nil {
  256. // Defined as Role, fetch role color
  257. if *color == "role" || *color == "user" {
  258. botColor := bot.State.UserColor(user.ID, channelID)
  259. if botColor != 0 {
  260. return botColor
  261. }
  262. goto color_random
  263. }
  264. // Defined as Random, jump below (not preferred method but seems to work flawlessly)
  265. if *color == "random" || *color == "rand" {
  266. goto color_random
  267. }
  268. var colorString string = *color
  269. // Input is Hex
  270. colorString = strings.ReplaceAll(colorString, "#", "")
  271. if convertedHex, err := strconv.ParseUint(colorString, 16, 64); err == nil {
  272. return int(convertedHex)
  273. }
  274. // Input is Int
  275. if convertedInt, err := strconv.Atoi(colorString); err == nil {
  276. return convertedInt
  277. }
  278. // Definition is invalid since hasn't returned, so defaults to below...
  279. }
  280. // User color
  281. channelInfo, err = bot.State.Channel(channelID)
  282. if err == nil {
  283. if channelInfo.Type != discordgo.ChannelTypeDM && channelInfo.Type != discordgo.ChannelTypeGroupDM {
  284. if bot.State.UserColor(user.ID, channelID) != 0 {
  285. return bot.State.UserColor(user.ID, channelID)
  286. }
  287. }
  288. }
  289. // Random color
  290. color_random:
  291. var randomColor string = randomcolor.GetRandomColorInHex()
  292. if convertedRandom, err := strconv.ParseUint(strings.ReplaceAll(randomColor, "#", ""), 16, 64); err == nil {
  293. return int(convertedRandom)
  294. }
  295. return 16777215 // white
  296. }
  297. // Shortcut function for quickly constructing a styled embed with Title & Description
  298. func buildEmbed(channelID string, title string, description string) *discordgo.MessageEmbed {
  299. return &discordgo.MessageEmbed{
  300. Title: title,
  301. Description: description,
  302. Color: getEmbedColor(channelID),
  303. Footer: &discordgo.MessageEmbedFooter{
  304. IconURL: projectIcon,
  305. Text: fmt.Sprintf("%s v%s", projectName, projectVersion),
  306. },
  307. }
  308. }
  309. // Shortcut function for quickly replying a styled embed with Title & Description
  310. func replyEmbed(m *discordgo.Message, title string, description string) (*discordgo.Message, error) {
  311. if m != nil {
  312. if hasPerms(m.ChannelID, discordgo.PermissionSendMessages) {
  313. if selfbot {
  314. return bot.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s **%s**\n\n%s", m.Author.Mention(), title, description))
  315. } else {
  316. return bot.ChannelMessageSendComplex(m.ChannelID,
  317. &discordgo.MessageSend{
  318. Content: m.Author.Mention(),
  319. Embed: buildEmbed(m.ChannelID, title, description),
  320. },
  321. )
  322. }
  323. }
  324. log.Println(color.HiRedString(fmtBotSendPerm, m.ChannelID))
  325. }
  326. return nil, nil
  327. }
  328. type logStatusType int
  329. const (
  330. logStatusStartup logStatusType = iota
  331. logStatusReconnect
  332. logStatusExit
  333. )
  334. func logStatusLabel(status logStatusType) string {
  335. switch status {
  336. case logStatusStartup:
  337. return "has launched"
  338. case logStatusReconnect:
  339. return "has reconnected"
  340. case logStatusExit:
  341. return "is exiting"
  342. }
  343. return "<<ERROR>>"
  344. }
  345. func logStatusMessage(status logStatusType) {
  346. for _, adminChannel := range config.AdminChannels {
  347. if *adminChannel.LogStatus {
  348. var message string
  349. var label string
  350. if status == logStatusStartup || status == logStatusReconnect {
  351. label = "startup"
  352. message += fmt.Sprintf("%s %s and connected to %d server%s...\n", projectLabel, logStatusLabel(status), len(bot.State.Guilds), pluralS(len(bot.State.Guilds)))
  353. message += fmt.Sprintf("\n• Uptime is %s", uptime())
  354. message += fmt.Sprintf("\n• %s total downloads", formatNumber(int64(dbDownloadCount())))
  355. message += fmt.Sprintf("\n• Bound to %d channel%s and %d server%s", getBoundChannelsCount(), pluralS(getBoundChannelsCount()), getBoundServersCount(), pluralS(getBoundServersCount()))
  356. if config.All != nil {
  357. message += "\n• **ALL MODE ENABLED -** Bot will use all available channels"
  358. }
  359. allChannels := getAllRegisteredChannels()
  360. message += fmt.Sprintf("\n• ***Listening to %s channel%s...***\n", formatNumber(int64(len(allChannels))), pluralS(len(allChannels)))
  361. if twitterConnected {
  362. message += "\n• Connected to Twitter API"
  363. }
  364. if googleDriveConnected {
  365. message += "\n• Connected to Google Drive"
  366. }
  367. message += fmt.Sprintf("\n_%s-%s %s / discordgo v%s / Discord API v%s_",
  368. runtime.GOOS, runtime.GOARCH, runtime.Version(), discordgo.VERSION, discordgo.APIVersion)
  369. } else if status == logStatusExit {
  370. label = "exit"
  371. message += fmt.Sprintf("%s %s...\n", projectLabel, logStatusLabel(status))
  372. message += fmt.Sprintf("\n• Uptime was %s", uptime())
  373. message += fmt.Sprintf("\n• %s total downloads", formatNumber(int64(dbDownloadCount())))
  374. message += fmt.Sprintf("\n• Bound to %d channel%s and %d server%s", getBoundChannelsCount(), pluralS(getBoundChannelsCount()), getBoundServersCount(), pluralS(getBoundServersCount()))
  375. }
  376. // Send
  377. if config.DebugOutput {
  378. log.Println(logPrefixDebug, color.HiCyanString("Sending log for %s to admin channel %s", label, adminChannel.ChannelID))
  379. }
  380. if hasPerms(adminChannel.ChannelID, discordgo.PermissionEmbedLinks) && !selfbot {
  381. bot.ChannelMessageSendEmbed(adminChannel.ChannelID, buildEmbed(adminChannel.ChannelID, "Log — Status", message))
  382. } else if hasPerms(adminChannel.ChannelID, discordgo.PermissionSendMessages) {
  383. bot.ChannelMessageSend(adminChannel.ChannelID, message)
  384. } else {
  385. log.Println(logPrefixDebug, color.HiRedString("Perms checks failed for sending status log to %s", adminChannel.ChannelID))
  386. }
  387. }
  388. }
  389. }
  390. func logErrorMessage(err string) {
  391. for _, adminChannel := range config.AdminChannels {
  392. if *adminChannel.LogErrors {
  393. // Send
  394. if hasPerms(adminChannel.ChannelID, discordgo.PermissionEmbedLinks) && !selfbot { // not confident this is the right permission
  395. if config.DebugOutput {
  396. log.Println(logPrefixDebug, color.HiCyanString("Sending embed log for error to %s", adminChannel.ChannelID))
  397. }
  398. bot.ChannelMessageSendEmbed(adminChannel.ChannelID, buildEmbed(adminChannel.ChannelID, "Log — Error", err))
  399. } else if hasPerms(adminChannel.ChannelID, discordgo.PermissionSendMessages) {
  400. if config.DebugOutput {
  401. log.Println(logPrefixDebug, color.HiCyanString("Sending message log for error to %s", adminChannel.ChannelID))
  402. }
  403. bot.ChannelMessageSend(adminChannel.ChannelID, err)
  404. } else {
  405. log.Println(logPrefixDebug, color.HiRedString("Perms checks failed for sending error log to %s", adminChannel.ChannelID))
  406. }
  407. }
  408. }
  409. }
  410. //#endregion
  411. //#region Permissions
  412. // Checks if message author is a specified bot admin.
  413. func isBotAdmin(m *discordgo.Message) bool {
  414. // No Admins or Admin Channels
  415. if len(config.Admins) == 0 && len(config.AdminChannels) == 0 {
  416. return true
  417. }
  418. // configurationAdminChannel.UnlockCommands Bypass
  419. if isAdminChannelRegistered(m.ChannelID) {
  420. channelConfig := getAdminChannelConfig(m.ChannelID)
  421. if *channelConfig.UnlockCommands == true {
  422. return true
  423. }
  424. }
  425. return m.Author.ID == user.ID || stringInSlice(m.Author.ID, config.Admins)
  426. }
  427. // Checks if message author is a specified bot admin OR is server admin OR has message management perms in channel
  428. func isLocalAdmin(m *discordgo.Message) bool {
  429. if m == nil {
  430. if config.DebugOutput {
  431. log.Println(logPrefixDebug, color.YellowString("isLocalAdmin check failed due to empty message"))
  432. }
  433. return true
  434. }
  435. sourceChannel, err := bot.State.Channel(m.ChannelID)
  436. if err != nil || sourceChannel == nil {
  437. if config.DebugOutput {
  438. log.Println(logPrefixDebug, color.YellowString("isLocalAdmin check failed due to an error or received empty channel info for message:\t%s", err))
  439. }
  440. return true
  441. } else if sourceChannel.Name == "" || sourceChannel.GuildID == "" {
  442. if config.DebugOutput {
  443. log.Println(logPrefixDebug, color.YellowString("isLocalAdmin check failed due to incomplete channel info"))
  444. }
  445. return true
  446. }
  447. guild, _ := bot.State.Guild(m.GuildID)
  448. localPerms, err := bot.State.UserChannelPermissions(m.Author.ID, m.ChannelID)
  449. if err != nil {
  450. if config.DebugOutput {
  451. log.Println(logPrefixDebug, color.YellowString("isLocalAdmin check failed due to error when checking permissions:\t%s", err))
  452. }
  453. return true
  454. }
  455. botSelf := m.Author.ID == user.ID
  456. botAdmin := stringInSlice(m.Author.ID, config.Admins)
  457. guildOwner := m.Author.ID == guild.OwnerID
  458. guildAdmin := localPerms&discordgo.PermissionAdministrator > 0
  459. localManageMessages := localPerms&discordgo.PermissionManageMessages > 0
  460. return botSelf || botAdmin || guildOwner || guildAdmin || localManageMessages
  461. }
  462. func hasPerms(channelID string, permission int64) bool {
  463. if !config.CheckPermissions {
  464. return true
  465. }
  466. sourceChannel, err := bot.State.Channel(channelID)
  467. if sourceChannel != nil && err == nil {
  468. switch sourceChannel.Type {
  469. case discordgo.ChannelTypeDM:
  470. return true
  471. case discordgo.ChannelTypeGroupDM:
  472. return true
  473. case discordgo.ChannelTypeGuildText:
  474. perms, err := bot.UserChannelPermissions(user.ID, channelID)
  475. if err == nil {
  476. return perms&permission == permission
  477. }
  478. log.Println(color.HiRedString("Failed to check permissions (%d) for %s:\t%s", permission, channelID, err))
  479. }
  480. }
  481. return false
  482. }
  483. //#endregion
  484. //#region Labeling
  485. func getUserIdentifier(usr discordgo.User) string {
  486. return fmt.Sprintf("\"%s\"#%s", usr.Username, usr.Discriminator)
  487. }
  488. //TODO: Clean below
  489. func getChannelState(channelID string) *discordgo.Channel {
  490. sourceChannel, _ := bot.State.Channel(channelID)
  491. if sourceChannel != nil {
  492. return sourceChannel
  493. }
  494. return &discordgo.Channel{}
  495. }
  496. func getGuildState(guildID string) *discordgo.Guild {
  497. sourceGuild, _ := bot.State.Guild(guildID)
  498. if sourceGuild != nil {
  499. return sourceGuild
  500. }
  501. return &discordgo.Guild{}
  502. }
  503. func getChannelGuildID(channelID string) string {
  504. sourceChannel, _ := bot.State.Channel(channelID)
  505. if sourceChannel != nil {
  506. return sourceChannel.GuildID
  507. }
  508. return ""
  509. }
  510. func getGuildName(guildID string) string {
  511. sourceGuildName := "UNKNOWN"
  512. sourceGuild, _ := bot.State.Guild(guildID)
  513. if sourceGuild != nil && sourceGuild.Name != "" {
  514. sourceGuildName = sourceGuild.Name
  515. }
  516. return sourceGuildName
  517. }
  518. func getChannelName(channelID string) string {
  519. sourceChannelName := "unknown"
  520. sourceChannel, _ := bot.State.Channel(channelID)
  521. if sourceChannel != nil {
  522. if sourceChannel.Name != "" {
  523. sourceChannelName = sourceChannel.Name
  524. } else {
  525. switch sourceChannel.Type {
  526. case discordgo.ChannelTypeDM:
  527. sourceChannelName = "dm"
  528. case discordgo.ChannelTypeGroupDM:
  529. sourceChannelName = "group-dm"
  530. }
  531. }
  532. }
  533. return sourceChannelName
  534. }
  535. func getSourceName(guildID string, channelID string) string {
  536. guildName := getGuildName(guildID)
  537. channelName := getChannelName(channelID)
  538. if channelName == "dm" || channelName == "group-dm" {
  539. return channelName
  540. }
  541. return fmt.Sprintf("\"%s\"#%s", guildName, channelName)
  542. }
  543. //#endregion
  544. // For command case-insensitivity
  545. func messageToLower(message *discordgo.Message) *discordgo.Message {
  546. newMessage := *message
  547. newMessage.Content = strings.ToLower(newMessage.Content)
  548. return &newMessage
  549. }
  550. func fixMessage(m *discordgo.Message) *discordgo.Message {
  551. // If message content is empty (likely due to userbot/selfbot)
  552. ubIssue := "Message is corrupted due to endpoint restriction"
  553. if m.Content == "" && len(m.Attachments) == 0 && len(m.Embeds) == 0 {
  554. // Get message history
  555. mCache, err := bot.ChannelMessages(m.ChannelID, 20, "", "", "")
  556. if err == nil {
  557. if len(mCache) > 0 {
  558. for _, mCached := range mCache {
  559. if mCached.ID == m.ID {
  560. // Fix original message having empty Guild ID
  561. guildID := m.GuildID
  562. // Replace message
  563. m = mCached
  564. // ^^
  565. if m.GuildID == "" && guildID != "" {
  566. m.GuildID = guildID
  567. }
  568. // Parse commands
  569. dgr.FindAndExecute(bot, strings.ToLower(config.CommandPrefix), bot.State.User.ID, messageToLower(m))
  570. break
  571. }
  572. }
  573. } else if config.DebugOutput {
  574. log.Println(logPrefixDebug, color.RedString("%s, and an attempt to get channel messages found nothing...", ubIssue))
  575. }
  576. } else if config.DebugOutput {
  577. log.Println(logPrefixDebug, color.HiRedString("%s, and an attempt to get channel messages encountered an error:\t%s", ubIssue, err))
  578. }
  579. }
  580. if m.Content == "" && len(m.Attachments) == 0 && len(m.Embeds) == 0 {
  581. if config.DebugOutput {
  582. log.Println(logPrefixDebug, color.YellowString("%s, and attempts to fix seem to have failed...", ubIssue))
  583. }
  584. }
  585. return m
  586. }