Skip to content

Commit de8b0f1

Browse files
committed
Add PostManuallyCommand
1 parent 2e04de2 commit de8b0f1

File tree

6 files changed

+250
-4
lines changed

6 files changed

+250
-4
lines changed

Source/WetPicsRebirth.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<s:Boolean x:Key="/Default/UserDictionary/Words/=dailyr/@EntryIndexedValue">True</s:Boolean>
1111
<s:Boolean x:Key="/Default/UserDictionary/Words/=Danbooru/@EntryIndexedValue">True</s:Boolean>
1212
<s:Boolean x:Key="/Default/UserDictionary/Words/=donmai/@EntryIndexedValue">True</s:Boolean>
13+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gelbooru/@EntryIndexedValue">True</s:Boolean>
1314
<s:Boolean x:Key="/Default/UserDictionary/Words/=getactresses/@EntryIndexedValue">True</s:Boolean>
1415
<s:Boolean x:Key="/Default/UserDictionary/Words/=Loli/@EntryIndexedValue">True</s:Boolean>
1516
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pixiv/@EntryIndexedValue">True</s:Boolean>
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
using System.Security.Cryptography;
2+
using Microsoft.Extensions.Caching.Memory;
3+
using Telegram.Bot.Types;
4+
using WetPicsRebirth.Commands.UserCommands.Abstract;
5+
using WetPicsRebirth.Data.Entities;
6+
using WetPicsRebirth.Data.Repositories.Abstract;
7+
using WetPicsRebirth.Exceptions;
8+
using WetPicsRebirth.Extensions;
9+
using WetPicsRebirth.Infrastructure;
10+
using WetPicsRebirth.Infrastructure.ImageProcessing;
11+
using WetPicsRebirth.Infrastructure.Models;
12+
using WetPicsRebirth.Services;
13+
14+
namespace WetPicsRebirth.Commands.UserCommands.Posting;
15+
16+
public class PostManuallyCommandHandler : MessageHandler
17+
{
18+
private readonly ITelegramBotClient _telegramBotClient;
19+
private readonly IEngineFactory _engineFactory;
20+
private readonly IPostedMediaRepository _postedMediaRepository;
21+
private readonly ITelegramPreparer _telegramPreparer;
22+
private readonly IPopularListLoader _popularListLoader;
23+
private readonly string _channelLink;
24+
private readonly string _accessLink;
25+
26+
public PostManuallyCommandHandler(
27+
ITelegramBotClient telegramBotClient,
28+
ILogger<PostManuallyCommandHandler> logger,
29+
IMemoryCache memoryCache,
30+
IEngineFactory engineFactory,
31+
IPostedMediaRepository postedMediaRepository,
32+
ITelegramPreparer telegramPreparer,
33+
IConfiguration configuration,
34+
IPopularListLoader popularListLoader)
35+
: base(telegramBotClient, logger, memoryCache)
36+
{
37+
_telegramBotClient = telegramBotClient;
38+
_engineFactory = engineFactory;
39+
_postedMediaRepository = postedMediaRepository;
40+
_telegramPreparer = telegramPreparer;
41+
_popularListLoader = popularListLoader;
42+
_channelLink = configuration.GetRequiredValue<string>("ChannelInviteLink");
43+
_accessLink = configuration.GetRequiredValue<string>("AccessLink");
44+
}
45+
46+
public override IEnumerable<string> ProvidedCommands
47+
=> "/post rule34 1234".ToEnumerable();
48+
49+
protected override bool WantHandle(Message message, string? command) => command == "/post";
50+
51+
protected override async Task Handle(Message message, string? command, CancellationToken cancellationToken)
52+
{
53+
var parameters = message.Text?.Split(' ') ?? Array.Empty<string>();
54+
55+
if (parameters.Length != 4)
56+
{
57+
await _telegramBotClient.SendTextMessageAsync(
58+
message.Chat.Id,
59+
"Команда должна выглядеть как /post -1001411191119 rule34 1234, " +
60+
"где первый параметр это айди чата или канала, " +
61+
"второй источник поста, " +
62+
"третий id поста в иточнике",
63+
replyToMessageId: message.MessageId,
64+
cancellationToken: cancellationToken);
65+
return;
66+
}
67+
68+
if (!long.TryParse(parameters[1], out var targetId))
69+
{
70+
await _telegramBotClient.SendTextMessageAsync(
71+
message.Chat.Id,
72+
"Неверный айди чата или канала",
73+
replyToMessageId: message.MessageId,
74+
cancellationToken: cancellationToken);
75+
return;
76+
}
77+
78+
if (!TryGetImageSource(parameters[2], out var source))
79+
{
80+
await _telegramBotClient.SendTextMessageAsync(
81+
message.Chat.Id,
82+
"Неверный источник, доступны: pixiv, yandere, danbooru, ???",
83+
replyToMessageId: message.MessageId,
84+
cancellationToken: cancellationToken);
85+
return;
86+
}
87+
88+
if (!int.TryParse(parameters[3], out var postId))
89+
{
90+
await _telegramBotClient.SendTextMessageAsync(
91+
message.Chat.Id,
92+
"Неверный айди поста в источнике",
93+
replyToMessageId: message.MessageId,
94+
cancellationToken: cancellationToken);
95+
return;
96+
}
97+
98+
if (!await CheckOnAdmin(targetId, message.From!.Id))
99+
{
100+
await _telegramBotClient.SendTextMessageAsync(
101+
message.Chat.Id,
102+
"У вас должны быть права администратора в выбранном чате или канале",
103+
replyToMessageId: message.MessageId,
104+
cancellationToken: cancellationToken);
105+
return;
106+
}
107+
108+
try
109+
{
110+
var post = await _engineFactory.Get(source).LoadPost(new(postId, null));
111+
112+
var hash = post.Post.PostHeader.Md5Hash ?? GetHash(post.Post.File);
113+
114+
var (sentPost, fileId, fileType) = await SentPostToTelegram(source, targetId, post.Post);
115+
116+
await _postedMediaRepository.Add(
117+
targetId,
118+
sentPost.MessageId,
119+
fileId,
120+
fileType,
121+
source,
122+
postId,
123+
hash);
124+
125+
await post.Post.File.DisposeAsync();
126+
}
127+
catch (Exception e)
128+
{
129+
await _telegramBotClient.SendTextMessageAsync(
130+
message.Chat.Id,
131+
"Не удалось отправить пост: " + e.Message,
132+
replyToMessageId: message.MessageId,
133+
cancellationToken: cancellationToken);
134+
}
135+
}
136+
137+
private static bool TryGetImageSource(string sourceString, out ImageSource source)
138+
{
139+
source = default;
140+
141+
if (!Enum.TryParse(typeof(ImageSource), sourceString, true, out var result))
142+
return false;
143+
144+
source = (ImageSource)result!;
145+
return true;
146+
}
147+
148+
private static string GetHash(Stream file)
149+
{
150+
file.Seek(0, SeekOrigin.Begin);
151+
152+
using var md5 = MD5.Create();
153+
var result = string.Join("", md5.ComputeHash(file).Select(x => x.ToString("X2"))).ToLowerInvariant();
154+
file.Seek(0, SeekOrigin.Begin);
155+
156+
return result;
157+
}
158+
159+
private string EnrichCaption(string caption)
160+
=> $"{caption} | <a href=\"{_channelLink}\">join</a> | <a href=\"{_accessLink}\">access</a>";
161+
162+
private async Task<(Message sentPost, string fileId, MediaType fileType)> SentPostToTelegram(
163+
ImageSource source,
164+
long chatId,
165+
Post post)
166+
{
167+
try
168+
{
169+
var caption = _popularListLoader.CreateCaption(source, string.Empty, post);
170+
caption = EnrichCaption(caption);
171+
var ((sentPost, fileId), fileType) = post.Type switch
172+
{
173+
MediaType.Photo => (await SendPhoto(chatId, post, caption), MediaType.Photo),
174+
MediaType.Video => (await SendVideo(chatId, post, caption), MediaType.Video),
175+
_ => throw new ArgumentOutOfRangeException(nameof(post.Type))
176+
};
177+
return (sentPost, fileId, fileType);
178+
179+
}
180+
catch (Exception e)
181+
{
182+
throw new UnableToPostForActressException(post.PostHeader.Id, e);
183+
}
184+
}
185+
186+
private async Task<(Message sentPost, string fileId)> SendPhoto(long chatId, Post post, string caption)
187+
{
188+
await using var file = _telegramPreparer.Prepare(post.File, post.FileSize);
189+
190+
var sentPost = await _telegramBotClient.SendPhotoAsync(
191+
chatId: chatId,
192+
photo: new InputFileStream(file),
193+
caption: caption,
194+
parseMode: ParseMode.Html,
195+
replyMarkup: Keyboards.WithLikes(0));
196+
197+
var fileId = sentPost.Photo!
198+
.OrderByDescending(x => x.Height)
199+
.ThenByDescending(x => x.Width)
200+
.Select(x => x.FileId)
201+
.First();
202+
203+
return (sentPost, fileId);
204+
}
205+
206+
private async Task<(Message sentPost, string fileId)> SendVideo(long chatId, Post post, string caption)
207+
{
208+
var sentPost = await _telegramBotClient.SendVideoAsync(
209+
chatId,
210+
new InputFileStream(post.File),
211+
caption: caption,
212+
parseMode: ParseMode.Html,
213+
replyMarkup: Keyboards.WithLikes(0));
214+
215+
var fileId = sentPost.Video!.FileId;
216+
217+
return (sentPost, fileId);
218+
}
219+
}

