diff --git a/server/scripts/modules/custom-rss-feed.mjs b/server/scripts/modules/custom-rss-feed.mjs
new file mode 100644
index 0000000..5ee728b
--- /dev/null
+++ b/server/scripts/modules/custom-rss-feed.mjs
@@ -0,0 +1,91 @@
+import Setting from './utils/setting.mjs';
+import { reset as resetScroll } from './currentweatherscroll.mjs';
+import { json } from './utils/fetch.mjs';
+
+let firstRun = true;
+
+const parser = new DOMParser();
+
+// change of enable handler
+const changeEnable = (newValue) => {
+ let newDisplay;
+ if (newValue) {
+ // add the feed to the scroll
+ getFeed(customFeed.value);
+ // show the string box
+ newDisplay = 'block';
+ } else {
+ // set scroll back to original
+ resetScroll();
+ // hide the string entry
+ newDisplay = 'none';
+ }
+ const stringEntry = document.getElementById('settings-customFeed-label');
+ if (stringEntry) {
+ stringEntry.style.display = newDisplay;
+ }
+};
+
+// get the rss feed and then swap out the current weather scroll
+const getFeed = async (url) => {
+ // skip getting the feed on first run
+ if (firstRun) return;
+
+ // test validity
+ if (url === undefined || url === '') return;
+
+ // get the text as a string
+ // it needs to be proxied, use a free service
+ const rssResponse = await json(`https://api.allorigins.win/get?url=${url}`);
+
+ // this returns a data url
+ // a few sanity checks
+ if (rssResponse.status.content_type.indexOf('application/rss+xml') < 0) return;
+ if (rssResponse.contents.indexOf('base64') > 100) return;
+ // base 64 decode everything after the comma
+ const rss = atob(rssResponse.contents.split(',')[1]);
+
+ // parse the rss
+ const doc = parser.parseFromString(rss, 'text/xml');
+
+ // get the title
+ const title = doc.querySelector('channel title').innerHTML;
+
+ // get each item
+ const titles = [...doc.querySelectorAll('item title')].map((t) => t.innerHTML);
+};
+
+// change the feed source and re-load if necessary
+const changeFeed = (newValue) => {
+ // first pass through won't have custom feed enable ready
+ if (firstRun) return;
+
+ if (customFeedEnable.value) {
+ getFeed(newValue);
+ }
+};
+
+const customFeed = new Setting('customFeed', {
+ name: 'Custom RSS Feed',
+ defaultValue: '',
+ type: 'string',
+ changeAction: changeFeed,
+});
+
+const customFeedEnable = new Setting('customFeedEnable', {
+ name: 'Enable Custom RSS Feed',
+ defaultValue: false,
+ changeAction: changeEnable,
+});
+
+// initialize the custom feed inputs on the page
+document.addEventListener('DOMContentLoaded', () => {
+ // add the controls to the page
+ const settingsSection = document.querySelector('#settings');
+ settingsSection.append(customFeedEnable.generate(), customFeed.generate());
+ // clear the first run value
+ firstRun = false;
+ // call change enable with the current value to show/hide the url box
+ // and make the call to get the feed if enabled
+ changeEnable(customFeedEnable.value);
+});
diff --git a/server/scripts/modules/media.mjs b/server/scripts/modules/media.mjs
index 9199340..353451e 100644
--- a/server/scripts/modules/media.mjs
+++ b/server/scripts/modules/media.mjs
@@ -20,45 +20,44 @@ document.addEventListener('DOMContentLoaded', () => {
});
const scanMusicDirectory = async () => {
- const parseDirectory = async (path, prefix = "") => {
- const listing = await text(path);
- const matches = [...listing.matchAll(/href="([^\"]+\.mp3)"/gi)];
- return matches.map((m) => `${prefix}${m[1]}`);
- };
+ const parseDirectory = async (path, prefix = '') => {
+ const listing = await text(path);
+ const matches = [...listing.matchAll(/href="([^"]+\.mp3)"/gi)];
+ return matches.map((m) => `${prefix}${m[1]}`);
+ };
- try {
- let files = await parseDirectory("music/");
- if (files.length === 0) {
- files = await parseDirectory("music/default/", "default/");
- }
- return { availableFiles: files };
- } catch (e) {
- console.error("Unable to scan music directory");
- console.error(e);
- return { availableFiles: [] };
- }
+ try {
+ let files = await parseDirectory('music/');
+ if (files.length === 0) {
+ files = await parseDirectory('music/default/', 'default/');
+ }
+ return { availableFiles: files };
+ } catch (e) {
+ console.error('Unable to scan music directory');
+ console.error(e);
+ return { availableFiles: [] };
+ }
};
-
const getMedia = async () => {
- try {
- const response = await fetch('playlist.json');
- if (response.ok) {
- playlist = await response.json();
- } else if (response.status === 404
- && response.headers.get('X-Weatherstar') === 'true') {
- console.warn("Couldn't get playlist.json, falling back to directory scan");
- playlist = await scanMusicDirectory();
- } else {
- console.warn(`Couldn't get playlist.json: ${response.status} ${response.statusText}`);
- playlist = { availableFiles: [] };
- }
- } catch (e) {
- console.warn("Couldn't get playlist.json, falling back to directory scan");
- playlist = await scanMusicDirectory();
- }
+ try {
+ const response = await fetch('playlist.json');
+ if (response.ok) {
+ playlist = await response.json();
+ } else if (response.status === 404
+ && response.headers.get('X-Weatherstar') === 'true') {
+ console.warn("Couldn't get playlist.json, falling back to directory scan");
+ playlist = await scanMusicDirectory();
+ } else {
+ console.warn(`Couldn't get playlist.json: ${response.status} ${response.statusText}`);
+ playlist = { availableFiles: [] };
+ }
+ } catch (e) {
+ console.warn("Couldn't get playlist.json, falling back to directory scan");
+ playlist = await scanMusicDirectory();
+ }
- enableMediaPlayer();
+ enableMediaPlayer();
};
const enableMediaPlayer = () => {
@@ -219,11 +218,11 @@ const playerEnded = () => {
};
const setTrackName = (fileName) => {
- const baseName = fileName.split('/').pop();
- const trackName = decodeURIComponent(
- baseName.replace(/\.mp3/gi, '').replace(/(_-)/gi, '')
- );
- document.getElementById('musicTrack').innerHTML = trackName;
+ const baseName = fileName.split('/').pop();
+ const trackName = decodeURIComponent(
+ baseName.replace(/\.mp3/gi, '').replace(/(_-)/gi, ''),
+ );
+ document.getElementById('musicTrack').innerHTML = trackName;
};
export {
diff --git a/server/scripts/modules/utils/setting.mjs b/server/scripts/modules/utils/setting.mjs
index 2bfe853..7c70a56 100644
--- a/server/scripts/modules/utils/setting.mjs
+++ b/server/scripts/modules/utils/setting.mjs
@@ -48,6 +48,9 @@ class Setting {
// couldn't parse as a float, store as a string
urlState = urlValue;
}
+ if (this.type === 'string' && urlValue !== undefined) {
+ urlState = urlValue;
+ }
// get existing value if present
const storedValue = urlState ?? this.getFromLocalStorage();
@@ -60,6 +63,9 @@ class Setting {
case 'select':
this.selectChange({ target: { value: this.myValue } });
break;
+ case 'string':
+ this.stringChange({ target: { value: this.myValue } });
+ break;
case 'checkbox':
default:
this.checkboxChange({ target: { checked: this.myValue } });
@@ -124,6 +130,33 @@ class Setting {
return label;
}
+ generateString() {
+ // create a string input and accompanying set button
+ const label = document.createElement('label');
+ label.for = `settings-${this.shortName}-string`;
+ label.id = `settings-${this.shortName}-label`;
+ // text input box
+ const textInput = document.createElement('input');
+ textInput.type = 'text';
+ textInput.value = this.myValue;
+ textInput.id = `settings-${this.shortName}-string`;
+ textInput.name = `settings-${this.shortName}-string`;
+ // set button
+ const setButton = document.createElement('input');
+ setButton.type = 'button';
+ setButton.value = 'Set';
+ setButton.id = `settings-${this.shortName}-button`;
+ setButton.name = `settings-${this.shortName}-button`;
+ setButton.addEventListener('click', () => {
+ this.stringChange({ target: { value: textInput.value } });
+ });
+ // assemble
+ label.append(textInput, setButton);
+
+ this.element = label;
+ return label;
+ }
+
checkboxChange(e) {
// update the state
this.myValue = e.target.checked;
@@ -146,6 +179,15 @@ class Setting {
this.changeAction(this.myValue);
}
+ stringChange(e) {
+ // update the value
+ this.myValue = e.target.value;
+ this.storeToLocalStorage(this.myValue);
+
+ // call the change action
+ this.changeAction(this.myValue);
+ }
+
storeToLocalStorage(value) {
if (!this.sticky) return;
const allSettingsString = localStorage?.getItem(SETTINGS_KEY) ?? '{}';
@@ -163,8 +205,8 @@ class Setting {
switch (this.type) {
case 'boolean':
case 'checkbox':
- return storedValue;
case 'select':
+ case 'string':
return storedValue;
default:
return null;
@@ -214,6 +256,8 @@ class Setting {
switch (this.type) {
case 'select':
return this.generateSelect();
+ case 'string':
+ return this.generateString();
case 'checkbox':
default:
return this.generateCheckbox();
diff --git a/views/index.ejs b/views/index.ejs
index 85f8f49..005ec3e 100644
--- a/views/index.ejs
+++ b/views/index.ejs
@@ -54,6 +54,7 @@
+