downloads.go 39 KB

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