parse rss feed #57

This commit is contained in:
Matt Walsh
2025-06-28 00:22:47 -05:00
parent 0fde88cd8f
commit 945c12e6c6
4 changed files with 175 additions and 40 deletions

View File

@@ -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);
});

View File

@@ -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 {

View File

@@ -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();

View File

@@ -54,6 +54,7 @@
<script type="module" src="scripts/modules/radar.mjs"></script>
<script type="module" src="scripts/modules/settings.mjs"></script>
<script type="module" src="scripts/modules/media.mjs"></script>
<script type="module" src="scripts/modules/custom-rss-feed.mjs"></script>
<script type="module" src="scripts/index.mjs"></script>
<!-- data -->
<script type="text/javascript" src="scripts/data/travelcities.js"></script>