Skip to content

Commit

Permalink
Uploading through MyRadio
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-grace committed Sep 10, 2023
1 parent 24b30e2 commit a4f89f0
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 3 deletions.
4 changes: 3 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
API_KEY=
DRY_RUN=0
API_DIR=
DEBUG_MODE=1
DEBUG_MODE=1
MYR_USER=
MYR_PASS=
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
FROM golang:1.16 AS gobuild

WORKDIR /usr/src/app
COPY . .
RUN cd myradio-uploader && go build

FROM python:3

WORKDIR /usr/src/app
Expand All @@ -9,4 +15,6 @@ RUN pip install --no-cache-dir -r requirements.txt

COPY . .

COPY --from=gobuild /usr/src/app/myradio-uploader/myradio-uploader /myradio-uploader/myradio-uploader

CMD ["python", "daemon.py"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ Docker will run the `daemon.py` file, calling the ShowImageGen once an hour.

- Build the image: `docker build -t show-image-gen .`
- Set the `.env` file. As `DEBUG_MODE` and `DRY_RUN` are booleans, set `1` for true, `0` for false.
- Run the container, mounting the ouput directory to `/tmp/showimages`, and adding the `.env`, i.e.:
- Run the container, mounting the ouput directory to `/tmp/showimages` (not needed with direct myradio uploads), and adding the `.env`, i.e.:

`docker run -v /mnt/showimages:/tmp/showimages --env-file .env -d --name show-image-gen show-image-gen`
13 changes: 12 additions & 1 deletion ShowImageCreator.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def getIfDefaultImage(image):


def setImage(apiKey, showId):
# deprecated
image_location = apiDir + showId + '.jpg'
debug("API file location: " + image_location, showId)
if dryRun == False:
Expand All @@ -68,6 +69,8 @@ def setImage(apiKey, showId):
debug("Dry run, not setting image with API.", showId)
return True

def upload_to_myradio(showID):
return os.system(f"myradio-uploader/myradio-uploader {myr_user} {myr_pass} {showID} {outputDir + showID + '.jpg'}") == 0

def deleteImage(showId):
image_location = outputDir + showId + '.jpg'
Expand Down Expand Up @@ -228,6 +231,8 @@ def main(env):
global dryRun
global apiDir
global outputDir
global myr_user
global myr_pass

if env:
# This is for Docker usage, so output dir is set, so the volume can link to it
Expand All @@ -236,6 +241,8 @@ def main(env):
apiDir = os.environ["API_DIR"]
debugMode = os.environ["DEBUG_MODE"] == "1"
outputDir = "/tmp/showimages/"
myr_user = os.environ["MYR_USER"]
myr_pass = os.environ["MYR_PASS"]
else:

parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -279,7 +286,11 @@ def main(env):
showID = str(showKey)
branding = show[2]
if applyBrand(showName, showID, branding):
if setImage(apiKey, showID):
if not env:
if setImage(apiKey, showID):
debug("Image set succeessfully, deleting.", showID)
deleteImage(showID)
if upload_to_myradio(showID):
debug("Image set succeessfully, deleting.", showID)
deleteImage(showID)

Expand Down
1 change: 1 addition & 0 deletions myradio-uploader/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
myradio-uploader
4 changes: 4 additions & 0 deletions myradio-uploader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
this bit is in Go because it was written separately as part of a plan to replace the entire thing, but that was never finished, so just this bit stayed to end up here

usage: `./myradio-uploader myradio-user myradio-pass showid photopath`
i.e. `./myradio-uploader user1 password1 12345 tmp-img.png`
18 changes: 18 additions & 0 deletions myradio-uploader/cookieJar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"net/http"
"net/url"
)

type cookieJar struct {
cookies map[string][]*http.Cookie
}

func (j cookieJar) Cookies(u *url.URL) []*http.Cookie {
return j.cookies[u.Host]
}

func (j cookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
j.cookies[u.Host] = cookies
}
5 changes: 5 additions & 0 deletions myradio-uploader/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/UniversityRadioYork/URYShowImageGen/myradio-uploader

go 1.18

require golang.org/x/net v0.15.0 // indirect
2 changes: 2 additions & 0 deletions myradio-uploader/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
31 changes: 31 additions & 0 deletions myradio-uploader/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"context"
"os"
"strconv"
)

func main() {
// usage: ./myradio-uploader myradio-user myradio-pass showid photopath
// i.e. ./myradio-uploader user1 password1 12345 tmp-img.png

session, err := CreateMyRadioLoginSession(os.Args[1], os.Args[2])
if err != nil {
panic(err)
}

ctx, cnl := context.WithTimeout(context.Background(), session.timeout)
defer cnl()

showID, err := strconv.Atoi(os.Args[3])
if err != nil {
panic(err)
}

_, err = session.SetShowPhoto(ctx, showID, os.Args[4])

if err != nil {
panic(err)
}
}
101 changes: 101 additions & 0 deletions myradio-uploader/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"context"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"time"
)

