diff --git a/Dockerfile b/Dockerfile index 462ed49d..ecbf48bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,91 @@ -FROM golang:1.25.0-alpine AS build +# Evolution GO fork — bug fixes para Solar Teles +# +# Base: evolution-foundation/evolution-go v0.7.2 (release 2026-07-03) +# Patches (todos upstream-pendentes, mantidos aqui porque os PRs ainda +# não foram mergeados no upstream): +# +# * PR #136 (cesar-carlos, 2026-07-27) — webhook wiping fix +# stop Connect/advanced-settings from wiping config (settings, +# webhook, events). Recreate com nome já em uso passa a retornar +# 409 em vez de sobrescrever config existente. +# +# * PR #178 (wilsonborba, 2026-08-19) — Postgres connection-pool leak +# Reusa authDB *sql.DB já configurado em vez de abrir pool novo +# a cada StartClient/ReconnectClient. Sem ele, ~15 reconnects +# === 32 connections abertas, satura max_connections=100 em 6 +# dias, derruba QR pairing. +# +# * PR #149 (iagocotta, 2026-07-31) — fix GetQr disconnecting active +# session +# GET /instance/qr não derruba sessão ativa quando está logado +# apenas retorna "already logged in" em vez de matar o container. +# +# Resultado: imagem registry.gitlab.com/douglasanpa/nextbotsdr/evolution-go:0.7.2-solar-fixes +# +# Quando dropar o fork: quando qualquer um dos 3 PRs for mergeado no +# upstream + nova tag v0.7.3+ for cortada. Aí volta a usar +# evoapicloud/evolution-go:latest direto. +# +# ── Stage 1: build ─────────────────────────────────────────────── +FROM golang:1.25.0-alpine AS builder -RUN apk update && apk add --no-cache git build-base libjpeg-turbo-dev libwebp-dev +ARG VERSION=0.7.2 +ARG REPO=https://github.com/evolution-foundation/evolution-go.git -WORKDIR /build +RUN apk update && apk add --no-cache git build-base libjpeg-turbo-dev libwebp-dev ca-certificates tzdata -# Copiar apenas arquivos de dependências primeiro para cachear o download -COPY go.mod go.sum ./ +WORKDIR /src -# whatsmeow agora vem do proxy oficial (go.mau.fi/whatsmeow, sem replace local) — -# não há mais submódulo whatsmeow-lib para copiar. -RUN go mod download +# Clone the tag we want to fork from +RUN git clone --depth 1 --branch ${VERSION} ${REPO} . \ + && git log -1 --format='%H' > /tmp/base_commit -# Copiar o restante do código -COPY . . +# Apply patches in order. -3 way-back merge + fuzz tolerance for slight +# context drift across upstream commits. +COPY patch/ /patches/ +RUN for p in /patches/pr-*.patch; do \ + echo "==> applying $p"; \ + git apply --whitespace=fix --reject "$p" || \ + (echo "git apply failed for $p" && exit 1); \ + done -ARG VERSION=dev -RUN CGO_ENABLED=1 go build -ldflags "-X main.version=${VERSION}" -o server ./cmd/evolution-go +# Build the server binary — must match upstream: CGO_ENABLED=1 + libwebp +# (chai2010/webp needs CGO; CGO_ENABLED=0 quebra com undefined: webpGetInfo) +RUN CGO_ENABLED=1 GOOS=linux go build \ + -ldflags="-s -w -X main.version=${VERSION}" \ + -o /out/server ./cmd/evolution-go +# ── Stage 2: runtime ───────────────────────────────────────────── FROM alpine:3.19.1 AS final -# poppler-utils provides pdftoppm, used to rasterize PDF page 1 for /send/media document thumbnails -RUN apk update && apk add --no-cache tzdata ffmpeg libjpeg-turbo libwebp poppler-utils +LABEL org.opencontainers.image.title="evolution-go (Solar Teles fork)" +LABEL org.opencontainers.image.description="Evolution GO v0.7.2 with PR #136, #178, #149 applied" +LABEL org.opencontainers.image.source="https://github.com/evolution-foundation/evolution-go" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.version="0.7.2-solar-fixes" + +RUN apk update && apk add --no-cache tzdata ffmpeg libjpeg-turbo libwebp poppler-utils ca-certificates curl \ + && addgroup -S evolution && adduser -S evolution -G evolution WORKDIR /app -COPY --from=build /build/server . -COPY --from=build /build/manager/dist ./manager/dist -COPY --from=build /build/VERSION ./VERSION +COPY --from=builder /out/server /app/server +COPY --from=builder /src/manager/dist ./manager/dist +COPY --from=builder /src/VERSION ./VERSION + +# ── Mobile UX fix (mantido no fork até upstream corrigir) ───────── +# Sidebar era hidden md:flex (some no celular) + action bar opacity-0 +# group-hover (invisível sem hover). Injeta CSS+JS sem precisar do +# manager/src — dist é pré-buildado no upstream. +COPY manager-mobile-fix.css manager-mobile-fix.js ./manager/dist/assets/ +RUN sed -i 's|||' ./manager/dist/index.html && \ + sed -i 's|||' ./manager/dist/index.html ENV TZ=America/Sao_Paulo +EXPOSE 4000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD sh -c 'curl -fsS http://localhost:${SERVER_PORT:-4000}/server/ok || exit 1' + ENTRYPOINT ["/app/server"] diff --git a/README.md b/README.md index 0b4999be..0892a22c 100644 --- a/README.md +++ b/README.md @@ -1,261 +1,134 @@ -

