From 45217553447a1ec5acb15e609a2f063425db925a Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Sun, 22 Feb 2026 18:05:26 +0000 Subject: [PATCH] Fix Signal receiving --- .dockerignore | 9 ++ .gitignore | 1 + Makefile | 2 +- app/urls.py | 10 ++ .../pages/compose-contact-match.html | 86 ++++++++++-- core/views/compose.py | 125 ++++++++++++++++++ scripts/quadlet/manage.sh | 6 +- 7 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..aa090b9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.podman +artifacts +.container-home +db.sqlite3 +docker/data +signal-cli-config +__pycache__ +*.pyc diff --git a/.gitignore b/.gitignore index 7f355a2..31c1f2c 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,4 @@ oom node_modules/ .podman/ .beads/ +.sisyphus/ diff --git a/Makefile b/Makefile index 30f0568..4f7ae52 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ run: bash $(QUADLET_MGR) up build: - docker-compose --env-file=stack.env build + OPERATION=uwsgi podman build --build-arg OPERATION=uwsgi -t localhost/xf/gia:prod -f Dockerfile . stop: bash $(QUADLET_MGR) down diff --git a/app/urls.py b/app/urls.py index f2b733e..da364fb 100644 --- a/app/urls.py +++ b/app/urls.py @@ -220,6 +220,16 @@ urlpatterns = [ compose.ComposeContactMatch.as_view(), name="compose_contact_match", ), + path( + "compose/contacts/create/", + compose.ComposeContactCreate.as_view(), + name="compose_contact_create", + ), + path( + "compose/contacts/create-all/", + compose.ComposeContactCreateAll.as_view(), + name="compose_contact_create_all", + ), # AIs path( "ai/workspace/", diff --git a/core/templates/pages/compose-contact-match.html b/core/templates/pages/compose-contact-match.html index b667510..9192032 100644 --- a/core/templates/pages/compose-contact-match.html +++ b/core/templates/pages/compose-contact-match.html @@ -13,7 +13,18 @@
- +
+ {% csrf_token %} + +
+
Manual Workspace @@ -187,23 +198,43 @@ {{ row.identifier }} - {% if not row.linked_person and row.suggestions %} + {% if not row.linked_person %}
- {% for suggestion in row.suggestions %} -
+ {% if row.detected_name %} + {% csrf_token %} - +
- {% endfor %} + {% endif %} + {% if row.suggestions %} + {% for suggestion in row.suggestions %} +
+ {% csrf_token %} + + + + +
+ {% endfor %} + {% endif %}
{% else %} - @@ -436,6 +467,41 @@ if (ev.key === "Escape") hidePopover(); }); + document.querySelectorAll(".js-create-contact").forEach((btn) => { + btn.addEventListener("click", function (ev) { + const name = this.dataset.name || "this contact"; + const identifier = this.dataset.identifier || ""; + const service = this.dataset.service || ""; + const confirmMsg = "Create new person '" + name + "' and link to " + identifier + " (" + service + ")?"; + if (!confirm(confirmMsg)) { + ev.preventDefault(); + return false; + } + }); + }); + + const createAllBtn = document.getElementById("create-all-btn"); + if (createAllBtn) { + createAllBtn.addEventListener("click", function (ev) { + const table = document.getElementById("discovered-contacts-table"); + if (!table) return; + const unlinkedWithName = table.querySelectorAll("tbody tr").filter((row) => { + const statusCell = row.querySelector('[data-discovered-col="5"]'); + return statusCell && statusCell.textContent.includes("unlinked"); + }).length; + if (unlinkedWithName === 0) { + ev.preventDefault(); + alert("No unlinked contacts with detected names to create."); + return false; + } + const confirmMsg = "Create " + unlinkedWithName + " new contact" + (unlinkedWithName > 1 ? "s" : "") + " from detected names?"; + if (!confirm(confirmMsg)) { + ev.preventDefault(); + return false; + } + }); + } + applyFilters(); applyColumns(); })(); diff --git a/core/views/compose.py b/core/views/compose.py index 30855d9..4615689 100644 --- a/core/views/compose.py +++ b/core/views/compose.py @@ -2361,6 +2361,131 @@ class ComposeContactMatch(LoginRequiredMixin, View): ) +class ComposeContactCreate(LoginRequiredMixin, View): + template_name = "pages/compose-contact-match.html" + + def post(self, request): + service = _default_service(request.POST.get("service")) + identifier = str(request.POST.get("identifier") or "").strip() + person_name = str(request.POST.get("person_name") or "").strip() + + if not identifier: + return render( + request, + self.template_name, + self._context(request, "Identifier is required.", "warning"), + ) + + if not person_name: + return render( + request, + self.template_name, + self._context(request, "Person name is required.", "warning"), + ) + + existing = PersonIdentifier.objects.filter( + user=request.user, + service=service, + identifier=identifier, + ).first() + + if existing and existing.person: + return render( + request, + self.template_name, + self._context( + request, + f"{identifier} ({service}) is already linked to {existing.person.name}.", + "warning", + ), + ) + + person = Person.objects.create(user=request.user, name=person_name) + + PersonIdentifier.objects.create( + user=request.user, + person=person, + service=service, + identifier=identifier, + ) + + message = f"Created person '{person_name}' and linked {identifier} ({service})." + return render( + request, + self.template_name, + self._context(request, message, "success"), + ) + + +class ComposeContactCreateAll(LoginRequiredMixin, View): + template_name = "pages/compose-contact-match.html" + + def post(self, request): + candidates = _manual_contact_rows(request.user) + + created_count = 0 + skipped_count = 0 + errors = [] + + for candidate in candidates: + if candidate.get("linked_person"): + skipped_count += 1 + continue + + detected_name = candidate.get("detected_name", "") + if not detected_name: + skipped_count += 1 + continue + + service = candidate.get("service", "") + identifier = candidate.get("identifier", "") + + if not service or not identifier: + skipped_count += 1 + continue + + existing = PersonIdentifier.objects.filter( + user=request.user, + service=service, + identifier=identifier, + ).first() + + if existing and existing.person: + skipped_count += 1 + continue + + try: + person = Person.objects.create(user=request.user, name=detected_name) + + PersonIdentifier.objects.create( + user=request.user, + person=person, + service=service, + identifier=identifier, + ) + + created_count += 1 + except Exception as e: + errors.append(f"{identifier} ({service}): {str(e)}") + skipped_count += 1 + + if errors: + message = f"Created {created_count} contacts. Errors: {'; '.join(errors[:5])}" + level = "warning" + elif created_count > 0: + message = f"Created {created_count} new contact{'s' if created_count != 1 else ''}. Skipped {skipped_count}." + level = "success" + else: + message = f"No new contacts to create. {skipped_count} already linked or without names." + level = "info" + + return render( + request, + self.template_name, + self._context(request, message, level), + ) + + class ComposePage(LoginRequiredMixin, View): template_name = "pages/compose.html" diff --git a/scripts/quadlet/manage.sh b/scripts/quadlet/manage.sh index 714dbde..6ebf128 100755 --- a/scripts/quadlet/manage.sh +++ b/scripts/quadlet/manage.sh @@ -35,7 +35,11 @@ ensure_dirs() { mkdir -p "$REDIS_DATA_DIR" "$WHATSAPP_DATA_DIR" "$VRUN_DIR" "$ROOT_DIR/signal-cli-config" # Container runs as uid 1000 (xf); rootless Podman remaps uids so plain # chown won't work — podman unshare translates to the correct host uid. - podman unshare chown 1000:1000 "$WHATSAPP_DATA_DIR" 2>/dev/null || true + if [[ -n "${QUADLET_SKIP_UNSHARE:-}" ]] || [[ -n "${CONTAINER_HOST:-}" ]] || [[ -n "${PODMAN_HOST:-}" ]] || [[ -n "${DOCKER_HOST:-}" ]]; then + echo "Skipping podman unshare chown (QUADLET_SKIP_UNSHARE or remote host detected)" + else + podman unshare chown 1000:1000 "$WHATSAPP_DATA_DIR" 2>/dev/null || true + fi } rm_if_exists() {