Skip to content

feat: enhance Telegram bot with search improvements, Docker support and CI#195

Open
aiastia wants to merge 2 commits into
usememos:mainfrom
aiastia123:feat/docker-search-ci
Open

feat: enhance Telegram bot with search improvements, Docker support and CI#195
aiastia wants to merge 2 commits into
usememos:mainfrom
aiastia123:feat/docker-search-ci

Conversation

@aiastia

@aiastia aiastia commented May 13, 2026

Copy link
Copy Markdown

Changes

  • Add GitHub Actions workflow for multi-arch Docker build (amd64/arm64) with GHCR
  • Add Dockerfile with cross-compilation, non-root user, and minimal runtime
  • Add docker-compose.yml with data persistence
  • Support --all flag in /search to search all public memos
  • Add clickable memo links in search results (HTML parse mode)
  • Wrap search result content in <code> tag for easy copy
  • Default memo visibility to PRIVATE
  • Add normalizeBaseURL() for valid HTML memo links
  • Degrade to plain HTML on UID extraction failure
  • Copy /etc/group alongside /etc/passwd for user resolution
  • Use static:latest instead of static:latest-glibc
  • Align README Docker Compose docs with actual docker-compose.yml
  • Document /search --all command in README

…nd CI

- Add GitHub Actions workflow for multi-arch Docker build (amd64/arm64)
- Add Dockerfile with cross-compilation, non-root user, and layer caching
- Add docker-compose.yml with data persistence
- Support --all flag in /search to search all public memos
- Add clickable memo links in search results (HTML parse mode)
- Wrap search result content in <code> tag for easy copy
- Default memo visibility to PUBLIC
- Add normalizeBaseURL() for valid HTML memo links
- Degrade to plain HTML on UID extraction failure
- Copy /etc/group alongside /etc/passwd for user resolution
- Use static:latest instead of static:latest-glibc
- Align README Docker Compose docs with actual docker-compose.yml
- Document /search --all command in README
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8eca73f8-3374-4cf0-8def-943605a0685d

📥 Commits

Reviewing files that changed from the base of the PR and between fd83eb0 and d585f99.

📒 Files selected for processing (2)
  • Dockerfile
  • memogram.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • memogram.go

📝 Walkthrough

Walkthrough

This PR extends the memogram Telegram bot with containerization, deployment infrastructure, and an enhanced search feature. It adds a GitHub Actions workflow for multi-platform Docker builds, updates the Dockerfile for cross-compilation and rootless execution, introduces docker-compose configuration, documents data persistence, implements a --all flag for public memo search, and refactors result rendering with HTML formatting and URL normalization.

Changes

Container and Deployment Infrastructure

Layer / File(s) Summary
Container Build and Registry Pipeline
.github/workflows/build.yml
GitHub Actions workflow triggers on main pushes and dispatch, logs in to GHCR, generates image tags via metadata-action, builds for linux/amd64 and linux/arm64 with caching, and pushes to the registry.
Dockerfile Multi-Architecture and Rootless Runtime
Dockerfile
Dockerfile restructured for platform-aware cross-compilation using GOOS/GOARCH environment variables, creates non-root user and data directory, switches to cgr.dev/chainguard/static:latest, and copies the built binary into the runtime image.
Docker Compose and Data Persistence Configuration
docker-compose.yml, README.md
New docker-compose.yml defines memogram service with GHCR image, restart policy, data volume mount at /app/data, and environment variables; README updated with volume mapping examples and Chinese persistence notes; /search docs clarify --all behavior.

Search Enhancement Feature

Layer / File(s) Summary
Search Handler and Filter Logic
memogram.go
Search handler parses new --all flag and updates usage text; buildMemoSearchFilter now accepts a searchAll boolean and returns a visibility == "PUBLIC" constraint when true, otherwise applies user-specific filtering.
Search Filter Tests
memogram_search_filter_test.go
Existing tests updated to pass false to the new searchAll parameter; new test cases verify that passing true appends visibility == "PUBLIC" constraint with or without user context.

Result Rendering, Visibility, and URL Normalization

Layer / File(s) Summary
URL Normalization and Imports
memogram.go
New normalizeBaseURL utility strips dns: prefix, adds http:// when no scheme exists, and removes trailing slashes; html package imported to support HTML escaping.
Memo Visibility and Link Generation
memogram.go
Memo creation explicitly sets Visibility to PRIVATE; "content saved" and "memo updated" callbacks use normalizeBaseURL to generate consistent memo link URLs in Telegram messages.
Search Result Display with HTML Formatting
memogram.go, README.md
Search results rendered as HTML with escaped content, normalized base URLs for memo links, per-memo link generation via UID extraction, and a dedicated "No memos found" message; README clarifies /search <words> (user memos) versus /search --all <words> (public memos).
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: search improvements (--all flag, clickable links), Docker support (Dockerfile, docker-compose.yml), and CI (GitHub Actions workflow).
Description check ✅ Passed The description is directly related to the changeset, providing a detailed list of changes that align with the modified files and their summaries.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@aiastia

