main.go 28 KB

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