discord.go 24 KB

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