discord.go 21 KB

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