clean
This commit is contained in:
854
news4.html
854
news4.html
@@ -1,854 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
|
||||||
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.js" integrity="sha256-6XMVI0zB8cRzfZjqKcD01PBsAy3FlDASrlC8SxCpInY=" crossorigin="anonymous"></script>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>RLX News</title>
|
|
||||||
<style>
|
|
||||||
/* --- Flexbox Sticky Footer --- */
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
min-height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#full-display-container, #sad-display-area {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container {
|
|
||||||
max-width: 800px;
|
|
||||||
position: relative;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-btn, button { font-size: 20px; padding: 8px 15px; cursor: pointer; }
|
|
||||||
button { background-color: #4CAF50; color: white; border: none; border-radius: 4px; }
|
|
||||||
button:hover { background-color: #45a049; }
|
|
||||||
.form-group label { font-size: 20px; margin-bottom: 5px; display: block; }
|
|
||||||
input, textarea { width: 100%; padding: 10px; font-size: 18px; box-sizing: border-box; }
|
|
||||||
|
|
||||||
#full-display-container {
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sad-display-area {
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#top-stories-container, #bottom-stories-container {
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
#top-stories-container { flex-basis: 50%; flex-shrink: 0; }
|
|
||||||
#bottom-stories-container { background: #e9e9e9; padding: 5px; border-radius: 8px; }
|
|
||||||
|
|
||||||
.scroller-inner {
|
|
||||||
animation-name: continuous-scroll;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-block {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes continuous-scroll {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-item { background: white; padding: 5px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); display: flex; align-items: center; }
|
|
||||||
.news-item img { width: 150px; height: 100px; object-fit: cover; border-radius: 6px; margin-right: 10px; }
|
|
||||||
.news-content { flex: 2; }
|
|
||||||
.headline { color: #333; font-size: 36px; margin: 0 0 5px 0; line-height: 1.0; font-weight: bold; }
|
|
||||||
.summary { color: #666; font-size: 28px; margin: 0 0 5px 0; line-height: 1.0; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; }
|
|
||||||
.storylink { color: #007BFF; text-decoration: none; }
|
|
||||||
.storylink:hover { text-decoration: underline; }
|
|
||||||
|
|
||||||
.relevance-high { background-color: lightblue; }
|
|
||||||
.relevance-really-high { background-color: cyan; }
|
|
||||||
.relevance-super-high { background-color: yellow; }
|
|
||||||
.relevance-mazza-high { background-color: orange; }
|
|
||||||
.relevance-cheech-high { background-color: #FF8790; }
|
|
||||||
|
|
||||||
@keyframes flashRedOutline {
|
|
||||||
0% { outline: 7px solid red; }
|
|
||||||
50% { outline: 7px solid transparent; }
|
|
||||||
100% { outline: 10px solid red; }
|
|
||||||
}
|
|
||||||
.new-story-flash {
|
|
||||||
animation: flashRedOutline 2s linear infinite;
|
|
||||||
border-radius: 8px; /* Match the news-item border-radius */
|
|
||||||
}
|
|
||||||
|
|
||||||
#ticker-container {
|
|
||||||
width: 100%;
|
|
||||||
background-color: black;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 5px 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 1000;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ticker-content {
|
|
||||||
display: inline-block;
|
|
||||||
white-space: nowrap;
|
|
||||||
/* animation removed, now handled by JS */
|
|
||||||
}
|
|
||||||
|
|
||||||
#ticker-content > span {
|
|
||||||
margin: 0 20px;
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ticker-year { color: lightgray; }
|
|
||||||
.ticker-event { color: white; }
|
|
||||||
.ticker-report { color: lightblue; }
|
|
||||||
.ticker-wikimedia { color: lightpink; } /* Style for Wikimedia events */
|
|
||||||
.ticker-holiday { color: lightgreen; } /* Style for holiday events */
|
|
||||||
.ticker-upcoming { color: cyan; } /* Style for upcoming events */
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
#full-display-container {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
.content-block {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="form-container" id="form-container">
|
|
||||||
<button class="toggle-btn" onclick="toggleForm();">Expand News Search Dialog</button>
|
|
||||||
<div id="searchForm" style="display: none;">
|
|
||||||
<div class="form-group"><label for="startTime">Start Time (Zulu/UTC):</label><input type="datetime-local" id="startTime" name="startTime" required></div>
|
|
||||||
<div class="form-group"><label for="endTime">End Time (Zulu/UTC):</label><input type="datetime-local" id="endTime" name="endTime" required></div>
|
|
||||||
<div class="form-group"><label for="keyTerms">Key Terms (comma-separated):</label><textarea id="keyTerms" name="keyTerms" rows="3" required>weather,flood,fire,fog,snow,emergency,wind,ice,rain,power,explosion,warmer,colder,drown,stream,river,air,wind,destroyed,rime,glaze,river,ice,creek,crash,thunder,spinup,black ice,fog,spill,pileup,pile-up,gust,frozen,funnel,rainfall,fatal,injury,sleet,injured,frost,dead,death,landslide,culvert,slippery,wildfire,tornado,blizzard,creek,hail,thunderstorm,downburst,microburst,crash,heatstroke,derecho,lightning,hypothermia,slide,flow,ski,water,innundation,victim,victims,flooding,flooded,snowing,freezing rain,clouds,cloud,storm,aircraft</textarea></div>
|
|
||||||
<button onclick="updatenews();">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="full-display-container"></div>
|
|
||||||
|
|
||||||
<div class="display-area" id="sad-display-area" style="display: none;">
|
|
||||||
<div id="top-stories-container"></div>
|
|
||||||
<div id="bottom-stories-container"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="ticker-container">
|
|
||||||
<div id="ticker-content"></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
let lastTickerData = null;
|
|
||||||
let isSadMode = false;
|
|
||||||
var refreshTimer;
|
|
||||||
var tickerWatchdogTimer;
|
|
||||||
const NUM_TOP_STORIES = 8;
|
|
||||||
const BOTTOM_SCROLLER_SPEED_MULTIPLIER = 2;
|
|
||||||
window.currentWikimediaEvents = []; // Initialize the global variable
|
|
||||||
let tickerCycleCount = 0;
|
|
||||||
let upcomingHolidays = [];
|
|
||||||
let triviaQuestions = [];
|
|
||||||
let lastTriviaFetchTime = 0;
|
|
||||||
|
|
||||||
// Always fetch fresh Wikimedia events
|
|
||||||
// No need to cache them between cycles
|
|
||||||
|
|
||||||
const TARGET_BROADCAST_SECONDS = 150;
|
|
||||||
const PIXELS_PER_SECOND_SPEED = 150; // Adjust this value to control scroll speed. Higher is faster.
|
|
||||||
let lastTickerUpdateTime = Date.now();
|
|
||||||
let lastNewsData = null;
|
|
||||||
let currentNewsUrl = 'https://wx.stoat.org/lsr.php?news3=potato';
|
|
||||||
|
|
||||||
let nextTickerHtml = null;
|
|
||||||
let animationId = null;
|
|
||||||
let tickerPosition = 0;
|
|
||||||
let lastTime = 0;
|
|
||||||
let tickerContent = document.getElementById('ticker-content');
|
|
||||||
let injectionHtml = null;
|
|
||||||
|
|
||||||
function animateTicker(currentTime) {
|
|
||||||
if (!lastTime) lastTime = currentTime;
|
|
||||||
const deltaTime = (currentTime - lastTime) / 1000;
|
|
||||||
lastTime = currentTime;
|
|
||||||
tickerPosition -= PIXELS_PER_SECOND_SPEED * deltaTime;
|
|
||||||
const scrollWidth = tickerContent.scrollWidth;
|
|
||||||
if (tickerPosition <= -scrollWidth) {
|
|
||||||
updateTickerContent();
|
|
||||||
tickerPosition = 0;
|
|
||||||
}
|
|
||||||
tickerContent.style.transform = `translateX(${tickerPosition}px)`;
|
|
||||||
animationId = requestAnimationFrame(animateTicker);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTickerContent() {
|
|
||||||
if (nextTickerHtml) {
|
|
||||||
tickerContent.innerHTML = nextTickerHtml;
|
|
||||||
const containerWidth = document.getElementById('ticker-container').clientWidth;
|
|
||||||
tickerPosition = -containerWidth;
|
|
||||||
nextTickerHtml = null;
|
|
||||||
// Fetch new
|
|
||||||
fetchAndDisplayTickerData(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchAndDisplayNews(url = 'https://wx.stoat.org/lsr.php?news3=potato') {
|
|
||||||
// Update the current URL if provided
|
|
||||||
if (url) {
|
|
||||||
currentNewsUrl = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.getJSON(currentNewsUrl, function(newsData) {
|
|
||||||
// Sort the data
|
|
||||||
newsData.sort((a, b) => {
|
|
||||||
if (b.impact_score !== a.impact_score) return b.impact_score - a.impact_score;
|
|
||||||
return new Date(b.timeutc) - new Date(a.timeutc);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if data has changed
|
|
||||||
if (lastNewsData && JSON.stringify(newsData) === JSON.stringify(lastNewsData)) {
|
|
||||||
console.log('News data unchanged, skipping update');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update cache
|
|
||||||
lastNewsData = newsData;
|
|
||||||
|
|
||||||
if (isSadMode) {
|
|
||||||
const topContainer = document.getElementById('top-stories-container');
|
|
||||||
const bottomContainer = document.getElementById('bottom-stories-container');
|
|
||||||
topContainer.innerHTML = '';
|
|
||||||
bottomContainer.innerHTML = '';
|
|
||||||
|
|
||||||
const topStories = newsData.slice(0, NUM_TOP_STORIES);
|
|
||||||
const scrollingStories = newsData.slice(NUM_TOP_STORIES);
|
|
||||||
|
|
||||||
function createScroller(stories, durationMultiplier, isBottomScroller = false) {
|
|
||||||
if (stories.length === 0) return null;
|
|
||||||
const scrollerInner = document.createElement('div');
|
|
||||||
scrollerInner.className = 'scroller-inner';
|
|
||||||
const contentBlock1 = document.createElement('div');
|
|
||||||
contentBlock1.className = 'content-block';
|
|
||||||
stories.forEach(news => contentBlock1.appendChild(createNewsItem(news)));
|
|
||||||
const contentBlock2 = contentBlock1.cloneNode(true);
|
|
||||||
contentBlock2.setAttribute('aria-hidden', 'true');
|
|
||||||
scrollerInner.appendChild(contentBlock1);
|
|
||||||
scrollerInner.appendChild(contentBlock2);
|
|
||||||
const duration = stories.length * durationMultiplier;
|
|
||||||
scrollerInner.style.animationName = 'continuous-scroll';
|
|
||||||
scrollerInner.style.animationDuration = `${duration}s`;
|
|
||||||
// Ensure no delay is applied to any scroller
|
|
||||||
scrollerInner.style.animationDelay = '0s';
|
|
||||||
return scrollerInner;
|
|
||||||
}
|
|
||||||
|
|
||||||
const topScroller = createScroller(topStories, 7, false);
|
|
||||||
if (topScroller) topContainer.appendChild(topScroller);
|
|
||||||
|
|
||||||
const bottomScroller = createScroller(scrollingStories, BOTTOM_SCROLLER_SPEED_MULTIPLIER, true);
|
|
||||||
if (bottomScroller) bottomContainer.appendChild(bottomScroller);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const fullContainer = document.getElementById('full-display-container');
|
|
||||||
fullContainer.innerHTML = '';
|
|
||||||
newsData.forEach(news => fullContainer.appendChild(createNewsItem(news)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNewsItem(news) {
|
|
||||||
const newsItem = document.createElement('div');
|
|
||||||
newsItem.className = 'news-item';
|
|
||||||
let score = news.impact_score;
|
|
||||||
const storyTime = new Date(news.timeutc);
|
|
||||||
const currentTime = new Date();
|
|
||||||
const oneHourInMs = 3600000;
|
|
||||||
|
|
||||||
// Add flashRedOutline class if the story is less than 1 hour old
|
|
||||||
if (currentTime - storyTime < oneHourInMs) {
|
|
||||||
newsItem.classList.add('new-story-flash');
|
|
||||||
}
|
|
||||||
|
|
||||||
const relevanceClasses = {
|
|
||||||
'relevance-high': score > 15,
|
|
||||||
'relevance-really-high': score > 25,
|
|
||||||
'relevance-super-high': score > 50,
|
|
||||||
'relevance-mazza-high': score > 90,
|
|
||||||
'relevance-cheech-high': score > 150
|
|
||||||
};
|
|
||||||
Object.entries(relevanceClasses).filter(([, c]) => c).forEach(([cN]) => newsItem.classList.add(cN));
|
|
||||||
|
|
||||||
newsItem.innerHTML = `<a href="${news.storylink}" target="_blank"><img src="${news.imageurl}"></a><div class="news-content"><h2 class="headline"><a href="${news.storylink}" target="_blank" class="storylink">(${extractTextBetweenHttpAndCom(news.storylink)}) ${news.headline}</a></h2><p class="summary">${news.summary} ${convertPostgresTimestamp(news.timeutc)}L</p></div>`;
|
|
||||||
return newsItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractTextBetweenHttpAndCom(url) {
|
|
||||||
url = url.replace(/www\./, '');
|
|
||||||
const match = url.match(/https?:\/\/(.*?)\.com/);
|
|
||||||
return match && match[1] ? match[1].toUpperCase() : 'FAUXNEWS';
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertPostgresTimestamp(timestamp) {
|
|
||||||
const d = new Date(timestamp.replace('Z', ''));
|
|
||||||
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleForm() {
|
|
||||||
const formContent = document.getElementById("searchForm");
|
|
||||||
const toggleBtn = document.querySelector(".toggle-btn");
|
|
||||||
if (formContent.style.display === "none") {
|
|
||||||
formContent.style.display = "block";
|
|
||||||
toggleBtn.textContent = "Collapse News Search Dialog";
|
|
||||||
if (refreshTimer) clearInterval(refreshTimer);
|
|
||||||
} else {
|
|
||||||
formContent.style.display = "none";
|
|
||||||
toggleBtn.textContent = "Expand News Search Dialog";
|
|
||||||
// Always use the current URL
|
|
||||||
fetchAndDisplayNews(currentNewsUrl);
|
|
||||||
refreshTimer = setInterval(() => {
|
|
||||||
fetchAndDisplayNews(currentNewsUrl);
|
|
||||||
}, 300000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function completelyHide() { document.getElementById("form-container").style.display = "none"; }
|
|
||||||
|
|
||||||
function updatenews() {
|
|
||||||
const start = document.getElementById("startTime").value;
|
|
||||||
const end = document.getElementById("endTime").value;
|
|
||||||
const keyTerms = document.getElementById("keyTerms").value;
|
|
||||||
const terms = keyTerms.split(',');
|
|
||||||
let arrayterms = terms.map(term => `key[]=${encodeURIComponent(term)}`).join('&');
|
|
||||||
url = `lsr.php?newsarchive=true&start=${start}&end=${end}&${arrayterms}`;
|
|
||||||
// Clear the cache to force an update
|
|
||||||
lastNewsData = null;
|
|
||||||
fetchAndDisplayNews(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sadCheck() {
|
|
||||||
const params = new URLSearchParams(document.location.search);
|
|
||||||
if (params.has("sad") || params.has("SAD")) {
|
|
||||||
isSadMode = true;
|
|
||||||
completelyHide();
|
|
||||||
Object.assign(document.documentElement.style, {height: '100%'});
|
|
||||||
Object.assign(document.body.style, {height: '100%', overflow: 'hidden', display: 'flex', flexDirection: 'column'});
|
|
||||||
document.getElementById('sad-display-area').style.display = 'flex';
|
|
||||||
document.getElementById('full-display-container').style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function format_date_with_ordinal(date) {
|
|
||||||
const day = date.getDate();
|
|
||||||
const month = date.toLocaleString('default', { month: 'long' });
|
|
||||||
const get_ordinal_suffix = (day) => {
|
|
||||||
if (day > 3 && day < 21) return 'th';
|
|
||||||
switch (day % 10) {
|
|
||||||
case 1: return "st";
|
|
||||||
case 2: return "nd";
|
|
||||||
case 3: return "rd";
|
|
||||||
default: return "th";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const suffix = get_ordinal_suffix(day);
|
|
||||||
return `${month} ${day}${suffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchWikimediaEvents() {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// Get the individual components
|
|
||||||
const year = now.getFullYear();
|
|
||||||
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed, so add 1
|
|
||||||
const day = String(now.getDate()).padStart(2, '0');
|
|
||||||
const hours = String(now.getHours()).padStart(2, '0');
|
|
||||||
|
|
||||||
// Concatenate them into the final string
|
|
||||||
const formattedDate = `${year}${month}${day}${hours}`;
|
|
||||||
|
|
||||||
|
|
||||||
const url = 'https://wx.stoat.org/calendar/wikimedia_onthisday.json';
|
|
||||||
const cacheBustingUrl = `${url}?v=${formattedDate}`;
|
|
||||||
|
|
||||||
return $.getJSON(cacheBustingUrl)
|
|
||||||
.done(function(data) {
|
|
||||||
if (data && data.events && Array.isArray(data.events) && data.events.length > 0) {
|
|
||||||
// Always shuffle and use all events
|
|
||||||
window.currentWikimediaEvents = [...data.events].sort(() => 0.5 - Math.random());
|
|
||||||
} else {
|
|
||||||
console.warn("Wikimedia JSON is empty, invalid, or does not contain an 'events' array.");
|
|
||||||
window.currentWikimediaEvents = [];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
||||||
console.error(`Failed to load from ${cacheBustingUrl}. Status: ${textStatus}, Error: ${errorThrown}`);
|
|
||||||
window.currentWikimediaEvents = [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchHolidays() {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// Format today's date as YYYY-MM-DD to match the API response keys
|
|
||||||
const year = now.getFullYear();
|
|
||||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(now.getDate()).padStart(2, '0');
|
|
||||||
const todayFormatted = `${year}-${month}-${day}`;
|
|
||||||
|
|
||||||
// Get the individual components for cache busting
|
|
||||||
const hours = String(now.getHours()).padStart(2, '0');
|
|
||||||
const formattedDate = `${year}${month}${day}${hours}`;
|
|
||||||
|
|
||||||
const url = 'https://calendar.wx4rlx.org/get_holidays.py';
|
|
||||||
const cacheBustingUrl = `${url}?time=${formattedDate}`;
|
|
||||||
|
|
||||||
return $.getJSON(cacheBustingUrl)
|
|
||||||
.done(function(data) {
|
|
||||||
if (data && data.holidays) {
|
|
||||||
// Get holidays for today using the formatted date as the key
|
|
||||||
window.currentHolidays = data.holidays[todayFormatted] || [];
|
|
||||||
|
|
||||||
// Store upcoming holidays for the next 9 days
|
|
||||||
upcomingHolidays = [];
|
|
||||||
for (let i = 1; i <= 9; i++) {
|
|
||||||
const nextDate = new Date(now);
|
|
||||||
nextDate.setDate(now.getDate() + i);
|
|
||||||
const nextYear = nextDate.getFullYear();
|
|
||||||
const nextMonth = String(nextDate.getMonth() + 1).padStart(2, '0');
|
|
||||||
const nextDay = String(nextDate.getDate()).padStart(2, '0');
|
|
||||||
const nextFormatted = `${nextYear}-${nextMonth}-${nextDay}`;
|
|
||||||
|
|
||||||
const nextHolidays = data.holidays[nextFormatted] || [];
|
|
||||||
if (nextHolidays.length > 0) {
|
|
||||||
upcomingHolidays.push({
|
|
||||||
date: nextDate,
|
|
||||||
holidays: nextHolidays
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("Holidays JSON is empty, invalid, or does not contain 'holidays'.");
|
|
||||||
window.currentHolidays = [];
|
|
||||||
upcomingHolidays = [];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
||||||
console.error(`Failed to load holidays from ${cacheBustingUrl}. Status: ${textStatus}, Error: ${errorThrown}`);
|
|
||||||
window.currentHolidays = [];
|
|
||||||
upcomingHolidays = [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchTriviaQuestions() {
|
|
||||||
const now = Date.now();
|
|
||||||
// Check if we need to fetch new questions and respect the 5-second rate limit
|
|
||||||
if (triviaQuestions.length > 10 || now - lastTriviaFetchTime < 5000) {
|
|
||||||
return $.Deferred().resolve().promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
lastTriviaFetchTime = now;
|
|
||||||
const url = 'https://opentdb.com/api.php?amount=50&type=multiple';
|
|
||||||
return $.getJSON(url)
|
|
||||||
.done(function(data) {
|
|
||||||
if (data.response_code === 0 && data.results) {
|
|
||||||
triviaQuestions = data.results;
|
|
||||||
console.log(`Fetched ${triviaQuestions.length} trivia questions`);
|
|
||||||
} else {
|
|
||||||
console.warn('Trivia API returned non-zero response code or no results');
|
|
||||||
triviaQuestions = [];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
||||||
console.error(`Failed to fetch trivia questions: ${textStatus}, ${errorThrown}`);
|
|
||||||
triviaQuestions = [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchInjection() {
|
|
||||||
const injectionApiUrl = 'https://calendar.wx4rlx.org/onetime.py?action=api';
|
|
||||||
return $.ajax({
|
|
||||||
url: injectionApiUrl,
|
|
||||||
dataType: 'text' // Treat the response as plain text
|
|
||||||
})
|
|
||||||
.done(function(data) {
|
|
||||||
if (data && data.trim().length > 0) {
|
|
||||||
injectionHtml = data;
|
|
||||||
console.log('Injection content fetched:', injectionHtml);
|
|
||||||
} else {
|
|
||||||
injectionHtml = null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.fail(function() {
|
|
||||||
console.log('No injection content available or error fetching');
|
|
||||||
injectionHtml = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchAndDisplayTickerData(startImmediately = true) {
|
|
||||||
// First, fetch the injection content
|
|
||||||
fetchInjection().always(function() {
|
|
||||||
// Then fetch other data
|
|
||||||
$.when(fetchWikimediaEvents(), fetchHolidays(), fetchTriviaQuestions()).always(function() {
|
|
||||||
const tickerApiUrl = 'https://calendar.wx4rlx.org/?action=api';
|
|
||||||
$.getJSON(tickerApiUrl, function(data) {
|
|
||||||
if (data.status !== 'success') return;
|
|
||||||
updateTickerLastUpdateTime();
|
|
||||||
|
|
||||||
// Always update the ticker with fresh data
|
|
||||||
const today = new Date();
|
|
||||||
const currentYear = today.getFullYear();
|
|
||||||
const formatted_date = format_date_with_ordinal(today);
|
|
||||||
const tickerContent = $('#ticker-content');
|
|
||||||
|
|
||||||
let localItems = [];
|
|
||||||
data.events.forEach(item => localItems.push({ date: item.date, text: item.event, type: 'event' }));
|
|
||||||
data.weather_reports.forEach(item => localItems.push({ date: item.date, text: item.report, type: 'report' }));
|
|
||||||
|
|
||||||
// Increment cycle count
|
|
||||||
tickerCycleCount++;
|
|
||||||
|
|
||||||
// Add upcoming events based on cycle count
|
|
||||||
let upcomingEventItem = null;
|
|
||||||
if (upcomingHolidays.length > 0) {
|
|
||||||
// Every 10 cycles takes priority over every other cycle
|
|
||||||
if (tickerCycleCount % 10 === 0) {
|
|
||||||
const nextFiveDays = upcomingHolidays.slice(0, 5);
|
|
||||||
let upcomingTexts = [];
|
|
||||||
nextFiveDays.forEach(day => {
|
|
||||||
const formattedDay = format_date_with_ordinal(day.date);
|
|
||||||
upcomingTexts.push(`${formattedDay} - ${day.holidays.join(', ')}`);
|
|
||||||
});
|
|
||||||
if (upcomingTexts.length > 0) {
|
|
||||||
upcomingEventItem = {
|
|
||||||
date: today.toISOString().split('T')[0],
|
|
||||||
text: 'Upcoming Special Days: ' + upcomingTexts.join('; '),
|
|
||||||
type: 'upcoming',
|
|
||||||
year: 'Upcoming'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Only show tomorrow's events if it's an even cycle AND not a multiple of 10
|
|
||||||
else if (tickerCycleCount % 2 === 0) {
|
|
||||||
const tomorrow = upcomingHolidays[0];
|
|
||||||
if (tomorrow) {
|
|
||||||
upcomingEventItem = {
|
|
||||||
date: tomorrow.date.toISOString().split('T')[0],
|
|
||||||
text: 'Upcoming Special Days: Tomorrow - ' + tomorrow.holidays.join(', '),
|
|
||||||
type: 'upcoming',
|
|
||||||
year: 'Tomorrow'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add today's holidays to local items
|
|
||||||
if (window.currentHolidays && window.currentHolidays.length > 0) {
|
|
||||||
localItems.push({
|
|
||||||
date: today.toISOString().split('T')[0],
|
|
||||||
text: 'Special Days: ' + window.currentHolidays.join(', '),
|
|
||||||
type: 'holiday',
|
|
||||||
year: 'Today'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add injection HTML at the beginning if available
|
|
||||||
// Use the injectionHtml that was fetched at the start of this function
|
|
||||||
if (injectionHtml) {
|
|
||||||
localItems.unshift({
|
|
||||||
date: today.toISOString().split('T')[0],
|
|
||||||
text: injectionHtml,
|
|
||||||
type: 'injection',
|
|
||||||
year: 'INJECTION'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add upcoming event at the very end if it exists
|
|
||||||
if (upcomingEventItem) {
|
|
||||||
localItems.push(upcomingEventItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort items by year
|
|
||||||
localItems.sort((a, b) => {
|
|
||||||
const getYear = (item) => {
|
|
||||||
if (item.year !== undefined) {
|
|
||||||
// Handle 'INJECTION', 'Today', 'Tomorrow', 'Upcoming' etc.
|
|
||||||
if (isNaN(Number(item.year))) return Infinity;
|
|
||||||
return Number(item.year);
|
|
||||||
}
|
|
||||||
return Number(item.date.split('-')[0]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const yearA = getYear(a);
|
|
||||||
const yearB = getYear(b);
|
|
||||||
|
|
||||||
if (isNaN(yearA) && isNaN(yearB)) return 0;
|
|
||||||
if (isNaN(yearA)) return 1;
|
|
||||||
if (isNaN(yearB)) return -1;
|
|
||||||
|
|
||||||
return yearA - yearB;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Calculate duration of local items
|
|
||||||
const tempLocalHtml = buildTickerHtml(localItems, currentYear);
|
|
||||||
tickerContent.html(tempLocalHtml);
|
|
||||||
const localWidth = tickerContent[0].scrollWidth;
|
|
||||||
const localDuration = localWidth / PIXELS_PER_SECOND_SPEED;
|
|
||||||
|
|
||||||
// Determine number of Wikimedia items to add
|
|
||||||
let numToSprinkle = 0;
|
|
||||||
if (window.currentWikimediaEvents && window.currentWikimediaEvents.length > 0) {
|
|
||||||
// Always include at least one item
|
|
||||||
numToSprinkle = 1;
|
|
||||||
|
|
||||||
// Calculate average width per Wikimedia item using the first few events
|
|
||||||
const sampleEvents = window.currentWikimediaEvents.slice(0, Math.min(5, window.currentWikimediaEvents.length));
|
|
||||||
let totalWidth = 0;
|
|
||||||
sampleEvents.forEach(event => {
|
|
||||||
const tempWikiItem = { date: `${event.year}-01-01`, text: event.text, type: 'wikimedia', year: event.year };
|
|
||||||
tickerContent.html(buildTickerHtml([tempWikiItem], currentYear));
|
|
||||||
totalWidth += tickerContent[0].scrollWidth;
|
|
||||||
});
|
|
||||||
|
|
||||||
const avgWikiWidth = totalWidth / sampleEvents.length;
|
|
||||||
const timePerWikiItem = avgWikiWidth / PIXELS_PER_SECOND_SPEED;
|
|
||||||
|
|
||||||
// Add more items if there's time
|
|
||||||
const durationGap = TARGET_BROADCAST_SECONDS - localDuration;
|
|
||||||
if (timePerWikiItem > 0 && durationGap > timePerWikiItem) {
|
|
||||||
const additionalItems = Math.floor((durationGap - timePerWikiItem) / timePerWikiItem);
|
|
||||||
numToSprinkle += Math.max(0, additionalItems);
|
|
||||||
}
|
|
||||||
numToSprinkle = Math.min(numToSprinkle, window.currentWikimediaEvents.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Wikimedia items to local items
|
|
||||||
if (numToSprinkle > 0 && window.currentWikimediaEvents && window.currentWikimediaEvents.length > 0) {
|
|
||||||
const eventsToAdd = window.currentWikimediaEvents.slice(0, numToSprinkle);
|
|
||||||
eventsToAdd.forEach(event => {
|
|
||||||
localItems.push({ date: `${event.year}-01-01`, text: event.text, type: 'wikimedia', year: event.year });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Re-sort with the new items
|
|
||||||
localItems.sort((a, b) => {
|
|
||||||
const getYear = (item) => {
|
|
||||||
if (item.year !== undefined) {
|
|
||||||
if (isNaN(Number(item.year))) return Infinity;
|
|
||||||
return Number(item.year);
|
|
||||||
}
|
|
||||||
return Number(item.date.split('-')[0]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const yearA = getYear(a);
|
|
||||||
const yearB = getYear(b);
|
|
||||||
|
|
||||||
if (isNaN(yearA) && isNaN(yearB)) return 0;
|
|
||||||
if (isNaN(yearA)) return 1;
|
|
||||||
if (isNaN(yearB)) return -1;
|
|
||||||
|
|
||||||
return yearA - yearB;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalContentHtml = buildTickerHtml(localItems, currentYear, formatted_date);
|
|
||||||
|
|
||||||
// Set the content
|
|
||||||
tickerContent.html(finalContentHtml);
|
|
||||||
nextTickerHtml = finalContentHtml; // For next update
|
|
||||||
|
|
||||||
if (startImmediately) {
|
|
||||||
if (!animationId) {
|
|
||||||
animationId = requestAnimationFrame(animateTicker);
|
|
||||||
}
|
|
||||||
// Clear injectionHtml after using it to prevent reuse in next cycle
|
|
||||||
injectionHtml = null;
|
|
||||||
// Fetch new for next cycle, which will fetch a new injection
|
|
||||||
fetchAndDisplayTickerData(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}).fail(function() {
|
|
||||||
console.error("Failed to fetch data for the horizontal ticker.");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTickerHtml(items, currentYear, formatted_date) {
|
|
||||||
let contentHtml = `<span style="display: inline-block; width: 100vw;"></span>`;
|
|
||||||
|
|
||||||
// First, add injection items at the very beginning
|
|
||||||
const injectionItems = items.filter(item => item.type === 'injection');
|
|
||||||
injectionItems.forEach(item => {
|
|
||||||
contentHtml += `<span>${item.text}</span>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add the "On This Day" header
|
|
||||||
if (formatted_date) {
|
|
||||||
contentHtml += `<span><span class="ticker-event">On This Day, ${formatted_date}:</span></span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all other items (excluding injection items which we've already added)
|
|
||||||
items.filter(item => item.type !== 'injection').forEach(item => {
|
|
||||||
const year = item.year || parseInt(item.date.split('-')[0]);
|
|
||||||
let textClass = `ticker-${item.type}`;
|
|
||||||
// Add a specific class for holidays and upcoming events
|
|
||||||
if (item.type === 'holiday') {
|
|
||||||
textClass = 'ticker-holiday';
|
|
||||||
} else if (item.type === 'upcoming') {
|
|
||||||
textClass = 'ticker-upcoming';
|
|
||||||
}
|
|
||||||
const yearDiff = currentYear - year;
|
|
||||||
let anniversaryPrefix = '';
|
|
||||||
// Only show anniversary for positive year differences (past events)
|
|
||||||
// Skip for holiday type
|
|
||||||
if (item.type !== 'holiday' && yearDiff > 0 && yearDiff % 5 === 0) {
|
|
||||||
anniversaryPrefix = `<span style="color: yellow; font-weight: bold;">${yearDiff} Years Ago: </span>`;
|
|
||||||
}
|
|
||||||
let itemText = item.text;
|
|
||||||
let yearText = year;
|
|
||||||
const arbitraryLength = 500;
|
|
||||||
if (item.text.length > arbitraryLength) {
|
|
||||||
const mazzaImgTag = '<img src="mazza.png" alt="Mazza" style="height: 1.2em; vertical-align: middle; margin: 0 0.3em;">';
|
|
||||||
const imageCount = Math.floor((item.text.length - arbitraryLength) / 200);
|
|
||||||
const imageTags = mazzaImgTag.repeat(imageCount);
|
|
||||||
yearText = imageTags ? `${imageTags} ${year}` : year;
|
|
||||||
}
|
|
||||||
// For holidays and upcoming events, don't show the year prefix
|
|
||||||
if (item.type === 'holiday' || item.type === 'upcoming') {
|
|
||||||
contentHtml += `<span><span class="${textClass}">${itemText}</span></span>`;
|
|
||||||
} else {
|
|
||||||
contentHtml += `<span>${anniversaryPrefix}<span class="ticker-year">${yearText}:</span> <span class="${textClass}">${itemText}</span></span>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (formatted_date) {
|
|
||||||
contentHtml += `<span><span class="ticker-event">Office/Local Event</span></span>`;
|
|
||||||
contentHtml += `<span><span class="ticker-wikimedia">World Event</span></span>`;
|
|
||||||
contentHtml += `<span><span class="ticker-report">Local Weather Event</span></span>`;
|
|
||||||
|
|
||||||
// Add trivia question if available
|
|
||||||
if (triviaQuestions.length > 0) {
|
|
||||||
const trivia = triviaQuestions.shift();
|
|
||||||
// Decode HTML entities in question and answers
|
|
||||||
const question = $('<div>').html(trivia.question).text();
|
|
||||||
const correctAnswer = $('<div>').html(trivia.correct_answer).text();
|
|
||||||
const allAnswers = [correctAnswer, ...trivia.incorrect_answers.map(ans => $('<div>').html(ans).text())];
|
|
||||||
|
|
||||||
// Shuffle answers
|
|
||||||
for (let i = allAnswers.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
[allAnswers[i], allAnswers[j]] = [allAnswers[j], allAnswers[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build question with choices
|
|
||||||
let questionHtml = `<span style="color: gold;">TRIVIA: ${question} `;
|
|
||||||
const choices = ['A', 'B', 'C', 'D'];
|
|
||||||
allAnswers.forEach((answer, index) => {
|
|
||||||
questionHtml += `${choices[index]}) ${answer} `;
|
|
||||||
});
|
|
||||||
questionHtml += `</span>`;
|
|
||||||
contentHtml += `<span>${questionHtml}</span>`;
|
|
||||||
|
|
||||||
// Store the correct answer for later display
|
|
||||||
// Find which choice corresponds to the correct answer
|
|
||||||
const correctIndex = allAnswers.indexOf(correctAnswer);
|
|
||||||
const correctChoice = choices[correctIndex];
|
|
||||||
window.lastTriviaAnswer = { correctChoice, correctAnswer };
|
|
||||||
}
|
|
||||||
|
|
||||||
contentHtml += `<span><span class="ticker-event">Visit <b>calendar.wx4rlx.org</b> to make updates or see info for upcoming days!</span></span>`;
|
|
||||||
|
|
||||||
// Add trivia answer if available
|
|
||||||
if (window.lastTriviaAnswer) {
|
|
||||||
const { correctChoice, correctAnswer } = window.lastTriviaAnswer;
|
|
||||||
contentHtml += `<span><span style="color: gold;">ANSWER: ${correctChoice}) ${correctAnswer}</span></span>`;
|
|
||||||
// Clear the answer after displaying it
|
|
||||||
window.lastTriviaAnswer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contentHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startTickerWatchdog() {
|
|
||||||
// Clear any existing watchdog
|
|
||||||
if (tickerWatchdogTimer) {
|
|
||||||
clearInterval(tickerWatchdogTimer);
|
|
||||||
}
|
|
||||||
// Check every 30 seconds if the ticker hasn't updated in 3x the expected duration
|
|
||||||
tickerWatchdogTimer = setInterval(() => {
|
|
||||||
const timeSinceLastUpdate = Date.now() - lastTickerUpdateTime;
|
|
||||||
const maxAllowedTime = (TARGET_BROADCAST_SECONDS + 5) * 3 * 1000; // 3x expected duration in ms
|
|
||||||
if (timeSinceLastUpdate > maxAllowedTime) {
|
|
||||||
console.warn('Ticker watchdog triggered - forcing refresh');
|
|
||||||
fetchAndDisplayTickerData(true);
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTickerLastUpdateTime() {
|
|
||||||
lastTickerUpdateTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug function to print ticker info and force next cycle
|
|
||||||
window.debugTicker = function() {
|
|
||||||
console.log('=== TICKER DEBUG INFO ===');
|
|
||||||
console.log('Current cycle count:', tickerCycleCount);
|
|
||||||
console.log('Upcoming holidays:', upcomingHolidays);
|
|
||||||
console.log('Current Wikimedia events count:', window.currentWikimediaEvents ? window.currentWikimediaEvents.length : 0);
|
|
||||||
console.log('Current holidays:', window.currentHolidays);
|
|
||||||
console.log('Next ticker HTML length:', nextTickerHtml ? nextTickerHtml.length : 'null');
|
|
||||||
console.log('Current ticker content:', document.getElementById('ticker-content').innerHTML);
|
|
||||||
|
|
||||||
// Force next cycle
|
|
||||||
console.log('Forcing next ticker cycle...');
|
|
||||||
tickerCycleCount++;
|
|
||||||
fetchAndDisplayTickerData(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
sadCheck();
|
|
||||||
toggleForm();
|
|
||||||
refreshTimer = setInterval(() => {
|
|
||||||
fetchAndDisplayNews(currentNewsUrl);
|
|
||||||
}, 300000);
|
|
||||||
fetchAndDisplayNews(currentNewsUrl);
|
|
||||||
// Start the ticker independently
|
|
||||||
fetchAndDisplayTickerData(true);
|
|
||||||
startTickerWatchdog();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
438
test.py
438
test.py
@@ -1,438 +0,0 @@
|
|||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
# 1. Create a session object
|
|
||||||
session = requests.Session()
|
|
||||||
|
|
||||||
# 2. Set the User-Agent for the session
|
|
||||||
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
|
|
||||||
session.headers.update({'User-Agent': user_agent})
|
|
||||||
|
|
||||||
# 3. Define and add cookies to the session
|
|
||||||
cookies_to_add = [
|
|
||||||
("SID", "g.a000vwi83sZx47d1q9Po8BgPR3jBX7lpsrzWSuPIKyp6RycUEctVzyy0oo1gNvQbwZa53AWllwACgYKAVQSARMSFQHGX2MiUpZ3JZlRTzUS5L-5fPmgpBoVAUF8yKprZzPlNM_X83070Ct35bq_0076", "/", ".google.com"),
|
|
||||||
("__Secure-1PSID", "g.a000vwi83sZx47d1q9Po8BgPR3jBX7lpsrzWSuPIKyp6RycUEctVKNqZm8qWU9ZC2DlRVPn0igACgYKAfQSARMSFQHGX2MiYGW66m_L_p1vlqiuEg-IrxoVAUF8yKrgRbA9tcA4VinZix0qlexX0076", "/", ".google.com"),
|
|
||||||
("__Secure-3PSID", "g.a000vwi83sZx47d1q9Po8BgPR3jBX7lpsrzWSuPIKyp6RycUEctVnGF-cJAl9Wr1rJ-NOCW63wACgYKAZASARMSFQHGX2Mi1gYcRRnI8v2AdvofwvFG5BoVAUF8yKooSw-opCZ1-vxmkQXCB7330076", "/", ".google.com"),
|
|
||||||
("HSID", "AJifEcy5MKbQSoqIz", "/", ".google.com"),
|
|
||||||
("SSID", "AB87p4RIXK_USto4D", "/", ".google.com"),
|
|
||||||
("APISID", "fQw9oTJbNnptFKdr/AIXxJuvoAk3qrIeWi", "/", ".google.com"),
|
|
||||||
("SAPISID", "3zFHAXrGoL_XNe0X/A7AafTXBL2NRj7N2h", "/", ".google.com"),
|
|
||||||
("__Secure-1PAPISID", "3zFHAXrGoL_XNe0X/A7AafTXBL2NRj7N2h", "/", ".google.com"),
|
|
||||||
("__Secure-3PAPISID", "3zFHAXrGoL_XNe0X/A7AafTXBL2NRj7N2h", "/", ".google.com"),
|
|
||||||
("AEC", "AVcja2e2MbzHDDL2yqJDMgUeDL-S3BEFB_yK293aRhQwshqv22Bd7IQ9mvg", "/", ".google.com"),
|
|
||||||
("NID", "523=SVwTZRVo9GJ5sSI0guSXNWwegQ7YY0h597wyCkrqkuIRMnQqpH3qrPA0SGB77KF69ibosOMUd-VNbNg1R7TXjPYFxYTUTP0uLtg41CaQctv6j1NKBaVPJoUkK3wzQ1e3l5yGsfBwGtypz5vxHP6yeyRtVWmYSiDiHr7AN1dKYXwzVVi7Dp8bvtppEAwUX7dQCqQ-4qGlHcoMW44STYwNELgd6CVXWPIhypl4qx_Wwcv7sY3GGP04s2kb0ljX7rYXKagj_Uv3xJDujDa4XUTAJKficyiWOrZT5LpeewUp1Kt5RyEBH2U7IHocmsWC6lfAKsoIt21wc1QwYioj9XkIIasJ4tCu1F7yXHjrglyGwbe5i67stMHI2FL3KXdoxe3ly_MORZWlVNM1Co7W43Rab01HMj2Ad940eXU9aV1PVzwlTtqiF0R0gLO_ubD6kykDRVMVMdMjMapz_dgIUfbkGMluSVykNRKnO7yf_rS7DKjXZXsh18UEsLcz6WjKtEYgmvm51MdH2JN5dcElSK_SWxrejJl-UW_eT_EqUQaeCtK2N7dTMFT38PGAkZFEKCvMqpjU86iLbFbmq_lKT-dm6IjIxFXj8yv652EYoCBlS3MsTjaQWP4_yRJa3oWhnB1kgwiBgBVmidRShZOT09L77WL6jVKE6YE09QrjGnoH_RjZFTwwGiuSFCLKfz9SwVsJBNV-nn5nZrU40kWmlhnfBNY3Kphq0fwO9RQ6wvzMAGzz684-kxzePSX3CgA74-rppEZrkXFMFjSaZGE-D7NAQp1qD2ez0-H2n0OXbRaLDN7JXn9fqhZu_FwvHJGfTBPZOZnk7taSnZSipmFU8gsTRQSVdFX6SlCXGpKueSRjhho", "/", ".google.com"),
|
|
||||||
("S", "maestro=IUwVdLn0rPZ27SY26uYPEonS9u1J9oQi8YzVJpqIc-w", "/", ".google.com"),
|
|
||||||
("__Secure-1PSIDTS", "sidts-CjIB7pHptaI1HakO1xLmYgvHilIKZJuufs1na9HjqCHJL13_z6LJNW13liGWofxE3NQ-NxAA", "/", ".google.com"),
|
|
||||||
("__Secure-3PSIDTS", "sidts-CjIB7pHptaI1HakO1xLmYgvHilIKZJuufs1na9HjqCHJL13_z6LJNW13liGWofxE3NQ-NxAA", "/", ".google.com"),
|
|
||||||
("_gid", "GA1.3.1639311329.1744393279", "/", ".lookerstudio.google.com"), # Different domain
|
|
||||||
("_ga", "GA1.1.1275703331.1743917675", "/", ".lookerstudio.google.com"), # Different domain
|
|
||||||
("_ga_LPCKLD3Z7X", "GS1.1.1744393286.6.0.1744393286.0.0.0", "/", ".lookerstudio.google.com"), # Different domain
|
|
||||||
("RAP_XSRF_TOKEN", "AImk1AJGP3sIKZ7N5-RA2jKHcJ0jdpxGKw:1744393288187", "/", "lookerstudio.google.com"), # Different domain (no leading dot)
|
|
||||||
("_gat", "1", "/", ".lookerstudio.google.com"), # Different domain
|
|
||||||
("SIDCC", "AKEyXzW9xrpFKS4Ox9-THfQ3DfB62JRx-bxxg0ZEiKYze2jaerhvWrVMFVCyjTjxIJfOhCOKQw4", "/", ".google.com"),
|
|
||||||
("__Secure-1PSIDCC", "AKEyXzXdMIev0GvM5xA0kMifj4jnuGZYNiob-2fJssX_jTBlwE8M4Bm9edS4J_i7UTSMFFEbCIm_", "/", ".google.com"),
|
|
||||||
("__Secure-3PSIDCC", "AKEyXzU8YysnVlR_9UcCx2GFo5hIUNPh6OqSCRE6Fpo9y12BNmobniOBCjTZc1_qHTS6VnivWX25", "/", ".google.com"),
|
|
||||||
("_ga_S4FJY0X3VX", "GS1.1.1744393279.7.1.1744393369.0.0.0", "/", ".lookerstudio.google.com") # Different domain
|
|
||||||
]
|
|
||||||
|
|
||||||
for name, value, path, domain in cookies_to_add:
|
|
||||||
session.cookies.set(name=name, value=value, path=path, domain=domain)
|
|
||||||
|
|
||||||
# 4. Define the Target URL
|
|
||||||
url = "https://lookerstudio.google.com/batchedDataV2?appVersion=20250324_0406"
|
|
||||||
|
|
||||||
# 5. Define Headers (User-Agent and Cookies are handled by the session)
|
|
||||||
headers = {
|
|
||||||
"authority": "lookerstudio.google.com",
|
|
||||||
"accept": "application/json, text/plain, */*",
|
|
||||||
"accept-encoding": "gzip, deflate, br, zstd", # requests usually handles this, but specifying can be safer
|
|
||||||
"accept-language": "en-US,en;q=0.9",
|
|
||||||
"cache-control": "no-cache",
|
|
||||||
# "encoding": "null", # This header seems unusual/invalid for requests, omitting
|
|
||||||
"origin": "https://lookerstudio.google.com",
|
|
||||||
"pragma": "no-cache",
|
|
||||||
"priority": "u=1, i",
|
|
||||||
"referer": "https://lookerstudio.google.com/reporting/1413fcfb-1416-4e56-8967-55f8e9f30ec8/page/p_pbm4eo88qc",
|
|
||||||
"sec-ch-ua": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
|
|
||||||
"sec-ch-ua-mobile": "?0",
|
|
||||||
"sec-ch-ua-platform": '"Windows"',
|
|
||||||
"sec-fetch-dest": "empty",
|
|
||||||
"sec-fetch-mode": "cors",
|
|
||||||
"sec-fetch-site": "same-origin",
|
|
||||||
"x-client-data": "CIS2yQEIorbJAQipncoBCMHbygEIk6HLAQiFoM0BCP6lzgEIvdXOAQjJ4M4BCIbizgEIu+fOAQjS6M4BCKzpzgE=",
|
|
||||||
"x-rap-xsrf-token": "AImk1AJGP3sIKZ7N5-RA2jKHcJ0jdpxGKw:1744393288187",
|
|
||||||
# Content-Type will be set automatically by requests when using json=payload
|
|
||||||
}
|
|
||||||
|
|
||||||
# 6. Define the JSON Body (Payload)
|
|
||||||
# Use triple quotes for the multi-line string and parse it with json.loads
|
|
||||||
body_string = """
|
|
||||||
{
|
|
||||||
"dataRequest": [
|
|
||||||
{
|
|
||||||
"requestContext": {
|
|
||||||
"reportContext": {
|
|
||||||
"reportId": "1413fcfb-1416-4e56-8967-55f8e9f30ec8",
|
|
||||||
"pageId": "p_pbm4eo88qc",
|
|
||||||
"mode": 1,
|
|
||||||
"componentId": "cd-rdkny9a9qc",
|
|
||||||
"displayType": "simple-table",
|
|
||||||
"actionId": "crossFilters"
|
|
||||||
},
|
|
||||||
"requestMode": 0
|
|
||||||
},
|
|
||||||
"datasetSpec": {
|
|
||||||
"dataset": [
|
|
||||||
{
|
|
||||||
"datasourceId": "c2fc8cdd-46bb-454c-bf09-90ebfd4067d7",
|
|
||||||
"revisionNumber": 0,
|
|
||||||
"parameterOverrides": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"queryFields": [
|
|
||||||
{
|
|
||||||
"name": "qt_3nwfu9yq1c",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"resultTransformation": {
|
|
||||||
"analyticalFunction": 0,
|
|
||||||
"isRelativeToBase": false
|
|
||||||
},
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_Event_Id_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_8yjok4izsc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"resultTransformation": {
|
|
||||||
"analyticalFunction": 0,
|
|
||||||
"isRelativeToBase": false
|
|
||||||
},
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_DateTime_EST_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_sfkc163arc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_KYTC_Type_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_4e66idhbrc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_Incident_Source_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_re76qrqe2c",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_District_",
|
|
||||||
"aggregation": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_tfkc163arc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_County_Name_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_ufkc163arc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_Route_Label_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_vfkc163arc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_BMP_Initial_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_o7kc163arc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_EMP_Initial_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_p7kc163arc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_Description_"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sortData": [
|
|
||||||
{
|
|
||||||
"sortColumn": {
|
|
||||||
"name": "qt_8yjok4izsc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_DateTime_EST_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sortDir": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"includeRowsCount": true,
|
|
||||||
"relatedDimensionMask": {
|
|
||||||
"addDisplay": false,
|
|
||||||
"addUniqueId": false,
|
|
||||||
"addLatLong": false
|
|
||||||
},
|
|
||||||
"paginateInfo": {
|
|
||||||
"startRow": 1,
|
|
||||||
"rowsCount": 50
|
|
||||||
},
|
|
||||||
"dsFilterOverrides": [],
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"filterDefinition": {
|
|
||||||
"filterExpression": {
|
|
||||||
"include": false,
|
|
||||||
"conceptType": 0,
|
|
||||||
"concept": {
|
|
||||||
"ns": "t0",
|
|
||||||
"name": "qt_exs3vib9qc"
|
|
||||||
},
|
|
||||||
"filterConditionType": "PT",
|
|
||||||
"stringValues": [
|
|
||||||
"Shoulder"
|
|
||||||
],
|
|
||||||
"numberValues": [],
|
|
||||||
"queryTimeTransformation": {
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_Source_Type_"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dataSubsetNs": {
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"contextNs": "c0"
|
|
||||||
},
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filterDefinition": {
|
|
||||||
"filterExpression": {
|
|
||||||
"include": true,
|
|
||||||
"conceptType": 0,
|
|
||||||
"concept": {
|
|
||||||
"name": "qt_kjyfx83arc",
|
|
||||||
"ns": "t0"
|
|
||||||
},
|
|
||||||
"queryTimeTransformation": {
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_County_Name_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"filterConditionType": "IN",
|
|
||||||
"stringValues": [
|
|
||||||
"Boyd",
|
|
||||||
"Carter",
|
|
||||||
"Greenup"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dataSubsetNs": {
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"contextNs": "c0"
|
|
||||||
},
|
|
||||||
"version": 3,
|
|
||||||
"isCanvasFilter": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"features": [],
|
|
||||||
"dateRanges": [],
|
|
||||||
"contextNsCount": 1,
|
|
||||||
"calculatedField": [],
|
|
||||||
"needGeocoding": false,
|
|
||||||
"geoFieldMask": [],
|
|
||||||
"multipleGeocodeFields": [],
|
|
||||||
"timezone": "America/New_York"
|
|
||||||
},
|
|
||||||
"role": "main",
|
|
||||||
"retryHints": {
|
|
||||||
"useClientControlledRetry": true,
|
|
||||||
"isLastRetry": false,
|
|
||||||
"retryCount": 0,
|
|
||||||
"originalRequestId": "cd-rdkny9a9qc_0_0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"requestContext": {
|
|
||||||
"reportContext": {
|
|
||||||
"reportId": "1413fcfb-1416-4e56-8967-55f8e9f30ec8",
|
|
||||||
"pageId": "p_pbm4eo88qc",
|
|
||||||
"mode": 1,
|
|
||||||
"componentId": "cd-hgodhfhbrc",
|
|
||||||
"displayType": "dimension-filter",
|
|
||||||
"actionId": "crossFilters"
|
|
||||||
},
|
|
||||||
"requestMode": 7
|
|
||||||
},
|
|
||||||
"datasetSpec": {
|
|
||||||
"dataset": [
|
|
||||||
{
|
|
||||||
"datasourceId": "c2fc8cdd-46bb-454c-bf09-90ebfd4067d7",
|
|
||||||
"revisionNumber": 0,
|
|
||||||
"parameterOverrides": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"queryFields": [
|
|
||||||
{
|
|
||||||
"name": "qt_vwmdhfhbrc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_Incident_Source_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qt_d5jjfyu6wc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "datastudio_record_count_system_field_id_98323387"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sortData": [
|
|
||||||
{
|
|
||||||
"sortColumn": {
|
|
||||||
"name": "qt_vwmdhfhbrc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_Incident_Source_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sortDir": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"includeRowsCount": true,
|
|
||||||
"relatedDimensionMask": {
|
|
||||||
"addDisplay": false,
|
|
||||||
"addUniqueId": false,
|
|
||||||
"addLatLong": false
|
|
||||||
},
|
|
||||||
"paginateInfo": {
|
|
||||||
"startRow": 1,
|
|
||||||
"rowsCount": 5001
|
|
||||||
},
|
|
||||||
"dsFilterOverrides": [],
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"filterDefinition": {
|
|
||||||
"filterExpression": {
|
|
||||||
"include": true,
|
|
||||||
"conceptType": 0,
|
|
||||||
"concept": {
|
|
||||||
"name": "qt_kjyfx83arc",
|
|
||||||
"ns": "t0"
|
|
||||||
},
|
|
||||||
"queryTimeTransformation": {
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_County_Name_"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"filterConditionType": "IN",
|
|
||||||
"stringValues": [
|
|
||||||
"Boyd",
|
|
||||||
"Carter",
|
|
||||||
"Greenup"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dataSubsetNs": {
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"contextNs": "c0"
|
|
||||||
},
|
|
||||||
"version": 3,
|
|
||||||
"isCanvasFilter": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"features": [],
|
|
||||||
"dateRanges": [],
|
|
||||||
"contextNsCount": 1,
|
|
||||||
"dateRangeDimensions": [
|
|
||||||
{
|
|
||||||
"name": "qt_ilkdhfhbrc",
|
|
||||||
"datasetNs": "d0",
|
|
||||||
"tableNs": "t0",
|
|
||||||
"dataTransformation": {
|
|
||||||
"sourceFieldName": "_Incident_Begin_Time_",
|
|
||||||
"transformationConfig": {
|
|
||||||
"transformationType": 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"calculatedField": [],
|
|
||||||
"needGeocoding": false,
|
|
||||||
"geoFieldMask": [],
|
|
||||||
"multipleGeocodeFields": [],
|
|
||||||
"timezone": "America/New_York"
|
|
||||||
},
|
|
||||||
"role": "main",
|
|
||||||
"retryHints": {
|
|
||||||
"useClientControlledRetry": true,
|
|
||||||
"isLastRetry": false,
|
|
||||||
"retryCount": 0,
|
|
||||||
"originalRequestId": "cd-hgodhfhbrc_0_0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
payload = json.loads(body_string)
|
|
||||||
|
|
||||||
# 7. Send the POST request using the session
|
|
||||||
try:
|
|
||||||
print(f"Sending POST request to {url}...")
|
|
||||||
response = session.post(url, headers=headers, json=payload)
|
|
||||||
|
|
||||||
# Raise an exception for bad status codes (4xx or 5xx)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
# 8. Process the response
|
|
||||||
print(f"Status Code: {response.status_code}")
|
|
||||||
|
|
||||||
# Attempt to print the response as JSON, fall back to text if decoding fails
|
|
||||||
try:
|
|
||||||
print(response.text)
|
|
||||||
print("Response JSON:")
|
|
||||||
# Use json.dumps for pretty printing the received JSON
|
|
||||||
print(json.dumps(response.json(), indent=2))
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print("Response Text (non-JSON):")
|
|
||||||
print(response.text)
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"An error occurred during the request: {e}")
|
|
||||||
if hasattr(e, 'response') and e.response is not None:
|
|
||||||
print(f"Response status code: {e.response.status_code}")
|
|
||||||
print(f"Response text: {e.response.text}")
|
|
||||||
Reference in New Issue
Block a user