// LoginSession provides a login session for MyRadio after authenticating
// with a MyRadio username and password
type LoginSession struct {
client *http.Client
xsrfToken string
timeout time.Duration
}

// Close will log out the MyRadio session
func (e *LoginSession) Close() {
log.Printf("logging out of myradio")
ctx, cnl := context.WithTimeout(context.Background(), e.timeout)
defer cnl()

form := url.Values{
"myradio_logout-__xsrftoken": []string{e.xsrfToken},
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://ury.org.uk/myradio/MyRadio/logout", strings.NewReader(form.Encode()))
if err != nil {
return
}

req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

e.client.Do(req)
}

// CreateMyRadioLoginSession gets an XSRF token from MyRadio, and will
// then log in to MyRadio, returning the LoginSession
func CreateMyRadioLoginSession(username, password string) (*LoginSession, error) {
log.Printf("creating myradio login session for user %s", username)
myr := LoginSession{
client: http.DefaultClient,
}

myr.client.Jar = cookieJar{
cookies: make(map[string][]*http.Cookie),
}

myr.timeout = time.Duration(5) * time.Second

ctx, cnl := context.WithTimeout(context.Background(), time.Duration(2)*myr.timeout)
defer cnl()

log.Println("getting myradio xsrf token for this session")
var err error
myr.xsrfToken, err = myr.getXSRFTokenFromMyRadio(ctx)
if err != nil {
log.Printf("failed to get myradio xsrf token: %v", err)
return nil, err
}

log.Printf("logging in to myradio as %s", username)
if err := myr.login(ctx, username, password); err != nil {
log.Printf("failed to login: %v", err)
return nil, err
}

return &myr, nil
}

func (e *LoginSession) login(ctx context.Context, username, password string) error {
form := url.Values{
"myradio_login-user": []string{username},
"myradio_login-password": []string{password},
"myradio_login-next": []string{"/myradio"},
"myradio_login-__xsrf-token": []string{e.xsrfToken},
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://ury.org.uk/myradio/MyRadio/login", strings.NewReader(form.Encode()))
if err != nil {
return err
}

req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

res, err := e.client.Do(req)
if err != nil {
return err
}

if res.StatusCode != http.StatusOK {
return fmt.Errorf("not OK: %v", res.Status)
}

return nil

}
68 changes: 68 additions & 0 deletions myradio-uploader/showPhoto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"bytes"
"context"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"strconv"
)

// SetShowPhoto will take a context, a showIDKey value, and a path to an image
// and set the show image using the *LoginSession
func (e *LoginSession) SetShowPhoto(ctx context.Context, showID int, path string) (string, error) {
log.Printf("%v | using myradio to set show photo to %s", showID, path)
img, err := os.Open(path)
if err != nil {
return "", err
}
defer img.Close()

contents, err := io.ReadAll(img)
if err != nil {
return "", err
}

stat, err := img.Stat()
if err != nil {
return "", err
}

body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
defer writer.Close()

part, err := writer.CreateFormFile("sched_showphoto-image_file", stat.Name())
if err != nil {
return "", err
}
part.Write(contents)

if err = writer.WriteField("sched_showphoto-show_id", strconv.Itoa(showID)); err != nil {
return "", err
}

if err = writer.WriteField("sched_showphoto-__xsrf-token", e.xsrfToken); err != nil {
return "", err
}

writer.Close()

log.Printf("%v | doing the upload request", showID)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://ury.org.uk/myradio/Scheduler/showPhoto?show_id=%v", showID), body)
if err != nil {
return "", err
}

req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("Content-Length", strconv.Itoa(body.Len()))

_, err = e.client.Do(req)

return "", err

}
65 changes: 65 additions & 0 deletions myradio-uploader/xsrf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"context"
"fmt"

"net/http"

"golang.org/x/net/html"
)

func getXSRFTokenFromInputTag(tag html.Token) string {
attrs := tag.Attr

var name string
var value string

for _, attr := range attrs {
switch attr.Key {
case "name":
name = attr.Val
case "value":
value = attr.Val
}
}
if name == "myradio_login-__xsrf-token" {
return value
}

return ""
}

func (myr *LoginSession) getXSRFTokenFromMyRadio(ctx context.Context) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://ury.org.uk/myradio", nil)
if err != nil {
return "", err
}

res, err := myr.client.Do(req)
if err != nil {
return "", err
}

defer res.Body.Close()

tkn := html.NewTokenizer(res.Body)

for {
next := tkn.Next()

switch next {
case html.SelfClosingTagToken:
tag := tkn.Token()

if tag.Data == "input" {
if token := getXSRFTokenFromInputTag(tag); token != "" {
return token, nil
}
}
case html.ErrorToken:
return "", fmt.Errorf("xsrf token not found")
}
}

}

0 comments on commit a4f89f0

Please sign in to comment.