From c459c88c62f8c0508d799a8d163628c27feb03c6 Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 21:41:57 +0300 Subject: [PATCH 01/11] GetRegistrationDateAsync --- VkNetExtensions/VkNetExtensions.cs | 47 ++++++++++++++++++++++++++ VkNetExtensions/VkNetExtensions.csproj | 6 +++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 VkNetExtensions/VkNetExtensions.cs diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs new file mode 100644 index 0000000..fb3350b --- /dev/null +++ b/VkNetExtensions/VkNetExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using HtmlAgilityPack; +using VkNet.Abstractions; +using VkNet.Enums.Filters; + +namespace VkNetExtensions +{ + /// + /// Методы расширения для VkNet + /// + public static class VkNetExtensions + { + /// + /// Получить дату регистрации пользователя или дату создания сообщества + /// + /// + /// ID пользователя или сообщества (у сообщества с минусом) + /// + public static async Task GetRegistrationDateAsync(this IVkApi api, long id) + { + if (id < 0) + { + var info = (await api.Groups.GetByIdAsync(null, (-id).ToString(), GroupsFields.StartDate)).First(); + return info.StartDate.GetValueOrDefault(); + } + + var client = new HttpClient(); + var str = await client.GetStringAsync($"https://vk.com/foaf.php?id={id}"); + var doc = new HtmlDocument(); + doc.LoadHtml(str); + try + { + var created = doc.DocumentNode.Descendants("ya:created").ToArray()[0]; + var dataStr = created.Attributes["dc:date"].Value; + + return Convert.ToDateTime(dataStr); + } + catch + { + return null; + } + } + } +} diff --git a/VkNetExtensions/VkNetExtensions.csproj b/VkNetExtensions/VkNetExtensions.csproj index 8467264..62134c8 100644 --- a/VkNetExtensions/VkNetExtensions.csproj +++ b/VkNetExtensions/VkNetExtensions.csproj @@ -8,6 +8,7 @@ vk;vk api;vknet;vkontakte;api https://github.com/vknet/vknetextensions LICENCE + enable false @@ -23,6 +24,9 @@ true - + + + + From c0e719dd47d9cb44284e0423d1636f4131fa7352 Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 21:46:56 +0300 Subject: [PATCH 02/11] GetForwardedMessages --- VkNetExtensions/VkNetExtensions.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs index fb3350b..c715d4c 100644 --- a/VkNetExtensions/VkNetExtensions.cs +++ b/VkNetExtensions/VkNetExtensions.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using HtmlAgilityPack; using VkNet.Abstractions; using VkNet.Enums.Filters; +using VkNet.Model; namespace VkNetExtensions { @@ -43,5 +45,17 @@ public static class VkNetExtensions return null; } } + + /// + /// Получить реплай сообщение (если есть) или пересланные сообщения + /// + /// + /// + public static IEnumerable GetForwardedMessages(this Message message) + { + if (message.ReplyMessage != null) return new[] {message.ReplyMessage}; + + return message.ForwardedMessages; + } } } From 2eb07f478a1fb2d01d5d89f2583ae797ddf3c03a Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 22:04:44 +0300 Subject: [PATCH 03/11] AdditionalMethodsForExtensions --- .../AdditionalMethodsForExtensions.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 VkNetExtensions/AdditionalMethodsForExtensions.cs diff --git a/VkNetExtensions/AdditionalMethodsForExtensions.cs b/VkNetExtensions/AdditionalMethodsForExtensions.cs new file mode 100644 index 0000000..ae45041 --- /dev/null +++ b/VkNetExtensions/AdditionalMethodsForExtensions.cs @@ -0,0 +1,20 @@ +using System.Text; + +namespace VkNetExtensions +{ + /// + /// В этом файле нет методов расширения для VkNet, но сами методы расширения используют методы отсюда. + /// + public static class AdditionalMethodsForExtensions + { + /// + /// Получить строку в байтах + /// + /// + /// + public static byte[] GetFileTxtByte(string text) + { + return new UTF8Encoding(true).GetBytes(text); + } + } +} From 4a299bfab50ef6d1e09624f97c9b41905d779e63 Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 22:06:28 +0300 Subject: [PATCH 04/11] UploadFile --- .../AdditionalMethodsForExtensions.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/VkNetExtensions/AdditionalMethodsForExtensions.cs b/VkNetExtensions/AdditionalMethodsForExtensions.cs index ae45041..887cf6e 100644 --- a/VkNetExtensions/AdditionalMethodsForExtensions.cs +++ b/VkNetExtensions/AdditionalMethodsForExtensions.cs @@ -1,4 +1,7 @@ -using System.Text; +using System; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; namespace VkNetExtensions { @@ -16,5 +19,26 @@ public static byte[] GetFileTxtByte(string text) { return new UTF8Encoding(true).GetBytes(text); } + + /// + /// Загружает массив байт на указанный url + /// + /// Адрес для загрузки + /// Массив данных для загрузки + /// Строка, которую вернул сервер. + public static async Task UploadFile(string url, byte[] data, string filename) + { + var index = filename.LastIndexOf('.') + 1; + var format = filename.Substring(index, filename.Length - index); + using var client = new HttpClient(); + + throw new NotImplementedException(); + + // todo расскомментить и реализовать без flurl + // return await url.PostMultipartAsync + // ( + // mp => mp.AddFile("file", new MemoryStream(data), $"file.{format}") + // ).ReceiveString(); + } } } From 5f1a2f9c3e6c957b254e841e83e8cdcd050cdc90 Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 22:07:50 +0300 Subject: [PATCH 05/11] LoadDocumentToChatAsync --- VkNetExtensions/VkNetExtensions.cs | 47 +++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs index c715d4c..683b03a 100644 --- a/VkNetExtensions/VkNetExtensions.cs +++ b/VkNetExtensions/VkNetExtensions.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using HtmlAgilityPack; using VkNet.Abstractions; using VkNet.Enums.Filters; +using VkNet.Enums.SafetyEnums; using VkNet.Model; +using VkNet.Model.Attachments; namespace VkNetExtensions { @@ -29,7 +32,7 @@ public static class VkNetExtensions return info.StartDate.GetValueOrDefault(); } - var client = new HttpClient(); + using var client = new HttpClient(); var str = await client.GetStringAsync($"https://vk.com/foaf.php?id={id}"); var doc = new HtmlDocument(); doc.LoadHtml(str); @@ -57,5 +60,47 @@ public static IEnumerable GetForwardedMessages(this Message message) return message.ForwardedMessages; } + + /// + /// Загружает документ на сервер ВК. + /// + /// + /// Attachment, байты которого будут отправлены на сервер + /// Тип документа - документ или аудиосообщение + /// Идентификатор назначения + /// Итоговое название документа + /// Attachment для отправки вместе с сообщением + public static async Task LoadDocumentToChatAsync(IVkApi vkApi, byte[] data, + DocMessageType docMessageType, long peerId, string filename) + { + var uploadServer = vkApi.Docs.GetMessagesUploadServer(peerId, docMessageType); + var r = await AdditionalMethodsForExtensions.UploadFile(uploadServer.UploadUrl, data, filename); + var documents = await vkApi.Docs.SaveAsync(r, filename, null); + + if (!documents.Any()) return null; + + return documents.First(); + } + + /// + /// Загружает txt файл на сервер вк + /// + /// + /// Текст в txt документе + /// Идентификатор назначения + /// Итоговое название документа + /// Подставлять ли тип .txt + /// + public static async Task LoadTxtDocumentToChatAsync(IVkApi vkApi, string text, long peerId, string filename, + bool txt = true) + { + var data = AdditionalMethodsForExtensions.GetFileTxtByte(text); + + return await LoadDocumentToChatAsync(vkApi, + data, + DocMessageType.Doc, + peerId, + $"{filename}{(txt ? ".txt" : "")}"); + } } } From d9448155777111833d0d599f0bfe98eb0730c334 Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 22:15:51 +0300 Subject: [PATCH 06/11] GetVisualPeerId and GetRealPeerId --- VkNetExtensions/VkNetExtensions.cs | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs index 683b03a..ddfa5ae 100644 --- a/VkNetExtensions/VkNetExtensions.cs +++ b/VkNetExtensions/VkNetExtensions.cs @@ -18,6 +18,11 @@ namespace VkNetExtensions /// public static class VkNetExtensions { + /// + /// С этого числа начинаются ID бесед + /// + public const long COUNT_CONVERSATION = 2000000000; + /// /// Получить дату регистрации пользователя или дату создания сообщества /// @@ -61,6 +66,47 @@ public static IEnumerable GetForwardedMessages(this Message message) return message.ForwardedMessages; } + /// + /// Возвращает зрительный номер беседы (не настоящий) из реального или зрительного + /// + public static long GetVisualPeerId(this long peerId) + { + if (peerId < COUNT_CONVERSATION) return peerId; + + return peerId - COUNT_CONVERSATION; + } + + /// + /// Возвращает зрительный номер беседы (не настоящий) из реального или зрительного + /// + public static long? GetVisualPeerId(this long? peerId) + { + if (peerId == null) return null; + + return GetVisualPeerId(peerId.Value); + + } + + /// + /// Возвращает реальный номер беседы из зрительного или реального + /// + public static long? GetRealPeerId(this long? peerId) + { + if (peerId == null) return null; + + return GetRealPeerId(peerId.Value); + } + + /// + /// Возвращает реальный номер беседы из зрительного или реального + /// + public static long GetRealPeerId(this long peerId) + { + if (peerId > COUNT_CONVERSATION) return peerId; + + return peerId + COUNT_CONVERSATION; + } + /// /// Загружает документ на сервер ВК. /// From c4e8cd14d7e948cce197aafbc1a97ba38aa36daf Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 22:36:15 +0300 Subject: [PATCH 07/11] VK DateTime --- VkNetExtensions/VkDate.cs | 55 ++++++++++++++++++++++++++++++ VkNetExtensions/VkNetExtensions.cs | 29 ++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 VkNetExtensions/VkDate.cs diff --git a/VkNetExtensions/VkDate.cs b/VkNetExtensions/VkDate.cs new file mode 100644 index 0000000..50f2d1a --- /dev/null +++ b/VkNetExtensions/VkDate.cs @@ -0,0 +1,55 @@ +using System; +using VkNet.Abstractions; + +namespace VkNetExtensions +{ + /// + /// Класс для работы с закэшированным временем, полученным от сервера ВКонтакте + /// + public class VkDate + { + /// + /// Последнее полученное время + /// + public static VkDate? VkDateObject; + + /// + /// Закэшированный экземпляр VkApi + /// + public static IVkApi? VkApi; + + /// + /// Время в ВК при запросе + /// + public DateTime? VkDateTime { get; } + + /// + /// Локальное время, которое было при запросе + /// + public DateTime VkDateTimeLocal { get; } + + public VkDate(DateTime vkDateTime, DateTime? vkDateTimeLocal = null) + { + VkDateTime = vkDateTime; + VkDateTimeLocal = vkDateTimeLocal ?? DateTime.UtcNow; + } + + /// + /// Текущее время в ВК в UTC + /// + /// время в UTC + public DateTime Get() + { + return VkDateTime!.Value.Add(DateTime.UtcNow - VkDateTimeLocal); + } + + /// + /// Получает время с ВК и кэширует + /// + /// + public static void UpdateVkDate(IVkApi api) + { + VkDateObject = new VkDate(api.Utils.GetServerTime()); + } + } +} diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs index ddfa5ae..5f78567 100644 --- a/VkNetExtensions/VkNetExtensions.cs +++ b/VkNetExtensions/VkNetExtensions.cs @@ -107,6 +107,35 @@ public static long GetRealPeerId(this long peerId) return peerId + COUNT_CONVERSATION; } + /// + /// Возвращает текущее время с серверов ВК. + /// Делает запрос лишь при первом использовании, в остальных случаях считается через закэшированный результат. + /// IVkApi обязательно передавать только при первом использовании, экземпляр будет закэширован. + /// Если нужно будет обновить время, используйте VkDate.UpdateVkDate(api). + /// + /// + /// Часовой пояс + /// + public static DateTime DateTime(IVkApi? api = null, int timezone = 0) + { + if (api == null) + { + api = VkDate.VkApi; + + if (api == null) + { + throw new Exception("IVkApi == null"); + } + } else + { + VkDate.VkApi = api; + } + + if (VkDate.VkDateObject == null) VkDate.UpdateVkDate(api); + + return VkDate.VkDateObject!.Get().AddHours(timezone); + } + /// /// Загружает документ на сервер ВК. /// From f3514ae24592dc0003f0f9509f95ab0c76a389be Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 22:39:29 +0300 Subject: [PATCH 08/11] GetClickableLinkById --- VkNetExtensions/VkNetExtensions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs index 5f78567..6ba4837 100644 --- a/VkNetExtensions/VkNetExtensions.cs +++ b/VkNetExtensions/VkNetExtensions.cs @@ -177,5 +177,16 @@ public static DateTime DateTime(IVkApi? api = null, int timezone = 0) peerId, $"{filename}{(txt ? ".txt" : "")}"); } + + /// + /// Получает кликабельную ссылку на юзера или сообщество + /// + /// id пользователя или сообщества (у сообщества с минусом) + /// Текст ссылки + /// + public static string GetClickableLinkById(this long id, string text) + { + return $"[{(id > 0 ? $"id{id}" : $"club{-id}")}|{text.Replace("]", "]")}]"; + } } } From 4f54398d1d177ba280214517c2ada88eb23e5ef5 Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 22:57:21 +0300 Subject: [PATCH 09/11] =?UTF-8?q?GetIdForUserOrCommunitiesFromTextLink=20?= =?UTF-8?q?=D0=B8=20GetUserOrCommunityIdFromArgument?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VkNetExtensions/VkNetExtensions.cs | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs index 6ba4837..b886f01 100644 --- a/VkNetExtensions/VkNetExtensions.cs +++ b/VkNetExtensions/VkNetExtensions.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using HtmlAgilityPack; using VkNet.Abstractions; +using VkNet.Enums; using VkNet.Enums.Filters; using VkNet.Enums.SafetyEnums; using VkNet.Model; @@ -188,5 +189,67 @@ public static string GetClickableLinkById(this long id, string text) { return $"[{(id > 0 ? $"id{id}" : $"club{-id}")}|{text.Replace("]", "]")}]"; } + + /// + /// Возвращает из аргумента id пользователя или сообщества, если найдёт. + /// + /// + /// + /// ID пользователя или сообщества, или null + public static async Task GetUserOrCommunityIdFromArgument(IVkApi api, string argument) + { + try + { + if (argument.Contains("[id")) + { + var indexStart = argument.IndexOf('d') + 1; + var indexEnd = argument.IndexOf('|'); + var id = argument.Substring(indexStart, indexEnd - indexStart); + + return Convert.ToInt64(id); + } else if (argument.Contains("club")) + { + var indexStart = argument.IndexOf('b') + 1; + var indexEnd = argument.IndexOf('|'); + var id = argument.Substring(indexStart, indexEnd - indexStart); + + return -Convert.ToInt64(id); + } + } + catch + { + // ignored + } + + if (argument.Contains("vk.com/")) + { + if (argument.Contains("vk.com/id") && long.TryParse(argument.Substring(argument.LastIndexOf('d') + 1), out var id3)) + return id3; + + var id = await GetIdForUserOrCommunitiesFromTextLink(api, argument); + + if (id != 0) return id; + } + + return null; + } + + /// + /// Получает id пользователя или сообщества из ссылки подобной "vk.com/durov". + /// Возвращает 0, если это ссылка не на пользователя или сообщество. + /// + /// + /// Например "vk.com/durov" или "https://vk.com/durov". + /// id пользователя или сообщества. + public static async Task GetIdForUserOrCommunitiesFromTextLink(IVkApi api, string link) + { + var id = await api.Utils.ResolveScreenNameAsync(link.Substring(link.LastIndexOf('/') + 1)); + + if (id?.Id == null || id.Type == VkObjectType.Application) return 0; + + if (id.Type == VkObjectType.Group) return -id.Id.Value; + + return id.Id.Value; + } } } From 300aabaadce9e3f3ced4c27d4213c9fb7732f033 Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 23:02:00 +0300 Subject: [PATCH 10/11] GetAdminsConversationAsync --- VkNetExtensions/VkNetExtensions.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs index b886f01..d732114 100644 --- a/VkNetExtensions/VkNetExtensions.cs +++ b/VkNetExtensions/VkNetExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Net.Http; @@ -251,5 +252,28 @@ public static async Task GetIdForUserOrCommunitiesFromTextLink(IVkApi api, return id.Id.Value; } + + /// + /// Возвращает администраторов беседы. + /// Первый в массиве является создателем (но это не точно, т.к. создатель может выйти из беседы). + /// Метод может упасть при +2000 участников, т.к. выполняется через execute и проходит всех участников в цикле. + /// Кто хочет - может оптимизировать. Мне просто лень. + /// + public static Task> GetAdminsConversationAsync(IVkApi api, long peerId) + { + var script = "var members = API.messages.getConversationMembers(" + + "{ \"peer_id\":" + + peerId + + "}).items;" + + " var admins = [];" + + " var i = 0;" + + " while (i < members.length)" + + " { if (members[i].is_admin)" + + " admins.push(members[i]);" + + " i = i + 1;" + + " } return admins;"; + + return api.Execute.ExecuteAsync>(script); + } } } From d340601f0370674fc24d6283ebd524b0ff5b5706 Mon Sep 17 00:00:00 2001 From: Ivan Melentev Date: Tue, 29 Dec 2020 23:09:12 +0300 Subject: [PATCH 11/11] =?UTF-8?q?GetCountMembersInConversation=20=D0=B8=20?= =?UTF-8?q?GetRandomMemberInConversation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VkNetExtensions/VkNetExtensions.cs | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/VkNetExtensions/VkNetExtensions.cs b/VkNetExtensions/VkNetExtensions.cs index d732114..46f3098 100644 --- a/VkNetExtensions/VkNetExtensions.cs +++ b/VkNetExtensions/VkNetExtensions.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Text; using System.Threading.Tasks; using HtmlAgilityPack; using VkNet.Abstractions; @@ -275,5 +276,44 @@ public static Task> GetAdminsConversation return api.Execute.ExecuteAsync>(script); } + + /// + /// Возвращает количество участников беседы + /// + /// + /// + /// + public static Task GetCountMembersInConversation(IVkApi api, long peerId) + { + var script = "return API.messages.getConversationMembers({ \"peer_id\":" + + peerId + + "}).items.length;"; + return api.Execute.ExecuteAsync(script); + } + + /// + /// Возвращает рандомного участника беседы + /// + /// + /// + /// Случайное число (получите его через (new Random).Next(0, 999999), только не создавайте экземпляр рандома каждый раз как в примере + /// ID рандомного участника беседы. Если произошла какая-то ошибка, вернёт null. + public static async Task GetRandomMemberInConversation(IVkApi api, long peerId, int random) + { + var stringBuilder = new StringBuilder(); + stringBuilder.Append($"var members=API.messages.getConversationMembers({{ \"peer_id\":{peerId}}}).items;"); + stringBuilder.Append($"return members[{random}%members.length].member_id;"); + try + { + var text = await api.Execute.ExecuteAsync(stringBuilder.ToString()); + if (long.TryParse(text, out var id)) return id; + + return null; + } + catch + { + return null; + } + } } }