Revert weather overlay project and associated changes

This commit is contained in:
Ramforth
2025-10-31 21:41:56 +01:00
parent 2ee32a1cc2
commit 760769bafd
7 changed files with 53 additions and 425 deletions

View File

@@ -4,7 +4,58 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ramforth Overlays (Reference)</title> <title>Ramforth Overlays (Reference)</title>
<link rel="stylesheet" href="style.css"> <style>
body {
font-family: sans-serif;
color: white;
background-color: #222;
margin: 0;
padding: 40px;
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: #333;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
h1 {
color: #4CAF50;
}
h2 {
color: #FFC107;
border-bottom: 1px solid #4CAF50;
padding-bottom: 5px;
margin-top: 30px;
}
a {
color: #2196F3;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin-bottom: 10px;
}
.important-note {
background-color: #555;
padding: 15px;
border-left: 5px solid #FFC107;
margin-top: 20px;
}
.footer-info {
margin-top: 30px;
font-size: 0.8em;
color: #aaa;
}
</style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@@ -30,7 +81,7 @@
<h3>Weather Overlay</h3> <h3>Weather Overlay</h3>
<ul> <ul>
<li><a href="/weather_overlay/test_websocket.html">Weather Overlay (Test Page)</a></li> <li><a href="/weather_overlay/weather.html">Weather Overlay</a></li>
</ul> </ul>
<h3>System Monitor Overlay</h3> <h3>System Monitor Overlay</h3>

View File

@@ -1,50 +0,0 @@
body {
font-family: sans-serif;
color: white;
background-color: #222;
margin: 0;
padding: 40px;
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: #333;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
h1 {
color: #4CAF50;
}
h2 {
color: #FFC107;
border-bottom: 1px solid #4CAF50;
padding-bottom: 5px;
margin-top: 30px;
}
a {
color: #2196F3;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin-bottom: 10px;
}
.important-note {
background-color: #555;
padding: 15px;
border-left: 5px solid #FFC107;
margin-top: 20px;
}
.footer-info {
margin-top: 30px;
font-size: 0.8em;
color: #aaa;
}

View File

@@ -1,82 +0,0 @@
# Development Plan: Chat-Interactive Weather Overlay
This document outlines the development plan for creating a chat-interactive weather overlay for Twitch and YouTube.
## Phase 1: Project Setup
* [x] Create a new project directory: `chat-weather-overlay`
* [ ] Set up a Python virtual environment.
* [ ] Initialize a new Git repository.
## Phase 2: Backend - The Core
* **Technology:** Python with the FastAPI framework.
* **Tasks:**
* [ ] Create a basic FastAPI server.
* [ ] Implement a WebSocket endpoint for the overlay to connect to.
## Phase 3: Frontend - The Connection
* **Tasks:**
* [ ] Modify the existing `weather.html` to connect to the new backend server's WebSocket.
* [ ] Implement logic to handle simple messages from the server to confirm the connection.
## Phase 4: Backend - Twitch Integration
* **Tasks:**
* [ ] Add the `twitchio` library to the project.
* [ ] Build a bot that can join a Twitch channel and listen for chat messages.
* [ ] Implement the `!weather <location>` command parsing.
* [ ] When the command is received, send the weather information to the overlay via the WebSocket.
## Phase 5: Frontend - Command Handling
* **Tasks:**
* [ ] Update the overlay to handle the weather data sent from the backend.
* [ ] Implement the logic for the temporary display (e.g., show the requested location for 10 seconds, then revert).
* [ ] Implement the `!weather on|off` functionality.
## Phase 6: Backend - YouTube Integration
* **Tasks:**
* [ ] Add a suitable library for reading YouTube chat (e.g., `pytchat`).
* [ ] Create a YouTube chat listener.
* [ ] Integrate the YouTube chat listener with the command parsing logic.
## Phase 7: Deployment
* **Tasks:**
* [ ] Guide the user on how to deploy the backend server on their Proxmox server.
* [ ] This will likely involve using a process manager like `systemd` and a web server like `Nginx` as a reverse proxy.
## Future Vision: Monetization and New Projects
This section serves as a reminder and a place to brainstorm future possibilities for this project and related ideas.
### Monetization Strategies
If this service or similar tools become popular, we will need to consider monetization to support the infrastructure costs. Here are some potential models:
* **Tiered Subscription Model:**
* **Free Tier:** Basic functionality (e.g., the weather overlay).
* **Premium Tier:** Advanced features, such as:
* More complex overlays (e.g., real-time social media feeds, interactive polls).
* Custom branding and styling options.
* Higher rate limits or faster response times.
* Access to a library of pre-made overlay themes.
* **Pay-per-Feature:** Allow users to purchase specific features or overlays a la carte.
* **Donation/Sponsorship Model:** Rely on the community to support the project through donations or sponsorships.
### Sidelying Project Ideas
This project can serve as a foundation for a suite of tools for live streamers and content creators. Here are some ideas for related projects:
* **Unified Chat Bot:** A bot that can connect to multiple platforms (Twitch, YouTube, Discord) and provide a unified interface for moderation, commands, and alerts.
* Unified Chat Bot with simple LLM functions. Something like the Frostytools.com, where the LLM sometimes chimes in and comments on chat, summarizes the past 30 minutes. This could utilize the off-site LLM I have on ai.ramforth.net (openwebui, running ollama and models that can have personalities)
* **Interactive Stream Widgets:** A collection of interactive widgets that can be controlled by chat commands, such as:
* **Soundboard:** Let viewers trigger sound effects with chat commands.
* **On-Screen Alerts:** Display custom alerts for new followers, subscribers, donations, etc.
* **Voting/Polling System:** Create real-time polls that viewers can vote on in chat.
* **Content Creation Dashboard:** A web-based dashboard that provides streamers with a centralized view of their stream health, chat activity, and audience engagement across all platforms.
* This is a huge one. If we can build a suite that uses youtube and twitch login, we can have various tools. I want to tie this one in with the community webpage on thecafeterium.com
* **Automated Content Repurposing:** A tool that can automatically clip highlights from a stream and post them to social media platforms like Twitter or TikTok.

View File

@@ -1,22 +0,0 @@
# Weather Overlay
This overlay displays the current weather for a specific location. It's designed to be used as a browser source in streaming software like OBS Studio.
## Installation
1. **Add to OBS Studio:**
* Add a new "Browser" source to your scene.
* Set the URL to the `weather.html` file in this directory.
* Set the width and height to your desired dimensions.
2. **Customize Location:**
To set your location, you need to open the `weather.html` file in a regular web browser (like Firefox or Chrome) first.
1. Open the `weather.html` file in your browser.
2. Enter a city name in the input field and click the "Update" button.
3. The overlay will now show the weather for the new location. This location will be saved in your browser's local storage, so it will be remembered the next time you open the overlay.
4. You can now add the `weather.html` file as a browser source in OBS Studio. It will use the location you just set.
**Note:** To reset the location, you can open the browser's developer tools (usually by pressing F12), go to the "Application" or "Storage" tab, find "Local Storage", and delete the `weather_latitude` and `weather_longitude` keys.

View File

@@ -1,83 +0,0 @@
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from starlette.middleware.cors import CORSMiddleware
import httpx
import json
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://overlays.ramforth.net", "http://192.168.10.16", "https://192.168.10.16", "ws://192.168.10.16", "wss://overlays.ramforth.net", "*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# OpenWeatherMap API Configuration
OPENWEATHERMAP_API_KEY = "88764d3fd0fec25358ebce447f9f8d0b"
OPENWEATHERMAP_URL = "http://api.openweathermap.org/data/2.5/weather"
async def get_weather_data(location: str):
params = {
"q": location,
"appid": OPENWEATHERMAP_API_KEY,
"units": "metric" # or "imperial" for Fahrenheit
}
async with httpx.AsyncClient() as client:
try:
response = await client.get(OPENWEATHERMAP_URL, params=params)
response.raise_for_status() # Raise an exception for bad status codes
data = response.json()
# Extract relevant information
weather_description = data["weather"][0]["description"]
temperature = data["main"]["temp"]
humidity = data["main"]["humidity"]
wind_speed = data["wind"]["speed"]
wind_deg = data["wind"]["deg"]
return {
"location": data["name"],
"description": weather_description,
"temperature": temperature,
"humidity": humidity,
"wind_speed": wind_speed,
"wind_direction_deg": wind_deg
}
except httpx.HTTPStatusError as e:
print(f"HTTP error occurred: {e.response.status_code} - {e.response.text}")
return {"error": f"Could not fetch weather data for {location}. HTTP Error: {e.response.status_code}"}
except httpx.RequestError as e:
print(f"An error occurred while requesting {e.request.url!r}: {e}")
return {"error": f"Network error while fetching weather data for {location}."}
except KeyError as e:
print(f"Key error in weather data parsing: {e}. Full response: {data}")
return {"error": f"Error parsing weather data for {location}. Missing key: {e}"}
except Exception as e:
print(f"An unexpected error occurred: {e}")
return {"error": f"An unexpected error occurred while fetching weather for {location}."}
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
data = await websocket.receive_text()
try:
message = json.loads(data)
location = message.get("location")
if location:
weather_data = await get_weather_data(location)
await websocket.send_json(weather_data)
else:
await websocket.send_json({"error": "No location provided in message."})
except json.JSONDecodeError:
await websocket.send_json({"error": "Invalid JSON format received."})
except Exception as e:
print(f"Error in websocket processing: {e}")
await websocket.send_json({"error": f"Server error: {e}"})
except WebSocketDisconnect:
print("Client disconnected")
except Exception as e:
print(f"Unexpected WebSocket error: {e}")

View File

@@ -1,34 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
</head>
<body>
<h1>WebSocket Test</h1>
<p style="font-size: 0.8em; color: #666;">
Last Modified: <script>document.write(document.lastModified);</script>
</p>
<input type="text" id="message" placeholder="Enter message">
<button onclick="sendMessage()">Send</button>
<ul id="messages"></ul>
<script>
const ws = new WebSocket("wss://overlays.ramforth.net/weather_ws");
ws.onmessage = function(event) {
const messages = document.getElementById('messages');
const message = document.createElement('li');
const timestamp = new Date().toLocaleTimeString();
const content = document.createTextNode(`[${timestamp}] ${event.data}`);
message.appendChild(content);
messages.appendChild(message);
};
function sendMessage() {
const input = document.getElementById("message");
const location = input.value;
if (location) {
ws.send(JSON.stringify({ "location": location }));
input.value = '';
}
}
</script>
</body>
</html>

View File

@@ -1,152 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Overlay</title>
<style>
body {
font-family: sans-serif;
color: white;
background-color: rgba(0, 0, 0, 0.5);
padding: 20px;
border-radius: 10px;
}
#weather-container {
display: flex;
align-items: center;
}
#weather-icon {
width: 50px;
height: 50px;
margin-right: 20px;
}
#location-container {
background-color: rgba(0, 0, 0, 0.7);
border: 1px solid white;
padding: 10px;
margin-top: 20px;
z-index: 100;
}
#city-input {
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
}
#update-button {
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #4CAF50;
color: white;
cursor: pointer;
}
</style>
</head>
<body>
<div id="weather-container">
<img id="weather-icon" src="" alt="Weather Icon">
<div>
<h1 id="temperature"></h1>
<p id="weather-description"></p>
</div>
</div>
<div id="location-container">
<input type="text" id="city-input" placeholder="Enter city name">
<button id="update-button">Update</button>
</div>
<script>
const defaultLatitude = 40.71;
const defaultLongitude = -74.01;
function saveLocation(latitude, longitude) {
localStorage.setItem('weather_latitude', latitude);
localStorage.setItem('weather_longitude', longitude);
}
function loadLocation() {
const latitude = localStorage.getItem('weather_latitude');
const longitude = localStorage.getItem('weather_longitude');
return { latitude, longitude };
}
function fetchWeather(latitude, longitude) {
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true`;
fetch(apiUrl)
.then(response => response.json())
.then(data => {
const temperature = data.current_weather.temperature;
const weatherCode = data.current_weather.weathercode;
document.getElementById('temperature').textContent = `${temperature}°C`;
document.getElementById('weather-description').textContent = getWeatherDescription(weatherCode);
document.getElementById('weather-icon').src = getWeatherIcon(weatherCode);
})
.catch(error => console.error('Error fetching weather data:', error));
}
function getCoordinates(city) {
const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${city}`;
fetch(geocodingUrl)
.then(response => response.json())
.then(data => {
if (data.results && data.results.length > 0) {
const latitude = data.results[0].latitude;
const longitude = data.results[0].longitude;
saveLocation(latitude, longitude);
fetchWeather(latitude, longitude);
document.getElementById('location-container').style.display = 'none';
} else {
alert("Could not find city. Please try again.");
}
})
.catch(error => console.error('Error fetching coordinates:', error));
}
document.getElementById('update-button').addEventListener('click', () => {
const city = document.getElementById('city-input').value;
if (city) {
getCoordinates(city);
}
});
function getWeatherDescription(code) {
// This is a simplified mapping. You can expand it for more detailed descriptions.
if (code === 0) return "Clear sky";
if (code > 0 && code < 4) return "Partly cloudy";
if (code > 44 && code < 49) return "Fog";
if (code > 50 && code < 58) return "Drizzle";
if (code > 60 && code < 68) return "Rain";
if (code > 70 && code < 78) return "Snow";
if (code > 80 && code < 83) return "Rain showers";
if (code > 94) return "Thunderstorm";
return "Cloudy";
}
function getWeatherIcon(code) {
// This is a simplified mapping. You can expand it with more icons.
if (code === 0) return "https://openweathermap.org/img/wn/01d@2x.png"; // Clear sky
if (code > 0 && code < 4) return "https://openweathermap.org/img/wn/02d@2x.png"; // Partly cloudy
if (code > 44 && code < 49) return "https://openweathermap.org/img/wn/50d@2x.png"; // Fog
if (code > 50 && code < 58) return "https://openweathermap.org/img/wn/09d@2x.png"; // Drizzle
if (code > 60 && code < 68) return "https://openweathermap.org/img/wn/10d@2x.png"; // Rain
if (code > 70 && code < 78) return "https://openweathermap.org/img/wn/13d@2x.png"; // Snow
if (code > 80 && code < 83) return "https://openweathermap.org/img/wn/09d@2x.png"; // Rain showers
if (code > 94) return "https://openweathermap.org/img/wn/11d@2x.png"; // Thunderstorm
return "https://openweathermap.org/img/wn/03d@2x.png"; // Cloudy
}
// Initial fetch
const storedLocation = loadLocation();
if (storedLocation.latitude && storedLocation.longitude) {
fetchWeather(storedLocation.latitude, storedLocation.longitude);
} else {
fetchWeather(defaultLatitude, defaultLongitude);
}
</script>
</body>
</html>