Skip to content

feat(display): 添加URL显示截断服务#246

Merged
ModerRAS merged 8 commits intomasterfrom
issue-24-url-shortening
Apr 22, 2026
Merged

feat(display): 添加URL显示截断服务#246
ModerRAS merged 8 commits intomasterfrom
issue-24-url-shortening

Conversation

@ModerRAS
Copy link
Copy Markdown
Owner

@ModerRAS ModerRAS commented Mar 19, 2026

实现Issue #24 - 二维码链接过长造成的刷屏问题

新增功能

UrlDisplayService

  • : 截断长URL显示(默认80字符)
  • : 自动格式化文本中的URL
  • : 检测纯URL消息
  • : 获取消息预览(限制行数)

显示规则

  1. URL长度阈值

    • 短URL(< 80字符):完整显示
    • 长URL(> 80字符):显示域名+路径截断
  2. 截断格式

文件变更

    • 新增URL显示服务

使用场景

  1. 搜索结果显示时自动截断长URL
  2. 消息导入时处理长URL显示
  3. 消息发送时可选截断

Fixes #24

Summary by CodeRabbit

  • New Features

    • QR code results now display as formatted, clickable markdown links with shortened URL labels instead of raw strings.
    • Added link preview suppression controls for improved message formatting flexibility.
  • Tests

    • Added comprehensive test coverage for URL display functionality, including URL validation, markdown link formatting, and label truncation.

Copilot AI and others added 4 commits March 3, 2026 08:33
…e bundle support

- Add EnableLocalBotAPI, TelegramBotApiId, TelegramBotApiHash, LocalBotApiPort to Config/Env
- Auto-set BaseUrl and IsLocalAPI when EnableLocalBotAPI is true
- Launch telegram-bot-api.exe as a ChildProcessManager-managed process in GeneralBootstrap
- Add conditional Content entry for telegram-bot-api.exe in .csproj
- Build telegram-bot-api from source via cmake+vcpkg in GitHub Actions push workflow

Co-authored-by: ModerRAS <28183976+ModerRAS@users.noreply.github.com>
- 新增UrlDisplayService处理长URL显示
- 支持URL截断(默认80字符)
- 自动提取域名和路径进行显示优化
- 支持纯URL消息检测
- 支持消息预览截断

用于解决长URL(如二维码链接)在搜索结果和消息中造成的刷屏问题

Fixes #24
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

This change adds a new UrlDisplayService to format long QR code links as shortened Markdown links, addressing message spam from long URLs and Telegram's automatic link preview. The service is integrated into AutoQRController and SendMessageService to support conditional link formatting and link preview suppression.

Changes

Cohort / File(s) Summary
URL Display Service
TelegramSearchBot/Service/Common/UrlDisplayService.cs, TelegramSearchBot.Test/Service/Common/UrlDisplayServiceTests.cs
New service for detecting URL-only messages and formatting them as compact Markdown links with display labels derived from URL host and path segments. Tests validate URL detection, formatting, and label truncation behavior.
Message Sending Interface & Implementation
TelegramSearchBot/Interface/ISendMessageService.cs, TelegramSearchBot/Service/BotAPI/SendMessageService.cs
Extended fallback send methods with optional disableLinkPreview and plainTextFallbackOverride parameters to support link preview suppression and custom fallback content during message formatting failures.
QR Controller Integration
TelegramSearchBot/Controller/AI/QR/AutoQRController.cs
Integrated UrlDisplayService dependency; modified to conditionally format QR strings as Markdown links when URL-only, sending with link preview disabled, otherwise sending raw QR string.

Sequence Diagram

