downloads.go 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299
  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "image"
  6. "io"
  7. "io/fs"
  8. "log"
  9. "math/rand"
  10. "mime"
  11. "net/http"
  12. "net/url"
  13. "os"
  14. "path/filepath"
  15. "sort"
  16. "strconv"
  17. "strings"
  18. "time"
  19. "github.com/bwmarrin/discordgo"
  20. "github.com/dustin/go-humanize"
  21. "github.com/fatih/color"
  22. "github.com/hako/durafmt"
  23. "github.com/rivo/duplo"
  24. "mvdan.cc/xurls/v2"
  25. )
  26. type downloadItem struct {
  27. URL string
  28. Time time.Time
  29. Destination string
  30. Filename string
  31. ChannelID string
  32. UserID string
  33. }
  34. type downloadStatus int
  35. const (
  36. downloadSuccess downloadStatus = iota
  37. downloadIgnored
  38. downloadSkipped
  39. downloadSkippedDuplicate
  40. downloadSkippedUnpermittedDomain
  41. downloadSkippedUnpermittedExtension
  42. downloadSkippedUnpermittedFilename
  43. downloadSkippedUnpermittedReaction
  44. downloadSkippedUnpermittedType
  45. downloadSkippedDetectedDuplicate
  46. downloadFailed
  47. downloadFailedCode
  48. downloadFailedCode403
  49. downloadFailedCode404
  50. downloadFailedInvalidSource
  51. downloadFailedInvalidPath
  52. downloadFailedCreatingFolder
  53. downloadFailedRequesting
  54. downloadFailedDownloadingResponse
  55. downloadFailedReadResponse
  56. downloadFailedCreatingSubfolder
  57. downloadFailedWritingFile
  58. downloadFailedWritingDatabase
  59. )
  60. type downloadStatusStruct struct {
  61. Status downloadStatus
  62. Error error
  63. }
  64. func mDownloadStatus(status downloadStatus, _error ...error) downloadStatusStruct {
  65. if len(_error) == 0 {
  66. return downloadStatusStruct{
  67. Status: status,
  68. Error: nil,
  69. }
  70. }
  71. return downloadStatusStruct{
  72. Status: status,
  73. Error: _error[0],
  74. }
  75. }
  76. func getDownloadStatusString(status downloadStatus) string {
  77. switch status {
  78. case downloadSuccess:
  79. return "Succeeded"
  80. //
  81. case downloadIgnored:
  82. return "Ignored"
  83. //
  84. case downloadSkipped:
  85. return "Skipped"
  86. case downloadSkippedDuplicate:
  87. return "Skipped - Duplicate"
  88. case downloadSkippedUnpermittedDomain:
  89. return "Skipped - Unpermitted Domain"
  90. case downloadSkippedUnpermittedExtension:
  91. return "Skipped - Unpermitted File Extension"
  92. case downloadSkippedUnpermittedFilename:
  93. return "Skipped - Unpermitted Filename Content"
  94. case downloadSkippedUnpermittedReaction:
  95. return "Skipped - Unpermitted Message Reaction"
  96. case downloadSkippedUnpermittedType:
  97. return "Skipped - Unpermitted File Type"
  98. case downloadSkippedDetectedDuplicate:
  99. return "Skipped - Detected Duplicate"
  100. //
  101. case downloadFailed:
  102. return "Failed"
  103. case downloadFailedCode:
  104. return "Failed - BAD CONNECTION"
  105. case downloadFailedCode403:
  106. return "Failed - 403 UNAVAILABLE"
  107. case downloadFailedCode404:
  108. return "Failed - 404 NOT FOUND"
  109. case downloadFailedInvalidSource:
  110. return "Failed - Invalid Source"
  111. case downloadFailedInvalidPath:
  112. return "Failed - Invalid Path"
  113. case downloadFailedCreatingFolder:
  114. return "Failed - Error Creating Folder"
  115. case downloadFailedRequesting:
  116. return "Failed - Error Requesting"
  117. case downloadFailedDownloadingResponse:
  118. return "Failed - Error Downloading Data"
  119. case downloadFailedReadResponse:
  120. return "Failed - Error Reading Data"
  121. case downloadFailedCreatingSubfolder:
  122. return "Failed - Error Mapping Subfolder(s)"
  123. case downloadFailedWritingFile:
  124. return "Failed - Error Saving File"
  125. case downloadFailedWritingDatabase:
  126. return "Failed - Error Saving to Database"
  127. }
  128. return "Unknown Error"
  129. }
  130. // Trim duplicate links in link list
  131. func trimDuplicateLinks(fileItems []*fileItem) []*fileItem {
  132. var result []*fileItem
  133. seen := map[string]bool{}
  134. for _, item := range fileItems {
  135. if seen[item.Link] {
  136. continue
  137. }
  138. seen[item.Link] = true
  139. result = append(result, item)
  140. }
  141. return result
  142. }
  143. // Trim files already downloaded and stored in database
  144. func trimDownloadedLinks(linkList map[string]string, m *discordgo.Message) map[string]string {
  145. channelConfig := getSource(m)
  146. newList := make(map[string]string, 0)
  147. for link, filename := range linkList {
  148. downloadedFiles := dbFindDownloadByURL(link)
  149. alreadyDownloaded := false
  150. for _, downloadedFile := range downloadedFiles {
  151. if downloadedFile.ChannelID == m.ChannelID {
  152. alreadyDownloaded = true
  153. }
  154. }
  155. if !alreadyDownloaded || *channelConfig.SavePossibleDuplicates {
  156. newList[link] = filename
  157. } else if config.Debug {
  158. log.Println(lg("Download", "SKIP", color.GreenString, "Found URL has already been downloaded for this channel: %s", link))
  159. }
  160. }
  161. return newList
  162. }
  163. func getRawLinks(m *discordgo.Message) []*fileItem {
  164. var links []*fileItem
  165. if m.Author == nil {
  166. m.Author = new(discordgo.User)
  167. }
  168. for _, attachment := range m.Attachments {
  169. links = append(links, &fileItem{
  170. Link: attachment.URL,
  171. Filename: attachment.Filename,
  172. })
  173. }
  174. foundLinks := xurls.Strict().FindAllString(m.Content, -1)
  175. for _, foundLink := range foundLinks {
  176. links = append(links, &fileItem{
  177. Link: foundLink,
  178. })
  179. }
  180. for _, embed := range m.Embeds {
  181. if embed.URL != "" {
  182. links = append(links, &fileItem{
  183. Link: embed.URL,
  184. })
  185. }
  186. // Removing for now as this causes it to try and pull shit from things like YouTube descriptions
  187. /*if embed.Description != "" {
  188. foundLinks = xurls.Strict().FindAllString(embed.Description, -1)
  189. for _, foundLink := range foundLinks {
  190. links = append(links, &fileItem{
  191. Link: foundLink,
  192. })
  193. }
  194. }*/
  195. if embed.Image != nil && embed.Image.URL != "" {
  196. links = append(links, &fileItem{
  197. Link: embed.Image.URL,
  198. })
  199. }
  200. if embed.Video != nil && embed.Video.URL != "" {
  201. links = append(links, &fileItem{
  202. Link: embed.Video.URL,
  203. })
  204. }
  205. }
  206. return links
  207. }
  208. func getDownloadLinks(inputURL string, m *discordgo.Message) map[string]string {
  209. /* TODO: Download Support...
  210. - TikTok: Tried, once the connection is closed the cdn URL is rendered invalid
  211. - Facebook Photos: Tried, it doesn't preload image data, it's loaded in after. Would have to keep connection open, find alternative way to grab, or use api.
  212. - Facebook Videos: Previously supported but they split mp4 into separate audio and video streams
  213. */
  214. if twitterConnected {
  215. inputURL = strings.ReplaceAll(inputURL, "mobile.twitter", "twitter")
  216. inputURL = strings.ReplaceAll(inputURL, "fxtwitter.com", "twitter.com")
  217. inputURL = strings.ReplaceAll(inputURL, "c.vxtwitter.com", "twitter.com")
  218. inputURL = strings.ReplaceAll(inputURL, "vxtwitter.com", "twitter.com")
  219. if regexUrlTwitter.MatchString(inputURL) {
  220. links, err := getTwitterUrls(inputURL)
  221. if err != nil {
  222. if !strings.Contains(err.Error(), "suspended") {
  223. log.Println(lg("Download", "", color.RedString, "Twitter Media fetch failed for %s -- %s", inputURL, err))
  224. }
  225. } else if len(links) > 0 {
  226. return trimDownloadedLinks(links, m)
  227. }
  228. }
  229. if regexUrlTwitterStatus.MatchString(inputURL) {
  230. links, err := getTwitterStatusUrls(inputURL, m)
  231. if err != nil {
  232. if !strings.Contains(err.Error(), "suspended") && !strings.Contains(err.Error(), "No status found") {
  233. log.Println(lg("Download", "", color.RedString, "Twitter Status fetch failed for %s -- %s", inputURL, err))
  234. }
  235. } else if len(links) > 0 {
  236. return trimDownloadedLinks(links, m)
  237. }
  238. }
  239. }
  240. if instagramConnected {
  241. if regexUrlInstagram.MatchString(inputURL) || regexUrlInstagramReel.MatchString(inputURL) {
  242. if strings.Contains(inputURL, "?") {
  243. inputURL = inputURL[:strings.Index(inputURL, "?")]
  244. }
  245. links, err := getInstagramUrls(inputURL, m)
  246. if err != nil {
  247. log.Println(lg("Download", "", color.RedString, "Instagram media fetch failed for %s -- %s", inputURL, err))
  248. } else if len(links) > 0 {
  249. return trimDownloadedLinks(links, m)
  250. }
  251. }
  252. }
  253. if regexUrlImgurSingle.MatchString(inputURL) {
  254. links, err := getImgurSingleUrls(inputURL)
  255. if err != nil {
  256. log.Println(lg("Download", "", color.RedString, "Imgur Media fetch failed for %s -- %s", inputURL, err))
  257. } else if len(links) > 0 {
  258. return trimDownloadedLinks(links, m)
  259. }
  260. }
  261. if regexUrlImgurAlbum.MatchString(inputURL) {
  262. links, err := getImgurAlbumUrls(inputURL)
  263. if err != nil {
  264. log.Println(lg("Download", "", color.RedString, "Imgur Album fetch failed for %s -- %s", inputURL, err))
  265. } else if len(links) > 0 {
  266. return trimDownloadedLinks(links, m)
  267. }
  268. }
  269. if regexUrlStreamable.MatchString(inputURL) {
  270. links, err := getStreamableUrls(inputURL)
  271. if err != nil {
  272. log.Println(lg("Download", "", color.RedString, "Streamable fetch failed for %s -- %s", inputURL, err))
  273. } else if len(links) > 0 {
  274. return trimDownloadedLinks(links, m)
  275. }
  276. }
  277. if regexUrlGfycat.MatchString(inputURL) {
  278. links, err := getGfycatUrls(inputURL)
  279. if err != nil {
  280. log.Println(lg("Download", "", color.RedString, "Gfycat fetch failed for %s -- %s", inputURL, err))
  281. } else if len(links) > 0 {
  282. return trimDownloadedLinks(links, m)
  283. }
  284. }
  285. if regexUrlFlickrPhoto.MatchString(inputURL) {
  286. links, err := getFlickrPhotoUrls(inputURL)
  287. if err != nil {
  288. log.Println(lg("Download", "", color.RedString, "Flickr Photo fetch failed for %s -- %s", inputURL, err))
  289. } else if len(links) > 0 {
  290. return trimDownloadedLinks(links, m)
  291. }
  292. }
  293. if regexUrlFlickrAlbum.MatchString(inputURL) {
  294. links, err := getFlickrAlbumUrls(inputURL)
  295. if err != nil {
  296. log.Println(lg("Download", "", color.RedString, "Flickr Album fetch failed for %s -- %s", inputURL, err))
  297. } else if len(links) > 0 {
  298. return trimDownloadedLinks(links, m)
  299. }
  300. }
  301. if regexUrlFlickrAlbumShort.MatchString(inputURL) {
  302. links, err := getFlickrAlbumShortUrls(inputURL)
  303. if err != nil {
  304. log.Println(lg("Download", "", color.RedString, "Flickr Album (short) fetch failed for %s -- %s", inputURL, err))
  305. } else if len(links) > 0 {
  306. return trimDownloadedLinks(links, m)
  307. }
  308. }
  309. if regexUrlTistory.MatchString(inputURL) {
  310. links, err := getTistoryUrls(inputURL)
  311. if err != nil {
  312. log.Println(lg("Download", "", color.RedString, "Tistory URL failed for %s -- %s", inputURL, err))
  313. } else if len(links) > 0 {
  314. return trimDownloadedLinks(links, m)
  315. }
  316. }
  317. if regexUrlTistoryLegacy.MatchString(inputURL) {
  318. links, err := getLegacyTistoryUrls(inputURL)
  319. if err != nil {
  320. log.Println(lg("Download", "", color.RedString, "Legacy Tistory URL failed for %s -- %s", inputURL, err))
  321. } else if len(links) > 0 {
  322. return trimDownloadedLinks(links, m)
  323. }
  324. }
  325. if regexUrlRedditPost.MatchString(inputURL) {
  326. links, err := getRedditPostUrls(inputURL)
  327. if err != nil {
  328. log.Println(lg("Download", "", color.RedString, "Reddit Post URL failed for %s -- %s", inputURL, err))
  329. } else if len(links) > 0 {
  330. return trimDownloadedLinks(links, m)
  331. }
  332. }
  333. // The original project has this as an option,
  334. if regexUrlPossibleTistorySite.MatchString(inputURL) {
  335. links, err := getPossibleTistorySiteUrls(inputURL)
  336. if err != nil {
  337. log.Println(lg("Download", "", color.RedString, "Checking for Tistory site failed for %s -- %s", inputURL, err))
  338. } else if len(links) > 0 {
  339. return trimDownloadedLinks(links, m)
  340. }
  341. }
  342. if strings.HasPrefix(inputURL, "https://cdn.discordapp.com/emojis/") {
  343. return nil
  344. }
  345. // Try without queries
  346. parsedURL, err := url.Parse(inputURL)
  347. if err == nil {
  348. if strings.Contains(parsedURL.String(), "format=") {
  349. parsedURL.RawQuery = "format=" + parsedURL.Query().Get("format")
  350. } else {
  351. parsedURL.RawQuery = ""
  352. }
  353. inputURLWithoutQueries := parsedURL.String()
  354. if inputURLWithoutQueries != inputURL {
  355. return trimDownloadedLinks(getDownloadLinks(inputURLWithoutQueries, m), m)
  356. }
  357. }
  358. return trimDownloadedLinks(map[string]string{inputURL: ""}, m)
  359. }
  360. func getFileLinks(m *discordgo.Message) []*fileItem {
  361. var fileItems []*fileItem
  362. linkTime := m.Timestamp
  363. rawLinks := getRawLinks(m)
  364. for _, rawLink := range rawLinks {
  365. downloadLinks := getDownloadLinks(rawLink.Link, m)
  366. for link, filename := range downloadLinks {
  367. if rawLink.Filename != "" {
  368. filename = rawLink.Filename
  369. }
  370. fileItems = append(fileItems, &fileItem{
  371. Link: link,
  372. Filename: filename,
  373. Time: linkTime,
  374. })
  375. }
  376. }
  377. return trimDuplicateLinks(fileItems)
  378. }
  379. type downloadRequestStruct struct {
  380. InputURL string
  381. Filename string
  382. Extension string
  383. Path string
  384. Message *discordgo.Message
  385. FileTime time.Time
  386. HistoryCmd bool
  387. EmojiCmd bool
  388. ManualDownload bool
  389. StartTime time.Time
  390. }
  391. func (download downloadRequestStruct) handleDownload() (downloadStatusStruct, int64) {
  392. status := mDownloadStatus(downloadFailed)
  393. var tempfilesize int64 = -1
  394. for i := 0; i < config.DownloadRetryMax; i++ {
  395. status, tempfilesize = download.tryDownload()
  396. // Success or Skip
  397. if status.Status < downloadFailed || status.Status == downloadFailedCode404 || status.Status == downloadFailedCode403 {
  398. break
  399. } else {
  400. time.Sleep(5 * time.Second)
  401. }
  402. }
  403. // Any kind of failure
  404. if status.Status >= downloadFailed && !download.HistoryCmd && !download.EmojiCmd {
  405. log.Println(lg("Download", "", color.RedString,
  406. "Gave up on downloading %s after %d failed attempts...\t%s",
  407. download.InputURL, config.DownloadRetryMax, getDownloadStatusString(status.Status)))
  408. if channelConfig := getSource(download.Message); channelConfig != emptyConfig {
  409. if !download.HistoryCmd && *channelConfig.SendErrorMessages {
  410. content := fmt.Sprintf(
  411. "Gave up trying to download\n<%s>\nafter %d failed attempts...\n\n``%s``",
  412. download.InputURL, config.DownloadRetryMax, getDownloadStatusString(status.Status))
  413. if status.Error != nil {
  414. content += fmt.Sprintf("\n```ERROR: %s```", status.Error)
  415. }
  416. // Failure Notice
  417. if !hasPerms(download.Message.ChannelID, discordgo.PermissionSendMessages) {
  418. log.Println(lg("Download", "", color.HiRedString, fmtBotSendPerm, download.Message.ChannelID))
  419. } else {
  420. if selfbot {
  421. _, err := bot.ChannelMessageSend(download.Message.ChannelID,
  422. fmt.Sprintf("%s **Download Failure**\n\n%s", download.Message.Author.Mention(), content))
  423. if err != nil {
  424. log.Println(lg("Download", "", color.HiRedString,
  425. "Failed to send failure message to %s: %s", download.Message.ChannelID, err))
  426. }
  427. } else {
  428. if _, err := bot.ChannelMessageSendComplex(download.Message.ChannelID,
  429. &discordgo.MessageSend{
  430. Content: fmt.Sprintf("<@!%s>", download.Message.Author.ID),
  431. Embed: buildEmbed(download.Message.ChannelID, "Download Failure", content),
  432. }); err != nil {
  433. log.Println(lg("Download", "", color.HiRedString,
  434. "Failed to send failure message to %s: %s",
  435. download.Message.ChannelID, err))
  436. }
  437. }
  438. }
  439. }
  440. if status.Error != nil {
  441. sendErrorMessage(fmt.Sprintf("**%s**\n\n%s", getDownloadStatusString(status.Status), status.Error))
  442. }
  443. }
  444. }
  445. // Log Links to File
  446. if channelConfig := getSource(download.Message); channelConfig != emptyConfig {
  447. if channelConfig.LogLinks != nil {
  448. if channelConfig.LogLinks.Destination != "" {
  449. logPath := channelConfig.LogLinks.Destination
  450. if *channelConfig.LogLinks.DestinationIsFolder {
  451. if !strings.HasSuffix(logPath, string(os.PathSeparator)) {
  452. logPath += string(os.PathSeparator)
  453. }
  454. err := os.MkdirAll(logPath, 0755)
  455. if err == nil {
  456. logPath += "Log_Links"
  457. if *channelConfig.LogLinks.DivideLogsByServer {
  458. if download.Message.GuildID == "" {
  459. ch, err := bot.State.Channel(download.Message.ChannelID)
  460. if err == nil {
  461. if ch.Type == discordgo.ChannelTypeDM {
  462. logPath += " DM"
  463. } else if ch.Type == discordgo.ChannelTypeGroupDM {
  464. logPath += " GroupDM"
  465. } else {
  466. logPath += " Unknown"
  467. }
  468. } else {
  469. logPath += " Unknown"
  470. }
  471. } else {
  472. logPath += " SID_" + download.Message.GuildID
  473. }
  474. }
  475. if *channelConfig.LogLinks.DivideLogsByChannel {
  476. logPath += " CID_" + download.Message.ChannelID
  477. }
  478. if *channelConfig.LogLinks.DivideLogsByUser {
  479. logPath += " UID_" + download.Message.Author.ID
  480. }
  481. if *channelConfig.LogLinks.DivideLogsByStatus {
  482. if status.Status >= downloadFailed {
  483. logPath += " - FAILED"
  484. } else if status.Status >= downloadSkipped {
  485. logPath += " - SKIPPED"
  486. } else if status.Status == downloadIgnored {
  487. logPath += " - IGNORED"
  488. } else if status.Status == downloadSuccess {
  489. logPath += " - DOWNLOADED"
  490. }
  491. }
  492. }
  493. logPath += ".txt"
  494. }
  495. // Read
  496. currentLog, err := os.ReadFile(logPath)
  497. currentLogS := ""
  498. if err == nil {
  499. currentLogS = string(currentLog)
  500. }
  501. // Writer
  502. f, err := os.OpenFile(logPath, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0600)
  503. if err != nil {
  504. log.Println(lg("Download", "", color.RedString,
  505. "[channelConfig.LogLinks] Failed to open log file:\t%s", err))
  506. f.Close()
  507. }
  508. defer f.Close()
  509. var newLine string
  510. shouldLog := true
  511. // Log Failures
  512. if status.Status > downloadSuccess {
  513. shouldLog = *channelConfig.LogLinks.LogFailures // will not log if LogFailures is false
  514. } else if *channelConfig.LogLinks.LogDownloads { // Log Downloads
  515. shouldLog = true
  516. }
  517. // Filter Duplicates
  518. if channelConfig.LogLinks.FilterDuplicates != nil {
  519. if *channelConfig.LogLinks.FilterDuplicates {
  520. if strings.Contains(currentLogS, download.InputURL) {
  521. shouldLog = false
  522. }
  523. }
  524. }
  525. if shouldLog {
  526. // Prepend
  527. prefix := ""
  528. if channelConfig.LogLinks.Prefix != nil {
  529. prefix = *channelConfig.LogLinks.Prefix
  530. }
  531. // More Data
  532. additionalInfo := ""
  533. if channelConfig.LogLinks.UserData != nil {
  534. if *channelConfig.LogLinks.UserData {
  535. additionalInfo = fmt.Sprintf("[%s/%s] \"%s\"#%s (%s) @ %s: ",
  536. download.Message.GuildID, download.Message.ChannelID,
  537. download.Message.Author.Username, download.Message.Author.Discriminator,
  538. download.Message.Author.ID, download.Message.Timestamp)
  539. }
  540. }
  541. // Append
  542. suffix := ""
  543. if channelConfig.LogLinks.Suffix != nil {
  544. suffix = *channelConfig.LogLinks.Suffix
  545. }
  546. // New Line
  547. newLine += "\n" + prefix + additionalInfo + download.InputURL + suffix
  548. if _, err = f.WriteString(newLine); err != nil {
  549. log.Println(lg("Download", "", color.RedString,
  550. "[channelConfig.LogLinks] Failed to append file:\t%s", err))
  551. }
  552. }
  553. }
  554. }
  555. }
  556. return status, tempfilesize
  557. }
  558. func (download downloadRequestStruct) tryDownload() (downloadStatusStruct, int64) {
  559. var err error
  560. cachedDownloadID++
  561. logPrefix := ""
  562. if download.HistoryCmd {
  563. logPrefix = "HISTORY "
  564. }
  565. var fileinfo fs.FileInfo
  566. var channelConfig configurationSource
  567. sourceDefault(&channelConfig)
  568. _channelConfig := getSource(download.Message)
  569. if _channelConfig != emptyConfig {
  570. channelConfig = _channelConfig
  571. }
  572. if _channelConfig != emptyConfig || download.EmojiCmd || download.ManualDownload {
  573. // Source validation
  574. if _, err = url.ParseRequestURI(download.InputURL); err != nil {
  575. return mDownloadStatus(downloadFailedInvalidSource, err), 0
  576. }
  577. // Clean/fix path
  578. if download.Path == "" || download.Path == string(os.PathSeparator) {
  579. log.Println(lg("Download", "", color.HiRedString, "Destination cannot be empty path..."))
  580. return mDownloadStatus(downloadFailedInvalidPath, err), 0
  581. }
  582. if !strings.HasSuffix(download.Path, string(os.PathSeparator)) {
  583. download.Path = download.Path + string(os.PathSeparator)
  584. }
  585. // Create folder
  586. if err = os.MkdirAll(download.Path, 0755); err != nil {
  587. log.Println(lg("Download", "", color.HiRedString,
  588. "Error while creating destination folder \"%s\": %s",
  589. download.Path, err))
  590. return mDownloadStatus(downloadFailedCreatingFolder, err), 0
  591. }
  592. // Request
  593. timeout := time.Duration(time.Duration(config.DownloadTimeout) * time.Second)
  594. client := &http.Client{
  595. Timeout: timeout,
  596. }
  597. request, err := http.NewRequest("GET", download.InputURL, nil)
  598. request.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36")
  599. if err != nil {
  600. log.Println(lg("Download", "", color.HiRedString, "Error while requesting \"%s\": %s", download.InputURL, err))
  601. return mDownloadStatus(downloadFailedRequesting, err), 0
  602. }
  603. request.Header.Add("Accept-Encoding", "identity")
  604. response, err := client.Do(request)
  605. if err != nil {
  606. if !strings.Contains(err.Error(), "no such host") && !strings.Contains(err.Error(), "connection refused") {
  607. log.Println(lg("Download", "", color.HiRedString,
  608. "Error while receiving response from \"%s\": %s",
  609. download.InputURL, err))
  610. }
  611. return mDownloadStatus(downloadFailedDownloadingResponse, err), 0
  612. }
  613. defer response.Body.Close()
  614. // Read
  615. bodyOfResp, err := io.ReadAll(response.Body)
  616. if err != nil {
  617. log.Println(lg("Download", "", color.HiRedString,
  618. "Could not read response from \"%s\": %s",
  619. download.InputURL, err))
  620. return mDownloadStatus(downloadFailedReadResponse, err), 0
  621. }
  622. // Errors
  623. if response.StatusCode >= 400 {
  624. log.Println(lg("Download", "", color.HiRedString, logPrefix+"DOWNLOAD FAILED, %d %s: %s",
  625. response.StatusCode, http.StatusText(response.StatusCode), download.InputURL))
  626. if response.StatusCode == 403 {
  627. return mDownloadStatus(downloadFailedCode403, err), 0
  628. } else if response.StatusCode == 404 {
  629. return mDownloadStatus(downloadFailedCode404, err), 0
  630. } else {
  631. return mDownloadStatus(downloadFailedCode, err), 0
  632. }
  633. }
  634. // Content Type
  635. contentType := http.DetectContentType(bodyOfResp)
  636. contentTypeParts := strings.Split(contentType, "/")
  637. contentTypeBase := contentTypeParts[0]
  638. isHtml := strings.Contains(contentType, "text/html")
  639. // Filename
  640. if download.Filename == "" {
  641. download.Filename = filenameFromURL(response.Request.URL.String())
  642. for key, iHeader := range response.Header {
  643. if key == "Content-Disposition" {
  644. if _, params, err := mime.ParseMediaType(iHeader[0]); err == nil {
  645. newFilename, err := url.QueryUnescape(params["filename"])
  646. if err != nil {
  647. newFilename = params["filename"]
  648. }
  649. if newFilename != "" {
  650. download.Filename = newFilename
  651. }
  652. }
  653. }
  654. }
  655. }
  656. // Check Filename
  657. if channelConfig.Filters.AllowedFilenames != nil || channelConfig.Filters.BlockedFilenames != nil {
  658. shouldAbort := false
  659. if channelConfig.Filters.AllowedFilenames != nil {
  660. shouldAbort = true
  661. }
  662. if channelConfig.Filters.BlockedFilenames != nil {
  663. for _, phrase := range *channelConfig.Filters.BlockedFilenames {
  664. if phrase != "" && phrase != " " && strings.ContainsAny(download.Filename, phrase) {
  665. shouldAbort = true
  666. }
  667. }
  668. }
  669. if channelConfig.Filters.AllowedFilenames != nil {
  670. for _, phrase := range *channelConfig.Filters.AllowedFilenames {
  671. if phrase != "" && phrase != " " && strings.ContainsAny(download.Filename, phrase) {
  672. shouldAbort = false
  673. }
  674. }
  675. }
  676. // Abort
  677. if shouldAbort {
  678. if !download.HistoryCmd {
  679. log.Println(lg("Download", "Skip", color.GreenString,
  680. "Unpermitted filename content \"%s\"", download.Filename))
  681. }
  682. return mDownloadStatus(downloadSkippedUnpermittedFilename), 0
  683. }
  684. }
  685. // Check Reactions
  686. if channelConfig.Filters.AllowedReactions != nil || channelConfig.Filters.BlockedReactions != nil {
  687. shouldAbort := false
  688. if channelConfig.Filters.AllowedReactions != nil {
  689. shouldAbort = true
  690. }
  691. if download.Message.Reactions != nil {
  692. for _, reaction := range download.Message.Reactions {
  693. if channelConfig.Filters.BlockedReactions != nil {
  694. if stringInSlice(reaction.Emoji.ID, *channelConfig.Filters.BlockedReactions) {
  695. shouldAbort = true
  696. }
  697. }
  698. if channelConfig.Filters.AllowedReactions != nil {
  699. if stringInSlice(reaction.Emoji.ID, *channelConfig.Filters.AllowedReactions) {
  700. shouldAbort = false
  701. }
  702. }
  703. }
  704. }
  705. // Abort
  706. if shouldAbort {
  707. if !download.HistoryCmd {
  708. log.Println(lg("Download", "Skip", color.GreenString,
  709. "Did not meet reaction filter criteria"))
  710. }
  711. return mDownloadStatus(downloadSkippedUnpermittedReaction), 0
  712. }
  713. }
  714. // Extension
  715. download.Extension = strings.ToLower(filepath.Ext(download.Filename))
  716. if filepath.Ext(download.Filename) == "" {
  717. if possibleExtension, _ := mime.ExtensionsByType(contentType); len(possibleExtension) > 0 {
  718. download.Filename += possibleExtension[0]
  719. download.Extension = possibleExtension[0]
  720. }
  721. }
  722. // Format Keys
  723. download.Filename = dynamicKeyReplacement(channelConfig, download)
  724. // Fix filename length
  725. if len(download.Filename) >= 260 {
  726. download.Filename = download.Filename[:250]
  727. download.Filename += download.Extension
  728. }
  729. // Swap Extensions
  730. if download.Extension == ".jfif" {
  731. download.Extension = ".jpg"
  732. download.Filename = strings.ReplaceAll(download.Filename, ".jfif", ".jpg")
  733. }
  734. // Fix content type using extension
  735. if stringInSlice(download.Extension, []string{".mov"}) ||
  736. stringInSlice(download.Extension, []string{".mp4"}) ||
  737. stringInSlice(download.Extension, []string{".webm"}) {
  738. contentTypeBase = "video"
  739. } else if stringInSlice(download.Extension, []string{".psd"}) ||
  740. stringInSlice(download.Extension, []string{".nef"}) ||
  741. stringInSlice(download.Extension, []string{".dng"}) ||
  742. stringInSlice(download.Extension, []string{".tif"}) ||
  743. stringInSlice(download.Extension, []string{".tiff"}) {
  744. contentTypeBase = "image"
  745. }
  746. // Check extension
  747. if channelConfig.Filters.AllowedExtensions != nil || channelConfig.Filters.BlockedExtensions != nil {
  748. shouldAbort := false
  749. if channelConfig.Filters.AllowedExtensions != nil {
  750. shouldAbort = true
  751. }
  752. if channelConfig.Filters.BlockedExtensions != nil {
  753. if stringInSlice(download.Extension, *channelConfig.Filters.BlockedExtensions) {
  754. shouldAbort = true
  755. }
  756. }
  757. if channelConfig.Filters.AllowedExtensions != nil {
  758. if stringInSlice(download.Extension, *channelConfig.Filters.AllowedExtensions) {
  759. shouldAbort = false
  760. }
  761. }
  762. // Abort
  763. if shouldAbort {
  764. if !download.HistoryCmd && !isHtml {
  765. log.Println(lg("Download", "Skip", color.GreenString, "Unpermitted extension (%s) found at %s",
  766. download.Extension, download.InputURL))
  767. }
  768. return mDownloadStatus(downloadSkippedUnpermittedExtension), 0
  769. }
  770. }
  771. // Check Domain
  772. parsedURL, err := url.Parse(download.InputURL)
  773. if err != nil {
  774. log.Println(lg("Download", "", color.RedString, "Error while parsing url:\t%s", err))
  775. }
  776. domain := parsedURL.Hostname()
  777. if channelConfig.Filters.AllowedDomains != nil || channelConfig.Filters.BlockedDomains != nil {
  778. shouldAbort := false
  779. if channelConfig.Filters.AllowedDomains != nil {
  780. shouldAbort = true
  781. }
  782. if channelConfig.Filters.BlockedDomains != nil {
  783. if stringInSlice(domain, *channelConfig.Filters.BlockedDomains) {
  784. shouldAbort = true
  785. }
  786. }
  787. if channelConfig.Filters.AllowedDomains != nil {
  788. if stringInSlice(domain, *channelConfig.Filters.AllowedDomains) {
  789. shouldAbort = false
  790. }
  791. }
  792. // Abort
  793. if shouldAbort {
  794. if !download.HistoryCmd {
  795. log.Println(lg("Download", "Skip", color.GreenString,
  796. "Unpermitted domain (%s) found at %s", domain, download.InputURL))
  797. }
  798. return mDownloadStatus(downloadSkippedUnpermittedDomain), 0
  799. }
  800. }
  801. // Check content type
  802. if !((*channelConfig.SaveImages && contentTypeBase == "image") ||
  803. (*channelConfig.SaveVideos && contentTypeBase == "video") ||
  804. (*channelConfig.SaveAudioFiles && contentTypeBase == "audio") ||
  805. (*channelConfig.SaveTextFiles && contentTypeBase == "text" && !isHtml) ||
  806. (*channelConfig.SaveOtherFiles && contentTypeBase == "application")) {
  807. if !download.HistoryCmd && !isHtml {
  808. log.Println(lg("Download", "Skip", color.GreenString,
  809. "Unpermitted filetype (%s) found at %s", contentTypeBase, download.InputURL))
  810. }
  811. return mDownloadStatus(downloadSkippedUnpermittedType), 0
  812. }
  813. // Duplicate Image Filter
  814. if config.Duplo && contentTypeBase == "image" && download.Extension != ".gif" && download.Extension != ".webp" {
  815. img, _, err := image.Decode(bytes.NewReader(bodyOfResp))
  816. if err != nil {
  817. log.Println(lg("Duplo", "Download", color.HiRedString,
  818. "Error converting buffer to image for hashing:\t%s", err))
  819. } else {
  820. hash, _ := duplo.CreateHash(img)
  821. matches := duploCatalog.Query(hash)
  822. sort.Sort(matches)
  823. for _, match := range matches {
  824. if match.Score < config.DuploThreshold {
  825. log.Println(lg("Duplo", "Download", color.GreenString,
  826. "Duplicate detected (Score of %f) found at %s", match.Score, download.InputURL))
  827. return mDownloadStatus(downloadSkippedDetectedDuplicate), 0
  828. }
  829. }
  830. duploCatalog.Add(cachedDownloadID, hash)
  831. }
  832. }
  833. // Names
  834. sourceChannelName := download.Message.ChannelID
  835. sourceName := "UNKNOWN"
  836. sourceChannel, _ := bot.State.Channel(download.Message.ChannelID)
  837. if sourceChannel != nil {
  838. // Channel Naming
  839. if sourceChannel.Name != "" {
  840. sourceChannelName = "#" + sourceChannel.Name
  841. }
  842. switch sourceChannel.Type {
  843. case discordgo.ChannelTypeGuildText:
  844. // Server Naming
  845. if sourceChannel.GuildID != "" {
  846. sourceGuild, _ := bot.State.Guild(sourceChannel.GuildID)
  847. if sourceGuild != nil && sourceGuild.Name != "" {
  848. sourceName = sourceGuild.Name
  849. }
  850. }
  851. // Category Naming
  852. if sourceChannel.ParentID != "" {
  853. sourceParent, _ := bot.State.Channel(sourceChannel.ParentID)
  854. if sourceParent != nil {
  855. if sourceParent.Name != "" {
  856. sourceChannelName = sourceParent.Name + " / " + sourceChannelName
  857. }
  858. }
  859. }
  860. case discordgo.ChannelTypeDM:
  861. sourceName = "Direct Messages"
  862. case discordgo.ChannelTypeGroupDM:
  863. sourceName = "Group Messages"
  864. }
  865. }
  866. subfolder := ""
  867. // Subfolder Division - Year Nesting
  868. if *channelConfig.DivideByYear {
  869. year := fmt.Sprint(time.Now().Year())
  870. if download.Message.Author != nil {
  871. year = fmt.Sprint(download.Message.Timestamp.Year())
  872. }
  873. subfolderSuffix := year + string(os.PathSeparator)
  874. subfolder = subfolder + subfolderSuffix
  875. // Create folder
  876. if err := os.MkdirAll(download.Path+subfolder, 0755); err != nil {
  877. log.Println(lg("Download", "", color.HiRedString,
  878. "Error while creating server subfolder \"%s\": %s", download.Path, err))
  879. return mDownloadStatus(downloadFailedCreatingSubfolder, err), 0
  880. }
  881. }
  882. // Subfolder Division - Month Nesting
  883. if *channelConfig.DivideByMonth {
  884. year := fmt.Sprintf("%02d", time.Now().Month())
  885. if download.Message.Author != nil {
  886. year = fmt.Sprintf("%02d", download.Message.Timestamp.Month())
  887. }
  888. subfolderSuffix := year + string(os.PathSeparator)
  889. subfolder = subfolder + subfolderSuffix
  890. // Create folder
  891. if err := os.MkdirAll(download.Path+subfolder, 0755); err != nil {
  892. log.Println(lg("Download", "", color.HiRedString,
  893. "Error while creating server subfolder \"%s\": %s", download.Path, err))
  894. return mDownloadStatus(downloadFailedCreatingSubfolder, err), 0
  895. }
  896. }
  897. // Subfolder Division - Server Nesting
  898. if *channelConfig.DivideByServer {
  899. subfolderSuffix := download.Message.GuildID
  900. if !*channelConfig.DivideFoldersUseID && sourceName != "" && sourceName != "UNKNOWN" {
  901. subfolderSuffix = clearPath(sourceName)
  902. }
  903. if subfolderSuffix != "" {
  904. subfolderSuffix = subfolderSuffix + string(os.PathSeparator)
  905. subfolder = subfolder + subfolderSuffix
  906. // Create folder
  907. if err := os.MkdirAll(download.Path+subfolder, 0755); err != nil {
  908. log.Println(lg("Download", "", color.HiRedString,
  909. "Error while creating server subfolder \"%s\": %s", download.Path, err))
  910. return mDownloadStatus(downloadFailedCreatingSubfolder, err), 0
  911. }
  912. }
  913. }
  914. // Subfolder Division - Channel Nesting
  915. if *channelConfig.DivideByChannel {
  916. subfolderSuffix := download.Message.ChannelID
  917. if !*channelConfig.DivideFoldersUseID && sourceChannelName != "" {
  918. subfolderSuffix = clearPath(sourceChannelName)
  919. }
  920. if subfolderSuffix != "" {
  921. subfolder = subfolder + subfolderSuffix + string(os.PathSeparator)
  922. // Create folder
  923. if err := os.MkdirAll(download.Path+subfolder, 0755); err != nil {
  924. log.Println(lg("Download", "", color.HiRedString,
  925. "Error while creating channel subfolder \"%s\": %s", download.Path, err))
  926. return mDownloadStatus(downloadFailedCreatingSubfolder, err), 0
  927. }
  928. }
  929. }
  930. // Subfolder Division - User Nesting
  931. if *channelConfig.DivideByUser && download.Message.Author != nil {
  932. subfolderSuffix := download.Message.Author.ID
  933. if !*channelConfig.DivideFoldersUseID && download.Message.Author.Username != "" {
  934. subfolderSuffix = clearPath(download.Message.Author.Username + "#" +
  935. download.Message.Author.Discriminator)
  936. }
  937. if subfolderSuffix != "" {
  938. subfolder = subfolder + subfolderSuffix + string(os.PathSeparator)
  939. // Create folder
  940. if err := os.MkdirAll(download.Path+subfolder, 0755); err != nil {
  941. log.Println(lg("Download", "", color.HiRedString,
  942. "Error while creating user subfolder \"%s\": %s", download.Path, err))
  943. return mDownloadStatus(downloadFailedCreatingSubfolder, err), 0
  944. }
  945. }
  946. }
  947. // Subfolder Division - Content Type
  948. if *channelConfig.DivideByType {
  949. subfolderSuffix := contentTypeBase
  950. switch contentTypeBase {
  951. case "image":
  952. subfolderSuffix = "images"
  953. case "video":
  954. subfolderSuffix = "videos"
  955. case "application":
  956. subfolderSuffix = "applications"
  957. }
  958. if subfolderSuffix != "" {
  959. subfolder = subfolder + subfolderSuffix + string(os.PathSeparator)
  960. // Create folder.
  961. if err := os.MkdirAll(download.Path+subfolder, 0755); err != nil {
  962. log.Println(lg("Download", "", color.HiRedString,
  963. "Error while creating type subfolder \"%s\": %s", download.Path+subfolder, err))
  964. return mDownloadStatus(downloadFailedCreatingSubfolder, err), 0
  965. }
  966. }
  967. }
  968. // Format Path
  969. download.Path = download.Path + subfolder
  970. completePath := filepath.Clean(download.Path + download.Filename)
  971. // Check if filepath exists
  972. if _, err := os.Stat(completePath); err == nil {
  973. if *channelConfig.SavePossibleDuplicates {
  974. tmpPath := completePath
  975. i := 1
  976. for {
  977. // Append number to name
  978. completePath = tmpPath[0:len(tmpPath)-len(filepathExtension(tmpPath))] +
  979. "-" + strconv.Itoa(i) + filepathExtension(tmpPath)
  980. if _, err := os.Stat(completePath); os.IsNotExist(err) {
  981. break
  982. }
  983. i = i + 1
  984. }
  985. if !download.HistoryCmd {
  986. log.Println(lg("Download", "Skip", color.GreenString,
  987. "Matching filenames, possible duplicate? Saving \"%s\" as \"%s\" instead",
  988. tmpPath, completePath))
  989. }
  990. } else {
  991. if !download.HistoryCmd {
  992. log.Println(lg("Download", "Skip", color.GreenString,
  993. "Matching filenames, possible duplicate..."))
  994. }
  995. return mDownloadStatus(downloadSkippedDuplicate), 0
  996. }
  997. }
  998. // Write
  999. if *channelConfig.Save {
  1000. if err = os.WriteFile(completePath, bodyOfResp, 0644); err != nil {
  1001. log.Println(lg("Download", "", color.HiRedString,
  1002. "Error while writing file to disk \"%s\": %s", download.InputURL, err))
  1003. return mDownloadStatus(downloadFailedWritingFile, err), 0
  1004. }
  1005. // Change file time
  1006. if err = os.Chtimes(completePath, download.FileTime, download.FileTime); err != nil {
  1007. log.Println(lg("Download", "", color.RedString,
  1008. logPrefix+"Error while changing metadata date \"%s\": %s", download.InputURL, err))
  1009. }
  1010. filesize := "unknown"
  1011. speed := 0.0
  1012. speedlabel := "kB/s"
  1013. fileinfo, err = os.Stat(completePath)
  1014. if err == nil {
  1015. filesize = humanize.Bytes(uint64(fileinfo.Size()))
  1016. speed = float64(fileinfo.Size() / humanize.KByte)
  1017. if fileinfo.Size() >= humanize.MByte {
  1018. speed = float64(fileinfo.Size() / humanize.MByte)
  1019. speedlabel = "MB/s"
  1020. }
  1021. }
  1022. dlColor := color.HiGreenString
  1023. msgTimestamp := ""
  1024. if download.HistoryCmd {
  1025. dlColor = color.HiCyanString
  1026. msgTimestamp = "on " + download.Message.Timestamp.Format("2006/01/02 @ 15:04:05") + " "
  1027. }
  1028. log.Println(lg("Download", "", dlColor,
  1029. logPrefix+"SAVED %s sent %sin %s\n\t\t\t\t\t\t%s",
  1030. strings.ToUpper(contentTypeBase), msgTimestamp,
  1031. color.HiYellowString("\"%s / %s\" (%s, %s)", sourceName, sourceChannelName, download.Message.ChannelID, download.Message.ID),
  1032. color.GreenString("> %s to \"%s%s\"\t\t%s", domain, download.Path, download.Filename,
  1033. color.WhiteString("(%s, %s, %0.1f %s)",
  1034. filesize, shortenTime(durafmt.ParseShort(time.Since(download.StartTime)).String()), speed/time.Since(download.StartTime).Seconds(), speedlabel))))
  1035. } else {
  1036. log.Println(lg("Download", "", color.GreenString,
  1037. logPrefix+"Did not save %s sent in %s#%s --- file saving disabled...",
  1038. contentTypeBase, sourceName, sourceChannelName))
  1039. }
  1040. userID := botUser.ID
  1041. if download.Message.Author != nil {
  1042. userID = download.Message.Author.ID
  1043. }
  1044. // Store in db
  1045. err = dbInsertDownload(&downloadItem{
  1046. URL: download.InputURL,
  1047. Time: time.Now(),
  1048. Destination: completePath,
  1049. Filename: download.Filename,
  1050. ChannelID: download.Message.ChannelID,
  1051. UserID: userID,
  1052. })
  1053. if err != nil {
  1054. log.Println(lg("Download", "", color.HiRedString, "Error writing to database: %s", err))
  1055. return mDownloadStatus(downloadFailedWritingDatabase, err), 0
  1056. }
  1057. // React
  1058. {
  1059. shouldReact := config.ReactWhenDownloaded
  1060. if channelConfig.ReactWhenDownloaded != nil {
  1061. shouldReact = *channelConfig.ReactWhenDownloaded
  1062. }
  1063. if download.HistoryCmd {
  1064. if !config.ReactWhenDownloadedHistory {
  1065. shouldReact = false
  1066. }
  1067. if channelConfig.ReactWhenDownloadedHistory != nil {
  1068. if *channelConfig.ReactWhenDownloadedHistory {
  1069. shouldReact = true
  1070. }
  1071. }
  1072. }
  1073. if download.Message.Author != nil && shouldReact {
  1074. reaction := defaultReact
  1075. if channelConfig.ReactWhenDownloadedEmoji == nil {
  1076. if download.Message.GuildID != "" {
  1077. guild, err := bot.State.Guild(download.Message.GuildID)
  1078. if err != nil {
  1079. log.Println(lg("Download", "", color.RedString,
  1080. "Error fetching guild state for emojis from %s: %s",
  1081. download.Message.GuildID, err))
  1082. } else {
  1083. emojis := guild.Emojis
  1084. if len(emojis) > 1 {
  1085. for {
  1086. rand.Seed(time.Now().UnixNano())
  1087. chosenEmoji := emojis[rand.Intn(len(emojis))]
  1088. formattedEmoji := chosenEmoji.APIName()
  1089. if !chosenEmoji.Animated && !stringInSlice(formattedEmoji,
  1090. *channelConfig.BlacklistReactEmojis) {
  1091. reaction = formattedEmoji
  1092. break
  1093. }
  1094. }
  1095. } else {
  1096. reaction = defaultReact
  1097. }
  1098. }
  1099. } else {
  1100. reaction = defaultReact
  1101. }
  1102. } else {
  1103. reaction = *channelConfig.ReactWhenDownloadedEmoji
  1104. }
  1105. // Add Reaction
  1106. if hasPerms(download.Message.ChannelID, discordgo.PermissionAddReactions) {
  1107. if err = bot.MessageReactionAdd(download.Message.ChannelID, download.Message.ID, reaction); err != nil {
  1108. log.Println(lg("Download", "", color.RedString,
  1109. "Error adding reaction to message: %s", err))
  1110. }
  1111. } else {
  1112. log.Println(lg("Download", "", color.RedString,
  1113. "Bot does not have permission to add reactions in %s", download.Message.ChannelID))
  1114. }
  1115. }
  1116. }
  1117. // Log Media To Channel(s)
  1118. {
  1119. var logMediaChannels []string
  1120. if channelConfig.SendFileToChannel != nil {
  1121. if *channelConfig.SendFileToChannel != "" {
  1122. logMediaChannels = append(logMediaChannels, *channelConfig.SendFileToChannel)
  1123. }
  1124. }
  1125. if channelConfig.SendFileToChannels != nil {
  1126. logMediaChannels = append(logMediaChannels, *channelConfig.SendFileToChannels...)
  1127. }
  1128. for _, logChannel := range logMediaChannels {
  1129. if logChannel != "" {
  1130. if hasPerms(logChannel, discordgo.PermissionSendMessages) {
  1131. actualFile := false
  1132. if channelConfig.SendFileDirectly != nil {
  1133. actualFile = *channelConfig.SendFileDirectly
  1134. }
  1135. msg := ""
  1136. if channelConfig.SendFileCaption != nil {
  1137. msg = *channelConfig.SendFileCaption
  1138. msg = channelKeyReplacement(msg, download.Message.ChannelID)
  1139. }
  1140. // File
  1141. if actualFile {
  1142. _, err := bot.ChannelMessageSendComplex(logChannel,
  1143. &discordgo.MessageSend{
  1144. Content: msg,
  1145. File: &discordgo.File{Name: download.Filename, Reader: bytes.NewReader(bodyOfResp)},
  1146. },
  1147. )
  1148. if err != nil {
  1149. log.Println(lg("Download", "", color.HiRedString,
  1150. "File log message failed to send:\t%s", err))
  1151. }
  1152. } else { // Embed
  1153. embed := &discordgo.MessageEmbed{
  1154. Title: fmt.Sprintf("Downloaded: %s", download.Filename),
  1155. Color: getEmbedColor(logChannel),
  1156. Footer: &discordgo.MessageEmbedFooter{
  1157. IconURL: projectIcon,
  1158. Text: fmt.Sprintf("%s v%s", projectName, projectVersion),
  1159. },
  1160. }
  1161. if contentTypeBase == "image" {
  1162. embed.Image = &discordgo.MessageEmbedImage{URL: download.InputURL}
  1163. } else if contentTypeBase == "video" {
  1164. embed.Video = &discordgo.MessageEmbedVideo{URL: download.InputURL}
  1165. } else {
  1166. embed.Description = fmt.Sprintf("Unsupported filetype: %s\n%s",
  1167. contentTypeBase, download.InputURL)
  1168. }
  1169. _, err := bot.ChannelMessageSendComplex(logChannel,
  1170. &discordgo.MessageSend{
  1171. Content: msg,
  1172. Embed: embed,
  1173. },
  1174. )
  1175. if err != nil {
  1176. log.Println(lg("Download", "", color.HiRedString,
  1177. "File log message failed to send:\t%s", err))
  1178. }
  1179. }
  1180. }
  1181. }
  1182. }
  1183. }
  1184. // Update Presence
  1185. if !download.HistoryCmd {
  1186. timeLastUpdated = time.Now()
  1187. if *channelConfig.PresenceEnabled {
  1188. go updateDiscordPresence()
  1189. }
  1190. }
  1191. timeLastDownload = time.Now()
  1192. if *channelConfig.Save {
  1193. return mDownloadStatus(downloadSuccess), fileinfo.Size()
  1194. } else {
  1195. return mDownloadStatus(downloadSuccess), 0
  1196. }
  1197. }
  1198. return mDownloadStatus(downloadIgnored), 0
  1199. }