Source/WetPicsRebirth/Data/Entities/ImageSource.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ public enum ImageSource
44
{
55
Pixiv,
66
Danbooru,
7-
Yandere
8-
}
7+
Yandere,
8+
Rule34,
9+
Gelbooru
10+
}

Source/WetPicsRebirth/Infrastructure/EngineFactory.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using Flurl.Http.Configuration;
22
using Imouto.BooruParser.Implementations.Danbooru;
3+
using Imouto.BooruParser.Implementations.Gelbooru;
4+
using Imouto.BooruParser.Implementations.Rule34;
35
using Imouto.BooruParser.Implementations.Yandere;
46
using Microsoft.Extensions.Options;
57
using WetPicsRebirth.Data.Entities;
@@ -49,6 +51,24 @@ public IPopularListLoaderEngine Get(ImageSource imageSource)
4951
})),
5052
_httpClient,
5153
_loggerFactory.CreateLogger<BooruEngine>()),
54+
ImageSource.Rule34 => new BooruEngine(
55+
new Rule34ApiLoader(
56+
new PerBaseUrlFlurlClientFactory(),
57+
Options.Create(new Rule34Settings()
58+
{
59+
PauseBetweenRequestsInMs = 1,
60+
})),
61+
_httpClient,
62+
_loggerFactory.CreateLogger<BooruEngine>()),
63+
ImageSource.Gelbooru => new BooruEngine(
64+
new GelbooruApiLoader(
65+
new PerBaseUrlFlurlClientFactory(),
66+
Options.Create(new GelbooruSettings()
67+
{
68+
PauseBetweenRequestsInMs = 1,
69+
})),
70+
_httpClient,
71+
_loggerFactory.CreateLogger<BooruEngine>()),
5272
ImageSource.Pixiv => new PixivEngine(_pixivApiClient),
5373
_ => throw new ArgumentOutOfRangeException(nameof(imageSource), imageSource, null)
5474
};

