main.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "net/url"
  7. "os"
  8. "os/signal"
  9. "runtime"
  10. "strings"
  11. "sync"
  12. "syscall"
  13. "time"
  14. "github.com/ChimeraCoder/anaconda"
  15. "github.com/Davincible/goinsta/v3"
  16. "github.com/HouzuoGuo/tiedot/db"
  17. "github.com/Necroforger/dgrouter/exrouter"
  18. "github.com/bwmarrin/discordgo"
  19. "github.com/fatih/color"
  20. "github.com/fsnotify/fsnotify"
  21. "github.com/hako/durafmt"
  22. orderedmap "github.com/wk8/go-ordered-map/v2"
  23. )
  24. var (
  25. err error
  26. mainWg sync.WaitGroup
  27. // Bot
  28. bot *discordgo.Session
  29. botUser *discordgo.User
  30. botCommands *exrouter.Route
  31. selfbot bool = false
  32. botReady bool = false
  33. // Storage
  34. myDB *db.DB
  35. // APIs
  36. twitterConnected bool = false
  37. twitterClient *anaconda.TwitterApi
  38. instagramConnected bool = false
  39. instagramClient *goinsta.Instagram
  40. // Gen
  41. loop chan os.Signal
  42. startTime time.Time
  43. timeLastUpdated time.Time
  44. timeLastDownload time.Time
  45. timeLastMessage time.Time
  46. cachedDownloadID int
  47. configReloadLastTime time.Time
  48. // Validation
  49. invalidAdminChannels []string
  50. invalidChannels []string
  51. invalidCategories []string
  52. invalidServers []string
  53. )
  54. const (
  55. imgurClientID = "08af502a9e70d65"
  56. sneakyUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
  57. )
  58. func versions(multiline bool) string {
  59. if multiline {
  60. return fmt.Sprintf("%s/%s / %s\ndiscordgo v%s (API v%s)",
  61. runtime.GOOS, runtime.GOARCH, runtime.Version(), discordgo.VERSION, discordgo.APIVersion)
  62. } else {
  63. return fmt.Sprintf("%s/%s / %s / discordgo v%s (API v%s)",
  64. runtime.GOOS, runtime.GOARCH, runtime.Version(), discordgo.VERSION, discordgo.APIVersion)
  65. }
  66. }
  67. func init() {
  68. log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
  69. log.SetOutput(color.Output)
  70. log.Println(color.HiCyanString(wrapHyphensW(fmt.Sprintf("Welcome to %s v%s", projectName, projectVersion))))
  71. loop = make(chan os.Signal, 1)
  72. startTime = time.Now()
  73. historyJobs = orderedmap.New[string, historyJob]()
  74. if len(os.Args) > 1 {
  75. configFileBase = os.Args[1]
  76. }
  77. log.Println(lg("Version", "", color.CyanString, versions(false)))
  78. // Github Update Check
  79. if config.GithubUpdateChecking {
  80. if !isLatestGithubRelease() {
  81. log.Println(lg("Version", "UPDATE", color.HiCyanString, "***\tUPDATE AVAILABLE\t***"))
  82. log.Println(lg("Version", "UPDATE", color.CyanString, projectReleaseURL))
  83. log.Println(lg("Version", "UPDATE", color.HiCyanString, "*** See changelog for information ***"))
  84. log.Println(lg("Version", "UPDATE", color.HiCyanString, "CHECK ALL CHANGELOGS SINCE YOUR LAST UPDATE"))
  85. log.Println(lg("Version", "UPDATE", color.HiCyanString, "SOME SETTINGS MAY NEED TO BE UPDATED"))
  86. time.Sleep(5 * time.Second)
  87. }
  88. }
  89. log.Println(lg("Info", "", color.HiCyanString, "** Need help? Discord: https://discord.gg/6Z6FJZVaDV **"))
  90. }
  91. func main() {
  92. mainWg.Add(2)
  93. go loadConfig()
  94. time.Sleep(1 * time.Second)
  95. go openDatabase()
  96. // Regex
  97. mainWg.Add(1)
  98. go func() {
  99. if err = compileRegex(); err != nil {
  100. log.Println(lg("Regex", "", color.HiRedString, "Error initializing:\t%s", err))
  101. return
  102. }
  103. mainWg.Done()
  104. }()
  105. mainWg.Wait() // wait because credentials from json
  106. mainWg.Add(2)
  107. go botLoadAPIs()
  108. go botLoadDiscord()
  109. mainWg.Wait()
  110. //#region MAIN STARTUP COMPLETE
  111. if config.Debug {
  112. log.Println(lg("Main", "", color.YellowString, "Startup finished, took %s...", uptime()))
  113. }
  114. log.Println(lg("Main", "", color.HiCyanString,
  115. wrapHyphensW(fmt.Sprintf("%s v%s is online with access to %d server%s",
  116. projectLabel, projectVersion, len(bot.State.Guilds), pluralS(len(bot.State.Guilds))))))
  117. log.Println(lg("Main", "", color.RedString, "CTRL+C to exit..."))
  118. // Log Status
  119. go sendStatusMessage(sendStatusStartup)
  120. //#endregion
  121. //#region BG Tasks
  122. //#region BG Tasks - Tickers
  123. tickerCheckup := time.NewTicker(time.Duration(config.CheckupRate) * time.Minute)
  124. tickerPresence := time.NewTicker(time.Duration(config.PresenceRefreshRate) * time.Minute)
  125. tickerConnection := time.NewTicker(time.Duration(config.ConnectionCheckRate) * time.Minute)
  126. go func() {
  127. for {
  128. select {
  129. case <-tickerCheckup.C:
  130. if config.Debug {
  131. //MARKER: history jobs polled for waiting count in checkup
  132. historyJobsWaiting := 0
  133. if historyJobs.Len() > 0 {
  134. for jobPair := historyJobs.Oldest(); jobPair != nil; jobPair.Next() {
  135. job := jobPair.Value
  136. if job.Status == historyStatusWaiting {
  137. historyJobsWaiting++
  138. }
  139. }
  140. }
  141. str := fmt.Sprintf("... %dms latency,\t\tlast discord heartbeat %s ago,\t\t%s uptime",
  142. bot.HeartbeatLatency().Milliseconds(),
  143. shortenTime(durafmt.ParseShort(time.Since(bot.LastHeartbeatSent)).String()),
  144. shortenTime(durafmt.ParseShort(time.Since(startTime)).String()))
  145. if !timeLastMessage.IsZero() {
  146. str += fmt.Sprintf(",\tlast message %s ago",
  147. shortenTime(durafmt.ParseShort(time.Since(timeLastMessage)).String()))
  148. }
  149. if !timeLastDownload.IsZero() {
  150. str += fmt.Sprintf(",\tlast download %s ago",
  151. shortenTime(durafmt.ParseShort(time.Since(timeLastDownload)).String()))
  152. }
  153. if historyJobsWaiting > 0 {
  154. str += fmt.Sprintf(",\t%d history jobs waiting", historyJobsWaiting)
  155. }
  156. log.Println(lg("Checkup", "", color.YellowString, str))
  157. }
  158. case <-tickerPresence.C:
  159. // If bot experiences connection interruption the status will go blank until updated by message, this fixes that
  160. go updateDiscordPresence()
  161. case <-tickerConnection.C:
  162. doReconnect := func() {
  163. log.Println(lg("Discord", "", color.YellowString, "Closing Discord connections..."))
  164. bot.Client.CloseIdleConnections()
  165. bot.CloseWithCode(1001)
  166. bot = nil
  167. log.Println(lg("Discord", "", color.RedString, "Discord connections closed!"))
  168. time.Sleep(15 * time.Second)
  169. if config.ExitOnBadConnection {
  170. properExit()
  171. } else {
  172. log.Println(lg("Discord", "", color.GreenString, "Logging in..."))
  173. botLoad()
  174. log.Println(lg("Discord", "", color.HiGreenString,
  175. "Reconnected! The bot *should* resume working..."))
  176. // Log Status
  177. sendStatusMessage(sendStatusReconnect)
  178. }
  179. }
  180. gate, err := bot.Gateway()
  181. if err != nil || gate == "" {
  182. log.Println(lg("Discord", "", color.HiYellowString,
  183. "Bot encountered a gateway error: GATEWAY: %s,\tERR: %s", gate, err))
  184. doReconnect()
  185. } else if time.Since(bot.LastHeartbeatAck).Seconds() > 4*60 {
  186. log.Println(lg("Discord", "", color.HiYellowString,
  187. "Bot has not received a heartbeat from Discord in 4 minutes..."))
  188. doReconnect()
  189. }
  190. }
  191. }
  192. }()
  193. //#endregion
  194. //#region BG Tasks - Autorun History
  195. type arh struct{ channel, before, since string }
  196. var autoHistoryChannels []arh
  197. // Compile list of channels to autorun history
  198. for _, channel := range getAllRegisteredChannels() {
  199. channelConfig := getSource(&discordgo.Message{ChannelID: channel})
  200. if channelConfig.AutoHistory != nil {
  201. if *channelConfig.AutoHistory {
  202. var autoHistoryChannel arh
  203. autoHistoryChannel.channel = channel
  204. autoHistoryChannel.before = *channelConfig.AutoHistoryBefore
  205. autoHistoryChannel.since = *channelConfig.AutoHistorySince
  206. autoHistoryChannels = append(autoHistoryChannels, autoHistoryChannel)
  207. }
  208. continue
  209. }
  210. }
  211. // Process auto history
  212. for _, ah := range autoHistoryChannels {
  213. //MARKER: history jobs queued from auto
  214. if job, exists := historyJobs.Get(ah.channel); !exists ||
  215. (job.Status != historyStatusRunning && job.Status != historyStatusAbortRequested) {
  216. job.Status = historyStatusWaiting
  217. job.OriginChannel = "AUTORUN"
  218. job.OriginUser = "AUTORUN"
  219. job.TargetCommandingMessage = nil
  220. job.TargetChannelID = ah.channel
  221. job.TargetBefore = ah.before
  222. job.TargetSince = ah.since
  223. job.Updated = time.Now()
  224. job.Added = time.Now()
  225. historyJobs.Set(ah.channel, job)
  226. //TODO: signals for this and typical history cmd??
  227. }
  228. }
  229. if len(autoHistoryChannels) > 0 {
  230. log.Println(lg("History", "Autorun", color.HiYellowString,
  231. "History Autoruns completed (for %d channel%s)",
  232. len(autoHistoryChannels), pluralS(len(autoHistoryChannels))))
  233. log.Println(lg("History", "Autorun", color.CyanString,
  234. "Waiting for something else to do..."))
  235. }
  236. //#endregion
  237. //#region BG Tasks - History Job Processing
  238. go func() {
  239. for {
  240. // Empty Local Cache
  241. nhistoryJobCnt,
  242. nhistoryJobCntWaiting,
  243. nhistoryJobCntRunning,
  244. nhistoryJobCntAborted,
  245. nhistoryJobCntErrored,
  246. nhistoryJobCntCompleted := historyJobs.Len(), 0, 0, 0, 0, 0
  247. //MARKER: history jobs launch
  248. // do we even bother?
  249. if nhistoryJobCnt > 0 {
  250. // New Cache
  251. for pair := historyJobs.Oldest(); pair != nil; pair = pair.Next() {
  252. job := pair.Value
  253. if job.Status == historyStatusWaiting {
  254. nhistoryJobCntWaiting++
  255. } else if job.Status == historyStatusRunning {
  256. nhistoryJobCntRunning++
  257. } else if job.Status == historyStatusAbortRequested || job.Status == historyStatusAbortCompleted {
  258. nhistoryJobCntAborted++
  259. } else if job.Status == historyStatusErrorReadMessageHistoryPerms || job.Status == historyStatusErrorRequesting {
  260. nhistoryJobCntErrored++
  261. } else if job.Status >= historyStatusCompletedNoMoreMessages {
  262. nhistoryJobCntCompleted++
  263. }
  264. }
  265. // Should Start New Job(s)?
  266. if nhistoryJobCntRunning < config.HistoryMaxJobs || config.HistoryMaxJobs < 1 {
  267. openSlots := config.HistoryMaxJobs - nhistoryJobCntRunning
  268. newJobs := make([]historyJob, openSlots)
  269. filledSlots := 0
  270. // Find Jobs
  271. for pair := historyJobs.Oldest(); pair != nil; pair = pair.Next() {
  272. if filledSlots == openSlots {
  273. break
  274. }
  275. if pair.Value.Status == historyStatusWaiting {
  276. newJobs = append(newJobs, pair.Value)
  277. filledSlots++
  278. }
  279. }
  280. // Start Jobs
  281. if len(newJobs) > 0 {
  282. for _, job := range newJobs {
  283. if job != (historyJob{}) {
  284. go handleHistory(job.TargetCommandingMessage, job.TargetChannelID, job.TargetBefore, job.TargetSince)
  285. }
  286. }
  287. }
  288. }
  289. }
  290. // Update Cache
  291. historyJobCnt = nhistoryJobCnt
  292. historyJobCntWaiting = nhistoryJobCntWaiting
  293. historyJobCntRunning = nhistoryJobCntRunning
  294. historyJobCntAborted = nhistoryJobCntAborted
  295. historyJobCntErrored = nhistoryJobCntErrored
  296. historyJobCntCompleted = nhistoryJobCntCompleted
  297. //~ optional setting?
  298. time.Sleep(5 * time.Second)
  299. }
  300. }()
  301. //#endregion
  302. //#region BG Tasks - Settings Watcher
  303. watcher, err := fsnotify.NewWatcher()
  304. if err != nil {
  305. log.Println(lg("Settings", "Watcher", color.HiRedString, "Error creating NewWatcher:\t%s", err))
  306. }
  307. defer watcher.Close()
  308. if err = watcher.Add(configFile); err != nil {
  309. log.Println(lg("Settings", "Watcher", color.HiRedString, "Error adding watcher for settings:\t%s", err))
  310. }
  311. go func() {
  312. for {
  313. select {
  314. case event, ok := <-watcher.Events:
  315. if !ok {
  316. return
  317. }
  318. if event.Op&fsnotify.Write == fsnotify.Write {
  319. // It double-fires the event without time check, might depend on OS but this works anyways
  320. if time.Since(configReloadLastTime).Milliseconds() > 1 {
  321. time.Sleep(1 * time.Second)
  322. log.Println(lg("Settings", "Watcher", color.YellowString,
  323. "Detected changes in \"%s\", reloading...", configFile))
  324. mainWg.Add(1)
  325. go loadConfig()
  326. allString := ""
  327. if config.All != nil {
  328. allString = ", ALL ENABLED"
  329. }
  330. log.Println(lg("Settings", "Watcher", color.HiYellowString,
  331. "Reloaded - bound to %d channel%s, %d categories, %d server%s, %d user%s%s",
  332. getBoundChannelsCount(), pluralS(getBoundChannelsCount()),
  333. getBoundCategoriesCount(),
  334. getBoundServersCount(), pluralS(getBoundServersCount()),
  335. getBoundUsersCount(), pluralS(getBoundUsersCount()), allString,
  336. ))
  337. go updateDiscordPresence()
  338. }
  339. configReloadLastTime = time.Now()
  340. }
  341. case err, ok := <-watcher.Errors:
  342. if !ok {
  343. return
  344. }
  345. log.Println(color.HiRedString("[Watchers] Error:\t%s", err))
  346. }
  347. }
  348. }()
  349. //#endregion
  350. //#endregion
  351. //#region Cache Constants
  352. constants := make(map[string]string)
  353. //--- Compile constants
  354. for _, server := range bot.State.Guilds {
  355. serverKey := fmt.Sprintf("SERVER_%s", stripSymbols(server.Name))
  356. serverKey = strings.ReplaceAll(serverKey, " ", "_")
  357. for strings.Contains(serverKey, "__") {
  358. serverKey = strings.ReplaceAll(serverKey, "__", "_")
  359. }
  360. serverKey = strings.ToUpper(serverKey)
  361. if constants[serverKey] == "" {
  362. constants[serverKey] = server.ID
  363. }
  364. for _, channel := range server.Channels {
  365. if channel.Type != discordgo.ChannelTypeGuildCategory {
  366. categoryName := ""
  367. if channel.ParentID != "" {
  368. channelParent, err := bot.State.Channel(channel.ParentID)
  369. if err == nil {
  370. categoryName = channelParent.Name
  371. }
  372. }
  373. channelKey := fmt.Sprintf("CHANNEL_%s_%s_%s",
  374. stripSymbols(server.Name), stripSymbols(categoryName), stripSymbols(channel.Name))
  375. channelKey = strings.ReplaceAll(channelKey, " ", "_")
  376. for strings.Contains(channelKey, "__") {
  377. channelKey = strings.ReplaceAll(channelKey, "__", "_")
  378. }
  379. channelKey = strings.ToUpper(channelKey)
  380. if constants[channelKey] == "" {
  381. constants[channelKey] = channel.ID
  382. }
  383. }
  384. }
  385. }
  386. //--- Save constants
  387. os.MkdirAll(cachePath, 0755)
  388. if _, err := os.Stat(constantsPath); err == nil {
  389. err = os.Remove(constantsPath)
  390. if err != nil {
  391. log.Println(lg("Constants", "", color.HiRedString, "Encountered error deleting cache file:\t%s", err))
  392. }
  393. }
  394. constantsStruct := constStruct{}
  395. constantsStruct.Constants = constants
  396. newJson, err := json.MarshalIndent(constantsStruct, "", "\t")
  397. if err != nil {
  398. log.Println(lg("Constants", "", color.HiRedString, "Failed to format constants...\t%s", err))
  399. } else {
  400. err := os.WriteFile(constantsPath, newJson, 0644)
  401. if err != nil {
  402. log.Println(lg("Constants", "", color.HiRedString, "Failed to save new constants file...\t%s", err))
  403. }
  404. }
  405. //#endregion
  406. // ~~~ RUNNING
  407. //#region ----------- TEST ENV / main
  408. //#endregion ------------------------
  409. //#region Exit...
  410. signal.Notify(loop, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, os.Interrupt, os.Kill)
  411. <-loop
  412. sendStatusMessage(sendStatusExit) // not goroutine because we want to wait to send this before logout
  413. log.Println(lg("Discord", "", color.GreenString, "Logging out of discord..."))
  414. bot.Close()
  415. log.Println(lg("Database", "", color.YellowString, "Closing database..."))
  416. myDB.Close()
  417. log.Println(lg("Main", "", color.HiRedString, "Exiting... "))
  418. //#endregion
  419. }
  420. func openDatabase() {
  421. // Database
  422. log.Println(lg("Database", "", color.YellowString, "Opening database...\t(this can take a second...)"))
  423. myDB, err = db.OpenDB(databasePath)
  424. if err != nil {
  425. log.Println(lg("Database", "", color.HiRedString, "Unable to open database: %s", err))
  426. return
  427. }
  428. if myDB.Use("Downloads") == nil {
  429. log.Println(lg("Database", "Setup", color.YellowString, "Creating database, please wait..."))
  430. if err := myDB.Create("Downloads"); err != nil {
  431. log.Println(lg("Database", "Setup", color.HiRedString, "Error while trying to create database: %s", err))
  432. return
  433. }
  434. log.Println(lg("Database", "Setup", color.HiYellowString, "Created new database..."))
  435. log.Println(lg("Database", "Setup", color.YellowString, "Indexing database, please wait..."))
  436. if err := myDB.Use("Downloads").Index([]string{"URL"}); err != nil {
  437. log.Println(lg("Database", "Setup", color.HiRedString, "Unable to create database index for URL: %s", err))
  438. return
  439. }
  440. if err := myDB.Use("Downloads").Index([]string{"ChannelID"}); err != nil {
  441. log.Println(lg("Database", "Setup", color.HiRedString, "Unable to create database index for ChannelID: %s", err))
  442. return
  443. }
  444. if err := myDB.Use("Downloads").Index([]string{"UserID"}); err != nil {
  445. log.Println(lg("Database", "Setup", color.HiRedString, "Unable to create database index for UserID: %s", err))
  446. return
  447. }
  448. log.Println(lg("Database", "Setup", color.HiYellowString, "Created new indexes..."))
  449. }
  450. // Cache download tally
  451. cachedDownloadID = dbDownloadCount()
  452. log.Println(lg("Database", "", color.HiYellowString, "Database opened, contains %d entries...", cachedDownloadID))
  453. mainWg.Done()
  454. }
  455. func botLoad() {
  456. mainWg.Add(1)
  457. botLoadAPIs()
  458. mainWg.Add(1)
  459. botLoadDiscord()
  460. }
  461. func botLoadAPIs() {
  462. // Twitter API
  463. if config.Credentials.TwitterAccessToken != "" &&
  464. config.Credentials.TwitterAccessTokenSecret != "" &&
  465. config.Credentials.TwitterConsumerKey != "" &&
  466. config.Credentials.TwitterConsumerSecret != "" {
  467. log.Println(lg("API", "Twitter", color.MagentaString, "Connecting to API..."))
  468. twitterLoginCount := 0
  469. do_twitter_login:
  470. twitterLoginCount++
  471. if twitterLoginCount > 1 {
  472. time.Sleep(3 * time.Second)
  473. }
  474. twitterClient = anaconda.NewTwitterApiWithCredentials(
  475. config.Credentials.TwitterAccessToken,
  476. config.Credentials.TwitterAccessTokenSecret,
  477. config.Credentials.TwitterConsumerKey,
  478. config.Credentials.TwitterConsumerSecret,
  479. )
  480. twitterSelf, err := twitterClient.GetSelf(url.Values{})
  481. if err != nil {
  482. log.Println(lg("API", "Twitter", color.HiRedString, "API Login Error: %s", err.Error()))
  483. if twitterLoginCount <= 3 {
  484. goto do_twitter_login
  485. } else {
  486. log.Println(lg("API", "Twitter", color.HiRedString,
  487. "Failed to login to Twitter API, the bot will not fetch tweet media..."))
  488. }
  489. } else {
  490. log.Println(lg("API", "Twitter", color.HiMagentaString,
  491. "Connected to API @%s", twitterSelf.ScreenName))
  492. twitterConnected = true
  493. }
  494. } else {
  495. log.Println(lg("API", "Twitter", color.MagentaString,
  496. "API credentials missing, the bot won't use the Twitter API."))
  497. }
  498. // Instagram API
  499. if config.Credentials.InstagramUsername != "" &&
  500. config.Credentials.InstagramPassword != "" {
  501. log.Println(lg("API", "Instagram", color.MagentaString, "Connecting to API..."))
  502. instagramLoginCount := 0
  503. do_instagram_login:
  504. instagramLoginCount++
  505. if instagramLoginCount > 1 {
  506. time.Sleep(3 * time.Second)
  507. }
  508. if instagramClient, err = goinsta.Import(instagramCachePath); err != nil {
  509. instagramClient = goinsta.New(config.Credentials.InstagramUsername, config.Credentials.InstagramPassword)
  510. if err := instagramClient.Login(); err != nil {
  511. log.Println(lg("API", "Instagram", color.HiRedString, "API Login Error: %s", err.Error()))
  512. if instagramLoginCount <= 3 {
  513. goto do_instagram_login
  514. } else {
  515. log.Println(lg("API", "Instagram", color.HiRedString,
  516. "Failed to login to Instagram API, the bot will not fetch tweet media..."))
  517. }
  518. } else {
  519. log.Println(lg("API", "Instagram", color.HiMagentaString,
  520. "Connected to API @%s via new login", instagramClient.Account.Username))
  521. instagramConnected = true
  522. }
  523. } else {
  524. log.Println(lg("API", "Instagram", color.HiMagentaString,
  525. "Connected to API @%s via cache", instagramClient.Account.Username))
  526. instagramConnected = true
  527. }
  528. defer instagramClient.Export(instagramCachePath)
  529. } else {
  530. log.Println(lg("API", "Instagram", color.MagentaString,
  531. "API credentials missing, the bot won't use the Instagram API."))
  532. }
  533. mainWg.Done()
  534. }
  535. func botLoadDiscord() {
  536. var err error
  537. // Discord Login
  538. connectBot := func() {
  539. // Event Handlers
  540. botCommands = handleCommands()
  541. bot.AddHandler(messageCreate)
  542. bot.AddHandler(messageUpdate)
  543. // Connect Bot
  544. bot.LogLevel = -1 // to ignore dumb wsapi error
  545. err = bot.Open()
  546. if err != nil && !strings.Contains(strings.ToLower(err.Error()), "web socket already opened") {
  547. log.Println(lg("Discord", "", color.HiRedString, "Discord login failed:\t%s", err))
  548. properExit()
  549. }
  550. bot.LogLevel = config.DiscordLogLevel // reset
  551. bot.ShouldReconnectOnError = true
  552. dur, err := time.ParseDuration(fmt.Sprint(config.DiscordTimeout) + "s")
  553. if err != nil {
  554. dur, _ = time.ParseDuration("180s")
  555. }
  556. bot.Client.Timeout = dur
  557. bot.State.MaxMessageCount = 100000
  558. bot.State.TrackChannels = true
  559. bot.State.TrackThreads = true
  560. bot.State.TrackMembers = true
  561. bot.State.TrackThreadMembers = true
  562. botUser, err = bot.User("@me")
  563. if err != nil {
  564. botUser = bot.State.User
  565. }
  566. }
  567. discord_login_count := 0
  568. do_discord_login:
  569. discord_login_count++
  570. if discord_login_count > 1 {
  571. time.Sleep(3 * time.Second)
  572. }
  573. if config.Credentials.Token != "" && config.Credentials.Token != placeholderToken {
  574. // Login via Token (Bot or User)
  575. log.Println(lg("Discord", "", color.GreenString, "Connecting to Discord via Token..."))
  576. // attempt login without Bot prefix
  577. bot, err = discordgo.New(config.Credentials.Token)
  578. connectBot()
  579. if botUser.Bot { // is bot application, reconnect properly
  580. //log.Println(lg("Discord", "", color.GreenString, "Reconnecting as bot..."))
  581. bot, err = discordgo.New("Bot " + config.Credentials.Token)
  582. connectBot()
  583. }
  584. } else if (config.Credentials.Email != "" && config.Credentials.Email != placeholderEmail) &&
  585. (config.Credentials.Password != "" && config.Credentials.Password != placeholderPassword) {
  586. // Login via Email+Password (User Only obviously)
  587. log.Println(lg("Discord", "", color.GreenString, "Connecting via Login..."))
  588. bot, err = discordgo.New(config.Credentials.Email, config.Credentials.Password)
  589. } else {
  590. if discord_login_count > 5 {
  591. log.Println(lg("Discord", "", color.HiRedString, "No valid credentials for Discord..."))
  592. properExit()
  593. } else {
  594. goto do_discord_login
  595. }
  596. }
  597. if err != nil {
  598. if discord_login_count > 5 {
  599. log.Println(lg("Discord", "", color.HiRedString, "Error logging in: %s", err))
  600. properExit()
  601. } else {
  602. goto do_discord_login
  603. }
  604. }
  605. connectBot()
  606. // Fetch Bot's User Info
  607. botUser, err = bot.User("@me")
  608. if err != nil {
  609. botUser = bot.State.User
  610. if botUser == nil {
  611. if discord_login_count > 5 {
  612. log.Println(lg("Discord", "", color.HiRedString, "Error obtaining user details: %s", err))
  613. properExit()
  614. } else {
  615. goto do_discord_login
  616. }
  617. }
  618. } else if botUser == nil {
  619. if discord_login_count > 5 {
  620. log.Println(lg("Discord", "", color.HiRedString, "No error encountered obtaining user details, but it's empty..."))
  621. properExit()
  622. } else {
  623. goto do_discord_login
  624. }
  625. } else {
  626. botReady = true
  627. log.Println(lg("Discord", "", color.HiGreenString, "Logged into %s", getUserIdentifier(*botUser)))
  628. if botUser.Bot {
  629. log.Println(lg("Discord", "", color.MagentaString, "This is a genuine Discord Bot Application"))
  630. log.Println(lg("Discord", "", color.MagentaString, "- Presence details & state are disabled, only status will work."))
  631. log.Println(lg("Discord", "", color.MagentaString, "- The bot can only see servers you have added it to."))
  632. log.Println(lg("Discord", "", color.MagentaString, "- Nothing is wrong, this is just info :)"))
  633. } else {
  634. log.Println(lg("Discord", "", color.MagentaString, "This is a User Account (Self-Bot)"))
  635. 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."))
  636. log.Println(lg("Discord", "", color.MagentaString, "- See GitHub page for link to Discord's official statement."))
  637. log.Println(lg("Discord", "", color.MagentaString, "- If you wish to avoid this, use a Bot Application if possible."))
  638. log.Println(lg("Discord", "", color.MagentaString, "- Nothing is wrong, this is just info :)"))
  639. }
  640. }
  641. if bot.State.User != nil { // is selfbot
  642. selfbot = bot.State.User.Email != ""
  643. }
  644. // Source Validation
  645. if config.Debug {
  646. log.Println(lg("Discord", "Validation", color.HiYellowString, "Validating configured sources..."))
  647. }
  648. //-
  649. if config.AdminChannels != nil {
  650. for _, adminChannel := range config.AdminChannels {
  651. if adminChannel.ChannelIDs != nil {
  652. for _, subchannel := range *adminChannel.ChannelIDs {
  653. _, err := bot.State.Channel(subchannel)
  654. if err != nil {
  655. invalidAdminChannels = append(invalidAdminChannels, subchannel)
  656. log.Println(lg("Discord", "Validation", color.HiRedString,
  657. "Bot cannot access admin subchannel %s...\t%s", subchannel, err))
  658. }
  659. }
  660. } else {
  661. _, err := bot.State.Channel(adminChannel.ChannelID)
  662. if err != nil {
  663. invalidAdminChannels = append(invalidAdminChannels, adminChannel.ChannelID)
  664. log.Println(lg("Discord", "Validation", color.HiRedString,
  665. "Bot cannot access admin channel %s...\t%s", adminChannel.ChannelID, err))
  666. }
  667. }
  668. }
  669. }
  670. //-
  671. for _, server := range config.Servers {
  672. if server.ServerIDs != nil {
  673. for _, subserver := range *server.ServerIDs {
  674. _, err := bot.State.Guild(subserver)
  675. if err != nil {
  676. invalidServers = append(invalidServers, subserver)
  677. log.Println(lg("Discord", "Validation", color.HiRedString,
  678. "Bot cannot access subserver %s...\t%s", subserver, err))
  679. }
  680. }
  681. } else {
  682. _, err := bot.State.Guild(server.ServerID)
  683. if err != nil {
  684. invalidServers = append(invalidServers, server.ServerID)
  685. log.Println(lg("Discord", "Validation", color.HiRedString,
  686. "Bot cannot access server %s...\t%s", server.ServerID, err))
  687. }
  688. }
  689. }
  690. for _, category := range config.Categories {
  691. if category.CategoryIDs != nil {
  692. for _, subcategory := range *category.CategoryIDs {
  693. _, err := bot.State.Channel(subcategory)
  694. if err != nil {
  695. invalidCategories = append(invalidCategories, subcategory)
  696. log.Println(lg("Discord", "Validation", color.HiRedString,
  697. "Bot cannot access subcategory %s...\t%s", subcategory, err))
  698. }
  699. }
  700. } else {
  701. _, err := bot.State.Channel(category.CategoryID)
  702. if err != nil {
  703. invalidCategories = append(invalidCategories, category.CategoryID)
  704. log.Println(lg("Discord", "Validation", color.HiRedString,
  705. "Bot cannot access category %s...\t%s", category.CategoryID, err))
  706. }
  707. }
  708. }
  709. for _, channel := range config.Channels {
  710. if channel.ChannelIDs != nil {
  711. for _, subchannel := range *channel.ChannelIDs {
  712. _, err := bot.State.Channel(subchannel)
  713. if err != nil {
  714. invalidChannels = append(invalidChannels, subchannel)
  715. log.Println(lg("Discord", "Validation", color.HiRedString,
  716. "Bot cannot access subchannel %s...\t%s", subchannel, err))
  717. }
  718. }
  719. } else {
  720. _, err := bot.State.Channel(channel.ChannelID)
  721. if err != nil {
  722. invalidChannels = append(invalidChannels, channel.ChannelID)
  723. log.Println(lg("Discord", "Validation", color.HiRedString,
  724. "Bot cannot access channel %s...\t%s", channel.ChannelID, err))
  725. }
  726. }
  727. }
  728. //-
  729. invalidSources := len(invalidAdminChannels) + len(invalidChannels) + len(invalidCategories) + len(invalidServers)
  730. if invalidSources > 0 {
  731. log.Println(lg("Discord", "Validation", color.HiRedString,
  732. "Found %d invalid channels/servers in configuration...", invalidSources))
  733. logMsg := fmt.Sprintf("Validation found %d invalid sources...\n", invalidSources)
  734. if len(invalidAdminChannels) > 0 {
  735. logMsg += fmt.Sprintf("\n**- Admin Channels: (%d)** - %s",
  736. len(invalidAdminChannels), strings.Join(invalidAdminChannels, ", "))
  737. }
  738. if len(invalidServers) > 0 {
  739. logMsg += fmt.Sprintf("\n**- Download Servers: (%d)** - %s",
  740. len(invalidServers), strings.Join(invalidServers, ", "))
  741. }
  742. if len(invalidCategories) > 0 {
  743. logMsg += fmt.Sprintf("\n**- Download Categories: (%d)** - %s",
  744. len(invalidCategories), strings.Join(invalidCategories, ", "))
  745. }
  746. if len(invalidChannels) > 0 {
  747. logMsg += fmt.Sprintf("\n**- Download Channels: (%d)** - %s",
  748. len(invalidChannels), strings.Join(invalidChannels, ", "))
  749. }
  750. sendErrorMessage(logMsg)
  751. } else if config.Debug {
  752. log.Println(lg("Discord", "Validation", color.HiGreenString, "All sources successfully validated!"))
  753. }
  754. // Start Presence
  755. timeLastUpdated = time.Now()
  756. go updateDiscordPresence()
  757. mainWg.Done()
  758. }