main.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "net/url"
  8. "os"
  9. "os/signal"
  10. "runtime"
  11. "strings"
  12. "syscall"
  13. "time"
  14. "github.com/ChimeraCoder/anaconda"
  15. "github.com/HouzuoGuo/tiedot/db"
  16. "github.com/Necroforger/dgrouter/exrouter"
  17. "github.com/bwmarrin/discordgo"
  18. "github.com/fatih/color"
  19. "github.com/fsnotify/fsnotify"
  20. "github.com/hako/durafmt"
  21. )
  22. /* v2.0.0 REWRITE TODO:
  23. * Logging System
  24. *** Implement Log Leveling?
  25. *** Truncate links to exact size?
  26. *** Table/Indentation output?
  27. * Better Message/Embed Send+Error Handling
  28. *** Ensure USER Permission Check Compat
  29. * Audit Settings/Config structure
  30. *** Better Settings Insight / Corrective Suggestions
  31. * Ensure 100% nil checks
  32. * Fix Reddit
  33. * Fix Mastodon
  34. * Fix/Implement Instagram?
  35. * Command: Reboot System
  36. */
  37. var (
  38. err error
  39. // Bot
  40. bot *discordgo.Session
  41. botUser *discordgo.User
  42. botCommands *exrouter.Route
  43. selfbot bool = false
  44. botReady bool = false
  45. // Storage
  46. myDB *db.DB
  47. // APIs
  48. twitterConnected bool
  49. // Gen
  50. loop chan os.Signal
  51. startTime time.Time
  52. timeLastUpdated time.Time
  53. timeLastDownload time.Time
  54. timeLastMessage time.Time
  55. cachedDownloadID int
  56. configReloadLastTime time.Time
  57. // Validation
  58. invalidAdminChannels []string
  59. invalidChannels []string
  60. invalidCategories []string
  61. invalidServers []string
  62. )
  63. func init() {
  64. loop = make(chan os.Signal, 1)
  65. startTime = time.Now()
  66. historyJobs = make(map[string]historyJob)
  67. if len(os.Args) > 1 {
  68. configFileBase = os.Args[1]
  69. }
  70. log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
  71. log.SetOutput(color.Output)
  72. log.Println(color.HiCyanString(wrapHyphensW(fmt.Sprintf("Welcome to %s v%s", projectName, projectVersion))))
  73. log.Println(lg("Version", "", color.CyanString,
  74. "%s / discord-go v%s (modified) / Discord API v%s", runtime.Version(), discordgo.VERSION, discordgo.APIVersion))
  75. // Github Update Check
  76. if config.GithubUpdateChecking {
  77. if !isLatestGithubRelease() {
  78. log.Println(lg("Version", "UPDATE", color.HiCyanString, "*** Update Available! ***"))
  79. log.Println(lg("Version", "UPDATE", color.CyanString, projectReleaseURL))
  80. log.Println(lg("Version", "UPDATE", color.HiCyanString, "*** See changelog for information ***"))
  81. log.Println(lg("Version", "UPDATE", color.HiCyanString, "CHECK ALL CHANGELOGS SINCE YOUR LAST UPDATE"))
  82. log.Println(lg("Version", "UPDATE", color.HiCyanString, "SOME SETTINGS MAY NEED TO BE UPDATED"))
  83. time.Sleep(5 * time.Second)
  84. }
  85. }
  86. log.Println(lg("Info", "", color.HiCyanString, "** Need help? Discord: https://discord.gg/6Z6FJZVaDV **"))
  87. }
  88. func main() {
  89. //#region Config
  90. loadConfig()
  91. allString := ""
  92. if config.All != nil {
  93. allString = ", ALL ENABLED"
  94. }
  95. log.Println(lg("Settings", "", color.HiYellowString,
  96. "Loaded - bound to %d channel%s, %d categories, %d server%s, %d user%s%s",
  97. getBoundChannelsCount(), pluralS(getBoundChannelsCount()),
  98. getBoundCategoriesCount(),
  99. getBoundServersCount(), pluralS(getBoundServersCount()),
  100. getBoundUsersCount(), pluralS(getBoundUsersCount()), allString,
  101. ))
  102. //#endregion
  103. go openDatabase()
  104. //#region Component Initialization
  105. // Regex
  106. if err = compileRegex(); err != nil {
  107. log.Println(lg("Regex", "", color.HiRedString, "Error initializing:\t%s", err))
  108. return
  109. }
  110. botLoadAPIs()
  111. //#endregion
  112. //#region Discord Initialization
  113. botLoadDiscord()
  114. //#endregion
  115. //#region MAIN STARTUP COMPLETE
  116. if config.Debug {
  117. log.Println(lg("Main", "", color.YellowString, "Startup finished, took %s...", uptime()))
  118. }
  119. log.Println(lg("Main", "", color.HiCyanString,
  120. wrapHyphensW(fmt.Sprintf("%s v%s is online and connected to %d server%s",
  121. projectLabel, projectVersion, len(bot.State.Guilds), pluralS(len(bot.State.Guilds))))))
  122. log.Println(lg("Main", "", color.RedString, "CTRL+C to exit..."))
  123. // Log Status
  124. sendStatusMessage(sendStatusStartup)
  125. //#endregion
  126. //#region Cache Constants
  127. constants := make(map[string]string)
  128. //--- Compile constants
  129. for _, server := range bot.State.Guilds {
  130. serverKey := fmt.Sprintf("SERVER_%s", stripSymbols(server.Name))
  131. serverKey = strings.ReplaceAll(serverKey, " ", "_")
  132. for strings.Contains(serverKey, "__") {
  133. serverKey = strings.ReplaceAll(serverKey, "__", "_")
  134. }
  135. serverKey = strings.ToUpper(serverKey)
  136. if constants[serverKey] == "" {
  137. constants[serverKey] = server.ID
  138. }
  139. for _, channel := range server.Channels {
  140. if channel.Type != discordgo.ChannelTypeGuildCategory {
  141. categoryName := ""
  142. if channel.ParentID != "" {
  143. channelParent, err := bot.State.Channel(channel.ParentID)
  144. if err == nil {
  145. categoryName = channelParent.Name
  146. }
  147. }
  148. channelKey := fmt.Sprintf("CHANNEL_%s_%s_%s",
  149. stripSymbols(server.Name), stripSymbols(categoryName), stripSymbols(channel.Name))
  150. channelKey = strings.ReplaceAll(channelKey, " ", "_")
  151. for strings.Contains(channelKey, "__") {
  152. channelKey = strings.ReplaceAll(channelKey, "__", "_")
  153. }
  154. channelKey = strings.ToUpper(channelKey)
  155. if constants[channelKey] == "" {
  156. constants[channelKey] = channel.ID
  157. }
  158. }
  159. }
  160. }
  161. //--- Save constants
  162. os.MkdirAll(cachePath, 0755)
  163. if _, err := os.Stat(constantsPath); err == nil {
  164. err = os.Remove(constantsPath)
  165. if err != nil {
  166. log.Println(lg("Constants", "", color.HiRedString, "Encountered error deleting cache file:\t%s", err))
  167. }
  168. }
  169. constantsStruct := constStruct{}
  170. constantsStruct.Constants = constants
  171. newJson, err := json.MarshalIndent(constantsStruct, "", "\t")
  172. if err != nil {
  173. log.Println(lg("Constants", "", color.HiRedString, "Failed to format constants...\t%s", err))
  174. } else {
  175. err := ioutil.WriteFile(constantsPath, newJson, 0644)
  176. if err != nil {
  177. log.Println(lg("Constants", "", color.HiRedString, "Failed to save new constants file...\t%s", err))
  178. }
  179. }
  180. //#endregion
  181. //#region BG Tasks
  182. //#region BG Tasks - Tickers
  183. tickerCheckup := time.NewTicker(time.Duration(config.CheckupRate) * time.Minute)
  184. tickerPresence := time.NewTicker(time.Duration(config.PresenceRefreshRate) * time.Minute)
  185. tickerConnection := time.NewTicker(time.Duration(config.ConnectionCheckRate) * time.Minute)
  186. go func() {
  187. for {
  188. select {
  189. case <-tickerCheckup.C:
  190. if config.Debug {
  191. str := fmt.Sprintf("... %dms latency, last discord heartbeat %s ago, %s uptime",
  192. bot.HeartbeatLatency().Milliseconds(),
  193. durafmt.ParseShort(time.Since(bot.LastHeartbeatSent)), durafmt.ParseShort(time.Since(startTime)))
  194. if !timeLastMessage.IsZero() {
  195. str += fmt.Sprintf(", last message %s ago", durafmt.ParseShort(time.Since(timeLastMessage)))
  196. }
  197. if !timeLastDownload.IsZero() {
  198. str += fmt.Sprintf(", last download %s ago", durafmt.ParseShort(time.Since(timeLastDownload)))
  199. }
  200. if len(historyJobs) > 0 {
  201. str += fmt.Sprintf(", %d history jobs", len(historyJobs))
  202. }
  203. log.Println(lg("Checkup", "", color.CyanString, str))
  204. }
  205. case <-tickerPresence.C:
  206. // If bot experiences connection interruption the status will go blank until updated by message, this fixes that
  207. updateDiscordPresence()
  208. case <-tickerConnection.C:
  209. doReconnect := func() {
  210. log.Println(lg("Discord", "", color.YellowString, "Closing Discord connections..."))
  211. bot.Client.CloseIdleConnections()
  212. bot.CloseWithCode(1001)
  213. bot = nil
  214. log.Println(lg("Discord", "", color.RedString, "Discord connections closed!"))
  215. if config.ExitOnBadConnection {
  216. properExit()
  217. } else {
  218. log.Println(lg("Discord", "", color.GreenString, "Logging in..."))
  219. botLoad()
  220. log.Println(lg("Discord", "", color.HiGreenString,
  221. "Reconnected! The bot *should* resume working..."))
  222. // Log Status
  223. sendStatusMessage(sendStatusReconnect)
  224. }
  225. }
  226. gate, err := bot.Gateway()
  227. if err != nil || gate == "" {
  228. log.Println(lg("Discord", "", color.HiYellowString,
  229. "Bot encountered a gateway error: GATEWAY: %s,\tERR: %s", gate, err))
  230. doReconnect()
  231. } else if time.Since(bot.LastHeartbeatAck).Seconds() > 4*60 {
  232. log.Println(lg("Discord", "", color.HiYellowString,
  233. "Bot has not received a heartbeat from Discord in 4 minutes..."))
  234. doReconnect()
  235. }
  236. }
  237. }
  238. }()
  239. //#endregion
  240. //#region BG Tasks - Autorun History
  241. type arh struct{ channel, before, since string }
  242. var autorunHistoryChannels []arh
  243. // Compile list of channels to autorun history
  244. for _, channel := range getAllRegisteredChannels() {
  245. channelConfig := getSource(&discordgo.Message{ChannelID: channel})
  246. if channelConfig.AutorunHistory != nil {
  247. if *channelConfig.AutorunHistory {
  248. var autorunHistoryChannel arh
  249. autorunHistoryChannel.channel = channel
  250. autorunHistoryChannel.before = *channelConfig.AutorunHistoryBefore
  251. autorunHistoryChannel.since = *channelConfig.AutorunHistorySince
  252. autorunHistoryChannels = append(autorunHistoryChannels, autorunHistoryChannel)
  253. }
  254. continue
  255. }
  256. }
  257. // Process autorun history
  258. for _, arh := range autorunHistoryChannels {
  259. if job, exists := historyJobs[arh.channel]; !exists ||
  260. (job.Status != historyStatusDownloading && job.Status != historyStatusAbortRequested) {
  261. job.Status = historyStatusWaiting
  262. job.OriginChannel = "AUTORUN"
  263. job.OriginUser = "AUTORUN"
  264. job.TargetCommandingMessage = nil
  265. job.TargetChannelID = arh.channel
  266. job.TargetBefore = dateLocalToUTC(arh.before)
  267. job.TargetSince = dateLocalToUTC(arh.since)
  268. job.Updated = time.Now()
  269. job.Added = time.Now()
  270. historyJobs[arh.channel] = job
  271. }
  272. }
  273. if len(autorunHistoryChannels) > 0 {
  274. log.Println(lg("History", "Autorun", color.HiYellowString,
  275. "History Autoruns completed (for %d channel%s)",
  276. len(autorunHistoryChannels), pluralS(len(autorunHistoryChannels))))
  277. log.Println(lg("History", "Autorun", color.CyanString,
  278. "Waiting for something else to do..."))
  279. }
  280. //#endregion
  281. //#region BG Tasks - History Job Processing
  282. go func() {
  283. for {
  284. if !historyProcessing {
  285. anyRunning := false
  286. for _, job := range historyJobs {
  287. if job.Status == historyStatusDownloading {
  288. anyRunning = true
  289. }
  290. }
  291. if !anyRunning && len(historyJobs) > 0 {
  292. var job historyJob
  293. for _, _job := range historyJobs {
  294. if _job.Status == historyStatusWaiting {
  295. job = _job
  296. break
  297. }
  298. }
  299. // because of modifying the job while iterating historyJobs above
  300. go handleHistory(job.TargetCommandingMessage, job.TargetChannelID, job.TargetBefore, job.TargetSince)
  301. }
  302. }
  303. time.Sleep(10 * time.Second)
  304. }
  305. }()
  306. //#endregion
  307. //#region BG Tasks - Settings Watcher
  308. watcher, err := fsnotify.NewWatcher()
  309. if err != nil {
  310. log.Println(lg("Settings", "Watcher", color.HiRedString, "Error creating NewWatcher:\t%s", err))
  311. }
  312. defer watcher.Close()
  313. err = watcher.Add(configFile)
  314. if err != nil {
  315. log.Println(lg("Settings", "Watcher", color.HiRedString, "Error adding watcher for settings:\t%s", err))
  316. }
  317. go func() {
  318. for {
  319. select {
  320. case event, ok := <-watcher.Events:
  321. if !ok {
  322. return
  323. }
  324. if event.Op&fsnotify.Write == fsnotify.Write {
  325. // It double-fires the event without time check, might depend on OS but this works anyways
  326. if time.Since(configReloadLastTime).Milliseconds() > 1 {
  327. time.Sleep(1 * time.Second)
  328. log.Println(lg("Settings", "Watcher", color.YellowString,
  329. "Detected changes in \"%s\", reloading...", configFile))
  330. loadConfig()
  331. allString := ""
  332. if config.All != nil {
  333. allString = ", ALL ENABLED"
  334. }
  335. log.Println(lg("Settings", "Watcher", color.HiYellowString,
  336. "Reloaded - bound to %d channel%s, %d categories, %d server%s, %d user%s%s",
  337. getBoundChannelsCount(), pluralS(getBoundChannelsCount()),
  338. getBoundCategoriesCount(),
  339. getBoundServersCount(), pluralS(getBoundServersCount()),
  340. getBoundUsersCount(), pluralS(getBoundUsersCount()), allString,
  341. ))
  342. updateDiscordPresence()
  343. }
  344. configReloadLastTime = time.Now()
  345. }
  346. case err, ok := <-watcher.Errors:
  347. if !ok {
  348. return
  349. }
  350. log.Println(color.HiRedString("[Watchers] Error:\t%s", err))
  351. }
  352. }
  353. }()
  354. //#endregion
  355. //#endregion
  356. // ~~~ RUNNING
  357. //#region Exit...
  358. signal.Notify(loop, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, os.Interrupt, os.Kill)
  359. <-loop
  360. sendStatusMessage(sendStatusExit)
  361. log.Println(lg("Discord", "", color.GreenString, "Logging out of discord..."))
  362. bot.Close()
  363. log.Println(lg("Database", "", color.YellowString, "Closing database..."))
  364. myDB.Close()
  365. log.Println(lg("Main", "", color.HiRedString, "Exiting... "))
  366. //#endregion
  367. }
  368. func openDatabase() {
  369. // Database
  370. log.Println(lg("Database", "", color.YellowString, "Opening database..."))
  371. myDB, err = db.OpenDB(databasePath)
  372. if err != nil {
  373. log.Println(lg("Database", "", color.HiRedString, "Unable to open database: %s", err))
  374. return
  375. }
  376. if myDB.Use("Downloads") == nil {
  377. log.Println(lg("Database", "Setup", color.YellowString, "Creating database, please wait..."))
  378. if err := myDB.Create("Downloads"); err != nil {
  379. log.Println(lg("Database", "Setup", color.HiRedString, "Error while trying to create database: %s", err))
  380. return
  381. }
  382. log.Println(lg("Database", "Setup", color.HiYellowString, "Created new database..."))
  383. log.Println(lg("Database", "Setup", color.YellowString, "Indexing database, please wait..."))
  384. if err := myDB.Use("Downloads").Index([]string{"URL"}); err != nil {
  385. log.Println(lg("Database", "Setup", color.HiRedString, "Unable to create database index for URL: %s", err))
  386. return
  387. }
  388. if err := myDB.Use("Downloads").Index([]string{"ChannelID"}); err != nil {
  389. log.Println(lg("Database", "Setup", color.HiRedString, "Unable to create database index for ChannelID: %s", err))
  390. return
  391. }
  392. if err := myDB.Use("Downloads").Index([]string{"UserID"}); err != nil {
  393. log.Println(lg("Database", "Setup", color.HiRedString, "Unable to create database index for UserID: %s", err))
  394. return
  395. }
  396. log.Println(lg("Database", "Setup", color.HiYellowString, "Created new indexes..."))
  397. }
  398. // Cache download tally
  399. cachedDownloadID = dbDownloadCount()
  400. log.Println(lg("Database", "", color.HiYellowString, "Database opened, contains %d entries...", cachedDownloadID))
  401. }
  402. func botLoad() {
  403. botLoadAPIs()
  404. botLoadDiscord()
  405. }
  406. func botLoadAPIs() {
  407. // Twitter API
  408. if config.Credentials.TwitterAccessToken != "" &&
  409. config.Credentials.TwitterAccessTokenSecret != "" &&
  410. config.Credentials.TwitterConsumerKey != "" &&
  411. config.Credentials.TwitterConsumerSecret != "" {
  412. log.Println(lg("API", "Twitter", color.MagentaString, "Connecting to API..."))
  413. twitterClient = anaconda.NewTwitterApiWithCredentials(
  414. config.Credentials.TwitterAccessToken,
  415. config.Credentials.TwitterAccessTokenSecret,
  416. config.Credentials.TwitterConsumerKey,
  417. config.Credentials.TwitterConsumerSecret,
  418. )
  419. twitterSelf, err := twitterClient.GetSelf(url.Values{})
  420. if err != nil {
  421. log.Println(lg("API", "Twitter", color.HiRedString, "API Login Error: %s", err.Error()))
  422. log.Println(lg("API", "Twitter", color.MagentaString,
  423. "Error encountered while connecting to API, the bot won't use the Twitter API."))
  424. } else {
  425. log.Println(lg("API", "Twitter", color.HiMagentaString,
  426. "Connected to API @%s", twitterSelf.ScreenName))
  427. twitterConnected = true
  428. }
  429. } else {
  430. log.Println(lg("API", "Twitter", color.MagentaString,
  431. "API credentials missing, the bot won't use the Twitter API."))
  432. }
  433. }
  434. func botLoadDiscord() {
  435. var err error
  436. // Discord Login
  437. connectBot := func() {
  438. // Connect Bot
  439. bot.LogLevel = -1 // to ignore dumb wsapi error
  440. err = bot.Open()
  441. if err != nil && !strings.Contains(strings.ToLower(err.Error()), "web socket already opened") {
  442. log.Println(lg("Discord", "", color.HiRedString, "Discord login failed:\t%s", err))
  443. properExit()
  444. }
  445. bot.LogLevel = config.DiscordLogLevel // reset
  446. bot.ShouldReconnectOnError = true
  447. dur, err := time.ParseDuration(fmt.Sprint(config.DiscordTimeout) + "s")
  448. if err != nil {
  449. dur, _ = time.ParseDuration("180s")
  450. }
  451. bot.Client.Timeout = dur
  452. bot.State.MaxMessageCount = 100000
  453. bot.State.TrackChannels = true
  454. bot.State.TrackThreads = true
  455. bot.State.TrackMembers = true
  456. bot.State.TrackThreadMembers = true
  457. botUser, err = bot.User("@me")
  458. if err != nil {
  459. botUser = bot.State.User
  460. }
  461. }
  462. if config.Credentials.Token != "" && config.Credentials.Token != placeholderToken {
  463. // Login via Token (Bot or User)
  464. log.Println(lg("Discord", "", color.GreenString, "Connecting to Discord via Token..."))
  465. // attempt login without Bot prefix
  466. bot, err = discordgo.New(config.Credentials.Token)
  467. connectBot()
  468. if botUser.Bot {
  469. // is bot application, reconnect properly
  470. log.Println(lg("Discord", "", color.GreenString, "Reconnecting as bot..."))
  471. bot, err = discordgo.New("Bot " + config.Credentials.Token)
  472. connectBot()
  473. }
  474. } else if (config.Credentials.Email != "" && config.Credentials.Email != placeholderEmail) &&
  475. (config.Credentials.Password != "" && config.Credentials.Password != placeholderPassword) {
  476. // Login via Email+Password (User Only obviously)
  477. log.Println(lg("Discord", "", color.GreenString, "Connecting via Login..."))
  478. bot, err = discordgo.New(config.Credentials.Email, config.Credentials.Password)
  479. } else {
  480. log.Println(lg("Discord", "", color.HiRedString, "No valid credentials for Discord..."))
  481. properExit()
  482. }
  483. if err != nil {
  484. log.Println(lg("Discord", "", color.HiRedString, "Error logging in: %s", err))
  485. properExit()
  486. }
  487. connectBot()
  488. // Fetch Bot's User Info
  489. botUser, err = bot.User("@me")
  490. if err != nil {
  491. botUser = bot.State.User
  492. if botUser == nil {
  493. log.Println(lg("Discord", "", color.HiRedString, "Error obtaining user details: %s", err))
  494. loop <- syscall.SIGINT
  495. }
  496. } else if botUser == nil {
  497. log.Println(lg("Discord", "", color.HiRedString, "No error encountered obtaining user details, but it's empty..."))
  498. loop <- syscall.SIGINT
  499. } else {
  500. botReady = true
  501. log.Println(lg("Discord", "", color.HiGreenString, "Logged into %s", getUserIdentifier(*botUser)))
  502. if botUser.Bot {
  503. log.Println(lg("Discord", "", color.MagentaString, "This is a genuine Discord Bot Application"))
  504. log.Println(lg("Discord", "", color.MagentaString, "- Presence details & state are disabled, only status will work."))
  505. log.Println(lg("Discord", "", color.MagentaString, "- The bot can only see servers you have added it to."))
  506. } else {
  507. log.Println(lg("Discord", "", color.MagentaString, "This is a User Account (Self-Bot)"))
  508. log.Println(lg("Discord", "", color.MagentaString, "- Discord does not allow Automated User Accounts (Self-Bots), so by using this bot you potentially risk account termination."))
  509. log.Println(lg("Discord", "", color.MagentaString, "- See GitHub page for link to Discord's official statement."))
  510. log.Println(lg("Discord", "", color.MagentaString, "- If you wish to avoid this, use a Bot Application if possible."))
  511. }
  512. }
  513. if bot.State.User != nil { // is selfbot
  514. selfbot = bot.State.User.Email != ""
  515. }
  516. // Event Handlers
  517. botCommands = handleCommands()
  518. bot.AddHandler(messageCreate)
  519. bot.AddHandler(messageUpdate)
  520. // Source Validation
  521. if config.Debug {
  522. log.Println(lg("Discord", "Validation", color.HiYellowString, "Validating configured sources..."))
  523. }
  524. //-
  525. if config.AdminChannels != nil {
  526. for _, adminChannel := range config.AdminChannels {
  527. if adminChannel.ChannelIDs != nil {
  528. for _, subchannel := range *adminChannel.ChannelIDs {
  529. _, err := bot.State.Channel(subchannel)
  530. if err != nil {
  531. invalidAdminChannels = append(invalidAdminChannels, subchannel)
  532. log.Println(lg("Discord", "Validation", color.HiRedString,
  533. "Bot cannot access admin subchannel %s...\t%s", subchannel, err))
  534. }
  535. }
  536. } else {
  537. _, err := bot.State.Channel(adminChannel.ChannelID)
  538. if err != nil {
  539. invalidAdminChannels = append(invalidAdminChannels, adminChannel.ChannelID)
  540. log.Println(lg("Discord", "Validation", color.HiRedString,
  541. "Bot cannot access admin channel %s...\t%s", adminChannel.ChannelID, err))
  542. }
  543. }
  544. }
  545. }
  546. //-
  547. for _, server := range config.Servers {
  548. if server.ServerIDs != nil {
  549. for _, subserver := range *server.ServerIDs {
  550. _, err := bot.State.Guild(subserver)
  551. if err != nil {
  552. invalidServers = append(invalidServers, subserver)
  553. log.Println(lg("Discord", "Validation", color.HiRedString,
  554. "Bot cannot access subserver %s...\t%s", subserver, err))
  555. }
  556. }
  557. } else {
  558. _, err := bot.State.Guild(server.ServerID)
  559. if err != nil {
  560. invalidServers = append(invalidServers, server.ServerID)
  561. log.Println(lg("Discord", "Validation", color.HiRedString,
  562. "Bot cannot access server %s...\t%s", server.ServerID, err))
  563. }
  564. }
  565. }
  566. for _, category := range config.Categories {
  567. if category.CategoryIDs != nil {
  568. for _, subcategory := range *category.CategoryIDs {
  569. _, err := bot.State.Channel(subcategory)
  570. if err != nil {
  571. invalidCategories = append(invalidCategories, subcategory)
  572. log.Println(lg("Discord", "Validation", color.HiRedString,
  573. "Bot cannot access subcategory %s...\t%s", subcategory, err))
  574. }
  575. }
  576. } else {
  577. _, err := bot.State.Channel(category.CategoryID)
  578. if err != nil {
  579. invalidCategories = append(invalidCategories, category.CategoryID)
  580. log.Println(lg("Discord", "Validation", color.HiRedString,
  581. "Bot cannot access category %s...\t%s", category.CategoryID, err))
  582. }
  583. }
  584. }
  585. for _, channel := range config.Channels {
  586. if channel.ChannelIDs != nil {
  587. for _, subchannel := range *channel.ChannelIDs {
  588. _, err := bot.State.Channel(subchannel)
  589. if err != nil {
  590. invalidChannels = append(invalidChannels, subchannel)
  591. log.Println(lg("Discord", "Validation", color.HiRedString,
  592. "Bot cannot access subchannel %s...\t%s", subchannel, err))
  593. }
  594. }
  595. } else {
  596. _, err := bot.State.Channel(channel.ChannelID)
  597. if err != nil {
  598. invalidChannels = append(invalidChannels, channel.ChannelID)
  599. log.Println(lg("Discord", "Validation", color.HiRedString,
  600. "Bot cannot access channel %s...\t%s", channel.ChannelID, err))
  601. }
  602. }
  603. }
  604. //-
  605. invalidSources := len(invalidAdminChannels) + len(invalidChannels) + len(invalidCategories) + len(invalidServers)
  606. if invalidSources > 0 {
  607. log.Println(lg("Discord", "Validation", color.HiRedString,
  608. "Found %d invalid channels/servers in configuration...", invalidSources))
  609. logMsg := fmt.Sprintf("Validation found %d invalid sources...\n", invalidSources)
  610. if len(invalidAdminChannels) > 0 {
  611. logMsg += fmt.Sprintf("\n**- Admin Channels: (%d)** - %s",
  612. len(invalidAdminChannels), strings.Join(invalidAdminChannels, ", "))
  613. }
  614. if len(invalidServers) > 0 {
  615. logMsg += fmt.Sprintf("\n**- Download Servers: (%d)** - %s",
  616. len(invalidServers), strings.Join(invalidServers, ", "))
  617. }
  618. if len(invalidCategories) > 0 {
  619. logMsg += fmt.Sprintf("\n**- Download Categories: (%d)** - %s",
  620. len(invalidCategories), strings.Join(invalidCategories, ", "))
  621. }
  622. if len(invalidChannels) > 0 {
  623. logMsg += fmt.Sprintf("\n**- Download Channels: (%d)** - %s",
  624. len(invalidChannels), strings.Join(invalidChannels, ", "))
  625. }
  626. sendErrorMessage(logMsg)
  627. } else if config.Debug {
  628. log.Println(lg("Discord", "Validation", color.HiGreenString, "All sources successfully validated!"))
  629. }
  630. // Start Presence
  631. timeLastUpdated = time.Now()
  632. updateDiscordPresence()
  633. }