mirror of
https://github.com/Ry3yr/Fake-Social-MediaWriter.git
synced 2026-08-19 17:20:03 -04:00
97fa2d6026
Bandcamp Support
823 lines
39 KiB
HTML
823 lines
39 KiB
HTML
<style>
|
|
/* Core Timeline Styles */
|
|
.btn,.btn2,.post a{text-decoration:none}.controls,.post{margin-bottom:20px;max-width:600px}body{font-family:Arial,sans-serif;margin:0;padding:20px}.post{background-color:#fff;border:1px solid #ccd6dd;border-radius:8px;padding:15px;box-shadow:0 2px 4px rgba(0,0,0,.05)}#loadMoreBtn,.btn,.btn2{background-color:#1da1f2;color:#fff;cursor:pointer}.post img{border-radius:5%;vertical-align:middle;margin-bottom:10px}.post a{color:#1da1f2}.post a:hover{text-decoration:underline}.post p{margin:10px 0;color:#14171a;font-size:15px;line-height:1.5}.btn,.btn2{border:none;padding:8px 12px;border-radius:9999px;margin-right:10px;font-size:14px;margin-top:10px;display:inline-block}.btn2{background-color:#657786}.btn2:hover,.btn:hover{opacity:.9}.post iframe{margin-top:10px;border-radius:8px}blockquote{border-left:4px solid #657786;background-color:#f5f8fa;padding:10px;margin:10px 0;font-style:italic;color:#657786}#loadMoreBtn{border:none;padding:12px 20px;border-radius:9999px;font-size:16px;margin:20px auto;display:block;max-width:200px;text-align:center;transition:background-color .2s}#loadMoreBtn:hover{background-color:#1a91da}#loadMoreBtn:disabled{background-color:#ccd6dd;cursor:not-allowed}.loading{opacity:.7;pointer-events:none}.controls{display:flex;justify-content:space-between;align-items:center;padding:10px;background-color:#fff;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1)}.limit-info{font-size:14px;color:#657786}
|
|
/* NEW: Error Table Styling (Twitter-esque) */
|
|
.error-container { max-width: 600px; margin-bottom: 20px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
|
.error-header { background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 8px 8px 0 0; padding: 15px; color: #721c24; font-size: 15px; }
|
|
.error-table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid #ccd6dd; border-top: none; border-radius: 0 0 8px 8px; font-size: 14px; table-layout: fixed; }
|
|
.error-table th { background-color: #f5f8fa; color: #657786; text-align: left; padding: 12px; border-bottom: 1px solid #ccd6dd; font-weight: bold; }
|
|
.error-table td { padding: 12px; border-bottom: 1px solid #e1e8ed; vertical-align: top; word-wrap: break-word; }
|
|
.error-table tr:last-child td { border-bottom: none; }
|
|
.error-table tr:hover { background-color: #f5f8fa; }
|
|
.err-id { width: 25%; font-weight: bold; color: #1da1f2; font-family: monospace; }
|
|
.err-val { width: 45%; color: #14171a; }
|
|
.err-msg { width: 30%; color: #e0245e; font-weight: bold; font-size: 13px; }
|
|
.err-hint { display: block; margin-top: 4px; font-weight: normal; font-size: 12px; color: #657786; }
|
|
/* YouTube Facade Styling */
|
|
.yt-facade {
|
|
position: relative;
|
|
width: 400px;
|
|
height: 255px;
|
|
background-image: url('https://alceawis.de/other/images/fediavas/youtubeoverlay.gif');
|
|
background-size: cover;
|
|
background-position: center;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-top: 10px;
|
|
border: 1px solid #ccd6dd;
|
|
}
|
|
.yt-facade::before {
|
|
content: '▶ Load Video';
|
|
background: rgba(0,0,0,0.7);
|
|
color: white;
|
|
padding: 8px 15px;
|
|
border-radius: 20px;
|
|
font-size: 14px;
|
|
}
|
|
</style>
|
|
<script src="other/extra/scripts/libraries/crypto-js.min.js"></script>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Twitter-style Timeline</title>
|
|
</head>
|
|
<body>
|
|
<details><summary>-</summary>
|
|
<div class="controls">
|
|
<div class="limit-info">Initial limit: <span id="limitValue">15</span> posts</div>
|
|
<a href="?user=alcea&limit=3" class="btn2">View 3 Posts</a>
|
|
<a href="?user=alcea&limit=10" class="btn2">View 10 Posts</a>
|
|
<a href="?user=alcea" class="btn">Default (15)</a>
|
|
<hr><br>Changelog:<br>
|
|
2025-08-28 Add dynamic "Load more posts" feature<br>
|
|
<hr>
|
|
</div></details>
|
|
|
|
<div id="errorLog"></div>
|
|
<div id="jsonloaded"></div>
|
|
|
|
<form action="/other/extra/scripts/fakesocialmedia/commentload.html" method="get" target="_blank" style="margin-bottom: 20px; max-width: 600px;">
|
|
<input type="text" id="keyword" name="text" required placeholder="Search posts..." style="padding: 8px; width: 70%; border: 1px solid #ccd6dd; border-radius: 4px;">
|
|
<input type="hidden" name="number" value="8000">
|
|
<button type="submit" style="padding: 8px 16px; background-color: #1da1f2; color: white; border: none; border-radius: 4px; cursor: pointer;">Search</button>
|
|
</form>
|
|
|
|
<div id="containerfakesocialmedia"></div>
|
|
<button id="loadMoreBtn" style="display: none;">Load More</button>
|
|
<script>
|
|
// Global variables to manage post loading state
|
|
let currentOffset = 0;
|
|
let allPosts = [];
|
|
let postsPerLoad = 15;
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
let limit = urlParams.get("limit") || "15";
|
|
const searchText = urlParams.get("text") || "";
|
|
const baseurl = "https://alcea-wisteria.de/z_files/emoji";
|
|
const errorLogDiv = document.getElementById('errorLog');
|
|
const loadMoreBtn = document.getElementById('loadMoreBtn');
|
|
const container = document.getElementById('containerfakesocialmedia');
|
|
const limitValueSpan = document.getElementById('limitValue');
|
|
// Update the limit display
|
|
limitValueSpan.textContent = limit;
|
|
function getQueryParam(param) {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
return urlParams.get(param);
|
|
}
|
|
function logError(message) {
|
|
const errorMessage = document.createElement('p');
|
|
errorMessage.textContent = message;
|
|
errorLogDiv.appendChild(errorMessage);
|
|
const fixLink = document.createElement('a');
|
|
fixLink.textContent = 'Fix!';
|
|
fixLink.target = '_blank';
|
|
fixLink.href = '/other/extra/scripts/fakesocialmedia/fix.php';
|
|
fixLink.style.color = 'pink';
|
|
errorLogDiv.appendChild(fixLink);
|
|
const commonIssuesLink = document.createElement('a');
|
|
commonIssuesLink.textContent = '- Common Issues';
|
|
commonIssuesLink.target = '_blank';
|
|
commonIssuesLink.href = 'https://ry3yr.github.io/fakesocialmediatimelineissue.html';
|
|
commonIssuesLink.style.color = 'blue';
|
|
errorLogDiv.appendChild(commonIssuesLink);
|
|
}
|
|
let all = "";
|
|
const url = new URL(window.location.href);
|
|
if (url.searchParams.get("all") === "yes") {
|
|
all = "";
|
|
} else {
|
|
all = "_part";
|
|
}
|
|
// Function to check if post contains search text
|
|
function postContainsText(post, searchText) {
|
|
if (!searchText) return true;
|
|
|
|
try {
|
|
const date = Object.keys(post)[0];
|
|
const innerObj = post[date];
|
|
const value = innerObj.value || "";
|
|
const hashtags = innerObj.hashtags || "";
|
|
|
|
// Check if search text is in post content, hashtags, or date
|
|
return value.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
hashtags.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
date.toLowerCase().includes(searchText.toLowerCase());
|
|
} catch (e) {
|
|
console.error("Error checking post for search text:", e);
|
|
return true;
|
|
}
|
|
}
|
|
// Function to set avatar with fallback
|
|
function setAvatarWithFallback(imgElement, avatarUrl, defaultUrl = 'https://alcea-wisteria.de/z_files/emoji/undefined.png') {
|
|
if (!imgElement) return;
|
|
|
|
const testImg = new Image();
|
|
testImg.src = avatarUrl;
|
|
|
|
testImg.onload = function() {
|
|
// Avatar exists, use it
|
|
imgElement.src = avatarUrl;
|
|
};
|
|
|
|
testImg.onerror = function() {
|
|
// Avatar doesn't exist, use default
|
|
imgElement.src = defaultUrl;
|
|
imgElement.alt = 'default avatar';
|
|
};
|
|
}
|
|
// Function to create a post element
|
|
function createPostElement(obj) {
|
|
const date = Object.keys(obj)[0];
|
|
const innerObj = obj[date];
|
|
let value = innerObj.value.replace(/\\\//g, '/');
|
|
const hashtags = innerObj.hashtags;
|
|
const postElement = document.createElement('div');
|
|
postElement.classList.add('post');
|
|
const dateElement = document.createElement('p');
|
|
dateElement.innerHTML = `<hr>Date: ${date}`;
|
|
const valueElement = document.createElement('p');
|
|
const valueWithoutQuotes = value.replace(/𒐫[\s\S]*?𒐫/g, ''); // Removes 𒐫...𒐫 block
|
|
|
|
// Set initial avatar (will be updated with fallback)
|
|
const userAvatarUrl = `${baseurl}/${user}.png`;
|
|
valueElement.innerHTML = `<img src="${userAvatarUrl}" width=100px><br>@<a target="_blank" href="/fakesocialrender_limited.html?user=${user}" style="color:pink">${user}</a>: ${replaceEmojis(valueWithoutQuotes)
|
|
.replace(/(<a\s[^>]*>.*?<\/a>)|(<[^>]+>)|(https?:\/\/[^\s"<]+)/g, (match, anchorTag, htmlTag, plainUrl) => {
|
|
// If it's already an anchor tag or HTML tag, return it as-is
|
|
if (anchorTag || htmlTag) {
|
|
return match;
|
|
}
|
|
|
|
// Handle plain URLs
|
|
if (plainUrl) {
|
|
if (plainUrl.includes('pixiv.net')) {
|
|
const pixivRegex = /https?:\/\/(?:www\.)?pixiv\.net\/(?:en\/)?artworks\/(\d+)/;
|
|
const pixivMatch = plainUrl.match(pixivRegex);
|
|
if (pixivMatch) {
|
|
const artworkId = pixivMatch[1];
|
|
return `<div><img src="https://embed.pixiv.net/decorate.php?illust_id=${artworkId}&mode=sns-automator" width="50%"></div><br><a href="${plainUrl}" target="_blank">${plainUrl}</a>`;
|
|
}
|
|
}
|
|
if (plainUrl.endsWith('.gif') || plainUrl.endsWith('.png') || plainUrl.endsWith('.webp') || plainUrl.endsWith('.jpg') || plainUrl.endsWith('.jpeg')) {
|
|
let imageWidth = "50%";
|
|
if (plainUrl.includes("emoji")) {
|
|
imageWidth = "45px";
|
|
}
|
|
const imageElement = `<img src="${plainUrl}" width="${imageWidth}" alt="${plainUrl}">`;
|
|
if (plainUrl.includes(`${baseurl}`)) {
|
|
return imageElement;
|
|
} else {
|
|
return `${imageElement}<br><a href="${plainUrl}" target="_blank">${plainUrl}</a>`;
|
|
}
|
|
}
|
|
return `<a href="${plainUrl}" target="_blank">${plainUrl}</a>`;
|
|
}
|
|
return match;
|
|
})
|
|
.replace(/\n/g, '<br>')}`;
|
|
|
|
// Set avatar with fallback
|
|
const profileImg = valueElement.querySelector('img');
|
|
if (profileImg) {
|
|
setAvatarWithFallback(profileImg, userAvatarUrl);
|
|
}
|
|
|
|
const hashtagsElement = document.createElement('p');
|
|
hashtagsElement.textContent = `Hashtags: ${hashtags}`;
|
|
postElement.appendChild(dateElement);
|
|
postElement.appendChild(valueElement);
|
|
//quote function
|
|
const quoteRegex = /𒐫([\s\S]*?)𒐫/g;
|
|
let match;
|
|
while ((match = quoteRegex.exec(value)) !== null) {
|
|
const quoteBlock = document.createElement('div');
|
|
quoteBlock.style.borderLeft = '4px solid #ccc';
|
|
quoteBlock.style.padding = '10px';
|
|
quoteBlock.style.backgroundColor = '#f9f9f9';
|
|
const formattedQuote = match[1].replace(/\n/g, '<br>');
|
|
quoteBlock.innerHTML = formattedQuote.trim();
|
|
postElement.appendChild(quoteBlock);
|
|
}
|
|
// link2acws-akkoma integration
|
|
if (value.includes("•acws")) {
|
|
async function safeFetchJson(url, retries = 3) {
|
|
try {
|
|
const res = await fetch(url, { cache: "no-store" });
|
|
if (!res.ok) {
|
|
throw new Error(`HTTP error ${res.status} (${res.statusText}) while fetching ${url}`);
|
|
}
|
|
const text = await res.text();
|
|
if (text.length < 100) {
|
|
throw new Error(`Fetched data is too short, possible incomplete response: ${text.length} bytes`);
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(text);
|
|
if (Array.isArray(parsed)) {
|
|
return parsed;
|
|
}
|
|
if (parsed && Array.isArray(parsed.json)) {
|
|
return parsed.json;
|
|
}
|
|
throw new Error(`Unexpected JSON structure in ${url}`);
|
|
} catch (err) {
|
|
throw new Error(`JSON parsing failed for ${url}: ${err.message}`);
|
|
}
|
|
} catch (err) {
|
|
if (retries > 0) {
|
|
console.warn(`Fetch failed for ${url}, retrying... (${retries} retries left)\nReason: ${err.message}`);
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
return safeFetchJson(url, retries - 1);
|
|
} else {
|
|
console.error("Fetch failed with no retries left:", err.message);
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
function normalizeLineBreaks(str) {
|
|
return str.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
}
|
|
function normalizeText(str) {
|
|
return normalizeLineBreaks(str).normalize('NFC').trim();
|
|
}
|
|
function removeLineBreaks(str) {
|
|
return normalizeText(str).replace(/\n/g, '');
|
|
}
|
|
function normalizeTextCompletely(str) {
|
|
return str
|
|
.replace(/\r\n|\r/g, '\n')
|
|
.replace(/\s+/g, ' ')
|
|
.replace(/^\s+|\s+$/g, '')
|
|
.normalize('NFC');
|
|
}
|
|
safeFetchJson("/other/extra/scripts/fakesocialmedia/0ld/data_akkoma.json")
|
|
.then(akkomaData => {
|
|
if (!akkomaData) throw new Error("JSON parse failure");
|
|
let matchedUrl = null;
|
|
let matchedPreview = null;
|
|
let matchType = null;
|
|
const rawValue = value;
|
|
const normValue = normalizeText(value);
|
|
const noBreaksValue = removeLineBreaks(value);
|
|
for (const akkoma of akkomaData) {
|
|
if (!akkoma.preview) continue;
|
|
const rawPreview = akkoma.preview;
|
|
const normPreview = normalizeText(rawPreview);
|
|
const noBreaksPreview = removeLineBreaks(rawPreview);
|
|
if (rawValue.startsWith(rawPreview)) {
|
|
matchedUrl = akkoma.url;
|
|
matchedPreview = rawPreview;
|
|
matchType = 'raw';
|
|
break;
|
|
}
|
|
if (normValue.startsWith(normPreview)) {
|
|
matchedUrl = akkoma.url;
|
|
matchedPreview = rawPreview;
|
|
matchType = 'normalized';
|
|
break;
|
|
}
|
|
if (noBreaksValue.startsWith(noBreaksPreview)) {
|
|
matchedUrl = akkoma.url;
|
|
matchedPreview = rawPreview;
|
|
matchType = 'noBreaks';
|
|
break;
|
|
}
|
|
const maxOffset = 30;
|
|
const indexInNorm = normValue.indexOf(normPreview);
|
|
if (indexInNorm >= 0 && indexInNorm <= maxOffset) {
|
|
matchedUrl = akkoma.url;
|
|
matchedPreview = rawPreview;
|
|
matchType = 'loose';
|
|
break;
|
|
}
|
|
}
|
|
if (!matchedUrl) {
|
|
const normValueCompletely = normalizeTextCompletely(value);
|
|
const maxOffset = 30;
|
|
for (const akkoma of akkomaData) {
|
|
if (!akkoma.preview) continue;
|
|
const rawPreview = akkoma.preview;
|
|
const normPreviewCompletely = normalizeTextCompletely(rawPreview);
|
|
const indexInNormCompletely = normValueCompletely.indexOf(normPreviewCompletely);
|
|
if (indexInNormCompletely >= 0 && indexInNormCompletely <= maxOffset) {
|
|
matchedUrl = akkoma.url;
|
|
matchedPreview = rawPreview;
|
|
matchType = 'finalNormalized';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!matchedUrl) {
|
|
for (const akkoma of akkomaData) {
|
|
if (!akkoma.preview) continue;
|
|
const rawPreview = akkoma.preview;
|
|
if (rawValue === rawPreview) {
|
|
matchedUrl = akkoma.url;
|
|
matchedPreview = rawPreview;
|
|
matchType = 'exact';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matchedUrl) {
|
|
try {
|
|
const urlObj = new URL(matchedUrl);
|
|
const domain = urlObj.hostname;
|
|
|
|
const profileImg = valueElement.querySelector('img');
|
|
if (profileImg) {
|
|
const domainImageUrl = `/other/images/fediavas/${domain}.gif`;
|
|
|
|
const testDomain = new Image();
|
|
testDomain.src = domainImageUrl;
|
|
//testDomain.onload = function() {profileImg.src = domainImageUrl;profileImg.alt = `${domain} icon`;}; //old
|
|
|
|
testDomain.onload = function() {
|
|
profileImg.src = domainImageUrl;
|
|
profileImg.alt = `${domain} icon`;
|
|
const domainBtn = document.createElement('a');
|
|
//domainBtn.href = matchedUrl;
|
|
domainBtn.href = `https://${domain}`;
|
|
domainBtn.target = "_blank";
|
|
domainBtn.textContent = domain;
|
|
let hash = 0; for(let i=0;i<domain.length;i++) hash=domain.charCodeAt(i)+((hash<<5)-hash);
|
|
domainBtn.style.cssText = `margin-left:8px;padding:2px 8px;background:hsl(${Math.abs(hash%360)},70%,50%);color:white;border-radius:4px;text-decoration:none;font-size:0.8em`;
|
|
profileImg.parentNode.insertBefore(domainBtn, profileImg.nextSibling);
|
|
};
|
|
|
|
testDomain.onerror = function() {
|
|
//const domainImageUrl = `/other/images/fediavas/${domain}.gif`;
|
|
console.log(`No domain-specific GIF found for ${domain}.`);
|
|
};
|
|
}
|
|
} catch (e) {
|
|
//const domainImageUrl = `/other/images/fediavas/${domain}.gif`;
|
|
console.log("Profile image replacement skipped:", e);
|
|
}
|
|
|
|
const container = document.createElement('span');
|
|
const link = document.createElement('a');
|
|
link.target = "_blank";
|
|
link.classList.add('btn2');
|
|
link.href = matchedUrl;
|
|
link.textContent = "Permalink (match found)";
|
|
const proof = document.createElement('div');
|
|
proof.style.fontSize = '0em';
|
|
proof.style.marginTop = '0px';
|
|
switch (matchType) {
|
|
case 'raw':
|
|
proof.style.color = 'green';
|
|
proof.textContent = `✔ Match found on first try (raw):\nPreview: "${matchedPreview}"\nPost value: "${value.slice(0, matchedPreview.length + 20)}..."`;
|
|
break;
|
|
case 'normalized':
|
|
proof.style.color = 'lightgreen';
|
|
proof.textContent = `✔ Match found after normalization:\nPreview: "${matchedPreview}"\nPost value: "${value.slice(0, matchedPreview.length + 20)}..."`;
|
|
break;
|
|
case 'noBreaks':
|
|
proof.style.color = 'lightblue';
|
|
proof.textContent = `✔ Match found after removing line breaks:\nPreview: "${matchedPreview}"\nPost value: "${value.slice(0, matchedPreview.length + 20)}..."`;
|
|
break;
|
|
case 'loose':
|
|
proof.style.color = 'orange';
|
|
proof.textContent = `✔ Loose match found (near start of post):\nPreview: "${matchedPreview}"\nPost value: "${value.slice(0, matchedPreview.length + 20)}..."`;
|
|
break;
|
|
case 'finalNormalized':
|
|
proof.style.color = 'purple';
|
|
proof.textContent = `✔ Match found after final normalization:\nPreview: "${matchedPreview}"\nPost value: "${value.slice(0, matchedPreview.length + 20)}..."`;
|
|
break;
|
|
case 'exact':
|
|
proof.style.color = 'blue';
|
|
proof.textContent = `✔ Exact match found:\nPreview: "${matchedPreview}"\nPost value: "${value.slice(0, matchedPreview.length + 20)}..."`;
|
|
break;
|
|
}
|
|
container.appendChild(link);
|
|
container.appendChild(proof);
|
|
const iframeContainer = document.createElement('div');
|
|
const iframe = document.createElement('iframe');
|
|
iframe.src = `/other/extra/scripts/fakesocialmedia/replyhandler.html?url=${encodeURIComponent(matchedUrl)}&sorted`;
|
|
iframe.style.border = 'none';
|
|
iframe.style.height = '65px';
|
|
iframe.style.width = '100%';
|
|
iframe.style.overflow = 'auto';
|
|
const iframeHeightText = document.createElement('div');
|
|
iframeHeightText.style.marginTop = '10px';
|
|
iframeHeightText.style.fontFamily = 'monospace';
|
|
iframeHeightText.style.color = 'gray';
|
|
iframeHeightText.textContent = "";
|
|
iframe.onload = function () {
|
|
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
|
|
function measureIframeHeight() {
|
|
const bodyHeight = iframeDocument.body.scrollHeight;
|
|
iframe.style.height = `${bodyHeight + 33}px`;
|
|
}
|
|
measureIframeHeight();
|
|
setInterval(measureIframeHeight, 3000);
|
|
};
|
|
iframeContainer.appendChild(iframe);
|
|
container.appendChild(iframeContainer);
|
|
container.appendChild(iframeHeightText);
|
|
postElement.appendChild(container);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error("Fetch error:", err);
|
|
const fallback = document.createElement('a');
|
|
fallback.classList.add('btn2');
|
|
fallback.href = "#";
|
|
fallback.textContent = "Fetch error";
|
|
const reason = document.createElement('div');
|
|
reason.style.color = 'red';
|
|
reason.style.fontFamily = 'monospace';
|
|
reason.style.fontSize = '0.9em';
|
|
reason.style.marginTop = '5px';
|
|
reason.textContent = `❌ Reason: ${err.message}`;
|
|
postElement.appendChild(fallback);
|
|
postElement.appendChild(reason);
|
|
});
|
|
}
|
|
postElement.appendChild(hashtagsElement);
|
|
const buttonElement = document.createElement('button');
|
|
buttonElement.classList.add('btn');
|
|
const buttonText = value.slice(0, 60) || 'btn';
|
|
buttonElement.textContent = 'Comment';
|
|
buttonElement.addEventListener('click', () => {
|
|
window.open(`/other/extra/scripts/fakesocialmedia/comment.php?text=${encodeURIComponent(buttonText)}`, '_blank');
|
|
});
|
|
postElement.appendChild(buttonElement);
|
|
|
|
const buttonElement2 = document.createElement('a');
|
|
buttonElement2.classList.add('btn2');
|
|
const buttonText2 = value.slice(0, 60) || 'btn';
|
|
buttonElement2.textContent = '(load comments)';
|
|
buttonElement2.href = `/other/extra/scripts/fakesocialmedia/commentload.html?number=8000&text=${encodeURIComponent(buttonText2)}`;
|
|
buttonElement2.target = "_blank";
|
|
postElement.appendChild(buttonElement2);
|
|
if (value.includes('soundcloud.com') || value.includes('m.soundcloud.com')) {
|
|
const soundcloudRegex = /https?:\/\/(?:www\.|m\.)?soundcloud\.com\/[^\s'"\)]+/g;
|
|
const soundcloudMatches = value.match(soundcloudRegex);
|
|
if (soundcloudMatches && soundcloudMatches.length > 0) {
|
|
const soundcloudUrl = soundcloudMatches[0];
|
|
const debugLog = document.createElement('div');
|
|
debugLog.style.fontSize = '12px';
|
|
debugLog.style.color = '#666';
|
|
debugLog.style.marginTop = '5px';
|
|
postElement.appendChild(debugLog);
|
|
const soundcloudEmbed = document.createElement('iframe');
|
|
soundcloudEmbed.width = '100%';
|
|
soundcloudEmbed.height = '266';
|
|
soundcloudEmbed.scrolling = 'no';
|
|
soundcloudEmbed.frameBorder = 'no';
|
|
soundcloudEmbed.allow = 'autoplay';
|
|
soundcloudEmbed.src = `/other/extra/scripts/fakesocialmedia/soundcloudfetch.php?scurl=${soundcloudUrl}`;
|
|
soundcloudEmbed.style.marginTop = '10px';
|
|
soundcloudEmbed.style.borderRadius = '8px';
|
|
postElement.appendChild(soundcloudEmbed);
|
|
soundcloudEmbed.onerror = function() {
|
|
const errorMsg = document.createElement('div');
|
|
errorMsg.style.color = 'red';
|
|
errorMsg.style.fontSize = '12px';
|
|
errorMsg.textContent = 'Failed to load SoundCloud embed';
|
|
postElement.appendChild(errorMsg);
|
|
};
|
|
}
|
|
}
|
|
if (value.includes('bandcamp.com')) {
|
|
// Regex to capture the full Bandcamp track URL
|
|
const bandcampRegex = /https?:\/\/[a-zA-Z0-9-]+\.bandcamp\.com\/track\/[^\s'"\)]+/g;
|
|
const bandcampMatches = value.match(bandcampRegex);
|
|
|
|
if (bandcampMatches && bandcampMatches.length > 0) {
|
|
const bandcampUrl = encodeURIComponent(bandcampMatches[0]);
|
|
const embedContainer = document.createElement('div');
|
|
embedContainer.style.marginTop = '10px';
|
|
|
|
// Use fetch to get the iframe string generated by your PHP
|
|
fetch(`/other/extra/scripts/fakesocialmedia/bandcampfetch.php?url=${bandcampUrl}`)
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
// html is the <iframe...> tag returned by your PHP script
|
|
embedContainer.innerHTML = html;
|
|
postElement.appendChild(embedContainer);
|
|
})
|
|
.catch(err => {
|
|
const errorMsg = document.createElement('div');
|
|
errorMsg.style.color = 'red';
|
|
errorMsg.style.fontSize = '12px';
|
|
errorMsg.textContent = 'Failed to load Bandcamp player';
|
|
postElement.appendChild(errorMsg);
|
|
});
|
|
}
|
|
}
|
|
// --- NEW YOUTUBE FACADE LOGIC ---
|
|
if (value.includes('youtube.com') && !value.includes('youtube.com/shorts')) {
|
|
const vId = getVideoId(value);
|
|
if (vId) {
|
|
const facade = document.createElement('div');
|
|
facade.className = 'yt-facade';
|
|
facade.onclick = function() {
|
|
const iframe = document.createElement('iframe');
|
|
iframe.src = `https://www.youtube.com/embed/${vId}?autoplay=1`;
|
|
iframe.width = '400'; iframe.height = '255'; iframe.frameBorder = '0';
|
|
iframe.allow = "autoplay; encrypted-media; picture-in-picture";
|
|
iframe.allowFullscreen = true;
|
|
facade.replaceWith(iframe);
|
|
};
|
|
postElement.appendChild(facade);
|
|
}
|
|
}
|
|
|
|
return postElement;
|
|
}
|
|
// Function to load more posts
|
|
function loadMorePosts() {
|
|
loadMoreBtn.classList.add('loading');
|
|
loadMoreBtn.textContent = 'Loading...';
|
|
|
|
// Calculate the next batch of posts to show
|
|
const nextBatch = allPosts.slice(currentOffset, currentOffset + postsPerLoad);
|
|
|
|
if (nextBatch.length === 0) {
|
|
loadMoreBtn.innerHTML = 'No more posts <a href="?user=alcea&all=yes&limit=55">(all)</a>';
|
|
loadMoreBtn.disabled = true;
|
|
return;
|
|
}
|
|
|
|
// Add a small delay for better UX
|
|
setTimeout(() => {
|
|
nextBatch.forEach(post => {
|
|
// Only show posts that contain the search text (if any)
|
|
if (postContainsText(post, searchText)) {
|
|
container.appendChild(createPostElement(post));
|
|
}
|
|
});
|
|
|
|
currentOffset += postsPerLoad;
|
|
|
|
// Check if there are more posts to load
|
|
if (currentOffset >= allPosts.length) {
|
|
loadMoreBtn.innerHTML = 'No more posts <a href="?user=alcea&all=yes&limit=55">(all)</a>';
|
|
loadMoreBtn.disabled = true;
|
|
} else {
|
|
loadMoreBtn.textContent = 'Load More';
|
|
loadMoreBtn.disabled = false;
|
|
}
|
|
|
|
loadMoreBtn.classList.remove('loading');
|
|
}, 300);
|
|
}
|
|
const user = getQueryParam('user');
|
|
if (user) {
|
|
const jsonFile = `/other/extra/scripts/fakesocialmedia/data${all}_${user}.json?t=${Date.now()}`;
|
|
console.log(jsonFile);
|
|
let myDiv = document.getElementById("jsonloaded");
|
|
myDiv.textContent = jsonFile;
|
|
|
|
// REPLACED FETCH LOGIC WITH DETAILED ERROR TABLE
|
|
fetch(jsonFile)
|
|
.then(response => response.text())
|
|
.then(text => {
|
|
try {
|
|
// Try to parse globally first
|
|
const jsonData = JSON.parse(text);
|
|
allPosts = jsonData;
|
|
|
|
// --- SUCCESS PATH ---
|
|
const initialLimit = Math.min(parseInt(limit) || 15, allPosts.length);
|
|
const initialPosts = allPosts.slice(0, initialLimit);
|
|
|
|
initialPosts.forEach(post => {
|
|
if (postContainsText(post, searchText)) {
|
|
container.appendChild(createPostElement(post));
|
|
}
|
|
});
|
|
|
|
currentOffset = initialLimit;
|
|
if (allPosts.length > initialLimit) {
|
|
loadMoreBtn.style.display = 'block';
|
|
loadMoreBtn.addEventListener('click', loadMorePosts);
|
|
}
|
|
} catch (globalError) {
|
|
// --- FAILURE PATH: DIAGNOSTIC MODE ---
|
|
console.error("Global parse failed, starting itemized diagnosis...", globalError);
|
|
|
|
const errorContainer = document.createElement('div');
|
|
errorContainer.className = 'error-container';
|
|
errorContainer.innerHTML = `
|
|
<div class="error-header">
|
|
<strong>Something went wrong.</strong><br>
|
|
The timeline couldn't be loaded because the data file has syntax errors. See the specific issues below.
|
|
</div>
|
|
<table class="error-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Post ID / Date</th>
|
|
<th>Content Snippet</th>
|
|
<th>Error Details</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="diagnosticBody"></tbody>
|
|
</table>
|
|
`;
|
|
errorLogDiv.appendChild(errorContainer);
|
|
|
|
// Add standard fix links AFTER the table
|
|
const linkWrapper = document.createElement('div');
|
|
linkWrapper.style.marginTop = '10px';
|
|
linkWrapper.innerHTML = `
|
|
<a href="/other/extra/scripts/fakesocialmedia/fix.php" class="btn" style="background-color:#e0245e;">Autofix JSON</a>
|
|
<a href="https://ry3yr.github.io/fakesocialmediatimelineissue.html" class="btn2" target="_blank">Help Guide</a>
|
|
`;
|
|
errorLogDiv.appendChild(linkWrapper);
|
|
const tbody = document.getElementById('diagnosticBody');
|
|
// Heuristic: Remove outer brackets and split by object delimiters
|
|
let cleanText = text.trim();
|
|
if(cleanText.startsWith('[')) cleanText = cleanText.substring(1);
|
|
if(cleanText.endsWith(']')) cleanText = cleanText.substring(0, cleanText.length - 1);
|
|
|
|
// Split roughly by "}, {"
|
|
const rawItems = cleanText.split(/}\s*,\s*{/);
|
|
let errorCount = 0;
|
|
rawItems.forEach((item, index) => {
|
|
// Reconstruct valid JSON object syntax for individual testing
|
|
let candidate = item.trim();
|
|
// Add braces back if missing
|
|
if (!candidate.startsWith('{')) candidate = '{' + candidate;
|
|
if (!candidate.endsWith('}')) candidate = candidate + '}';
|
|
try {
|
|
JSON.parse(candidate);
|
|
// If this passes, the item is fine, do nothing.
|
|
} catch (itemError) {
|
|
errorCount++;
|
|
|
|
// 1. Try to extract ID (Date key)
|
|
const idMatch = candidate.match(/"(\d{4}-\d{2}-\d{2}[^"]+)"/);
|
|
const displayId = idMatch ? idMatch[1] : `Post #${index + 1}`;
|
|
// 2. Try to extract Value
|
|
const valMatch = candidate.match(/"value"\s*:\s*"(.*?)"/);
|
|
let safeVal = valMatch ? valMatch[1] : "<em>(Could not read value)</em>";
|
|
// Truncate and escape HTML
|
|
safeVal = safeVal.replace(/</g, "<").substring(0, 80) + (safeVal.length > 80 ? "..." : "");
|
|
// 3. Create Table Row
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td class="err-id">${displayId}</td>
|
|
<td class="err-val">${safeVal}</td>
|
|
<td class="err-msg">
|
|
${itemError.message}
|
|
<span class="err-hint">Check for unescaped quotes or newlines.</span>
|
|
</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
}
|
|
});
|
|
// If we couldn't find specific item errors (e.g. file is empty or totally garbled)
|
|
if (errorCount === 0) {
|
|
tbody.innerHTML = `
|
|
<tr>
|
|
<td colspan="3" style="text-align:center; padding:20px; color:#657786;">
|
|
Could not isolate individual items. The file structure might be completely broken.<br>
|
|
Global Error: ${globalError.message}
|
|
</td>
|
|
</tr>`;
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
logError(`Network/Fetch Error: ${error.message}`);
|
|
});
|
|
} else {
|
|
const element = document.createElement('div');
|
|
element.textContent = 'No user specified (?user=user)';
|
|
console.log('No user selected, defaulting to alcea');
|
|
var currentUrl = window.location.href;
|
|
var newUrl = currentUrl.split('?')[0] + '?user=alcea';
|
|
window.location.href = newUrl;
|
|
document.body.appendChild(element);
|
|
}
|
|
function getVideoId(url) {
|
|
const regex = /(?:v=|\/embed\/|\.be\/|\/v\/)([a-zA-Z0-9_-]{11})/;
|
|
const match = url.match(regex);
|
|
return match ? match[1] : null;
|
|
}
|
|
function replaceEmojis(text) {
|
|
return text.replace(/:(\w+):/g, (match, emoji) => {
|
|
return `${baseurl}/${emoji}.gif`;
|
|
});
|
|
}
|
|
// Modal functionality for images
|
|
const modal = document.createElement('div');
|
|
modal.id = 'imageModal';
|
|
modal.className = 'modal';
|
|
modal.innerHTML = `
|
|
<span class="modal-close">×</span>
|
|
<img class="modal-content" id="modalImage">
|
|
<div id="modalCaption"></div>
|
|
`;
|
|
document.body.appendChild(modal);
|
|
const modalStyles = document.createElement('style');
|
|
modalStyles.textContent = `
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 1000;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: auto;
|
|
background-color: rgba(0, 0, 0, 0.9);
|
|
}
|
|
.modal-content {
|
|
display: block;
|
|
margin: auto;
|
|
max-width: 90%;
|
|
max-height: 80%;
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
.modal-close {
|
|
position: absolute;
|
|
top: 15px;
|
|
right: 35px;
|
|
color: #f1f1f1;
|
|
font-size: 40px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
z-index: 1001;
|
|
}
|
|
.modal-close:hover,
|
|
.modal-close:focus {
|
|
color: #bbb;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
#modalCaption {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
color: white;
|
|
text-align: center;
|
|
width: 80%;
|
|
}
|
|
.post img {
|
|
cursor: pointer;
|
|
transition: 0.3s;
|
|
}
|
|
.post img:hover {
|
|
opacity: 0.7;
|
|
}
|
|
`;
|
|
document.head.appendChild(modalStyles);
|
|
document.addEventListener('click', function(e) {
|
|
if (e.target.tagName === 'IMG' && e.target.closest('.post')) {
|
|
const modal = document.getElementById('imageModal');
|
|
const modalImg = document.getElementById('modalImage');
|
|
const captionText = document.getElementById('modalCaption');
|
|
modal.style.display = "block";
|
|
modalImg.src = e.target.src;
|
|
captionText.innerHTML = e.target.alt || "Image";
|
|
}
|
|
if (e.target.classList.contains('modal-close')) {
|
|
document.getElementById('imageModal').style.display = "none";
|
|
}
|
|
});
|
|
document.getElementById('imageModal').addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
this.style.display = "none";
|
|
}
|
|
});
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === "Escape") {
|
|
document.getElementById('imageModal').style.display = "none";
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|