Skip to content

Instantly share code, notes, and snippets.

@severak
Last active June 25, 2023 17:39
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save severak/1e1bf8ae603ef056ea42ce52d4fa1a5a to your computer and use it in GitHub Desktop.
Save severak/1e1bf8ae603ef056ea42ce52d4fa1a5a to your computer and use it in GitHub Desktop.
generates songs of the day
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))
}
@severak
Copy link
Author

severak commented Jul 13, 2021

Fixed. Thanks for notifying me.

@phtan
Copy link

phtan commented Jul 30, 2021

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