Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions src/HuaJiBot.NET.Adapter.Telegram/CommandReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace HuaJiBot.NET.Adapter.Telegram;
/// <summary>
/// 指令读取
/// </summary>
internal class TelegramCommandReader(BotService service, Message msg) : CommonCommandReader
internal class TelegramCommandReader(TelegramAdapter service, Message msg) : CommonCommandReader
{
public override IEnumerable<ReaderEntity> Msg
{
Expand Down Expand Up @@ -37,10 +37,16 @@ IEnumerable<ReaderEntity> Parse()
// Get the text content (from Text or Caption)
var text = msg.Text ?? msg.Caption ?? string.Empty;

// "/日程" has no bot_command entity because Telegram commands are ASCII only.
var lastIndex =
text.StartsWith('/')
&& msg.Entities?.FirstOrDefault() is not { Type: MessageEntityType.BotCommand, Offset: 0 }
? 1
: 0;

// Parse entities if present
if (msg.Entities is { Length: > 0 } entities)
{
var lastIndex = 0;

foreach (var entity in entities)
{
Expand All @@ -59,9 +65,21 @@ IEnumerable<ReaderEntity> Parse()
{
case MessageEntityType.Mention: // @username
var mentionText = text.Substring(entity.Offset, entity.Length);
// For @username mentions, we don't have the user ID directly
// We'll yield it as text or try to extract from the message
yield return mentionText;
if (IsThisBot(mentionText[1..]))
yield return new ReaderAt(service.BotUser!.Id.ToString(), mentionText);
else
yield return mentionText;
break;

case MessageEntityType.BotCommand: // /command or /command@bot
var command = text.Substring(entity.Offset + 1, entity.Length - 1);
if (command.IndexOf('@') is var at and >= 0)
{
if (!IsThisBot(command[(at + 1)..]))
yield break;
command = command[..at];
}
yield return service.ResolveCommandAlias(command) ?? command;
break;

case MessageEntityType.TextMention: // mention with user object
Expand Down Expand Up @@ -97,10 +115,10 @@ IEnumerable<ReaderEntity> Parse()
}
}
}
else if (!string.IsNullOrWhiteSpace(text))
else if (!string.IsNullOrWhiteSpace(text[lastIndex..]))
{
// No entities, just yield the whole text
yield return text;
yield return text[lastIndex..];
}

// Handle other message types that aren't text-based
Expand All @@ -119,4 +137,7 @@ IEnumerable<ReaderEntity> Parse()
}
}
}

private bool IsThisBot(string username) =>
string.Equals(username, service.BotUser?.Username, StringComparison.OrdinalIgnoreCase);
}
34 changes: 26 additions & 8 deletions src/HuaJiBot.NET.Adapter.Telegram/TelegramAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class TelegramAdapter(string botToken, HttpClient? httpClient = null) : B

public override required ILogger Logger { get; init; }

private User? _botUser;
internal User? BotUser { get; set; }

protected override void ReconnectCore()
{
Expand All @@ -41,20 +41,20 @@ protected override async Task SetupServiceAsyncCore()
{
try
{
_botUser = await _botClient.GetMe(_cancellationTokenSource.Token);
BotUser = await _botClient.GetMe(_cancellationTokenSource.Token);
await _botClient.DeleteWebhook(); // you may comment this line if you find it unnecessary
await _botClient.DropPendingUpdates(); // you may comment this line if you find it unnecessary

Log(
$"Telegram bot started: @{_botUser.Username} ({_botUser.FirstName}) {_botUser.CanReadAllGroupMessages}"
$"Telegram bot started: @{BotUser.Username} ({BotUser.FirstName}) {BotUser.CanReadAllGroupMessages}"
);
Events.CallOnBotLogin(
new()
{
Service = this,
Accounts = [_botUser.Id.ToString()],
Accounts = [BotUser.Id.ToString()],
ClientName = "Telegram Bot",
ClientVersion = _botUser.Username,
ClientVersion = BotUser.Username,
}
);
// Subscribe to events
Expand Down Expand Up @@ -82,7 +82,7 @@ protected override async Task SetupServiceAsyncCore()
}
}

public override string[] AllRobots => _botUser != null ? [_botUser.Id.ToString()] : [];
public override string[] AllRobots => BotUser != null ? [BotUser.Id.ToString()] : [];

internal static ReplyParameters? ToReplyParameters(string? messageId) =>
int.TryParse(messageId, out var id) ? new ReplyParameters { MessageId = id } : null;
Expand Down Expand Up @@ -252,6 +252,24 @@ async Task<Message> SendAsync(ParseMode parseMode, Func<Post, int, string> compo
}
}