sequenceDiagram
    participant Controller as AutoQRController
    participant Service as UrlDisplayService
    participant SendMsg as SendMessageService
    participant Bot as Telegram Bot API

    Controller->>Service: IsUrlOnlyMessage(qrStr)
    activate Service
    Service-->>Controller: true/false
    deactivate Service

    alt URL Only Message
        Controller->>Service: TryFormatUrlOnlyMessage(qrStr)
        activate Service
        Service-->>Controller: markdownLink
        deactivate Service
        Controller->>SendMsg: TrySendMessageWithFallback(..., disableLinkPreview=true)
        activate SendMsg
        SendMsg->>Bot: SendMessage with LinkPreviewOptions.Disabled
        Bot-->>SendMsg: success
        SendMsg-->>Controller: success
        deactivate SendMsg
    else Not URL Only
        Controller->>SendMsg: SendMessage(qrStr)
        activate SendMsg
        SendMsg->>Bot: SendMessage(raw)
        Bot-->>SendMsg: success
        SendMsg-->>Controller: success
        deactivate SendMsg
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A QR string hops through the digital air,
No longer too long to cause Telegram despair—
A shortened display with the full link concealed,
The spam is now tamed, the rabbit's revealed! 🔗✨

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main feature: adding a URL display truncation service, which matches the primary changes introducing UrlDisplayService to address the long QR code link issue.
Linked Issues check ✅ Passed The PR implements Issue #24 by introducing UrlDisplayService with URL truncation, hypertext formatting, and link preview prevention - directly addressing the QR code long link issue described in the linked issue.
Out of Scope Changes check ✅ Passed All code changes are in scope: new UrlDisplayService for URL formatting [#24], test coverage, interface updates, and integration into AutoQRController for the intended use case.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-24-url-shortening

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 19, 2026

🔍 PR检查报告

📋 检查概览

🧪 测试结果

平台 状态 详情
Ubuntu 🔴 失败 测试结果不可用
Windows 🔴 失败 测试结果不可用

📊 代码质量

  • ✅ 代码格式化检查
  • ✅ 安全漏洞扫描
  • ✅ 依赖包分析
  • ✅ 代码覆盖率收集

📁 测试产物

  • 测试结果文件已上传为artifacts
  • 代码覆盖率已上传到Codecov

🔗 相关链接


此报告由GitHub Actions自动生成

Test User and others added 4 commits March 19, 2026 13:06
Fix whitespace formatting issues detected by CI on Windows
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
TelegramSearchBot.Test/Service/Common/UrlDisplayServiceTests.cs (1)

5-36: LGTM — consider adding negative-scheme coverage.

If you adopt the http(s) allowlist suggested in UrlDisplayService, add a [Theory] covering javascript:alert(1), file:///etc/passwd, and data:text/html,... returning false from IsUrlOnlyMessage to lock the behavior in.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot.Test/Service/Common/UrlDisplayServiceTests.cs` around lines
5 - 36, Add a parameterized test for UrlDisplayService.IsUrlOnlyMessage that
asserts it returns false for disallowed schemes; specifically add a [Theory]
(with InlineData for "javascript:alert(1)", "file:///etc/passwd",
"data:text/html,<svg/onload=alert(1)>") that calls _service.IsUrlOnlyMessage on
each input and asserts False, so the http(s) allowlist behavior in
UrlDisplayService.IsUrlOnlyMessage is locked in by test.
TelegramSearchBot/Controller/AI/QR/AutoQRController.cs (1)

78-95: LGTM on the integration, one small note.

The conditional formatting + plainTextFallbackOverride: qrStr + disableLinkPreview: true is a nice way to keep the raw URL recoverable if Markdown/HTML parsing fails. Two minor remarks:

  • initialContentForNewMessage: qrStr (positional arg 7) is ignored by the current TrySendMessageWithFallback implementation for the non-edit path; passing qrStr here is harmless but can be misleading to future readers.
  • Consider unit-testing this controller path with a mocked ISendMessageService to assert both branches (URL-only vs. non-URL) produce the intended calls, since this is the user-visible behavior fixing #24.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot/Controller/AI/QR/AutoQRController.cs` around lines 78 - 95,
The call to _sendMessageService.TrySendMessageWithFallback passes qrStr as the
positional initialContentForNewMessage (arg 7) even though
TrySendMessageWithFallback currently ignores that parameter on the non-edit
path; remove the misleading positional arg (pass only named parameters or update
TrySendMessageWithFallback to accept and use an initialContentForNewMessage for
new messages) and add unit tests mocking ISendMessageService to assert both
branches of _urlDisplayService.TryFormatUrlOnlyMessage produce the expected
calls to TrySendMessageWithFallback (with plainTextFallbackOverride and
disableLinkPreview) and to SendMessage respectively.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@TelegramSearchBot/Service/Common/UrlDisplayService.cs`:
- Line 9: The DefaultMaxDisplayLength constant in UrlDisplayService
(DefaultMaxDisplayLength = 60) conflicts with the PR description that specifies
an 80-character default; update the constant to 80 to match the PR/issue or, if
the intended default is 60, update the PR description/docs to state 60 —
typically change the value of DefaultMaxDisplayLength in UrlDisplayService to 80
and run related tests/usage checks to ensure consistent behavior across methods
that reference DefaultMaxDisplayLength.
- Around line 17-26: TryFormatUrlOnlyMessage currently emits the angle-bracket
link form which breaks if the URL contains '>' or whitespace; update
TryFormatUrlOnlyMessage to use the non-bracketed markdown form instead
(markdownText = $"[打开链接:{label}]({url})") after verifying the url returned by
TryGetStandaloneUrl contains no unencoded ')' (or other raw whitespace/'>') — if
such characters are present, either reject the URL (return false) or
re-encode/escape them before emitting. Keep label generation via
BuildDisplayLabel and EscapeMarkdownLinkText unchanged but add the
validation/guard against raw '>'/whitespace in the url inside
TryFormatUrlOnlyMessage.
- Around line 60-73: The TryGetStandaloneUrl method currently accepts any
absolute URI; restrict it to only allow http and https schemes by changing the
Uri.TryCreate call to capture the resulting Uri (e.g., Uri.TryCreate(trimmed,
UriKind.Absolute, out var uri)) and then verify uri.Scheme equals
Uri.UriSchemeHttp or Uri.UriSchemeHttps (case-insensitive) before returning
true; leave url = trimmed only when the scheme check passes and return false
otherwise so unsafe schemes like javascript:, file:, data:, mailto:, ftp:, etc.
are rejected.

---

Nitpick comments:
In `@TelegramSearchBot.Test/Service/Common/UrlDisplayServiceTests.cs`:
- Around line 5-36: Add a parameterized test for
UrlDisplayService.IsUrlOnlyMessage that asserts it returns false for disallowed
schemes; specifically add a [Theory] (with InlineData for "javascript:alert(1)",
"file:///etc/passwd", "data:text/html,<svg/onload=alert(1)>") that calls
_service.IsUrlOnlyMessage on each input and asserts False, so the http(s)
allowlist behavior in UrlDisplayService.IsUrlOnlyMessage is locked in by test.

In `@TelegramSearchBot/Controller/AI/QR/AutoQRController.cs`:
- Around line 78-95: The call to _sendMessageService.TrySendMessageWithFallback
passes qrStr as the positional initialContentForNewMessage (arg 7) even though
TrySendMessageWithFallback currently ignores that parameter on the non-edit
path; remove the misleading positional arg (pass only named parameters or update
TrySendMessageWithFallback to accept and use an initialContentForNewMessage for
new messages) and add unit tests mocking ISendMessageService to assert both
branches of _urlDisplayService.TryFormatUrlOnlyMessage produce the expected
calls to TrySendMessageWithFallback (with plainTextFallbackOverride and
disableLinkPreview) and to SendMessage respectively.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56767db2-dfdc-4221-8176-753bc2b3314b

📥 Commits

Reviewing files that changed from the base of the PR and between 96d1ac2 and a9034ea.

📒 Files selected for processing (5)
  • TelegramSearchBot.Test/Service/Common/UrlDisplayServiceTests.cs
  • TelegramSearchBot/Controller/AI/QR/AutoQRController.cs
  • TelegramSearchBot/Interface/ISendMessageService.cs
  • TelegramSearchBot/Service/BotAPI/SendMessageService.cs
  • TelegramSearchBot/Service/Common/UrlDisplayService.cs

namespace TelegramSearchBot.Service.Common {
[Injectable(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient)]
public class UrlDisplayService : IService {
private const int DefaultMaxDisplayLength = 60;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Default truncation length disagrees with PR description.

The PR description (and linked issue context) states the default truncation is 80 characters, but DefaultMaxDisplayLength = 60. Please reconcile — either update the constant to 80 or adjust the PR description / docs to reflect 60.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot/Service/Common/UrlDisplayService.cs` at line 9, The
DefaultMaxDisplayLength constant in UrlDisplayService (DefaultMaxDisplayLength =
60) conflicts with the PR description that specifies an 80-character default;
update the constant to 80 to match the PR/issue or, if the intended default is
60, update the PR description/docs to state 60 — typically change the value of
DefaultMaxDisplayLength in UrlDisplayService to 80 and run related tests/usage
checks to ensure consistent behavior across methods that reference
DefaultMaxDisplayLength.

Comment on lines +17 to +26
public bool TryFormatUrlOnlyMessage(string text, out string markdownText) {
markdownText = string.Empty;
if (!TryGetStandaloneUrl(text, out var url)) {
return false;
}

var label = EscapeMarkdownLinkText(BuildDisplayLabel(url));
markdownText = $"[打开链接:{label}](<{url}>)";
return true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

URL containing > or whitespace would break the [label](<url>) output.

The angle-bracket link form terminates at the first >. If the QR payload is a URI that contains %3E decoded somewhere upstream, or any character that closes the bracketed form (>, newline), the resulting Markdown is malformed. Since TryGetStandaloneUrl returns the raw trimmed text without re-encoding, consider either using the non-bracketed form [label](url) (URLs don’t contain ) unencoded) or escaping/rejecting URLs that contain >/whitespace. Minor because real http(s) URLs rarely contain raw >, but worth a defensive check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot/Service/Common/UrlDisplayService.cs` around lines 17 - 26,
TryFormatUrlOnlyMessage currently emits the angle-bracket link form which breaks
if the URL contains '>' or whitespace; update TryFormatUrlOnlyMessage to use the
non-bracketed markdown form instead (markdownText = $"[打开链接:{label}]({url})")
after verifying the url returned by TryGetStandaloneUrl contains no unencoded
')' (or other raw whitespace/'>') — if such characters are present, either
reject the URL (return false) or re-encode/escape them before emitting. Keep
label generation via BuildDisplayLabel and EscapeMarkdownLinkText unchanged but
add the validation/guard against raw '>'/whitespace in the url inside
TryFormatUrlOnlyMessage.

Comment on lines +60 to +73
private static bool TryGetStandaloneUrl(string text, out string url) {
url = string.Empty;
if (string.IsNullOrWhiteSpace(text)) {
return false;
}

var trimmed = text.Trim();
if (!Uri.TryCreate(trimmed, UriKind.Absolute, out _)) {
return false;
}

url = trimmed;
return true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restrict TryGetStandaloneUrl to safe URL schemes.

Uri.TryCreate(..., UriKind.Absolute, ...) accepts any absolute URI, including javascript:, data:, file:, ftp:, mailto:, etc. Since a QR code payload is untrusted input and the output of this method is rendered as a clickable Markdown link sent to the chat, any non-http(s) scheme (e.g., file:///etc/passwd, javascript:...) will become a one-tap link for every chat member. Consider whitelisting http/https only.

🛡️ Proposed fix
-            var trimmed = text.Trim();
-            if (!Uri.TryCreate(trimmed, UriKind.Absolute, out _)) {
-                return false;
-            }
-
-            url = trimmed;
-            return true;
+            var trimmed = text.Trim();
+            if (!Uri.TryCreate(trimmed, UriKind.Absolute, out var parsed)) {
+                return false;
+            }
+            if (parsed.Scheme != Uri.UriSchemeHttp && parsed.Scheme != Uri.UriSchemeHttps) {
+                return false;
+            }
+
+            url = trimmed;
+            return true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static bool TryGetStandaloneUrl(string text, out string url) {
url = string.Empty;
if (string.IsNullOrWhiteSpace(text)) {
return false;
}
var trimmed = text.Trim();
if (!Uri.TryCreate(trimmed, UriKind.Absolute, out _)) {
return false;
}
url = trimmed;
return true;
}
private static bool TryGetStandaloneUrl(string text, out string url) {
url = string.Empty;
if (string.IsNullOrWhiteSpace(text)) {
return false;
}
var trimmed = text.Trim();
if (!Uri.TryCreate(trimmed, UriKind.Absolute, out var parsed)) {
return false;
}
if (parsed.Scheme != Uri.UriSchemeHttp && parsed.Scheme != Uri.UriSchemeHttps) {
return false;
}
url = trimmed;
return true;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot/Service/Common/UrlDisplayService.cs` around lines 60 - 73,
The TryGetStandaloneUrl method currently accepts any absolute URI; restrict it
to only allow http and https schemes by changing the Uri.TryCreate call to
capture the resulting Uri (e.g., Uri.TryCreate(trimmed, UriKind.Absolute, out
var uri)) and then verify uri.Scheme equals Uri.UriSchemeHttp or
Uri.UriSchemeHttps (case-insensitive) before returning true; leave url = trimmed
only when the scheme check passes and return false otherwise so unsafe schemes
like javascript:, file:, data:, mailto:, ftp:, etc. are rejected.

@ModerRAS ModerRAS merged commit f34f4ac into master Apr 22, 2026
12 checks passed
@ModerRAS ModerRAS deleted the issue-24-url-shortening branch April 22, 2026 01:45
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.

二维码链接过长造成的刷屏问题

2 participants