Skip to content

Instantly share code, notes, and snippets.

@severak
Created June 27, 2018 22:36
Show Gist options
  • Save severak/58785a9cc192fa7fc642160845b9ceba to your computer and use it in GitHub Desktop.
Save severak/58785a9cc192fa7fc642160845b9ceba to your computer and use it in GitHub Desktop.
sotd playlist 2.0
package main
import (
"database/sql"
"fmt"
"flag"
"os"
// "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"`
}
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 main() {
dbFile := flag.String("db", "/home/caff/code/sotdbot/sotd.db", "database file to read")
limit := flag.Int("limit", 72, "max number of included videos")
flag.Parse()
if _, err := os.Stat(*dbFile); os.IsNotExist(err) {
fmt.Printf("file %s not exists!\n", *dbFile)
os.Exit(2)
}
db, err := sql.Open("sqlite3", *dbFile)
checkErr(err)
defer db.Close()
checkErr(db.Ping())
name := flag.Arg(0)
rows, err := db.Query("SELECT username, link FROM sotd ORDER BY created_at DESC")
if flag.Arg(0) != "" {
rows, err = db.Query("SELECT username, link FROM sotd WHERE username=? ORDER BY created_at DESC", "~" + name )
}
if name == "" {
name = "town"
}
checkErr(err)
defer rows.Close()
plist := Playlist{Title: name + "'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))
}
func checkErr(err error, args ...string) {
if err != nil {
fmt.Println("Error")
fmt.Println("%q: %s", err, args)
}
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>town's radio</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="//tilde.town/~severak/flogiston-tui.css">
</head>
<body>
<div class="flogiston-page">
<div class="flogiston-article">
<h1 id="title">loading...</h1>
<p>now playing:<br><strong id="songName">?</strong><br>recommended by DJ <a id="djName" href="#">?</a></p>
<div id="player"></div>
<p><a href="?shuffle=1">shuffle</a> <a href="?order">play in order</a></p>
<hr>
<small>generated using <a href="https://gist.github.com/severak/9abf7080d4691d71639d879d6d82a265">this ugly script</a> by <a href="https://tilde.town/~severak">severak</a></small>
</div>
</div>
<script>
fetch('town.json').then(function(response) { return response.json() }).then(function(remote){
var playlist = remote.videos;
document.title = remote.title;
document.getElementById('title').textContent = remote.title;
var details = {};
var idList = [];
playlist.forEach(function(vid){
details[vid.id] = vid;
idList.push(vid.id);
});
var url = new URL(window.location.href);
var shuffle = url.searchParams.get("shuffle");
// 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;
window.onYouTubeIframeAPIReady = function() {
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});
if (shuffle) {
event.target.setShuffle(true);
}
}
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment