// ==UserScript==
// @name Proza.ru & Stihi.ru Randomizer 22
// @namespace http://tampermonkey.net/
// @version 1.3
// @description "Мне повезёт" + навигация по дате/публикации
// @match https://stihi.ru/*
// @match https://proza.ru/*
// @icon https://i.postimg.cc/3r2Z3hSs/psic.png
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const isProza = window.location.hostname.includes('proza.ru');
const publicationLimits = isProza ? [1, 2000] : [1, 11000];
const startLimits = isProza ? [1, 250] : [1, 450];
saveToLocalStorage('publication_min', publicationLimits[0]);
saveToLocalStorage('publication_max', publicationLimits[1]);
saveToLocalStorage('start_min', startLimits[0]);
saveToLocalStorage('start_max', startLimits[1]);
// Helper functions
function parseUrl() {
const urlParts = window.location.pathname.split('/');
return {
year: urlParts[1] || '',
month: urlParts[2] || '',
day: urlParts[3] || '',
publication: urlParts[4] || ''
};
}
function updateUrl(year, month, day, publication) {
const baseUrl = window.location.origin + '/';
window.location.href = `${baseUrl}${year}/${month}/${day}/${publication}`;
}
function saveToLocalStorage(key, value) {
localStorage.setItem(key, value);
}
function loadFromLocalStorage(key, defaultValue) {
return localStorage.getItem(key) || defaultValue;
}
function validateInput(field, value) {
let min, max;
switch (field) {
case 'publication':
min = parseInt(loadFromLocalStorage('publication_min', publicationLimits[0]), 10);
max = parseInt(loadFromLocalStorage('publication_max', publicationLimits[1]), 10);
break;
case 'start':
min = parseInt(loadFromLocalStorage('start_min', startLimits[0]), 10);
max = parseInt(loadFromLocalStorage('start_max', startLimits[1]), 10);
break;
case 'year':
min = Math.max(parseInt(loadFromLocalStorage('year_min', '2000') || '2000', 10), 1);
max = Math.min(parseInt(loadFromLocalStorage('year_max', '9999') || '9999', 10), new Date().getFullYear());
break;
default:
min = parseInt(loadFromLocalStorage(`${field}_min`, '1') || '1', 10);
max = parseInt(loadFromLocalStorage(`${field}_max`, '9999') || '9999', 10);
}
return Math.min(Math.max(value, min), max);
}
// Create UI container
const container = document.createElement('div');
container.id = 'navigation-enhancer';
container.innerHTML = `
<table><tr><td>
<input id="year" type="number" placeholder="Year" style="width: 80px;">
<input id="month" type="number" placeholder="Month" style="width: 60px;">
<input id="day" type="number" placeholder="Day" style="width: 60px;">
<input id="publication" type="number" placeholder="Publication" style="width: 100px;">
<button id="reset-main-button">🔄</button>
<button id="settings-button">⚙️</button>
</td><td>
<button id="lucky-button">🍀</button>
</td></tr></table>
<div id="settings-menu" style="display:none;">
<div class="settings-header">
<label>Set Limits:</label>
<button id="reset-settings-button">🔄</button>
</div>
<div class="settings-group">
<label>Year:</label>
<input id="year_min" type="number" style="width: 60px;">
<input id="year_max" type="number" style="width: 60px;"><br>
<label>Month:</label>
<input id="month_min" type="number" style="width: 60px;">
<input id="month_max" type="number" style="width: 60px;"><br>
<label>Day:</label>
<input id="day_min" type="number" style="width: 60px;">
<input id="day_max" type="number" style="width: 60px;"><br>
<div>
<label>Publication:</label>
<input id="publication_min" type="number" style="width: 60px;">
<input id="publication_max" type="number" style="width: 60px;"><br>
</div><div>
<label>Start:</label>
<input id="start_min" type="number" style="width: 60px;">
<input id="start_max" type="number" style="width: 60px;"><br>
</div>
</div>
<div class="settings-group">
<label>Redirect on Change:</label>
<input id="redirect-on-change" type="checkbox" style="width: 20px;">
</div>
<div class="settings-group">
<label>Retry on Error 🍀:</label>
<input id="retry-on-error" type="checkbox" style="width: 20px;">
</div>
</div>
`;
let accessedThroughLucky = false;
function feelingLuckyList() {
accessedThroughLucky = true;
const yearMin = parseInt(loadFromLocalStorage('year_min', '2000'), 10);
const yearMax = parseInt(loadFromLocalStorage('year_max', String(new Date().getFullYear())), 10);
const randomYear = Math.floor(Math.random() * (yearMax - yearMin + 1)) + yearMin;
// Respect user's month limits
const monthMin = parseInt(loadFromLocalStorage('month_min', '1'), 10);
const monthMax = parseInt(loadFromLocalStorage('month_max', '12'), 10);
const randomMonth = Math.floor(Math.random() * (monthMax - monthMin + 1)) + monthMin;
let maxDays = new Date(randomYear, randomMonth, 0).getDate();
const randomDay = '01'; // List pages use '01' for day
const randomStart = Math.floor(Math.random() * (parseInt(loadFromLocalStorage('start_max', startLimits[1]), 10) - parseInt(loadFromLocalStorage('start_min', startLimits[0]), 10) + 1)) + parseInt(loadFromLocalStorage('start_min', startLimits[0]), 10);
// Parse the topic from the current URL
const currentUrl = new URL(window.location.href);
const topic = currentUrl.searchParams.get('topic');
const baseUrl = window.location.origin + '/texts/list.html';
const urlParams = `?day=${randomDay}&month=${String(randomMonth).padStart(2, '0')}&year=${randomYear}&topic=${topic}&start=${randomStart}`;
window.location.href = baseUrl + urlParams;
}
function feelingLucky() {
accessedThroughLucky = true;
const yearMin = parseInt(loadFromLocalStorage('year_min', '2000'), 10);
const yearMax = parseInt(loadFromLocalStorage('year_max', String(new Date().getFullYear())), 10);
const randomYear = Math.floor(Math.random() * (yearMax - yearMin + 1)) + yearMin;
const randomMonth = Math.floor(Math.random() * 12) + 1;
let maxDays = new Date(randomYear, randomMonth, 0).getDate();
const randomDay = Math.floor(Math.random() * maxDays) + 1;
const randomPublication = Math.floor(Math.random() * 100) + 1;
updateUrl(randomYear, String(randomMonth).padStart(2, '0'), String(randomDay).padStart(2, '0'), randomPublication);
}
// Find a suitable location for the navigation panel
const header = document.querySelector('#header');
const headerAlt = document.querySelector('body > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(1) > table:nth-child(1)');
if (header) {
header.insertAdjacentElement('afterend', container);
container.style.marginTop = '32px';
} else if (headerAlt) {
headerAlt.insertAdjacentElement('afterend', container);
} else {
document.body.insertBefore(container, document.body.firstChild);
}
// Select the retryOnError element
const retryOnError = document.getElementById('retry-on-error');
// Restore the retryOnError state from localStorage
retryOnError.checked = localStorage.getItem('checkboxState') === 'true';
// Add an event listener to save the retryOnError state whenever it changes
retryOnError.addEventListener('change', () => {
localStorage.setItem('checkboxState', retryOnError.checked);
});
// Try again if the page is not found and the retryOnError is checked
const notFound = document.querySelector('h1[align="center"], div[align="center"] > h1')?.textContent.match(/найд|удал|закр|ошиб|стр/i);
if (notFound && retryOnError.checked && accessedThroughLucky) {
feelingLucky();
}
// Load initial values into inputs
const urlInfo = parseUrl();
const inputFields = ['year', 'month', 'day', 'publication'];
inputFields.forEach(field => {
const input = document.getElementById(field);
input.value = validateInput(field, urlInfo[field]);
input.addEventListener('focus', () => saveToLocalStorage('lastFocusedField', field));
});
// Restore focus after reload
const lastFocusedField = loadFromLocalStorage('lastFocusedField', '');
if (lastFocusedField) {
const focusedInput = document.getElementById(lastFocusedField);
if (focusedInput) focusedInput.focus();
}
// Function to handle redirect logic
let redirectOnChange = loadFromLocalStorage('redirectOnChange', false) === 'true';
const redirectCheckbox = document.getElementById('redirect-on-change');
redirectCheckbox.checked = redirectOnChange;
redirectCheckbox.addEventListener('change', () => {
redirectOnChange = redirectCheckbox.checked;
saveToLocalStorage('redirectOnChange', redirectOnChange);
});
function updateAndRedirect() {
accessedThroughLucky = false;
if (redirectOnChange) {
updateUrl(
validateInput('year', document.getElementById('year').value),
String(validateInput('month', document.getElementById('month').value)).padStart(2, '0'),
String(validateInput('day', document.getElementById('day').value)).padStart(2, '0'),
validateInput('publication', document.getElementById('publication').value)
);
}
}
let previousValues = {};
inputFields.forEach(field => {
const input = document.getElementById(field);
previousValues[field] = input.value; // Initialize previous value
input.addEventListener('keydown', (e) => {
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
e.preventDefault();
handleArrowKeys(field, e.key === 'ArrowUp' ? 'up' : 'down');
} else if (e.key === 'Enter') {
e.preventDefault();
saveToLocalStorage(field, input.value);
previousValues[field] = input.value;
updateUrl(
validateInput('year', document.getElementById('year').value),
String(validateInput('month', document.getElementById('month').value)).padStart(2, '0'),
String(validateInput('day', document.getElementById('day').value)).padStart(2, '0'),
validateInput('publication', document.getElementById('publication').value)
);
}
});
input.addEventListener('blur', () => {
const validatedValue = validateInput(field, input.value);
input.value = validatedValue;
if (input.value !== previousValues[field]) {
previousValues[field] = input.value; // Update previous value
updateAndRedirect();
}
});
// Add change event listener for year and month fields
if (field === 'year' || field === 'month') {
input.addEventListener('change', () => {
updateDayLimits();
});
}
});
function updateDayLimits() {
const year = parseInt(document.getElementById('year').value);
const month = parseInt(document.getElementById('month').value);
const dayInput = document.getElementById('day');
const maxDays = new Date(year, month, 0).getDate();
dayInput.max = maxDays;
dayInput.value = Math.min(parseInt(dayInput.value), maxDays);
// Update day limits in settings
const dayMinInput = document.getElementById('day_min');
const dayMaxInput = document.getElementById('day_max');
dayMaxInput.max = maxDays;
dayMaxInput.value = Math.min(parseInt(dayMaxInput.value), maxDays);
saveToLocalStorage('day_max', dayMaxInput.value);
// Ensure day_max is always greater than or equal to day_min
if (parseInt(dayMaxInput.value) < parseInt(dayMinInput.value)) {
dayMaxInput.value = dayMinInput.value;
saveToLocalStorage('day_max', dayMaxInput.value);
}
}
function handleArrowKeys(field, direction) {
accessedThroughLucky = false;
const input = document.getElementById(field);
let step = event.shiftKey ? 5 : 1;
let newValue = parseInt(input.value) + (direction === 'up' ? step : -step);
if (field === 'year') {
input.value = validateInput(field, newValue);
} else if (field === 'month') {
if (newValue > 12) {
document.getElementById('year').value = validateInput('year', parseInt(document.getElementById('year').value) + Math.floor((newValue - 1) / 12));
input.value = validateInput('month', ((newValue - 1) % 12) + 1);
} else if (newValue < 1) {
document.getElementById('year').value = validateInput('year', parseInt(document.getElementById('year').value) - Math.ceil(-newValue / 12));
input.value = validateInput('month', 12 - ((-newValue) % 12));
} else {
input.value = validateInput('month', newValue);
}
} else if (field === 'day') {
const year = parseInt(document.getElementById('year').value);
const month = parseInt(document.getElementById('month').value);
let maxDays = new Date(year, month, 0).getDate();
if (newValue > maxDays) {
document.getElementById('month').value = validateInput('month', month + 1);
if (parseInt(document.getElementById('month').value) > 12) {
document.getElementById('year').value = validateInput('year', year + 1);
document.getElementById('month').value = '1';
}
input.value = '1';
} else if (newValue < 1) {
document.getElementById('month').value = validateInput('month', month - 1);
if (parseInt(document.getElementById('month').value) < 1) {
document.getElementById('year').value = validateInput('year', year - 1);
document.getElementById('month').value = '12';
}
maxDays = new Date(parseInt(document.getElementById('year').value), parseInt(document.getElementById('month').value), 0).getDate();
input.value = maxDays;
} else {
input.value = validateInput('day', newValue);
}
} else if (field === 'publication') {
input.value = validateInput('publication', newValue);
}
updateDayLimits();
previousValues[field] = input.value;
updateAndRedirect();
}
// "Feeling Lucky" button event listener
document.getElementById('lucky-button').addEventListener('click', () => {
if (window.location.pathname.includes('list.html')) {
feelingLuckyList();
} else {
feelingLucky();
}
});
// Settings button functionality (toggle visibility)
const settingsButton = document.getElementById('settings-button');
const settingsMenu = document.getElementById('settings-menu');
settingsButton.addEventListener('click', () => {
settingsMenu.style.display = settingsMenu.style.display === 'none' ? 'block' : 'none';
const limitFields = ['year_min', 'year_max', 'month_min', 'month_max', 'day_min', 'day_max'];
if (window.location.pathname.includes('list.html')) {
limitFields.push('start_min', 'start_max');
} else {
limitFields.push('publication_min', 'publication_max');
}
limitFields.forEach(field => {
let defaultValue;
if (field === 'publication_min' || field === 'publication_max') {
defaultValue = field.includes('min') ? '1' : publicationLimits[1];
} else if (field === 'start_min' || field === 'start_max') {
defaultValue = field.includes('min') ? '1' : startLimits[1];
} else if (field === 'day_max') {
defaultValue = '31'; // Default max day
} else {
defaultValue = field.includes('min') ? '1' : '9999';
}
document.getElementById(field).value = loadFromLocalStorage(field, defaultValue);
const input = document.getElementById(field);
input.addEventListener('change', () => {
saveToLocalStorage(field, input.value);
});
});
updateDayLimits(); // Add this line
});
// Close settings when clicking outside
document.addEventListener('click', (event) => {
if (!container.contains(event.target) && event.target !== settingsButton) {
settingsMenu.style.display = 'none';
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && settingsMenu.style.display !== 'none') {
settingsMenu.style.display = 'none';
}
});
const currentYear = new Date().getFullYear();
const defaultSettings = {
year_min: '2000',
year_max: String(currentYear),
month_min: '1',
month_max: '12',
day_min: '1',
day_max: '31',
publication_min: '1',
publication_max: publicationLimits[1],
start_min: '1',
start_max: startLimits[1],
};
const isListPage = window.location.pathname.includes('list.html');
const listPageSettings = document.getElementById('start_min').parentElement;
const publicationSettings = document.getElementById('publication_min').parentElement;
if (isListPage) {
listPageSettings.style.display = 'block';
publicationSettings.style.display = 'none';
} else {
listPageSettings.style.display = 'none';
publicationSettings.style.display = 'block';
}
document.getElementById('reset-settings-button').addEventListener('click', resetSettings);
function resetSettings() {
const limitFields = ['year_min', 'year_max', 'month_min', 'month_max', 'day_min', 'day_max', 'publication_min', 'publication_max', 'start_min', 'start_max'];
limitFields.forEach(field => {
let defaultValue;
if (field === 'publication_min' || field === 'publication_max') {
defaultValue = defaultSettings[field];
} else if (field === 'start_min' || field === 'start_max') {
defaultValue = defaultSettings[field] || (field.includes('min') ? '1' : startLimits[1]);
} else {
defaultValue = defaultSettings[field];
}
document.getElementById(field).value = defaultValue;
saveToLocalStorage(field, defaultValue);
});
}
document.getElementById('reset-main-button').addEventListener('click', resetMainPanel);
function resetMainPanel() {
const currentYear = new Date().getFullYear();
const currentMonth = new Date().getMonth() + 1;
const currentDay = new Date().getDate();
const inputFields = ['year', 'month', 'day', 'publication'];
inputFields.forEach(field => {
let defaultValue;
switch (field) {
case 'year':
defaultValue = currentYear;
break;
case 'month':
defaultValue = currentMonth;
break;
case 'day':
defaultValue = currentDay;
break;
case 'publication':
defaultValue = 1;
break;
}
const input = document.getElementById(field);
input.value = validateInput(field, defaultValue);
saveToLocalStorage(field, input.value);
});
updateUrl(currentYear, String(currentMonth).padStart(2, '0'), String(currentDay).padStart(2, '0'), 1);
}
// Add CSS styles directly in the script
GM_addStyle(`
#navigation-enhancer {
background-color: #111111;
padding: 5px;
font-family: Arial, sans-serif;
width: 320px;
margin: 0 24px 0 auto;
color:white
}
#navigation-enhancer input {
width: 40px;
margin-right: 5px;
background: #111111;
color: #ffffff;
}
#navigation-enhancer input#publication {
width: 60px;
}
#navigation-enhancer button {
margin-right: 5px;
color: white;
border: none;
border-radius: 4px;
padding: 5px 10px;
margin-top: 5px;
background:black
}
#navigation-enhancer button:hover {
background: #444444
}
#lucky-button{
font-size:200%
}
.settings-header {
}
#reset-settings-button{
}
#settings-menu {
border-left: 4px solid white;
padding:8px
}
.settings-group {
margin-top: 16px;
}
`);
})();