minor changes
This commit is contained in:
@@ -26,6 +26,50 @@ const MAX_POST_TEXT_LENGTH = 4000;
|
||||
const MIN_TEXT_HASH_LENGTH = 120;
|
||||
const MAX_BOOKMARK_LABEL_LENGTH = 120;
|
||||
const MAX_BOOKMARK_QUERY_LENGTH = 200;
|
||||
const SPORTS_SCORING_DEFAULTS = {
|
||||
enabled: 1,
|
||||
threshold: 5,
|
||||
auto_hide_enabled: 0,
|
||||
weights: {
|
||||
scoreline: 3,
|
||||
scoreEmoji: 2,
|
||||
sportEmoji: 2,
|
||||
sportVerb: 1.5,
|
||||
sportNoun: 2,
|
||||
hashtag: 1.5,
|
||||
teamToken: 2,
|
||||
competition: 2,
|
||||
celebration: 1,
|
||||
location: 1
|
||||
}
|
||||
};
|
||||
const SPORTS_SCORING_TERMS_DEFAULTS = {
|
||||
nouns: [
|
||||
'auswärtssieg', 'heimsieg', 'derbysieg', 'revanche', 'spiel', 'spieltag', 'match', 'derby', 'finale',
|
||||
'cup', 'pokal', 'liga', 'bundesliga', 'oberliga', 'kreisliga', 'bezirksliga', 'meisterschaft',
|
||||
'turnier', 'halbzeit', 'tabellenplatz', 'tabelle', 'tor', 'tore', 'treffer', 'stadion', 'arena', 'halle',
|
||||
'trainerteam', 'mannschaft', 'fans', 'fanblock', 'jugend', 'u17', 'u19', 'u15'
|
||||
],
|
||||
verbs: [
|
||||
'gewinnen', 'siegen', 'geholt', 'erkämpfen', 'erkämpft', 'erkämpfen', 'drehen', 'punkten',
|
||||
'trifft', 'treffen', 'schießt', 'schiesst', 'schießen', 'schiessen', 'verteidigen', 'stürmen', 'kämpfen'
|
||||
],
|
||||
competitions: [
|
||||
'bundesliga', 'liga', 'serie a', 'premier league', 'champions league', 'europa league', 'dfb-pokal',
|
||||
'cup', 'pokal', 'qualifikation', 'qualirunde', 'halbfinale', 'viertelfinale', 'achtelfinale', 'relegation'
|
||||
],
|
||||
celebrations: [
|
||||
'sieg', 'siege', 'auswärtssieg', 'heimsieg', 'auswärtsspiel', 'punkte', 'punkte geholt', 'man of the match', 'motm',
|
||||
'tabellenführung', 'tabellenplatz', 'tabellendritter', 'tabellenzweiter'
|
||||
],
|
||||
locations: [
|
||||
'auswärts', 'heimspiel', 'derby', 'arena', 'stadion', 'halle', 'bolle', 'bölle', 'mosel'
|
||||
],
|
||||
negatives: [
|
||||
'rezept', 'kochen', 'politik', 'wahl', 'bundestag', 'landtag', 'software', 'release', 'update', 'konzert',
|
||||
'album', 'tour', 'podcast', 'karriere', 'job', 'stellenangebot', 'bewerbung'
|
||||
]
|
||||
};
|
||||
|
||||
const screenshotDir = path.join(__dirname, 'data', 'screenshots');
|
||||
if (!fs.existsSync(screenshotDir)) {
|
||||
@@ -803,6 +847,21 @@ db.exec(`
|
||||
);
|
||||
`);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS moderation_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
sports_scoring_enabled INTEGER DEFAULT ${SPORTS_SCORING_DEFAULTS.enabled},
|
||||
sports_score_threshold REAL DEFAULT ${SPORTS_SCORING_DEFAULTS.threshold},
|
||||
sports_auto_hide_enabled INTEGER DEFAULT ${SPORTS_SCORING_DEFAULTS.auto_hide_enabled},
|
||||
sports_score_weights TEXT,
|
||||
sports_terms TEXT,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
ensureColumn('moderation_settings', 'sports_terms', 'sports_terms TEXT');
|
||||
ensureColumn('moderation_settings', 'sports_auto_hide_enabled', 'sports_auto_hide_enabled INTEGER DEFAULT 0');
|
||||
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_search_seen_posts_last_seen_at
|
||||
ON search_seen_posts(last_seen_at);
|
||||
@@ -894,6 +953,7 @@ ensureColumn('ai_credentials', 'auto_disabled_until', 'auto_disabled_until DATET
|
||||
ensureColumn('ai_credentials', 'usage_24h_count', 'usage_24h_count INTEGER DEFAULT 0');
|
||||
ensureColumn('ai_credentials', 'usage_24h_reset_at', 'usage_24h_reset_at DATETIME');
|
||||
ensureColumn('search_seen_posts', 'manually_hidden', 'manually_hidden INTEGER NOT NULL DEFAULT 0');
|
||||
ensureColumn('search_seen_posts', 'sports_auto_hidden', 'sports_auto_hidden INTEGER NOT NULL DEFAULT 0');
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS ai_usage_events (
|
||||
@@ -1620,6 +1680,152 @@ function cleanupExpiredSearchPosts() {
|
||||
}
|
||||
}
|
||||
|
||||
function safeParseSportsWeights(raw) {
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
||||
return raw;
|
||||
}
|
||||
if (typeof raw !== 'string') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeSportsScoreThreshold(value) {
|
||||
const parsed = parseFloat(value);
|
||||
if (Number.isNaN(parsed) || parsed < 0) {
|
||||
return SPORTS_SCORING_DEFAULTS.threshold;
|
||||
}
|
||||
return Math.min(50, Math.max(0, parsed));
|
||||
}
|
||||
|
||||
function normalizeSportsWeights(weights) {
|
||||
const defaults = SPORTS_SCORING_DEFAULTS.weights;
|
||||
const normalized = {};
|
||||
const source = (weights && typeof weights === 'object' && !Array.isArray(weights))
|
||||
? weights
|
||||
: {};
|
||||
|
||||
for (const key of Object.keys(defaults)) {
|
||||
const raw = source[key];
|
||||
const parsed = typeof raw === 'number' ? raw : parseFloat(raw);
|
||||
const value = Number.isFinite(parsed) ? parsed : defaults[key];
|
||||
normalized[key] = Math.max(0, Math.min(10, value));
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizeSportsTerms(terms) {
|
||||
const defaults = SPORTS_SCORING_TERMS_DEFAULTS;
|
||||
const result = {};
|
||||
const source = (terms && typeof terms === 'object' && !Array.isArray(terms))
|
||||
? terms
|
||||
: {};
|
||||
|
||||
const normalizeList = (list, fallback) => {
|
||||
const arr = Array.isArray(list) ? list : [];
|
||||
const cleaned = arr
|
||||
.map((entry) => {
|
||||
if (typeof entry !== 'string') return '';
|
||||
return entry.trim().toLowerCase();
|
||||
})
|
||||
.filter((entry) => entry && entry.length <= 60);
|
||||
const unique = Array.from(new Set(cleaned)).slice(0, 200);
|
||||
if (unique.length) {
|
||||
return unique;
|
||||
}
|
||||
return fallback.slice();
|
||||
};
|
||||
|
||||
for (const key of Object.keys(defaults)) {
|
||||
result[key] = normalizeList(source[key], defaults[key]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function loadModerationSettings() {
|
||||
let settings = db.prepare('SELECT * FROM moderation_settings WHERE id = 1').get();
|
||||
|
||||
if (!settings) {
|
||||
const serializedWeights = JSON.stringify(SPORTS_SCORING_DEFAULTS.weights);
|
||||
const serializedTerms = JSON.stringify(SPORTS_SCORING_TERMS_DEFAULTS);
|
||||
db.prepare(`
|
||||
INSERT INTO moderation_settings (id, sports_scoring_enabled, sports_score_threshold, sports_auto_hide_enabled, sports_score_weights, sports_terms, updated_at)
|
||||
VALUES (1, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
`).run(SPORTS_SCORING_DEFAULTS.enabled, SPORTS_SCORING_DEFAULTS.threshold, SPORTS_SCORING_DEFAULTS.auto_hide_enabled, serializedWeights, serializedTerms);
|
||||
settings = {
|
||||
id: 1,
|
||||
sports_scoring_enabled: SPORTS_SCORING_DEFAULTS.enabled,
|
||||
sports_score_threshold: SPORTS_SCORING_DEFAULTS.threshold,
|
||||
sports_auto_hide_enabled: SPORTS_SCORING_DEFAULTS.auto_hide_enabled,
|
||||
sports_score_weights: serializedWeights,
|
||||
sports_terms: serializedTerms
|
||||
};
|
||||
}
|
||||
|
||||
const weights = normalizeSportsWeights(safeParseSportsWeights(settings.sports_score_weights));
|
||||
const threshold = normalizeSportsScoreThreshold(settings.sports_score_threshold);
|
||||
let terms = SPORTS_SCORING_TERMS_DEFAULTS;
|
||||
try {
|
||||
const parsedTerms = settings.sports_terms ? JSON.parse(settings.sports_terms) : null;
|
||||
terms = normalizeSportsTerms(parsedTerms);
|
||||
} catch (error) {
|
||||
terms = SPORTS_SCORING_TERMS_DEFAULTS;
|
||||
}
|
||||
|
||||
return {
|
||||
sports_scoring_enabled: !!settings.sports_scoring_enabled,
|
||||
sports_score_threshold: threshold,
|
||||
sports_auto_hide_enabled: !!settings.sports_auto_hide_enabled,
|
||||
sports_score_weights: weights,
|
||||
sports_terms: terms
|
||||
};
|
||||
}
|
||||
|
||||
function persistModerationSettings({ enabled, threshold, weights, terms, autoHide }) {
|
||||
const normalizedEnabled = enabled ? 1 : 0;
|
||||
const normalizedAutoHide = autoHide ? 1 : 0;
|
||||
const normalizedThreshold = normalizeSportsScoreThreshold(threshold);
|
||||
const normalizedWeights = normalizeSportsWeights(weights);
|
||||
const serializedWeights = JSON.stringify(normalizedWeights);
|
||||
const normalizedTerms = normalizeSportsTerms(terms);
|
||||
const serializedTerms = JSON.stringify(normalizedTerms);
|
||||
|
||||
const existing = db.prepare('SELECT id FROM moderation_settings WHERE id = 1').get();
|
||||
if (existing) {
|
||||
db.prepare(`
|
||||
UPDATE moderation_settings
|
||||
SET sports_scoring_enabled = ?, sports_score_threshold = ?, sports_auto_hide_enabled = ?, sports_score_weights = ?, sports_terms = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = 1
|
||||
`).run(normalizedEnabled, normalizedThreshold, normalizedAutoHide, serializedWeights, serializedTerms);
|
||||
} else {
|
||||
db.prepare(`
|
||||
INSERT INTO moderation_settings (id, sports_scoring_enabled, sports_score_threshold, sports_auto_hide_enabled, sports_score_weights, sports_terms, updated_at)
|
||||
VALUES (1, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
`).run(normalizedEnabled, normalizedThreshold, normalizedAutoHide, serializedWeights, serializedTerms);
|
||||
}
|
||||
|
||||
return {
|
||||
sports_scoring_enabled: !!normalizedEnabled,
|
||||
sports_score_threshold: normalizedThreshold,
|
||||
sports_auto_hide_enabled: !!normalizedAutoHide,
|
||||
sports_score_weights: normalizedWeights,
|
||||
sports_terms: normalizedTerms
|
||||
};
|
||||
}
|
||||
|
||||
function expandPhotoUrlHostVariants(url) {
|
||||
if (typeof url !== 'string' || !url) {
|
||||
return [];
|
||||
@@ -1826,14 +2032,14 @@ function removeSearchSeenEntries(urls) {
|
||||
|
||||
cleanupExpiredSearchPosts();
|
||||
|
||||
const selectSearchSeenStmt = db.prepare('SELECT url, seen_count, manually_hidden, first_seen_at, last_seen_at FROM search_seen_posts WHERE url = ?');
|
||||
const selectSearchSeenStmt = db.prepare('SELECT url, seen_count, manually_hidden, sports_auto_hidden, first_seen_at, last_seen_at FROM search_seen_posts WHERE url = ?');
|
||||
const insertSearchSeenStmt = db.prepare(`
|
||||
INSERT INTO search_seen_posts (url, seen_count, manually_hidden, first_seen_at, last_seen_at)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
INSERT INTO search_seen_posts (url, seen_count, manually_hidden, sports_auto_hidden, first_seen_at, last_seen_at)
|
||||
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
`);
|
||||
const updateSearchSeenStmt = db.prepare(`
|
||||
UPDATE search_seen_posts
|
||||
SET seen_count = ?, manually_hidden = ?, last_seen_at = CURRENT_TIMESTAMP
|
||||
SET seen_count = ?, manually_hidden = ?, sports_auto_hidden = ?, last_seen_at = CURRENT_TIMESTAMP
|
||||
WHERE url = ?
|
||||
`);
|
||||
const checkIndexes = db.prepare("PRAGMA index_list('checks')").all();
|
||||
@@ -2158,7 +2364,7 @@ app.get('/api/posts/by-url', (req, res) => {
|
||||
|
||||
app.post('/api/search-posts', (req, res) => {
|
||||
try {
|
||||
const { url, candidates, skip_increment, force_hide } = req.body || {};
|
||||
const { url, candidates, skip_increment, force_hide, sports_auto_hide } = req.body || {};
|
||||
|
||||
const normalizedUrls = collectNormalizedFacebookUrls(url, candidates);
|
||||
if (!normalizedUrls.length) {
|
||||
@@ -2200,6 +2406,8 @@ app.post('/api/search-posts', (req, res) => {
|
||||
|
||||
const targetUrl = existingUrl || normalizedUrls[0];
|
||||
const existingManualHidden = existingRow ? !!existingRow.manually_hidden : false;
|
||||
const existingSportsHidden = existingRow ? !!existingRow.sports_auto_hidden : false;
|
||||
const sportsHideRequested = !!sports_auto_hide;
|
||||
|
||||
if (force_hide) {
|
||||
const desiredCount = Math.max(existingRow ? existingRow.seen_count : 0, SEARCH_POST_HIDE_THRESHOLD);
|
||||
@@ -2208,36 +2416,38 @@ app.post('/api/search-posts', (req, res) => {
|
||||
for (const candidate of urlsToUpdate) {
|
||||
const row = selectSearchSeenStmt.get(candidate);
|
||||
const candidateCount = row ? Math.max(row.seen_count, desiredCount) : desiredCount;
|
||||
const nextSportsHidden = sportsHideRequested || (row ? !!row.sports_auto_hidden : false);
|
||||
if (row) {
|
||||
updateSearchSeenStmt.run(candidateCount, 1, candidate);
|
||||
updateSearchSeenStmt.run(candidateCount, 1, nextSportsHidden ? 1 : 0, candidate);
|
||||
} else {
|
||||
insertSearchSeenStmt.run(candidate, candidateCount, 1);
|
||||
insertSearchSeenStmt.run(candidate, candidateCount, 1, nextSportsHidden ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
return res.json({ seen_count: desiredCount, should_hide: true, manually_hidden: true });
|
||||
return res.json({ seen_count: desiredCount, should_hide: true, manually_hidden: true, sports_auto_hidden: sportsHideRequested || existingSportsHidden });
|
||||
}
|
||||
|
||||
if (skip_increment) {
|
||||
if (!existingRow) {
|
||||
return res.json({ seen_count: 0, should_hide: false, manually_hidden: false });
|
||||
return res.json({ seen_count: 0, should_hide: false, manually_hidden: false, sports_auto_hidden: false });
|
||||
}
|
||||
const seenCount = existingRow.seen_count;
|
||||
const shouldHide = seenCount >= SEARCH_POST_HIDE_THRESHOLD || existingManualHidden;
|
||||
return res.json({ seen_count: seenCount, should_hide: shouldHide, manually_hidden: existingManualHidden });
|
||||
const shouldHide = seenCount >= SEARCH_POST_HIDE_THRESHOLD || existingManualHidden || existingSportsHidden;
|
||||
return res.json({ seen_count: seenCount, should_hide: shouldHide, manually_hidden: existingManualHidden, sports_auto_hidden: existingSportsHidden });
|
||||
}
|
||||
|
||||
let seenCount = existingRow ? existingRow.seen_count + 1 : 1;
|
||||
const manualHidden = existingManualHidden;
|
||||
const sportsHidden = sportsHideRequested || existingSportsHidden;
|
||||
|
||||
if (existingRow) {
|
||||
updateSearchSeenStmt.run(seenCount, manualHidden ? 1 : 0, targetUrl);
|
||||
updateSearchSeenStmt.run(seenCount, manualHidden ? 1 : 0, sportsHidden ? 1 : 0, targetUrl);
|
||||
} else {
|
||||
insertSearchSeenStmt.run(targetUrl, seenCount, manualHidden ? 1 : 0);
|
||||
insertSearchSeenStmt.run(targetUrl, seenCount, manualHidden ? 1 : 0, sportsHidden ? 1 : 0);
|
||||
}
|
||||
|
||||
const shouldHide = seenCount >= SEARCH_POST_HIDE_THRESHOLD || manualHidden;
|
||||
res.json({ seen_count: seenCount, should_hide: shouldHide, manually_hidden: manualHidden });
|
||||
const shouldHide = seenCount >= SEARCH_POST_HIDE_THRESHOLD || manualHidden || sportsHidden;
|
||||
res.json({ seen_count: seenCount, should_hide: shouldHide, manually_hidden: manualHidden, sports_auto_hidden: sportsHidden });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
@@ -3259,6 +3469,31 @@ app.put('/api/ai-settings', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/moderation-settings', (req, res) => {
|
||||
try {
|
||||
const settings = loadModerationSettings();
|
||||
res.json(settings);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/moderation-settings', (req, res) => {
|
||||
try {
|
||||
const body = req.body || {};
|
||||
const saved = persistModerationSettings({
|
||||
enabled: !!body.sports_scoring_enabled,
|
||||
threshold: body.sports_score_threshold,
|
||||
weights: body.sports_score_weights,
|
||||
terms: body.sports_terms,
|
||||
autoHide: !!body.sports_auto_hide_enabled
|
||||
});
|
||||
res.json(saved);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/hidden-settings', (req, res) => {
|
||||
try {
|
||||
const settings = loadHiddenSettings();
|
||||
|
||||
Reference in New Issue
Block a user