discord.go 23 KB

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