Source/WetPicsRebirth/Infrastructure/Engines/BooruEngine.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,16 @@ private static string GetMediaUrl(Imouto.BooruParser.Post post)
7474

7575
public string CreateCaption(ImageSource source, string options, Post post)
7676
{
77-
var type = GetPopularType(options).MakeAdverb().ToLower();
77+
var type = options == string.Empty
78+
? "selected"
79+
: GetPopularType(options).MakeAdverb().ToLower();
7880

7981
return source switch
8082
{
8183
ImageSource.Danbooru => $"<a href=\"https://danbooru.donmai.us/posts/{post.PostHeader.Id}\">danbooru {type}</a>",
8284
ImageSource.Yandere => $"<a href=\"https://yande.re/post/show/{post.PostHeader.Id}\">yandere {type}</a>",
85+
ImageSource.Rule34 => $"<a href=\"https://rule34.xxx/index.php?page=post&s=view&id={post.PostHeader.Id}\">rule34 {type}</a>",
86+
ImageSource.Gelbooru => $"<a href=\"https://gelbooru.com/index.php?page=post&s=view&id={post.PostHeader.Id}\">gelbooru {type}</a>",
8387
_ => throw new ArgumentOutOfRangeException(nameof(source), source, null)
8488
};
8589
}

Source/WetPicsRebirth/WetPicsRebirth.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Imouto.BooruParser" Version="3.2.8" />
11+
<PackageReference Include="Imouto.BooruParser" Version="3.2.9" />
1212
<PackageReference Include="MediatR" Version="12.2.0" />
1313
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0" />
1414
<PackageReference Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="7.0.0" />

0 commit comments

Comments
 (0)