Browse Source

Initial commit

master
Patrick Gaskin 1 year ago
commit
15f8fe8788
Signed by: geek1011 GPG Key ID: A2FD79F68A2AB707
6 changed files with 253 additions and 0 deletions
  1. +2
    -0
      README.md
  2. +10
    -0
      go.mod
  3. +16
    -0
      go.sum
  4. +86
    -0
      main.go
  5. +98
    -0
      mediafire.go
  6. +41
    -0
      mediafire_test.go

+ 2
- 0
README.md View File

@ -0,0 +1,2 @@
# mfdl
Resolves MediaFire share IDs into direct links.

+ 10
- 0
go.mod View File

@ -0,0 +1,10 @@
module git.geek1011.net/geek1011/mfdl
go 1.12
require (
github.com/PuerkitoBio/goquery v1.5.0
github.com/go-chi/chi v4.0.2+incompatible
github.com/spf13/pflag v1.0.3
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect
)

+ 16
- 0
go.sum View File

@ -0,0 +1,16 @@
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

+ 86
- 0
main.go View File

@ -0,0 +1,86 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/spf13/pflag"
)
func main() {
addr := pflag.StringP("addr", "a", ":8080", "The address to listen on")
help := pflag.Bool("help", false, "Show this help text")
envmap := map[string]string{
"addr": "MFDL_ADDR",
}
if val, ok := os.LookupEnv("PORT"); ok {
val = ":" + val
fmt.Printf("Setting --addr from PORT to %#v\n", val)
if err := pflag.Set("addr", val); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(2)
}
}
pflag.VisitAll(func(flag *pflag.Flag) {
if env, ok := envmap[flag.Name]; ok {
flag.Usage += fmt.Sprintf(" (env %s)", env)
if val, ok := os.LookupEnv(env); ok {
fmt.Printf("Setting --%s from %s to %#v\n", flag.Name, env, val)
if err := flag.Value.Set(val); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(2)
}
}
}
})
pflag.Parse()
if *help {
pflag.Usage()
os.Exit(1)
}
r := chi.NewRouter()
r.Use(middleware.Recoverer)
r.Use(middleware.NoCache)
r.Get("/", handleAPI)
r.Get("/s/{id}", handleLink)
fmt.Printf("Listening on http://%s\n", *addr)
if err := http.ListenAndServe(*addr, r); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func handleAPI(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"link_url": "/s/{id}",
})
}
func handleLink(w http.ResponseWriter, r *http.Request) {
l, err := getDirectLink(chi.URLParam(r, "id"))
switch {
case errFileStillUploading.Is(err):
http.Error(w, err.Error(), http.StatusServiceUnavailable)
case errNoSuchShare.Is(err):
http.Error(w, err.Error(), http.StatusNotFound)
case errUnknownMediaFireError.Is(err):
http.Error(w, err.Error(), http.StatusBadGateway)
case err != nil:
http.Error(w, err.Error(), http.StatusInternalServerError)
default:
http.Redirect(w, r, l, http.StatusTemporaryRedirect)
}
}

+ 98
- 0
mediafire.go View File

@ -0,0 +1,98 @@
package main
import (
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/PuerkitoBio/goquery"
)
var shareRe = regexp.MustCompile(`^[a-z0-9]+$`)
func getDirectLink(shareID string) (string, error) {
if !shareRe.MatchString(shareID) {
return "", errNoSuchShare.Errorf("invalid share id")
}
req, err := http.NewRequest("GET", fmt.Sprintf("https://www.mediafire.com/file/%s", shareID), nil)
if err != nil {
return "", err
}
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
break
case http.StatusNotFound:
return "", errNoSuchShare
default:
return "", errUnknownMediaFireError.Errorf("unexpected status %s", resp.Status)
}
u := resp.Request.URL.String()
switch {
case strings.HasPrefix(resp.Request.URL.Path, req.URL.Path):
break
case strings.Contains(u, "download_status.php"):
return "", errFileStillUploading
case strings.Contains(u, "error.php"):
return "", errUnknownMediaFireError.Errorf("%s", u)
default:
return "", errUnknownMediaFireError.Errorf("unexpected redirect to %s", u)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return "", err
}
sel := doc.Find("#download_link a[aria-label='Download file'][href]").First()
if sel.Length() < 1 {
return "", fmt.Errorf("could not parse download page link")
}
return sel.AttrOr("href", ""), nil
}
type errMessage string
const (
errUnknownMediaFireError errMessage = "unknown mediafire error"
errNoSuchShare errMessage = "no such share"
errFileStillUploading errMessage = "file still uploading"
)
func (e errMessage) Errorf(format string, a ...interface{}) error {
if len(format) > 0 {
return fmt.Errorf("%s: %s", e, fmt.Sprintf(format, a...))
}
return errors.New(string(e))
}
func (e errMessage) Wrap(err error) error {
if err != nil {
return e.Errorf("%s", err.Error())
}
return nil
}
func (e errMessage) Error() string {
return string(e)
}
func (e errMessage) Is(err error) bool {
if err != nil {
return strings.HasPrefix(err.Error(), string(e))
}
return false
}

+ 41
- 0
mediafire_test.go View File

@ -0,0 +1,41 @@
package main
import (
"errors"
"testing"
)
func TestErr(t *testing.T) {
for v, err := range map[string]errMessage{
"Unknown": errUnknownMediaFireError,
"NoSuchShare": errNoSuchShare,
"FileStillUploading": errFileStillUploading,
} {
v, err := v, err
t.Run(v, func(t *testing.T) {
if err.Error() != string(err) {
t.Errorf("unexpected err text %#v", err.Error())
}
if err.Wrap(nil) != nil {
t.Errorf("expected wrapped nil error to be nil")
}
if w := err.Wrap(errors.New("test")); w.Error() != string(err)+": test" {
t.Errorf("unexpected wrapped error %#v", w.Error())
} else if !err.Is(w) {
t.Errorf("expected wrapped error to be an instance of original")
}
if err.Is(errors.New("test")) {
t.Error("expected error not to match")
}
if err.Is(nil) {
t.Error("expected error not to match nil")
}
if err.Errorf("%s", "test").Error() != string(err)+": test" {
t.Errorf("expected errorf result to match")
}
if err.Errorf("").Error() != string(err) {
t.Errorf("expected blank errorf result to be original")
}
})
}
}