-
Notifications
You must be signed in to change notification settings - Fork 2
feat(display): 添加URL显示截断服务 #246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9c45b75
fd02e16
33c89b3
ca9e4ce
0b2d691
7806192
e85ab01
a9034ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| using TelegramSearchBot.Service.Common; | ||
| using Xunit; | ||
|
|
||
| namespace TelegramSearchBot.Test.Service.Common { | ||
| public class UrlDisplayServiceTests { | ||
| private readonly UrlDisplayService _service = new(); | ||
|
|
||
| [Fact] | ||
| public void IsUrlOnlyMessage_WithAbsoluteUrl_ReturnsTrue() { | ||
| Assert.True(_service.IsUrlOnlyMessage(" https://example.com/path?q=1 ")); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void IsUrlOnlyMessage_WithMixedText_ReturnsFalse() { | ||
| Assert.False(_service.IsUrlOnlyMessage("QR result: https://example.com/path?q=1")); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TryFormatUrlOnlyMessage_WithLongUrl_ReturnsMarkdownLink() { | ||
| var url = "https://example.com/some/really/long/path/to/resource-name?query=1&another=2"; | ||
|
|
||
| var result = _service.TryFormatUrlOnlyMessage(url, out var markdownText); | ||
|
|
||
| Assert.True(result); | ||
| Assert.Equal("[打开链接:example.com/resource-name?...](<https://example.com/some/really/long/path/to/resource-name?query=1&another=2>)", markdownText); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void BuildDisplayLabel_WithInvalidUrl_TruncatesRawText() { | ||
| var rawText = "not-a-valid-url-but-still-a-very-long-value-that-needs-shortening"; | ||
|
|
||
| var label = _service.BuildDisplayLabel(rawText, 20); | ||
|
|
||
| Assert.Equal("not-a-valid-url-b...", label); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,94 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Linq; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using TelegramSearchBot.Attributes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using TelegramSearchBot.Interface; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace TelegramSearchBot.Service.Common { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Injectable(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class UrlDisplayService : IService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private const int DefaultMaxDisplayLength = 60; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public string ServiceName => nameof(UrlDisplayService); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public bool IsUrlOnlyMessage(string text) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return TryGetStandaloneUrl(text, out _); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. URL containing The angle-bracket link form terminates at the first 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public string BuildDisplayLabel(string url, int maxLength = DefaultMaxDisplayLength) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (string.IsNullOrWhiteSpace(url)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return string.Empty; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var trimmedUrl = url.Trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!Uri.TryCreate(trimmedUrl, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Host)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Truncate(trimmedUrl, maxLength); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var builder = uri.Host; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var lastSegment = uri.Segments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Select(static segment => segment.Trim('/')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .LastOrDefault(static segment => !string.IsNullOrWhiteSpace(segment)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!string.IsNullOrWhiteSpace(lastSegment)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder += "/" + Uri.UnescapeDataString(lastSegment); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (!string.IsNullOrWhiteSpace(uri.AbsolutePath) && uri.AbsolutePath != "/") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder += uri.AbsolutePath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!string.IsNullOrWhiteSpace(uri.Query)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder += "?..."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!string.IsNullOrWhiteSpace(uri.Fragment)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder += "#..."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Truncate(builder, maxLength); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+60
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restrict
🛡️ 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static string EscapeMarkdownLinkText(string text) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return text | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Replace("\\", "\\\\", StringComparison.Ordinal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Replace("[", "\\[", StringComparison.Ordinal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Replace("]", "\\]", StringComparison.Ordinal); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static string Truncate(string value, int maxLength) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value.Length <= maxLength) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (maxLength <= 3) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return value.Substring(0, maxLength); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return value.Substring(0, maxLength - 3) + "..."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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