chore: checkpoint current working state

This commit is contained in:
2026-02-12 17:40:52 +01:00
parent 585e5d5455
commit bbfa93a586
6 changed files with 206 additions and 44 deletions

View File

@@ -687,6 +687,77 @@ function sanitizeProfileNumber(value) {
return parsed; return parsed;
} }
function applyProfileVariantTemplates(text, profileNumber) {
if (typeof text !== 'string' || !text) {
return text || '';
}
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
let result = text.replace(/\{Profil(?:e)?-(\d+)(?:-(\d+))?\?\s*"([\s\S]*?)"(?:\s*:\s*"([\s\S]*?)")?\}/gi, (match, startStr, endStr, ifText, elseText) => {
if (!normalizedProfileNumber) {
return '';
}
const start = parseInt(startStr, 10);
const end = endStr ? parseInt(endStr, 10) : start;
if (Number.isNaN(start) || Number.isNaN(end)) {
return '';
}
const lower = Math.min(start, end);
const upper = Math.max(start, end);
if (normalizedProfileNumber < lower || normalizedProfileNumber > upper) {
return elseText || '';
}
return ifText || '';
});
result = result.replace(/\{Profil(?:e)?-(\d+)(?:-(\d+))?:([\s\S]*?)\}/gi, (match, startStr, endStr, content) => {
if (!normalizedProfileNumber) {
return '';
}
const start = parseInt(startStr, 10);
const end = endStr ? parseInt(endStr, 10) : start;
if (Number.isNaN(start) || Number.isNaN(end)) {
return '';
}
const lower = Math.min(start, end);
const upper = Math.max(start, end);
if (normalizedProfileNumber < lower || normalizedProfileNumber > upper) {
return '';
}
return content;
});
return result;
}
function applyRandomNumberTemplates(text) {
if (typeof text !== 'string' || !text) {
return text || '';
}
return text.replace(/\{ZUFALL-(-?\d+)-(-?\d+)\}/gi, (match, startStr, endStr) => {
const start = parseInt(startStr, 10);
const end = parseInt(endStr, 10);
if (Number.isNaN(start) || Number.isNaN(end)) {
return match;
}
const lower = Math.min(start, end);
const upper = Math.max(start, end);
const span = upper - lower + 1;
if (span <= 0) {
return String(lower);
}
const value = Math.floor(Math.random() * span) + lower;
return String(value);
});
}
function normalizeDeadline(value) { function normalizeDeadline(value) {
if (!value && value !== 0) { if (!value && value !== 0) {
return null; return null;
@@ -5059,13 +5130,8 @@ app.delete('/api/search-posts', (req, res) => {
app.get('/api/profile-state', (req, res) => { app.get('/api/profile-state', (req, res) => {
try { try {
const scopeId = req.profileScope; const scopeId = req.profileScope;
let profileNumber = getScopedProfileNumber(scopeId); const profileNumber = getScopedProfileNumber(scopeId);
if (!profileNumber) { res.json({ profile_number: profileNumber || null });
profileNumber = 1;
setScopedProfileNumber(scopeId, profileNumber);
}
res.json({ profile_number: profileNumber });
} catch (error) { } catch (error) {
res.status(500).json({ error: error.message }); res.status(500).json({ error: error.message });
} }
@@ -5469,10 +5535,9 @@ app.post('/api/posts/:postId/check', (req, res) => {
didChange = true; didChange = true;
} }
let profileValue = sanitizeProfileNumber(profile_number); let profileValue = sanitizeProfileNumber(profile_number) || getScopedProfileNumber(req.profileScope);
if (!profileValue) { if (!profileValue) {
const storedProfile = getScopedProfileNumber(req.profileScope); return res.status(400).json({ error: 'Profil muss zuerst ausgewählt werden.' });
profileValue = storedProfile || requiredProfiles[0];
} }
const completedRows = db.prepare('SELECT profile_number FROM checks WHERE post_id = ?').all(postId); const completedRows = db.prepare('SELECT profile_number FROM checks WHERE post_id = ?').all(postId);
@@ -5620,10 +5685,9 @@ app.post('/api/check-by-url', (req, res) => {
didChange = true; didChange = true;
} }
let profileValue = sanitizeProfileNumber(profile_number); let profileValue = sanitizeProfileNumber(profile_number) || getScopedProfileNumber(req.profileScope);
if (!profileValue) { if (!profileValue) {
const storedProfile = getScopedProfileNumber(req.profileScope); return res.status(400).json({ error: 'Profil muss zuerst ausgewählt werden.' });
profileValue = storedProfile || requiredProfiles[0];
} }
const completedRows = db.prepare('SELECT profile_number FROM checks WHERE post_id = ?').all(post.id); const completedRows = db.prepare('SELECT profile_number FROM checks WHERE post_id = ?').all(post.id);
@@ -6673,10 +6737,11 @@ app.post('/api/ai/generate-comment', async (req, res) => {
} }
let promptPrefix = settings.prompt_prefix || ''; let promptPrefix = settings.prompt_prefix || '';
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
// Get friend names for the profile if available // Get friend names for the profile if available
if (profileNumber) { if (normalizedProfileNumber) {
const friends = db.prepare('SELECT friend_names FROM profile_friends WHERE profile_number = ?').get(profileNumber); const friends = db.prepare('SELECT friend_names FROM profile_friends WHERE profile_number = ?').get(normalizedProfileNumber);
if (friends && friends.friend_names) { if (friends && friends.friend_names) {
promptPrefix = promptPrefix.replace('{FREUNDE}', friends.friend_names); promptPrefix = promptPrefix.replace('{FREUNDE}', friends.friend_names);
} else { } else {
@@ -6686,6 +6751,12 @@ app.post('/api/ai/generate-comment', async (req, res) => {
promptPrefix = promptPrefix.replace('{FREUNDE}', ''); promptPrefix = promptPrefix.replace('{FREUNDE}', '');
} }
const today = new Date();
const todayDisplay = today.toLocaleDateString('de-DE');
promptPrefix = promptPrefix.replaceAll('{DATUM}', todayDisplay);
promptPrefix = applyRandomNumberTemplates(promptPrefix);
promptPrefix = applyProfileVariantTemplates(promptPrefix, normalizedProfileNumber);
// Try each active credential until one succeeds // Try each active credential until one succeeds
let lastError = null; let lastError = null;
const attemptDetails = []; const attemptDetails = [];

View File

@@ -17,6 +17,7 @@ const SEARCH_RESULTS_PATH_PREFIX = '/search';
const FEED_HOME_PATHS = ['/', '/home.php']; const FEED_HOME_PATHS = ['/', '/home.php'];
const sessionSearchRecordedUrls = new Set(); const sessionSearchRecordedUrls = new Set();
const sessionSearchInfoCache = new Map(); const sessionSearchInfoCache = new Map();
let profileSelectionNoticeShown = false;
function isOnSearchResultsPage() { function isOnSearchResultsPage() {
try { try {
@@ -49,6 +50,19 @@ function maybeRedirectPageReelsToMain() {
if (typeof pathname !== 'string') { if (typeof pathname !== 'string') {
return false; return false;
} }
if (pathname.toLowerCase() === '/profile.php') {
const params = new URLSearchParams(location.search || '');
const profileId = params.get('id');
const sk = params.get('sk');
if (profileId && sk && sk.toLowerCase().startsWith('reel')) {
const targetUrl = `${location.origin}/profile.php?id=${encodeURIComponent(profileId)}`;
if (location.href !== targetUrl) {
location.replace(targetUrl);
return true;
}
return false;
}
}
const match = pathname.match(/^\/([^/]+)\/reels\/?$/i); const match = pathname.match(/^\/([^/]+)\/reels\/?$/i);
if (!match) { if (!match) {
return false; return false;
@@ -513,6 +527,10 @@ async function getProfileNumber() {
console.warn('[FB Tracker] Failed to resolve profile number from backend:', error); console.warn('[FB Tracker] Failed to resolve profile number from backend:', error);
} }
if (!profileSelectionNoticeShown) {
profileSelectionNoticeShown = true;
showToast('Bitte zuerst ein Profil im Tracker auswählen.', 'error');
}
return null; return null;
} }

View File

@@ -112,6 +112,7 @@
<div class="section"> <div class="section">
<label for="profileSelect">Aktuelles Profil:</label> <label for="profileSelect">Aktuelles Profil:</label>
<select id="profileSelect"> <select id="profileSelect">
<option value="">-- Profil wählen --</option>
<option value="1">Profil 1</option> <option value="1">Profil 1</option>
<option value="2">Profil 2</option> <option value="2">Profil 2</option>
<option value="3">Profil 3</option> <option value="3">Profil 3</option>

View File

@@ -2,6 +2,10 @@ const profileSelect = document.getElementById('profileSelect');
const statusEl = document.getElementById('status'); const statusEl = document.getElementById('status');
const debugToggle = document.getElementById('debugLoggingToggle'); const debugToggle = document.getElementById('debugLoggingToggle');
function isValidProfileNumber(value) {
return Number.isInteger(value) && value >= 1 && value <= 5;
}
function apiFetch(url, options = {}) { function apiFetch(url, options = {}) {
const config = { const config = {
...options, ...options,
@@ -61,7 +65,7 @@ function updateStatus(message, saved = false) {
async function initProfileSelect() { async function initProfileSelect() {
const backendProfile = await fetchProfileState(); const backendProfile = await fetchProfileState();
if (backendProfile) { if (isValidProfileNumber(backendProfile)) {
profileSelect.value = String(backendProfile); profileSelect.value = String(backendProfile);
chrome.storage.sync.set({ profileNumber: backendProfile }); chrome.storage.sync.set({ profileNumber: backendProfile });
updateStatus(`Profil ${backendProfile} ausgewählt`); updateStatus(`Profil ${backendProfile} ausgewählt`);
@@ -69,9 +73,14 @@ async function initProfileSelect() {
} }
chrome.storage.sync.get(['profileNumber'], (result) => { chrome.storage.sync.get(['profileNumber'], (result) => {
const profileNumber = result.profileNumber || 1; const profileNumber = result.profileNumber;
profileSelect.value = String(profileNumber); if (isValidProfileNumber(profileNumber)) {
updateStatus(`Profil ${profileNumber} ausgewählt (lokal)`); profileSelect.value = String(profileNumber);
updateStatus(`Profil ${profileNumber} ausgewählt (lokal)`);
return;
}
profileSelect.value = '';
updateStatus('Bitte zuerst ein Profil auswählen.');
}); });
} }
@@ -101,6 +110,10 @@ function reloadFacebookTabs() {
document.getElementById('saveBtn').addEventListener('click', async () => { document.getElementById('saveBtn').addEventListener('click', async () => {
const profileNumber = parseInt(profileSelect.value, 10); const profileNumber = parseInt(profileSelect.value, 10);
if (!isValidProfileNumber(profileNumber)) {
updateStatus('Bitte zuerst ein Profil auswählen.');
return;
}
chrome.storage.sync.set({ profileNumber }, async () => { chrome.storage.sync.set({ profileNumber }, async () => {
updateStatus(`Profil ${profileNumber} gespeichert!`, true); updateStatus(`Profil ${profileNumber} gespeichert!`, true);

View File

@@ -16,7 +16,7 @@ let focusHandled = false;
let initialTabOverride = null; let initialTabOverride = null;
let focusTabAdjusted = null; let focusTabAdjusted = null;
let currentProfile = 1; let currentProfile = null;
let currentTab = 'pending'; let currentTab = 'pending';
let posts = []; let posts = [];
let includeExpiredPosts = false; let includeExpiredPosts = false;
@@ -39,6 +39,10 @@ const PROFILE_NAMES = {
5: 'Profil 5' 5: 'Profil 5'
}; };
function isValidProfileNumber(value) {
return Number.isInteger(value) && value >= 1 && value <= MAX_PROFILES;
}
function redirectToLogin() { function redirectToLogin() {
try { try {
const redirect = encodeURIComponent(window.location.href); const redirect = encodeURIComponent(window.location.href);
@@ -408,12 +412,18 @@ function persistIncludeExpiredPreference(value) {
} }
function getPendingOpenCooldownStorageKey(profileNumber = currentProfile) { function getPendingOpenCooldownStorageKey(profileNumber = currentProfile) {
const safeProfile = profileNumber || currentProfile || 1; const safeProfile = isValidProfileNumber(profileNumber) ? profileNumber : null;
if (!safeProfile) {
return null;
}
return `${PENDING_OPEN_COOLDOWN_STORAGE_KEY}:${safeProfile}`; return `${PENDING_OPEN_COOLDOWN_STORAGE_KEY}:${safeProfile}`;
} }
function loadPendingOpenCooldownMap(profileNumber = currentProfile) { function loadPendingOpenCooldownMap(profileNumber = currentProfile) {
const storageKey = getPendingOpenCooldownStorageKey(profileNumber); const storageKey = getPendingOpenCooldownStorageKey(profileNumber);
if (!storageKey) {
return {};
}
try { try {
const raw = localStorage.getItem(storageKey); const raw = localStorage.getItem(storageKey);
if (raw) { if (raw) {
@@ -441,6 +451,9 @@ function loadPendingOpenCooldownMap(profileNumber = currentProfile) {
function persistPendingOpenCooldownMap(profileNumber, map) { function persistPendingOpenCooldownMap(profileNumber, map) {
const storageKey = getPendingOpenCooldownStorageKey(profileNumber); const storageKey = getPendingOpenCooldownStorageKey(profileNumber);
if (!storageKey) {
return;
}
try { try {
localStorage.setItem(storageKey, JSON.stringify(map || {})); localStorage.setItem(storageKey, JSON.stringify(map || {}));
} catch (error) { } catch (error) {
@@ -3209,6 +3222,10 @@ function applyAutoRefreshSettings() {
autoRefreshTimer = null; autoRefreshTimer = null;
} }
if (!isValidProfileNumber(currentProfile)) {
return;
}
if (autoRefreshIntervalSelect) { if (autoRefreshIntervalSelect) {
const disabled = !autoRefreshSettings.enabled || updatesStreamHealthy; const disabled = !autoRefreshSettings.enabled || updatesStreamHealthy;
autoRefreshIntervalSelect.disabled = disabled; autoRefreshIntervalSelect.disabled = disabled;
@@ -3229,6 +3246,9 @@ function applyAutoRefreshSettings() {
if (document.hidden) { if (document.hidden) {
return; return;
} }
if (!isValidProfileNumber(currentProfile)) {
return;
}
fetchPosts({ showLoader: false }); fetchPosts({ showLoader: false });
}, autoRefreshSettings.interval); }, autoRefreshSettings.interval);
} }
@@ -3506,8 +3526,17 @@ async function pushProfileState(profileNumber) {
} }
} }
function ensureProfileSelected() {
if (isValidProfileNumber(currentProfile)) {
hideError();
return true;
}
showError('Bitte zuerst ein Profil auswählen.');
return false;
}
function applyProfileNumber(profileNumber, { fromBackend = false } = {}) { function applyProfileNumber(profileNumber, { fromBackend = false } = {}) {
if (!profileNumber) { if (!isValidProfileNumber(profileNumber)) {
return; return;
} }
@@ -3524,6 +3553,7 @@ function applyProfileNumber(profileNumber, { fromBackend = false } = {}) {
currentProfile = profileNumber; currentProfile = profileNumber;
localStorage.setItem('profileNumber', currentProfile); localStorage.setItem('profileNumber', currentProfile);
hideError();
if (!fromBackend) { if (!fromBackend) {
pushProfileState(currentProfile); pushProfileState(currentProfile);
@@ -3533,20 +3563,32 @@ function applyProfileNumber(profileNumber, { fromBackend = false } = {}) {
pendingOpenCooldownMap = loadPendingOpenCooldownMap(currentProfile); pendingOpenCooldownMap = loadPendingOpenCooldownMap(currentProfile);
renderPosts(); renderPosts();
maybeAutoOpenPending('profile'); maybeAutoOpenPending('profile');
if (!posts.length && !isFetchingPosts) {
fetchPosts({ showLoader: false });
}
checkAutoCheck();
applyAutoRefreshSettings();
} }
// Load profile from localStorage // Load profile from localStorage
function loadProfile() { function loadProfile() {
fetchProfileState().then((backendProfile) => { fetchProfileState().then((backendProfile) => {
if (backendProfile) { if (isValidProfileNumber(backendProfile)) {
applyProfileNumber(backendProfile, { fromBackend: true }); applyProfileNumber(backendProfile, { fromBackend: true });
} else { } else {
const saved = localStorage.getItem('profileNumber'); const saved = localStorage.getItem('profileNumber');
if (saved) { const parsed = saved ? parseInt(saved, 10) : NaN;
applyProfileNumber(parseInt(saved, 10) || 1, { fromBackend: true }); if (isValidProfileNumber(parsed)) {
} else { applyProfileNumber(parsed, { fromBackend: true });
applyProfileNumber(1, { fromBackend: true }); return;
} }
if (profileSelectElement) {
profileSelectElement.value = '';
}
currentProfile = null;
showError('Bitte zuerst ein Profil auswählen.');
} }
}); });
} }
@@ -3572,7 +3614,12 @@ function startProfilePolling() {
// Profile selector change handler // Profile selector change handler
if (profileSelectElement) { if (profileSelectElement) {
profileSelectElement.addEventListener('change', (e) => { profileSelectElement.addEventListener('change', (e) => {
saveProfile(parseInt(e.target.value, 10)); const parsed = parseInt(e.target.value, 10);
if (!isValidProfileNumber(parsed)) {
showError('Bitte zuerst ein Profil auswählen.');
return;
}
saveProfile(parsed);
}); });
} }
@@ -3762,6 +3809,10 @@ async function fetchPosts({ showLoader = true } = {}) {
return; return;
} }
if (!ensureProfileSelected()) {
return;
}
isFetchingPosts = true; isFetchingPosts = true;
cancelPendingAutoOpen(false); cancelPendingAutoOpen(false);
@@ -4038,6 +4089,13 @@ async function mergeSelectedPosts() {
// Render posts // Render posts
function renderPosts() { function renderPosts() {
hideLoading(); hideLoading();
if (!ensureProfileSelected()) {
const container = document.getElementById('postsContainer');
if (container) {
container.innerHTML = '';
}
return;
}
hideError(); hideError();
const container = document.getElementById('postsContainer'); const container = document.getElementById('postsContainer');
@@ -4596,6 +4654,10 @@ function createPostCard(post, status, meta = {}) {
// Open post and auto-check // Open post and auto-check
async function openPost(postId) { async function openPost(postId) {
if (!ensureProfileSelected()) {
return;
}
const post = posts.find((item) => item.id === postId); const post = posts.find((item) => item.id === postId);
if (!post) { if (!post) {
alert('Beitrag konnte nicht gefunden werden.'); alert('Beitrag konnte nicht gefunden werden.');
@@ -4619,20 +4681,8 @@ async function openPost(postId) {
return; return;
} }
if (!status.canCurrentProfileCheck) {
if (status.waitingForNames.length) {
const proceed = confirm(`Vorherige Profile müssen zuerst bestätigen (${status.waitingForNames.join(', ')}). Trotzdem bestätigen?`);
if (!proceed) {
return;
}
} else {
alert('Der Beitrag kann aktuell nicht abgehakt werden.');
return;
}
}
try { try {
const ignoreOrder = !status.canCurrentProfileCheck && status.waitingForNames.length > 0; const ignoreOrder = true;
const response = await apiFetch(`${API_URL}/check-by-url`, { const response = await apiFetch(`${API_URL}/check-by-url`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -4686,6 +4736,9 @@ async function toggleSuccessStatus(postId, isSuccessful) {
} }
async function toggleProfileStatus(postId, profileNumber, currentStatus) { async function toggleProfileStatus(postId, profileNumber, currentStatus) {
if (!ensureProfileSelected()) {
return;
}
if (!profileNumber) { if (!profileNumber) {
return; return;
} }
@@ -4698,7 +4751,8 @@ async function toggleProfileStatus(postId, profileNumber, currentStatus) {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
profile_number: profileNumber, profile_number: profileNumber,
status: desiredStatus status: desiredStatus,
ignore_order: true
}) })
}); });
@@ -5058,13 +5112,17 @@ function checkAutoCheck() {
const autoCheckUrl = urlParams.get('check'); const autoCheckUrl = urlParams.get('check');
if (autoCheckUrl) { if (autoCheckUrl) {
if (!ensureProfileSelected()) {
return;
}
// Try to check this URL automatically // Try to check this URL automatically
apiFetch(`${API_URL}/check-by-url`, { apiFetch(`${API_URL}/check-by-url`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
url: decodeURIComponent(autoCheckUrl), url: decodeURIComponent(autoCheckUrl),
profile_number: currentProfile profile_number: currentProfile,
ignore_order: true
}) })
}).then(() => { }).then(() => {
// Remove nur den check-Parameter aus der URL // Remove nur den check-Parameter aus der URL

View File

@@ -130,6 +130,7 @@
<div class="control-group"> <div class="control-group">
<label for="profileSelect">Dein Profil:</label> <label for="profileSelect">Dein Profil:</label>
<select id="profileSelect" class="control-select"> <select id="profileSelect" class="control-select">
<option value="">-- Profil wählen --</option>
<option value="1">Profil 1</option> <option value="1">Profil 1</option>
<option value="2">Profil 2</option> <option value="2">Profil 2</option>
<option value="3">Profil 3</option> <option value="3">Profil 3</option>
@@ -1089,7 +1090,7 @@
<textarea id="aiPromptPrefix" class="form-textarea" rows="4" <textarea id="aiPromptPrefix" class="form-textarea" rows="4"
placeholder="Anweisungen für die KI vor dem Post-Text..."></textarea> placeholder="Anweisungen für die KI vor dem Post-Text..."></textarea>
<p class="form-help"> <p class="form-help">
Dieser Text wird vor dem eigentlichen Post-Text an die KI gesendet. Verwende <code>{FREUNDE}</code> als Platzhalter für Freundesnamen. Dieser Text wird vor dem eigentlichen Post-Text an die KI gesendet. Platzhalter: <code>{FREUNDE}</code> (Freundesnamen), <code>{DATUM}</code> (heutiges Datum), <code>{Profil-1?"Text1":"Text2"}</code> bzw. <code>{Profil-1?"Text1"}</code> für profilabhängige Varianten (auch mit <code>Profile</code>) und <code>{ZUFALL-1-5}</code> für eine Zufallszahl im Bereich.
</p> </p>
</div> </div>