Last active
June 25, 2023 17:39
-
-
Save severak/1e1bf8ae603ef056ea42ce52d4fa1a5a to your computer and use it in GitHub Desktop.
generates songs of the day
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"database/sql" | |
"fmt" | |
"flag" | |
"os" | |
"log" | |
"runtime" | |
"html/template" | |
"strings" | |
"net/url" | |
"errors" | |
"encoding/json" | |
_ "github.com/mattn/go-sqlite3" | |
) | |
type PlaylistItem struct { | |
Id string `json:"id"` | |
DJ string `json:"dj"` | |
} | |
type Playlist struct { | |
Title string `json:"title"` | |
Videos []PlaylistItem `json:"videos"` | |
} | |
const playerHtml = `<!DOCTYPE html> | |
<head> | |
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> | |
<title>~{{.Title}}'s radio</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link rel="stylesheet" href="//tilde.town/~severak/flogiston-tui.css"> | |
</head> | |
<html> | |
<body> | |
<div class="flogiston-page"> | |
<div class="flogiston-article"> | |
<h1>~{{.Title}}'s radio</h1> | |
<p>now playing:<br><strong id="songName">?</strong><br>recommended by DJ <a id="djName" href="#">?</a></p> | |
<div id="player"></div> | |
<hr> | |
<p>generated using <a href="https://gist.github.com/severak/1e1bf8ae603ef056ea42ce52d4fa1a5a">this script</a></p> | |
</div> | |
</div> | |
<script> | |
var playlist = {{.Videos}}; | |
var details = {}; | |
var idList = []; | |
playlist.forEach(function(vid){ | |
details[vid.id] = vid; | |
idList.push(vid.id); | |
}); | |
// 2. This code loads the IFrame Player API code asynchronously. | |
var tag = document.createElement('script'); | |
tag.src = "https://www.youtube.com/iframe_api"; | |
var firstScriptTag = document.getElementsByTagName('script')[0]; | |
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); | |
// 3. This function creates an <iframe> (and YouTube player) | |
// after the API code downloads. | |
var player; | |
function onYouTubeIframeAPIReady() { | |
player = new YT.Player('player', { | |
height: '360', | |
width: '640', | |
videoId: idList.pop(), | |
events: { | |
'onReady': onPlayerReady, | |
'onStateChange': onPlayerStateChange | |
} | |
}); | |
} | |
// 4. The API will call this function when the video player is ready. | |
function onPlayerReady(event) { | |
event.target.loadPlaylist({playlist: idList}); | |
event.target.playVideo(); | |
} | |
function updateNowPlaying() { | |
var vdata = player.getVideoData(); | |
document.getElementById('songName').textContent = vdata.title; | |
var dj = details[vdata.video_id]['dj'] | |
document.getElementById('djName').textContent = dj; | |
document.getElementById('djName').href = 'https://tilde.town/~' + dj; | |
} | |
// 5. The API calls this function when the player's state changes. | |
// The function indicates that when playing a video (state=1), | |
// the player should play for six seconds and then stop. | |
function onPlayerStateChange(event) { | |
//if (event.data == 0 || event.data == 5) { | |
updateNowPlaying(); | |
//} | |
} | |
</script> | |
</body> | |
</html> | |
` | |
func getYoutubeId(video string) (string, error) { | |
u, err := url.Parse(video) | |
if err != nil { | |
return "", errors.New("Cannot parse URL.") | |
} | |
if (u.Host != "youtu.be" && u.Host != "youtube.com" && u.Host != "www.youtube.com") { | |
return "", errors.New("Not a youtube URL.") | |
} | |
if u.Host=="youtu.be" { | |
return strings.TrimPrefix(u.Path, "/"), nil | |
} | |
q := u.Query() | |
if q.Get("v") != "" { | |
return q.Get("v"), nil | |
} | |
if q.Get("vi") != "" { | |
return q.Get("vi"), nil | |
} | |
return "", errors.New("Missing URL param.") | |
} | |
func checkErr(err error, args ...string) { | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
func main() { | |
defaultDb := "/home/caff/code/sotdbot/sotd.db" | |
if runtime.GOOS == "windows" { | |
defaultDb = "./sotd.db" | |
} | |
format := flag.String("output", "names", "output format, one of: names, json, playlist") | |
by := flag.String("by", "TOWN", "list songs posted by") | |
dbFile := flag.String("db", defaultDb, "database file to read") | |
limit := flag.Int("limit", 72, "max number of included songs") | |
flag.Parse() | |
if !(*format=="names" || *format=="json" || *format=="player" || *format=="stats") { | |
log.Fatal("Unknown format!") | |
} | |
if _, err := os.Stat(*dbFile); os.IsNotExist(err) { | |
log.Fatalf("file %s not exists!\n", *dbFile) | |
} | |
db, err := sql.Open("sqlite3", *dbFile) | |
checkErr(err) | |
defer db.Close() | |
checkErr(db.Ping()) | |
if *format=="names" { | |
outNames(db, by, limit) | |
} | |
if *format=="json" { | |
outJson(db, by, limit) | |
} | |
if *format=="player" { | |
outPlayer(db, by, limit) | |
} | |
if *format=="stats" { | |
outStats(db) | |
} | |
} | |
func outStats(db *sql.DB) { | |
var total int | |
err := db.QueryRow("SELECT count(*) FROM sotd ").Scan(&total) | |
checkErr(err) | |
fmt.Printf("%d total songs posted\n\n", total) | |
rows, err := db.Query("SELECT username, count(*) FROM sotd GROUP BY username ORDER BY count(*) DESC") | |
checkErr(err) | |
defer rows.Close() | |
for rows.Next() { | |
var count int | |
var username string | |
err := rows.Scan(&username, &count) | |
fmt.Println(username, count) | |
checkErr(err) | |
} | |
err = rows.Err() | |
checkErr(err) | |
} | |
func outPlayer(db *sql.DB, by *string, limit *int) { | |
t, err := template.New("webpage").Parse(playerHtml) | |
checkErr(err) | |
rows, err := db.Query("SELECT username, link FROM sotd ORDER BY created_at DESC") | |
if *by != "TOWN" { | |
rows, err = db.Query("SELECT username, link FROM sotd WHERE username=? ORDER BY created_at DESC", "~" + *by ) | |
} | |
checkErr(err) | |
defer rows.Close() | |
plist := Playlist{Title: *by} | |
added := 0 | |
for rows.Next() { | |
var link string | |
var username string | |
err := rows.Scan(&username, &link) | |
checkErr(err) | |
if vid, viderr := getYoutubeId(link); viderr==nil { | |
plist.Videos = append(plist.Videos, PlaylistItem{Id: vid, DJ: username}) | |
added = added + 1 | |
if added==*limit { | |
break | |
} | |
} | |
} | |
err = rows.Err() | |
checkErr(err) | |
err = t.Execute(os.Stdout, plist) | |
checkErr(err) | |
} | |
func outNames(db *sql.DB, by *string, limit *int) { | |
rows, err := db.Query("SELECT username, link, display FROM sotd ORDER BY created_at DESC") | |
if *by != "TOWN" { | |
rows, err = db.Query("SELECT username, link, display FROM sotd WHERE username=? ORDER BY created_at DESC", "~" + *by ) | |
} | |
checkErr(err) | |
defer rows.Close() | |
added := 0 | |
fmt.Println(*by + "'s playlist\n") | |
for rows.Next() { | |
var link string | |
var username string | |
var display string | |
err := rows.Scan(&username, &link, &display) | |
checkErr(err) | |
if *by=="TOWN" { | |
fmt.Printf("%s (%s) by %s\n", display, link, username) | |
} else { | |
fmt.Printf("%s (%s)\n", display, link) | |
} | |
added = added + 1 | |
if added==*limit { | |
break | |
} | |
} | |
err = rows.Err() | |
checkErr(err) | |
} | |
func outJson(db *sql.DB, by *string, limit *int) { | |
rows, err := db.Query("SELECT username, link FROM sotd ORDER BY created_at DESC") | |
if *by != "TOWN" { | |
rows, err = db.Query("SELECT username, link FROM sotd WHERE username=? ORDER BY created_at DESC", "~" + *by ) | |
} | |
checkErr(err) | |
defer rows.Close() | |
plist := Playlist{Title: *by + "'s playlist"} | |
added := 0 | |
for rows.Next() { | |
var link string | |
var username string | |
err := rows.Scan(&username, &link) | |
checkErr(err) | |
if vid, viderr := getYoutubeId(link); viderr==nil { | |
plist.Videos = append(plist.Videos, PlaylistItem{Id: vid, DJ: username}) | |
added = added + 1 | |
if added==*limit { | |
break | |
} | |
} | |
} | |
err = rows.Err() | |
checkErr(err) | |
output, err := json.MarshalIndent(plist, "", " ") | |
if err != nil { | |
checkErr(err) | |
} | |
fmt.Println(string(output)) | |
} |
May I suggest that, in Line 91, instead of:
document.getElementById('djName').href = 'https://tilde.town/' + dj;
it should be:
document.getElementById('djName').href = 'https://tilde.town/~' + dj;
That is, there should be a tilde in the hyperlink to the dj's home-page on tilde.town
Fixed. Thanks for notifying me.
Hi ~severak :
I'm at your service; it is my pleasure, and Christian duty.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO: