diff --git a/src/routers/sources.py b/src/routers/sources.py
index 55377f9..458893b 100644
--- a/src/routers/sources.py
+++ b/src/routers/sources.py
@@ -42,7 +42,7 @@ router = APIRouter(prefix="/api/sources", tags=["sources"])
SOURCE_UPDATE_COLUMNS = {
"name", "url", "domain", "source_type", "category", "status", "notes",
- "language", "primary_language", "bias", "fetch_strategy",
+ "language", "bias", "fetch_strategy",
"political_orientation", "media_type", "reliability",
"state_affiliated", "country_code",
}
@@ -118,12 +118,11 @@ class GlobalSourceCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
url: Optional[str] = None
domain: Optional[str] = None
- source_type: str = Field(default="rss_feed", pattern="^(rss_feed|web_source|excluded|telegram_channel|podcast_feed|pdf_document|x_account)$")
+ source_type: str = Field(default="rss_feed", pattern="^(rss_feed|web_source|excluded|telegram_channel|podcast_feed|pdf_document)$")
category: str = Field(default="sonstige")
status: str = Field(default="active", pattern="^(active|inactive)$")
notes: Optional[str] = None
language: Optional[str] = Field(default=None, max_length=100)
- primary_language: Optional[str] = Field(default=None, max_length=16)
bias: Optional[str] = Field(default=None, max_length=500)
fetch_strategy: Optional[str] = Field(default="default", pattern="^(default|googlebot|paywall|skip)$")
@@ -132,12 +131,11 @@ class GlobalSourceUpdate(BaseModel):
name: Optional[str] = Field(default=None, max_length=200)
url: Optional[str] = None
domain: Optional[str] = None
- source_type: Optional[str] = Field(default=None, pattern="^(rss_feed|web_source|excluded|telegram_channel|podcast_feed|pdf_document|x_account)$")
+ source_type: Optional[str] = Field(default=None, pattern="^(rss_feed|web_source|excluded|telegram_channel|podcast_feed|pdf_document)$")
category: Optional[str] = None
status: Optional[str] = Field(default=None, pattern="^(active|inactive)$")
notes: Optional[str] = None
language: Optional[str] = Field(default=None, max_length=100)
- primary_language: Optional[str] = Field(default=None, max_length=16)
bias: Optional[str] = Field(default=None, max_length=500)
political_orientation: Optional[str] = None
media_type: Optional[str] = None
@@ -232,10 +230,10 @@ async def create_global_source(
)
cursor = await db.execute(
- """INSERT INTO sources (name, url, domain, source_type, category, status, notes, language, primary_language, bias, fetch_strategy, added_by, tenant_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'system', NULL)""",
+ """INSERT INTO sources (name, url, domain, source_type, category, status, notes, language, bias, fetch_strategy, added_by, tenant_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'system', NULL)""",
(data.name, data.url, data.domain, data.source_type, data.category, data.status, data.notes,
- data.language, data.primary_language, data.bias, data.fetch_strategy or "default"),
+ data.language, data.bias, data.fetch_strategy or "default"),
)
src_id = cursor.lastrowid
await db.commit()
diff --git a/src/source_meta.py b/src/source_meta.py
index 4a9e25f..9f6faa8 100644
--- a/src/source_meta.py
+++ b/src/source_meta.py
@@ -38,7 +38,6 @@ SOURCE_CATEGORIES: list[CategoryEntry] = [
{"key": "russische-staatspropaganda", "label": "Russische Staatspropaganda"},
{"key": "russische-opposition", "label": "Russische Opposition / Exilmedien"},
{"key": "syrien-nahost", "label": "Syrien / Nahost"},
- {"key": "x", "label": "X-Recherche"},
]
@@ -48,7 +47,6 @@ SOURCE_TYPES: list[TypeEntry] = [
{"key": "telegram_channel", "label": "Telegram-Kanal"},
{"key": "podcast_feed", "label": "Podcast-Feed"},
{"key": "excluded", "label": "Ausgeschlossen"},
- {"key": "x_account", "label": "X-Account"},
]
diff --git a/src/static/dashboard.html b/src/static/dashboard.html
index 10051c4..b4813a3 100644
--- a/src/static/dashboard.html
+++ b/src/static/dashboard.html
@@ -329,7 +329,6 @@
-
@@ -472,36 +471,6 @@
-
-
-
-
-
-
-
-
-
-
-
X-Accounts (Twitter), die der Monitor als Recherchequelle nutzt. Pro Lage über die Option „X (Twitter) einbeziehen" zuschaltbar.
-
-
-
-
- | Account |
- URL |
- Sprache |
- Notiz |
- Artikel |
- Status |
- Aktionen |
-
-
-
-
-
-
-
-
@@ -969,59 +938,8 @@
-
-
-
-
+
diff --git a/src/static/js/sources.js b/src/static/js/sources.js
index 2b4aa12..7c51850 100644
--- a/src/static/js/sources.js
+++ b/src/static/js/sources.js
@@ -38,152 +38,10 @@ function setupSourceSubTabs() {
else if (subtab === "tenant-sources") loadTenantSources();
else if (subtab === "source-health") loadHealthData();
else if (subtab === "classification-review") loadClassificationQueue();
- else if (subtab === "x-accounts") loadXAccounts();
});
});
}
-// --- X-Accounts (Recherche-Accounts für den Monitor) ---
-let xAccountsCache = [];
-let editingXAccountId = null;
-
-function normalizeXHandle(raw) {
- let h = (raw || "").trim();
- h = h.replace(/^https?:\/\//i, "").replace(/^www\./i, "");
- h = h.replace(/^(x\.com\/|twitter\.com\/|nitter\.net\/)/i, "");
- h = h.replace(/^@/, "").replace(/\/+$/, "");
- return h.split(/[/?#]/)[0];
-}
-
-async function loadXAccounts() {
- setupXAccountForm();
- const tbody = document.getElementById("xAccountTable");
- tbody.innerHTML = '| Lade... |
';
- try {
- const all = await API.get("/api/sources/global");
- xAccountsCache = (all || []).filter((s) => s.source_type === "x_account");
- renderXAccounts(xAccountsCache);
- } catch (err) {
- tbody.innerHTML = '| Fehler beim Laden |
';
- showToast("X-Accounts konnten nicht geladen werden", "error");
- }
-}
-
-function renderXAccounts(list) {
- const tbody = document.getElementById("xAccountTable");
- const cnt = document.getElementById("xAccountCount");
- if (cnt) cnt.textContent = list.length + (list.length === 1 ? " Account" : " Accounts");
- if (!list.length) {
- tbody.innerHTML = '| Keine X-Accounts. Mit „+ X-Account hinzufügen" anlegen. |
';
- return;
- }
- tbody.innerHTML = list.map((s) => {
- const handle = normalizeXHandle(s.url || s.domain || s.name || "");
- const url = "https://x.com/" + handle;
- const lang = s.primary_language || s.language || "—";
- const notes = s.notes ? esc(s.notes) : '—';
- const status = s.status === "active"
- ? 'Aktiv'
- : 'Inaktiv';
- return ''
- + '| ' + esc(s.name || ("@" + handle)) + ' | '
- + '' + esc(handle) + ' | '
- + '' + esc(lang) + ' | '
- + '' + notes + ' | '
- + '' + (s.article_count || 0) + ' | '
- + '' + status + ' | '
- + ''
- + ' '
- + ''
- + ' | '
- + '
';
- }).join("");
-}
-
-function filterXAccounts() {
- const q = (document.getElementById("xAccountSearch").value || "").toLowerCase();
- if (!q) { renderXAccounts(xAccountsCache); return; }
- renderXAccounts(xAccountsCache.filter((s) =>
- (s.name || "").toLowerCase().includes(q)
- || (s.url || "").toLowerCase().includes(q)
- || (s.notes || "").toLowerCase().includes(q)
- ));
-}
-
-function openXAccountModal(id) {
- editingXAccountId = id || null;
- const errEl = document.getElementById("xAccountError");
- errEl.style.display = "none";
- const s = editingXAccountId ? xAccountsCache.find((a) => a.id === editingXAccountId) : null;
- if (editingXAccountId && !s) return;
- document.getElementById("xAccountModalTitle").textContent = s ? "X-Account bearbeiten" : "X-Account hinzufügen";
- document.getElementById("xAccountHandle").value = s ? normalizeXHandle(s.url || s.domain || "") : "";
- document.getElementById("xAccountName").value = s ? (s.name || "") : "";
- document.getElementById("xAccountLanguage").value = s ? (s.primary_language || s.language || "en") : "en";
- document.getElementById("xAccountNotes").value = s ? (s.notes || "") : "";
- document.getElementById("xAccountStatus").value = s ? (s.status || "active") : "active";
- openModal("modalXAccount");
-}
-
-function setupXAccountForm() {
- const form = document.getElementById("xAccountForm");
- if (!form || form.dataset.wired) return;
- form.dataset.wired = "1";
- form.addEventListener("submit", async (e) => {
- e.preventDefault();
- const errEl = document.getElementById("xAccountError");
- errEl.style.display = "none";
- const handle = normalizeXHandle(document.getElementById("xAccountHandle").value);
- if (!handle) {
- errEl.textContent = "Bitte einen Handle oder eine x.com-URL eingeben.";
- errEl.style.display = "block";
- return;
- }
- const nameVal = document.getElementById("xAccountName").value.trim();
- const body = {
- name: nameVal || ("@" + handle),
- url: "x.com/" + handle,
- domain: "x.com/" + handle,
- source_type: "x_account",
- category: "x",
- status: document.getElementById("xAccountStatus").value,
- notes: document.getElementById("xAccountNotes").value.trim() || null,
- primary_language: document.getElementById("xAccountLanguage").value || null,
- };
- try {
- if (editingXAccountId) {
- await API.put("/api/sources/global/" + editingXAccountId, body);
- } else {
- await API.post("/api/sources/global", body);
- }
- closeModal("modalXAccount");
- loadXAccounts();
- showToast("X-Account gespeichert.", "success");
- } catch (err) {
- errEl.textContent = err.message || "Speichern fehlgeschlagen";
- errEl.style.display = "block";
- }
- });
-}
-
-function confirmDeleteXAccount(id) {
- const s = xAccountsCache.find((a) => a.id === id);
- if (!s) return;
- showConfirm(
- "X-Account entfernen",
- 'Soll der X-Account "' + (s.name || "") + '" als Recherchequelle entfernt werden?',
- async () => {
- try {
- await API.del("/api/sources/global/" + id);
- loadXAccounts();
- showToast("X-Account entfernt.", "success");
- } catch (err) {
- showToast(err.message || "Löschen fehlgeschlagen", "error");
- }
- }
- );
-}
-
// --- Grundquellen ---
async function loadGlobalSources() {
try {