downloads.go 38 KB

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