protected override void OnCommandsRegistered(
IEnumerable<(string Alias, string Description)> commands
)
{
var menu = commands.Select(x => new BotCommand(x.Alias, x.Description)).ToArray();
_ = Task.Run(async () =>
{
try
{
await _botClient.SetMyCommands(menu, cancellationToken: _cancellationTokenSource.Token);
}
catch (Exception ex)
{
LogError("Failed to set the bot command menu", ex);
}
});
}

// Telegram re-encodes photos as JPEG, which turns the card's transparent corners white.
internal static async Task<Stream> OpenPhotoAsync(string path)
{
Expand Down Expand Up @@ -450,7 +468,7 @@ private async Task HandleMessageAsync(Message message, UpdateType type)
)
{
Service = this,
RobotId = _botUser?.Id.ToString(),
RobotId = BotUser?.Id.ToString(),
GroupId = null, // Private chats don't have group ID
SenderId = userId,
MessageId = message.MessageId.ToString(),
Expand Down Expand Up @@ -491,7 +509,7 @@ await _botClient.GetChat(
)
{
Service = this,
RobotId = _botUser?.Id.ToString(),
RobotId = BotUser?.Id.ToString(),
GroupId = groupTopic,
SenderId = userId,
SenderMemberCard = userName,
Expand Down
4 changes: 2 additions & 2 deletions src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private async Task<bool> EnterCooldownAsync(GroupMessageEventArgs e)
return true;
}

[Command("最近日程", "查看最近一次日程详细信息")]
[Command("最近日程", "查看最近一次日程详细信息", Alias = "next")]
// ReSharper disable once UnusedMember.Global
public async Task CalendarCommandAsync(GroupMessageEventArgs e)
{
Expand All @@ -167,7 +167,7 @@ public async Task CalendarCommandAsync(GroupMessageEventArgs e)
await e.Reply(output);
}

[Command("日程", "查看近期日程")]
[Command("日程", "查看近期日程", Alias = "schedule")]
// ReSharper disable once UnusedMember.Global
public async Task CalendarCommandAsync(
[CommandArgumentString("时间")] string? content,
Expand Down
2 changes: 1 addition & 1 deletion src/HuaJiBot.NET.Plugin.DailySummary/PluginMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ [new ChatMessage(ChatRole.User, userMessage)],
: response.Text;
}

[Command("总结", "总结群聊记录,可选今天、昨天、前天,默认昨天")]
[Command("总结", "总结群聊记录,可选今天、昨天、前天,默认昨天", Alias = "summary")]
// ReSharper disable once UnusedMember.Global
public async Task SummaryCommand(
GroupMessageEventArgs e,
Expand Down
2 changes: 1 addition & 1 deletion src/HuaJiBot.NET.Plugin.MessageBridge/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ConcurrentBag<GetPlayerListResponsePacket> data
)
> _requestQueue = new();

[Command("查询", "查询MC服务器状态")]
[Command("查询", "查询MC服务器状态", Alias = "mc")]
// ReSharper disable once UnusedMember.Local
private async Task QueryAsync(GroupMessageEventArgs e)
{
Expand Down
2 changes: 1 addition & 1 deletion src/HuaJiBot.NET.Plugin.MessageBridge/PluginMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ public string EnumToAttributeName<T>(T value)
return value.ToString();
}

[Command("事件", "开关事件")]
[Command("事件", "开关事件", Alias = "events")]
// ReSharper disable once UnusedMember.Global
public void EventControlCommand(
[CommandArgumentEnum<ClientEventType>("类型")] ClientEventType? typeOptional,
Expand Down
2 changes: 1 addition & 1 deletion src/HuaJiBot.NET.Plugin.RepairTeam/PluginMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ private void OnMessageReceived(object? sender, string msg)
}
}