aiastia commented May 13, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
.github/workflows/build.yml (1)

45-54: ⚡ Quick win

Consider adding vulnerability scanning and provenance attestation.

The build step successfully creates and pushes multi-architecture images, but consider enhancing the pipeline with:

  • Image vulnerability scanning (e.g., using Trivy action) to catch security issues before publishing
  • SBOM generation and provenance attestation for supply chain security

These additions would improve the security posture of published images.

🔒 Example: Add Trivy scanning

Add this step after the build step:

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          format: 'sarif'
          output: 'trivy-results.sarif'

      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'

For provenance attestation, add to the build-push-action:

          provenance: true
          sbom: true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml around lines 45 - 54, Update the "Build and
push" workflow step (the job using docker/build-push-action@v6) to emit
provenance and SBOM by enabling the provenance and sbom options on the
build-push action and add a follow-up vulnerability scan step using the
aquasecurity/trivy-action (e.g., a new job/step named "Run Trivy vulnerability
scanner") that scans the pushed image (use the same image ref pattern as used
for tags) and outputs SARIF; also add an "Upload Trivy results to GitHub
Security" step (using github/codeql-action/upload-sarif@v3) that always runs to
upload the SARIF file.
Dockerfile (1)

20-20: 💤 Low value

Address the Trivy warning about placeholder secrets.

