diff --git a/src/Client.Helpers.cs b/src/Client.Helpers.cs index cff35f6..4bfc62c 100644 --- a/src/Client.Helpers.cs +++ b/src/Client.Helpers.cs @@ -680,31 +680,31 @@ namespace WTelegram public async Task ReadHistory(InputPeer peer, int max_id = default) => peer is InputPeerChannel channel ? await this.Channels_ReadHistory(channel, max_id) : (await this.Messages_ReadHistory(peer, max_id)) != null; - private static readonly char[] QueryOrFragment = new[] { '?', '#' }; + private static readonly char[] UrlSeparator = new[] { '?', '#', '/' }; /// Return information about a chat/channel based on Invite Link /// Public link or Invite link, like https://t.me/+InviteHash, https://t.me/joinchat/InviteHash or https://t.me/channelname
Also work without https:// prefix /// to also join the chat/channel + /// previously collected chats, to prevent unnecessary ResolveUsername /// a Chat or Channel, possibly partial Channel information only (with flag ) - public async Task AnalyzeInviteLink(string url, bool join = false) + public async Task AnalyzeInviteLink(string url, bool join = false, IDictionary chats = null) { int start = url.IndexOf("//"); start = url.IndexOf('/', start + 2) + 1; - int end = url.IndexOfAny(QueryOrFragment, start); + int end = url.IndexOfAny(UrlSeparator, start); if (end == -1) end = url.Length; if (start == 0 || end == start) throw new ArgumentException("Invalid URL"); string hash; if (url[start] == '+') - hash = url[(start + 1)..end]; + hash = url[(start + 1)..end]; else if (string.Compare(url, start, "joinchat/", 0, 9, StringComparison.OrdinalIgnoreCase) == 0) - hash = url[(start + 9)..end]; + hash = url[(end + 1)..]; else { - var resolved = await this.Contacts_ResolveUsername(url[start..end]); - var chat = resolved.Chat; - if (join && chat != null) + var chat = await CachedOrResolveUsername(url[start..end], chats); + if (join && chat is Channel channel) { - var res = await this.Channels_JoinChannel((Channel)chat); + var res = await this.Channels_JoinChannel(channel); chat = res.Chats[chat.ID]; } return chat; @@ -750,32 +750,45 @@ namespace WTelegram /// Return chat and message details based on a message URL /// Message Link, like https://t.me/c/1234567890/1234 or https://t.me/channelname/1234 + /// previously collected chats, to prevent unnecessary ResolveUsername /// Structure containing the message, chat and user details /// If link is for private group (t.me/c/..), user must have joined the group - public async Task GetMessageByLink(string url) + public async Task GetMessageByLink(string url, IDictionary chats = null) { int start = url.IndexOf("//"); start = url.IndexOf('/', start + 2) + 1; int slash = url.IndexOf('/', start + 2); if (start == 0 || slash == -1) throw new ArgumentException("Invalid URL"); - int end = url.IndexOfAny(QueryOrFragment, slash + 1); + int end = url.IndexOfAny(UrlSeparator, slash + 1); if (end == -1) end = url.Length; + int msgId = int.Parse(url[(slash + 1)..end]); ChatBase chat; if (url[start] is 'c' or 'C' && url[start + 1] == '/') { long chatId = long.Parse(url[(start + 2)..slash]); - var chats = await this.Channels_GetChannels(new InputChannel(chatId, 0)); - if (!chats.chats.TryGetValue(chatId, out chat)) + var mc = await this.Channels_GetChannels(new InputChannel(chatId, 0)); + if (!mc.chats.TryGetValue(chatId, out chat)) throw new WTException($"Channel {chatId} not found"); } else + chat = await CachedOrResolveUsername(url[start..slash], chats); + if (chat is not Channel channel) throw new WTException($"URL does not identify a valid Channel"); + return await this.Channels_GetMessages(channel, msgId) as Messages_ChannelMessages; + } + + private async Task CachedOrResolveUsername(string username, IDictionary chats = null) + { + if (chats == null) + return (await this.Contacts_ResolveUsername(username)).Chat; + ChatBase chat; + lock (chats) + chat = chats.Values.OfType().FirstOrDefault(ch => ch.ActiveUsernames.Contains(username, StringComparer.OrdinalIgnoreCase)); + if (chat == null) { - var resolved = await this.Contacts_ResolveUsername(url[start..slash]); - chat = resolved.Chat; - if (chat is null) throw new WTException($"@{url[start..slash]} is not a Chat/Channel"); + chat = (await this.Contacts_ResolveUsername(username)).Chat; + if (chat != null) lock (chats) chats[chat.ID] = chat; } - int msgId = int.Parse(url[(slash + 1)..end]); - return await this.Channels_GetMessages((Channel)chat, msgId) as Messages_ChannelMessages; + return chat; } #endregion } diff --git a/src/TL.Helpers.cs b/src/TL.Helpers.cs index b108333..8e99634 100644 --- a/src/TL.Helpers.cs +++ b/src/TL.Helpers.cs @@ -223,12 +223,24 @@ namespace TL { public override bool IsActive => (flags & Flags.left) == 0; public override bool IsChannel => (flags & Flags.broadcast) != 0; - public override string MainUsername => username ?? usernames?.FirstOrDefault(u => u.flags.HasFlag(Username.Flags.active))?.username; + public override string MainUsername => username ?? usernames?.FirstOrDefault(un => un.flags.HasFlag(Username.Flags.active))?.username; public override ChatPhoto Photo => photo; public override bool IsBanned(ChatBannedRights.Flags flags = 0) => ((banned_rights?.flags ?? 0) & flags) != 0 || ((default_banned_rights?.flags ?? 0) & flags) != 0; public override InputPeer ToInputPeer() => new InputPeerChannel(id, access_hash); public static implicit operator InputChannel(Channel channel) => new(channel.id, channel.access_hash); public override string ToString() => (flags.HasFlag(Flags.broadcast) ? "Channel " : "Group ") + (MainUsername is string uname ? '@' + uname : $"\"{title}\""); + public IEnumerable ActiveUsernames + { + get + { + if (username != null) + yield return username; + if (usernames != null) + foreach (var un in usernames) + if (un.flags.HasFlag(Username.Flags.active)) + yield return un.username; + } + } } partial class ChannelForbidden {