[Command("工单", "查看进行中的维修工单")]
[Command("工单", "查看进行中的维修工单", Alias = "tickets")]
// ReSharper disable once UnusedMember.Local
private async Task TicketsCommandAsync(GroupMessageEventArgs e)
{
Expand Down
9 changes: 8 additions & 1 deletion src/HuaJiBot.NET.SourceGenerator/CommandSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ SemanticModel semanticModel

var key = GetStringLiteralValue(keyArg.Expression);
var description = GetStringLiteralValue(descArg.Expression);
var aliasArg = commandAttr.ArgumentList.Arguments.FirstOrDefault(a =>
a.NameEquals?.Name.Identifier.ValueText == "Alias"
);
var alias = aliasArg is null
? "null"
: $"\"{GetStringLiteralValue(aliasArg.Expression)}\"";

sb.AppendLine($" yield return new CommandInfo(");
sb.AppendLine($" \"{key}\",");
Expand Down Expand Up @@ -261,7 +267,8 @@ SemanticModel semanticModel
GenerateArgumentInfo(param, semanticModel, sb);
}

sb.AppendLine($" }}");
sb.AppendLine($" }},");
sb.AppendLine($" {alias}");
sb.AppendLine($" );");
sb.AppendLine();
}
Expand Down
27 changes: 25 additions & 2 deletions src/HuaJiBot.NET.UnitTest/CommandMatchTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ protected override void Unload() { }

private readonly RecordingAdapter _adapter = new();

private GroupMessageEventArgs Message(string text) =>
private GroupMessageEventArgs Message(string text, RecordingAdapter? adapter = null) =>
new(() => new DefaultCommandReader([text]), () => ValueTask.FromResult("group"))
{
Service = _adapter,
Service = adapter ?? _adapter,
MessageId = "1",
GroupId = "group",
SenderId = "user",
Expand Down Expand Up @@ -65,6 +65,29 @@ public void Help_MatchesWholeWord(string text, bool expected)
Assert.That(service.ProcessHelp(Message(text)), Is.EqualTo(expected));
}

[Test]
public async Task Help_OnQq_RepliesWithTheSameText()
{
RecordingAdapter adapter = new();
var service = new CommandService(adapter);
service.SetupCommands(new SchedulePlugin());

service.ProcessHelp(Message("帮助", adapter));
for (var i = 0; i < 50 && adapter.Sends.Count == 0; i++)
await Task.Delay(20);

Assert.That(
adapter.Sends.Single().Messages,
Is.EqualTo(
new SendingMessageBase[]
{
new ReplyMessage("1"),
new TextMessage($"可用命令:{Environment.NewLine}日程 <string> {Environment.NewLine} {Environment.NewLine}"),
}
)
);
}

[Test]
public void RestOfMessage_WithReplyInTheMiddle_HasNoRecordText()
{
Expand Down
7 changes: 7 additions & 0 deletions src/HuaJiBot.NET.UnitTest/Doubles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ params SendingMessageBase[] messages
Sends.Add((targetGroup, sent));
return ["42"];
}

public override Task<string[]> FeedbackAt(
string? robotId,
string targetGroup,
string msgId,
string text
) => SendGroupMessageAsync(robotId, targetGroup, new ReplyMessage(msgId), new TextMessage(text));
}

internal sealed class RichAdapter : TestAdapter
Expand Down
Loading
Loading