ok
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using PepperBot.Application.Interfaces;
|
||||
using PepperBot.Domain;
|
||||
|
||||
namespace PepperBot.Infrastructure.Data;
|
||||
|
||||
public class SqliteDealRepository : IDealRepository
|
||||
{
|
||||
private const string DatabaseFileName = "pepper_bot.db";
|
||||
|
||||
private string ConnectionString =>
|
||||
new SqliteConnectionStringBuilder
|
||||
{
|
||||
DataSource = DatabaseFileName
|
||||
}.ToString();
|
||||
|
||||
public async Task InitializeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await using var connection = new SqliteConnection(ConnectionString);
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText =
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS Deals (
|
||||
Id INTEGER PRIMARY KEY,
|
||||
Title TEXT NOT NULL,
|
||||
CurrentPrice REAL NULL,
|
||||
RetailPrice REAL NULL,
|
||||
PercentOff INTEGER NULL,
|
||||
CreatedAt TEXT NOT NULL,
|
||||
CreatedAtInMillis INTEGER NOT NULL,
|
||||
DealUrl TEXT NOT NULL,
|
||||
StoreName TEXT NOT NULL,
|
||||
StorePermalink TEXT NOT NULL
|
||||
);
|
||||
""";
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var connection = new SqliteConnection(ConnectionString);
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT 1 FROM Deals WHERE Id = $id LIMIT 1;";
|
||||
command.Parameters.AddWithValue("$id", id);
|
||||
|
||||
var result = await command.ExecuteScalarAsync(cancellationToken);
|
||||
return result is not null;
|
||||
}
|
||||
|
||||
public async Task AddAsync(Deal deal, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var connection = new SqliteConnection(ConnectionString);
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText =
|
||||
"""
|
||||
INSERT INTO Deals
|
||||
(Id, Title, CurrentPrice, RetailPrice, PercentOff, CreatedAt, CreatedAtInMillis, DealUrl, StoreName, StorePermalink)
|
||||
VALUES
|
||||
($id, $title, $currentPrice, $retailPrice, $percentOff, $createdAt, $createdAtInMillis, $dealUrl, $storeName, $storePermalink);
|
||||
""";
|
||||
|
||||
command.Parameters.AddWithValue("$id", deal.Id);
|
||||
command.Parameters.AddWithValue("$title", deal.Title);
|
||||
command.Parameters.AddWithValue("$currentPrice", (object?)deal.CurrentPrice ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("$retailPrice", (object?)deal.RetailPrice ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("$percentOff", (object?)deal.PercentOff ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("$createdAt", deal.CreatedAt.ToString("O"));
|
||||
command.Parameters.AddWithValue("$createdAtInMillis", deal.CreatedAtInMillis);
|
||||
command.Parameters.AddWithValue("$dealUrl", deal.DealUrl);
|
||||
command.Parameters.AddWithValue("$storeName", deal.StoreName);
|
||||
command.Parameters.AddWithValue("$storePermalink", deal.StorePermalink);
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PepperBot.Application.Interfaces;
|
||||
using PepperBot.Domain;
|
||||
|
||||
namespace PepperBot.Infrastructure.Pepper;
|
||||
|
||||
public class PepperClient : IPepperClient
|
||||
{
|
||||
private const string Url =
|
||||
"https://auth.pepper.ru/v4/home/new?per_page=10&page=1&type=new&fields=id%2Ctitle%2Ccurrent_price%2Cshipping_and_handling%2Cretail_price%2Cpercent_off%2Ctop_deal%2Cimage_medium%2Ccreated_at%2Ccreated_at_in_millis%2Ccomments_count%2Clife_time_hotness%2Cuser%7Bid%2Clogin%2Cimage_medium%2Ccurrent_title%2Cdisplay_title%2Cdisplay_title_icon%7D%2Cstore%7Bpermalink%2Cname%2Cimage%7D%2Cpermalink%2Cposts_count%2Cvote_value%2Cdeal_types%2Cview_count%2Cforum%7Bforum_type%2Cname%2Cpermalink%7D%2Ctag_color_code%2C+dark_theme_tag_color_code%2Cworkflow_state%2Cdisplay_hotness_icon%2Creferral_state%2Ccoupon_code%2Cdeal_url%2Ccomment_preview%2Cis_nsfw%2Cdescription";
|
||||
|
||||
private const string ClientHeaderName = "x-desidime-client";
|
||||
private const string DefaultClientHeaderValue =
|
||||
"6c266d5dc070c811e61f611ddd970a3defb493a23c50ee01f32eb9b0cfe46cd8";
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<PepperClient> _logger;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
public PepperClient(ILogger<PepperClient> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri("https://auth.pepper.ru")
|
||||
};
|
||||
_httpClient.DefaultRequestHeaders.Accept.Add(
|
||||
new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
var clientHeader = Environment.GetEnvironmentVariable("PEPPER_CLIENT_HEADER");
|
||||
_httpClient.DefaultRequestHeaders.Add(ClientHeaderName,
|
||||
string.IsNullOrWhiteSpace(clientHeader) ? DefaultClientHeaderValue : clientHeader);
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Host = "auth.pepper.ru";
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<Deal>> GetLatestDealsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, Url);
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
var dto = await JsonSerializer.DeserializeAsync<PepperResponse>(stream, JsonOptions, cancellationToken);
|
||||
|
||||
if (dto?.Deals is null)
|
||||
{
|
||||
_logger.LogWarning("Не удалось распарсить ответ Pepper");
|
||||
return Array.Empty<Deal>();
|
||||
}
|
||||
|
||||
return dto.Deals.Select(MapToDeal).ToArray();
|
||||
}
|
||||
|
||||
private static Deal MapToDeal(PepperDeal dto)
|
||||
{
|
||||
return new Deal
|
||||
{
|
||||
Id = dto.Id,
|
||||
Title = dto.Title ?? string.Empty,
|
||||
CurrentPrice = dto.CurrentPrice,
|
||||
RetailPrice = dto.RetailPrice,
|
||||
PercentOff = dto.PercentOff,
|
||||
CreatedAt = dto.CreatedAt ?? DateTimeOffset.FromUnixTimeMilliseconds(dto.CreatedAtInMillis).UtcDateTime,
|
||||
CreatedAtInMillis = dto.CreatedAtInMillis,
|
||||
DealUrl = dto.DealUrl ?? string.Empty,
|
||||
StoreName = dto.Store?.Name ?? string.Empty,
|
||||
StorePermalink = dto.Store?.Permalink ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class PepperResponse
|
||||
{
|
||||
[JsonPropertyName("deals")]
|
||||
public List<PepperDeal>? Deals { get; set; }
|
||||
}
|
||||
|
||||
private sealed class PepperDeal
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[JsonPropertyName("current_price")]
|
||||
public decimal? CurrentPrice { get; set; }
|
||||
|
||||
[JsonPropertyName("retail_price")]
|
||||
public decimal? RetailPrice { get; set; }
|
||||
|
||||
[JsonPropertyName("percent_off")]
|
||||
public int? PercentOff { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at_in_millis")]
|
||||
public long CreatedAtInMillis { get; set; }
|
||||
|
||||
[JsonPropertyName("deal_url")]
|
||||
public string? DealUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("store")]
|
||||
public PepperStore? Store { get; set; }
|
||||
}
|
||||
|
||||
private sealed class PepperStore
|
||||
{
|
||||
[JsonPropertyName("permalink")]
|
||||
public string? Permalink { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PepperBot.Application.Interfaces;
|
||||
using PepperBot.Domain;
|
||||
|
||||
namespace PepperBot.Infrastructure.Telegram;
|
||||
|
||||
public class TelegramNotifier : ITelegramNotifier
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<TelegramNotifier> _logger;
|
||||
private readonly string _botToken;
|
||||
private readonly string _chatId;
|
||||
|
||||
public TelegramNotifier(ILogger<TelegramNotifier> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_botToken = Environment.GetEnvironmentVariable("TELEGRAM_BOT_TOKEN") ?? string.Empty;
|
||||
_chatId = Environment.GetEnvironmentVariable("TELEGRAM_CHAT_ID") ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_botToken) || string.IsNullOrWhiteSpace(_chatId))
|
||||
{
|
||||
_logger.LogWarning("TELEGRAM_BOT_TOKEN или TELEGRAM_CHAT_ID не заданы. Отправка сообщений отключена.");
|
||||
}
|
||||
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task NotifyNewDealAsync(Deal deal, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_botToken) || string.IsNullOrWhiteSpace(_chatId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var url = $"https://api.telegram.org/bot{_botToken}/sendMessage";
|
||||
|
||||
var textBuilder = new StringBuilder();
|
||||
textBuilder.AppendLine($"🔥 *{Escape(deal.Title)}*");
|
||||
|
||||
if (deal.CurrentPrice is not null)
|
||||
{
|
||||
textBuilder.AppendLine($"Цена: {deal.CurrentPrice:0.##} ₽");
|
||||
}
|
||||
|
||||
if (deal.RetailPrice is not null && deal.RetailPrice > 0 && deal.CurrentPrice is not null)
|
||||
{
|
||||
textBuilder.AppendLine($"Старая цена: {deal.RetailPrice:0.##} ₽");
|
||||
}
|
||||
|
||||
if (deal.PercentOff is not null)
|
||||
{
|
||||
textBuilder.AppendLine($"Скидка: {deal.PercentOff}%");
|
||||
}
|
||||
|
||||
textBuilder.AppendLine();
|
||||
textBuilder.AppendLine($"Магазин: {Escape(deal.StoreName)}");
|
||||
textBuilder.AppendLine();
|
||||
textBuilder.AppendLine($"[Открыть на Pepper.ru]({Escape(deal.DealUrl)})");
|
||||
|
||||
var payload = new
|
||||
{
|
||||
chat_id = _chatId,
|
||||
text = textBuilder.ToString(),
|
||||
parse_mode = "Markdown"
|
||||
};
|
||||
|
||||
var response = await _httpClient.PostAsJsonAsync(url, payload, cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var body = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
_logger.LogWarning("Не удалось отправить сообщение в Telegram: {Status} {Body}", response.StatusCode, body);
|
||||
}
|
||||
}
|
||||
|
||||
private static string Escape(string value)
|
||||
{
|
||||
return value
|
||||
.Replace("_", "\\_")
|
||||
.Replace("*", "\\*")
|
||||
.Replace("[", "\\[")
|
||||
.Replace("`", "\\`");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user