discord.go 24 KB

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