The static analysis tool flags BOT_TOKEN as a potential secret exposure. While this is a false positive (it's clearly a placeholder that will be overridden at runtime), you can eliminate the warning by removing the default value entirely, since BOT_TOKEN must be provided by the user anyway.

♻️ Proposed fix
-ENV BOT_TOKEN=your_telegram_bot_token
+# BOT_TOKEN must be set via docker run -e or docker-compose environment
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` at line 20, Remove the placeholder default value for BOT_TOKEN to
avoid Trivy false positives: locate the ENV declaration for BOT_TOKEN (the line
with "ENV BOT_TOKEN=your_telegram_bot_token") and either delete the line
entirely or change it to an empty ENV declaration (e.g., "ENV BOT_TOKEN") so
that BOT_TOKEN must be supplied at runtime rather than baked into the image.
memogram.go (2)

302-305: ⚡ Quick win

Consider extracting base URL resolution logic.

The pattern of checking instanceProfile.InstanceUrl and falling back to config.ServerAddr is repeated in three locations (memo save, callback handler, search handler). Extracting this to a helper method would improve maintainability.

♻️ Suggested refactor

Add a helper method:

func (s *Service) getBaseURL() string {
	baseURL := normalizeBaseURL(s.config.ServerAddr)
	if s.instanceProfile != nil && s.instanceProfile.InstanceUrl != "" {
		baseURL = normalizeBaseURL(s.instanceProfile.InstanceUrl)
	}
	return baseURL
}

Then replace all three occurrences with:

-	baseURL := normalizeBaseURL(s.config.ServerAddr)
-	if s.instanceProfile != nil && s.instanceProfile.InstanceUrl != "" {
-		baseURL = normalizeBaseURL(s.instanceProfile.InstanceUrl)
-	}
+	baseURL := s.getBaseURL()

Also applies to: 458-461, 532-535

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memogram.go` around lines 302 - 305, Extract the repeated base URL resolution
into a helper on Service: create a method (e.g., func (s *Service) getBaseURL()
string) that calls normalizeBaseURL(s.config.ServerAddr) and returns
normalizeBaseURL(s.instanceProfile.InstanceUrl) when s.instanceProfile != nil &&
s.instanceProfile.InstanceUrl != "" ; then replace the three inline blocks that
set baseURL (the ones using normalizeBaseURL(s.config.ServerAddr) and checking
s.instanceProfile.InstanceUrl) with a call to s.getBaseURL() in the memo save,
callback handler, and search handler locations to centralize the logic and avoid
duplication.

562-566: ⚡ Quick win

The filter syntax is correct, but consider extracting the visibility string to a constant.

The visibility == "PUBLIC" filter syntax matches the Memos API specification. However, to improve maintainability and prevent typos, consider defining a constant:

const visibilityPublicFilter = "PUBLIC"

Then use it in the filter expression:

return fmt.Sprintf("%s && visibility == %q", filter, visibilityPublicFilter)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memogram.go` around lines 562 - 566, Extract the literal "PUBLIC" used in the
filter inside buildMemoSearchFilter into a named constant (e.g.,
visibilityPublicFilter = "PUBLIC") and use that constant when composing the
filter string so the visibility token is centralized and less error-prone;
update buildMemoSearchFilter to reference visibilityPublicFilter when building
the "%s && visibility == %q" expression.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker-compose.yml`:
- Line 11: The SERVER_ADDR default of "dns:localhost:5230" is incorrect for
containers and will point to the container's own localhost; update the
docker-compose environment handling so SERVER_ADDR no longer defaults to
dns:localhost:5230 (remove the default or change it to a clearly invalid
placeholder) and document required values (e.g., host.docker.internal:5230 for
host, dns:memos:5230 for another container service, or the remote hostname) so
callers must explicitly set SERVER_ADDR (refer to the SERVER_ADDR variable and
the .env usage) and update the README to emphasize setting SERVER_ADDR in the
.env before running.

In `@Dockerfile`:
- Line 19: The Dockerfile currently sets a non-functional default ENV
SERVER_ADDR=dns:localhost:5230 which breaks containerized deployments; change
this so the image requires explicit configuration by replacing that default with
a clear placeholder (e.g., set ENV SERVER_ADDR=REQUIRED_SET_THIS_VALUE) or
remove the ENV assignment entirely so users must supply SERVER_ADDR at runtime,
and update any README/docs to mention that SERVER_ADDR must be provided; target
the ENV SERVER_ADDR entry in the Dockerfile to make this change.
- Line 13: The Dockerfile uses Alpine-specific "adduser -D -u 1000 memogram"
which is incompatible with the Wolfi-based Chainguard image; replace that RUN
instruction with the standard useradd invocation (create the home directory and
set the UID) by using useradd with -u 1000 and -m for memogram so the user is
created correctly on Wolfi.

In `@memogram.go`:
- Around line 543-552: The anchor branch for tgMessage constructs HTML without
escaping memo.Name and memoUID, risking XSS; update the successful-path
construction (where memoUID is set from ExtractMemoUIDFromName and tgMessage is
assigned) to HTML-escape both memo.Name and memoUID (e.g., via
html.EscapeString) before interpolating them into the href and link text so the
anchor uses the escaped memoUID and escaped memo.Name; keep the existing
escapedContent usage and preserve the error-path behavior that already escapes
memo.Name.

---

Nitpick comments:
In @.github/workflows/build.yml:
- Around line 45-54: Update the "Build and push" workflow step (the job using
docker/build-push-action@v6) to emit provenance and SBOM by enabling the
provenance and sbom options on the build-push action and add a follow-up
vulnerability scan step using the aquasecurity/trivy-action (e.g., a new
job/step named "Run Trivy vulnerability scanner") that scans the pushed image
(use the same image ref pattern as used for tags) and outputs SARIF; also add an
"Upload Trivy results to GitHub Security" step (using
github/codeql-action/upload-sarif@v3) that always runs to upload the SARIF file.

In `@Dockerfile`:
- Line 20: Remove the placeholder default value for BOT_TOKEN to avoid Trivy
false positives: locate the ENV declaration for BOT_TOKEN (the line with "ENV
BOT_TOKEN=your_telegram_bot_token") and either delete the line entirely or
change it to an empty ENV declaration (e.g., "ENV BOT_TOKEN") so that BOT_TOKEN
must be supplied at runtime rather than baked into the image.

In `@memogram.go`:
- Around line 302-305: Extract the repeated base URL resolution into a helper on
Service: create a method (e.g., func (s *Service) getBaseURL() string) that
calls normalizeBaseURL(s.config.ServerAddr) and returns
normalizeBaseURL(s.instanceProfile.InstanceUrl) when s.instanceProfile != nil &&
s.instanceProfile.InstanceUrl != "" ; then replace the three inline blocks that
set baseURL (the ones using normalizeBaseURL(s.config.ServerAddr) and checking
s.instanceProfile.InstanceUrl) with a call to s.getBaseURL() in the memo save,
callback handler, and search handler locations to centralize the logic and avoid
duplication.
- Around line 562-566: Extract the literal "PUBLIC" used in the filter inside
buildMemoSearchFilter into a named constant (e.g., visibilityPublicFilter =
"PUBLIC") and use that constant when composing the filter string so the
visibility token is centralized and less error-prone; update
buildMemoSearchFilter to reference visibilityPublicFilter when building the "%s
&& visibility == %q" expression.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c3b5c06-493e-40f8-a05d-197c10030c02

📥 Commits

Reviewing files that changed from the base of the PR and between ea70bc8 and fd83eb0.

📒 Files selected for processing (6)
  • .github/workflows/build.yml
  • Dockerfile
  • README.md
  • docker-compose.yml
  • memogram.go
  • memogram_search_filter_test.go

Comment thread docker-compose.yml
Comment thread Dockerfile Outdated
Comment thread Dockerfile
Comment thread memogram.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant