-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
af60b83
commit f85f570
Showing
1 changed file
with
326 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,326 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Reddit → Redlib</title> | ||
<style> | ||
body { | ||
font-family: Arial, sans-serif; | ||
max-width: 800px; | ||
margin: 0 auto; | ||
padding: 20px; | ||
text-align: center; | ||
} | ||
|
||
.container { | ||
max-width: 600px; | ||
margin: 0 auto; | ||
} | ||
|
||
#status { | ||
margin: 20px 0; | ||
padding: 10px; | ||
border-radius: 4px; | ||
} | ||
|
||
.loading { | ||
background-color: #fff3cd; | ||
color: #856404; | ||
} | ||
|
||
.error { | ||
background-color: #f8d7da; | ||
color: #721c24; | ||
} | ||
|
||
.success { | ||
background-color: #d4edda; | ||
color: #155724; | ||
} | ||
|
||
#spinner { | ||
display: none; | ||
width: 20px; | ||
height: 20px; | ||
border: 3px solid #f3f3f3; | ||
border-top: 3px solid #3498db; | ||
border-radius: 50%; | ||
animation: spin 1s linear infinite; | ||
margin-right: 10px; | ||
} | ||
|
||
@keyframes spin { | ||
0% { | ||
transform: rotate(0deg); | ||
} | ||
|
||
100% { | ||
transform: rotate(360deg); | ||
} | ||
} | ||
|
||
.loading #spinner { | ||
display: inline-block; | ||
} | ||
|
||
.info { | ||
background-color: #e9ecef; | ||
padding: 20px; | ||
border-radius: 8px; | ||
margin: 20px 0; | ||
text-align: left; | ||
} | ||
|
||
.info h2 { | ||
margin-top: 0; | ||
color: #495057; | ||
} | ||
|
||
.info ul { | ||
margin: 0; | ||
padding-left: 20px; | ||
} | ||
|
||
.info li { | ||
margin: 8px 0; | ||
} | ||
|
||
.example { | ||
background-color: #f8f9fa; | ||
padding: 10px; | ||
border-radius: 4px; | ||
font-family: monospace; | ||
word-break: break-all; | ||
} | ||
|
||
.footer { | ||
margin-top: 40px; | ||
font-size: 0.9em; | ||
color: #6c757d; | ||
} | ||
|
||
.hidden { | ||
display: none; | ||
} | ||
|
||
#cache-status { | ||
font-size: 0.9em; | ||
color: #6c757d; | ||
margin-top: 10px; | ||
} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<div class="container"> | ||
<h1>Reddit → Redlib</h1> | ||
|
||
<div class="info"> | ||
<h2>About</h2> | ||
<p>This tool automatically redirects Reddit links to available Redlib instances. Redlib is a private | ||
front-end for Reddit that doesn't require JavaScript to work.</p> | ||
|
||
<h2>How to Use</h2> | ||
<ul> | ||
<li>Add your Reddit URL as a parameter to this page using <code>?url=</code></li> | ||
<li>The redirector will find an available Redlib instance and redirect you there</li> | ||
<li>Previously working instances are cached and prioritized for faster access</li> | ||
</ul> | ||
|
||
<h2>Example</h2> | ||
<p>To visit r/privacy on Redlib, use:</p> | ||
<div class="example"> | ||
<span id="current-domain">reddit-redirector.html</span>?url=https://reddit.com/r/privacy | ||
</div> | ||
</div> | ||
|
||
<div id="status"> | ||
<div id="spinner"></div> | ||
<span id="status-text"></span> | ||
</div> | ||
<div id="cache-status"></div> | ||
|
||
<div class="footer"> | ||
<p>Source code available on <a href="https://github.com/mshibanami/redirect-web" target="_blank">GitHub</a> | ||
</p> | ||
</div> | ||
</div> | ||
|
||
<script> | ||
const INSTANCES_URL = 'https://raw.githubusercontent.com/redlib-org/redlib-instances/refs/heads/main/instances.json'; | ||
const STATUS_TIMEOUT = 3000; | ||
const CACHE_KEY = 'redlib_working_instances'; | ||
const CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours | ||
|
||
// Update the example URL with the current domain | ||
document.getElementById('current-domain').textContent = window.location.href.split('?')[0]; | ||
|
||
// Cache management functions | ||
function getCachedInstances() { | ||
try { | ||
const cached = localStorage.getItem(CACHE_KEY); | ||
if (!cached) return null; | ||
|
||
const { instances, timestamp } = JSON.parse(cached); | ||
|
||
// Check if cache is expired | ||
if (Date.now() - timestamp > CACHE_EXPIRY) { | ||
localStorage.removeItem(CACHE_KEY); | ||
return null; | ||
} | ||
|
||
return instances; | ||
} catch (error) { | ||
console.error('Cache read error:', error); | ||
return null; | ||
} | ||
} | ||
|
||
function cacheWorkingInstance(instance) { | ||
try { | ||
const existingCache = getCachedInstances() || []; | ||
const newCache = [ | ||
instance, | ||
...existingCache.filter(i => i.url !== instance.url) | ||
].slice(0, 5); // Keep only the 5 most recent working instances | ||
|
||
localStorage.setItem(CACHE_KEY, JSON.stringify({ | ||
instances: newCache, | ||
timestamp: Date.now() | ||
})); | ||
} catch (error) { | ||
console.error('Cache write error:', error); | ||
} | ||
} | ||
|
||
function checkInstanceAvailability(instance) { | ||
return new Promise((resolve) => { | ||
const img = new Image(); | ||
const timeout = setTimeout(() => { | ||
img.src = ''; | ||
resolve(false); | ||
}, STATUS_TIMEOUT); | ||
|
||
img.onload = () => { | ||
clearTimeout(timeout); | ||
resolve(true); | ||
}; | ||
|
||
img.onerror = () => { | ||
clearTimeout(timeout); | ||
resolve(false); | ||
}; | ||
|
||
img.src = `${instance.url}/favicon.ico`; | ||
}); | ||
} | ||
|
||
function convertRedditUrl(redlibUrl, redditUrl) { | ||
try { | ||
const url = new URL(redditUrl); | ||
if (!url.hostname.includes('reddit.com')) { | ||
throw new Error('Not a Reddit URL'); | ||
} | ||
return redlibUrl + url.pathname + url.search + url.hash; | ||
} catch (error) { | ||
return null; | ||
} | ||
} | ||
|
||
async function findWorkingInstance(allInstances) { | ||
// Randomize instance order to distribute load evenly | ||
const shuffledInstances = allInstances.sort(() => Math.random() - 0.5); | ||
|
||
// First check cached instances | ||
const cachedInstances = getCachedInstances(); | ||
if (cachedInstances?.length > 0) { | ||
document.getElementById('cache-status').textContent = 'Checking cached instances...'; | ||
|
||
for (const instance of cachedInstances) { | ||
const isAvailable = await checkInstanceAvailability(instance); | ||
if (isAvailable) { | ||
document.getElementById('cache-status').textContent = 'Using cached instance'; | ||
return instance; | ||
} | ||
} | ||
|
||
document.getElementById('cache-status').textContent = 'Cached instances unavailable, checking others...'; | ||
} | ||
|
||
// Filter out cached instances from all instances to avoid duplicate checks | ||
const remainingInstances = shuffledInstances.filter(instance => | ||
!cachedInstances?.some(cached => cached.url === instance.url) | ||
); | ||
|
||
// Check remaining instances sequentially to avoid the issue of race conditions | ||
for (const instance of remainingInstances) { | ||
const isAvailable = await checkInstanceAvailability(instance); | ||
if (isAvailable) { | ||
cacheWorkingInstance(instance); | ||
document.getElementById('cache-status').textContent = 'New working instance found and cached'; | ||
return instance; | ||
} | ||
} | ||
|
||
document.getElementById('cache-status').textContent = ''; | ||
return null; | ||
} | ||
|
||
function updateStatus(type, message) { | ||
const statusEl = document.getElementById('status'); | ||
const statusTextEl = document.getElementById('status-text'); | ||
statusEl.className = type; | ||
statusTextEl.textContent = message; | ||
} | ||
|
||
async function init() { | ||
const urlParams = new URLSearchParams(window.location.search); | ||
const redditUrl = urlParams.get('url'); | ||
const decodedRedditUrl = redditUrl ? decodeURIComponent(redditUrl) : null; | ||
|
||
if (!redditUrl) { | ||
updateStatus('error', 'No URL parameter provided. See usage instructions above.'); | ||
document.getElementById('cache-status').textContent = ''; | ||
return; | ||
} | ||
|
||
try { | ||
updateStatus('loading', 'Checking for available instances...'); | ||
|
||
const response = await fetch(INSTANCES_URL); | ||
const data = await response.json(); | ||
|
||
const workingInstance = await findWorkingInstance(data.instances); | ||
|
||
if (!decodedRedditUrl) { | ||
throw new Error('Invalid or malformed Reddit URL.'); | ||
} | ||
|
||
if (!workingInstance) { | ||
throw new Error('No available instances found. Please try again later.'); | ||
} | ||
|
||
const redirectUrl = convertRedditUrl(workingInstance.url, decodedRedditUrl); | ||
if (!redirectUrl) { | ||
throw new Error('Invalid Reddit URL.'); | ||
} | ||
|
||
updateStatus('success', `Redirecting to ${workingInstance.url}...`); | ||
|
||
setTimeout(() => { | ||
window.location.href = redirectUrl; | ||
}, 1000); | ||
|
||
} catch (error) { | ||
updateStatus('error', error.message); | ||
document.getElementById('cache-status').textContent = ''; | ||
} | ||
} | ||
|
||
init(); | ||
</script> | ||
</body> | ||
|
||
</html> |