- - Evolution Foundation - -

- -

Evolution Go

- -

- High-performance WhatsApp API built in Go — part of the Evolution Foundation ecosystem. -

- -

- Latest version - License: Apache 2.0 - Documentation - Community - Docker image -

- -

- Website · - Documentation · - Community · - Support -

- - ---- - -## About - -**Evolution Go** is a high-performance WhatsApp API built in Go. Part of the Evolution Foundation ecosystem, it provides a robust, lightweight solution for WhatsApp integration using the [whatsmeow](https://github.com/tulir/whatsmeow) library. - -## Part of the Evolution Foundation ecosystem - -Evolution Go is one of the messaging engines maintained by Evolution Foundation. It is used as a WhatsApp provider by the [Evo CRM Community](https://github.com/evolution-foundation/evo-crm-community) and other projects in the ecosystem. - ---- - -## Features - -- **High performance** — built with Go for minimal resource usage -- **RESTful API** — clean, well-documented REST endpoints with Swagger -- **Real-time events** — WebSocket, Webhook, AMQP/RabbitMQ and NATS support -- **Media support** — images, videos, audio, documents with MinIO/S3 storage -- **Message storage** — optional PostgreSQL persistence -- **QR code pairing** — built-in QR code generation for device linking -- **License management** — built-in licensing with registration, activation, and heartbeat -- **Docker ready** — production-ready Docker configuration - ---- - -## Quick Start - -### Docker (recommended) - -```bash -git clone https://github.com/evolution-foundation/evolution-go.git -cd evolution-go -make docker-build -make docker-run +# evolution-go fork — Solar Teles bug fixes + +**Path no repo:** `evolution-go/` +**Imagem resultante:** `registry.gitlab.com/douglasanpa/nextbotsdr/evolution-go:0.7.2-solar-fixes` + +## Por que esse fork existe + +4 bugs **críticos para o Solar Teles** estão reportados a **4+ semanas** no upstream +e os PRs com fix ainda não foram mergeados (ou os autores abandonaram). Cada bug, +sozinho, é capaz de derrubar a integração WhatsApp do Solar Teles em produção: + +| # | PR | Sintoma | Detecção Solar Teles | +|---|---|---|---| +| #136 | cesar-carlos | Webhook/config são apagados ao recriar instance com mesmo nome | Webhook inbound quebrado silenciosamente | +| #178 | wilsonborba | Postgres connection pool leak — satura `max_connections` em ~6 dias | QR pairing para de funcionar; SendText vira 500 | +| #149 | iagocotta | `GET /instance/qr` derruba sessão ativa ao tentar regerar | "Por que minha sessão caiu sozinha?" | +| #163 | FlavioPulli | `/user/avatar` dá `info query timed out` + revoke/edit ignorados (`+` JID) | Avatar 500; apagar/editar mensagem silenciosamente ignorados | + +Construir essa imagem localmente antes do merge upstream elimina os 3 sem +precisar esperar o release da v0.7.3. + +## Como usar + +```yaml +# docker-compose.yml — substitui o serviço evolution-go padrão +services: + evolution-go: + image: registry.gitlab.com/douglasanpa/nextbotsdr/evolution-go:0.7.2-solar-fixes + container_name: evolution-go + environment: + - SERVER_TYPE=http + - SERVER_PORT=8080 + - AUTHENTICATION_TYPE=apikey + - AUTHENTICATION_API_KEY=${EVOLUTION_API_KEY} + - AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true + - DATABASE_ENABLED=true + - DATABASE_PROVIDER=postgresql + - DATABASE_CONNECTION_URI=postgresql://evogo:${DB_PASSWORD}@evogo-db:5432/evogo?schema=public + - POSTGRES_ENABLED=true + - POSTGRES_CONNECTION_URI=postgresql://evogo:${DB_PASSWORD}@evogo-db:5432/evogo_auth + - POSTGRES_AUTH_DB=postgresql://evogo:${DB_PASSWORD}@evogo-db:5432/evogo_auth + - WEBHOOK_GLOBAL_URL=${DOMAIN}/webhook/whatsapp + - WEBHOOK_GLOBAL_ENABLED=true + restart: unless-stopped + ports: + - "8080:8080" ``` -### Local development +Substituir a imagem padrão `evoapicloud/evolution-go:latest` por esta no +`docker-compose.yml` do Solar Teles. -```bash -git clone https://github.com/evolution-foundation/evolution-go.git -cd evolution-go +## Como buildar localmente (testes / dev) -# Setup, configure and run -make setup -cp .env.example .env -make dev +```bash +cd evolution-go/ +docker build -t evolution-go:0.7.2-solar-fixes . ``` -> Run `make help` to see all available commands. See [COMMANDS.md](./COMMANDS.md) for detailed workflows. - ---- - -## Configuration - -Create a `.env` file: - -```env -# Server -SERVER_PORT=8080 -CLIENT_NAME=evolution +Build multi-stage: ~3min na primeira vez (download deps Go), ~30s nas +seguintes (cache de camadas). -# Security -GLOBAL_API_KEY=your-secure-api-key-here +## Validação automática -# Database -POSTGRES_AUTH_DB=postgresql://postgres:password@localhost:5432/evogo_auth?sslmode=disable -POSTGRES_USERS_DB=postgresql://postgres:password@localhost:5432/evogo_users?sslmode=disable -DATABASE_SAVE_MESSAGES=false +Cada patch é aplicado pelo `Dockerfile` via `git apply --reject` durante o +build. Camadas: -# Logging -WADEBUG=DEBUG -LOGTYPE=console - -# Optional -# AMQP_URL=amqp://guest:guest@localhost:5672/ -# NATS_URL=nats://localhost:4222 -# WEBHOOK_URL=https://your-webhook-url.com/webhook -# MINIO_ENABLED=true -# MINIO_ENDPOINT=localhost:9000 -# MINIO_ACCESS_KEY=minioadmin -# MINIO_SECRET_KEY=minioadmin +``` +1. git clone --depth 1 --branch 0.7.2 → base +2. apply pr-136.patch → webhook fix +3. apply pr-178.patch → postgres leak fix +4. apply pr-149.patch → QR-no-disconnect fix +5. apply pr-163-avatar-canonical.patch → avatar/revoke/edit JID fix (CanonicalJID) +5. go build ./cmd/evolution-go → binary +6. COPY binary to alpine → runtime ``` -| Variable | Description | Default | -|---|---|---| -| `SERVER_PORT` | Server port | `8080` | -| `CLIENT_NAME` | Client identifier | `evolution` | -| `GLOBAL_API_KEY` | API authentication key | **Required** | -| `DATABASE_SAVE_MESSAGES` | Enable message storage | `false` | -| `WADEBUG` | WhatsApp debug level | `INFO` | - ---- +Se algum patch falhar, o `RUN` quebra o build com exit code != 0 — fork +não-builda = fork não-sobe em prod. -## License Activation +## Verificação de impacto em produção -Evolution Go requires a license to operate. On first run: +### Antes de subir o fork -1. Start the server — API endpoints return `503` until activated -2. Open the **Manager** at `http://localhost:8080/manager/login` -3. Enter your API URL and `GLOBAL_API_KEY` -4. Complete the license registration flow -5. Once activated, the API is fully operational +```bash +# Contar conexões abertas na DB auth do evolution +docker compose exec -T evogo-db psql -U evogo -d evogo_auth -c \ + "SELECT count(*), state FROM pg_stat_activity GROUP BY state ORDER BY 1 DESC;" -The license status persists in the database (`runtime_configs` table). Heartbeats are sent periodically to maintain activation. +# Em prod Solar Teles, esperar ver ~15-30 conexões idle crescentes +# (sinal de leak do PR #178) +``` ---- +### Depois (com fork aplicado) -## API Documentation +```bash +# Mesmo comando — conexões idle devem ficar planas (~2-4) mesmo após +# várias recriações de instance / reconnects +``` -Swagger UI available at: +Critério de aceite: conexão idle plana por ≥24h mesmo com +`POST /instance/reconnect` acionado 20+ vezes. -``` -http://localhost:8080/swagger/index.html -``` +## Quando descartar o fork -### Key Endpoints +Quando: -| Method | Endpoint | Description | -|---|---|---| -| `POST` | `/instance/create` | Create WhatsApp instance | -| `GET` | `/instance/{name}/qrcode` | Get QR code for pairing | -| `POST` | `/message/sendText` | Send text message | -| `POST` | `/message/sendMedia` | Send media message | -| `GET` | `/instance/{name}/status` | Get instance status | -| `DELETE` | `/instance/{name}` | Delete instance | +1. **PR #178** (ou similar) for mergeado em `evolution-foundation/evolution-go` +2. **PR #149** for mergeado (idem) +3. **PR #136** já está aplicado no seu fork anterior — manter até confirmar merge upstream +4. Cortada nova tag ≥ v0.7.3 incluindo esses fixes ---- +Aí trocar a imagem do `docker-compose.yml` de volta para +`evoapicloud/evolution-go:latest` (ou v0.7.3+ fixa). -## Project Structure +## Arquivos do fork ``` evolution-go/ -├── cmd/evolution-go/ # Application entry point -├── pkg/ -│ ├── core/ # License management & middleware -│ ├── instance/ # Instance management -│ ├── message/ # Message handling -│ ├── sendMessage/ # Message sending -│ ├── routes/ # HTTP routes -│ ├── middleware/ # Auth & validation middleware -│ ├── config/ # Configuration -│ ├── events/ # Event producers (AMQP, NATS, Webhook, WS) -│ └── storage/ # Media storage (MinIO) -├── docs/ # Swagger documentation -├── Dockerfile -├── Makefile -└── VERSION +├── Dockerfile # Multi-stage com aplicação de 3 patches +├── README.md # Este arquivo +└── patch/ + ├── pr-136.patch # webhook wiping fix + ├── pr-178.patch # postgres connection leak + ├── pr-149.patch # QR disconnect active session + └── pr-163-avatar-canonical.patch # avatar + revoke/edit JID (+ prefix) ``` ---- - -## Tech Stack - -| Component | Technology | -|---|---| -| Language | Go 1.24+ | -| HTTP framework | Gin | -| WhatsApp | [whatsmeow](https://github.com/tulir/whatsmeow) | -| Database | PostgreSQL | -| ORM | GORM | -| Message queue | RabbitMQ, NATS | -| Object storage | MinIO/S3 | -| Documentation | Swagger/OpenAPI | -| Container | Docker | - ---- - -## Documentation - -| Resource | Link | -|---|---| -| Website | [evolutionfoundation.com.br](https://evolutionfoundation.com.br) | -| Documentation | [docs.evolutionfoundation.com.br](https://docs.evolutionfoundation.com.br) | -| Community | [evolutionfoundation.com.br/community](https://evolutionfoundation.com.br/community) | -| Docker Hub | [evoapicloud/evolution-go](https://hub.docker.com/r/evoapicloud/evolution-go) | -| Changelog | [CHANGELOG.md](./CHANGELOG.md) | -| Contributing | [CONTRIBUTING.md](./CONTRIBUTING.md) | -| Security | [SECURITY.md](./SECURITY.md) | - ---- - -## Hosting - -Deploy Evolution Go with optimized infrastructure through our HostGator partnership: - -[**Evolution Go VPS — HostGator**](https://evolution-api.com/vps-evolution-go) - ---- - -## Telemetry - -Evolution Go collects anonymous telemetry data (routes used, API version) to help improve the service. **No sensitive or personal data is collected.** - ---- - -## Contributing - -Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to submit issues, propose features, and open pull requests. - -Join our [community](https://evolutionfoundation.com.br/community) to discuss ideas and collaborate. - ---- - -## Security - -For security issues, **do not open a public issue**. Email **suporte@evofoundation.com.br** or use GitHub's private vulnerability reporting. See [SECURITY.md](./SECURITY.md) for details. - ---- - -## Acknowledgments - -- [whatsmeow](https://github.com/tulir/whatsmeow) by [Tulir Asokan](https://github.com/tulir) — WhatsApp protocol library -- [Evolution API](https://github.com/evolution-foundation/evolution-api) — Node.js sister project - ---- - -## License - -Evolution Go is licensed under the Apache License 2.0, with additional brand-protection conditions (LOGO/copyright preservation and Usage Notification requirement). See [LICENSE](./LICENSE) for full details. - -For licensing inquiries, contact **suporte@evofoundation.com.br**. - -## Trademarks - -"Evolution Foundation", "Evolution" and "Evolution Go" are trademarks of Evolution Foundation. See [TRADEMARKS.md](./TRADEMARKS.md) for the brand assets policy. - -Third-party attributions are documented in [NOTICE](./NOTICE). +## Pipeline CI/CD ---- +Adicionado stage `build-evolution-go` em `.gitlab-ci.yml` no projeto +nextbotsdr. Dispara no push da branch `dev` ou via trigger manual. -

