downloads.go 41 KB

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