Compare commits
2 Commits
585e5d5455
...
2feba4e585
| Author | SHA1 | Date | |
|---|---|---|---|
| 2feba4e585 | |||
| bbfa93a586 |
@@ -28,6 +28,9 @@ const MIN_TEXT_HASH_LENGTH = 120;
|
|||||||
const MIN_SIMILAR_TEXT_LENGTH = 60;
|
const MIN_SIMILAR_TEXT_LENGTH = 60;
|
||||||
const MAX_BOOKMARK_LABEL_LENGTH = 120;
|
const MAX_BOOKMARK_LABEL_LENGTH = 120;
|
||||||
const MAX_BOOKMARK_QUERY_LENGTH = 200;
|
const MAX_BOOKMARK_QUERY_LENGTH = 200;
|
||||||
|
const DEFAULT_BOOKMARK_ID = 'default-search';
|
||||||
|
const DEFAULT_BOOKMARK_LABEL = 'Gewinnspiel / gewinnen / verlosen';
|
||||||
|
const DEFAULT_BOOKMARK_QUERY = '';
|
||||||
const DAILY_BOOKMARK_TITLE_MAX_LENGTH = 160;
|
const DAILY_BOOKMARK_TITLE_MAX_LENGTH = 160;
|
||||||
const DAILY_BOOKMARK_URL_MAX_LENGTH = 800;
|
const DAILY_BOOKMARK_URL_MAX_LENGTH = 800;
|
||||||
const DAILY_BOOKMARK_NOTES_MAX_LENGTH = 800;
|
const DAILY_BOOKMARK_NOTES_MAX_LENGTH = 800;
|
||||||
@@ -687,6 +690,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;
|
||||||
@@ -846,18 +920,26 @@ function normalizeBookmarkLabel(value, fallback = '') {
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDefaultBookmarkQuery(value) {
|
||||||
|
return typeof value === 'string' && !value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
function serializeBookmark(row) {
|
function serializeBookmark(row) {
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDefault = isDefaultBookmarkQuery(row.query);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
label: row.label,
|
label: row.label,
|
||||||
query: row.query,
|
query: row.query,
|
||||||
created_at: sqliteTimestampToUTC(row.created_at),
|
created_at: sqliteTimestampToUTC(row.created_at),
|
||||||
updated_at: sqliteTimestampToUTC(row.updated_at),
|
updated_at: sqliteTimestampToUTC(row.updated_at),
|
||||||
last_clicked_at: sqliteTimestampToUTC(row.last_clicked_at)
|
last_clicked_at: sqliteTimestampToUTC(row.last_clicked_at),
|
||||||
|
is_default: isDefault,
|
||||||
|
deletable: !isDefault
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1586,6 +1668,11 @@ const insertBookmarkStmt = db.prepare(`
|
|||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const ensureDefaultBookmarkStmt = db.prepare(`
|
||||||
|
INSERT OR IGNORE INTO bookmarks (id, label, query)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
`);
|
||||||
|
|
||||||
const deleteBookmarkStmt = db.prepare(`
|
const deleteBookmarkStmt = db.prepare(`
|
||||||
DELETE FROM bookmarks
|
DELETE FROM bookmarks
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
@@ -1598,6 +1685,8 @@ const updateBookmarkLastClickedStmt = db.prepare(`
|
|||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
ensureDefaultBookmarkStmt.run(DEFAULT_BOOKMARK_ID, DEFAULT_BOOKMARK_LABEL, DEFAULT_BOOKMARK_QUERY);
|
||||||
|
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS daily_bookmarks (
|
CREATE TABLE IF NOT EXISTS daily_bookmarks (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
@@ -4484,6 +4573,14 @@ app.delete('/api/bookmarks/:bookmarkId', (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const existing = getBookmarkByIdStmt.get(bookmarkId);
|
||||||
|
if (!existing) {
|
||||||
|
return res.status(404).json({ error: 'Bookmark nicht gefunden' });
|
||||||
|
}
|
||||||
|
if (isDefaultBookmarkQuery(existing.query)) {
|
||||||
|
return res.status(403).json({ error: 'Standard-Bookmark kann nicht gelöscht werden' });
|
||||||
|
}
|
||||||
|
|
||||||
const result = deleteBookmarkStmt.run(bookmarkId);
|
const result = deleteBookmarkStmt.run(bookmarkId);
|
||||||
if (!result.changes) {
|
if (!result.changes) {
|
||||||
return res.status(404).json({ error: 'Bookmark nicht gefunden' });
|
return res.status(404).json({ error: 'Bookmark nicht gefunden' });
|
||||||
@@ -5059,13 +5156,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 +5561,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 +5711,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 +6763,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 +6777,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 = [];
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
174
web/app.js
174
web/app.js
@@ -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);
|
||||||
@@ -375,6 +379,7 @@ const DEFAULT_SORT_SETTINGS = { mode: 'created', direction: 'desc' };
|
|||||||
const BOOKMARKS_BASE_URL = 'https://www.facebook.com/search/top';
|
const BOOKMARKS_BASE_URL = 'https://www.facebook.com/search/top';
|
||||||
const BOOKMARK_WINDOW_DAYS = 28;
|
const BOOKMARK_WINDOW_DAYS = 28;
|
||||||
const BOOKMARK_SUFFIXES = ['Gewinnspiel', 'gewinnen', 'verlosen'];
|
const BOOKMARK_SUFFIXES = ['Gewinnspiel', 'gewinnen', 'verlosen'];
|
||||||
|
const DEFAULT_BOOKMARK_LABEL = 'Gewinnspiel / gewinnen / verlosen';
|
||||||
const BOOKMARK_PREFS_KEY = 'trackerBookmarkPreferences';
|
const BOOKMARK_PREFS_KEY = 'trackerBookmarkPreferences';
|
||||||
const INCLUDE_EXPIRED_STORAGE_KEY = 'trackerIncludeExpired';
|
const INCLUDE_EXPIRED_STORAGE_KEY = 'trackerIncludeExpired';
|
||||||
const PENDING_BULK_COUNT_STORAGE_KEY = 'trackerPendingBulkCount';
|
const PENDING_BULK_COUNT_STORAGE_KEY = 'trackerPendingBulkCount';
|
||||||
@@ -408,12 +413,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 +452,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) {
|
||||||
@@ -901,11 +915,14 @@ function normalizeServerBookmark(entry) {
|
|||||||
|
|
||||||
const id = typeof entry.id === 'string' ? entry.id : null;
|
const id = typeof entry.id === 'string' ? entry.id : null;
|
||||||
const query = typeof entry.query === 'string' ? entry.query.trim() : '';
|
const query = typeof entry.query === 'string' ? entry.query.trim() : '';
|
||||||
if (!id || !query) {
|
const isDefault = entry.is_default === true || !query;
|
||||||
|
if (!id || (!isDefault && !query)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = typeof entry.label === 'string' && entry.label.trim() ? entry.label.trim() : query;
|
const label = typeof entry.label === 'string' && entry.label.trim()
|
||||||
|
? entry.label.trim()
|
||||||
|
: (isDefault ? DEFAULT_BOOKMARK_LABEL : query);
|
||||||
|
|
||||||
const normalizeDate = (value) => {
|
const normalizeDate = (value) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -925,7 +942,8 @@ function normalizeServerBookmark(entry) {
|
|||||||
created_at: normalizeDate(entry.created_at),
|
created_at: normalizeDate(entry.created_at),
|
||||||
updated_at: normalizeDate(entry.updated_at),
|
updated_at: normalizeDate(entry.updated_at),
|
||||||
last_clicked_at: normalizeDate(entry.last_clicked_at),
|
last_clicked_at: normalizeDate(entry.last_clicked_at),
|
||||||
deletable: true
|
deletable: entry.deletable === false ? false : !isDefault,
|
||||||
|
isDefault
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,10 +952,11 @@ function deduplicateBookmarks(list) {
|
|||||||
const deduped = [];
|
const deduped = [];
|
||||||
|
|
||||||
list.forEach((bookmark) => {
|
list.forEach((bookmark) => {
|
||||||
if (!bookmark || !bookmark.query) {
|
if (!bookmark || typeof bookmark.id !== 'string') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const key = bookmark.query.toLowerCase();
|
const query = typeof bookmark.query === 'string' ? bookmark.query : '';
|
||||||
|
const key = query.toLowerCase();
|
||||||
if (seen.has(key)) {
|
if (seen.has(key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1037,25 +1056,13 @@ function updateBookmarkSortDirectionUI() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_BOOKMARK_LAST_CLICK_KEY = 'trackerDefaultBookmarkLastClickedAt';
|
|
||||||
|
|
||||||
const bookmarkState = {
|
const bookmarkState = {
|
||||||
items: [],
|
items: [],
|
||||||
loaded: false,
|
loaded: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null
|
||||||
defaultLastClickedAt: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
const storedDefaultBookmark = localStorage.getItem(DEFAULT_BOOKMARK_LAST_CLICK_KEY);
|
|
||||||
if (storedDefaultBookmark) {
|
|
||||||
bookmarkState.defaultLastClickedAt = storedDefaultBookmark;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Konnte letzte Nutzung des Standard-Bookmarks nicht laden:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
let bookmarkFetchPromise = null;
|
let bookmarkFetchPromise = null;
|
||||||
|
|
||||||
function formatRelativeTimeFromNow(timestamp) {
|
function formatRelativeTimeFromNow(timestamp) {
|
||||||
@@ -1104,10 +1111,11 @@ function upsertBookmarkInState(bookmark) {
|
|||||||
|
|
||||||
const lowerQuery = normalized.query.toLowerCase();
|
const lowerQuery = normalized.query.toLowerCase();
|
||||||
const existingIndex = bookmarkState.items.findIndex((item) => {
|
const existingIndex = bookmarkState.items.findIndex((item) => {
|
||||||
if (!item || !item.query) {
|
if (!item || typeof item.id !== 'string') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return item.id === normalized.id || item.query.toLowerCase() === lowerQuery;
|
const itemQuery = typeof item.query === 'string' ? item.query.toLowerCase() : '';
|
||||||
|
return item.id === normalized.id || itemQuery === lowerQuery;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
@@ -1406,26 +1414,18 @@ function openBookmark(bookmark) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stateBookmark = bookmarkState.items.find((item) => item.id === bookmark.id) || bookmark;
|
const stateBookmark = bookmarkState.items.find((item) => item.id === bookmark.id) || bookmark;
|
||||||
const isDefaultBookmark = stateBookmark && stateBookmark.isDefault;
|
|
||||||
|
|
||||||
const nowIso = new Date().toISOString();
|
const nowIso = new Date().toISOString();
|
||||||
|
|
||||||
if (isDefaultBookmark) {
|
if (stateBookmark && stateBookmark.id) {
|
||||||
bookmarkState.defaultLastClickedAt = nowIso;
|
|
||||||
try {
|
|
||||||
localStorage.setItem(DEFAULT_BOOKMARK_LAST_CLICK_KEY, nowIso);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Konnte Standard-Bookmark-Zeit nicht speichern:', error);
|
|
||||||
}
|
|
||||||
renderBookmarks();
|
|
||||||
} else if (stateBookmark && stateBookmark.id && stateBookmark.deletable !== false) {
|
|
||||||
upsertBookmarkInState({
|
upsertBookmarkInState({
|
||||||
id: stateBookmark.id,
|
id: stateBookmark.id,
|
||||||
label: stateBookmark.label,
|
label: stateBookmark.label,
|
||||||
query: stateBookmark.query,
|
query: stateBookmark.query,
|
||||||
last_clicked_at: nowIso,
|
last_clicked_at: nowIso,
|
||||||
created_at: stateBookmark.created_at || nowIso,
|
created_at: stateBookmark.created_at || nowIso,
|
||||||
updated_at: nowIso
|
updated_at: nowIso,
|
||||||
|
deletable: stateBookmark.deletable !== false,
|
||||||
|
is_default: stateBookmark.isDefault === true
|
||||||
});
|
});
|
||||||
renderBookmarks();
|
renderBookmarks();
|
||||||
markBookmarkClick(stateBookmark.id);
|
markBookmarkClick(stateBookmark.id);
|
||||||
@@ -1575,20 +1575,13 @@ function renderBookmarks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dynamicBookmarks = bookmarkState.items;
|
const dynamicBookmarks = bookmarkState.items;
|
||||||
|
|
||||||
const staticDefault = {
|
|
||||||
id: 'default-search',
|
|
||||||
label: 'Gewinnspiel / gewinnen / verlosen',
|
|
||||||
query: '',
|
|
||||||
last_clicked_at: bookmarkState.defaultLastClickedAt || null,
|
|
||||||
deletable: false,
|
|
||||||
isDefault: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredBookmarks = filterBookmarksBySearch(dynamicBookmarks);
|
const filteredBookmarks = filterBookmarksBySearch(dynamicBookmarks);
|
||||||
const sortedForAll = sortBookmarksForDisplay(filteredBookmarks);
|
const sortedForAll = sortBookmarksForDisplay(filteredBookmarks);
|
||||||
|
const defaultBookmark = sortedForAll.find((bookmark) => bookmark.isDefault);
|
||||||
const displayList = bookmarkSearchTerm ? sortedForAll : [staticDefault, ...sortedForAll];
|
const nonDefaultBookmarks = sortedForAll.filter((bookmark) => !bookmark.isDefault);
|
||||||
|
const displayList = bookmarkSearchTerm
|
||||||
|
? sortedForAll
|
||||||
|
: (defaultBookmark ? [defaultBookmark, ...nonDefaultBookmarks] : nonDefaultBookmarks);
|
||||||
const titleText = bookmarkSearchTerm
|
const titleText = bookmarkSearchTerm
|
||||||
? (filteredBookmarks.length ? `Suchergebnisse (${filteredBookmarks.length})` : 'Keine Treffer')
|
? (filteredBookmarks.length ? `Suchergebnisse (${filteredBookmarks.length})` : 'Keine Treffer')
|
||||||
: 'Alle Bookmarks';
|
: 'Alle Bookmarks';
|
||||||
@@ -3209,6 +3202,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 +3226,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 +3506,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 +3533,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 +3543,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 +3594,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 +3789,10 @@ async function fetchPosts({ showLoader = true } = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ensureProfileSelected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isFetchingPosts = true;
|
isFetchingPosts = true;
|
||||||
cancelPendingAutoOpen(false);
|
cancelPendingAutoOpen(false);
|
||||||
|
|
||||||
@@ -4038,6 +4069,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 +4634,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 +4661,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 +4716,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 +4731,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 +5092,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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user