- Made by Evolution Foundation · © 2026 -

+A imagem é publicada no GitLab Container Registry do projeto e consumida +pelo `docker-compose.yml` do Solar Teles na VPS. diff --git a/manager-mobile-fix.css b/manager-mobile-fix.css new file mode 100644 index 00000000..6e1cd9ed --- /dev/null +++ b/manager-mobile-fix.css @@ -0,0 +1,43 @@ +/* manager-mobile-fix.css — Evolution GO mobile UX fix (fork) */ +/* Sidebar: hidden md:flex => drawer on <768px; instance actions: always visible (no hover) */ + +/* Instance action bar — was opacity-0 group-hover:opacity-100, invisible on touch devices */ +/* Mobile drawer */ +@media (max-width: 767px) { + /* Instance actions: sempre visíveis só no mobile (desktop mantém hover) */ + .group .flex.border-t.opacity-0 { opacity: 1 !important; } + + /* Sidebar becomes off-canvas drawer */ + div.hidden.md\:flex.bg-sidebar { + display: flex !important; + position: fixed !important; + inset: 0 auto 0 0 !important; + z-index: 50 !important; + width: 16rem !important; /* w-64 */ + transform: translateX(-100%) !important; + transition: transform 0.25s ease !important; + box-shadow: 4px 0 24px rgba(0,0,0,0.4); + } + div.hidden.md\:flex.bg-sidebar.open { + transform: translateX(0) !important; + } + /* Backdrop */ + #mobile-sidebar-backdrop { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.5); + z-index: 40; + display: none; + } + #mobile-sidebar-backdrop.open { display: block; } + /* Header: hide the empty w-56 spacer on mobile, make title visible */ + header .w-56 { display: none !important; } + header { padding-left: 0.5rem !important; padding-right: 0.5rem !important; } + /* Instance grid: force single column on narrow screens */ + /* (cards already responsive but ensure padding) */ + main { padding-left: 0.5rem !important; padding-right: 0.5rem !important; } +} +@media (min-width: 768px) { + #mobile-sidebar-backdrop { display: none !important; } + #hamburger-btn { display: none !important; } +} diff --git a/manager-mobile-fix.js b/manager-mobile-fix.js new file mode 100644 index 00000000..e08ddc4b --- /dev/null +++ b/manager-mobile-fix.js @@ -0,0 +1,64 @@ +// manager-mobile-fix.js — inject hamburger + drawer behavior for mobile +(function(){ + function init(){ + var sidebar = document.querySelector('div.hidden.md\\:flex.bg-sidebar'); + var header = document.querySelector('header.flex.h-16'); + if(!sidebar || !header) return false; + if(document.getElementById('hamburger-btn')) return true; // already injected + + // mark sidebar for targeting + sidebar.id = 'mobile-sidebar'; + + // backdrop + var backdrop = document.createElement('div'); + backdrop.id = 'mobile-sidebar-backdrop'; + backdrop.addEventListener('click', closeDrawer); + document.body.appendChild(backdrop); + + // hamburger button — insert at start of header + var btn = document.createElement('button'); + btn.id = 'hamburger-btn'; + btn.setAttribute('aria-label','Abrir menu'); + btn.style.cssText = 'display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:8px;background:transparent;border:none;cursor:pointer;flex-shrink:0;'; + btn.innerHTML = ''; + btn.addEventListener('click', function(e){ + e.stopPropagation(); + var isOpen = sidebar.classList.contains('open'); + isOpen ? closeDrawer() : openDrawer(); + }); + + // insert as first child of header + header.insertBefore(btn, header.firstChild); + + // close drawer when clicking a nav link + sidebar.querySelectorAll('a').forEach(function(a){ + a.addEventListener('click', closeDrawer); + }); + + // close on escape + document.addEventListener('keydown', function(e){ + if(e.key === 'Escape') closeDrawer(); + }); + + function openDrawer(){ + sidebar.classList.add('open'); + backdrop.classList.add('open'); + document.body.style.overflow = 'hidden'; + } + function closeDrawer(){ + sidebar.classList.remove('open'); + backdrop.classList.remove('open'); + document.body.style.overflow = ''; + } + return true; + } + + // SPA: retry until React mounts (sem limite — bundle pode demorar em rede lenta) + var timer = setInterval(function(){ + if(init()) clearInterval(timer); + }, 300); + // also re-init on navigation (history changes) + var _pushState = history.pushState; + history.pushState = function(){ _pushState.apply(this, arguments); setTimeout(init, 400); }; + window.addEventListener('popstate', function(){ setTimeout(init, 400); }); +})(); diff --git a/patch/pr-163-avatar-canonical.patch b/patch/pr-163-avatar-canonical.patch new file mode 100644 index 00000000..0f38054d --- /dev/null +++ b/patch/pr-163-avatar-canonical.patch @@ -0,0 +1,32 @@ +diff --git a/pkg/message/service/message_service.go b/pkg/message/service/message_service.go +index f8f1392..83b278d 100644 +--- a/pkg/message/service/message_service.go ++++ b/pkg/message/service/message_service.go +@@ -477,6 +477,7 @@ func (m *messageService) DeleteMessageEveryone(data *MessageStruct, instance *in + m.loggerWrapper.GetLogger(instance.Id).LogError("[%s] Error validating message fields", instance.Id) + return "", "", errors.New("invalid phone number") + } ++ recipient = utils.CanonicalJID(recipient) + + m.loggerWrapper.GetLogger(instance.Id).LogInfo("Revoking message %s from %s", data.MessageID, recipient) + +@@ -505,6 +506,7 @@ func (m *messageService) EditMessage(data *EditMessageStruct, instance *instance + m.loggerWrapper.GetLogger(instance.Id).LogError("[%s] Error validating message fields", instance.Id) + return "", "", errors.New("invalid phone number") + } ++ recipient = utils.CanonicalJID(recipient) + + resp, err := client.SendMessage( + context.Background(), +diff --git a/pkg/user/service/user_service.go b/pkg/user/service/user_service.go +index f301160..8e5a8bc 100644 +--- a/pkg/user/service/user_service.go ++++ b/pkg/user/service/user_service.go +@@ -335,6 +335,7 @@ func (u *userService) GetAvatar(data *GetAvatarStruct, instance *instance_model. + if !ok { + return nil, errors.New("invalid phone number") + } ++ jid = utils.CanonicalJID(jid) + + u.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Requesting avatar for JID: %s, Preview: %v", instance.Id, jid, data.Preview) +