discord.go 21 KB

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