Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,65 +1,21 @@
using System.Globalization;
using System.Net;
using System.Web;
using Microsoft.Extensions.Logging;
using MimeKit;
using MailKit.Net.Smtp;

using RealEstatesWatcher.AdPostsHandlers.Contracts;
using RealEstatesWatcher.AdPostsHandlers.Templates;
using RealEstatesWatcher.Models;

namespace RealEstatesWatcher.AdPostsHandlers.Email;

public class EmailNotifyingAdPostsHandler(EmailNotifyingAdPostsHandlerSettings settings,
ILogger<EmailNotifyingAdPostsHandler>? logger = null) : IRealEstateAdPostsHandler
{
private static class HtmlTemplates
{
public const string FullPage = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Real Estate Advertisements</title>
</head>
<body style="max-width: 800px; margin:10px auto;">
<maintitle/>
<posts/>
</body>
</html>
""";

public const string TitleNewPosts = """<h1>🏦 <span style="color: #4f4f4f; font-style: italic;">NEW Real estate offer</span></h1>""";

public const string TitleInitialPosts = """<h1>🏦 <span style="color: #4f4f4f; font-style: italic;"> Current Real estate offer</span></h1>""";

public const string Post = """
<div style="padding: 10px; background: #ededed; min-height: 200px;">
<div style="float: left; margin-right: 1em; width: 30%; height: 180px; display: {$img-display};">
<img src="{$img-link}" style="height: 100%; width: 100%; object-fit: cover;" />
</div>
<a href="{$post-link}">
<h3 style="margin: 0.2em; margin-top: 0;">{$title}</h3>
</a>
<span style="font-size: medium; color: #4f4f4f; display: {$price-display};">
<strong>{$price}</strong> {$currency}
<span style="display: {$additional-fees-display};"> + {$additional-fees} {$currency}</span><br/>
</span>
<span style="font-size: medium; color: #4f4f4f; display: {$price-comment-display};">
<strong>{$price-comment}</strong><br/>
</span>
<span>
<strong>Server:</strong> {$portal-name}<br/>
<strong>Adresa:</strong> {$address}<br/>
<strong>Výmera:</strong> {$floor-area}<br/>
<strong>Dispozícia:</strong> {$layout}</br>
</span>
<p style="margin: 0.2em; font-size: small; text-align: justify; display: {$text-display};">{$text}</p>
</div>
""";
}

private readonly EmailNotifyingAdPostsHandlerSettings _settings = settings ?? throw new ArgumentNullException(nameof(settings));

public bool IsEnabled { get; } = settings.Enabled;

public async Task HandleNewRealEstateAdPostAsync(RealEstateAdPost adPost, CancellationToken cancellationToken = default)
Expand All @@ -68,7 +24,7 @@ public async Task HandleNewRealEstateAdPostAsync(RealEstateAdPost adPost, Cancel

logger?.LogDebug("Received new Real Estate Ad Post: {Post}", adPost);

await SendEmailAsync("🆕 New Real Estate Advert published!", CreateHtmlBody(adPost, HtmlTemplates.TitleNewPosts), cancellationToken).ConfigureAwait(false);
await SendEmailAsync("🆕 New Real Estate Advert published!", CreateHtmlBody(adPost, CommonHtmlTemplateElements.TitleNewPosts), cancellationToken).ConfigureAwait(false);
}

public async Task HandleNewRealEstatesAdPostsAsync(IList<RealEstateAdPost> adPosts, CancellationToken cancellationToken = default)
Expand All @@ -77,7 +33,7 @@ public async Task HandleNewRealEstatesAdPostsAsync(IList<RealEstateAdPost> adPos

logger?.LogDebug("Received '{PostsCount}' new Real Estate Ad Posts.", adPosts.Count);

await SendEmailAsync("🆕 New Real Estate Adverts published!", CreateHtmlBody(adPosts, HtmlTemplates.TitleNewPosts), cancellationToken).ConfigureAwait(false);
await SendEmailAsync("🆕 New Real Estate Adverts published!", CreateHtmlBody(adPosts, CommonHtmlTemplateElements.TitleNewPosts), cancellationToken).ConfigureAwait(false);
}

public async Task HandleInitialRealEstateAdPostsAsync(IList<RealEstateAdPost> adPosts, CancellationToken cancellationToken = default)
Expand All @@ -89,10 +45,10 @@ public async Task HandleInitialRealEstateAdPostsAsync(IList<RealEstateAdPost> ad
logger?.LogDebug("Skipping initial notification on {PostsCount} Real Estate Ad posts", adPosts.Count);
return;
}

logger?.LogDebug("Received initial {PostsCount} Real Estate Ad Posts.", adPosts.Count);

await SendEmailAsync("🏦 Current Real Estate Adverts offering", CreateHtmlBody(adPosts, HtmlTemplates.TitleInitialPosts), cancellationToken).ConfigureAwait(false);
await SendEmailAsync("🏦 Current Real Estate Adverts offering", CreateHtmlBody(adPosts, CommonHtmlTemplateElements.TitleInitialPosts), cancellationToken).ConfigureAwait(false);
}

private async Task SendEmailAsync(string subject, string body, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -137,30 +93,41 @@ await client.AuthenticateAsync(new NetworkCredential(_settings.Username,

private static string CreateHtmlBody(RealEstateAdPost adPost, string titleHtmlElement)
{
return HtmlTemplates.FullPage.Replace("<maintitle/>", titleHtmlElement)
return CommonHtmlTemplateElements.FullPage.Replace("<maintitle/>", titleHtmlElement)
.Replace("<posts/>", CreateSingleHtmlPost(adPost));
}

private static string CreateHtmlBody(IEnumerable<RealEstateAdPost> adPosts, string titleHtmlElement)
{
return HtmlTemplates.FullPage.Replace("<maintitle/>", titleHtmlElement)
return CommonHtmlTemplateElements.FullPage.Replace("<maintitle/>", titleHtmlElement)
.Replace("<posts/>", string.Join(Environment.NewLine, adPosts.Select(CreateSingleHtmlPost)));
}

private static string CreateSingleHtmlPost(RealEstateAdPost post)
{
var postHtml = HtmlTemplates.Post
var postHtml = CommonHtmlTemplateElements.Post
.Replace("{$title}", post.Title)
.Replace("{$portal-name}", post.AdsPortalName)
.Replace("{$post-link}", post.WebUrl.AbsoluteUri)
.Replace("{$address}", post.Address);

// address links
if (!string.IsNullOrEmpty(post.Address))
{
postHtml = postHtml.Replace("{$address-links-display}", "inline-block")
.Replace("{$address-encoded}", HttpUtility.UrlEncode(post.Address));
}
else
{
postHtml = postHtml.Replace("{$address-links-display}", "none");
}

// layout
postHtml = postHtml.Replace("{$layout}", post.Layout is not Layout.NotSpecified ? post.Layout.ToDisplayString() : "-");

// floor area
postHtml = post.FloorArea is not null and not decimal.Zero ? postHtml.Replace("{$floor-area}", post.FloorArea + " m²") : postHtml.Replace("{$floor-area}", "-");

// image
if (post.ImageUrl is not null)
{
Expand All @@ -176,7 +143,7 @@ private static string CreateSingleHtmlPost(RealEstateAdPost post)
// price
if (post.Price is not decimal.Zero)
{
postHtml = postHtml.Replace("{$price}", post.Price.ToString("N", new NumberFormatInfo {NumberGroupSeparator = " "}))
postHtml = postHtml.Replace("{$price}", post.Price.ToString("N", new NumberFormatInfo { NumberGroupSeparator = " " }))
.Replace("{$currency}", post.Currency.ToString())
.Replace("{$price-display}", "block")
.Replace("{$price-comment-display}", "none")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\RealEstatesWatcher.AdPostsHandlers.Templates\RealEstatesWatcher.AdPostsHandlers.Templates.csproj" />
<ProjectReference Include="..\RealEstatesWatcher.AdPostsHandlers.Contracts\RealEstatesWatcher.AdPostsHandlers.Contracts.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
using System.Globalization;
using System.Web;
using RealEstatesWatcher.AdPostsHandlers.Contracts;
using RealEstatesWatcher.AdPostsHandlers.Templates;
using RealEstatesWatcher.Models;

namespace RealEstatesWatcher.AdPostsHandlers.File;

public class LocalFileAdPostsHandler(LocalFileAdPostsHandlerSettings settings) : IRealEstateAdPostsHandler
{
private static class HtmlTemplates
{
public const string FullPage = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Real Estate Advertisements</title>
</head>
<body style="max-width: 800px; margin:10px auto;">
<maintitle/>
<posts/>
</body>
</html>
""";

public const string TitleNewPosts = """<h1>🏦 <span style="color: #4f4f4f; font-style: italic;">NEW Real estate offer</span></h1>""";

public const string TitleInitialPosts = """<h1>🏦 <span style="color: #4f4f4f; font-style: italic;"> Current Real estate offer</span></h1>""";

public const string Post = "<div style=\"padding: 10px; background: #ededed; min-height: 200px;\">\r\n <div style=\"float: left; margin-right: 1em; width: 30%; height: 180px; display: {$img-display};\">\r\n <img src=\"{$img-link}\" style=\"height: 100%; width: 100%; object-fit: cover;\" />\r\n </div>\r\n <a href=\"{$post-link}\">\r\n <h3 style=\"margin: 0.2em; margin-top: 0;\">{$title}</h3>\r\n </a>\r\n <span style=\"font-size: medium; color: #4f4f4f; display: {$price-display};\">\r\n <strong>{$price}</strong> {$currency}\r\n <span style=\"display: {$additional-fees-display};\"> + {$additional-fees} {$currency}</span><br/>\r\n </span>\r\n <span style=\"font-size: medium; color: #4f4f4f; display: {$price-comment-display};\">\r\n <strong>{$price-comment}</strong><br/>\r\n </span>\r\n <span>\r\n <strong>Server:</strong> {$portal-name}<br/>\r\n <strong>Adresa:</strong> {$address}<br/>\r\n <strong>Výmera:</strong> {$floor-area}<br/>\r\n <strong>Dispozícia:</strong> {$layout}</br>\r\n </span>\r\n <p style=\"margin: 0.2em; font-size: small; text-align: justify; display: {$text-display};\">{$text}</p>\r\n</div>";
}

private readonly LocalFileAdPostsHandlerSettings _settings = settings ?? throw new ArgumentNullException(nameof(settings));

public bool IsEnabled { get; } = settings.Enabled;
Expand Down Expand Up @@ -99,7 +78,7 @@ private async Task WriteNewAdPostsToFileInHtmlAsync(IList<RealEstateAdPost> adPo
else
{
// create new content
pageContent = HtmlTemplates.FullPage.Replace("<maintitle/>", HtmlTemplates.TitleNewPosts);
pageContent = CommonHtmlTemplateElements.FullPage.Replace("<maintitle/>", CommonHtmlTemplateElements.TitleNewPosts);

var index = pageContent.IndexOf("<posts/>", StringComparison.Ordinal);
pageContent = pageContent.Insert(index + 8, htmlPostsElements + Environment.NewLine);
Expand All @@ -120,7 +99,7 @@ private async Task WriteInitialAdPostsToFileInHtmlAsync(IList<RealEstateAdPost>
var posts = adPosts.Select(CreateHtmlPostElement);

// insert title
var page = HtmlTemplates.FullPage.Replace("<maintitle/>", HtmlTemplates.TitleInitialPosts);
var page = CommonHtmlTemplateElements.FullPage.Replace("<maintitle/>", CommonHtmlTemplateElements.TitleInitialPosts);

var index = page.IndexOf("<posts/>", StringComparison.Ordinal);
page = page.Insert(index + 8, string.Join(Environment.NewLine, posts));
Expand Down Expand Up @@ -155,12 +134,23 @@ private static async Task WriteToFileAsync(string? path, string content, bool ap

private static string CreateHtmlPostElement(RealEstateAdPost post)
{
var postHtml = HtmlTemplates.Post
var postHtml = CommonHtmlTemplateElements.Post
.Replace("{$title}", post.Title)
.Replace("{$portal-name}", post.AdsPortalName)
.Replace("{$post-link}", post.WebUrl.AbsoluteUri)
.Replace("{$address}", post.Address);

// address links
if (!string.IsNullOrEmpty(post.Address))
{
postHtml = postHtml.Replace("{$address-links-display}", "inline-block")
.Replace("{$address-encoded}", HttpUtility.UrlEncode(post.Address));
}
else
{
postHtml = postHtml.Replace("{$address-links-display}", "none");
}

// layout
postHtml = postHtml.Replace("{$layout}", post.Layout is not Layout.NotSpecified ? post.Layout.ToDisplayString() : "-");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\RealEstatesWatcher.AdPostsHandlers.Templates\RealEstatesWatcher.AdPostsHandlers.Templates.csproj" />
<ProjectReference Include="..\RealEstatesWatcher.AdPostsHandlers.Contracts\RealEstatesWatcher.AdPostsHandlers.Contracts.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace RealEstatesWatcher.AdPostsHandlers.Templates;

public static class CommonHtmlTemplateElements
{
public const string FullPage = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Real Estate Advertisements</title>
</head>
<body style="max-width: 800px; margin:10px auto;">
<maintitle/>
<posts/>
</body>
</html>
""";

public const string TitleNewPosts = """
<h1>🏦 <span style="color: #4f4f4f; font-style: italic;">NEW Real estate offer</span></h1>
""";

public const string TitleInitialPosts = """
<h1>🏦 <span style="color: #4f4f4f; font-style: italic;"> Current Real estate offer</span></h1>
""";

public const string Post = """
<div style="padding: 10px; background: #ededed; min-height: 200px;">
<div style="float: left; margin-right: 1em; width: 30%; height: 180px; display: {$img-display};">
<img src="{$img-link}" style="height: 100%; width: 100%; object-fit: cover;" alt="Ad main visual presentation"/>
</div>
<a href="{$post-link}">
<h3 style="margin: 0.2em; margin-top: 0;">{$title}</h3>
</a>
<span style="font-size: medium; color: #4f4f4f; display: {$price-display};">
<strong>{$price}</strong> {$currency}
<span style="display: {$additional-fees-display};"> + {$additional-fees} {$currency}</span><br/>
</span>
<span style="font-size: medium; color: #4f4f4f; display: {$price-comment-display};">
<strong>{$price-comment}</strong><br/>
</span>
<span>
<strong>Server:</strong> {$portal-name}<br/>
<strong>Adresa:</strong> {$address}
<span style="display: {$address-links-display};">
<a target="_blank" rel="noopener noreferrer" href="https://www.google.com/maps/search/?api=1&query={$address-encoded}"><img alt="Google's map logo" title="Otvoriť v Google Mapách" src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Google_Maps_icon_%282020%29.svg/32px-Google_Maps_icon_%282020%29.svg.png?20200218211225" style="margin-left: 6px; max-height: 14px; width: auto" /></a>
<a target="_blank" rel="noopener noreferrer" href="https://mapy.com/fnc/v1/search?query={$address-encoded}"><img alt="Icon of Mapy.com" title="Otvoriť na Mapy.com" src="https://mapy.com/img/favicon/common/plain/favicon-32x32.png" style="max-height: 14px; width: auto"></a>
<a target="_blank" rel="noopener noreferrer" href="http://maps.apple.com/?q={$address-encoded}"><img alt="Icon of Apple Maps" title="Otvoriť v Apple Mapách" src="https://maps.apple.com/static/maps-app-web-client/images/maps-app-icon-120x120.png" style="max-height: 14px; width: auto"></a>
</span><br/>
<strong>Výmera:</strong> {$floor-area}<br/>
<strong>Dispozícia:</strong> {$layout}<br/>
</span>
<p style="margin: 0.2em; font-size: small; text-align: justify; display: {$text-display};">{$text}</p>
</div>
""";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
1 change: 1 addition & 0 deletions Real Estates Watcher.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<Project Path="Handlers/RealEstatesWatcher.AdPostsHandlers.Contracts/RealEstatesWatcher.AdPostsHandlers.Contracts.csproj" />
<Project Path="Handlers/RealEstatesWatcher.AdPostsHandlers.Email/RealEstatesWatcher.AdPostsHandlers.Email.csproj" />
<Project Path="Handlers/RealEstatesWatcher.AdPostsHandlers.File/RealEstatesWatcher.AdPostsHandlers.File.csproj" />
<Project Path="Handlers/RealEstatesWatcher.AdPostsHandlers.Templates/RealEstatesWatcher.AdPostsHandlers.Templates.csproj" />
</Folder>
<Folder Name="/Portals/">
<Project Path="Portals/RealEstatesWatcher.AdsPortals.Base/RealEstatesWatcher.AdsPortals.Base.csproj" />
Expand Down
Loading
Loading