main.go 28 KB

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