diff --git a/.gitattributes b/.gitattributes index dfe0770..176a458 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -# Auto detect text files and perform LF normalization * text=auto diff --git a/.gitignore b/.gitignore index efbb269..7a35c3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -node_modules/* -vendor/* -public/app.* .apikey [Tt]humbs.db desktop.ini diff --git a/LICENSE b/LICENSE index 7380327..292cd77 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Julian van de Groep +Copyright (c) 2016-2021 flashwave Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e4e47a4..15afd4c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # now listening -A replacement for the old /now page on lastfm profiles! +A replacement for the old /now page on last.fm profiles! ## Usage https://now.flash.moe/#/{username} @@ -7,13 +7,5 @@ https://now.flash.moe/#/{username} ## Configuration Create an application [here](https://www.last.fm/api/account/create), take the api and place it in a file called `.apikey` in the root (*not the public dir*). -## Building -To compile the LESS and TypeScript assets you need to have the individual compilers installed, -both are available through yarn and can be installed with the following command: -`yarn global add less typescript`. - -After that just run `build.sh`. - -The server side uses a PHP script with dependencies to fetch the data without exposing the API key. -To install these dependencies you're going to need [composer](https://getcomposer.org/). -After installing composer you can simply run `composer install` in the root directory. +## Requirements +Server side needs PHP 8.0 or newer. diff --git a/build.sh b/build.sh deleted file mode 100644 index 373dd17..0000000 --- a/build.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -# delete old files, using find to avoid errors -echo "=> Cleanup" -rm -v ./public/app.js -rm -v ./public/app.css - -# styles -echo -echo "=> LESS" -lessc --verbose ./src/less/app.less ./public/app.css - -# scripts -echo -echo "=> TypeScript" -tsc \ - -p ./src/typescript/tsconfig.json \ - --listFiles \ - --listEmittedFiles diff --git a/composer.json b/composer.json deleted file mode 100644 index 6b1f6e9..0000000 --- a/composer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "require": { - "matto1990/lastfm-api": "^1.2" - } -} diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 86372c4..0000000 --- a/composer.lock +++ /dev/null @@ -1,70 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "0447259d6060720f3c189f0fdca5c123", - "content-hash": "94d9635fa82a3525da82ed39e8471a8b", - "packages": [ - { - "name": "matto1990/lastfm-api", - "version": "v1.2", - "source": { - "type": "git", - "url": "https://github.com/matto1990/PHP-Last.fm-API.git", - "reference": "1cf9a8947bf756beb876d5f8e2af398058805e08" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/matto1990/PHP-Last.fm-API/zipball/1cf9a8947bf756beb876d5f8e2af398058805e08", - "reference": "1cf9a8947bf756beb876d5f8e2af398058805e08", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "php": ">=5.3.3", - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "LastFmApi\\": "src/lastfmapi/", - "Tests\\": "tests" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marcos", - "email": "devilcius@gmail.com" - }, - { - "name": "Matt", - "email": "matt@oakes.ws" - } - ], - "description": "Last.fm webservice client", - "homepage": "https://github.com/matto1990/PHP-Last.fm-API", - "keywords": [ - "api", - "last.fm", - "webservice client" - ], - "time": "2016-03-17 16:41:24" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [] -} diff --git a/public/get-xml.php b/public/get-xml.php new file mode 100644 index 0000000..d452b4c --- /dev/null +++ b/public/get-xml.php @@ -0,0 +1,3 @@ + 'application/json', + FNP_FMT_XML => 'application/xml', +]); + +if(!isset($format)) + $format = (string)(filter_input(INPUT_GET, 'f') ?? 'json'); +$pretty = !empty($_GET['p']); + +if($format !== 'json' && $format !== 'xml') { + http_response_code(400); + return; } -if (!file_exists('../vendor/autoload.php')) { - die(view(['error' => 'Please run "composer install" in the main directory first!'])); +$charset = preg_match('#MSIE#i', $_SERVER['HTTP_USER_AGENT'] ?? '') ? '' : 'utf-8'; + +header('Content-Type: ' . FNP_FMTS[$format] . (empty($charset) ? '' : ('; charset=' . $charset))); + +function errorResponse(string $text, int $code = -1): void { + global $format, $charset; + + http_response_code(500); + + switch($format) { + case FNP_FMT_JSON: + echo json_encode(['error' => $text, 'code' => $code]); + break; + + // Has strange formatting, but XML mode exists for backwards compatibility with an unreleased variation of get.php. + case FNP_FMT_XML: + $document = new DOMDocument('1.0', $charset); + $tracks = $document->appendChild($document->createElement('Tracks')); + $track = $tracks->appendChild($document->createElement('Track')); + $track->setAttribute('error', $text); + $track->setAttribute('code', $code); + echo $document->saveXML(); + break; + } + + exit; } -require_once '../vendor/autoload.php'; +if(!is_file(FNP_API_KEY)) + errorResponse('API key file missing! Create a file called ".apikey" in the root directory and place your last.fm api key in there.'); -if (!file_exists('../.apikey')) { - die(view(['error' => 'API key file missing! Create a file called ".apikey" in the root directory and place your last.fm api key in there.'])); +$apiKey = trim(file_get_contents(FNP_API_KEY)); +$userName = (string)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_STRING); + +$curl = curl_init(FNP_API_URL . '/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' . $apiKey . '&user=' . rawurlencode($userName)); +curl_setopt_array($curl, [ + CURLOPT_AUTOREFERER => false, + CURLOPT_COOKIESESSION => false, + CURLOPT_VERBOSE => false, + CURLOPT_FAILONERROR => false, + CURLOPT_FOLLOWLOCATION => false, + CURLOPT_TCP_NODELAY => true, + CURLOPT_HEADER => false, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TCP_FASTOPEN => true, + CURLOPT_VERBOSE => false, + CURLOPT_CONNECTTIMEOUT => 2, + CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, + CURLOPT_TIMEOUT => 2, + CURLOPT_USERAGENT => 'NowListening v2 (' . $_SERVER['HTTP_HOST'] . ')', +]); +$response = json_decode(curl_exec($curl)); +curl_close($curl); + +if($response === null) + errorResponse('API request failed.'); +if(isset($response->message)) + errorResponse($response->message, $response->error ?? -1); + +switch($format) { + case FNP_FMT_JSON: + $tracks = []; + + if(!empty($response->recenttracks?->track)) + foreach($response->recenttracks->track as $recentTrack) { + $tracks[] = $track = new stdClass; + $track->name = $recentTrack->name; + if(!empty($recentTrack->{"@attr"}?->nowplaying)) + $track->nowplaying = true; + $track->mbid = $recentTrack->mbid; + $track->url = $recentTrack->url; + $track->date = $recentTrack->date?->uts ?? ''; + $track->streamable = $recentTrack->streamable; + + $track->artist = new stdClass; + $track->artist->name = $recentTrack->artist?->{"#text"} ?? ''; + $track->artist->mbid = $recentTrack->artist?->mbid ?? ''; + + $track->album = new stdClass; + $track->album->name = $recentTrack->album?->{"#text"}; + $track->album->mbid = $recentTrack->album?->mbid ?? ''; + + $track->images = new stdClass; + foreach($recentTrack->image as $trackImage) + $track->images->{$trackImage->size} = $trackImage->{"#text"}; + } + + echo json_encode($tracks, $pretty ? JSON_PRETTY_PRINT : 0); + break; + + case FNP_FMT_XML: + $document = new DOMDocument('1.0', $charset); + $document->formatOutput = $pretty; + $tracks = $document->appendChild($document->createElement('Tracks')); + + if(!empty($response->recenttracks?->track)) + foreach($response->recenttracks->track as $recentTrack) { + $elem = $tracks->appendChild($document->createElement('Track')); + + $elem->appendChild($document->createElement('Name', htmlspecialchars($recentTrack->name))); + $elem->appendChild($document->createElement('Mbid', $recentTrack->mbid)); + $elem->appendChild($document->createElement('Url', htmlspecialchars($recentTrack->url))); + $elem->appendChild($document->createElement('Date', empty($recentTrack->date?->uts) ? '' : date('c', $recentTrack->date->uts))); + $elem->appendChild($document->createElement('Streamable', empty($recentTrack->streamable) ? '0' : '1')); + $elem->appendChild($document->createElement('IsPlaying', empty($recentTrack->{"@attr"}?->nowplaying) ? '0' : '1')); + + $artist = $elem->appendChild($document->createElement('Artist')); + $artist->appendChild($document->createElement('Name', htmlspecialchars($recentTrack->artist?->{"#text"} ?? ''))); + $artist->appendChild($document->createElement('Mbid', $recentTrack->artist?->mbid ?? '')); + + $album = $elem->appendChild($document->createElement('Album')); + $album->appendChild($document->createElement('Name', htmlspecialchars($recentTrack->album?->{"#text"} ?? ''))); + $album->appendChild($document->createElement('Mbid', $recentTrack->album?->mbid ?? '')); + + $images = $elem->appendChild($document->createElement('Images')); + foreach($recentTrack->image as $trackImage) { + $name = $trackImage->size; + if($name === 'extralarge') + $name = 'ExtraLarge'; + else + $name = ucfirst($name); + $images->appendChild($document->createElement($name, htmlspecialchars($trackImage->{"#text"}))); + } + } + + echo $document->saveXML(); + break; } - -$api_key = trim(file_get_contents('../.apikey')); - -$auth = new AuthApi('setsession', ['apiKey' => $api_key]); -$user = new UserApi($auth); -$now = $user->getRecentTracks(['user' => (isset($_GET['u']) ? $_GET['u'] : ''), 'limit' => '1']); - -if ($now === false) { - $now = ['error' => 'User not found.']; -} - -echo view($now); diff --git a/public/index.html b/public/index.html index f0d2d84..7b36386 100644 --- a/public/index.html +++ b/public/index.html @@ -1,13 +1,13 @@ - + - - + + Now Listening - - - - + + + +
@@ -40,6 +40,6 @@ - + diff --git a/public/now.css b/public/now.css new file mode 100644 index 0000000..e341e8b --- /dev/null +++ b/public/now.css @@ -0,0 +1,176 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + height: 100%; + width: 100%; +} + +a { + color: inherit; + text-decoration: none; +} + +.hidden { + display: none !important; +} + +.container { + background: url('resources/grid.png') transparent; + height: 100%; + width: 100%; + color: #fff; + font: 12px/20px 'Exo 2', sans-serif; + + display: flex; + align-items: center; + justify-content: center; +} + +@media (max-width: 600px) { + .container__index, + .container__user { + width: 100%; + height: 100%; + } +} + +.container__index { + text-align: center; + background: rgba(17, 17, 17, 0.8); + box-shadow: 0 1px 4px #111; + padding: 6px 10px; + display: flex; + flex-flow: column; + justify-content: center; +} + +.container__user { + display: flex; + flex-flow: row; + box-shadow: 0 1px 4px #111; +} + +.background { + background: none no-repeat center / cover #000; + z-index: -1; + position: absolute; + height: 100%; + width: 100%; + transition: background-color 2.1s; +} + +.index__title { + font-weight: 200; + font-style: italic; + font-size: 3em; + line-height: 1.5em; +} + +.index__form { + margin: 5px; + display: flex; + justify-content: center; + box-shadow: 0 1px 4px #111; +} + +.index__username, +.index__submit { + border: 1px solid #111; + color: #fff; + padding: 2px; + font-size: 1.5em; + height: 30px; +} + +.index__username { + background: rgba(17, 17, 17, 0.5); + border-right: 0; + font-family: 'Exo 2', sans-serif; + width: 100%; +} + +.index__submit { + border-left: 0; + background: #111; + width: 30px; +} + +.index__dev { + font-size: 1.1em; + font-weight: 200; +} + +.cover { + flex-shrink: 0; + flex-grow: 0; +} + +@media (max-width: 600px) { + .cover { + display: none; + } +} + +.cover__image { + background: none no-repeat center / cover rgba(17, 17, 17, 0.8); + width: 300px; + height: 300px; +} + +.info { + flex-grow: 1; + flex-shrink: 0; + + background: rgba(17, 17, 17, 0.8); + padding: 6px 8px; + + display: flex; + flex-flow: column; +} + +@media (min-width: 601px) { + .info { + width: 300px; + height: 300px; + } +} + +.info__title { + font-size: 3em; + line-height: 1.2em; + font-style: italic; + font-weight: 200; +} + +.info__artist { + font-size: 1.5em; + line-height: 1.2em; + font-weight: 200; +} + +.info__flags { + line-height: 1.5em; + font-size: 2em; + flex-grow: 1; +} + +.info__user { + align-self: end; + + display: inline-flex; + width: 100%; +} + +.info__user-back { + cursor: pointer; +} + +.info__user-name { + flex-grow: 1; + text-align: right; +} diff --git a/public/now.js b/public/now.js new file mode 100644 index 0000000..e29c942 --- /dev/null +++ b/public/now.js @@ -0,0 +1,184 @@ +var fnp = { + defaultCoverFnp: '/resources/no-cover.png', + defaultCoverLfm: 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png', + watch: { + userName: null, + interval: 0, + }, + ui: { + mode: '', + }, +}; + +fnp.goIndex = function() { + location.hash = ''; +}; +fnp.switchUser = function(userName) { + location.hash = '#/' + encodeURIComponent((userName ?? '').toString()); +}; + +fnp.updateHash = function() { + var userName = decodeURIComponent(location.hash.substring(2)); + + if(userName.length > 0) { + this.watch.start(userName, function(response) { + if(response.error) { + this.goIndex(); + alert(response.error); + return; + } + + var cover = response[0].images.extralarge; + console.log(cover); + if(cover.length < 1 || cover === this.defaultCoverLfm) + cover = this.defaultCoverFnp; + console.log(cover); + + this.ui.setInfo(cover, response[0].name, response[0].artist.name, userName, response[0].nowplaying || false); + this.ui.goUser(); + }.bind(this)); + } else { + this.watch.stop(); + this.ui.goIndex(); + this.ui.setBackgroundFade(); + } +}; + +fnp.watch.start = function(userName, callback) { + this.stop(); + this.userName = userName = (userName || '').toString(); + this.fetch(function(response) { + if(response.error) + this.stop(); + + callback(response); + }.bind(this)); +}; +fnp.watch.stop = function() { + if(this.interval !== 0) { + clearInterval(this.interval); + this.interval = 0; + } + + this.userName = null; +}; +fnp.watch.fetch = function(callback) { + if(typeof callback !== 'function') + return; + var xhr = new XMLHttpRequest; + xhr.onreadystatechange = function() { + if(xhr.readyState !== 4) + return; + callback(JSON.parse(xhr.responseText)); + }; + xhr.open('GET', '/get.php?u=' + encodeURIComponent(this.userName)); + xhr.send(); +}; + +fnp.ui.setMode = function(modeName) { + modeName = (modeName || '').toString(); + if(this.mode === modeName) + return; + this.mode = modeName; + + for(var i = 0; i < this.modes.length; ++i) { + var mode = this.modes[i]; + mode.elem.classList[mode.name === modeName ? 'remove' : 'add']('hidden'); + } +}; +fnp.ui.goIndex = function() { + this.setMode('index'); +}; +fnp.ui.goUser = function() { + this.setMode('user'); +}; + +fnp.ui.setBackground = function(bgPath) { + if(bgPath === '::fade') { + this.setBackgroundFade(); + return; + } + + if(this.iColourFade !== 0) { + clearInterval(this.iColourFade); + this.iColourFade = 0; + } + + this.eBg.style.backgroundColor = null; + this.eBg.style.backgroundImage = 'url(\'' + (bgPath || '').toString() + '\')'; +}; +fnp.ui.setBackgroundFade = function() { + if(this.iColourFade !== 0) + return; + + this.eBg.style.backgroundImage = null; + + var fader = function() { + var colour = Math.floor(Math.random() * 0xFFFFFF).toString(16); + + if(colour.length !== 6 && colour.length !== 3) + colour = '000000'.substring(colour.length) + colour; + + this.eBg.style.backgroundColor = '#' + colour; + }.bind(this); + + this.iColourFade = setInterval(fader, 2000); + fader(); +}; + +fnp.ui.setInfo = function(cover, title, artist, user, now) { + this.setBackground(cover); + this.eInfoCover.style.backgroundImage = 'url(\'' + (cover || '').toString() + '\')'; + this.eInfoTitle.textContent = (title || '').toString(); + this.eInfoArtist.textContent = (artist || '').toString(); + this.eInfoUser.textContent = (user || '').toString(); + this.eInfoFlags.innerHTML = ''; + + if(now) { + var nowIcon = document.createElement('span'); + nowIcon.className = 'fa fa-music'; + nowIcon.title = 'Now playing'; + this.eInfoFlags.appendChild(nowIcon); + } +}; + +window.onload = function() { + fnp.ui.eIndex = document.getElementById('index'); + fnp.ui.eUser = document.getElementById('user'); + fnp.ui.eBg = document.getElementById('background'); + fnp.ui.eFormUser = document.getElementById('username'); + fnp.ui.eFormSend = document.getElementById('submit'); + fnp.ui.eInfoCover = document.getElementById('np_cover'); + fnp.ui.eInfoTitle = document.getElementById('np_title'); + fnp.ui.eInfoArtist = document.getElementById('np_artist'); + fnp.ui.eInfoUser = document.getElementById('np_user'); + fnp.ui.eInfoBack = document.getElementById('np_back'); + fnp.ui.eInfoFlags = document.getElementById('np_flags'); + fnp.ui.iColourFade = 0; + fnp.ui.modes = [ + { name: 'index', elem: fnp.ui.eIndex }, + { name: 'user', elem: fnp.ui.eUser }, + ]; + + fnp.ui.eInfoBack.onclick = function() { + fnp.goIndex(); + }; + + window.onhashchange = function() { + fnp.updateHash(); + }; + + fnp.ui.eFormSend.onclick = function() { + fnp.switchUser(fnp.ui.eFormUser.value); + }; + + fnp.ui.eFormUser.onkeydown = function(ev) { + if(typeof ev.key === 'undefined') + ev.key = ev.keyCode === 13 ? 'Enter' : '?'; + + if(ev.key === 'Enter' || ev.key === 'NumpadEnter') + fnp.switchUser(fnp.ui.eFormUser.value); + }; + + fnp.updateHash(); +}; diff --git a/src/less/app.less b/src/less/app.less deleted file mode 100644 index 9a74bc4..0000000 --- a/src/less/app.less +++ /dev/null @@ -1,174 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, -body { - height: 100%; - width: 100%; -} - -a { - color: inherit; - text-decoration: none; -} - -.hidden { - display: none !important; -} - -.container { - background: url('resources/grid.png') transparent; - height: 100%; - width: 100%; - color: #fff; - font: 12px/20px "Exo 2", sans-serif; - - display: flex; - align-items: center; - justify-content: center; - - @media (max-width: 600px) { - &__index, - &__user { - width: 100%; - height: 100%; - } - } - - &__index { - text-align: center; - background: fade(#111, 80%); - box-shadow: 0 1px 4px #111; - padding: 6px 10px; - display: flex; - flex-flow: column; - justify-content: center; - } - - &__user { - display: flex; - flex-flow: row; - box-shadow: 0 1px 4px #111; - } -} - -.background { - background: none no-repeat center / cover #000; - z-index: -1; - position: absolute; - height: 100%; - width: 100%; - transition: background-color 2.1s; -} - -.index { - &__title { - font-weight: 200; - font-style: italic; - font-size: 3em; - line-height: 1.5em; - } - - &__form { - margin: 5px; - display: flex; - justify-content: center; - box-shadow: 0 1px 4px #111; - } - - &__username, - &__submit { - border: 1px solid #111; - color: #fff; - padding: 2px; - font-size: 1.5em; - height: 30px; - } - - &__username { - background: fade(#111, 50%); - border-right: 0; - font-family: "Exo 2", sans-serif; - width: 100%; - } - - &__submit { - border-left: 0; - background: #111; - width: 30px; - } - - &__dev { - font-size: 1.1em; - font-weight: 200; - } -} - -.cover { - flex-shrink: 0; - flex-grow: 0; - - @media (max-width: 600px) { - display: none; - } - - &__image { - background: none no-repeat center / cover fade(#111, 80%); - width: 300px; - height: 300px; - } -} - -.info { - flex-grow: 1; - flex-shrink: 0; - - background: fade(#111, 80%); - padding: 6px 8px; - - display: flex; - flex-flow: column; - - @media (min-width: 601px) { - width: 300px; - height: 300px; - } - - &__title { - font-size: 3em; - line-height: 1.2em; - font-style: italic; - font-weight: 200; - } - - &__artist { - font-size: 1.5em; - line-height: 1.2em; - font-weight: 200; - } - - &__flags { - line-height: 1.5em; - font-size: 2em; - flex-grow: 1; // fill space - } - - &__user { - align-self: end; - - display: inline-flex; - width: 100%; - - &-back { - cursor: pointer; - } - - &-name { - flex-grow: 1; - text-align: right; - } - } -} diff --git a/src/typescript/AJAX.ts b/src/typescript/AJAX.ts deleted file mode 100644 index c888720..0000000 --- a/src/typescript/AJAX.ts +++ /dev/null @@ -1,137 +0,0 @@ -namespace NP -{ - export class AJAX - { - // XMLHTTPRequest container - private Request: XMLHttpRequest; - private Callbacks: any; - private Headers: any; - private URL: string; - private Send: string = null; - private Asynchronous: boolean = true; - - // Prepares the XMLHttpRequest and stuff - constructor(async: boolean = true) { - this.Request = new XMLHttpRequest(); - this.Callbacks = new Object(); - this.Headers = new Object(); - this.Asynchronous = async; - } - - // Start - public Start(method: HTTPMethod, avoidCache: boolean = false): void { - // Open the connection - this.Request.open(HTTPMethod[method], this.URL + (avoidCache ? "?no-cache=" + Date.now() : ""), this.Asynchronous); - - // Set headers - this.PrepareHeaders(); - - // Watch the ready state - this.Request.onreadystatechange = () => { - // Only invoke when complete - if (this.Request.readyState === 4) { - // Check if a callback if present - if ((typeof this.Callbacks[this.Request.status]).toLowerCase() === 'function') { - this.Callbacks[this.Request.status](); - } else { // Else check if there's a generic fallback present - if ((typeof this.Callbacks['0']).toLowerCase() === 'function') { - // Call that - this.Callbacks['0'](); - } - } - } - } - - this.Request.send(this.Send); - } - - // Stop - public Stop(): void { - this.Request = null; - } - - // Add post data - public SetSend(data: any): void { - // Storage array - var store: Array = new Array(); - - // Iterate over the object and them in the array with an equals sign inbetween - for (var item in data) { - store.push(encodeURIComponent(item) + "=" + encodeURIComponent(data[item])); - } - - // Assign to send - this.Send = store.join('&'); - } - - // Set raw post - public SetRawSend(data: string): void { - this.Send = data; - } - - // Get response - public Response(): string { - return this.Request.responseText; - } - - // Get all headers - public ResponseHeaders(): string { - return this.Request.getAllResponseHeaders(); - } - - // Get a header - public ResponseHeader(name: string): string { - return this.Request.getResponseHeader(name); - } - - // Set charset - public ContentType(type: string, charset: string = null): void { - this.AddHeader('Content-Type', type + ';charset=' + (charset ? charset : 'utf-8')); - } - - // Add a header - public AddHeader(name: string, value: string): void { - // Attempt to remove a previous instance - this.RemoveHeader(name); - - // Add the new header - this.Headers[name] = value; - } - - // Remove a header - public RemoveHeader(name: string): void { - if ((typeof this.Headers[name]).toLowerCase() !== 'undefined') { - delete this.Headers[name]; - } - } - - // Prepare Request headers - public PrepareHeaders(): void { - for (var header in this.Headers) { - this.Request.setRequestHeader(header, this.Headers[header]); - } - } - - // Adds a callback - public AddCallback(status: number, callback: Function): void { - // Attempt to remove previous instances - this.RemoveCallback(status); - - // Add the new callback - this.Callbacks[status] = callback; - } - - // Delete a callback - public RemoveCallback(status: number): void { - // Delete the callback if present - if ((typeof this.Callbacks[status]).toLowerCase() === 'function') { - delete this.Callbacks[status]; - } - } - - // Sets the URL - public SetUrl(url: string): void { - this.URL = url; - } - } -} diff --git a/src/typescript/Background.ts b/src/typescript/Background.ts deleted file mode 100644 index 282c651..0000000 --- a/src/typescript/Background.ts +++ /dev/null @@ -1,8 +0,0 @@ -namespace NP -{ - export enum Background - { - IMAGE, - COLOURFADE - } -} diff --git a/src/typescript/DOM.ts b/src/typescript/DOM.ts deleted file mode 100644 index 5c499b9..0000000 --- a/src/typescript/DOM.ts +++ /dev/null @@ -1,134 +0,0 @@ -namespace NP -{ - export class DOM - { - // Block Element Modifier class name generator - public static BEM(block: string, element: string = null, modifiers: string[] = [], firstModifierOnly: boolean = false): string - { - var className: string = ""; - - if (firstModifierOnly && modifiers.length === 0) { - return null; - } - - className += block; - - if (element !== null) { - className += "__" + element; - } - - var baseName: string = className; - - for (var _i in modifiers) { - if (firstModifierOnly) { - return baseName + "--" + modifiers[_i]; - } - - className += " " + baseName + "--" + modifiers[_i]; - } - - return className; - } - - // Shorthand for creating an element with a class and string - public static Element(name: string, className: string = null, id: string = null): HTMLElement { - var element = document.createElement(name); - - if (className !== null) { - element.className = className; - } - - if (id !== null) { - element.id = id; - } - - return element; - } - - // Shorthand for textnode - public static Text(text: string): Text { - return document.createTextNode(text); - } - - // Shorthand for getElementById (i'm lazy) - public static ID(id: string): HTMLElement { - return document.getElementById(id); - } - - // Shorthand for removing an element - public static Remove(element: HTMLElement): void { - element.parentNode.removeChild(element); - } - - // Shorthand for the first element of getElementsByClassName - public static Class(className: string): NodeListOf { - return >document.getElementsByClassName(className); - } - - // Shorthand for prepending - public static Prepend(target: HTMLElement, element: HTMLElement | Text): void { - if (target.children.length) { - target.insertBefore(element, target.firstChild); - } else { - this.Append(target, element); - } - } - - // Shorthand for appending - public static Append(target: HTMLElement, element: HTMLElement | Text): void { - target.appendChild(element); - } - - // Getting all classes of an element - public static ClassNames(target: HTMLElement): string[] { - var className: string = target.className, - classes: string[] = []; - - if (className.length > 1) { - classes = className.split(' '); - } - - return classes; - } - - // Adding classes to an element - public static AddClass(target: HTMLElement, classes: string[]): void { - for (var _i in classes) { - var current: string[] = this.ClassNames(target), - index: number = current.indexOf(classes[_i]); - - if (index >= 0) { - continue; - } - - current.push(classes[_i]); - - target.className = current.join(' '); - } - } - - // Removing classes - public static RemoveClass(target: HTMLElement, classes: string[]): void { - for (var _i in classes) { - var current: string[] = this.ClassNames(target), - index: number = current.indexOf(classes[_i]); - - if (index < 0) { - continue; - } - - current.splice(index, 1); - - target.className = current.join(' '); - } - } - - public static Clone(subject: HTMLElement): HTMLElement { - return (subject.cloneNode(true)); - } - - public static Query(query: string): NodeListOf { - return document.querySelectorAll(query); - } - } -} diff --git a/src/typescript/HTTPMethod.ts b/src/typescript/HTTPMethod.ts deleted file mode 100644 index c161b57..0000000 --- a/src/typescript/HTTPMethod.ts +++ /dev/null @@ -1,11 +0,0 @@ -namespace NP -{ - export enum HTTPMethod - { - GET, - HEAD, - POST, - PUT, - DELETE - } -} diff --git a/src/typescript/Initialisation.ts b/src/typescript/Initialisation.ts deleted file mode 100644 index dbbb5f3..0000000 --- a/src/typescript/Initialisation.ts +++ /dev/null @@ -1,4 +0,0 @@ -window.addEventListener("load", () => { - NP.UI.RegisterHooks(); - NP.UI.Update(); -}); diff --git a/src/typescript/Mode.ts b/src/typescript/Mode.ts deleted file mode 100644 index c0f2d4d..0000000 --- a/src/typescript/Mode.ts +++ /dev/null @@ -1,8 +0,0 @@ -namespace NP -{ - export enum Mode - { - INDEX, - USER - } -} diff --git a/src/typescript/UI.ts b/src/typescript/UI.ts deleted file mode 100644 index 96f282a..0000000 --- a/src/typescript/UI.ts +++ /dev/null @@ -1,127 +0,0 @@ -namespace NP -{ - export class UI - { - private static IndexElem: HTMLDivElement = DOM.ID('index'); - private static UserElem: HTMLDivElement = DOM.ID('user'); - - private static BGElem: HTMLDivElement = DOM.ID('background'); - - private static FormUsername: HTMLInputElement = DOM.ID('username'); - private static FormSubmit: HTMLButtonElement = DOM.ID('submit'); - - private static InfoCover: HTMLDivElement = DOM.ID('np_cover'); - private static InfoTitle: HTMLDivElement = DOM.ID('np_title'); - private static InfoArtist: HTMLDivElement = DOM.ID('np_artist'); - private static InfoUser: HTMLDivElement = DOM.ID('np_user'); - private static InfoBack: HTMLDivElement = DOM.ID('np_back'); - private static InfoFlags: HTMLDivElement = DOM.ID('np_flags'); - - private static ColourFadeInterval: number = null; - - public static Mode(mode: Mode): void { - switch (mode) { - case Mode.INDEX: - DOM.AddClass(this.UserElem, ['hidden']); - DOM.RemoveClass(this.IndexElem, ['hidden']); - break; - - case Mode.USER: - DOM.AddClass(this.IndexElem, ['hidden']); - DOM.RemoveClass(this.UserElem, ['hidden']); - break; - } - } - - public static Background(mode: Background, property: string = null): void { - switch (mode) { - case Background.COLOURFADE: - if (this.ColourFadeInterval !== null) { - break; - } - - this.BGElem.style.backgroundImage = null; - - var fader: Function = () => { - var colour: string = Math.floor(Math.random() * 16777215).toString(16); - - if (colour.length !== 6 && colour.length !== 3) { - colour = "000000".substring(colour.length) + colour; - } - - UI.BGElem.style.backgroundColor = "#" + colour; - }; - - this.ColourFadeInterval = setInterval(fader, 2000); - fader.call(this); - break; - - case Background.IMAGE: - if (property === null) { - break; - } - - if (this.ColourFadeInterval !== null) { - clearInterval(this.ColourFadeInterval); - this.ColourFadeInterval = null; - } - - this.BGElem.style.backgroundColor = null; - this.BGElem.style.backgroundImage = "url('{0}')".replace('{0}', property); - break; - } - } - - public static SetInfo(cover: string, title: string, artist: string, user: string, now: boolean = false): void { - this.Background(Background.IMAGE, cover); - this.InfoCover.style.backgroundImage = "url('{0}')".replace('{0}', cover); - this.InfoTitle.innerText = title; - this.InfoArtist.innerText = artist; - this.InfoUser.innerText = user; - this.InfoFlags.innerHTML = ''; - - if (now) { - var nowIcon: HTMLSpanElement = DOM.Element('span', 'fa fa-music'); - nowIcon.title = 'Now playing'; - this.InfoFlags.appendChild(nowIcon); - } - } - - public static Update(): void { - var user: string = location.hash.substring(2); - - if (user.length > 0) { - Watcher.Start(user); - UI.Mode(Mode.USER); - } else { - Watcher.Stop(); - UI.Mode(Mode.INDEX); - UI.Background(Background.COLOURFADE); - } - } - - public static RegisterHooks(): void { - UI.InfoBack.addEventListener('click', () => { - location.hash = ''; - }); - - var enter: Function = () => { - location.hash = '#/' + UI.FormUsername.value; - }; - - window.addEventListener('hashchange', () => { - UI.Update(); - }); - - UI.FormSubmit.addEventListener('click', () => { - enter.call(this); - }); - - UI.FormUsername.addEventListener('keydown', (ev) => { - if (ev.keyCode === 13) { - enter.call(this); - } - }); - } - } -} diff --git a/src/typescript/Watcher.ts b/src/typescript/Watcher.ts deleted file mode 100644 index 4dfd8ec..0000000 --- a/src/typescript/Watcher.ts +++ /dev/null @@ -1,56 +0,0 @@ -namespace NP -{ - export class Watcher - { - private static Fetcher: AJAX = null; - private static CheckIntervalId: number = null; - private static CheckTimeout: number = 15 * 1000; - private static GetPath: string = "/get.php"; - private static User: string = null; - - public static Start(user: string): void { - if (this.Fetcher !== null) { - this.Stop(); - } - - this.User = user; - this.Fetcher = new AJAX; - - this.Fetcher.AddCallback(200, () => { - var data: any = JSON.parse(Watcher.Fetcher.Response()); - console.log(data); - - if ((typeof data.error).toLowerCase() !== 'undefined') { - Watcher.Stop(); - UI.Mode(Mode.INDEX); - NP.UI.Background(NP.Background.COLOURFADE); - alert(data.error); - return; - } - - UI.Mode(Mode.USER); - - var image: string = data[0].images.large.length < 1 ? '/resources/no-cover.png' : data[0].images.large.replace('/174s/', '/300s/'), - now: boolean = (typeof data[0].nowplaying).toLowerCase() === 'undefined' ? false : data[0].nowplaying; - - UI.SetInfo(image, data[0].name, data[0].artist.name, this.User, now); - }); - - this.Check(); - - this.CheckIntervalId = setInterval(this.Check, this.CheckTimeout); - } - - public static Stop(): void { - clearInterval(this.CheckIntervalId); - this.CheckIntervalId = null; - this.Fetcher = null; - this.User = null; - } - - public static Check(): void { - Watcher.Fetcher.SetUrl(Watcher.GetPath + "?u=" + Watcher.User); - Watcher.Fetcher.Start(HTTPMethod.GET); - } - } -} diff --git a/src/typescript/tsconfig.json b/src/typescript/tsconfig.json deleted file mode 100644 index 62dc844..0000000 --- a/src/typescript/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "noImplicitAny": false, - "removeComments": true, - "outFile": "../../public/app.js" - }, - "filesGlob": [ - "**/*.ts" - ] -} \ No newline at end of file