diff --git a/Directory.Build.props b/Directory.Build.props index 0751e1d..e9b2585 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 0.1.31 + 0.1.10 diff --git a/Femto.Api/Auth/CookieSettings.cs b/Femto.Api/Auth/CookieSettings.cs index a98a5cb..449a2cf 100644 --- a/Femto.Api/Auth/CookieSettings.cs +++ b/Femto.Api/Auth/CookieSettings.cs @@ -2,7 +2,6 @@ namespace Femto.Api.Auth; public class CookieSettings { - public SameSiteMode SameSite { get; set; } + public bool SameSite { get; set; } public bool Secure { get; set; } - public string? Domain { get; set; } } \ No newline at end of file diff --git a/Femto.Api/Auth/SessionAuthenticationHandler.cs b/Femto.Api/Auth/SessionAuthenticationHandler.cs index 9c44b04..01ec20e 100644 --- a/Femto.Api/Auth/SessionAuthenticationHandler.cs +++ b/Femto.Api/Auth/SessionAuthenticationHandler.cs @@ -3,11 +3,11 @@ using System.Text.Encodings.Web; using Femto.Api.Sessions; using Femto.Common; using Femto.Modules.Auth.Application; -using Femto.Modules.Auth.Application.Dto; -using Femto.Modules.Auth.Contracts; -using Femto.Modules.Auth.Models; +using Femto.Modules.Auth.Application.Interface.ValidateSession; +using Femto.Modules.Auth.Errors; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Extensions; namespace Femto.Api.Auth; @@ -15,115 +15,49 @@ internal class SessionAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, - IAuthService authService, - CurrentUserContext currentUserContext + IAuthModule authModule, + CurrentUserContext currentUserContext, + IOptions cookieOptions ) : AuthenticationHandler(options, logger, encoder) { protected override async Task HandleAuthenticateAsync() { - Logger.LogDebug("{TraceId} Authenticating session", this.Context.TraceIdentifier); - - var user = await this.TryAuthenticateWithSession(); - - if (user is null) - user = await this.TryAuthenticateWithRememberMeToken(); - - if (user is null) + var sessionId = this.Request.Cookies["session"]; + if (string.IsNullOrWhiteSpace(sessionId)) return AuthenticateResult.NoResult(); - var claims = new List + try { - new(ClaimTypes.Name, user.Username), - new("sub", user.Id.ToString()), - new("user_id", user.Id.ToString()), - }; + var result = await authModule.Command(new ValidateSessionCommand(sessionId)); - claims.AddRange(user.Roles.Select(role => new Claim(ClaimTypes.Role, role.ToString()))); + var claims = new List + { + new(ClaimTypes.Name, result.User.Username), + new("sub", result.User.Id.ToString()), + new("user_id", result.User.Id.ToString()), + }; - var identity = new ClaimsIdentity(claims, this.Scheme.Name); - var principal = new ClaimsPrincipal(identity); - currentUserContext.CurrentUser = new CurrentUser( - user.Id, - user.Username, - user.Roles.Contains(Role.SuperUser) - ); + claims.AddRange( + result.User.Roles.Select(role => new Claim(ClaimTypes.Role, role.ToString())) + ); - return AuthenticateResult.Success(new AuthenticationTicket(principal, this.Scheme.Name)); - } + var identity = new ClaimsIdentity(claims, this.Scheme.Name); + var principal = new ClaimsPrincipal(identity); - private async Task TryAuthenticateWithSession() - { - var sessionId = this.Context.GetSessionId(); + this.Context.SetSession(result.Session, result.User, cookieOptions.Value); + currentUserContext.CurrentUser = new CurrentUser( + result.User.Id, + result.User.Username, + result.Session.SessionId + ); - if (sessionId is null) - { - Logger.LogDebug("{TraceId} SessionId was null ", this.Context.TraceIdentifier); - return null; + return AuthenticateResult.Success( + new AuthenticationTicket(principal, this.Scheme.Name) + ); } - - var session = await authService.GetSession(sessionId); - - if (session is null) + catch (InvalidSessionError) { - Logger.LogDebug("{TraceId} Loaded session was null ", this.Context.TraceIdentifier); - return null; + return AuthenticateResult.Fail("Invalid session"); } - - if (session.IsExpired) - { - Logger.LogDebug("{TraceId} Loaded session was expired ", this.Context.TraceIdentifier); - await authService.DeleteSession(sessionId); - this.Context.DeleteSession(); - return null; - } - - var user = await authService.GetUserWithId(session.UserId); - - if (user is null) - { - await authService.DeleteSession(sessionId); - this.Context.DeleteSession(); - return null; - } - - if (session.ExpiresSoon) - { - session = await authService.CreateWeakSession(session.UserId); - this.Context.SetSession(session, user); - } - - return user; - } - - private async Task TryAuthenticateWithRememberMeToken() - { - /* - * load remember me from token - * if it is null, return null - * if it exists, validate it - * if it is valid, create a new weak session, return the user - * if it is almost expired, refresh it - */ - - var rememberMeToken = this.Context.GetRememberMeToken(); - - if (rememberMeToken is null) - return null; - - var (user, newRememberMeToken) = await authService.GetUserWithRememberMeToken( - rememberMeToken - ); - - if (user is null) - return null; - - var session = await authService.CreateWeakSession(user.Id); - - this.Context.SetSession(session, user); - - if (newRememberMeToken is not null) - this.Context.SetRememberMeToken(newRememberMeToken); - - return user; } } diff --git a/Femto.Api/Controllers/Auth/AuthController.cs b/Femto.Api/Controllers/Auth/AuthController.cs index c9108da..953926d 100644 --- a/Femto.Api/Controllers/Auth/AuthController.cs +++ b/Femto.Api/Controllers/Auth/AuthController.cs @@ -1,174 +1,94 @@ +using Femto.Api.Auth; using Femto.Api.Sessions; using Femto.Common; using Femto.Modules.Auth.Application; +using Femto.Modules.Auth.Application.Dto; +using Femto.Modules.Auth.Application.Interface.CreateSignupCode; +using Femto.Modules.Auth.Application.Interface.GetSignupCodesQuery; +using Femto.Modules.Auth.Application.Interface.Login; +using Femto.Modules.Auth.Application.Interface.RefreshUserSession; +using Femto.Modules.Auth.Application.Interface.Register; using Femto.Modules.Auth.Contracts; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; namespace Femto.Api.Controllers.Auth; [ApiController] [Route("auth")] -public class AuthController(ICurrentUserContext currentUserContext, IAuthService authService) - : ControllerBase +public class AuthController( + IAuthModule authModule, + IOptions cookieSettings, + ICurrentUserContext currentUserContext +) : ControllerBase { [HttpPost("login")] - public async Task> Login( - [FromBody] LoginRequest request, - CancellationToken cancellationToken - ) + public async Task> Login([FromBody] LoginRequest request) { - var result = await authService.AuthenticateUserCredentials( - request.Username, - request.Password, - cancellationToken + var result = await authModule.Command(new LoginCommand(request.Username, request.Password)); + + HttpContext.SetSession(result.Session, result.User, cookieSettings.Value); + + return new LoginResponse( + result.User.Id, + result.User.Username, + result.User.Roles.Any(r => r == Role.SuperUser) ); - - if (result is null) - return this.BadRequest(); - - var (user, session) = result; - - HttpContext.SetSession(session, user); - - if (request.RememberMe) - { - var newRememberMeToken = await authService.CreateRememberMeToken(user.Id); - HttpContext.SetRememberMeToken(newRememberMeToken); - } - - return new LoginResponse(user.Id, user.Username, user.Roles.Any(r => r == Role.SuperUser)); } [HttpPost("register")] - public async Task> Register( - [FromBody] RegisterRequest request, - CancellationToken cancellationToken - ) + public async Task> Register([FromBody] RegisterRequest request) { - var (user, session) = await authService.CreateUserWithCredentials( - request.Username, - request.Password, - request.SignupCode, - cancellationToken + var result = await authModule.Command( + new RegisterCommand(request.Username, request.Password, request.SignupCode) ); - HttpContext.SetSession(session, user); - - if (request.RememberMe) - { - var newRememberMeToken = await authService.CreateRememberMeToken(user.Id); - HttpContext.SetRememberMeToken(newRememberMeToken); - } + HttpContext.SetSession(result.Session, result.User, cookieSettings.Value); return new RegisterResponse( - user.Id, - user.Username, - user.Roles.Any(r => r == Role.SuperUser) + result.User.Id, + result.User.Username, + result.User.Roles.Any(r => r == Role.SuperUser) ); } - [HttpPost("change-password")] - public async Task ChangePassword( - [FromBody] ChangePasswordRequestBody req, - CancellationToken cancellationToken - ) - { - if (currentUserContext.CurrentUser is not {} user) - return this.BadRequest(); - - // superuser do what superuser want - if (!user.IsSuperUser) - { - if (user.Id != req.UserId) - return this.BadRequest(); - - var session = await authService.GetSession(this.HttpContext.GetSessionId()!); - - // require strong authentication to change password - // the user can re-enter their password - if (session is null || !session.IsStronglyAuthenticated) - return this.BadRequest(); - } - - await authService.ChangePassword(req.UserId, req.NewPassword, cancellationToken); - - // TODO would be better do handle this from inside the auth service. maybe just have it happen in a post-save event handler? - await authService.InvalidateUserSessions(req.UserId, cancellationToken); - - return this.Ok(new {}); - } - - [HttpPost("delete-current-session")] - public async Task DeleteSessionV2() - { - var sessionId = HttpContext.GetSessionId(); - - if (sessionId is not null) - { - await authService.DeleteSession(sessionId); - HttpContext.DeleteSession(); - } - - var rememberMeToken = HttpContext.GetRememberMeToken(); - - if (rememberMeToken is not null) - { - await authService.DeleteRememberMeToken(rememberMeToken); - HttpContext.DeleteRememberMeToken(); - } - - return Ok(new { }); - } - - [Obsolete("use POST /auth/delete-current-session")] [HttpDelete("session")] public async Task DeleteSession() { - var sessionId = HttpContext.GetSessionId(); - - if (sessionId is not null) - { - await authService.DeleteSession(sessionId); - HttpContext.DeleteSession(); - } - - var rememberMeToken = HttpContext.GetRememberMeToken(); - - if (rememberMeToken is not null) - { - await authService.DeleteRememberMeToken(rememberMeToken); - HttpContext.DeleteRememberMeToken(); - } - + HttpContext.DeleteSession(); return Ok(new { }); } [HttpGet("user/{userId}")] [Authorize] - public async Task> GetUserInfo( + public async Task> RefreshUser( Guid userId, CancellationToken cancellationToken ) { - var currentUser = currentUserContext.CurrentUser; + var currentUser = currentUserContext.CurrentUser!; - if (currentUser is null || currentUser.Id != userId) - return this.BadRequest(); + try + { + var result = await authModule.Command( + new RefreshUserSessionCommand(userId, currentUser), + cancellationToken + ); - var user = await authService.GetUserWithId(userId, cancellationToken); - - if (user is null) - return this.BadRequest(); - - return new GetUserInfoResult( - user.Id, - user.Username, - user.Roles.Any(r => r == Role.SuperUser) - ); + return new RefreshUserResult( + result.User.Id, + result.User.Username, + result.User.Roles.Any(r => r == Role.SuperUser) + ); + } + catch (Exception) + { + HttpContext.DeleteSession(); + return this.Forbid(); + } } - [Obsolete("use POST /auth/create-signup-code")] [HttpPost("signup-codes")] [Authorize(Roles = "SuperUser")] public async Task CreateSignupCode( @@ -176,51 +96,21 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService CancellationToken cancellationToken ) { - await authService.AddSignupCode(request.Code, request.Name, cancellationToken); + await authModule.Command( + new CreateSignupCodeCommand(request.Code, request.Email, request.Name), + cancellationToken + ); return Ok(new { }); } - [Obsolete("use GET /auth/list-signup-codes")] [HttpGet("signup-codes")] [Authorize(Roles = "SuperUser")] public async Task> ListSignupCodes( CancellationToken cancellationToken ) { - var codes = await authService.GetSignupCodes(cancellationToken); - - return new ListSignupCodesResult( - codes.Select(c => new SignupCodeDto( - c.Code, - c.Email, - c.Name, - c.RedeemedByUserId, - c.RedeemedByUsername, - c.ExpiresOn - )) - ); - } - - [HttpPost("create-signup-code")] - [Authorize(Roles = "SuperUser")] - public async Task CreateSignupCodeV2( - [FromBody] CreateSignupCodeRequest request, - CancellationToken cancellationToken - ) - { - await authService.AddSignupCode(request.Code, request.Name, cancellationToken); - - return Ok(new { }); - } - - [HttpGet("list-signup-codes")] - [Authorize(Roles = "SuperUser")] - public async Task> ListSignupCodesV2( - CancellationToken cancellationToken - ) - { - var codes = await authService.GetSignupCodes(cancellationToken); + var codes = await authModule.Query(new GetSignupCodesQuery(), cancellationToken); return new ListSignupCodesResult( codes.Select(c => new SignupCodeDto( diff --git a/Femto.Api/Controllers/Auth/ChangePasswordRequestBody.cs b/Femto.Api/Controllers/Auth/ChangePasswordRequestBody.cs deleted file mode 100644 index 77f1dcd..0000000 --- a/Femto.Api/Controllers/Auth/ChangePasswordRequestBody.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Api.Controllers.Auth; - -public record ChangePasswordRequestBody(Guid UserId, string NewPassword); \ No newline at end of file diff --git a/Femto.Api/Controllers/Auth/GetUserInfoResult.cs b/Femto.Api/Controllers/Auth/GetUserInfoResult.cs deleted file mode 100644 index 0212f32..0000000 --- a/Femto.Api/Controllers/Auth/GetUserInfoResult.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Api.Controllers.Auth; - -public record GetUserInfoResult(Guid UserId, string Username, bool IsSuperUser); \ No newline at end of file diff --git a/Femto.Api/Controllers/Auth/LoginRequest.cs b/Femto.Api/Controllers/Auth/LoginRequest.cs index 6c09e64..8366d14 100644 --- a/Femto.Api/Controllers/Auth/LoginRequest.cs +++ b/Femto.Api/Controllers/Auth/LoginRequest.cs @@ -1,3 +1,3 @@ namespace Femto.Api.Controllers.Auth; -public record LoginRequest(string Username, string Password, bool RememberMe); \ No newline at end of file +public record LoginRequest(string Username, string Password); \ No newline at end of file diff --git a/Femto.Api/Controllers/Auth/RefreshUserResult.cs b/Femto.Api/Controllers/Auth/RefreshUserResult.cs new file mode 100644 index 0000000..8dbdee8 --- /dev/null +++ b/Femto.Api/Controllers/Auth/RefreshUserResult.cs @@ -0,0 +1,3 @@ +namespace Femto.Api.Controllers.Auth; + +public record RefreshUserResult(Guid UserId, string Username, bool IsSuperUser); \ No newline at end of file diff --git a/Femto.Api/Controllers/Auth/RegisterRequest.cs b/Femto.Api/Controllers/Auth/RegisterRequest.cs index ee21297..f386198 100644 --- a/Femto.Api/Controllers/Auth/RegisterRequest.cs +++ b/Femto.Api/Controllers/Auth/RegisterRequest.cs @@ -1,3 +1,3 @@ namespace Femto.Api.Controllers.Auth; -public record RegisterRequest(string Username, string Password, string SignupCode, bool RememberMe); \ No newline at end of file +public record RegisterRequest(string Username, string Password, string SignupCode, string? Email); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/AddPostCommentRequest.cs b/Femto.Api/Controllers/Posts/Dto/AddPostCommentRequest.cs deleted file mode 100644 index 7546af0..0000000 --- a/Femto.Api/Controllers/Posts/Dto/AddPostCommentRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Api.Controllers.Posts.Dto; - -public record AddPostCommentRequest(Guid AuthorId, string Content); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/AddPostReactionRequest.cs b/Femto.Api/Controllers/Posts/Dto/AddPostReactionRequest.cs deleted file mode 100644 index 36330cf..0000000 --- a/Femto.Api/Controllers/Posts/Dto/AddPostReactionRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Api.Controllers.Posts.Dto; - -public record AddPostReactionRequest(string Emoji); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/CreatePostResponse.cs b/Femto.Api/Controllers/Posts/Dto/CreatePostResponse.cs index 1cec414..a03dd93 100644 --- a/Femto.Api/Controllers/Posts/Dto/CreatePostResponse.cs +++ b/Femto.Api/Controllers/Posts/Dto/CreatePostResponse.cs @@ -1,3 +1,3 @@ namespace Femto.Api.Controllers.Posts.Dto; -public record CreatePostResponse(PostDto Post); \ No newline at end of file +public record CreatePostResponse(Guid PostId); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/DeletePostReactionRequest.cs b/Femto.Api/Controllers/Posts/Dto/DeletePostReactionRequest.cs deleted file mode 100644 index cb39e0e..0000000 --- a/Femto.Api/Controllers/Posts/Dto/DeletePostReactionRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Api.Controllers.Posts.Dto; - -public record DeletePostReactionRequest(string Emoji); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/LoadPostsResponse.cs b/Femto.Api/Controllers/Posts/Dto/GetAllPublicPostsResponse.cs similarity index 51% rename from Femto.Api/Controllers/Posts/Dto/LoadPostsResponse.cs rename to Femto.Api/Controllers/Posts/Dto/GetAllPublicPostsResponse.cs index 54b9df7..ededad1 100644 --- a/Femto.Api/Controllers/Posts/Dto/LoadPostsResponse.cs +++ b/Femto.Api/Controllers/Posts/Dto/GetAllPublicPostsResponse.cs @@ -3,4 +3,4 @@ using JetBrains.Annotations; namespace Femto.Api.Controllers.Posts.Dto; [PublicAPI] -public record LoadPostsResponse(IEnumerable Posts); \ No newline at end of file +public record GetAllPublicPostsResponse(IEnumerable Posts, Guid? Next); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/GetPostResponse.cs b/Femto.Api/Controllers/Posts/Dto/GetPostResponse.cs deleted file mode 100644 index b44740c..0000000 --- a/Femto.Api/Controllers/Posts/Dto/GetPostResponse.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Api.Controllers.Posts.Dto; - -public record GetPostResponse(PostDto Post); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/GetPublicPostsSearchParams.cs b/Femto.Api/Controllers/Posts/Dto/GetPublicPostsSearchParams.cs index 3705456..e5155f6 100644 --- a/Femto.Api/Controllers/Posts/Dto/GetPublicPostsSearchParams.cs +++ b/Femto.Api/Controllers/Posts/Dto/GetPublicPostsSearchParams.cs @@ -3,4 +3,4 @@ using JetBrains.Annotations; namespace Femto.Api.Controllers.Posts.Dto; [PublicAPI] -public record GetPublicPostsSearchParams(Guid? After, int? Amount, Guid? AuthorId, string? Author); \ No newline at end of file +public record GetPublicPostsSearchParams(Guid? From, int? Amount, Guid? AuthorId, string? Author); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/PostCommentDto.cs b/Femto.Api/Controllers/Posts/Dto/PostCommentDto.cs deleted file mode 100644 index 04e180a..0000000 --- a/Femto.Api/Controllers/Posts/Dto/PostCommentDto.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Api.Controllers.Posts.Dto; - -public record PostCommentDto(string Author, string Content, DateTimeOffset PostedOn); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/PostDto.cs b/Femto.Api/Controllers/Posts/Dto/PostDto.cs index c9af7c6..c48014a 100644 --- a/Femto.Api/Controllers/Posts/Dto/PostDto.cs +++ b/Femto.Api/Controllers/Posts/Dto/PostDto.cs @@ -8,21 +8,5 @@ public record PostDto( Guid PostId, string Content, IEnumerable Media, - IEnumerable Reactions, - DateTimeOffset CreatedAt, - IEnumerable PossibleReactions, - IEnumerable Comments -) -{ - public static PostDto FromModel(Modules.Blog.Application.Queries.GetPosts.Dto.PostDto post) => - new( - new PostAuthorDto(post.Author.AuthorId, post.Author.Username), - post.PostId, - post.Text, - post.Media.Select(m => new PostMediaDto(m.Url, m.Width, m.Height)), - post.Reactions.Select(r => new PostReactionDto(r.Emoji, r.AuthorName, r.ReactedOn)), - post.CreatedAt, - post.PossibleReactions, - post.Comments.Select(c => new PostCommentDto(c.Author, c.Content, c.PostedOn)) - ); -} \ No newline at end of file + DateTimeOffset CreatedAt +); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/PostReactionDto.cs b/Femto.Api/Controllers/Posts/Dto/PostReactionDto.cs deleted file mode 100644 index f9934c6..0000000 --- a/Femto.Api/Controllers/Posts/Dto/PostReactionDto.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Api.Controllers.Posts.Dto; - -public record PostReactionDto(string Emoji, string AuthorName, DateTimeOffset ReactedOn); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/PostsController.cs b/Femto.Api/Controllers/Posts/PostsController.cs index ed882f7..9eeeafc 100644 --- a/Femto.Api/Controllers/Posts/PostsController.cs +++ b/Femto.Api/Controllers/Posts/PostsController.cs @@ -1,11 +1,7 @@ using Femto.Api.Controllers.Posts.Dto; using Femto.Common; using Femto.Modules.Blog.Application; -using Femto.Modules.Blog.Application.Commands.AddPostComment; -using Femto.Modules.Blog.Application.Commands.AddPostReaction; -using Femto.Modules.Blog.Application.Commands.ClearPostReaction; using Femto.Modules.Blog.Application.Commands.CreatePost; -using Femto.Modules.Blog.Application.Commands.DeletePost; using Femto.Modules.Blog.Application.Queries.GetPosts; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -14,19 +10,18 @@ namespace Femto.Api.Controllers.Posts; [ApiController] [Route("posts")] -public class PostsController(IBlogModule blogModule, ICurrentUserContext currentUserContext, IAuthorizationService auth) - : ControllerBase +public class PostsController(IBlogModule blogModule, ICurrentUserContext currentUserContext) : ControllerBase { [HttpGet] - public async Task> LoadPosts( + public async Task> LoadPosts( [FromQuery] GetPublicPostsSearchParams searchParams, CancellationToken cancellationToken ) { - var res = await blogModule.Query( + var res = await blogModule.PostQuery( new GetPostsQuery(currentUserContext.CurrentUser?.Id) { - After = searchParams.After, + From = searchParams.From, Amount = searchParams.Amount ?? 20, AuthorId = searchParams.AuthorId, Author = searchParams.Author, @@ -34,7 +29,16 @@ public class PostsController(IBlogModule blogModule, ICurrentUserContext current cancellationToken ); - return new LoadPostsResponse(res.Posts.Select(PostDto.FromModel)); + return new GetAllPublicPostsResponse( + res.Posts.Select(p => new PostDto( + new PostAuthorDto(p.Author.AuthorId, p.Author.Username), + p.PostId, + p.Text, + p.Media.Select(m => new PostMediaDto(m.Url, m.Width, m.Height)), + p.CreatedAt + )), + res.Next + ); } [HttpPost] @@ -44,7 +48,7 @@ public class PostsController(IBlogModule blogModule, ICurrentUserContext current CancellationToken cancellationToken ) { - var post = await blogModule.Command( + var guid = await blogModule.PostCommand( new CreatePostCommand( req.AuthorId, req.Content, @@ -59,96 +63,11 @@ public class PostsController(IBlogModule blogModule, ICurrentUserContext current media.Height ) ), - req.IsPublic, - currentUserContext.CurrentUser! + req.IsPublic ), cancellationToken ); - return new CreatePostResponse(PostDto.FromModel(post)); - } - - [HttpGet("{postId}")] - public async Task> GetPost( - Guid postId, - CancellationToken cancellationToken - ) - { - var result = await blogModule.Query( - new GetPostsQuery(postId, currentUserContext.CurrentUser?.Id), - cancellationToken - ); - - var post = result.Posts.SingleOrDefault(); - - if (post is null) - return NotFound(); - - return new GetPostResponse(PostDto.FromModel(post)); - } - - [HttpDelete("{postId}")] - [Authorize] - public async Task DeletePost(Guid postId, CancellationToken cancellationToken) - { - await blogModule.Command( - new DeletePostCommand(postId, currentUserContext.CurrentUser!.Id), - cancellationToken - ); - } - - [HttpPost("{postId}/reactions")] - [Authorize] - public async Task AddPostReaction( - Guid postId, - [FromBody] AddPostReactionRequest request, - CancellationToken cancellationToken - ) - { - var currentUser = currentUserContext.CurrentUser!; - - await blogModule.Command( - new AddPostReactionCommand(postId, request.Emoji, currentUser.Id), - cancellationToken - ); - - return this.Ok(); - } - - [HttpDelete("{postId}/reactions")] - [Authorize] - public async Task DeletePostReaction( - Guid postId, - [FromBody] DeletePostReactionRequest request, - CancellationToken cancellationToken - ) - { - var currentUser = currentUserContext.CurrentUser!; - - await blogModule.Command( - new ClearPostReactionCommand(postId, request.Emoji, currentUser.Id), - cancellationToken - ); - - return this.Ok(); - } - - [HttpPost("{postId}/comments")] - [Authorize] - public async Task AddPostComment( - Guid postId, - [FromBody] AddPostCommentRequest request, - CancellationToken cancellationToken - ) - { - if (currentUserContext.CurrentUser?.Id != request.AuthorId) - return this.BadRequest(); - - await blogModule.Command( - new AddPostCommentCommand(postId, request.AuthorId, request.Content), - cancellationToken - ); - - return this.Ok(); + return new CreatePostResponse(guid); } } diff --git a/Femto.Api/CurrentUserContext.cs b/Femto.Api/CurrentUserContext.cs index 6608a50..1754dbe 100644 --- a/Femto.Api/CurrentUserContext.cs +++ b/Femto.Api/CurrentUserContext.cs @@ -5,4 +5,4 @@ namespace Femto.Api; internal class CurrentUserContext : ICurrentUserContext { public CurrentUser? CurrentUser { get; set; } -} +} \ No newline at end of file diff --git a/Femto.Api/Program.cs b/Femto.Api/Program.cs index ebb81fd..29b75e4 100644 --- a/Femto.Api/Program.cs +++ b/Femto.Api/Program.cs @@ -21,17 +21,6 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); -var loggerFactory = LoggerFactory.Create(b => -{ - b.SetMinimumLevel(LogLevel.Information) - .AddConfiguration(builder.Configuration.GetSection("Logging")) - .AddConsole() - .AddDebug(); -}); - -builder.Services.AddSingleton(loggerFactory); -builder.Services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); - var connectionString = builder.Configuration.GetConnectionString("Database"); if (connectionString is null) throw new Exception("no database connection string found"); @@ -44,9 +33,9 @@ if (blobStorageRoot is null) var eventBus = new EventBus(Channel.CreateUnbounded()); builder.Services.AddHostedService(_ => eventBus); -builder.Services.InitializeBlogModule(connectionString, eventBus, loggerFactory); +builder.Services.InitializeBlogModule(connectionString, eventBus); builder.Services.InitializeMediaModule(connectionString, blobStorageRoot); -builder.Services.InitializeAuthenticationModule(connectionString, eventBus, loggerFactory, TimeProvider.System); +builder.Services.InitializeAuthenticationModule(connectionString, eventBus); builder.Services.AddScoped(); builder.Services.AddScoped(s => s.GetRequiredService()); diff --git a/Femto.Api/Properties/launchSettings.json b/Femto.Api/Properties/launchSettings.json index b024278..237dc27 100644 --- a/Femto.Api/Properties/launchSettings.json +++ b/Femto.Api/Properties/launchSettings.json @@ -14,7 +14,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, - "applicationUrl": "https://localhost:7269", + "applicationUrl": "https://0.0.0.0:7269;http://0.0.0.0:5181", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Femto.Api/Sessions/HttpContextSessionExtensions.cs b/Femto.Api/Sessions/HttpContextSessionExtensions.cs index 2b8ee96..865467e 100644 --- a/Femto.Api/Sessions/HttpContextSessionExtensions.cs +++ b/Femto.Api/Sessions/HttpContextSessionExtensions.cs @@ -1,52 +1,49 @@ using System.Text.Json; +using System.Text.Json.Serialization; using Femto.Api.Auth; using Femto.Modules.Auth.Application.Dto; -using Femto.Modules.Auth.Models; -using Microsoft.Extensions.Options; namespace Femto.Api.Sessions; -internal record SessionInfo(string? SessionId, Guid? UserId); - internal static class HttpContextSessionExtensions { - private static readonly JsonSerializerOptions JsonOptions = new() + public static void SetSession( + this HttpContext httpContext, + Session session, + UserInfo user, + CookieSettings cookieSettings + ) { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; + var secure = cookieSettings.Secure; + var sameSite = cookieSettings.SameSite ? SameSiteMode.Strict : SameSiteMode.Unspecified; + var expires = session.Expires; - public static string? GetSessionId(this HttpContext httpContext) => - httpContext.Request.Cookies["sid"]; - - public static void SetSession(this HttpContext context, Session session, UserInfo user) - { - var cookieSettings = context.RequestServices.GetRequiredService>(); - - context.Response.Cookies.Append( - "sid", - session.Id, + httpContext.Response.Cookies.Append( + "session", + session.SessionId, new CookieOptions { - Path = "/", - IsEssential = true, - Domain = cookieSettings.Value.Domain, HttpOnly = true, - Secure = cookieSettings.Value.Secure, - SameSite = cookieSettings.Value.SameSite, - Expires = session.Expires, + Secure = secure, + SameSite = sameSite, + Expires = expires, } ); - context.Response.Cookies.Append( + httpContext.Response.Cookies.Append( "user", - JsonSerializer.Serialize(user, JsonOptions), + JsonSerializer.Serialize( + user, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter() }, + } + ), new CookieOptions { - Path = "/", - Domain = cookieSettings.Value.Domain, - IsEssential = true, - Secure = cookieSettings.Value.Secure, - SameSite = cookieSettings.Value.SameSite, + Secure = cookieSettings.Secure, + SameSite = cookieSettings.SameSite ? SameSiteMode.Strict : SameSiteMode.Unspecified, Expires = session.Expires, } ); @@ -54,78 +51,7 @@ internal static class HttpContextSessionExtensions public static void DeleteSession(this HttpContext httpContext) { - var cookieSettings = httpContext.RequestServices.GetRequiredService< - IOptions - >(); - - httpContext.Response.Cookies.Delete( - "sid", - new CookieOptions - { - Path = "/", - HttpOnly = true, - Domain = cookieSettings.Value.Domain, - IsEssential = true, - Secure = cookieSettings.Value.Secure, - SameSite = cookieSettings.Value.SameSite, - Expires = DateTimeOffset.UtcNow.AddDays(-1), - } - ); - - httpContext.Response.Cookies.Delete( - "user", - new CookieOptions - { - Path = "/", - Domain = cookieSettings.Value.Domain, - IsEssential = true, - Secure = cookieSettings.Value.Secure, - SameSite = cookieSettings.Value.SameSite, - Expires = DateTimeOffset.UtcNow.AddDays(-1), - } - ); - } - - - public static RememberMeToken? GetRememberMeToken(this HttpContext httpContext) => - httpContext.Request.Cookies["rid"] is { } code ? RememberMeToken.FromCode(code) : null; - - public static void SetRememberMeToken(this HttpContext context, NewRememberMeToken token) - { - var cookieSettings = context.RequestServices.GetRequiredService>(); - - context.Response.Cookies.Append( - "rid", - token.Code, - new CookieOptions - { - Path = "/", - IsEssential = true, - Domain = cookieSettings.Value.Domain, - HttpOnly = true, - Secure = cookieSettings.Value.Secure, - SameSite = cookieSettings.Value.SameSite, - Expires = token.Expires, - } - ); - } - - public static void DeleteRememberMeToken(this HttpContext context) - { - var cookieSettings = context.RequestServices.GetRequiredService>(); - - context.Response.Cookies.Delete( - "rid", - new CookieOptions - { - Path = "/", - HttpOnly = true, - Domain = cookieSettings.Value.Domain, - IsEssential = true, - Secure = cookieSettings.Value.Secure, - SameSite = cookieSettings.Value.SameSite, - Expires = DateTimeOffset.UtcNow.AddDays(-1), - } - ); + httpContext.Response.Cookies.Delete("session"); + httpContext.Response.Cookies.Delete("user"); } } diff --git a/Femto.Api/appsettings.json b/Femto.Api/appsettings.json index 5c949c0..51120b2 100644 --- a/Femto.Api/appsettings.json +++ b/Femto.Api/appsettings.json @@ -3,9 +3,7 @@ "Logging": { "LogLevel": { "Default": "Information", - "Femto": "Debug", - "Microsoft.AspNetCore": "Warning", - "Microsoft.EntityFrameworkCore": "Warning" + "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" diff --git a/Femto.Common/ICurrentUserContext.cs b/Femto.Common/ICurrentUserContext.cs index 629b2d2..a7233e0 100644 --- a/Femto.Common/ICurrentUserContext.cs +++ b/Femto.Common/ICurrentUserContext.cs @@ -5,4 +5,4 @@ public interface ICurrentUserContext CurrentUser? CurrentUser { get; } } -public record CurrentUser(Guid Id, string Username, bool IsSuperUser); \ No newline at end of file +public record CurrentUser(Guid Id, string Username, string SessionId); diff --git a/Femto.Common/Infrastructure/DomainServiceExtensions.cs b/Femto.Common/Infrastructure/DomainServiceExtensions.cs index 9812c93..e83469e 100644 --- a/Femto.Common/Infrastructure/DomainServiceExtensions.cs +++ b/Femto.Common/Infrastructure/DomainServiceExtensions.cs @@ -12,7 +12,7 @@ public static class DomainServiceExtensions services.AddScoped(s => s.GetRequiredService()); services.AddTransient( typeof(IPipelineBehavior<,>), - typeof(DDDPipelineBehaviour<,>) + typeof(SaveChangesPipelineBehaviour<,>) ); } diff --git a/Femto.Common/Infrastructure/DDDPipelineBehaviour.cs b/Femto.Common/Infrastructure/SaveChangesPipelineBehaviour.cs similarity index 69% rename from Femto.Common/Infrastructure/DDDPipelineBehaviour.cs rename to Femto.Common/Infrastructure/SaveChangesPipelineBehaviour.cs index e5f338f..d9aaf03 100644 --- a/Femto.Common/Infrastructure/DDDPipelineBehaviour.cs +++ b/Femto.Common/Infrastructure/SaveChangesPipelineBehaviour.cs @@ -5,10 +5,10 @@ using Microsoft.Extensions.Logging; namespace Femto.Common.Infrastructure; -public class DDDPipelineBehaviour( +public class SaveChangesPipelineBehaviour( DbContext context, IPublisher publisher, - ILogger> logger + ILogger> logger ) : IPipelineBehavior where TRequest : notnull { @@ -18,12 +18,7 @@ public class DDDPipelineBehaviour( CancellationToken cancellationToken ) { - logger.LogDebug("handling request {Type}", typeof(TRequest).Name); var response = await next(cancellationToken); - - var hasChanges = context.ChangeTracker.HasChanges(); - logger.LogDebug("request handled. Changes? {HasChanges}", hasChanges); - if (context.ChangeTracker.HasChanges()) { await context.EmitDomainEvents(logger, publisher, cancellationToken); diff --git a/Femto.Database/Migrations/20250518193113_AddUserRole.sql b/Femto.Database/Migrations/20250518193113_AddUserRole.sql index 9febe66..8199a10 100644 --- a/Femto.Database/Migrations/20250518193113_AddUserRole.sql +++ b/Femto.Database/Migrations/20250518193113_AddUserRole.sql @@ -6,4 +6,4 @@ CREATE TABLE authn.user_role user_id uuid REFERENCES authn.user_identity(id), role int, primary key (user_id, role) -) \ No newline at end of file +); \ No newline at end of file diff --git a/Femto.Database/Migrations/20250526220032_AddReactions.sql b/Femto.Database/Migrations/20250526220032_AddReactions.sql deleted file mode 100644 index a99bbae..0000000 --- a/Femto.Database/Migrations/20250526220032_AddReactions.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Migration: AddReactions --- Created at: 26/05/2025 22:00:32 - -ALTER TABLE blog.post -ADD COLUMN possible_reactions TEXT; - -CREATE TABLE blog.post_reaction -( - post_id uuid REFERENCES blog.post(id), - author_id uuid REFERENCES blog.author(id), - emoji text not null, - primary key (post_id, author_id, emoji) -); \ No newline at end of file diff --git a/Femto.Database/Migrations/20250529101346_SessionsRework.sql b/Femto.Database/Migrations/20250529101346_SessionsRework.sql deleted file mode 100644 index 11cb84e..0000000 --- a/Femto.Database/Migrations/20250529101346_SessionsRework.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Migration: addLongTermSessions --- Created at: 29/05/2025 10:13:46 - -DROP TABLE authn.user_session; - -CREATE TABLE authn.long_term_session -( - id serial PRIMARY KEY, - selector varchar(16) NOT NULL, - hashed_verifier bytea NOT NULL, - expires timestamptz not null, - user_id uuid REFERENCES authn.user_identity (id) -); \ No newline at end of file diff --git a/Femto.Database/Migrations/20250719104200_AddInvalidateToLongTermSession.sql b/Femto.Database/Migrations/20250719104200_AddInvalidateToLongTermSession.sql deleted file mode 100644 index 15d0323..0000000 --- a/Femto.Database/Migrations/20250719104200_AddInvalidateToLongTermSession.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Migration: AddInvalidateToLongTermSession --- Created at: 19/07/2025 10:42:00 - -ALTER TABLE authn.long_term_session -ADD COLUMN is_invalidated BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/Femto.Database/Migrations/20250810152132_AddTimestampToReaction.sql b/Femto.Database/Migrations/20250810152132_AddTimestampToReaction.sql deleted file mode 100644 index 4557156..0000000 --- a/Femto.Database/Migrations/20250810152132_AddTimestampToReaction.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Migration: AddTimestampToReaction --- Created at: 10/08/2025 15:21:32 -alter table blog.post_reaction -add column created_at timestamptz; \ No newline at end of file diff --git a/Femto.Database/Migrations/20250810172242_AddCommentToPost.sql b/Femto.Database/Migrations/20250810172242_AddCommentToPost.sql deleted file mode 100644 index 44e0086..0000000 --- a/Femto.Database/Migrations/20250810172242_AddCommentToPost.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Migration: AddCommentToPost --- Created at: 10/08/2025 17:22:42 - -CREATE TABLE blog.post_comment -( - id uuid PRIMARY KEY, - post_id uuid REFERENCES blog.post(id), - author_id uuid REFERENCES blog.author(id), - content TEXT NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -) \ No newline at end of file diff --git a/Femto.Database/Seed/TestDataSeeder.cs b/Femto.Database/Seed/TestDataSeeder.cs index 2c8efcc..f180b8f 100644 --- a/Femto.Database/Seed/TestDataSeeder.cs +++ b/Femto.Database/Seed/TestDataSeeder.cs @@ -14,7 +14,7 @@ public static class TestDataSeeder var id = Guid.Parse("0196960c-6296-7532-ba66-8fabb38c6ae0"); var username = "johnbotris"; var salt = new byte[32]; - var password = "password"u8; + var password = "hunter2"u8; var hashInput = new byte[password.Length + salt.Length]; password.CopyTo(hashInput); salt.CopyTo(hashInput, password.Length); @@ -35,15 +35,14 @@ public static class TestDataSeeder ; INSERT INTO blog.post - (id, author_id, possible_reactions, content) + (id, author_id, content) VALUES - ('019691a0-48ed-7eba-b8d3-608e25e07d4b', @id, '["๐Ÿ†", "๐Ÿงข", "๐Ÿง‘๐Ÿพโ€๐ŸŽ“", "๐Ÿฅ•", "๐Ÿ•—"]', 'However, authors often misinterpret the zoology as a smothered advantage, when in actuality it feels more like a blindfold accordion. They were lost without the chastest puppy that composed their Santa.'), - ('019691a0-4ace-7bb5-a8f3-e3362920eba0', @id, '["๐Ÿ†", "๐Ÿงข", "๐Ÿง‘๐Ÿพโ€๐ŸŽ“", "๐Ÿฅ•", "๐Ÿ•—"]', 'Extending this logic, a swim can hardly be considered a seasick duckling without also being a tornado. Some posit the whity voyage to be less than dippy.'), - ('019691a0-4c3e-726f-b8f6-bcbaabe789ae', @id, '["๐Ÿ†", "๐Ÿงข", "๐Ÿง‘๐Ÿพโ€๐ŸŽ“", "๐Ÿฅ•", "๐Ÿ•—"]', 'Few can name a springless sun that isn''t a thudding Vietnam. The burn of a competitor becomes a frosted target.'), - ('019691a0-4dd3-7e89-909e-94a6fd19a05e', @id, '["๐Ÿ†", "๐Ÿงข", "๐Ÿง‘๐Ÿพโ€๐ŸŽ“", "๐Ÿฅ•", "๐Ÿ•—"]', 'Some unwitched marbles are thought of simply as currencies. A boundary sees a nepal as a chordal railway.') + ('019691a0-48ed-7eba-b8d3-608e25e07d4b', @id, 'However, authors often misinterpret the zoology as a smothered advantage, when in actuality it feels more like a blindfold accordion. They were lost without the chastest puppy that composed their Santa.'), + ('019691a0-4ace-7bb5-a8f3-e3362920eba0', @id, 'Extending this logic, a swim can hardly be considered a seasick duckling without also being a tornado. Some posit the whity voyage to be less than dippy.'), + ('019691a0-4c3e-726f-b8f6-bcbaabe789ae', @id,'Few can name a springless sun that isn''t a thudding Vietnam. The burn of a competitor becomes a frosted target.'), + ('019691a0-4dd3-7e89-909e-94a6fd19a05e', @id,'Some unwitched marbles are thought of simply as currencies. A boundary sees a nepal as a chordal railway.') ; - INSERT INTO blog.post_media (id, post_id, url, ordering) VALUES @@ -55,21 +54,6 @@ public static class TestDataSeeder ('019691b6-2608-7088-8110-f0f6e35fa633', '019691a0-4dd3-7e89-909e-94a6fd19a05e', 'https://www.pinclipart.com/picdir/big/535-5356059_big-transparent-chungus-png-background-big-chungus-clipart.png', 0) ; - INSERT INTO blog.post_reaction - (post_id, author_id, emoji) - VALUES - ('019691a0-48ed-7eba-b8d3-608e25e07d4b', @id, '๐Ÿ†'), - ('019691a0-4ace-7bb5-a8f3-e3362920eba0', @id, '๐Ÿ†'), - ('019691a0-4c3e-726f-b8f6-bcbaabe789ae', @id, '๐Ÿง‘๐Ÿพโ€'), - ('019691a0-4c3e-726f-b8f6-bcbaabe789ae', @id, '๐Ÿ•—') - ; - - INSERT INTO blog.post_comment - (id, post_id, author_id, content) - VALUES - ('9116da05-49eb-4053-9199-57f54f92e73a', '019691a0-48ed-7eba-b8d3-608e25e07d4b', @id, 'this is a comment!') - ; - INSERT INTO authn.user_identity (id, username, password_hash, password_salt) VALUES diff --git a/Femto.Docs/Design/Auth/RememberMe.md b/Femto.Docs/Design/Auth/RememberMe.md deleted file mode 100644 index 0ff9ec2..0000000 --- a/Femto.Docs/Design/Auth/RememberMe.md +++ /dev/null @@ -1,27 +0,0 @@ -# Remember me - -We want to implement long lived sessions - -we will do this with a remember me cookie - -this should be implemented as so: - - -logging or registering and including a "rememberMe" flag with the request will generate a new remember me token, which can be stored as a cookie . - -the remember me token should live until: -* the user changes password anywhere -* the user logs out on that device -* the user logs in with an expired session, in which case the remember me token will be used to refresh the session, and then it will be swapped out for a new one - -that means we need to implement three spots: -- [ ] login -- [ ] register -- [ ] validate session - -we will implement it as described [here](https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence) - -we will only check the remember me token in "validate session". - -"refresh session" is only called with valid sessions so we do not need to check it here, as the session should already have been validated - diff --git a/Femto.Docs/Design/Auth/strong_vs_weak_session.md b/Femto.Docs/Design/Auth/strong_vs_weak_session.md deleted file mode 100644 index 5a45a7d..0000000 --- a/Femto.Docs/Design/Auth/strong_vs_weak_session.md +++ /dev/null @@ -1,16 +0,0 @@ -# Strong vs weak sessions - -a **strong** session is one that should have the power to do account level admin tasks like change password - - -a **weak** session has strictly fewer privileges than a strong session - -## where to get a strong session - -a strong session is created when a user provides a username and a password. a session remains strong until it is refreshed, at which point it becomes weak. - -## where to get a weak session - -A weak session is any session that has not been directly created by user credentials, i.e.: -* short-term session refresh -* long-term session refresh \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/AuthModule.cs b/Femto.Modules.Auth/Application/AuthModule.cs new file mode 100644 index 0000000..d289d9e --- /dev/null +++ b/Femto.Modules.Auth/Application/AuthModule.cs @@ -0,0 +1,22 @@ +using Femto.Common.Domain; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Femto.Modules.Auth.Application; + +internal class AuthModule(IMediator mediator) : IAuthModule +{ + public async Task Command(ICommand command, CancellationToken cancellationToken = default) => + await mediator.Send(command, cancellationToken); + + public async Task Command( + ICommand command, + CancellationToken cancellationToken = default + ) => await mediator.Send(command, cancellationToken); + + public async Task Query( + IQuery query, + CancellationToken cancellationToken = default + ) => await mediator.Send(query, cancellationToken); +} diff --git a/Femto.Modules.Auth/Application/AuthService.cs b/Femto.Modules.Auth/Application/AuthService.cs deleted file mode 100644 index eb6215f..0000000 --- a/Femto.Modules.Auth/Application/AuthService.cs +++ /dev/null @@ -1,258 +0,0 @@ -using Dapper; -using Femto.Common.Domain; -using Femto.Common.Infrastructure.DbConnection; -using Femto.Modules.Auth.Application.Dto; -using Femto.Modules.Auth.Data; -using Femto.Modules.Auth.Infrastructure; -using Femto.Modules.Auth.Models; -using Microsoft.EntityFrameworkCore; - -namespace Femto.Modules.Auth.Application; - -internal class AuthService( - AuthContext context, - SessionStorage sessionStorage, - IDbConnectionFactory connectionFactory -) : IAuthService -{ - public async Task AuthenticateUserCredentials( - string username, - string password, - CancellationToken cancellationToken = default - ) - { - var user = await context - .Users.Where(u => u.Username == username) - .SingleOrDefaultAsync(cancellationToken); - - if (user is null) - return null; - - if (!user.HasPassword(password)) - return null; - - var session = new Session(user.Id, true); - - await sessionStorage.AddSession(session); - - return new( - new UserInfo(user.Id, user.Username, user.Roles.Select(r => r.Role).ToList()), - session - ); - } - - public Task GetUserWithId(Guid? userId, CancellationToken cancellationToken) - { - return context - .Users.Where(u => u.Id == userId) - .Select(u => new UserInfo(u.Id, u.Username, u.Roles.Select(r => r.Role).ToList())) - .SingleOrDefaultAsync(cancellationToken); - } - - public async Task CreateNewSession(Guid userId) - { - var session = new Session(userId, true); - - await sessionStorage.AddSession(session); - - return session; - } - - public async Task CreateWeakSession(Guid userId) - { - var session = new Session(userId, false); - - await sessionStorage.AddSession(session); - - return session; - } - - public Task GetSession(string sessionId) - { - return sessionStorage.GetSession(sessionId); - } - - public async Task DeleteSession(string sessionId) - { - await sessionStorage.DeleteSession(sessionId); - } - - public async Task CreateUserWithCredentials( - string username, - string password, - string signupCode, - CancellationToken cancellationToken = default - ) - { - var now = DateTimeOffset.UtcNow; - - var code = await context - .SignupCodes.Where(c => c.Code == signupCode) - .Where(c => c.ExpiresAt == null || c.ExpiresAt > now) - .Where(c => c.RedeemingUserId == null) - .SingleOrDefaultAsync(cancellationToken); - - if (code is null) - throw new DomainError("invalid signup code"); - - var usernameTaken = await context.Users.AnyAsync( - u => u.Username == username, - cancellationToken - ); - - if (usernameTaken) - throw new DomainError("username taken"); - - var user = new UserIdentity(username); - - await context.AddAsync(user, cancellationToken); - - user.SetPassword(password); - - code.Redeem(user.Id); - - var session = new Session(user.Id, true); - - await sessionStorage.AddSession(session); - - await context.SaveChangesAsync(cancellationToken); - - return new(new UserInfo(user), session); - } - - public async Task AddSignupCode( - string code, - string recipientName, - CancellationToken cancellationToken - ) - { - await context.SignupCodes.AddAsync( - new SignupCode("", recipientName, code), - cancellationToken - ); - - await context.SaveChangesAsync(cancellationToken); - } - - public async Task> GetSignupCodes( - CancellationToken cancellationToken = default - ) - { - using var conn = connectionFactory.GetConnection(); - - // lang=sql - const string sql = """ - SELECT - sc.code as Code, - sc.recipient_email as Email, - sc.recipient_name as Name, - sc.redeeming_user_id as RedeemedByUserId, - u.username as RedeemedByUsername, - sc.expires_at as ExpiresOn - FROM authn.signup_code sc - LEFT JOIN authn.user_identity u ON u.id = sc.redeeming_user_id - ORDER BY sc.created_at DESC - """; - - var result = await conn.QueryAsync(sql, cancellationToken); - - return result - .Select(row => new SignupCodeDto( - row.Code, - row.Email, - row.Name, - row.RedeemedByUserId, - row.RedeemedByUsername, - row.ExpiresOn - )) - .ToList(); - } - - public async Task CreateRememberMeToken(Guid userId) - { - var (rememberMeToken, verifier) = LongTermSession.Create(userId); - - await context.AddAsync(rememberMeToken); - await context.SaveChangesAsync(); - - return new(rememberMeToken.Selector, verifier, rememberMeToken.Expires); - } - - public async Task<(UserInfo?, NewRememberMeToken?)> GetUserWithRememberMeToken( - RememberMeToken rememberMeToken - ) - { - var token = await context.LongTermSessions.SingleOrDefaultAsync(t => - t.Selector == rememberMeToken.Selector - ); - - if (token is null) - return (null, null); - - if (!token.CheckVerifier(rememberMeToken.Verifier)) - return (null, null); - - var user = await context.Users.SingleOrDefaultAsync(u => u.Id == token.UserId); - - if (user is null) - return (null, null); - - if (token.ExpiresSoon) - { - var (newToken, verifier) = LongTermSession.Create(user.Id); - await context.AddAsync(newToken); - await context.SaveChangesAsync(); - - return (new(user), new(newToken.Selector, verifier, newToken.Expires)); - } - - return (new(user), null); - } - - public async Task DeleteRememberMeToken(RememberMeToken rememberMeToken) - { - var session = await context.LongTermSessions.SingleOrDefaultAsync(s => - s.Selector == rememberMeToken.Selector - ); - - if (session is null) - return; - - if (!session.CheckVerifier(rememberMeToken.Verifier)) - return; - - context.Remove(session); - await context.SaveChangesAsync(); - } - - public async Task ChangePassword(Guid userId, string password, CancellationToken cancellationToken) - { - // change the password - // invalidate long term sessions - // invalidate sessions - - var user = await context.Users.SingleOrDefaultAsync(u => u.Id == userId,cancellationToken); - - if (user is null) - throw new DomainError("invalid user"); - - user.SetPassword(password); - - await context.SaveChangesAsync(cancellationToken); - } - - public async Task InvalidateUserSessions(Guid userId, CancellationToken cancellationToken) - { - await sessionStorage.InvalidateUserSessions(userId); - } - - private class GetSignupCodesQueryResultRow - { - public string Code { get; set; } - public string Email { get; set; } - public string Name { get; set; } - public Guid? RedeemedByUserId { get; set; } - public string? RedeemedByUsername { get; set; } - public DateTimeOffset? ExpiresOn { get; set; } - } -} diff --git a/Femto.Modules.Auth/Application/AuthStartup.cs b/Femto.Modules.Auth/Application/AuthStartup.cs index 362e600..195aa23 100644 --- a/Femto.Modules.Auth/Application/AuthStartup.cs +++ b/Femto.Modules.Auth/Application/AuthStartup.cs @@ -1,4 +1,3 @@ -using Femto.Common; using Femto.Common.Infrastructure; using Femto.Common.Infrastructure.DbConnection; using Femto.Common.Infrastructure.Outbox; @@ -16,69 +15,42 @@ namespace Femto.Modules.Auth.Application; public static class AuthStartup { - public static void InitializeAuthenticationModule( - this IServiceCollection rootContainer, - string connectionString, - IEventBus eventBus, - ILoggerFactory loggerFactory, - TimeProvider timeProvider - ) + public static void InitializeAuthenticationModule(this IServiceCollection rootContainer, + string connectionString, IEventBus eventBus) { var hostBuilder = Host.CreateDefaultBuilder(); - - hostBuilder.ConfigureServices(services => - ConfigureServices(services, connectionString, eventBus, loggerFactory, timeProvider) - ); - + hostBuilder.ConfigureServices(services => ConfigureServices(services, connectionString, eventBus)); var host = hostBuilder.Build(); - - rootContainer.AddKeyedScoped( - "AuthServiceScope", - (s, o) => - { - var scope = host.Services.CreateScope(); - return new ScopeBinding(scope); - } - ); - - rootContainer.ExposeScopedService(); - + + rootContainer.AddScoped(_ => new ScopeBinding(host.Services.CreateScope())); + + rootContainer.AddScoped(services => + services.GetRequiredService().GetService()); + rootContainer.AddHostedService(services => new AuthApplication(host)); - eventBus.Subscribe( - (evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken) - ); + eventBus.Subscribe((evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken)); } - private static void ConfigureServices( - IServiceCollection services, - string connectionString, - IEventPublisher publisher, - ILoggerFactory loggerFactory, - TimeProvider timeProvider - ) + private static void ConfigureServices(IServiceCollection services, string connectionString, IEventPublisher publisher) { - services.AddSingleton(timeProvider); - services.AddTransient(_ => new DbConnectionFactory(connectionString)); - + services.AddDbContext(builder => { builder.UseNpgsql(connectionString); builder.UseSnakeCaseNamingConvention(); + var loggerFactory = LoggerFactory.Create(b => { }); builder.UseLoggerFactory(loggerFactory); // #if DEBUG // builder.EnableSensitiveDataLogging(); // #endif }); - services.AddSingleton(loggerFactory); - services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); - services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; }); - // #endif + services.AddOutbox(); services.AddMediatR(c => c.RegisterServicesFromAssembly(typeof(AuthStartup).Assembly)); @@ -86,13 +58,10 @@ public static class AuthStartup services.ConfigureDomainServices(); services.AddSingleton(publisher); - services.AddSingleton(); - - services.AddScoped(typeof(IPipelineBehavior<,>), typeof(SaveChangesPipelineBehaviour<,>)); - - services.AddScoped(); + + services.AddScoped(); } - + private static async Task EventSubscriber( IEvent evt, IServiceProvider provider, @@ -122,14 +91,3 @@ public static class AuthStartup } } } - -internal static class AuthServiceCollectionExtensions -{ - public static void ExposeScopedService(this IServiceCollection container) - where T : class - { - container.AddScoped(services => - services.GetRequiredKeyedService("AuthServiceScope").GetService() - ); - } -} diff --git a/Femto.Modules.Auth/Application/Dto/LoginResult.cs b/Femto.Modules.Auth/Application/Dto/LoginResult.cs index c9048ad..1405a28 100644 --- a/Femto.Modules.Auth/Application/Dto/LoginResult.cs +++ b/Femto.Modules.Auth/Application/Dto/LoginResult.cs @@ -1,3 +1,3 @@ namespace Femto.Modules.Auth.Application.Dto; -public record LoginResult(SessionDto SessionDto, UserInfo User); \ No newline at end of file +public record LoginResult(Session Session, UserInfo User); \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Dto/RefreshUserSessionResult.cs b/Femto.Modules.Auth/Application/Dto/RefreshUserSessionResult.cs index 19f1d17..ac1bbc3 100644 --- a/Femto.Modules.Auth/Application/Dto/RefreshUserSessionResult.cs +++ b/Femto.Modules.Auth/Application/Dto/RefreshUserSessionResult.cs @@ -1,3 +1,3 @@ namespace Femto.Modules.Auth.Application.Dto; -public record RefreshUserSessionResult(SessionDto SessionDto, UserInfo User); \ No newline at end of file +public record RefreshUserSessionResult(Session Session, UserInfo User); \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Dto/RegisterResult.cs b/Femto.Modules.Auth/Application/Dto/RegisterResult.cs index e0a1243..13e1d12 100644 --- a/Femto.Modules.Auth/Application/Dto/RegisterResult.cs +++ b/Femto.Modules.Auth/Application/Dto/RegisterResult.cs @@ -1,3 +1,3 @@ namespace Femto.Modules.Auth.Application.Dto; -public record RegisterResult(SessionDto SessionDto, UserInfo User); \ No newline at end of file +public record RegisterResult(Session Session, UserInfo User); \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Dto/RememberMeToken.cs b/Femto.Modules.Auth/Application/Dto/RememberMeToken.cs deleted file mode 100644 index 5750b8d..0000000 --- a/Femto.Modules.Auth/Application/Dto/RememberMeToken.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Femto.Modules.Auth.Models; - -namespace Femto.Modules.Auth.Application.Dto; - -public record RememberMeToken(string Selector, string Verifier) -{ - public static RememberMeToken FromCode(string code) - { - var parts = code.Split('.'); - return new RememberMeToken(parts[0], parts[1]); - } - -}; - -public record NewRememberMeToken(string Selector, string Verifier, DateTimeOffset Expires) -{ - public string Code => $"{Selector}.{Verifier}"; -} diff --git a/Femto.Modules.Auth/Application/Dto/Session.cs b/Femto.Modules.Auth/Application/Dto/Session.cs index 7f422eb..9e87ca8 100644 --- a/Femto.Modules.Auth/Application/Dto/Session.cs +++ b/Femto.Modules.Auth/Application/Dto/Session.cs @@ -2,16 +2,9 @@ using Femto.Modules.Auth.Models; namespace Femto.Modules.Auth.Application.Dto; -public record SessionDto( - string SessionId, - DateTimeOffset Expires, - bool Weak, - string? RememberMe = null -) +public record Session(string SessionId, DateTimeOffset Expires) { - internal SessionDto(Session session) - : this(session.Id, session.Expires, !session.IsStronglyAuthenticated) { } - - internal SessionDto(Session session, string? rememberMe) - : this(session.Id, session.Expires, !session.IsStronglyAuthenticated, rememberMe) { } -} + internal Session(UserSession session) : this(session.Id, session.Expires) + { + } +} \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Dto/ValidateSessionResult.cs b/Femto.Modules.Auth/Application/Dto/ValidateSessionResult.cs index e29c84a..7fb022f 100644 --- a/Femto.Modules.Auth/Application/Dto/ValidateSessionResult.cs +++ b/Femto.Modules.Auth/Application/Dto/ValidateSessionResult.cs @@ -1,3 +1,3 @@ namespace Femto.Modules.Auth.Application.Dto; -public record ValidateSessionResult(SessionDto SessionDto); \ No newline at end of file +public record ValidateSessionResult(Session Session, UserInfo User); \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/IAuthModule.cs b/Femto.Modules.Auth/Application/IAuthModule.cs new file mode 100644 index 0000000..4559161 --- /dev/null +++ b/Femto.Modules.Auth/Application/IAuthModule.cs @@ -0,0 +1,10 @@ +using Femto.Common.Domain; + +namespace Femto.Modules.Auth.Application; + +public interface IAuthModule +{ + Task Command(ICommand command, CancellationToken cancellationToken = default); + Task Command(ICommand command, CancellationToken cancellationToken = default); + Task Query(IQuery query, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/IAuthService.cs b/Femto.Modules.Auth/Application/IAuthService.cs deleted file mode 100644 index f656d95..0000000 --- a/Femto.Modules.Auth/Application/IAuthService.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Femto.Modules.Auth.Application.Dto; -using Femto.Modules.Auth.Models; - -namespace Femto.Modules.Auth.Application; - -public interface IAuthService -{ - public Task AuthenticateUserCredentials( - string username, - string password, - CancellationToken cancellationToken = default - ); - public Task GetUserWithId( - Guid? userId, - CancellationToken cancellationToken = default - ); - public Task CreateNewSession(Guid userId); - public Task CreateWeakSession(Guid userId); - public Task GetSession(string sessionId); - public Task DeleteSession(string sessionId); - - public Task CreateUserWithCredentials(string username, - string password, - string signupCode, - CancellationToken cancellationToken = default); - - public Task AddSignupCode( - string code, - string recipientName, - CancellationToken cancellationToken = default - ); - - public Task> GetSignupCodes( - CancellationToken cancellationToken = default - ); - - Task CreateRememberMeToken(Guid userId); - Task<(UserInfo?, NewRememberMeToken?)> GetUserWithRememberMeToken(RememberMeToken rememberMeToken); - Task DeleteRememberMeToken(RememberMeToken rememberMeToken); - - Task ChangePassword(Guid userId, string password, CancellationToken cancellationToken = default); - Task InvalidateUserSessions(Guid userId, CancellationToken cancellationToken = default); -} - -public record UserAndSession(UserInfo User, Session Session); \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommand.cs b/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommand.cs new file mode 100644 index 0000000..be24aa9 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommand.cs @@ -0,0 +1,5 @@ +using Femto.Common.Domain; + +namespace Femto.Modules.Auth.Application.Interface.CreateSignupCode; + +public record CreateSignupCodeCommand(string Code, string RecipientEmail, string RecipientName): ICommand; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommandHandler.cs new file mode 100644 index 0000000..cfbb44a --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommandHandler.cs @@ -0,0 +1,15 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Data; +using Femto.Modules.Auth.Models; + +namespace Femto.Modules.Auth.Application.Interface.CreateSignupCode; + +internal class CreateSignupCodeCommandHandler(AuthContext context) : ICommandHandler +{ + public async Task Handle(CreateSignupCodeCommand command, CancellationToken cancellationToken) + { + var code = new SignupCode(command.RecipientEmail, command.RecipientName, command.Code); + + await context.SignupCodes.AddAsync(code, cancellationToken); + } +} diff --git a/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQuery.cs b/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQuery.cs new file mode 100644 index 0000000..422a09d --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQuery.cs @@ -0,0 +1,6 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.GetSignupCodesQuery; + +public record GetSignupCodesQuery: IQuery>; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQueryHandler.cs b/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQueryHandler.cs new file mode 100644 index 0000000..201fdce --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQueryHandler.cs @@ -0,0 +1,55 @@ +using Dapper; +using Femto.Common.Domain; +using Femto.Common.Infrastructure.DbConnection; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.GetSignupCodesQuery; + +public class GetSignupCodesQueryHandler(IDbConnectionFactory connectionFactory) + : IQueryHandler> +{ + public async Task> Handle( + GetSignupCodesQuery request, + CancellationToken cancellationToken + ) + { + using var conn = connectionFactory.GetConnection(); + + // lang=sql + const string sql = """ + SELECT + sc.code as Code, + sc.recipient_email as Email, + sc.recipient_name as Name, + sc.redeeming_user_id as RedeemedByUserId, + u.username as RedeemedByUsername, + sc.expires_at as ExpiresOn + FROM authn.signup_code sc + LEFT JOIN authn.user_identity u ON u.id = sc.redeeming_user_id + ORDER BY sc.created_at DESC + """; + + var result = await conn.QueryAsync(sql); + + return result + .Select(row => new SignupCodeDto( + row.Code, + row.Email, + row.Name, + row.RedeemedByUserId, + row.RedeemedByUsername, + row.ExpiresOn + )) + .ToList(); + } + + private class QueryResultRow + { + public string Code { get; set; } + public string Email { get; set; } + public string Name { get; set; } + public Guid? RedeemedByUserId { get; set; } + public string? RedeemedByUsername { get; set; } + public DateTimeOffset? ExpiresOn { get; set; } + } +} diff --git a/Femto.Modules.Auth/Application/Interface/Login/LoginCommand.cs b/Femto.Modules.Auth/Application/Interface/Login/LoginCommand.cs new file mode 100644 index 0000000..8252e2e --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/Login/LoginCommand.cs @@ -0,0 +1,6 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.Login; + +public record LoginCommand(string Username, string Password) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/Login/LoginCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/Login/LoginCommandHandler.cs new file mode 100644 index 0000000..45b1ae4 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/Login/LoginCommandHandler.cs @@ -0,0 +1,28 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; +using Femto.Modules.Auth.Data; +using Microsoft.EntityFrameworkCore; + +namespace Femto.Modules.Auth.Application.Interface.Login; + +internal class LoginCommandHandler(AuthContext context) + : ICommandHandler +{ + public async Task Handle(LoginCommand request, CancellationToken cancellationToken) + { + var user = await context.Users.SingleOrDefaultAsync( + u => u.Username == request.Username, + cancellationToken + ); + + if (user is null) + throw new DomainError("invalid credentials"); + + if (!user.HasPassword(request.Password)) + throw new DomainError("invalid credentials"); + + var session = user.StartNewSession(); + + return new(new Session(session.Id, session.Expires), new UserInfo(user)); + } +} diff --git a/Femto.Modules.Auth/Application/Interface/RefreshUserSession/RefreshUserSessionCommand.cs b/Femto.Modules.Auth/Application/Interface/RefreshUserSession/RefreshUserSessionCommand.cs new file mode 100644 index 0000000..f04fa82 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/RefreshUserSession/RefreshUserSessionCommand.cs @@ -0,0 +1,7 @@ +using Femto.Common; +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.RefreshUserSession; + +public record RefreshUserSessionCommand(Guid ForUser, CurrentUser CurrentUser) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/RefreshUserSession/RefreshUserSessionCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/RefreshUserSession/RefreshUserSessionCommandHandler.cs new file mode 100644 index 0000000..f0c6dc1 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/RefreshUserSession/RefreshUserSessionCommandHandler.cs @@ -0,0 +1,32 @@ +using Femto.Common.Domain; +using Femto.Common.Infrastructure.DbConnection; +using Femto.Modules.Auth.Application.Dto; +using Femto.Modules.Auth.Data; +using Microsoft.EntityFrameworkCore; + +namespace Femto.Modules.Auth.Application.Interface.RefreshUserSession; + +internal class RefreshUserSessionCommandHandler(AuthContext context) + : ICommandHandler +{ + public async Task Handle( + RefreshUserSessionCommand request, + CancellationToken cancellationToken + ) + { + if (request.CurrentUser.Id != request.ForUser) + throw new DomainError("invalid request"); + + var user = await context.Users.SingleOrDefaultAsync( + u => u.Id == request.ForUser, + cancellationToken + ); + + if (user is null) + throw new DomainError("invalid request"); + + var session = user.PossiblyRefreshSession(request.CurrentUser.SessionId); + + return new(new Session(session), new UserInfo(user)); + } +} diff --git a/Femto.Modules.Auth/Application/Interface/Register/RegisterCommand.cs b/Femto.Modules.Auth/Application/Interface/Register/RegisterCommand.cs new file mode 100644 index 0000000..dd3c186 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/Register/RegisterCommand.cs @@ -0,0 +1,6 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.Register; + +public record RegisterCommand(string Username, string Password, string SignupCode) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/Register/RegisterCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/Register/RegisterCommandHandler.cs new file mode 100644 index 0000000..9e29be6 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/Register/RegisterCommandHandler.cs @@ -0,0 +1,35 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; +using Femto.Modules.Auth.Data; +using Femto.Modules.Auth.Models; +using Microsoft.EntityFrameworkCore; + +namespace Femto.Modules.Auth.Application.Interface.Register; + +internal class RegisterCommandHandler(AuthContext context) : ICommandHandler +{ + public async Task Handle(RegisterCommand request, CancellationToken cancellationToken) + { + var now = DateTimeOffset.UtcNow; + var code = await context.SignupCodes + .Where(c => c.Code == request.SignupCode) + .Where(c => c.ExpiresAt == null || c.ExpiresAt > now) + .Where(c => c.RedeemingUserId == null) + .SingleOrDefaultAsync(cancellationToken); + + if (code is null) + throw new DomainError("invalid signup code"); + + var user = new UserIdentity(request.Username); + + user.SetPassword(request.Password); + + var session = user.StartNewSession(); + + await context.AddAsync(user, cancellationToken); + + code.Redeem(user.Id); + + return new(new Session(session.Id, session.Expires), new UserInfo(user)); + } +} \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/ValidateSession/ValidateSessionCommand.cs b/Femto.Modules.Auth/Application/Interface/ValidateSession/ValidateSessionCommand.cs new file mode 100644 index 0000000..40d5417 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/ValidateSession/ValidateSessionCommand.cs @@ -0,0 +1,10 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.ValidateSession; + +/// +/// Validate an existing session, and then return either the current session, or a new one in case the expiry is further in the future +/// +/// +public record ValidateSessionCommand(string SessionId) : ICommand; diff --git a/Femto.Modules.Auth/Application/Interface/ValidateSession/ValidateSessionCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/ValidateSession/ValidateSessionCommandHandler.cs new file mode 100644 index 0000000..f79552c --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/ValidateSession/ValidateSessionCommandHandler.cs @@ -0,0 +1,34 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; +using Femto.Modules.Auth.Data; +using Femto.Modules.Auth.Errors; +using Microsoft.EntityFrameworkCore; + +namespace Femto.Modules.Auth.Application.Interface.ValidateSession; + +internal class ValidateSessionCommandHandler(AuthContext context) + : ICommandHandler +{ + public async Task Handle( + ValidateSessionCommand request, + CancellationToken cancellationToken + ) + { + var now = DateTimeOffset.UtcNow; + + var user = await context.Users.SingleOrDefaultAsync( + u => u.Sessions.Any(s => s.Id == request.SessionId && s.Expires > now), + cancellationToken + ); + + if (user is null) + throw new InvalidSessionError(); + + var session = user.PossiblyRefreshSession(request.SessionId); + + return new ValidateSessionResult( + new Session(session.Id, session.Expires), + new UserInfo(user) + ); + } +} diff --git a/Femto.Common/ScopeBinding.cs b/Femto.Modules.Auth/Application/ScopeBinding.cs similarity index 53% rename from Femto.Common/ScopeBinding.cs rename to Femto.Modules.Auth/Application/ScopeBinding.cs index da58589..4a6419f 100644 --- a/Femto.Common/ScopeBinding.cs +++ b/Femto.Modules.Auth/Application/ScopeBinding.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -namespace Femto.Common; +namespace Femto.Modules.Auth.Application; /// @@ -11,16 +10,7 @@ namespace Femto.Common; /// public class ScopeBinding(IServiceScope scope) : IDisposable { - private IServiceScope Scope { get; } = scope; + public T GetService() where T : notnull => scope.ServiceProvider.GetRequiredService(); - public T GetService() - where T : notnull - { - return this.Scope.ServiceProvider.GetRequiredService(); - } - - public virtual void Dispose() - { - this.Scope.Dispose(); - } -} + public void Dispose() => scope.Dispose(); +} \ No newline at end of file diff --git a/Femto.Modules.Auth/Data/AuthContext.cs b/Femto.Modules.Auth/Data/AuthContext.cs index ac395ba..e850eb8 100644 --- a/Femto.Modules.Auth/Data/AuthContext.cs +++ b/Femto.Modules.Auth/Data/AuthContext.cs @@ -1,10 +1,6 @@ -using Femto.Common.Domain; using Femto.Common.Infrastructure.Outbox; using Femto.Modules.Auth.Models; -using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.Logging; namespace Femto.Modules.Auth.Data; @@ -12,7 +8,6 @@ internal class AuthContext(DbContextOptions options) : DbContext(op { public virtual DbSet Users { get; set; } public virtual DbSet SignupCodes { get; set; } - public virtual DbSet LongTermSessions { get; set; } public virtual DbSet Outbox { get; set; } protected override void OnModelCreating(ModelBuilder builder) @@ -21,43 +16,4 @@ internal class AuthContext(DbContextOptions options) : DbContext(op builder.HasDefaultSchema("authn"); builder.ApplyConfigurationsFromAssembly(typeof(AuthContext).Assembly); } - - public override int SaveChanges() - { - throw new InvalidOperationException("Use SaveChangesAsync instead"); - } - - public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) - { - await EmitDomainEvents(cancellationToken); - - return await base.SaveChangesAsync(cancellationToken); - } - - private async Task EmitDomainEvents(CancellationToken cancellationToken) - { - var logger = this.GetService>(); - var publisher = this.GetService(); - var domainEvents = this - .ChangeTracker.Entries() - .SelectMany(e => - { - var events = e.Entity.DomainEvents; - e.Entity.ClearDomainEvents(); - return events; - }) - .ToList(); - - logger.LogTrace("loaded {Count} domain events", domainEvents.Count); - - foreach (var domainEvent in domainEvents) - { - logger.LogTrace( - "publishing {Type} domain event {Id}", - domainEvent.GetType().Name, - domainEvent.EventId - ); - await publisher.Publish(domainEvent, cancellationToken); - } - } } \ No newline at end of file diff --git a/Femto.Modules.Auth/Data/Configurations/LongTermSessionConfiguration.cs b/Femto.Modules.Auth/Data/Configurations/LongTermSessionConfiguration.cs deleted file mode 100644 index 00f2a13..0000000 --- a/Femto.Modules.Auth/Data/Configurations/LongTermSessionConfiguration.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Femto.Modules.Auth.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Femto.Modules.Auth.Data.Configurations; - -public class LongTermSessionConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("long_term_session"); - } -} \ No newline at end of file diff --git a/Femto.Modules.Auth/Data/Configurations/UserIdentityTypeConfiguration.cs b/Femto.Modules.Auth/Data/Configurations/UserIdentityTypeConfiguration.cs index 1921451..2e5086b 100644 --- a/Femto.Modules.Auth/Data/Configurations/UserIdentityTypeConfiguration.cs +++ b/Femto.Modules.Auth/Data/Configurations/UserIdentityTypeConfiguration.cs @@ -19,6 +19,8 @@ internal class UserIdentityTypeConfiguration : IEntityTypeConfiguration u.Sessions).WithOwner().HasForeignKey("user_id"); + builder .OwnsMany(u => u.Roles, entity => { diff --git a/Femto.Modules.Auth/Infrastructure/SaveChangesPipelineBehaviour.cs b/Femto.Modules.Auth/Infrastructure/SaveChangesPipelineBehaviour.cs deleted file mode 100644 index cc4f983..0000000 --- a/Femto.Modules.Auth/Infrastructure/SaveChangesPipelineBehaviour.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Femto.Modules.Auth.Data; -using MediatR; - -namespace Femto.Modules.Auth.Infrastructure; - -internal class SaveChangesPipelineBehaviour(AuthContext context) - : IPipelineBehavior - where TRequest : notnull -{ - public async Task Handle( - TRequest request, - RequestHandlerDelegate next, - CancellationToken cancellationToken - ) - { - var response = await next(cancellationToken); - - if (context.ChangeTracker.HasChanges()) - await context.SaveChangesAsync(cancellationToken); - - return response; - } -} diff --git a/Femto.Modules.Auth/Infrastructure/SessionStorage.cs b/Femto.Modules.Auth/Infrastructure/SessionStorage.cs deleted file mode 100644 index e331396..0000000 --- a/Femto.Modules.Auth/Infrastructure/SessionStorage.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Collections; -using System.Collections.Concurrent; -using Femto.Modules.Auth.Models; -using Microsoft.Extensions.Caching.Memory; - -namespace Femto.Modules.Auth.Infrastructure; - -internal class SessionStorage(TimeProvider timeProvider) -{ - private readonly IMemoryCache _storage = new MemoryCache(new MemoryCacheOptions()); - - public async Task GetSession(string id) - { - var session = this._storage.Get($"session:{id}"); - - if (session is null) - return null; - - var invalidUntil = this._storage.Get( - $"user:invalid_until:{session.UserId}" - ); - - if (invalidUntil is not null && invalidUntil > session.Expires) - return null; - - return session; - } - - public Task AddSession(Session session) - { - using var sessionEntry = this._storage.CreateEntry($"session:{session.Id}"); - sessionEntry.Value = session; - sessionEntry.SetAbsoluteExpiration(session.Expires); - - return Task.CompletedTask; - } - - public Task DeleteSession(string id) - { - this._storage.Remove($"session:{id}"); - - return Task.CompletedTask; - } - - public Task InvalidateUserSessions(Guid userId) - { - var invalidUntil = timeProvider.GetUtcNow() + Session.ValidityPeriod; - - // invalidate sessions who are currently valid - // any sessions created after this will have a validity period that extends past invalid_until - // this cache entry doesn't need to live longer than that point in time - this._storage.Set($"user:invalid_until:{userId}", invalidUntil, invalidUntil); - - return Task.CompletedTask; - } -} diff --git a/Femto.Modules.Auth/Models/DomainEventHandlers/UserPasswordChangedHandler.cs b/Femto.Modules.Auth/Models/DomainEventHandlers/UserPasswordChangedHandler.cs deleted file mode 100644 index e09c66e..0000000 --- a/Femto.Modules.Auth/Models/DomainEventHandlers/UserPasswordChangedHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Femto.Modules.Auth.Data; -using Femto.Modules.Auth.Models.Events; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Femto.Modules.Auth.Models.DomainEventHandlers; - -internal class UserPasswordChangedHandler(AuthContext context) - : INotificationHandler -{ - public async Task Handle(UserWasCreatedEvent notification, CancellationToken cancellationToken) - { - var longTermSessions = await context - .LongTermSessions.Where(s => s.UserId == notification.User.Id) - .ToListAsync(cancellationToken); - - foreach (var session in longTermSessions) - { - session.Invalidate(); - } - } -} diff --git a/Femto.Modules.Auth/Models/Events/UserPasswordChangedDomainEvent.cs b/Femto.Modules.Auth/Models/Events/UserPasswordChangedDomainEvent.cs deleted file mode 100644 index 70c9a73..0000000 --- a/Femto.Modules.Auth/Models/Events/UserPasswordChangedDomainEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Femto.Common.Domain; - -namespace Femto.Modules.Auth.Models.Events; - -internal record UserPasswordChangedDomainEvent(UserIdentity User) : DomainEvent; \ No newline at end of file diff --git a/Femto.Modules.Auth/Models/LongTermSession.cs b/Femto.Modules.Auth/Models/LongTermSession.cs deleted file mode 100644 index eba3d9d..0000000 --- a/Femto.Modules.Auth/Models/LongTermSession.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; -using System.Text; -using static System.Security.Cryptography.RandomNumberGenerator; - -namespace Femto.Modules.Auth.Models; - -public class LongTermSession -{ - private static TimeSpan TokenTimeout { get; } = TimeSpan.FromDays(90); - private static TimeSpan RefreshBuffer { get; } = TimeSpan.FromDays(5); - - public int Id { get; private set; } - - public string Selector { get; private set; } - - public byte[] HashedVerifier { get; private set; } - - public DateTimeOffset Expires { get; private set; } - - public Guid UserId { get; private set; } - - public bool IsInvalidated { get; private set; } - - [NotMapped] - public bool ExpiresSoon => this.Expires < DateTimeOffset.UtcNow + RefreshBuffer; - - private LongTermSession() { } - - public static (LongTermSession, string) Create(Guid userId) - { - var selector = GetString( - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", - 12 - ); - - var verifier = GetString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 32); - - using var sha256 = System.Security.Cryptography.SHA256.Create(); - - var longTermSession = new LongTermSession - { - Selector = selector, - HashedVerifier = ComputeHash(verifier), - UserId = userId, - Expires = DateTimeOffset.UtcNow + TokenTimeout, - }; - - return (longTermSession, verifier); - } - - public bool CheckVerifier(string verifier) - { - if (this.IsInvalidated) - return false; - - if (this.Expires < DateTimeOffset.UtcNow) - return false; - - return ComputeHash(verifier).SequenceEqual(this.HashedVerifier); - } - - private static byte[] ComputeHash(string verifier) - { - using var sha256 = System.Security.Cryptography.SHA256.Create(); - var hashedVerifier = sha256.ComputeHash(Encoding.UTF8.GetBytes(verifier)); - return hashedVerifier; - } - - public void Invalidate() - { - this.IsInvalidated = true; - } -} diff --git a/Femto.Modules.Auth/Models/Session.cs b/Femto.Modules.Auth/Models/Session.cs deleted file mode 100644 index e641a61..0000000 --- a/Femto.Modules.Auth/Models/Session.cs +++ /dev/null @@ -1,16 +0,0 @@ -using static System.Security.Cryptography.RandomNumberGenerator; - -namespace Femto.Modules.Auth.Models; - -public class Session(Guid userId, bool isStrong) -{ - public static readonly TimeSpan ValidityPeriod = TimeSpan.FromMinutes(15); - private static readonly TimeSpan RefreshBuffer = TimeSpan.FromMinutes(5); - public string Id { get; } = Convert.ToBase64String(GetBytes(32)); - public Guid UserId { get; } = userId; - public DateTimeOffset Expires { get; } = DateTimeOffset.UtcNow + ValidityPeriod; - - public bool ExpiresSoon => this.Expires < DateTimeOffset.UtcNow + RefreshBuffer; - public bool IsStronglyAuthenticated { get; } = isStrong; - public bool IsExpired => this.Expires < DateTimeOffset.UtcNow; -} diff --git a/Femto.Modules.Auth/Models/UserIdentity.cs b/Femto.Modules.Auth/Models/UserIdentity.cs index bd0288f..756be41 100644 --- a/Femto.Modules.Auth/Models/UserIdentity.cs +++ b/Femto.Modules.Auth/Models/UserIdentity.cs @@ -1,6 +1,9 @@ +using System.Text; +using System.Text.Unicode; using Femto.Common.Domain; using Femto.Modules.Auth.Contracts; using Femto.Modules.Auth.Models.Events; +using Geralt; namespace Femto.Modules.Auth.Models; @@ -12,6 +15,8 @@ internal class UserIdentity : Entity public Password? Password { get; private set; } + public ICollection Sessions { get; private set; } = []; + public ICollection Roles { get; private set; } = []; private UserIdentity() { } @@ -26,10 +31,14 @@ internal class UserIdentity : Entity this.AddDomainEvent(new UserWasCreatedEvent(this)); } + public UserIdentity WithPassword(string password) + { + this.SetPassword(password); + return this; + } + public void SetPassword(string password) { - if (this.Password is not null) - this.AddDomainEvent(new UserPasswordChangedDomainEvent(this)); this.Password = new Password(password); } @@ -42,6 +51,25 @@ internal class UserIdentity : Entity return this.Password.Check(requestPassword); } + + public UserSession PossiblyRefreshSession(string sessionId) + { + var session = this.Sessions.Single(s => s.Id == sessionId); + + if (session.ExpiresSoon) + return this.StartNewSession(); + + return session; + } + + public UserSession StartNewSession() + { + var session = UserSession.Create(); + + this.Sessions.Add(session); + + return session; + } } public class SetPasswordError(string message, Exception inner) : DomainError(message, inner); diff --git a/Femto.Modules.Auth/Models/UserSession.cs b/Femto.Modules.Auth/Models/UserSession.cs new file mode 100644 index 0000000..7deb251 --- /dev/null +++ b/Femto.Modules.Auth/Models/UserSession.cs @@ -0,0 +1,21 @@ +namespace Femto.Modules.Auth.Models; + +internal class UserSession +{ + private static TimeSpan SessionTimeout { get; } = TimeSpan.FromMinutes(30); + private static TimeSpan ExpiryBuffer { get; } = TimeSpan.FromMinutes(5); + public string Id { get; private set; } + public DateTimeOffset Expires { get; private set; } + public bool ExpiresSoon => Expires < DateTimeOffset.UtcNow + ExpiryBuffer; + + private UserSession() {} + + public static UserSession Create() + { + return new() + { + Id = Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(32)), + Expires = DateTimeOffset.UtcNow + SessionTimeout + }; + } +} \ No newline at end of file diff --git a/Femto.Modules.Blog.Data/Class1.cs b/Femto.Modules.Blog.Data/Class1.cs new file mode 100644 index 0000000..3be8b2a --- /dev/null +++ b/Femto.Modules.Blog.Data/Class1.cs @@ -0,0 +1,5 @@ +๏ปฟnamespace Femto.Modules.Blog.Data; + +public class Class1 +{ +} \ No newline at end of file diff --git a/Femto.Modules.Blog.Data/Femto.Modules.Blog.Data.csproj b/Femto.Modules.Blog.Data/Femto.Modules.Blog.Data.csproj new file mode 100644 index 0000000..17b910f --- /dev/null +++ b/Femto.Modules.Blog.Data/Femto.Modules.Blog.Data.csproj @@ -0,0 +1,9 @@ +๏ปฟ + + + net9.0 + enable + enable + + + diff --git a/Femto.Modules.Blog.Domain/Femto.Modules.Blog.Domain.csproj b/Femto.Modules.Blog.Domain/Femto.Modules.Blog.Domain.csproj new file mode 100644 index 0000000..6ae6742 --- /dev/null +++ b/Femto.Modules.Blog.Domain/Femto.Modules.Blog.Domain.csproj @@ -0,0 +1,24 @@ +๏ปฟ + + + net9.0 + enable + enable + + + + + + + + + + + + + + ..\..\..\..\.nuget\packages\microsoft.entityframeworkcore\9.0.4\lib\net8.0\Microsoft.EntityFrameworkCore.dll + + + + diff --git a/Femto.Modules.Blog/Application/BlogModule.cs b/Femto.Modules.Blog/Application/BlogModule.cs index c293ceb..d96228c 100644 --- a/Femto.Modules.Blog/Application/BlogModule.cs +++ b/Femto.Modules.Blog/Application/BlogModule.cs @@ -1,29 +1,37 @@ using Femto.Common.Domain; using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace Femto.Modules.Blog.Application; -internal class BlogModule(IMediator mediator) : IBlogModule +internal class BlogModule(IHost host) : IBlogModule { - public async Task Command(ICommand command, CancellationToken cancellationToken = default) + public async Task PostCommand(ICommand command, CancellationToken cancellationToken = default) { + using var scope = host.Services.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); await mediator.Send(command, cancellationToken); } - public async Task Command( + public async Task PostCommand( ICommand command, CancellationToken cancellationToken = default ) { + using var scope = host.Services.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); var response = await mediator.Send(command, cancellationToken); return response; } - public async Task Query( + public async Task PostQuery( IQuery query, CancellationToken cancellationToken = default ) { + using var scope = host.Services.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); var response = await mediator.Send(query, cancellationToken); return response; } diff --git a/Femto.Modules.Blog/Application/BlogStartup.cs b/Femto.Modules.Blog/Application/BlogStartup.cs index afd18b5..000e9bd 100644 --- a/Femto.Modules.Blog/Application/BlogStartup.cs +++ b/Femto.Modules.Blog/Application/BlogStartup.cs @@ -1,5 +1,4 @@ ๏ปฟusing System.Runtime.CompilerServices; -using Femto.Common; using Femto.Common.Infrastructure; using Femto.Common.Infrastructure.DbConnection; using Femto.Common.Infrastructure.Outbox; @@ -21,28 +20,20 @@ public static class BlogStartup public static void InitializeBlogModule( this IServiceCollection rootContainer, string connectionString, - IEventBus bus, - ILoggerFactory loggerFactory + IEventBus bus ) { var hostBuilder = Host.CreateDefaultBuilder(); hostBuilder.ConfigureServices(services => - ConfigureServices(services, connectionString, bus, loggerFactory) + ConfigureServices(services, connectionString, bus) ); var host = hostBuilder.Build(); - rootContainer.AddHostedService(_ => new BlogApplication(host)); + rootContainer.AddHostedService(services => new BlogApplication(host)); - rootContainer.AddKeyedScoped( - "BlogService", - (_, o) => new ScopeBinding(host.Services.CreateScope()) - ); - - rootContainer.AddScoped(services => - services.GetRequiredKeyedService("BlogService").GetService() - ); + rootContainer.AddScoped(_ => new BlogModule(host)); bus.Subscribe( (evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken) @@ -52,8 +43,7 @@ public static class BlogStartup private static void ConfigureServices( this IServiceCollection services, string connectionString, - IEventPublisher publisher, - ILoggerFactory loggerFactory + IEventPublisher publisher ) { services.AddTransient(_ => new DbConnectionFactory(connectionString)); @@ -68,12 +58,10 @@ public static class BlogStartup } ); builder.UseSnakeCaseNamingConvention(); + var loggerFactory = LoggerFactory.Create(b => { }); builder.UseLoggerFactory(loggerFactory); }); - - services.AddSingleton(loggerFactory); - services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); - + services.AddOutbox(); services.AddMediatR(c => @@ -83,8 +71,6 @@ public static class BlogStartup services.ConfigureDomainServices(); - services.AddScoped(); - services.AddSingleton(publisher); } diff --git a/Femto.Modules.Blog/Application/Commands/AddPostComment/AddPostCommentCommand.cs b/Femto.Modules.Blog/Application/Commands/AddPostComment/AddPostCommentCommand.cs deleted file mode 100644 index 445c59e..0000000 --- a/Femto.Modules.Blog/Application/Commands/AddPostComment/AddPostCommentCommand.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Femto.Common.Domain; - -namespace Femto.Modules.Blog.Application.Commands.AddPostComment; - -public record AddPostCommentCommand(Guid PostId, Guid AuthorId, string Content) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/Commands/AddPostComment/AddPostCommentCommandHandler.cs b/Femto.Modules.Blog/Application/Commands/AddPostComment/AddPostCommentCommandHandler.cs deleted file mode 100644 index 6e52877..0000000 --- a/Femto.Modules.Blog/Application/Commands/AddPostComment/AddPostCommentCommandHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Femto.Common.Domain; -using Microsoft.EntityFrameworkCore; - -namespace Femto.Modules.Blog.Application.Commands.AddPostComment; - -internal class AddPostCommentCommandHandler(BlogContext context) : ICommandHandler -{ - public async Task Handle(AddPostCommentCommand request, CancellationToken cancellationToken) - { - var post = await context.Posts.SingleOrDefaultAsync( - p => p.Id == request.PostId, - cancellationToken - ); - - if (post is null) - return; - - post.AddComment(request.AuthorId, request.Content); - } -} \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/Commands/AddPostReaction/AddPostReactionCommand.cs b/Femto.Modules.Blog/Application/Commands/AddPostReaction/AddPostReactionCommand.cs deleted file mode 100644 index 9096687..0000000 --- a/Femto.Modules.Blog/Application/Commands/AddPostReaction/AddPostReactionCommand.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Femto.Common; -using Femto.Common.Domain; - -namespace Femto.Modules.Blog.Application.Commands.AddPostReaction; - -public record AddPostReactionCommand(Guid PostId, string Emoji, Guid ReactorId) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/Commands/AddPostReaction/AddPostReactionCommandHandler.cs b/Femto.Modules.Blog/Application/Commands/AddPostReaction/AddPostReactionCommandHandler.cs deleted file mode 100644 index e2c3f8a..0000000 --- a/Femto.Modules.Blog/Application/Commands/AddPostReaction/AddPostReactionCommandHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Femto.Common.Domain; -using Microsoft.EntityFrameworkCore; - -namespace Femto.Modules.Blog.Application.Commands.AddPostReaction; - -internal class AddPostReactionCommandHandler(BlogContext context) - : ICommandHandler -{ - public async Task Handle(AddPostReactionCommand request, CancellationToken cancellationToken) - { - var post = await context.Posts.SingleOrDefaultAsync( - p => p.Id == request.PostId, - cancellationToken - ); - - if (post is null) - return; - - post.AddReaction(request.ReactorId, request.Emoji); - } -} diff --git a/Femto.Modules.Blog/Application/Commands/ClearPostReaction/ClearPostReactionCommand.cs b/Femto.Modules.Blog/Application/Commands/ClearPostReaction/ClearPostReactionCommand.cs deleted file mode 100644 index 618305f..0000000 --- a/Femto.Modules.Blog/Application/Commands/ClearPostReaction/ClearPostReactionCommand.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Femto.Common; -using Femto.Common.Domain; - -namespace Femto.Modules.Blog.Application.Commands.ClearPostReaction; - -public record ClearPostReactionCommand(Guid PostId, string Emoji, Guid ReactorId): ICommand; \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/Commands/ClearPostReaction/ClearPostReactionCommandHandler.cs b/Femto.Modules.Blog/Application/Commands/ClearPostReaction/ClearPostReactionCommandHandler.cs deleted file mode 100644 index e4d344e..0000000 --- a/Femto.Modules.Blog/Application/Commands/ClearPostReaction/ClearPostReactionCommandHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Femto.Common.Domain; -using Femto.Modules.Blog.Application.Commands.AddPostReaction; -using Microsoft.EntityFrameworkCore; - -namespace Femto.Modules.Blog.Application.Commands.ClearPostReaction; - -internal class ClearPostReactionCommandHandler(BlogContext context) - : ICommandHandler -{ - public async Task Handle(ClearPostReactionCommand request, CancellationToken cancellationToken) - { - var post = await context.Posts.SingleOrDefaultAsync( - p => p.Id == request.PostId, - cancellationToken - ); - - if (post is null) - return; - - post.RemoveReaction(request.ReactorId, request.Emoji); - } -} diff --git a/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommand.cs b/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommand.cs index 30cc602..d10c496 100644 --- a/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommand.cs +++ b/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommand.cs @@ -1,22 +1,8 @@ -using Femto.Common; using Femto.Common.Domain; -using Femto.Modules.Blog.Application.Queries.GetPosts.Dto; namespace Femto.Modules.Blog.Application.Commands.CreatePost; -public record CreatePostCommand( - Guid AuthorId, - string Content, - IEnumerable Media, - bool? IsPublic, - CurrentUser CurrentUser -) : ICommand; +public record CreatePostCommand(Guid AuthorId, string Content, IEnumerable Media, bool? IsPublic) + : ICommand; -public record CreatePostMedia( - Guid MediaId, - Uri Url, - string? Type, - int Order, - int? Width, - int? Height -); +public record CreatePostMedia(Guid MediaId, Uri Url, string? Type, int Order, int? Width, int? Height); diff --git a/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommandHandler.cs b/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommandHandler.cs index 25bab45..cda4b2d 100644 --- a/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommandHandler.cs +++ b/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommandHandler.cs @@ -1,16 +1,12 @@ -using Femto.Modules.Blog.Application.Queries.GetPosts.Dto; using Femto.Modules.Blog.Domain.Posts; using MediatR; namespace Femto.Modules.Blog.Application.Commands.CreatePost; internal class CreatePostCommandHandler(BlogContext context) - : IRequestHandler + : IRequestHandler { - public async Task Handle( - CreatePostCommand request, - CancellationToken cancellationToken - ) + public async Task Handle(CreatePostCommand request, CancellationToken cancellationToken) { var post = new Post( request.AuthorId, @@ -24,21 +20,13 @@ internal class CreatePostCommandHandler(BlogContext context) media.Width, media.Height )) - .ToList(), - request.IsPublic is true + .ToList() ); + + post.IsPublic = request.IsPublic is true; await context.AddAsync(post, cancellationToken); - return new PostDto( - post.Id, - post.Content, - post.Media.Select(m => new PostMediaDto(m.Url, m.Width, m.Height)).ToList(), - post.PostedOn, - new PostAuthorDto(post.AuthorId, request.CurrentUser.Username), - [], - post.PossibleReactions, - [] - ); + return post.Id; } } diff --git a/Femto.Modules.Blog/Application/Commands/DeletePost/DeletePostCommand.cs b/Femto.Modules.Blog/Application/Commands/DeletePost/DeletePostCommand.cs deleted file mode 100644 index 099ed49..0000000 --- a/Femto.Modules.Blog/Application/Commands/DeletePost/DeletePostCommand.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Femto.Common.Domain; - -namespace Femto.Modules.Blog.Application.Commands.DeletePost; - -public record DeletePostCommand(Guid PostId, Guid InitiatingUserId) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/Configurations/PostConfiguration.cs b/Femto.Modules.Blog/Application/Configurations/PostConfiguration.cs index 630cbe2..8cb2a64 100644 --- a/Femto.Modules.Blog/Application/Configurations/PostConfiguration.cs +++ b/Femto.Modules.Blog/Application/Configurations/PostConfiguration.cs @@ -10,24 +10,5 @@ internal class PostConfiguration : IEntityTypeConfiguration { table.ToTable("post"); table.OwnsMany(post => post.Media).WithOwner(); - table.OwnsMany( - post => post.Reactions, - reactions => - { - reactions.WithOwner().HasForeignKey(r => r.PostId); - reactions.HasKey(r => new - { - r.PostId, - r.AuthorId, - r.Emoji, - }); - } - ); - - table.OwnsMany(p => p.Comments).WithOwner(); - - table.Property("PossibleReactionsJson").HasColumnName("possible_reactions"); - - table.Ignore(e => e.PossibleReactions); } } diff --git a/Femto.Modules.Blog/Application/IBlogModule.cs b/Femto.Modules.Blog/Application/IBlogModule.cs index 083b184..941e1e2 100644 --- a/Femto.Modules.Blog/Application/IBlogModule.cs +++ b/Femto.Modules.Blog/Application/IBlogModule.cs @@ -4,14 +4,14 @@ namespace Femto.Modules.Blog.Application; public interface IBlogModule { - Task Command(ICommand command, CancellationToken cancellationToken = default); + Task PostCommand(ICommand command, CancellationToken cancellationToken = default); - Task Command( + Task PostCommand( ICommand command, CancellationToken cancellationToken = default ); - Task Query( + Task PostQuery( IQuery query, CancellationToken cancellationToken = default ); diff --git a/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/GetPostsQueryResult.cs b/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/GetPostsQueryResult.cs index 8b75d6e..be8157a 100644 --- a/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/GetPostsQueryResult.cs +++ b/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/GetPostsQueryResult.cs @@ -1,3 +1,3 @@ namespace Femto.Modules.Blog.Application.Queries.GetPosts.Dto; -public record GetPostsQueryResult(IList Posts); \ No newline at end of file +public record GetPostsQueryResult(IList Posts, Guid? Next); \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostCommentDto.cs b/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostCommentDto.cs deleted file mode 100644 index 55ea5e8..0000000 --- a/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostCommentDto.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Modules.Blog.Application.Queries.GetPosts.Dto; - -public record PostCommentDto(string Author, string Content, DateTimeOffset PostedOn); \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostDto.cs b/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostDto.cs index 63efede..584d1f5 100644 --- a/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostDto.cs +++ b/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostDto.cs @@ -1,12 +1,3 @@ namespace Femto.Modules.Blog.Application.Queries.GetPosts.Dto; -public record PostDto( - Guid PostId, - string Text, - IList Media, - DateTimeOffset CreatedAt, - PostAuthorDto Author, - IList Reactions, - IEnumerable PossibleReactions, - IList Comments -); \ No newline at end of file +public record PostDto(Guid PostId, string Text, IList Media, DateTimeOffset CreatedAt, PostAuthorDto Author); \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostReactionDto.cs b/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostReactionDto.cs deleted file mode 100644 index 60349b9..0000000 --- a/Femto.Modules.Blog/Application/Queries/GetPosts/Dto/PostReactionDto.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Femto.Modules.Blog.Application.Queries.GetPosts.Dto; - -public record PostReactionDto(string Emoji, string AuthorName, DateTimeOffset ReactedOn); diff --git a/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQuery.cs b/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQuery.cs index 1bb1d4c..f8af9d2 100644 --- a/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQuery.cs +++ b/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQuery.cs @@ -3,27 +3,22 @@ using Femto.Modules.Blog.Application.Queries.GetPosts.Dto; namespace Femto.Modules.Blog.Application.Queries.GetPosts; -/// -/// Get posts in reverse chronological order -/// -/// public record GetPostsQuery(Guid? CurrentUserId) : IQuery { - /// - /// Id of the specific post to load. If specified, After and Amount are ignored - /// - public Guid? PostId { get; } - - /// - /// If specified, loads posts from after the given Id. Used for paging - /// - public Guid? After { get; init; } + public Guid? From { get; init; } public int Amount { get; init; } = 20; public Guid? AuthorId { get; init; } public string? Author { get; init; } - public GetPostsQuery(Guid postId, Guid? currentUserId) : this(currentUserId) - { - this.PostId = postId; - } -} \ No newline at end of file + /// + /// Default is to load in reverse chronological order + /// TODO this is not exposed on the client as it probably wouldn't work that well + /// + public GetPostsDirection Direction { get; init; } = GetPostsDirection.Backward; +} + +public enum GetPostsDirection +{ + Forward, + Backward, +} diff --git a/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs b/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs index c57627f..7ace8a6 100644 --- a/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs +++ b/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using Dapper; using Femto.Common.Infrastructure.DbConnection; using Femto.Modules.Blog.Application.Queries.GetPosts.Dto; @@ -16,158 +15,101 @@ public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory) { using var conn = connectionFactory.GetConnection(); + var orderBy = query.Direction is GetPostsDirection.Backward ? "desc" : "asc"; + var pageFilter = query.Direction is GetPostsDirection.Backward ? "<=" : ">="; var username = query.Author; var authorGuid = query.AuthorId; - var cursor = query.After; + var cursor = query.From; var showPrivate = query.CurrentUserId is not null; - var loadPostsResult = await conn.QueryAsync( - """ - select - blog.post.id as PostId, - blog.post.content as Content, - blog.post.posted_on as PostedOn, - blog.author.username as Username, - blog.author.id as AuthorId, - blog.post.possible_reactions as PossibleReactions - from blog.post - inner join blog.author on blog.author.id = blog.post.author_id - where (@username is null or blog.author.username = @username) - and (@postId is null or blog.post.id = @postId) - and (@showPrivate or blog.post.is_public = true) - and (@authorGuid is null or blog.author.id = @authorGuid) - and (@cursor is null or blog.post.id < @cursor) - order by blog.post.id desc - limit @amount - """, + // lang=sql + var sql = $$""" + with page as ( + select blog.post.*, blog.author.username as Username, blog.author.id as AuthorId + from blog.post + inner join blog.author on blog.author.id = blog.post.author_id + where (@username is null or blog.author.username = @username) + and (@showPrivate or blog.post.is_public = true) + and (@authorGuid is null or blog.author.id = @authorGuid) + and (@cursor is null or blog.post.id {{pageFilter}} @cursor) + order by blog.post.id {{orderBy}} + limit @amount + ) + select + page.id as PostId, + page.content as Content, + blog.post_media.url as MediaUrl, + blog.post_media.width as MediaWidth, + blog.post_media.height as MediaHeight, + page.posted_on as PostedOn, + page.Username, + page.AuthorId + from page + left join blog.post_media on blog.post_media.post_id = page.id + order by page.id {{orderBy}} + """; + + var result = await conn.QueryAsync( + sql, new { username, authorGuid, cursor, - amount = query.PostId is not null ? 1 : query.Amount, + // load an extra one to take for the cursor + amount = query.Amount + 1, showPrivate, - postId = query.PostId, } ); - var posts = loadPostsResult.ToList(); + var rows = result.ToList(); - var postIds = posts.Select(p => p.PostId).ToList(); + var posts = rows.GroupBy(row => row.PostId) + .Select(group => + { + var postId = group.Key; + var post = group.First(); + var media = group + .Select(row => + { + if (row.MediaUrl is not null) + { + return new PostMediaDto( + new Uri(row.MediaUrl), + row.MediaHeight, + row.MediaHeight + ); + } + else + return null; + }) + .OfType() + .ToList(); + return new PostDto( + postId, + post.Content, + media, + post.PostedOn, + new PostAuthorDto(post.AuthorId, post.Username) + ); + }) + .ToList(); - var loadMediaResult = await conn.QueryAsync( - """ - select - pm.url as MediaUrl, - pm.type as MediaType, - pm.width as MediaWidth, - pm.height as MediaHeight, - pm.post_id as PostId - from blog.post_media pm where pm.post_id = ANY (@postIds) - order by pm.ordering - """, - new { postIds } - ); + var next = rows.Count >= query.Amount ? rows.LastOrDefault()?.PostId : null; - var media = loadMediaResult.ToList(); - - var loadReactionsResult = await conn.QueryAsync( - """ - select - pr.post_id as PostId, - a.username as AuthorName, - pr.emoji as Emoji, - pr.created_at as CreatedOn - from blog.post_reaction pr - join blog.author a on a.id = pr.author_id - where pr.post_id = ANY (@postIds) - """, - new { postIds } - ); - - var reactions = loadReactionsResult.ToList(); - - var loadCommentsResult = await conn.QueryAsync( - """ - select - pc.id as CommentId, - pc.post_id as PostId, - pc.content as Content, - pc.created_at as PostedOn, - a.username as AuthorName - from blog.post_comment pc - join blog.author a on pc.author_id = a.id - where pc.post_id = ANY (@postIds) - """, - new { postIds } - ); - - var comments = loadCommentsResult.ToList(); - - return new GetPostsQueryResult( - posts - .Select(p => new PostDto( - p.PostId, - p.Content, - media - .Where(m => m.PostId == p.PostId) - .Select(m => new PostMediaDto( - new Uri(m.MediaUrl), - m.MediaWidth, - m.MediaHeight - )) - .ToList(), - p.PostedOn, - new PostAuthorDto(p.AuthorId, p.Username), - reactions - .Where(r => r.PostId == p.PostId) - .Select(r => new PostReactionDto(r.Emoji, r.AuthorName, r.CreatedAt)) - .ToList(), - !string.IsNullOrEmpty(p.PossibleReactions) - ? JsonSerializer.Deserialize>(p.PossibleReactions)! - : [], - comments - .Where(c => c.PostId == p.PostId) - .Select(c => new PostCommentDto(c.AuthorName, c.Content, c.PostedOn)) - .ToList() - )) - .ToList() - ); + return new GetPostsQueryResult(posts, next); } - internal record LoadPostRow + internal class QueryResult { - public Guid PostId { get; init; } - public string Content { get; init; } - public DateTimeOffset PostedOn { get; init; } - public string Username { get; init; } - public Guid AuthorId { get; init; } - public string? PossibleReactions { get; init; } - } - - internal record LoadMediaRow - { - public string MediaUrl { get; init; } - public string? MediaType { get; init; } - public int? MediaWidth { get; init; } - public int? MediaHeight { get; init; } - public Guid PostId { get; init; } - } - - internal record LoadReactionRow - { - public Guid PostId { get; init; } - public string AuthorName { get; init; } - public string Emoji { get; init; } - public DateTimeOffset CreatedAt { get; init; } - } - - internal record LoadCommentRow - { - public Guid CommentId { get; init; } - public Guid PostId { get; init; } - public string Content { get; init; } - public DateTimeOffset PostedOn { get; init; } - public string AuthorName { get; init; } + public Guid PostId { get; set; } + public string Content { get; set; } + public string? MediaUrl { get; set; } + public string? MediaType { get; set; } + public int? MediaWidth { get; set; } + public int? MediaHeight { get; set; } + public DateTimeOffset PostedOn { get; set; } + public Guid AuthorId { get; set; } + public string Username { get; set; } } } diff --git a/Femto.Modules.Blog/Domain/Posts/Post.cs b/Femto.Modules.Blog/Domain/Posts/Post.cs index b5a9b2d..65f90d6 100644 --- a/Femto.Modules.Blog/Domain/Posts/Post.cs +++ b/Femto.Modules.Blog/Domain/Posts/Post.cs @@ -1,7 +1,5 @@ -using System.Text.Json; using Femto.Common.Domain; using Femto.Modules.Blog.Domain.Posts.Events; -using Femto.Modules.Blog.Emoji; namespace Femto.Modules.Blog.Domain.Posts; @@ -11,62 +9,17 @@ internal class Post : Entity public Guid AuthorId { get; private set; } public string Content { get; private set; } = null!; public IList Media { get; private set; } - - public IList Reactions { get; private set; } = []; - - public IList Comments { get; private set; } = []; - public bool IsPublic { get; private set; } - - public DateTimeOffset PostedOn { get; private set; } - - private string PossibleReactionsJson { get; set; } = null!; - - public IEnumerable PossibleReactions - { - get => JsonSerializer.Deserialize>(this.PossibleReactionsJson)!; - init => PossibleReactionsJson = JsonSerializer.Serialize(value); - } + public bool IsPublic { get; set; } private Post() { } - public Post(Guid authorId, string content, IList media, bool isPublic) + public Post(Guid authorId, string content, IList media) { this.Id = Guid.CreateVersion7(); this.AuthorId = authorId; this.Content = content; this.Media = media; - this.PossibleReactions = AllEmoji.GetRandomEmoji(5); - this.PostedOn = DateTimeOffset.UtcNow; - this.IsPublic = isPublic; this.AddDomainEvent(new PostCreated(this)); } - - public void AddReaction(Guid reactorId, string emoji) - { - if (!this.PossibleReactions.Contains(emoji)) - return; - - if (this.Reactions.Any(r => r.AuthorId == reactorId && r.Emoji == emoji)) - return; - - this.Reactions.Add(new PostReaction(reactorId, this.Id, emoji)); - } - - public void RemoveReaction(Guid reactorId, string emoji) - { - this.Reactions = this - .Reactions.Where(r => r.AuthorId != reactorId || r.Emoji != emoji) - .ToList(); - } - - public void AddComment(Guid authorId, string content) - { - // XXX just ignore empty comments for now. we may want to upgrade this to an error - // but it is probably suitable to just consider it a no-op - if (string.IsNullOrWhiteSpace(content)) - return; - - this.Comments.Add(new PostComment(authorId, content)); - } } diff --git a/Femto.Modules.Blog/Domain/Posts/PostComment.cs b/Femto.Modules.Blog/Domain/Posts/PostComment.cs deleted file mode 100644 index 6f658a8..0000000 --- a/Femto.Modules.Blog/Domain/Posts/PostComment.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Femto.Modules.Blog.Domain.Posts; - -internal class PostComment -{ - public Guid Id { get; private set; } - public Guid AuthorId { get; private set; } - public DateTimeOffset CreatedAt { get; private set; } - public string Content { get; private set; } - - private PostComment() {} - - public PostComment(Guid authorId, string content) - { - this.Id = Guid.CreateVersion7(); - this.AuthorId = authorId; - this.Content = content; - this.CreatedAt = TimeProvider.System.GetUtcNow(); - } -} \ No newline at end of file diff --git a/Femto.Modules.Blog/Domain/Posts/PostReaction.cs b/Femto.Modules.Blog/Domain/Posts/PostReaction.cs deleted file mode 100644 index 38e33b8..0000000 --- a/Femto.Modules.Blog/Domain/Posts/PostReaction.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Femto.Modules.Blog.Domain.Posts; - -public class PostReaction -{ - public Guid AuthorId { get; private set; } - public Guid PostId { get; private set; } - public string Emoji { get; private set; } = null!; - public DateTimeOffset CreatedAt { get; private set; } - public PostReaction(Guid authorId, Guid postId, string emoji) - { - this.AuthorId = authorId; - this.PostId = postId; - this.Emoji = emoji; - this.CreatedAt = TimeProvider.System.GetUtcNow(); - } - - private PostReaction() { } -} \ No newline at end of file diff --git a/Femto.Modules.Blog/Emoji/GetRandomEmoji.cs b/Femto.Modules.Blog/Emoji/GetRandomEmoji.cs deleted file mode 100644 index 8fa7928..0000000 --- a/Femto.Modules.Blog/Emoji/GetRandomEmoji.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Femto.Modules.Blog.Emoji; - -internal static partial class AllEmoji -{ - public static IList GetRandomEmoji(int count) => new Random().TakeRandomly(Emojis).Distinct().Take(count).ToList(); -} - -internal static class RandomExtensions -{ - public static IEnumerable TakeRandomly(this Random rand, ICollection collection) - { - while (true) - { - var idx = rand.Next(collection.Count); - yield return collection.ElementAt(idx); - } - } -} diff --git a/Femto.Modules.Blog/Emoji/ListOfEmoji.cs b/Femto.Modules.Blog/Emoji/ListOfEmoji.cs deleted file mode 100644 index f3ad8b5..0000000 --- a/Femto.Modules.Blog/Emoji/ListOfEmoji.cs +++ /dev/null @@ -1,3789 +0,0 @@ -namespace Femto.Modules.Blog.Emoji; - -internal static partial class AllEmoji -{ - public static readonly string[] Emojis = - [ - "๐Ÿ˜€", - "๐Ÿ˜ƒ", - "๐Ÿ˜„", - "๐Ÿ˜", - "๐Ÿ˜†", - "๐Ÿ˜…", - "๐Ÿคฃ", - "๐Ÿ˜‚", - "๐Ÿ™‚", - "๐Ÿ™ƒ", - "๐Ÿซ ", - "๐Ÿ˜‰", - "๐Ÿ˜Š", - "๐Ÿ˜‡", - "๐Ÿฅฐ", - "๐Ÿ˜", - "๐Ÿคฉ", - "๐Ÿ˜˜", - "๐Ÿ˜—", - "โ˜บ๏ธ", - "๐Ÿ˜š", - "๐Ÿ˜™", - "๐Ÿฅฒ", - "๐Ÿ˜‹", - "๐Ÿ˜›", - "๐Ÿ˜œ", - "๐Ÿคช", - "๐Ÿ˜", - "๐Ÿค‘", - "๐Ÿค—", - "๐Ÿคญ", - "๐Ÿซข", - "๐Ÿซฃ", - "๐Ÿคซ", - "๐Ÿค”", - "๐Ÿซก", - "๐Ÿค", - "๐Ÿคจ", - "๐Ÿ˜", - "๐Ÿ˜‘", - "๐Ÿ˜ถ", - "๐Ÿซฅ", - "๐Ÿ˜ถโ€๐ŸŒซ๏ธ", - "๐Ÿ˜", - "๐Ÿ˜’", - "๐Ÿ™„", - "๐Ÿ˜ฌ", - "๐Ÿ˜ฎโ€๐Ÿ’จ", - "๐Ÿคฅ", - "๐Ÿซจ", - "๐Ÿ™‚โ€โ†”๏ธ", - "๐Ÿ™‚โ€โ†•๏ธ", - "๐Ÿ˜Œ", - "๐Ÿ˜”", - "๐Ÿ˜ช", - "๐Ÿคค", - "๐Ÿ˜ด", - "๐Ÿซฉ", - "๐Ÿ˜ท", - "๐Ÿค’", - "๐Ÿค•", - "๐Ÿคข", - "๐Ÿคฎ", - "๐Ÿคง", - "๐Ÿฅต", - "๐Ÿฅถ", - "๐Ÿฅด", - "๐Ÿ˜ต", - "๐Ÿ˜ตโ€๐Ÿ’ซ", - "๐Ÿคฏ", - "๐Ÿค ", - "๐Ÿฅณ", - "๐Ÿฅธ", - "๐Ÿ˜Ž", - "๐Ÿค“", - "๐Ÿง", - "๐Ÿ˜•", - "๐Ÿซค", - "๐Ÿ˜Ÿ", - "๐Ÿ™", - "โ˜น๏ธ", - "๐Ÿ˜ฎ", - "๐Ÿ˜ฏ", - "๐Ÿ˜ฒ", - "๐Ÿ˜ณ", - "๐Ÿฅบ", - "๐Ÿฅน", - "๐Ÿ˜ฆ", - "๐Ÿ˜ง", - "๐Ÿ˜จ", - "๐Ÿ˜ฐ", - "๐Ÿ˜ฅ", - "๐Ÿ˜ข", - "๐Ÿ˜ญ", - "๐Ÿ˜ฑ", - "๐Ÿ˜–", - "๐Ÿ˜ฃ", - "๐Ÿ˜ž", - "๐Ÿ˜“", - "๐Ÿ˜ฉ", - "๐Ÿ˜ซ", - "๐Ÿฅฑ", - "๐Ÿ˜ค", - "๐Ÿ˜ก", - "๐Ÿ˜ ", - "๐Ÿคฌ", - "๐Ÿ˜ˆ", - "๐Ÿ‘ฟ", - "๐Ÿ’€", - "โ˜ ๏ธ", - "๐Ÿ’ฉ", - "๐Ÿคก", - "๐Ÿ‘น", - "๐Ÿ‘บ", - "๐Ÿ‘ป", - "๐Ÿ‘ฝ", - "๐Ÿ‘พ", - "๐Ÿค–", - "๐Ÿ˜บ", - "๐Ÿ˜ธ", - "๐Ÿ˜น", - "๐Ÿ˜ป", - "๐Ÿ˜ผ", - "๐Ÿ˜ฝ", - "๐Ÿ™€", - "๐Ÿ˜ฟ", - "๐Ÿ˜พ", - "๐Ÿ™ˆ", - "๐Ÿ™‰", - "๐Ÿ™Š", - "๐Ÿ’Œ", - "๐Ÿ’˜", - "๐Ÿ’", - "๐Ÿ’–", - "๐Ÿ’—", - "๐Ÿ’“", - "๐Ÿ’ž", - "๐Ÿ’•", - "๐Ÿ’Ÿ", - "โฃ๏ธ", - "๐Ÿ’”", - "โค๏ธโ€๐Ÿ”ฅ", - "โค๏ธโ€๐Ÿฉน", - "โค๏ธ", - "๐Ÿฉท", - "๐Ÿงก", - "๐Ÿ’›", - "๐Ÿ’š", - "๐Ÿ’™", - "๐Ÿฉต", - "๐Ÿ’œ", - "๐ŸคŽ", - "๐Ÿ–ค", - "๐Ÿฉถ", - "๐Ÿค", - "๐Ÿ’‹", - "๐Ÿ’ฏ", - "๐Ÿ’ข", - "๐Ÿ’ฅ", - "๐Ÿ’ซ", - "๐Ÿ’ฆ", - "๐Ÿ’จ", - "๐Ÿ•ณ๏ธ", - "๐Ÿ’ฌ", - "๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ", - "๐Ÿ—จ๏ธ", - "๐Ÿ—ฏ๏ธ", - "๐Ÿ’ญ", - "๐Ÿ’ค", - "๐Ÿ‘‹", - "๐Ÿ‘‹๐Ÿป", - "๐Ÿ‘‹๐Ÿผ", - "๐Ÿ‘‹๐Ÿฝ", - "๐Ÿ‘‹๐Ÿพ", - "๐Ÿ‘‹๐Ÿฟ", - "๐Ÿคš", - "๐Ÿคš๐Ÿป", - "๐Ÿคš๐Ÿผ", - "๐Ÿคš๐Ÿฝ", - "๐Ÿคš๐Ÿพ", - "๐Ÿคš๐Ÿฟ", - "๐Ÿ–๏ธ", - "๐Ÿ–๐Ÿป", - "๐Ÿ–๐Ÿผ", - "๐Ÿ–๐Ÿฝ", - "๐Ÿ–๐Ÿพ", - "๐Ÿ–๐Ÿฟ", - "โœ‹", - "โœ‹๐Ÿป", - "โœ‹๐Ÿผ", - "โœ‹๐Ÿฝ", - "โœ‹๐Ÿพ", - "โœ‹๐Ÿฟ", - "๐Ÿ––", - "๐Ÿ––๐Ÿป", - "๐Ÿ––๐Ÿผ", - "๐Ÿ––๐Ÿฝ", - "๐Ÿ––๐Ÿพ", - "๐Ÿ––๐Ÿฟ", - "๐Ÿซฑ", - "๐Ÿซฑ๐Ÿป", - "๐Ÿซฑ๐Ÿผ", - "๐Ÿซฑ๐Ÿฝ", - "๐Ÿซฑ๐Ÿพ", - "๐Ÿซฑ๐Ÿฟ", - "๐Ÿซฒ", - "๐Ÿซฒ๐Ÿป", - "๐Ÿซฒ๐Ÿผ", - "๐Ÿซฒ๐Ÿฝ", - "๐Ÿซฒ๐Ÿพ", - "๐Ÿซฒ๐Ÿฟ", - "๐Ÿซณ", - "๐Ÿซณ๐Ÿป", - "๐Ÿซณ๐Ÿผ", - "๐Ÿซณ๐Ÿฝ", - "๐Ÿซณ๐Ÿพ", - "๐Ÿซณ๐Ÿฟ", - "๐Ÿซด", - "๐Ÿซด๐Ÿป", - "๐Ÿซด๐Ÿผ", - "๐Ÿซด๐Ÿฝ", - "๐Ÿซด๐Ÿพ", - "๐Ÿซด๐Ÿฟ", - "๐Ÿซท", - "๐Ÿซท๐Ÿป", - "๐Ÿซท๐Ÿผ", - "๐Ÿซท๐Ÿฝ", - "๐Ÿซท๐Ÿพ", - "๐Ÿซท๐Ÿฟ", - "๐Ÿซธ", - "๐Ÿซธ๐Ÿป", - "๐Ÿซธ๐Ÿผ", - "๐Ÿซธ๐Ÿฝ", - "๐Ÿซธ๐Ÿพ", - "๐Ÿซธ๐Ÿฟ", - "๐Ÿ‘Œ", - "๐Ÿ‘Œ๐Ÿป", - "๐Ÿ‘Œ๐Ÿผ", - "๐Ÿ‘Œ๐Ÿฝ", - "๐Ÿ‘Œ๐Ÿพ", - "๐Ÿ‘Œ๐Ÿฟ", - "๐ŸคŒ", - "๐ŸคŒ๐Ÿป", - "๐ŸคŒ๐Ÿผ", - "๐ŸคŒ๐Ÿฝ", - "๐ŸคŒ๐Ÿพ", - "๐ŸคŒ๐Ÿฟ", - "๐Ÿค", - "๐Ÿค๐Ÿป", - "๐Ÿค๐Ÿผ", - "๐Ÿค๐Ÿฝ", - "๐Ÿค๐Ÿพ", - "๐Ÿค๐Ÿฟ", - "โœŒ๏ธ", - "โœŒ๐Ÿป", - "โœŒ๐Ÿผ", - "โœŒ๐Ÿฝ", - "โœŒ๐Ÿพ", - "โœŒ๐Ÿฟ", - "๐Ÿคž", - "๐Ÿคž๐Ÿป", - "๐Ÿคž๐Ÿผ", - "๐Ÿคž๐Ÿฝ", - "๐Ÿคž๐Ÿพ", - "๐Ÿคž๐Ÿฟ", - "๐Ÿซฐ", - "๐Ÿซฐ๐Ÿป", - "๐Ÿซฐ๐Ÿผ", - "๐Ÿซฐ๐Ÿฝ", - "๐Ÿซฐ๐Ÿพ", - "๐Ÿซฐ๐Ÿฟ", - "๐ŸคŸ", - "๐ŸคŸ๐Ÿป", - "๐ŸคŸ๐Ÿผ", - "๐ŸคŸ๐Ÿฝ", - "๐ŸคŸ๐Ÿพ", - "๐ŸคŸ๐Ÿฟ", - "๐Ÿค˜", - "๐Ÿค˜๐Ÿป", - "๐Ÿค˜๐Ÿผ", - "๐Ÿค˜๐Ÿฝ", - "๐Ÿค˜๐Ÿพ", - "๐Ÿค˜๐Ÿฟ", - "๐Ÿค™", - "๐Ÿค™๐Ÿป", - "๐Ÿค™๐Ÿผ", - "๐Ÿค™๐Ÿฝ", - "๐Ÿค™๐Ÿพ", - "๐Ÿค™๐Ÿฟ", - "๐Ÿ‘ˆ", - "๐Ÿ‘ˆ๐Ÿป", - "๐Ÿ‘ˆ๐Ÿผ", - "๐Ÿ‘ˆ๐Ÿฝ", - "๐Ÿ‘ˆ๐Ÿพ", - "๐Ÿ‘ˆ๐Ÿฟ", - "๐Ÿ‘‰", - "๐Ÿ‘‰๐Ÿป", - "๐Ÿ‘‰๐Ÿผ", - "๐Ÿ‘‰๐Ÿฝ", - "๐Ÿ‘‰๐Ÿพ", - "๐Ÿ‘‰๐Ÿฟ", - "๐Ÿ‘†", - "๐Ÿ‘†๐Ÿป", - "๐Ÿ‘†๐Ÿผ", - "๐Ÿ‘†๐Ÿฝ", - "๐Ÿ‘†๐Ÿพ", - "๐Ÿ‘†๐Ÿฟ", - "๐Ÿ–•", - "๐Ÿ–•๐Ÿป", - "๐Ÿ–•๐Ÿผ", - "๐Ÿ–•๐Ÿฝ", - "๐Ÿ–•๐Ÿพ", - "๐Ÿ–•๐Ÿฟ", - "๐Ÿ‘‡", - "๐Ÿ‘‡๐Ÿป", - "๐Ÿ‘‡๐Ÿผ", - "๐Ÿ‘‡๐Ÿฝ", - "๐Ÿ‘‡๐Ÿพ", - "๐Ÿ‘‡๐Ÿฟ", - "โ˜๏ธ", - "โ˜๐Ÿป", - "โ˜๐Ÿผ", - "โ˜๐Ÿฝ", - "โ˜๐Ÿพ", - "โ˜๐Ÿฟ", - "๐Ÿซต", - "๐Ÿซต๐Ÿป", - "๐Ÿซต๐Ÿผ", - "๐Ÿซต๐Ÿฝ", - "๐Ÿซต๐Ÿพ", - "๐Ÿซต๐Ÿฟ", - "๐Ÿ‘", - "๐Ÿ‘๐Ÿป", - "๐Ÿ‘๐Ÿผ", - "๐Ÿ‘๐Ÿฝ", - "๐Ÿ‘๐Ÿพ", - "๐Ÿ‘๐Ÿฟ", - "๐Ÿ‘Ž", - "๐Ÿ‘Ž๐Ÿป", - "๐Ÿ‘Ž๐Ÿผ", - "๐Ÿ‘Ž๐Ÿฝ", - "๐Ÿ‘Ž๐Ÿพ", - "๐Ÿ‘Ž๐Ÿฟ", - "โœŠ", - "โœŠ๐Ÿป", - "โœŠ๐Ÿผ", - "โœŠ๐Ÿฝ", - "โœŠ๐Ÿพ", - "โœŠ๐Ÿฟ", - "๐Ÿ‘Š", - "๐Ÿ‘Š๐Ÿป", - "๐Ÿ‘Š๐Ÿผ", - "๐Ÿ‘Š๐Ÿฝ", - "๐Ÿ‘Š๐Ÿพ", - "๐Ÿ‘Š๐Ÿฟ", - "๐Ÿค›", - "๐Ÿค›๐Ÿป", - "๐Ÿค›๐Ÿผ", - "๐Ÿค›๐Ÿฝ", - "๐Ÿค›๐Ÿพ", - "๐Ÿค›๐Ÿฟ", - "๐Ÿคœ", - "๐Ÿคœ๐Ÿป", - "๐Ÿคœ๐Ÿผ", - "๐Ÿคœ๐Ÿฝ", - "๐Ÿคœ๐Ÿพ", - "๐Ÿคœ๐Ÿฟ", - "๐Ÿ‘", - "๐Ÿ‘๐Ÿป", - "๐Ÿ‘๐Ÿผ", - "๐Ÿ‘๐Ÿฝ", - "๐Ÿ‘๐Ÿพ", - "๐Ÿ‘๐Ÿฟ", - "๐Ÿ™Œ", - "๐Ÿ™Œ๐Ÿป", - "๐Ÿ™Œ๐Ÿผ", - "๐Ÿ™Œ๐Ÿฝ", - "๐Ÿ™Œ๐Ÿพ", - "๐Ÿ™Œ๐Ÿฟ", - "๐Ÿซถ", - "๐Ÿซถ๐Ÿป", - "๐Ÿซถ๐Ÿผ", - "๐Ÿซถ๐Ÿฝ", - "๐Ÿซถ๐Ÿพ", - "๐Ÿซถ๐Ÿฟ", - "๐Ÿ‘", - "๐Ÿ‘๐Ÿป", - "๐Ÿ‘๐Ÿผ", - "๐Ÿ‘๐Ÿฝ", - "๐Ÿ‘๐Ÿพ", - "๐Ÿ‘๐Ÿฟ", - "๐Ÿคฒ", - "๐Ÿคฒ๐Ÿป", - "๐Ÿคฒ๐Ÿผ", - "๐Ÿคฒ๐Ÿฝ", - "๐Ÿคฒ๐Ÿพ", - "๐Ÿคฒ๐Ÿฟ", - "๐Ÿค", - "๐Ÿค๐Ÿป", - "๐Ÿค๐Ÿผ", - "๐Ÿค๐Ÿฝ", - "๐Ÿค๐Ÿพ", - "๐Ÿค๐Ÿฟ", - "๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿผ", - "๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฝ", - "๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿพ", - "๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฟ", - "๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿป", - "๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿฝ", - "๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿพ", - "๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿฟ", - "๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿป", - "๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿผ", - "๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿพ", - "๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿฟ", - "๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿป", - "๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿผ", - "๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿฝ", - "๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿฟ", - "๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿป", - "๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿผ", - "๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿฝ", - "๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿพ", - "๐Ÿ™", - "๐Ÿ™๐Ÿป", - "๐Ÿ™๐Ÿผ", - "๐Ÿ™๐Ÿฝ", - "๐Ÿ™๐Ÿพ", - "๐Ÿ™๐Ÿฟ", - "โœ๏ธ", - "โœ๐Ÿป", - "โœ๐Ÿผ", - "โœ๐Ÿฝ", - "โœ๐Ÿพ", - "โœ๐Ÿฟ", - "๐Ÿ’…", - "๐Ÿ’…๐Ÿป", - "๐Ÿ’…๐Ÿผ", - "๐Ÿ’…๐Ÿฝ", - "๐Ÿ’…๐Ÿพ", - "๐Ÿ’…๐Ÿฟ", - "๐Ÿคณ", - "๐Ÿคณ๐Ÿป", - "๐Ÿคณ๐Ÿผ", - "๐Ÿคณ๐Ÿฝ", - "๐Ÿคณ๐Ÿพ", - "๐Ÿคณ๐Ÿฟ", - "๐Ÿ’ช", - "๐Ÿ’ช๐Ÿป", - "๐Ÿ’ช๐Ÿผ", - "๐Ÿ’ช๐Ÿฝ", - "๐Ÿ’ช๐Ÿพ", - "๐Ÿ’ช๐Ÿฟ", - "๐Ÿฆพ", - "๐Ÿฆฟ", - "๐Ÿฆต", - "๐Ÿฆต๐Ÿป", - "๐Ÿฆต๐Ÿผ", - "๐Ÿฆต๐Ÿฝ", - "๐Ÿฆต๐Ÿพ", - "๐Ÿฆต๐Ÿฟ", - "๐Ÿฆถ", - "๐Ÿฆถ๐Ÿป", - "๐Ÿฆถ๐Ÿผ", - "๐Ÿฆถ๐Ÿฝ", - "๐Ÿฆถ๐Ÿพ", - "๐Ÿฆถ๐Ÿฟ", - "๐Ÿ‘‚", - "๐Ÿ‘‚๐Ÿป", - "๐Ÿ‘‚๐Ÿผ", - "๐Ÿ‘‚๐Ÿฝ", - "๐Ÿ‘‚๐Ÿพ", - "๐Ÿ‘‚๐Ÿฟ", - "๐Ÿฆป", - "๐Ÿฆป๐Ÿป", - "๐Ÿฆป๐Ÿผ", - "๐Ÿฆป๐Ÿฝ", - "๐Ÿฆป๐Ÿพ", - "๐Ÿฆป๐Ÿฟ", - "๐Ÿ‘ƒ", - "๐Ÿ‘ƒ๐Ÿป", - "๐Ÿ‘ƒ๐Ÿผ", - "๐Ÿ‘ƒ๐Ÿฝ", - "๐Ÿ‘ƒ๐Ÿพ", - "๐Ÿ‘ƒ๐Ÿฟ", - "๐Ÿง ", - "๐Ÿซ€", - "๐Ÿซ", - "๐Ÿฆท", - "๐Ÿฆด", - "๐Ÿ‘€", - "๐Ÿ‘๏ธ", - "๐Ÿ‘…", - "๐Ÿ‘„", - "๐Ÿซฆ", - "๐Ÿ‘ถ", - "๐Ÿ‘ถ๐Ÿป", - "๐Ÿ‘ถ๐Ÿผ", - "๐Ÿ‘ถ๐Ÿฝ", - "๐Ÿ‘ถ๐Ÿพ", - "๐Ÿ‘ถ๐Ÿฟ", - "๐Ÿง’", - "๐Ÿง’๐Ÿป", - "๐Ÿง’๐Ÿผ", - "๐Ÿง’๐Ÿฝ", - "๐Ÿง’๐Ÿพ", - "๐Ÿง’๐Ÿฟ", - "๐Ÿ‘ฆ", - "๐Ÿ‘ฆ๐Ÿป", - "๐Ÿ‘ฆ๐Ÿผ", - "๐Ÿ‘ฆ๐Ÿฝ", - "๐Ÿ‘ฆ๐Ÿพ", - "๐Ÿ‘ฆ๐Ÿฟ", - "๐Ÿ‘ง", - "๐Ÿ‘ง๐Ÿป", - "๐Ÿ‘ง๐Ÿผ", - "๐Ÿ‘ง๐Ÿฝ", - "๐Ÿ‘ง๐Ÿพ", - "๐Ÿ‘ง๐Ÿฟ", - "๐Ÿง‘", - "๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿฟ", - "๐Ÿ‘ฑ", - "๐Ÿ‘ฑ๐Ÿป", - "๐Ÿ‘ฑ๐Ÿผ", - "๐Ÿ‘ฑ๐Ÿฝ", - "๐Ÿ‘ฑ๐Ÿพ", - "๐Ÿ‘ฑ๐Ÿฟ", - "๐Ÿ‘จ", - "๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿฟ", - "๐Ÿง”", - "๐Ÿง”๐Ÿป", - "๐Ÿง”๐Ÿผ", - "๐Ÿง”๐Ÿฝ", - "๐Ÿง”๐Ÿพ", - "๐Ÿง”๐Ÿฟ", - "๐Ÿง”โ€โ™‚๏ธ", - "๐Ÿง”๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง”๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง”๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง”๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง”๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿง”โ€โ™€๏ธ", - "๐Ÿง”๐Ÿปโ€โ™€๏ธ", - "๐Ÿง”๐Ÿผโ€โ™€๏ธ", - "๐Ÿง”๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง”๐Ÿพโ€โ™€๏ธ", - "๐Ÿง”๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ‘จโ€๐Ÿฆฐ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฐ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฐ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฐ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฐ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฐ", - "๐Ÿ‘จโ€๐Ÿฆฑ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฑ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฑ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฑ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฑ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฑ", - "๐Ÿ‘จโ€๐Ÿฆณ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆณ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆณ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆณ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆณ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆณ", - "๐Ÿ‘จโ€๐Ÿฆฒ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฒ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฒ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฒ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฒ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฒ", - "๐Ÿ‘ฉ", - "๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉโ€๐Ÿฆฐ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฐ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฐ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฐ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฐ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฐ", - "๐Ÿง‘โ€๐Ÿฆฐ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆฐ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆฐ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฐ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆฐ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฐ", - "๐Ÿ‘ฉโ€๐Ÿฆฑ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฑ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฑ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฑ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฑ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฑ", - "๐Ÿง‘โ€๐Ÿฆฑ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆฑ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆฑ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฑ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆฑ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฑ", - "๐Ÿ‘ฉโ€๐Ÿฆณ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆณ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆณ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆณ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆณ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆณ", - "๐Ÿง‘โ€๐Ÿฆณ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆณ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆณ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆณ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆณ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆณ", - "๐Ÿ‘ฉโ€๐Ÿฆฒ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฒ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฒ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฒ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฒ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฒ", - "๐Ÿง‘โ€๐Ÿฆฒ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆฒ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆฒ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฒ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆฒ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฒ", - "๐Ÿ‘ฑโ€โ™€๏ธ", - "๐Ÿ‘ฑ๐Ÿปโ€โ™€๏ธ", - "๐Ÿ‘ฑ๐Ÿผโ€โ™€๏ธ", - "๐Ÿ‘ฑ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ‘ฑ๐Ÿพโ€โ™€๏ธ", - "๐Ÿ‘ฑ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ‘ฑโ€โ™‚๏ธ", - "๐Ÿ‘ฑ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ‘ฑ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ‘ฑ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ‘ฑ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ‘ฑ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿง“", - "๐Ÿง“๐Ÿป", - "๐Ÿง“๐Ÿผ", - "๐Ÿง“๐Ÿฝ", - "๐Ÿง“๐Ÿพ", - "๐Ÿง“๐Ÿฟ", - "๐Ÿ‘ด", - "๐Ÿ‘ด๐Ÿป", - "๐Ÿ‘ด๐Ÿผ", - "๐Ÿ‘ด๐Ÿฝ", - "๐Ÿ‘ด๐Ÿพ", - "๐Ÿ‘ด๐Ÿฟ", - "๐Ÿ‘ต", - "๐Ÿ‘ต๐Ÿป", - "๐Ÿ‘ต๐Ÿผ", - "๐Ÿ‘ต๐Ÿฝ", - "๐Ÿ‘ต๐Ÿพ", - "๐Ÿ‘ต๐Ÿฟ", - "๐Ÿ™", - "๐Ÿ™๐Ÿป", - "๐Ÿ™๐Ÿผ", - "๐Ÿ™๐Ÿฝ", - "๐Ÿ™๐Ÿพ", - "๐Ÿ™๐Ÿฟ", - "๐Ÿ™โ€โ™‚๏ธ", - "๐Ÿ™๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ™๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ™๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ™๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ™๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ™โ€โ™€๏ธ", - "๐Ÿ™๐Ÿปโ€โ™€๏ธ", - "๐Ÿ™๐Ÿผโ€โ™€๏ธ", - "๐Ÿ™๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ™๐Ÿพโ€โ™€๏ธ", - "๐Ÿ™๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ™Ž", - "๐Ÿ™Ž๐Ÿป", - "๐Ÿ™Ž๐Ÿผ", - "๐Ÿ™Ž๐Ÿฝ", - "๐Ÿ™Ž๐Ÿพ", - "๐Ÿ™Ž๐Ÿฟ", - "๐Ÿ™Žโ€โ™‚๏ธ", - "๐Ÿ™Ž๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ™Ž๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ™Ž๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ™Ž๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ™Ž๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ™Žโ€โ™€๏ธ", - "๐Ÿ™Ž๐Ÿปโ€โ™€๏ธ", - "๐Ÿ™Ž๐Ÿผโ€โ™€๏ธ", - "๐Ÿ™Ž๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ™Ž๐Ÿพโ€โ™€๏ธ", - "๐Ÿ™Ž๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ™…", - "๐Ÿ™…๐Ÿป", - "๐Ÿ™…๐Ÿผ", - "๐Ÿ™…๐Ÿฝ", - "๐Ÿ™…๐Ÿพ", - "๐Ÿ™…๐Ÿฟ", - "๐Ÿ™…โ€โ™‚๏ธ", - "๐Ÿ™…๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ™…๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ™…๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ™…๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ™…๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ™…โ€โ™€๏ธ", - "๐Ÿ™…๐Ÿปโ€โ™€๏ธ", - "๐Ÿ™…๐Ÿผโ€โ™€๏ธ", - "๐Ÿ™…๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ™…๐Ÿพโ€โ™€๏ธ", - "๐Ÿ™…๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ™†", - "๐Ÿ™†๐Ÿป", - "๐Ÿ™†๐Ÿผ", - "๐Ÿ™†๐Ÿฝ", - "๐Ÿ™†๐Ÿพ", - "๐Ÿ™†๐Ÿฟ", - "๐Ÿ™†โ€โ™‚๏ธ", - "๐Ÿ™†๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ™†๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ™†๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ™†๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ™†๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ™†โ€โ™€๏ธ", - "๐Ÿ™†๐Ÿปโ€โ™€๏ธ", - "๐Ÿ™†๐Ÿผโ€โ™€๏ธ", - "๐Ÿ™†๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ™†๐Ÿพโ€โ™€๏ธ", - "๐Ÿ™†๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ’", - "๐Ÿ’๐Ÿป", - "๐Ÿ’๐Ÿผ", - "๐Ÿ’๐Ÿฝ", - "๐Ÿ’๐Ÿพ", - "๐Ÿ’๐Ÿฟ", - "๐Ÿ’โ€โ™‚๏ธ", - "๐Ÿ’๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ’๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ’๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ’๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ’๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ’โ€โ™€๏ธ", - "๐Ÿ’๐Ÿปโ€โ™€๏ธ", - "๐Ÿ’๐Ÿผโ€โ™€๏ธ", - "๐Ÿ’๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ’๐Ÿพโ€โ™€๏ธ", - "๐Ÿ’๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ™‹", - "๐Ÿ™‹๐Ÿป", - "๐Ÿ™‹๐Ÿผ", - "๐Ÿ™‹๐Ÿฝ", - "๐Ÿ™‹๐Ÿพ", - "๐Ÿ™‹๐Ÿฟ", - "๐Ÿ™‹โ€โ™‚๏ธ", - "๐Ÿ™‹๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ™‹๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ™‹๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ™‹๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ™‹๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ™‹โ€โ™€๏ธ", - "๐Ÿ™‹๐Ÿปโ€โ™€๏ธ", - "๐Ÿ™‹๐Ÿผโ€โ™€๏ธ", - "๐Ÿ™‹๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ™‹๐Ÿพโ€โ™€๏ธ", - "๐Ÿ™‹๐Ÿฟโ€โ™€๏ธ", - "๐Ÿง", - "๐Ÿง๐Ÿป", - "๐Ÿง๐Ÿผ", - "๐Ÿง๐Ÿฝ", - "๐Ÿง๐Ÿพ", - "๐Ÿง๐Ÿฟ", - "๐Ÿงโ€โ™‚๏ธ", - "๐Ÿง๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿงโ€โ™€๏ธ", - "๐Ÿง๐Ÿปโ€โ™€๏ธ", - "๐Ÿง๐Ÿผโ€โ™€๏ธ", - "๐Ÿง๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง๐Ÿพโ€โ™€๏ธ", - "๐Ÿง๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ™‡", - "๐Ÿ™‡๐Ÿป", - "๐Ÿ™‡๐Ÿผ", - "๐Ÿ™‡๐Ÿฝ", - "๐Ÿ™‡๐Ÿพ", - "๐Ÿ™‡๐Ÿฟ", - "๐Ÿ™‡โ€โ™‚๏ธ", - "๐Ÿ™‡๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ™‡๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ™‡๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ™‡๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ™‡๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ™‡โ€โ™€๏ธ", - "๐Ÿ™‡๐Ÿปโ€โ™€๏ธ", - "๐Ÿ™‡๐Ÿผโ€โ™€๏ธ", - "๐Ÿ™‡๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ™‡๐Ÿพโ€โ™€๏ธ", - "๐Ÿ™‡๐Ÿฟโ€โ™€๏ธ", - "๐Ÿคฆ", - "๐Ÿคฆ๐Ÿป", - "๐Ÿคฆ๐Ÿผ", - "๐Ÿคฆ๐Ÿฝ", - "๐Ÿคฆ๐Ÿพ", - "๐Ÿคฆ๐Ÿฟ", - "๐Ÿคฆโ€โ™‚๏ธ", - "๐Ÿคฆ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿคฆ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿคฆ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿคฆ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿคฆ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿคฆโ€โ™€๏ธ", - "๐Ÿคฆ๐Ÿปโ€โ™€๏ธ", - "๐Ÿคฆ๐Ÿผโ€โ™€๏ธ", - "๐Ÿคฆ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿคฆ๐Ÿพโ€โ™€๏ธ", - "๐Ÿคฆ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿคท", - "๐Ÿคท๐Ÿป", - "๐Ÿคท๐Ÿผ", - "๐Ÿคท๐Ÿฝ", - "๐Ÿคท๐Ÿพ", - "๐Ÿคท๐Ÿฟ", - "๐Ÿคทโ€โ™‚๏ธ", - "๐Ÿคท๐Ÿปโ€โ™‚๏ธ", - "๐Ÿคท๐Ÿผโ€โ™‚๏ธ", - "๐Ÿคท๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿคท๐Ÿพโ€โ™‚๏ธ", - "๐Ÿคท๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿคทโ€โ™€๏ธ", - "๐Ÿคท๐Ÿปโ€โ™€๏ธ", - "๐Ÿคท๐Ÿผโ€โ™€๏ธ", - "๐Ÿคท๐Ÿฝโ€โ™€๏ธ", - "๐Ÿคท๐Ÿพโ€โ™€๏ธ", - "๐Ÿคท๐Ÿฟโ€โ™€๏ธ", - "๐Ÿง‘โ€โš•๏ธ", - "๐Ÿง‘๐Ÿปโ€โš•๏ธ", - "๐Ÿง‘๐Ÿผโ€โš•๏ธ", - "๐Ÿง‘๐Ÿฝโ€โš•๏ธ", - "๐Ÿง‘๐Ÿพโ€โš•๏ธ", - "๐Ÿง‘๐Ÿฟโ€โš•๏ธ", - "๐Ÿ‘จโ€โš•๏ธ", - "๐Ÿ‘จ๐Ÿปโ€โš•๏ธ", - "๐Ÿ‘จ๐Ÿผโ€โš•๏ธ", - "๐Ÿ‘จ๐Ÿฝโ€โš•๏ธ", - "๐Ÿ‘จ๐Ÿพโ€โš•๏ธ", - "๐Ÿ‘จ๐Ÿฟโ€โš•๏ธ", - "๐Ÿ‘ฉโ€โš•๏ธ", - "๐Ÿ‘ฉ๐Ÿปโ€โš•๏ธ", - "๐Ÿ‘ฉ๐Ÿผโ€โš•๏ธ", - "๐Ÿ‘ฉ๐Ÿฝโ€โš•๏ธ", - "๐Ÿ‘ฉ๐Ÿพโ€โš•๏ธ", - "๐Ÿ‘ฉ๐Ÿฟโ€โš•๏ธ", - "๐Ÿง‘โ€๐ŸŽ“", - "๐Ÿง‘๐Ÿปโ€๐ŸŽ“", - "๐Ÿง‘๐Ÿผโ€๐ŸŽ“", - "๐Ÿง‘๐Ÿฝโ€๐ŸŽ“", - "๐Ÿง‘๐Ÿพโ€๐ŸŽ“", - "๐Ÿง‘๐Ÿฟโ€๐ŸŽ“", - "๐Ÿ‘จโ€๐ŸŽ“", - "๐Ÿ‘จ๐Ÿปโ€๐ŸŽ“", - "๐Ÿ‘จ๐Ÿผโ€๐ŸŽ“", - "๐Ÿ‘จ๐Ÿฝโ€๐ŸŽ“", - "๐Ÿ‘จ๐Ÿพโ€๐ŸŽ“", - "๐Ÿ‘จ๐Ÿฟโ€๐ŸŽ“", - "๐Ÿ‘ฉโ€๐ŸŽ“", - "๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽ“", - "๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽ“", - "๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽ“", - "๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽ“", - "๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽ“", - "๐Ÿง‘โ€๐Ÿซ", - "๐Ÿง‘๐Ÿปโ€๐Ÿซ", - "๐Ÿง‘๐Ÿผโ€๐Ÿซ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿซ", - "๐Ÿง‘๐Ÿพโ€๐Ÿซ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿซ", - "๐Ÿ‘จโ€๐Ÿซ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿซ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿซ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿซ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿซ", - "๐Ÿ‘ฉโ€๐Ÿซ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿซ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿซ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿซ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿซ", - "๐Ÿง‘โ€โš–๏ธ", - "๐Ÿง‘๐Ÿปโ€โš–๏ธ", - "๐Ÿง‘๐Ÿผโ€โš–๏ธ", - "๐Ÿง‘๐Ÿฝโ€โš–๏ธ", - "๐Ÿง‘๐Ÿพโ€โš–๏ธ", - "๐Ÿง‘๐Ÿฟโ€โš–๏ธ", - "๐Ÿ‘จโ€โš–๏ธ", - "๐Ÿ‘จ๐Ÿปโ€โš–๏ธ", - "๐Ÿ‘จ๐Ÿผโ€โš–๏ธ", - "๐Ÿ‘จ๐Ÿฝโ€โš–๏ธ", - "๐Ÿ‘จ๐Ÿพโ€โš–๏ธ", - "๐Ÿ‘จ๐Ÿฟโ€โš–๏ธ", - "๐Ÿ‘ฉโ€โš–๏ธ", - "๐Ÿ‘ฉ๐Ÿปโ€โš–๏ธ", - "๐Ÿ‘ฉ๐Ÿผโ€โš–๏ธ", - "๐Ÿ‘ฉ๐Ÿฝโ€โš–๏ธ", - "๐Ÿ‘ฉ๐Ÿพโ€โš–๏ธ", - "๐Ÿ‘ฉ๐Ÿฟโ€โš–๏ธ", - "๐Ÿง‘โ€๐ŸŒพ", - "๐Ÿง‘๐Ÿปโ€๐ŸŒพ", - "๐Ÿง‘๐Ÿผโ€๐ŸŒพ", - "๐Ÿง‘๐Ÿฝโ€๐ŸŒพ", - "๐Ÿง‘๐Ÿพโ€๐ŸŒพ", - "๐Ÿง‘๐Ÿฟโ€๐ŸŒพ", - "๐Ÿ‘จโ€๐ŸŒพ", - "๐Ÿ‘จ๐Ÿปโ€๐ŸŒพ", - "๐Ÿ‘จ๐Ÿผโ€๐ŸŒพ", - "๐Ÿ‘จ๐Ÿฝโ€๐ŸŒพ", - "๐Ÿ‘จ๐Ÿพโ€๐ŸŒพ", - "๐Ÿ‘จ๐Ÿฟโ€๐ŸŒพ", - "๐Ÿ‘ฉโ€๐ŸŒพ", - "๐Ÿ‘ฉ๐Ÿปโ€๐ŸŒพ", - "๐Ÿ‘ฉ๐Ÿผโ€๐ŸŒพ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŒพ", - "๐Ÿ‘ฉ๐Ÿพโ€๐ŸŒพ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŒพ", - "๐Ÿง‘โ€๐Ÿณ", - "๐Ÿง‘๐Ÿปโ€๐Ÿณ", - "๐Ÿง‘๐Ÿผโ€๐Ÿณ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿณ", - "๐Ÿง‘๐Ÿพโ€๐Ÿณ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿณ", - "๐Ÿ‘จโ€๐Ÿณ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿณ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿณ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿณ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿณ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿณ", - "๐Ÿ‘ฉโ€๐Ÿณ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿณ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿณ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿณ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿณ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿณ", - "๐Ÿง‘โ€๐Ÿ”ง", - "๐Ÿง‘๐Ÿปโ€๐Ÿ”ง", - "๐Ÿง‘๐Ÿผโ€๐Ÿ”ง", - "๐Ÿง‘๐Ÿฝโ€๐Ÿ”ง", - "๐Ÿง‘๐Ÿพโ€๐Ÿ”ง", - "๐Ÿง‘๐Ÿฟโ€๐Ÿ”ง", - "๐Ÿ‘จโ€๐Ÿ”ง", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ง", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ง", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ง", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ง", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ง", - "๐Ÿ‘ฉโ€๐Ÿ”ง", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ง", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ง", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ง", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ง", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ง", - "๐Ÿง‘โ€๐Ÿญ", - "๐Ÿง‘๐Ÿปโ€๐Ÿญ", - "๐Ÿง‘๐Ÿผโ€๐Ÿญ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿญ", - "๐Ÿง‘๐Ÿพโ€๐Ÿญ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿญ", - "๐Ÿ‘จโ€๐Ÿญ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿญ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿญ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿญ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿญ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿญ", - "๐Ÿ‘ฉโ€๐Ÿญ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿญ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿญ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿญ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿญ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿญ", - "๐Ÿง‘โ€๐Ÿ’ผ", - "๐Ÿง‘๐Ÿปโ€๐Ÿ’ผ", - "๐Ÿง‘๐Ÿผโ€๐Ÿ’ผ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿ’ผ", - "๐Ÿง‘๐Ÿพโ€๐Ÿ’ผ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿ’ผ", - "๐Ÿ‘จโ€๐Ÿ’ผ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ผ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ผ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ผ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ผ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ผ", - "๐Ÿ‘ฉโ€๐Ÿ’ผ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ผ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ผ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ผ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ผ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ผ", - "๐Ÿง‘โ€๐Ÿ”ฌ", - "๐Ÿง‘๐Ÿปโ€๐Ÿ”ฌ", - "๐Ÿง‘๐Ÿผโ€๐Ÿ”ฌ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿ”ฌ", - "๐Ÿง‘๐Ÿพโ€๐Ÿ”ฌ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿ”ฌ", - "๐Ÿ‘จโ€๐Ÿ”ฌ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ฌ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ฌ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ฌ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ฌ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ฌ", - "๐Ÿ‘ฉโ€๐Ÿ”ฌ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ฌ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ฌ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ฌ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ฌ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ฌ", - "๐Ÿง‘โ€๐Ÿ’ป", - "๐Ÿง‘๐Ÿปโ€๐Ÿ’ป", - "๐Ÿง‘๐Ÿผโ€๐Ÿ’ป", - "๐Ÿง‘๐Ÿฝโ€๐Ÿ’ป", - "๐Ÿง‘๐Ÿพโ€๐Ÿ’ป", - "๐Ÿง‘๐Ÿฟโ€๐Ÿ’ป", - "๐Ÿ‘จโ€๐Ÿ’ป", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ป", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ป", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ป", - "๐Ÿ‘ฉโ€๐Ÿ’ป", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ป", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ป", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ป", - "๐Ÿง‘โ€๐ŸŽค", - "๐Ÿง‘๐Ÿปโ€๐ŸŽค", - "๐Ÿง‘๐Ÿผโ€๐ŸŽค", - "๐Ÿง‘๐Ÿฝโ€๐ŸŽค", - "๐Ÿง‘๐Ÿพโ€๐ŸŽค", - "๐Ÿง‘๐Ÿฟโ€๐ŸŽค", - "๐Ÿ‘จโ€๐ŸŽค", - "๐Ÿ‘จ๐Ÿปโ€๐ŸŽค", - "๐Ÿ‘จ๐Ÿผโ€๐ŸŽค", - "๐Ÿ‘จ๐Ÿฝโ€๐ŸŽค", - "๐Ÿ‘จ๐Ÿพโ€๐ŸŽค", - "๐Ÿ‘จ๐Ÿฟโ€๐ŸŽค", - "๐Ÿ‘ฉโ€๐ŸŽค", - "๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽค", - "๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽค", - "๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽค", - "๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽค", - "๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽค", - "๐Ÿง‘โ€๐ŸŽจ", - "๐Ÿง‘๐Ÿปโ€๐ŸŽจ", - "๐Ÿง‘๐Ÿผโ€๐ŸŽจ", - "๐Ÿง‘๐Ÿฝโ€๐ŸŽจ", - "๐Ÿง‘๐Ÿพโ€๐ŸŽจ", - "๐Ÿง‘๐Ÿฟโ€๐ŸŽจ", - "๐Ÿ‘จโ€๐ŸŽจ", - "๐Ÿ‘จ๐Ÿปโ€๐ŸŽจ", - "๐Ÿ‘จ๐Ÿผโ€๐ŸŽจ", - "๐Ÿ‘จ๐Ÿฝโ€๐ŸŽจ", - "๐Ÿ‘จ๐Ÿพโ€๐ŸŽจ", - "๐Ÿ‘จ๐Ÿฟโ€๐ŸŽจ", - "๐Ÿ‘ฉโ€๐ŸŽจ", - "๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽจ", - "๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽจ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽจ", - "๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽจ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽจ", - "๐Ÿง‘โ€โœˆ๏ธ", - "๐Ÿง‘๐Ÿปโ€โœˆ๏ธ", - "๐Ÿง‘๐Ÿผโ€โœˆ๏ธ", - "๐Ÿง‘๐Ÿฝโ€โœˆ๏ธ", - "๐Ÿง‘๐Ÿพโ€โœˆ๏ธ", - "๐Ÿง‘๐Ÿฟโ€โœˆ๏ธ", - "๐Ÿ‘จโ€โœˆ๏ธ", - "๐Ÿ‘จ๐Ÿปโ€โœˆ๏ธ", - "๐Ÿ‘จ๐Ÿผโ€โœˆ๏ธ", - "๐Ÿ‘จ๐Ÿฝโ€โœˆ๏ธ", - "๐Ÿ‘จ๐Ÿพโ€โœˆ๏ธ", - "๐Ÿ‘จ๐Ÿฟโ€โœˆ๏ธ", - "๐Ÿ‘ฉโ€โœˆ๏ธ", - "๐Ÿ‘ฉ๐Ÿปโ€โœˆ๏ธ", - "๐Ÿ‘ฉ๐Ÿผโ€โœˆ๏ธ", - "๐Ÿ‘ฉ๐Ÿฝโ€โœˆ๏ธ", - "๐Ÿ‘ฉ๐Ÿพโ€โœˆ๏ธ", - "๐Ÿ‘ฉ๐Ÿฟโ€โœˆ๏ธ", - "๐Ÿง‘โ€๐Ÿš€", - "๐Ÿง‘๐Ÿปโ€๐Ÿš€", - "๐Ÿง‘๐Ÿผโ€๐Ÿš€", - "๐Ÿง‘๐Ÿฝโ€๐Ÿš€", - "๐Ÿง‘๐Ÿพโ€๐Ÿš€", - "๐Ÿง‘๐Ÿฟโ€๐Ÿš€", - "๐Ÿ‘จโ€๐Ÿš€", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿš€", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿš€", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿš€", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿš€", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿš€", - "๐Ÿ‘ฉโ€๐Ÿš€", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš€", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš€", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš€", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš€", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš€", - "๐Ÿง‘โ€๐Ÿš’", - "๐Ÿง‘๐Ÿปโ€๐Ÿš’", - "๐Ÿง‘๐Ÿผโ€๐Ÿš’", - "๐Ÿง‘๐Ÿฝโ€๐Ÿš’", - "๐Ÿง‘๐Ÿพโ€๐Ÿš’", - "๐Ÿง‘๐Ÿฟโ€๐Ÿš’", - "๐Ÿ‘จโ€๐Ÿš’", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿš’", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿš’", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿš’", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿš’", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿš’", - "๐Ÿ‘ฉโ€๐Ÿš’", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš’", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš’", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš’", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš’", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš’", - "๐Ÿ‘ฎ", - "๐Ÿ‘ฎ๐Ÿป", - "๐Ÿ‘ฎ๐Ÿผ", - "๐Ÿ‘ฎ๐Ÿฝ", - "๐Ÿ‘ฎ๐Ÿพ", - "๐Ÿ‘ฎ๐Ÿฟ", - "๐Ÿ‘ฎโ€โ™‚๏ธ", - "๐Ÿ‘ฎ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ‘ฎ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ‘ฎ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ‘ฎ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ‘ฎ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ‘ฎโ€โ™€๏ธ", - "๐Ÿ‘ฎ๐Ÿปโ€โ™€๏ธ", - "๐Ÿ‘ฎ๐Ÿผโ€โ™€๏ธ", - "๐Ÿ‘ฎ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ‘ฎ๐Ÿพโ€โ™€๏ธ", - "๐Ÿ‘ฎ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ•ต๏ธ", - "๐Ÿ•ต๐Ÿป", - "๐Ÿ•ต๐Ÿผ", - "๐Ÿ•ต๐Ÿฝ", - "๐Ÿ•ต๐Ÿพ", - "๐Ÿ•ต๐Ÿฟ", - "๐Ÿ•ต๏ธโ€โ™‚๏ธ", - "๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ•ต๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ•ต๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ•ต๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ•ต๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ•ต๏ธโ€โ™€๏ธ", - "๐Ÿ•ต๐Ÿปโ€โ™€๏ธ", - "๐Ÿ•ต๐Ÿผโ€โ™€๏ธ", - "๐Ÿ•ต๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ•ต๐Ÿพโ€โ™€๏ธ", - "๐Ÿ•ต๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ’‚", - "๐Ÿ’‚๐Ÿป", - "๐Ÿ’‚๐Ÿผ", - "๐Ÿ’‚๐Ÿฝ", - "๐Ÿ’‚๐Ÿพ", - "๐Ÿ’‚๐Ÿฟ", - "๐Ÿ’‚โ€โ™‚๏ธ", - "๐Ÿ’‚๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ’‚๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ’‚๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ’‚๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ’‚๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ’‚โ€โ™€๏ธ", - "๐Ÿ’‚๐Ÿปโ€โ™€๏ธ", - "๐Ÿ’‚๐Ÿผโ€โ™€๏ธ", - "๐Ÿ’‚๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ’‚๐Ÿพโ€โ™€๏ธ", - "๐Ÿ’‚๐Ÿฟโ€โ™€๏ธ", - "๐Ÿฅท", - "๐Ÿฅท๐Ÿป", - "๐Ÿฅท๐Ÿผ", - "๐Ÿฅท๐Ÿฝ", - "๐Ÿฅท๐Ÿพ", - "๐Ÿฅท๐Ÿฟ", - "๐Ÿ‘ท", - "๐Ÿ‘ท๐Ÿป", - "๐Ÿ‘ท๐Ÿผ", - "๐Ÿ‘ท๐Ÿฝ", - "๐Ÿ‘ท๐Ÿพ", - "๐Ÿ‘ท๐Ÿฟ", - "๐Ÿ‘ทโ€โ™‚๏ธ", - "๐Ÿ‘ท๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ‘ท๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ‘ท๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ‘ท๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ‘ท๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ‘ทโ€โ™€๏ธ", - "๐Ÿ‘ท๐Ÿปโ€โ™€๏ธ", - "๐Ÿ‘ท๐Ÿผโ€โ™€๏ธ", - "๐Ÿ‘ท๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ‘ท๐Ÿพโ€โ™€๏ธ", - "๐Ÿ‘ท๐Ÿฟโ€โ™€๏ธ", - "๐Ÿซ…", - "๐Ÿซ…๐Ÿป", - "๐Ÿซ…๐Ÿผ", - "๐Ÿซ…๐Ÿฝ", - "๐Ÿซ…๐Ÿพ", - "๐Ÿซ…๐Ÿฟ", - "๐Ÿคด", - "๐Ÿคด๐Ÿป", - "๐Ÿคด๐Ÿผ", - "๐Ÿคด๐Ÿฝ", - "๐Ÿคด๐Ÿพ", - "๐Ÿคด๐Ÿฟ", - "๐Ÿ‘ธ", - "๐Ÿ‘ธ๐Ÿป", - "๐Ÿ‘ธ๐Ÿผ", - "๐Ÿ‘ธ๐Ÿฝ", - "๐Ÿ‘ธ๐Ÿพ", - "๐Ÿ‘ธ๐Ÿฟ", - "๐Ÿ‘ณ", - "๐Ÿ‘ณ๐Ÿป", - "๐Ÿ‘ณ๐Ÿผ", - "๐Ÿ‘ณ๐Ÿฝ", - "๐Ÿ‘ณ๐Ÿพ", - "๐Ÿ‘ณ๐Ÿฟ", - "๐Ÿ‘ณโ€โ™‚๏ธ", - "๐Ÿ‘ณ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ‘ณ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ‘ณ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ‘ณ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ‘ณ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ‘ณโ€โ™€๏ธ", - "๐Ÿ‘ณ๐Ÿปโ€โ™€๏ธ", - "๐Ÿ‘ณ๐Ÿผโ€โ™€๏ธ", - "๐Ÿ‘ณ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ‘ณ๐Ÿพโ€โ™€๏ธ", - "๐Ÿ‘ณ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ‘ฒ", - "๐Ÿ‘ฒ๐Ÿป", - "๐Ÿ‘ฒ๐Ÿผ", - "๐Ÿ‘ฒ๐Ÿฝ", - "๐Ÿ‘ฒ๐Ÿพ", - "๐Ÿ‘ฒ๐Ÿฟ", - "๐Ÿง•", - "๐Ÿง•๐Ÿป", - "๐Ÿง•๐Ÿผ", - "๐Ÿง•๐Ÿฝ", - "๐Ÿง•๐Ÿพ", - "๐Ÿง•๐Ÿฟ", - "๐Ÿคต", - "๐Ÿคต๐Ÿป", - "๐Ÿคต๐Ÿผ", - "๐Ÿคต๐Ÿฝ", - "๐Ÿคต๐Ÿพ", - "๐Ÿคต๐Ÿฟ", - "๐Ÿคตโ€โ™‚๏ธ", - "๐Ÿคต๐Ÿปโ€โ™‚๏ธ", - "๐Ÿคต๐Ÿผโ€โ™‚๏ธ", - "๐Ÿคต๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿคต๐Ÿพโ€โ™‚๏ธ", - "๐Ÿคต๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿคตโ€โ™€๏ธ", - "๐Ÿคต๐Ÿปโ€โ™€๏ธ", - "๐Ÿคต๐Ÿผโ€โ™€๏ธ", - "๐Ÿคต๐Ÿฝโ€โ™€๏ธ", - "๐Ÿคต๐Ÿพโ€โ™€๏ธ", - "๐Ÿคต๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ‘ฐ", - "๐Ÿ‘ฐ๐Ÿป", - "๐Ÿ‘ฐ๐Ÿผ", - "๐Ÿ‘ฐ๐Ÿฝ", - "๐Ÿ‘ฐ๐Ÿพ", - "๐Ÿ‘ฐ๐Ÿฟ", - "๐Ÿ‘ฐโ€โ™‚๏ธ", - "๐Ÿ‘ฐ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ‘ฐ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ‘ฐ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ‘ฐ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ‘ฐ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ‘ฐโ€โ™€๏ธ", - "๐Ÿ‘ฐ๐Ÿปโ€โ™€๏ธ", - "๐Ÿ‘ฐ๐Ÿผโ€โ™€๏ธ", - "๐Ÿ‘ฐ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ‘ฐ๐Ÿพโ€โ™€๏ธ", - "๐Ÿ‘ฐ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿคฐ", - "๐Ÿคฐ๐Ÿป", - "๐Ÿคฐ๐Ÿผ", - "๐Ÿคฐ๐Ÿฝ", - "๐Ÿคฐ๐Ÿพ", - "๐Ÿคฐ๐Ÿฟ", - "๐Ÿซƒ", - "๐Ÿซƒ๐Ÿป", - "๐Ÿซƒ๐Ÿผ", - "๐Ÿซƒ๐Ÿฝ", - "๐Ÿซƒ๐Ÿพ", - "๐Ÿซƒ๐Ÿฟ", - "๐Ÿซ„", - "๐Ÿซ„๐Ÿป", - "๐Ÿซ„๐Ÿผ", - "๐Ÿซ„๐Ÿฝ", - "๐Ÿซ„๐Ÿพ", - "๐Ÿซ„๐Ÿฟ", - "๐Ÿคฑ", - "๐Ÿคฑ๐Ÿป", - "๐Ÿคฑ๐Ÿผ", - "๐Ÿคฑ๐Ÿฝ", - "๐Ÿคฑ๐Ÿพ", - "๐Ÿคฑ๐Ÿฟ", - "๐Ÿ‘ฉโ€๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿผ", - "๐Ÿ‘จโ€๐Ÿผ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿผ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿผ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿผ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿผ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿผ", - "๐Ÿง‘โ€๐Ÿผ", - "๐Ÿง‘๐Ÿปโ€๐Ÿผ", - "๐Ÿง‘๐Ÿผโ€๐Ÿผ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿผ", - "๐Ÿง‘๐Ÿพโ€๐Ÿผ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿผ", - "๐Ÿ‘ผ", - "๐Ÿ‘ผ๐Ÿป", - "๐Ÿ‘ผ๐Ÿผ", - "๐Ÿ‘ผ๐Ÿฝ", - "๐Ÿ‘ผ๐Ÿพ", - "๐Ÿ‘ผ๐Ÿฟ", - "๐ŸŽ…", - "๐ŸŽ…๐Ÿป", - "๐ŸŽ…๐Ÿผ", - "๐ŸŽ…๐Ÿฝ", - "๐ŸŽ…๐Ÿพ", - "๐ŸŽ…๐Ÿฟ", - "๐Ÿคถ", - "๐Ÿคถ๐Ÿป", - "๐Ÿคถ๐Ÿผ", - "๐Ÿคถ๐Ÿฝ", - "๐Ÿคถ๐Ÿพ", - "๐Ÿคถ๐Ÿฟ", - "๐Ÿง‘โ€๐ŸŽ„", - "๐Ÿง‘๐Ÿปโ€๐ŸŽ„", - "๐Ÿง‘๐Ÿผโ€๐ŸŽ„", - "๐Ÿง‘๐Ÿฝโ€๐ŸŽ„", - "๐Ÿง‘๐Ÿพโ€๐ŸŽ„", - "๐Ÿง‘๐Ÿฟโ€๐ŸŽ„", - "๐Ÿฆธ", - "๐Ÿฆธ๐Ÿป", - "๐Ÿฆธ๐Ÿผ", - "๐Ÿฆธ๐Ÿฝ", - "๐Ÿฆธ๐Ÿพ", - "๐Ÿฆธ๐Ÿฟ", - "๐Ÿฆธโ€โ™‚๏ธ", - "๐Ÿฆธ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿฆธ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿฆธ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿฆธ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿฆธ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿฆธโ€โ™€๏ธ", - "๐Ÿฆธ๐Ÿปโ€โ™€๏ธ", - "๐Ÿฆธ๐Ÿผโ€โ™€๏ธ", - "๐Ÿฆธ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿฆธ๐Ÿพโ€โ™€๏ธ", - "๐Ÿฆธ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿฆน", - "๐Ÿฆน๐Ÿป", - "๐Ÿฆน๐Ÿผ", - "๐Ÿฆน๐Ÿฝ", - "๐Ÿฆน๐Ÿพ", - "๐Ÿฆน๐Ÿฟ", - "๐Ÿฆนโ€โ™‚๏ธ", - "๐Ÿฆน๐Ÿปโ€โ™‚๏ธ", - "๐Ÿฆน๐Ÿผโ€โ™‚๏ธ", - "๐Ÿฆน๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿฆน๐Ÿพโ€โ™‚๏ธ", - "๐Ÿฆน๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿฆนโ€โ™€๏ธ", - "๐Ÿฆน๐Ÿปโ€โ™€๏ธ", - "๐Ÿฆน๐Ÿผโ€โ™€๏ธ", - "๐Ÿฆน๐Ÿฝโ€โ™€๏ธ", - "๐Ÿฆน๐Ÿพโ€โ™€๏ธ", - "๐Ÿฆน๐Ÿฟโ€โ™€๏ธ", - "๐Ÿง™", - "๐Ÿง™๐Ÿป", - "๐Ÿง™๐Ÿผ", - "๐Ÿง™๐Ÿฝ", - "๐Ÿง™๐Ÿพ", - "๐Ÿง™๐Ÿฟ", - "๐Ÿง™โ€โ™‚๏ธ", - "๐Ÿง™๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง™๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง™๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง™๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง™๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿง™โ€โ™€๏ธ", - "๐Ÿง™๐Ÿปโ€โ™€๏ธ", - "๐Ÿง™๐Ÿผโ€โ™€๏ธ", - "๐Ÿง™๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง™๐Ÿพโ€โ™€๏ธ", - "๐Ÿง™๐Ÿฟโ€โ™€๏ธ", - "๐Ÿงš", - "๐Ÿงš๐Ÿป", - "๐Ÿงš๐Ÿผ", - "๐Ÿงš๐Ÿฝ", - "๐Ÿงš๐Ÿพ", - "๐Ÿงš๐Ÿฟ", - "๐Ÿงšโ€โ™‚๏ธ", - "๐Ÿงš๐Ÿปโ€โ™‚๏ธ", - "๐Ÿงš๐Ÿผโ€โ™‚๏ธ", - "๐Ÿงš๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿงš๐Ÿพโ€โ™‚๏ธ", - "๐Ÿงš๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿงšโ€โ™€๏ธ", - "๐Ÿงš๐Ÿปโ€โ™€๏ธ", - "๐Ÿงš๐Ÿผโ€โ™€๏ธ", - "๐Ÿงš๐Ÿฝโ€โ™€๏ธ", - "๐Ÿงš๐Ÿพโ€โ™€๏ธ", - "๐Ÿงš๐Ÿฟโ€โ™€๏ธ", - "๐Ÿง›", - "๐Ÿง›๐Ÿป", - "๐Ÿง›๐Ÿผ", - "๐Ÿง›๐Ÿฝ", - "๐Ÿง›๐Ÿพ", - "๐Ÿง›๐Ÿฟ", - "๐Ÿง›โ€โ™‚๏ธ", - "๐Ÿง›๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง›๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง›๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง›๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง›๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿง›โ€โ™€๏ธ", - "๐Ÿง›๐Ÿปโ€โ™€๏ธ", - "๐Ÿง›๐Ÿผโ€โ™€๏ธ", - "๐Ÿง›๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง›๐Ÿพโ€โ™€๏ธ", - "๐Ÿง›๐Ÿฟโ€โ™€๏ธ", - "๐Ÿงœ", - "๐Ÿงœ๐Ÿป", - "๐Ÿงœ๐Ÿผ", - "๐Ÿงœ๐Ÿฝ", - "๐Ÿงœ๐Ÿพ", - "๐Ÿงœ๐Ÿฟ", - "๐Ÿงœโ€โ™‚๏ธ", - "๐Ÿงœ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿงœ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿงœ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿงœ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿงœ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿงœโ€โ™€๏ธ", - "๐Ÿงœ๐Ÿปโ€โ™€๏ธ", - "๐Ÿงœ๐Ÿผโ€โ™€๏ธ", - "๐Ÿงœ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿงœ๐Ÿพโ€โ™€๏ธ", - "๐Ÿงœ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿง", - "๐Ÿง๐Ÿป", - "๐Ÿง๐Ÿผ", - "๐Ÿง๐Ÿฝ", - "๐Ÿง๐Ÿพ", - "๐Ÿง๐Ÿฟ", - "๐Ÿงโ€โ™‚๏ธ", - "๐Ÿง๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿงโ€โ™€๏ธ", - "๐Ÿง๐Ÿปโ€โ™€๏ธ", - "๐Ÿง๐Ÿผโ€โ™€๏ธ", - "๐Ÿง๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง๐Ÿพโ€โ™€๏ธ", - "๐Ÿง๐Ÿฟโ€โ™€๏ธ", - "๐Ÿงž", - "๐Ÿงžโ€โ™‚๏ธ", - "๐Ÿงžโ€โ™€๏ธ", - "๐ŸงŸ", - "๐ŸงŸโ€โ™‚๏ธ", - "๐ŸงŸโ€โ™€๏ธ", - "๐ŸงŒ", - "๐Ÿ’†", - "๐Ÿ’†๐Ÿป", - "๐Ÿ’†๐Ÿผ", - "๐Ÿ’†๐Ÿฝ", - "๐Ÿ’†๐Ÿพ", - "๐Ÿ’†๐Ÿฟ", - "๐Ÿ’†โ€โ™‚๏ธ", - "๐Ÿ’†๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ’†๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ’†๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ’†๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ’†๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ’†โ€โ™€๏ธ", - "๐Ÿ’†๐Ÿปโ€โ™€๏ธ", - "๐Ÿ’†๐Ÿผโ€โ™€๏ธ", - "๐Ÿ’†๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ’†๐Ÿพโ€โ™€๏ธ", - "๐Ÿ’†๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ’‡", - "๐Ÿ’‡๐Ÿป", - "๐Ÿ’‡๐Ÿผ", - "๐Ÿ’‡๐Ÿฝ", - "๐Ÿ’‡๐Ÿพ", - "๐Ÿ’‡๐Ÿฟ", - "๐Ÿ’‡โ€โ™‚๏ธ", - "๐Ÿ’‡๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ’‡๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ’‡๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ’‡๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ’‡๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ’‡โ€โ™€๏ธ", - "๐Ÿ’‡๐Ÿปโ€โ™€๏ธ", - "๐Ÿ’‡๐Ÿผโ€โ™€๏ธ", - "๐Ÿ’‡๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ’‡๐Ÿพโ€โ™€๏ธ", - "๐Ÿ’‡๐Ÿฟโ€โ™€๏ธ", - "๐Ÿšถ", - "๐Ÿšถ๐Ÿป", - "๐Ÿšถ๐Ÿผ", - "๐Ÿšถ๐Ÿฝ", - "๐Ÿšถ๐Ÿพ", - "๐Ÿšถ๐Ÿฟ", - "๐Ÿšถโ€โ™‚๏ธ", - "๐Ÿšถ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿšถ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿšถ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿšถ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿšถ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿšถโ€โ™€๏ธ", - "๐Ÿšถ๐Ÿปโ€โ™€๏ธ", - "๐Ÿšถ๐Ÿผโ€โ™€๏ธ", - "๐Ÿšถ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿšถ๐Ÿพโ€โ™€๏ธ", - "๐Ÿšถ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿšถโ€โžก๏ธ", - "๐Ÿšถ๐Ÿปโ€โžก๏ธ", - "๐Ÿšถ๐Ÿผโ€โžก๏ธ", - "๐Ÿšถ๐Ÿฝโ€โžก๏ธ", - "๐Ÿšถ๐Ÿพโ€โžก๏ธ", - "๐Ÿšถ๐Ÿฟโ€โžก๏ธ", - "๐Ÿšถโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿšถโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿšถ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿง", - "๐Ÿง๐Ÿป", - "๐Ÿง๐Ÿผ", - "๐Ÿง๐Ÿฝ", - "๐Ÿง๐Ÿพ", - "๐Ÿง๐Ÿฟ", - "๐Ÿงโ€โ™‚๏ธ", - "๐Ÿง๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿงโ€โ™€๏ธ", - "๐Ÿง๐Ÿปโ€โ™€๏ธ", - "๐Ÿง๐Ÿผโ€โ™€๏ธ", - "๐Ÿง๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง๐Ÿพโ€โ™€๏ธ", - "๐Ÿง๐Ÿฟโ€โ™€๏ธ", - "๐ŸงŽ", - "๐ŸงŽ๐Ÿป", - "๐ŸงŽ๐Ÿผ", - "๐ŸงŽ๐Ÿฝ", - "๐ŸงŽ๐Ÿพ", - "๐ŸงŽ๐Ÿฟ", - "๐ŸงŽโ€โ™‚๏ธ", - "๐ŸงŽ๐Ÿปโ€โ™‚๏ธ", - "๐ŸงŽ๐Ÿผโ€โ™‚๏ธ", - "๐ŸงŽ๐Ÿฝโ€โ™‚๏ธ", - "๐ŸงŽ๐Ÿพโ€โ™‚๏ธ", - "๐ŸงŽ๐Ÿฟโ€โ™‚๏ธ", - "๐ŸงŽโ€โ™€๏ธ", - "๐ŸงŽ๐Ÿปโ€โ™€๏ธ", - "๐ŸงŽ๐Ÿผโ€โ™€๏ธ", - "๐ŸงŽ๐Ÿฝโ€โ™€๏ธ", - "๐ŸงŽ๐Ÿพโ€โ™€๏ธ", - "๐ŸงŽ๐Ÿฟโ€โ™€๏ธ", - "๐ŸงŽโ€โžก๏ธ", - "๐ŸงŽ๐Ÿปโ€โžก๏ธ", - "๐ŸงŽ๐Ÿผโ€โžก๏ธ", - "๐ŸงŽ๐Ÿฝโ€โžก๏ธ", - "๐ŸงŽ๐Ÿพโ€โžก๏ธ", - "๐ŸงŽ๐Ÿฟโ€โžก๏ธ", - "๐ŸงŽโ€โ™€๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ", - "๐ŸงŽโ€โ™‚๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ", - "๐ŸงŽ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿง‘โ€๐Ÿฆฏ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆฏ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆฏ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆฏ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏ", - "๐Ÿง‘โ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘จโ€๐Ÿฆฏ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏ", - "๐Ÿ‘จโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘ฉโ€๐Ÿฆฏ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏ", - "๐Ÿ‘ฉโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ", - "๐Ÿง‘โ€๐Ÿฆผ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆผ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆผ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆผ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆผ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆผ", - "๐Ÿง‘โ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘จโ€๐Ÿฆผ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผ", - "๐Ÿ‘จโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘ฉโ€๐Ÿฆผ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผ", - "๐Ÿ‘ฉโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ", - "๐Ÿง‘โ€๐Ÿฆฝ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆฝ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆฝ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆฝ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝ", - "๐Ÿง‘โ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿง‘๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿง‘๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿง‘๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘จโ€๐Ÿฆฝ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝ", - "๐Ÿ‘จโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘ฉโ€๐Ÿฆฝ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝ", - "๐Ÿ‘ฉโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ", - "๐Ÿƒ", - "๐Ÿƒ๐Ÿป", - "๐Ÿƒ๐Ÿผ", - "๐Ÿƒ๐Ÿฝ", - "๐Ÿƒ๐Ÿพ", - "๐Ÿƒ๐Ÿฟ", - "๐Ÿƒโ€โ™‚๏ธ", - "๐Ÿƒ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿƒ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿƒ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿƒ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿƒ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿƒโ€โ™€๏ธ", - "๐Ÿƒ๐Ÿปโ€โ™€๏ธ", - "๐Ÿƒ๐Ÿผโ€โ™€๏ธ", - "๐Ÿƒ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿƒ๐Ÿพโ€โ™€๏ธ", - "๐Ÿƒ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿƒโ€โžก๏ธ", - "๐Ÿƒ๐Ÿปโ€โžก๏ธ", - "๐Ÿƒ๐Ÿผโ€โžก๏ธ", - "๐Ÿƒ๐Ÿฝโ€โžก๏ธ", - "๐Ÿƒ๐Ÿพโ€โžก๏ธ", - "๐Ÿƒ๐Ÿฟโ€โžก๏ธ", - "๐Ÿƒโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ", - "๐Ÿƒโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿƒ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ", - "๐Ÿ’ƒ", - "๐Ÿ’ƒ๐Ÿป", - "๐Ÿ’ƒ๐Ÿผ", - "๐Ÿ’ƒ๐Ÿฝ", - "๐Ÿ’ƒ๐Ÿพ", - "๐Ÿ’ƒ๐Ÿฟ", - "๐Ÿ•บ", - "๐Ÿ•บ๐Ÿป", - "๐Ÿ•บ๐Ÿผ", - "๐Ÿ•บ๐Ÿฝ", - "๐Ÿ•บ๐Ÿพ", - "๐Ÿ•บ๐Ÿฟ", - "๐Ÿ•ด๏ธ", - "๐Ÿ•ด๐Ÿป", - "๐Ÿ•ด๐Ÿผ", - "๐Ÿ•ด๐Ÿฝ", - "๐Ÿ•ด๐Ÿพ", - "๐Ÿ•ด๐Ÿฟ", - "๐Ÿ‘ฏ", - "๐Ÿ‘ฏโ€โ™‚๏ธ", - "๐Ÿ‘ฏโ€โ™€๏ธ", - "๐Ÿง–", - "๐Ÿง–๐Ÿป", - "๐Ÿง–๐Ÿผ", - "๐Ÿง–๐Ÿฝ", - "๐Ÿง–๐Ÿพ", - "๐Ÿง–๐Ÿฟ", - "๐Ÿง–โ€โ™‚๏ธ", - "๐Ÿง–๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง–๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง–๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง–๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง–๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿง–โ€โ™€๏ธ", - "๐Ÿง–๐Ÿปโ€โ™€๏ธ", - "๐Ÿง–๐Ÿผโ€โ™€๏ธ", - "๐Ÿง–๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง–๐Ÿพโ€โ™€๏ธ", - "๐Ÿง–๐Ÿฟโ€โ™€๏ธ", - "๐Ÿง—", - "๐Ÿง—๐Ÿป", - "๐Ÿง—๐Ÿผ", - "๐Ÿง—๐Ÿฝ", - "๐Ÿง—๐Ÿพ", - "๐Ÿง—๐Ÿฟ", - "๐Ÿง—โ€โ™‚๏ธ", - "๐Ÿง—๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง—๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง—๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง—๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง—๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿง—โ€โ™€๏ธ", - "๐Ÿง—๐Ÿปโ€โ™€๏ธ", - "๐Ÿง—๐Ÿผโ€โ™€๏ธ", - "๐Ÿง—๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง—๐Ÿพโ€โ™€๏ธ", - "๐Ÿง—๐Ÿฟโ€โ™€๏ธ", - "๐Ÿคบ", - "๐Ÿ‡", - "๐Ÿ‡๐Ÿป", - "๐Ÿ‡๐Ÿผ", - "๐Ÿ‡๐Ÿฝ", - "๐Ÿ‡๐Ÿพ", - "๐Ÿ‡๐Ÿฟ", - "โ›ท๏ธ", - "๐Ÿ‚", - "๐Ÿ‚๐Ÿป", - "๐Ÿ‚๐Ÿผ", - "๐Ÿ‚๐Ÿฝ", - "๐Ÿ‚๐Ÿพ", - "๐Ÿ‚๐Ÿฟ", - "๐ŸŒ๏ธ", - "๐ŸŒ๐Ÿป", - "๐ŸŒ๐Ÿผ", - "๐ŸŒ๐Ÿฝ", - "๐ŸŒ๐Ÿพ", - "๐ŸŒ๐Ÿฟ", - "๐ŸŒ๏ธโ€โ™‚๏ธ", - "๐ŸŒ๐Ÿปโ€โ™‚๏ธ", - "๐ŸŒ๐Ÿผโ€โ™‚๏ธ", - "๐ŸŒ๐Ÿฝโ€โ™‚๏ธ", - "๐ŸŒ๐Ÿพโ€โ™‚๏ธ", - "๐ŸŒ๐Ÿฟโ€โ™‚๏ธ", - "๐ŸŒ๏ธโ€โ™€๏ธ", - "๐ŸŒ๐Ÿปโ€โ™€๏ธ", - "๐ŸŒ๐Ÿผโ€โ™€๏ธ", - "๐ŸŒ๐Ÿฝโ€โ™€๏ธ", - "๐ŸŒ๐Ÿพโ€โ™€๏ธ", - "๐ŸŒ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ„", - "๐Ÿ„๐Ÿป", - "๐Ÿ„๐Ÿผ", - "๐Ÿ„๐Ÿฝ", - "๐Ÿ„๐Ÿพ", - "๐Ÿ„๐Ÿฟ", - "๐Ÿ„โ€โ™‚๏ธ", - "๐Ÿ„๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ„๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ„๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ„๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ„๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ„โ€โ™€๏ธ", - "๐Ÿ„๐Ÿปโ€โ™€๏ธ", - "๐Ÿ„๐Ÿผโ€โ™€๏ธ", - "๐Ÿ„๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ„๐Ÿพโ€โ™€๏ธ", - "๐Ÿ„๐Ÿฟโ€โ™€๏ธ", - "๐Ÿšฃ", - "๐Ÿšฃ๐Ÿป", - "๐Ÿšฃ๐Ÿผ", - "๐Ÿšฃ๐Ÿฝ", - "๐Ÿšฃ๐Ÿพ", - "๐Ÿšฃ๐Ÿฟ", - "๐Ÿšฃโ€โ™‚๏ธ", - "๐Ÿšฃ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿšฃ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿšฃ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿšฃ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿšฃ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿšฃโ€โ™€๏ธ", - "๐Ÿšฃ๐Ÿปโ€โ™€๏ธ", - "๐Ÿšฃ๐Ÿผโ€โ™€๏ธ", - "๐Ÿšฃ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿšฃ๐Ÿพโ€โ™€๏ธ", - "๐Ÿšฃ๐Ÿฟโ€โ™€๏ธ", - "๐ŸŠ", - "๐ŸŠ๐Ÿป", - "๐ŸŠ๐Ÿผ", - "๐ŸŠ๐Ÿฝ", - "๐ŸŠ๐Ÿพ", - "๐ŸŠ๐Ÿฟ", - "๐ŸŠโ€โ™‚๏ธ", - "๐ŸŠ๐Ÿปโ€โ™‚๏ธ", - "๐ŸŠ๐Ÿผโ€โ™‚๏ธ", - "๐ŸŠ๐Ÿฝโ€โ™‚๏ธ", - "๐ŸŠ๐Ÿพโ€โ™‚๏ธ", - "๐ŸŠ๐Ÿฟโ€โ™‚๏ธ", - "๐ŸŠโ€โ™€๏ธ", - "๐ŸŠ๐Ÿปโ€โ™€๏ธ", - "๐ŸŠ๐Ÿผโ€โ™€๏ธ", - "๐ŸŠ๐Ÿฝโ€โ™€๏ธ", - "๐ŸŠ๐Ÿพโ€โ™€๏ธ", - "๐ŸŠ๐Ÿฟโ€โ™€๏ธ", - "โ›น๏ธ", - "โ›น๐Ÿป", - "โ›น๐Ÿผ", - "โ›น๐Ÿฝ", - "โ›น๐Ÿพ", - "โ›น๐Ÿฟ", - "โ›น๏ธโ€โ™‚๏ธ", - "โ›น๐Ÿปโ€โ™‚๏ธ", - "โ›น๐Ÿผโ€โ™‚๏ธ", - "โ›น๐Ÿฝโ€โ™‚๏ธ", - "โ›น๐Ÿพโ€โ™‚๏ธ", - "โ›น๐Ÿฟโ€โ™‚๏ธ", - "โ›น๏ธโ€โ™€๏ธ", - "โ›น๐Ÿปโ€โ™€๏ธ", - "โ›น๐Ÿผโ€โ™€๏ธ", - "โ›น๐Ÿฝโ€โ™€๏ธ", - "โ›น๐Ÿพโ€โ™€๏ธ", - "โ›น๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ‹๏ธ", - "๐Ÿ‹๐Ÿป", - "๐Ÿ‹๐Ÿผ", - "๐Ÿ‹๐Ÿฝ", - "๐Ÿ‹๐Ÿพ", - "๐Ÿ‹๐Ÿฟ", - "๐Ÿ‹๏ธโ€โ™‚๏ธ", - "๐Ÿ‹๐Ÿปโ€โ™‚๏ธ", - "๐Ÿ‹๐Ÿผโ€โ™‚๏ธ", - "๐Ÿ‹๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿ‹๐Ÿพโ€โ™‚๏ธ", - "๐Ÿ‹๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿ‹๏ธโ€โ™€๏ธ", - "๐Ÿ‹๐Ÿปโ€โ™€๏ธ", - "๐Ÿ‹๐Ÿผโ€โ™€๏ธ", - "๐Ÿ‹๐Ÿฝโ€โ™€๏ธ", - "๐Ÿ‹๐Ÿพโ€โ™€๏ธ", - "๐Ÿ‹๐Ÿฟโ€โ™€๏ธ", - "๐Ÿšด", - "๐Ÿšด๐Ÿป", - "๐Ÿšด๐Ÿผ", - "๐Ÿšด๐Ÿฝ", - "๐Ÿšด๐Ÿพ", - "๐Ÿšด๐Ÿฟ", - "๐Ÿšดโ€โ™‚๏ธ", - "๐Ÿšด๐Ÿปโ€โ™‚๏ธ", - "๐Ÿšด๐Ÿผโ€โ™‚๏ธ", - "๐Ÿšด๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿšด๐Ÿพโ€โ™‚๏ธ", - "๐Ÿšด๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿšดโ€โ™€๏ธ", - "๐Ÿšด๐Ÿปโ€โ™€๏ธ", - "๐Ÿšด๐Ÿผโ€โ™€๏ธ", - "๐Ÿšด๐Ÿฝโ€โ™€๏ธ", - "๐Ÿšด๐Ÿพโ€โ™€๏ธ", - "๐Ÿšด๐Ÿฟโ€โ™€๏ธ", - "๐Ÿšต", - "๐Ÿšต๐Ÿป", - "๐Ÿšต๐Ÿผ", - "๐Ÿšต๐Ÿฝ", - "๐Ÿšต๐Ÿพ", - "๐Ÿšต๐Ÿฟ", - "๐Ÿšตโ€โ™‚๏ธ", - "๐Ÿšต๐Ÿปโ€โ™‚๏ธ", - "๐Ÿšต๐Ÿผโ€โ™‚๏ธ", - "๐Ÿšต๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿšต๐Ÿพโ€โ™‚๏ธ", - "๐Ÿšต๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿšตโ€โ™€๏ธ", - "๐Ÿšต๐Ÿปโ€โ™€๏ธ", - "๐Ÿšต๐Ÿผโ€โ™€๏ธ", - "๐Ÿšต๐Ÿฝโ€โ™€๏ธ", - "๐Ÿšต๐Ÿพโ€โ™€๏ธ", - "๐Ÿšต๐Ÿฟโ€โ™€๏ธ", - "๐Ÿคธ", - "๐Ÿคธ๐Ÿป", - "๐Ÿคธ๐Ÿผ", - "๐Ÿคธ๐Ÿฝ", - "๐Ÿคธ๐Ÿพ", - "๐Ÿคธ๐Ÿฟ", - "๐Ÿคธโ€โ™‚๏ธ", - "๐Ÿคธ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿคธ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿคธ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿคธ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿคธ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿคธโ€โ™€๏ธ", - "๐Ÿคธ๐Ÿปโ€โ™€๏ธ", - "๐Ÿคธ๐Ÿผโ€โ™€๏ธ", - "๐Ÿคธ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿคธ๐Ÿพโ€โ™€๏ธ", - "๐Ÿคธ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿคผ", - "๐Ÿคผโ€โ™‚๏ธ", - "๐Ÿคผโ€โ™€๏ธ", - "๐Ÿคฝ", - "๐Ÿคฝ๐Ÿป", - "๐Ÿคฝ๐Ÿผ", - "๐Ÿคฝ๐Ÿฝ", - "๐Ÿคฝ๐Ÿพ", - "๐Ÿคฝ๐Ÿฟ", - "๐Ÿคฝโ€โ™‚๏ธ", - "๐Ÿคฝ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿคฝ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿคฝ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿคฝ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿคฝ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿคฝโ€โ™€๏ธ", - "๐Ÿคฝ๐Ÿปโ€โ™€๏ธ", - "๐Ÿคฝ๐Ÿผโ€โ™€๏ธ", - "๐Ÿคฝ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿคฝ๐Ÿพโ€โ™€๏ธ", - "๐Ÿคฝ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿคพ", - "๐Ÿคพ๐Ÿป", - "๐Ÿคพ๐Ÿผ", - "๐Ÿคพ๐Ÿฝ", - "๐Ÿคพ๐Ÿพ", - "๐Ÿคพ๐Ÿฟ", - "๐Ÿคพโ€โ™‚๏ธ", - "๐Ÿคพ๐Ÿปโ€โ™‚๏ธ", - "๐Ÿคพ๐Ÿผโ€โ™‚๏ธ", - "๐Ÿคพ๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿคพ๐Ÿพโ€โ™‚๏ธ", - "๐Ÿคพ๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿคพโ€โ™€๏ธ", - "๐Ÿคพ๐Ÿปโ€โ™€๏ธ", - "๐Ÿคพ๐Ÿผโ€โ™€๏ธ", - "๐Ÿคพ๐Ÿฝโ€โ™€๏ธ", - "๐Ÿคพ๐Ÿพโ€โ™€๏ธ", - "๐Ÿคพ๐Ÿฟโ€โ™€๏ธ", - "๐Ÿคน", - "๐Ÿคน๐Ÿป", - "๐Ÿคน๐Ÿผ", - "๐Ÿคน๐Ÿฝ", - "๐Ÿคน๐Ÿพ", - "๐Ÿคน๐Ÿฟ", - "๐Ÿคนโ€โ™‚๏ธ", - "๐Ÿคน๐Ÿปโ€โ™‚๏ธ", - "๐Ÿคน๐Ÿผโ€โ™‚๏ธ", - "๐Ÿคน๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿคน๐Ÿพโ€โ™‚๏ธ", - "๐Ÿคน๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿคนโ€โ™€๏ธ", - "๐Ÿคน๐Ÿปโ€โ™€๏ธ", - "๐Ÿคน๐Ÿผโ€โ™€๏ธ", - "๐Ÿคน๐Ÿฝโ€โ™€๏ธ", - "๐Ÿคน๐Ÿพโ€โ™€๏ธ", - "๐Ÿคน๐Ÿฟโ€โ™€๏ธ", - "๐Ÿง˜", - "๐Ÿง˜๐Ÿป", - "๐Ÿง˜๐Ÿผ", - "๐Ÿง˜๐Ÿฝ", - "๐Ÿง˜๐Ÿพ", - "๐Ÿง˜๐Ÿฟ", - "๐Ÿง˜โ€โ™‚๏ธ", - "๐Ÿง˜๐Ÿปโ€โ™‚๏ธ", - "๐Ÿง˜๐Ÿผโ€โ™‚๏ธ", - "๐Ÿง˜๐Ÿฝโ€โ™‚๏ธ", - "๐Ÿง˜๐Ÿพโ€โ™‚๏ธ", - "๐Ÿง˜๐Ÿฟโ€โ™‚๏ธ", - "๐Ÿง˜โ€โ™€๏ธ", - "๐Ÿง˜๐Ÿปโ€โ™€๏ธ", - "๐Ÿง˜๐Ÿผโ€โ™€๏ธ", - "๐Ÿง˜๐Ÿฝโ€โ™€๏ธ", - "๐Ÿง˜๐Ÿพโ€โ™€๏ธ", - "๐Ÿง˜๐Ÿฟโ€โ™€๏ธ", - "๐Ÿ›€", - "๐Ÿ›€๐Ÿป", - "๐Ÿ›€๐Ÿผ", - "๐Ÿ›€๐Ÿฝ", - "๐Ÿ›€๐Ÿพ", - "๐Ÿ›€๐Ÿฟ", - "๐Ÿ›Œ", - "๐Ÿ›Œ๐Ÿป", - "๐Ÿ›Œ๐Ÿผ", - "๐Ÿ›Œ๐Ÿฝ", - "๐Ÿ›Œ๐Ÿพ", - "๐Ÿ›Œ๐Ÿฟ", - "๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘", - "๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿ‘ญ", - "๐Ÿ‘ญ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ญ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ญ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ญ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ญ๐Ÿฟ", - "๐Ÿ‘ซ", - "๐Ÿ‘ซ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ซ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ซ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ซ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ซ๐Ÿฟ", - "๐Ÿ‘ฌ", - "๐Ÿ‘ฌ๐Ÿป", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฌ๐Ÿผ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฌ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฌ๐Ÿพ", - "๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฌ๐Ÿฟ", - "๐Ÿ’", - "๐Ÿ’๐Ÿป", - "๐Ÿ’๐Ÿผ", - "๐Ÿ’๐Ÿฝ", - "๐Ÿ’๐Ÿพ", - "๐Ÿ’๐Ÿฟ", - "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ", - "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ’‘", - "๐Ÿ’‘๐Ÿป", - "๐Ÿ’‘๐Ÿผ", - "๐Ÿ’‘๐Ÿฝ", - "๐Ÿ’‘๐Ÿพ", - "๐Ÿ’‘๐Ÿฟ", - "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿพ", - "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ", - "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿป", - "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿผ", - "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ", - "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿพ", - "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", - "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", - "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", - "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", - "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", - "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง", - "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", - "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", - "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "๐Ÿ‘จโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘ง", - "๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "๐Ÿ‘ฉโ€๐Ÿ‘ฆ", - "๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "๐Ÿ‘ฉโ€๐Ÿ‘ง", - "๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "๐Ÿ—ฃ๏ธ", - "๐Ÿ‘ค", - "๐Ÿ‘ฅ", - "๐Ÿซ‚", - "๐Ÿ‘ช", - "๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’", - "๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’", - "๐Ÿง‘โ€๐Ÿง’", - "๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’", - "๐Ÿ‘ฃ", - "๐Ÿซ†", - "๐Ÿต", - "๐Ÿ’", - "๐Ÿฆ", - "๐Ÿฆง", - "๐Ÿถ", - "๐Ÿ•", - "๐Ÿฆฎ", - "๐Ÿ•โ€๐Ÿฆบ", - "๐Ÿฉ", - "๐Ÿบ", - "๐ŸฆŠ", - "๐Ÿฆ", - "๐Ÿฑ", - "๐Ÿˆ", - "๐Ÿˆโ€โฌ›", - "๐Ÿฆ", - "๐Ÿฏ", - "๐Ÿ…", - "๐Ÿ†", - "๐Ÿด", - "๐ŸซŽ", - "๐Ÿซ", - "๐ŸŽ", - "๐Ÿฆ„", - "๐Ÿฆ“", - "๐ŸฆŒ", - "๐Ÿฆฌ", - "๐Ÿฎ", - "๐Ÿ‚", - "๐Ÿƒ", - "๐Ÿ„", - "๐Ÿท", - "๐Ÿ–", - "๐Ÿ—", - "๐Ÿฝ", - "๐Ÿ", - "๐Ÿ‘", - "๐Ÿ", - "๐Ÿช", - "๐Ÿซ", - "๐Ÿฆ™", - "๐Ÿฆ’", - "๐Ÿ˜", - "๐Ÿฆฃ", - "๐Ÿฆ", - "๐Ÿฆ›", - "๐Ÿญ", - "๐Ÿ", - "๐Ÿ€", - "๐Ÿน", - "๐Ÿฐ", - "๐Ÿ‡", - "๐Ÿฟ๏ธ", - "๐Ÿฆซ", - "๐Ÿฆ”", - "๐Ÿฆ‡", - "๐Ÿป", - "๐Ÿปโ€โ„๏ธ", - "๐Ÿจ", - "๐Ÿผ", - "๐Ÿฆฅ", - "๐Ÿฆฆ", - "๐Ÿฆจ", - "๐Ÿฆ˜", - "๐Ÿฆก", - "๐Ÿพ", - "๐Ÿฆƒ", - "๐Ÿ”", - "๐Ÿ“", - "๐Ÿฃ", - "๐Ÿค", - "๐Ÿฅ", - "๐Ÿฆ", - "๐Ÿง", - "๐Ÿ•Š๏ธ", - "๐Ÿฆ…", - "๐Ÿฆ†", - "๐Ÿฆข", - "๐Ÿฆ‰", - "๐Ÿฆค", - "๐Ÿชถ", - "๐Ÿฆฉ", - "๐Ÿฆš", - "๐Ÿฆœ", - "๐Ÿชฝ", - "๐Ÿฆโ€โฌ›", - "๐Ÿชฟ", - "๐Ÿฆโ€๐Ÿ”ฅ", - "๐Ÿธ", - "๐ŸŠ", - "๐Ÿข", - "๐ŸฆŽ", - "๐Ÿ", - "๐Ÿฒ", - "๐Ÿ‰", - "๐Ÿฆ•", - "๐Ÿฆ–", - "๐Ÿณ", - "๐Ÿ‹", - "๐Ÿฌ", - "๐Ÿฆญ", - "๐ŸŸ", - "๐Ÿ ", - "๐Ÿก", - "๐Ÿฆˆ", - "๐Ÿ™", - "๐Ÿš", - "๐Ÿชธ", - "๐Ÿชผ", - "๐Ÿฆ€", - "๐Ÿฆž", - "๐Ÿฆ", - "๐Ÿฆ‘", - "๐Ÿฆช", - "๐ŸŒ", - "๐Ÿฆ‹", - "๐Ÿ›", - "๐Ÿœ", - "๐Ÿ", - "๐Ÿชฒ", - "๐Ÿž", - "๐Ÿฆ—", - "๐Ÿชณ", - "๐Ÿ•ท๏ธ", - "๐Ÿ•ธ๏ธ", - "๐Ÿฆ‚", - "๐ŸฆŸ", - "๐Ÿชฐ", - "๐Ÿชฑ", - "๐Ÿฆ ", - "๐Ÿ’", - "๐ŸŒธ", - "๐Ÿ’ฎ", - "๐Ÿชท", - "๐Ÿต๏ธ", - "๐ŸŒน", - "๐Ÿฅ€", - "๐ŸŒบ", - "๐ŸŒป", - "๐ŸŒผ", - "๐ŸŒท", - "๐Ÿชป", - "๐ŸŒฑ", - "๐Ÿชด", - "๐ŸŒฒ", - "๐ŸŒณ", - "๐ŸŒด", - "๐ŸŒต", - "๐ŸŒพ", - "๐ŸŒฟ", - "โ˜˜๏ธ", - "๐Ÿ€", - "๐Ÿ", - "๐Ÿ‚", - "๐Ÿƒ", - "๐Ÿชน", - "๐Ÿชบ", - "๐Ÿ„", - "๐Ÿชพ", - "๐Ÿ‡", - "๐Ÿˆ", - "๐Ÿ‰", - "๐ŸŠ", - "๐Ÿ‹", - "๐Ÿ‹โ€๐ŸŸฉ", - "๐ŸŒ", - "๐Ÿ", - "๐Ÿฅญ", - "๐ŸŽ", - "๐Ÿ", - "๐Ÿ", - "๐Ÿ‘", - "๐Ÿ’", - "๐Ÿ“", - "๐Ÿซ", - "๐Ÿฅ", - "๐Ÿ…", - "๐Ÿซ’", - "๐Ÿฅฅ", - "๐Ÿฅ‘", - "๐Ÿ†", - "๐Ÿฅ”", - "๐Ÿฅ•", - "๐ŸŒฝ", - "๐ŸŒถ๏ธ", - "๐Ÿซ‘", - "๐Ÿฅ’", - "๐Ÿฅฌ", - "๐Ÿฅฆ", - "๐Ÿง„", - "๐Ÿง…", - "๐Ÿฅœ", - "๐Ÿซ˜", - "๐ŸŒฐ", - "๐Ÿซš", - "๐Ÿซ›", - "๐Ÿ„โ€๐ŸŸซ", - "๐Ÿซœ", - "๐Ÿž", - "๐Ÿฅ", - "๐Ÿฅ–", - "๐Ÿซ“", - "๐Ÿฅจ", - "๐Ÿฅฏ", - "๐Ÿฅž", - "๐Ÿง‡", - "๐Ÿง€", - "๐Ÿ–", - "๐Ÿ—", - "๐Ÿฅฉ", - "๐Ÿฅ“", - "๐Ÿ”", - "๐ŸŸ", - "๐Ÿ•", - "๐ŸŒญ", - "๐Ÿฅช", - "๐ŸŒฎ", - "๐ŸŒฏ", - "๐Ÿซ”", - "๐Ÿฅ™", - "๐Ÿง†", - "๐Ÿฅš", - "๐Ÿณ", - "๐Ÿฅ˜", - "๐Ÿฒ", - "๐Ÿซ•", - "๐Ÿฅฃ", - "๐Ÿฅ—", - "๐Ÿฟ", - "๐Ÿงˆ", - "๐Ÿง‚", - "๐Ÿฅซ", - "๐Ÿฑ", - "๐Ÿ˜", - "๐Ÿ™", - "๐Ÿš", - "๐Ÿ›", - "๐Ÿœ", - "๐Ÿ", - "๐Ÿ ", - "๐Ÿข", - "๐Ÿฃ", - "๐Ÿค", - "๐Ÿฅ", - "๐Ÿฅฎ", - "๐Ÿก", - "๐ŸฅŸ", - "๐Ÿฅ ", - "๐Ÿฅก", - "๐Ÿฆ", - "๐Ÿง", - "๐Ÿจ", - "๐Ÿฉ", - "๐Ÿช", - "๐ŸŽ‚", - "๐Ÿฐ", - "๐Ÿง", - "๐Ÿฅง", - "๐Ÿซ", - "๐Ÿฌ", - "๐Ÿญ", - "๐Ÿฎ", - "๐Ÿฏ", - "๐Ÿผ", - "๐Ÿฅ›", - "โ˜•", - "๐Ÿซ–", - "๐Ÿต", - "๐Ÿถ", - "๐Ÿพ", - "๐Ÿท", - "๐Ÿธ", - "๐Ÿน", - "๐Ÿบ", - "๐Ÿป", - "๐Ÿฅ‚", - "๐Ÿฅƒ", - "๐Ÿซ—", - "๐Ÿฅค", - "๐Ÿง‹", - "๐Ÿงƒ", - "๐Ÿง‰", - "๐ŸงŠ", - "๐Ÿฅข", - "๐Ÿฝ๏ธ", - "๐Ÿด", - "๐Ÿฅ„", - "๐Ÿ”ช", - "๐Ÿซ™", - "๐Ÿบ", - "๐ŸŒ", - "๐ŸŒŽ", - "๐ŸŒ", - "๐ŸŒ", - "๐Ÿ—บ๏ธ", - "๐Ÿ—พ", - "๐Ÿงญ", - "๐Ÿ”๏ธ", - "โ›ฐ๏ธ", - "๐ŸŒ‹", - "๐Ÿ—ป", - "๐Ÿ•๏ธ", - "๐Ÿ–๏ธ", - "๐Ÿœ๏ธ", - "๐Ÿ๏ธ", - "๐Ÿž๏ธ", - "๐ŸŸ๏ธ", - "๐Ÿ›๏ธ", - "๐Ÿ—๏ธ", - "๐Ÿงฑ", - "๐Ÿชจ", - "๐Ÿชต", - "๐Ÿ›–", - "๐Ÿ˜๏ธ", - "๐Ÿš๏ธ", - "๐Ÿ ", - "๐Ÿก", - "๐Ÿข", - "๐Ÿฃ", - "๐Ÿค", - "๐Ÿฅ", - "๐Ÿฆ", - "๐Ÿจ", - "๐Ÿฉ", - "๐Ÿช", - "๐Ÿซ", - "๐Ÿฌ", - "๐Ÿญ", - "๐Ÿฏ", - "๐Ÿฐ", - "๐Ÿ’’", - "๐Ÿ—ผ", - "๐Ÿ—ฝ", - "โ›ช", - "๐Ÿ•Œ", - "๐Ÿ›•", - "๐Ÿ•", - "โ›ฉ๏ธ", - "๐Ÿ•‹", - "โ›ฒ", - "โ›บ", - "๐ŸŒ", - "๐ŸŒƒ", - "๐Ÿ™๏ธ", - "๐ŸŒ„", - "๐ŸŒ…", - "๐ŸŒ†", - "๐ŸŒ‡", - "๐ŸŒ‰", - "โ™จ๏ธ", - "๐ŸŽ ", - "๐Ÿ›", - "๐ŸŽก", - "๐ŸŽข", - "๐Ÿ’ˆ", - "๐ŸŽช", - "๐Ÿš‚", - "๐Ÿšƒ", - "๐Ÿš„", - "๐Ÿš…", - "๐Ÿš†", - "๐Ÿš‡", - "๐Ÿšˆ", - "๐Ÿš‰", - "๐ŸšŠ", - "๐Ÿš", - "๐Ÿšž", - "๐Ÿš‹", - "๐ŸšŒ", - "๐Ÿš", - "๐ŸšŽ", - "๐Ÿš", - "๐Ÿš‘", - "๐Ÿš’", - "๐Ÿš“", - "๐Ÿš”", - "๐Ÿš•", - "๐Ÿš–", - "๐Ÿš—", - "๐Ÿš˜", - "๐Ÿš™", - "๐Ÿ›ป", - "๐Ÿšš", - "๐Ÿš›", - "๐Ÿšœ", - "๐ŸŽ๏ธ", - "๐Ÿ๏ธ", - "๐Ÿ›ต", - "๐Ÿฆฝ", - "๐Ÿฆผ", - "๐Ÿ›บ", - "๐Ÿšฒ", - "๐Ÿ›ด", - "๐Ÿ›น", - "๐Ÿ›ผ", - "๐Ÿš", - "๐Ÿ›ฃ๏ธ", - "๐Ÿ›ค๏ธ", - "๐Ÿ›ข๏ธ", - "โ›ฝ", - "๐Ÿ›ž", - "๐Ÿšจ", - "๐Ÿšฅ", - "๐Ÿšฆ", - "๐Ÿ›‘", - "๐Ÿšง", - "โš“", - "๐Ÿ›Ÿ", - "โ›ต", - "๐Ÿ›ถ", - "๐Ÿšค", - "๐Ÿ›ณ๏ธ", - "โ›ด๏ธ", - "๐Ÿ›ฅ๏ธ", - "๐Ÿšข", - "โœˆ๏ธ", - "๐Ÿ›ฉ๏ธ", - "๐Ÿ›ซ", - "๐Ÿ›ฌ", - "๐Ÿช‚", - "๐Ÿ’บ", - "๐Ÿš", - "๐ŸšŸ", - "๐Ÿš ", - "๐Ÿšก", - "๐Ÿ›ฐ๏ธ", - "๐Ÿš€", - "๐Ÿ›ธ", - "๐Ÿ›Ž๏ธ", - "๐Ÿงณ", - "โŒ›", - "โณ", - "โŒš", - "โฐ", - "โฑ๏ธ", - "โฒ๏ธ", - "๐Ÿ•ฐ๏ธ", - "๐Ÿ•›", - "๐Ÿ•ง", - "๐Ÿ•", - "๐Ÿ•œ", - "๐Ÿ•‘", - "๐Ÿ•", - "๐Ÿ•’", - "๐Ÿ•ž", - "๐Ÿ•“", - "๐Ÿ•Ÿ", - "๐Ÿ•”", - "๐Ÿ• ", - "๐Ÿ••", - "๐Ÿ•ก", - "๐Ÿ•–", - "๐Ÿ•ข", - "๐Ÿ•—", - "๐Ÿ•ฃ", - "๐Ÿ•˜", - "๐Ÿ•ค", - "๐Ÿ•™", - "๐Ÿ•ฅ", - "๐Ÿ•š", - "๐Ÿ•ฆ", - "๐ŸŒ‘", - "๐ŸŒ’", - "๐ŸŒ“", - "๐ŸŒ”", - "๐ŸŒ•", - "๐ŸŒ–", - "๐ŸŒ—", - "๐ŸŒ˜", - "๐ŸŒ™", - "๐ŸŒš", - "๐ŸŒ›", - "๐ŸŒœ", - "๐ŸŒก๏ธ", - "โ˜€๏ธ", - "๐ŸŒ", - "๐ŸŒž", - "๐Ÿช", - "โญ", - "๐ŸŒŸ", - "๐ŸŒ ", - "๐ŸŒŒ", - "โ˜๏ธ", - "โ›…", - "โ›ˆ๏ธ", - "๐ŸŒค๏ธ", - "๐ŸŒฅ๏ธ", - "๐ŸŒฆ๏ธ", - "๐ŸŒง๏ธ", - "๐ŸŒจ๏ธ", - "๐ŸŒฉ๏ธ", - "๐ŸŒช๏ธ", - "๐ŸŒซ๏ธ", - "๐ŸŒฌ๏ธ", - "๐ŸŒ€", - "๐ŸŒˆ", - "๐ŸŒ‚", - "โ˜‚๏ธ", - "โ˜”", - "โ›ฑ๏ธ", - "โšก", - "โ„๏ธ", - "โ˜ƒ๏ธ", - "โ›„", - "โ˜„๏ธ", - "๐Ÿ”ฅ", - "๐Ÿ’ง", - "๐ŸŒŠ", - "๐ŸŽƒ", - "๐ŸŽ„", - "๐ŸŽ†", - "๐ŸŽ‡", - "๐Ÿงจ", - "โœจ", - "๐ŸŽˆ", - "๐ŸŽ‰", - "๐ŸŽŠ", - "๐ŸŽ‹", - "๐ŸŽ", - "๐ŸŽŽ", - "๐ŸŽ", - "๐ŸŽ", - "๐ŸŽ‘", - "๐Ÿงง", - "๐ŸŽ€", - "๐ŸŽ", - "๐ŸŽ—๏ธ", - "๐ŸŽŸ๏ธ", - "๐ŸŽซ", - "๐ŸŽ–๏ธ", - "๐Ÿ†", - "๐Ÿ…", - "๐Ÿฅ‡", - "๐Ÿฅˆ", - "๐Ÿฅ‰", - "โšฝ", - "โšพ", - "๐ŸฅŽ", - "๐Ÿ€", - "๐Ÿ", - "๐Ÿˆ", - "๐Ÿ‰", - "๐ŸŽพ", - "๐Ÿฅ", - "๐ŸŽณ", - "๐Ÿ", - "๐Ÿ‘", - "๐Ÿ’", - "๐Ÿฅ", - "๐Ÿ“", - "๐Ÿธ", - "๐ŸฅŠ", - "๐Ÿฅ‹", - "๐Ÿฅ…", - "โ›ณ", - "โ›ธ๏ธ", - "๐ŸŽฃ", - "๐Ÿคฟ", - "๐ŸŽฝ", - "๐ŸŽฟ", - "๐Ÿ›ท", - "๐ŸฅŒ", - "๐ŸŽฏ", - "๐Ÿช€", - "๐Ÿช", - "๐Ÿ”ซ", - "๐ŸŽฑ", - "๐Ÿ”ฎ", - "๐Ÿช„", - "๐ŸŽฎ", - "๐Ÿ•น๏ธ", - "๐ŸŽฐ", - "๐ŸŽฒ", - "๐Ÿงฉ", - "๐Ÿงธ", - "๐Ÿช…", - "๐Ÿชฉ", - "๐Ÿช†", - "โ™ ๏ธ", - "โ™ฅ๏ธ", - "โ™ฆ๏ธ", - "โ™ฃ๏ธ", - "โ™Ÿ๏ธ", - "๐Ÿƒ", - "๐Ÿ€„", - "๐ŸŽด", - "๐ŸŽญ", - "๐Ÿ–ผ๏ธ", - "๐ŸŽจ", - "๐Ÿงต", - "๐Ÿชก", - "๐Ÿงถ", - "๐Ÿชข", - "๐Ÿ‘“", - "๐Ÿ•ถ๏ธ", - "๐Ÿฅฝ", - "๐Ÿฅผ", - "๐Ÿฆบ", - "๐Ÿ‘”", - "๐Ÿ‘•", - "๐Ÿ‘–", - "๐Ÿงฃ", - "๐Ÿงค", - "๐Ÿงฅ", - "๐Ÿงฆ", - "๐Ÿ‘—", - "๐Ÿ‘˜", - "๐Ÿฅป", - "๐Ÿฉฑ", - "๐Ÿฉฒ", - "๐Ÿฉณ", - "๐Ÿ‘™", - "๐Ÿ‘š", - "๐Ÿชญ", - "๐Ÿ‘›", - "๐Ÿ‘œ", - "๐Ÿ‘", - "๐Ÿ›๏ธ", - "๐ŸŽ’", - "๐Ÿฉด", - "๐Ÿ‘ž", - "๐Ÿ‘Ÿ", - "๐Ÿฅพ", - "๐Ÿฅฟ", - "๐Ÿ‘ ", - "๐Ÿ‘ก", - "๐Ÿฉฐ", - "๐Ÿ‘ข", - "๐Ÿชฎ", - "๐Ÿ‘‘", - "๐Ÿ‘’", - "๐ŸŽฉ", - "๐ŸŽ“", - "๐Ÿงข", - "๐Ÿช–", - "โ›‘๏ธ", - "๐Ÿ“ฟ", - "๐Ÿ’„", - "๐Ÿ’", - "๐Ÿ’Ž", - "๐Ÿ”‡", - "๐Ÿ”ˆ", - "๐Ÿ”‰", - "๐Ÿ”Š", - "๐Ÿ“ข", - "๐Ÿ“ฃ", - "๐Ÿ“ฏ", - "๐Ÿ””", - "๐Ÿ”•", - "๐ŸŽผ", - "๐ŸŽต", - "๐ŸŽถ", - "๐ŸŽ™๏ธ", - "๐ŸŽš๏ธ", - "๐ŸŽ›๏ธ", - "๐ŸŽค", - "๐ŸŽง", - "๐Ÿ“ป", - "๐ŸŽท", - "๐Ÿช—", - "๐ŸŽธ", - "๐ŸŽน", - "๐ŸŽบ", - "๐ŸŽป", - "๐Ÿช•", - "๐Ÿฅ", - "๐Ÿช˜", - "๐Ÿช‡", - "๐Ÿชˆ", - "๐Ÿช‰", - "๐Ÿ“ฑ", - "๐Ÿ“ฒ", - "โ˜Ž๏ธ", - "๐Ÿ“ž", - "๐Ÿ“Ÿ", - "๐Ÿ“ ", - "๐Ÿ”‹", - "๐Ÿชซ", - "๐Ÿ”Œ", - "๐Ÿ’ป", - "๐Ÿ–ฅ๏ธ", - "๐Ÿ–จ๏ธ", - "โŒจ๏ธ", - "๐Ÿ–ฑ๏ธ", - "๐Ÿ–ฒ๏ธ", - "๐Ÿ’ฝ", - "๐Ÿ’พ", - "๐Ÿ’ฟ", - "๐Ÿ“€", - "๐Ÿงฎ", - "๐ŸŽฅ", - "๐ŸŽž๏ธ", - "๐Ÿ“ฝ๏ธ", - "๐ŸŽฌ", - "๐Ÿ“บ", - "๐Ÿ“ท", - "๐Ÿ“ธ", - "๐Ÿ“น", - "๐Ÿ“ผ", - "๐Ÿ”", - "๐Ÿ”Ž", - "๐Ÿ•ฏ๏ธ", - "๐Ÿ’ก", - "๐Ÿ”ฆ", - "๐Ÿฎ", - "๐Ÿช”", - "๐Ÿ“”", - "๐Ÿ“•", - "๐Ÿ“–", - "๐Ÿ“—", - "๐Ÿ“˜", - "๐Ÿ“™", - "๐Ÿ“š", - "๐Ÿ““", - "๐Ÿ“’", - "๐Ÿ“ƒ", - "๐Ÿ“œ", - "๐Ÿ“„", - "๐Ÿ“ฐ", - "๐Ÿ—ž๏ธ", - "๐Ÿ“‘", - "๐Ÿ”–", - "๐Ÿท๏ธ", - "๐Ÿ’ฐ", - "๐Ÿช™", - "๐Ÿ’ด", - "๐Ÿ’ต", - "๐Ÿ’ถ", - "๐Ÿ’ท", - "๐Ÿ’ธ", - "๐Ÿ’ณ", - "๐Ÿงพ", - "๐Ÿ’น", - "โœ‰๏ธ", - "๐Ÿ“ง", - "๐Ÿ“จ", - "๐Ÿ“ฉ", - "๐Ÿ“ค", - "๐Ÿ“ฅ", - "๐Ÿ“ฆ", - "๐Ÿ“ซ", - "๐Ÿ“ช", - "๐Ÿ“ฌ", - "๐Ÿ“ญ", - "๐Ÿ“ฎ", - "๐Ÿ—ณ๏ธ", - "โœ๏ธ", - "โœ’๏ธ", - "๐Ÿ–‹๏ธ", - "๐Ÿ–Š๏ธ", - "๐Ÿ–Œ๏ธ", - "๐Ÿ–๏ธ", - "๐Ÿ“", - "๐Ÿ’ผ", - "๐Ÿ“", - "๐Ÿ“‚", - "๐Ÿ—‚๏ธ", - "๐Ÿ“…", - "๐Ÿ“†", - "๐Ÿ—’๏ธ", - "๐Ÿ—“๏ธ", - "๐Ÿ“‡", - "๐Ÿ“ˆ", - "๐Ÿ“‰", - "๐Ÿ“Š", - "๐Ÿ“‹", - "๐Ÿ“Œ", - "๐Ÿ“", - "๐Ÿ“Ž", - "๐Ÿ–‡๏ธ", - "๐Ÿ“", - "๐Ÿ“", - "โœ‚๏ธ", - "๐Ÿ—ƒ๏ธ", - "๐Ÿ—„๏ธ", - "๐Ÿ—‘๏ธ", - "๐Ÿ”’", - "๐Ÿ”“", - "๐Ÿ”", - "๐Ÿ”", - "๐Ÿ”‘", - "๐Ÿ—๏ธ", - "๐Ÿ”จ", - "๐Ÿช“", - "โ›๏ธ", - "โš’๏ธ", - "๐Ÿ› ๏ธ", - "๐Ÿ—ก๏ธ", - "โš”๏ธ", - "๐Ÿ’ฃ", - "๐Ÿชƒ", - "๐Ÿน", - "๐Ÿ›ก๏ธ", - "๐Ÿชš", - "๐Ÿ”ง", - "๐Ÿช›", - "๐Ÿ”ฉ", - "โš™๏ธ", - "๐Ÿ—œ๏ธ", - "โš–๏ธ", - "๐Ÿฆฏ", - "๐Ÿ”—", - "โ›“๏ธโ€๐Ÿ’ฅ", - "โ›“๏ธ", - "๐Ÿช", - "๐Ÿงฐ", - "๐Ÿงฒ", - "๐Ÿชœ", - "๐Ÿช", - "โš—๏ธ", - "๐Ÿงช", - "๐Ÿงซ", - "๐Ÿงฌ", - "๐Ÿ”ฌ", - "๐Ÿ”ญ", - "๐Ÿ“ก", - "๐Ÿ’‰", - "๐Ÿฉธ", - "๐Ÿ’Š", - "๐Ÿฉน", - "๐Ÿฉผ", - "๐Ÿฉบ", - "๐Ÿฉป", - "๐Ÿšช", - "๐Ÿ›—", - "๐Ÿชž", - "๐ŸชŸ", - "๐Ÿ›๏ธ", - "๐Ÿ›‹๏ธ", - "๐Ÿช‘", - "๐Ÿšฝ", - "๐Ÿช ", - "๐Ÿšฟ", - "๐Ÿ›", - "๐Ÿชค", - "๐Ÿช’", - "๐Ÿงด", - "๐Ÿงท", - "๐Ÿงน", - "๐Ÿงบ", - "๐Ÿงป", - "๐Ÿชฃ", - "๐Ÿงผ", - "๐Ÿซง", - "๐Ÿชฅ", - "๐Ÿงฝ", - "๐Ÿงฏ", - "๐Ÿ›’", - "๐Ÿšฌ", - "โšฐ๏ธ", - "๐Ÿชฆ", - "โšฑ๏ธ", - "๐Ÿงฟ", - "๐Ÿชฌ", - "๐Ÿ—ฟ", - "๐Ÿชง", - "๐Ÿชช", - "๐Ÿง", - "๐Ÿšฎ", - "๐Ÿšฐ", - "โ™ฟ", - "๐Ÿšน", - "๐Ÿšบ", - "๐Ÿšป", - "๐Ÿšผ", - "๐Ÿšพ", - "๐Ÿ›‚", - "๐Ÿ›ƒ", - "๐Ÿ›„", - "๐Ÿ›…", - "โš ๏ธ", - "๐Ÿšธ", - "โ›”", - "๐Ÿšซ", - "๐Ÿšณ", - "๐Ÿšญ", - "๐Ÿšฏ", - "๐Ÿšฑ", - "๐Ÿšท", - "๐Ÿ“ต", - "๐Ÿ”ž", - "โ˜ข๏ธ", - "โ˜ฃ๏ธ", - "โฌ†๏ธ", - "โ†—๏ธ", - "โžก๏ธ", - "โ†˜๏ธ", - "โฌ‡๏ธ", - "โ†™๏ธ", - "โฌ…๏ธ", - "โ†–๏ธ", - "โ†•๏ธ", - "โ†”๏ธ", - "โ†ฉ๏ธ", - "โ†ช๏ธ", - "โคด๏ธ", - "โคต๏ธ", - "๐Ÿ”ƒ", - "๐Ÿ”„", - "๐Ÿ”™", - "๐Ÿ”š", - "๐Ÿ”›", - "๐Ÿ”œ", - "๐Ÿ”", - "๐Ÿ›", - "โš›๏ธ", - "๐Ÿ•‰๏ธ", - "โœก๏ธ", - "โ˜ธ๏ธ", - "โ˜ฏ๏ธ", - "โœ๏ธ", - "โ˜ฆ๏ธ", - "โ˜ช๏ธ", - "โ˜ฎ๏ธ", - "๐Ÿ•Ž", - "๐Ÿ”ฏ", - "๐Ÿชฏ", - "โ™ˆ", - "โ™‰", - "โ™Š", - "โ™‹", - "โ™Œ", - "โ™", - "โ™Ž", - "โ™", - "โ™", - "โ™‘", - "โ™’", - "โ™“", - "โ›Ž", - "๐Ÿ”€", - "๐Ÿ”", - "๐Ÿ”‚", - "โ–ถ๏ธ", - "โฉ", - "โญ๏ธ", - "โฏ๏ธ", - "โ—€๏ธ", - "โช", - "โฎ๏ธ", - "๐Ÿ”ผ", - "โซ", - "๐Ÿ”ฝ", - "โฌ", - "โธ๏ธ", - "โน๏ธ", - "โบ๏ธ", - "โ๏ธ", - "๐ŸŽฆ", - "๐Ÿ”…", - "๐Ÿ”†", - "๐Ÿ“ถ", - "๐Ÿ›œ", - "๐Ÿ“ณ", - "๐Ÿ“ด", - "โ™€๏ธ", - "โ™‚๏ธ", - "โšง๏ธ", - "โœ–๏ธ", - "โž•", - "โž–", - "โž—", - "๐ŸŸฐ", - "โ™พ๏ธ", - "โ€ผ๏ธ", - "โ‰๏ธ", - "โ“", - "โ”", - "โ•", - "โ—", - "ใ€ฐ๏ธ", - "๐Ÿ’ฑ", - "๐Ÿ’ฒ", - "โš•๏ธ", - "โ™ป๏ธ", - "โšœ๏ธ", - "๐Ÿ”ฑ", - "๐Ÿ“›", - "๐Ÿ”ฐ", - "โญ•", - "โœ…", - "โ˜‘๏ธ", - "โœ”๏ธ", - "โŒ", - "โŽ", - "โžฐ", - "โžฟ", - "ใ€ฝ๏ธ", - "โœณ๏ธ", - "โœด๏ธ", - "โ‡๏ธ", - "ยฉ๏ธ", - "ยฎ๏ธ", - "โ„ข๏ธ", - "๐ŸซŸ", - "#๏ธโƒฃ", - "*๏ธโƒฃ", - "0๏ธโƒฃ", - "1๏ธโƒฃ", - "2๏ธโƒฃ", - "3๏ธโƒฃ", - "4๏ธโƒฃ", - "5๏ธโƒฃ", - "6๏ธโƒฃ", - "7๏ธโƒฃ", - "8๏ธโƒฃ", - "9๏ธโƒฃ", - "๐Ÿ”Ÿ", - "๐Ÿ” ", - "๐Ÿ”ก", - "๐Ÿ”ข", - "๐Ÿ”ฃ", - "๐Ÿ”ค", - "๐Ÿ…ฐ๏ธ", - "๐Ÿ†Ž", - "๐Ÿ…ฑ๏ธ", - "๐Ÿ†‘", - "๐Ÿ†’", - "๐Ÿ†“", - "โ„น๏ธ", - "๐Ÿ†”", - "โ“‚๏ธ", - "๐Ÿ†•", - "๐Ÿ†–", - "๐Ÿ…พ๏ธ", - "๐Ÿ†—", - "๐Ÿ…ฟ๏ธ", - "๐Ÿ†˜", - "๐Ÿ†™", - "๐Ÿ†š", - "๐Ÿˆ", - "๐Ÿˆ‚๏ธ", - "๐Ÿˆท๏ธ", - "๐Ÿˆถ", - "๐Ÿˆฏ", - "๐Ÿ‰", - "๐Ÿˆน", - "๐Ÿˆš", - "๐Ÿˆฒ", - "๐Ÿ‰‘", - "๐Ÿˆธ", - "๐Ÿˆด", - "๐Ÿˆณ", - "ใŠ—๏ธ", - "ใŠ™๏ธ", - "๐Ÿˆบ", - "๐Ÿˆต", - "๐Ÿ”ด", - "๐ŸŸ ", - "๐ŸŸก", - "๐ŸŸข", - "๐Ÿ”ต", - "๐ŸŸฃ", - "๐ŸŸค", - "โšซ", - "โšช", - "๐ŸŸฅ", - "๐ŸŸง", - "๐ŸŸจ", - "๐ŸŸฉ", - "๐ŸŸฆ", - "๐ŸŸช", - "๐ŸŸซ", - "โฌ›", - "โฌœ", - "โ—ผ๏ธ", - "โ—ป๏ธ", - "โ—พ", - "โ—ฝ", - "โ–ช๏ธ", - "โ–ซ๏ธ", - "๐Ÿ”ถ", - "๐Ÿ”ท", - "๐Ÿ”ธ", - "๐Ÿ”น", - "๐Ÿ”บ", - "๐Ÿ”ป", - "๐Ÿ’ ", - "๐Ÿ”˜", - "๐Ÿ”ณ", - "๐Ÿ”ฒ", - "๐Ÿ", - "๐Ÿšฉ", - "๐ŸŽŒ", - "๐Ÿด", - "๐Ÿณ๏ธ", - "๐Ÿณ๏ธโ€๐ŸŒˆ", - "๐Ÿณ๏ธโ€โšง๏ธ", - "๐Ÿดโ€โ˜ ๏ธ", - "๐Ÿ‡ฆ๐Ÿ‡จ", - "๐Ÿ‡ฆ๐Ÿ‡ฉ", - "๐Ÿ‡ฆ๐Ÿ‡ช", - "๐Ÿ‡ฆ๐Ÿ‡ซ", - "๐Ÿ‡ฆ๐Ÿ‡ฌ", - "๐Ÿ‡ฆ๐Ÿ‡ฎ", - "๐Ÿ‡ฆ๐Ÿ‡ฑ", - "๐Ÿ‡ฆ๐Ÿ‡ฒ", - "๐Ÿ‡ฆ๐Ÿ‡ด", - "๐Ÿ‡ฆ๐Ÿ‡ถ", - "๐Ÿ‡ฆ๐Ÿ‡ท", - "๐Ÿ‡ฆ๐Ÿ‡ธ", - "๐Ÿ‡ฆ๐Ÿ‡น", - "๐Ÿ‡ฆ๐Ÿ‡บ", - "๐Ÿ‡ฆ๐Ÿ‡ผ", - "๐Ÿ‡ฆ๐Ÿ‡ฝ", - "๐Ÿ‡ฆ๐Ÿ‡ฟ", - "๐Ÿ‡ง๐Ÿ‡ฆ", - "๐Ÿ‡ง๐Ÿ‡ง", - "๐Ÿ‡ง๐Ÿ‡ฉ", - "๐Ÿ‡ง๐Ÿ‡ช", - "๐Ÿ‡ง๐Ÿ‡ซ", - "๐Ÿ‡ง๐Ÿ‡ฌ", - "๐Ÿ‡ง๐Ÿ‡ญ", - "๐Ÿ‡ง๐Ÿ‡ฎ", - "๐Ÿ‡ง๐Ÿ‡ฏ", - "๐Ÿ‡ง๐Ÿ‡ฑ", - "๐Ÿ‡ง๐Ÿ‡ฒ", - "๐Ÿ‡ง๐Ÿ‡ณ", - "๐Ÿ‡ง๐Ÿ‡ด", - "๐Ÿ‡ง๐Ÿ‡ถ", - "๐Ÿ‡ง๐Ÿ‡ท", - "๐Ÿ‡ง๐Ÿ‡ธ", - "๐Ÿ‡ง๐Ÿ‡น", - "๐Ÿ‡ง๐Ÿ‡ป", - "๐Ÿ‡ง๐Ÿ‡ผ", - "๐Ÿ‡ง๐Ÿ‡พ", - "๐Ÿ‡ง๐Ÿ‡ฟ", - "๐Ÿ‡จ๐Ÿ‡ฆ", - "๐Ÿ‡จ๐Ÿ‡จ", - "๐Ÿ‡จ๐Ÿ‡ฉ", - "๐Ÿ‡จ๐Ÿ‡ซ", - "๐Ÿ‡จ๐Ÿ‡ฌ", - "๐Ÿ‡จ๐Ÿ‡ญ", - "๐Ÿ‡จ๐Ÿ‡ฎ", - "๐Ÿ‡จ๐Ÿ‡ฐ", - "๐Ÿ‡จ๐Ÿ‡ฑ", - "๐Ÿ‡จ๐Ÿ‡ฒ", - "๐Ÿ‡จ๐Ÿ‡ณ", - "๐Ÿ‡จ๐Ÿ‡ด", - "๐Ÿ‡จ๐Ÿ‡ต", - "๐Ÿ‡จ๐Ÿ‡ถ", - "๐Ÿ‡จ๐Ÿ‡ท", - "๐Ÿ‡จ๐Ÿ‡บ", - "๐Ÿ‡จ๐Ÿ‡ป", - "๐Ÿ‡จ๐Ÿ‡ผ", - "๐Ÿ‡จ๐Ÿ‡ฝ", - "๐Ÿ‡จ๐Ÿ‡พ", - "๐Ÿ‡จ๐Ÿ‡ฟ", - "๐Ÿ‡ฉ๐Ÿ‡ช", - "๐Ÿ‡ฉ๐Ÿ‡ฌ", - "๐Ÿ‡ฉ๐Ÿ‡ฏ", - "๐Ÿ‡ฉ๐Ÿ‡ฐ", - "๐Ÿ‡ฉ๐Ÿ‡ฒ", - "๐Ÿ‡ฉ๐Ÿ‡ด", - "๐Ÿ‡ฉ๐Ÿ‡ฟ", - "๐Ÿ‡ช๐Ÿ‡ฆ", - "๐Ÿ‡ช๐Ÿ‡จ", - "๐Ÿ‡ช๐Ÿ‡ช", - "๐Ÿ‡ช๐Ÿ‡ฌ", - "๐Ÿ‡ช๐Ÿ‡ญ", - "๐Ÿ‡ช๐Ÿ‡ท", - "๐Ÿ‡ช๐Ÿ‡ธ", - "๐Ÿ‡ช๐Ÿ‡น", - "๐Ÿ‡ช๐Ÿ‡บ", - "๐Ÿ‡ซ๐Ÿ‡ฎ", - "๐Ÿ‡ซ๐Ÿ‡ฏ", - "๐Ÿ‡ซ๐Ÿ‡ฐ", - "๐Ÿ‡ซ๐Ÿ‡ฒ", - "๐Ÿ‡ซ๐Ÿ‡ด", - "๐Ÿ‡ซ๐Ÿ‡ท", - "๐Ÿ‡ฌ๐Ÿ‡ฆ", - "๐Ÿ‡ฌ๐Ÿ‡ง", - "๐Ÿ‡ฌ๐Ÿ‡ฉ", - "๐Ÿ‡ฌ๐Ÿ‡ช", - "๐Ÿ‡ฌ๐Ÿ‡ซ", - "๐Ÿ‡ฌ๐Ÿ‡ฌ", - "๐Ÿ‡ฌ๐Ÿ‡ญ", - "๐Ÿ‡ฌ๐Ÿ‡ฎ", - "๐Ÿ‡ฌ๐Ÿ‡ฑ", - "๐Ÿ‡ฌ๐Ÿ‡ฒ", - "๐Ÿ‡ฌ๐Ÿ‡ณ", - "๐Ÿ‡ฌ๐Ÿ‡ต", - "๐Ÿ‡ฌ๐Ÿ‡ถ", - "๐Ÿ‡ฌ๐Ÿ‡ท", - "๐Ÿ‡ฌ๐Ÿ‡ธ", - "๐Ÿ‡ฌ๐Ÿ‡น", - "๐Ÿ‡ฌ๐Ÿ‡บ", - "๐Ÿ‡ฌ๐Ÿ‡ผ", - "๐Ÿ‡ฌ๐Ÿ‡พ", - "๐Ÿ‡ญ๐Ÿ‡ฐ", - "๐Ÿ‡ญ๐Ÿ‡ฒ", - "๐Ÿ‡ญ๐Ÿ‡ณ", - "๐Ÿ‡ญ๐Ÿ‡ท", - "๐Ÿ‡ญ๐Ÿ‡น", - "๐Ÿ‡ญ๐Ÿ‡บ", - "๐Ÿ‡ฎ๐Ÿ‡จ", - "๐Ÿ‡ฎ๐Ÿ‡ฉ", - "๐Ÿ‡ฎ๐Ÿ‡ช", - "๐Ÿ‡ฎ๐Ÿ‡ฑ", - "๐Ÿ‡ฎ๐Ÿ‡ฒ", - "๐Ÿ‡ฎ๐Ÿ‡ณ", - "๐Ÿ‡ฎ๐Ÿ‡ด", - "๐Ÿ‡ฎ๐Ÿ‡ถ", - "๐Ÿ‡ฎ๐Ÿ‡ท", - "๐Ÿ‡ฎ๐Ÿ‡ธ", - "๐Ÿ‡ฎ๐Ÿ‡น", - "๐Ÿ‡ฏ๐Ÿ‡ช", - "๐Ÿ‡ฏ๐Ÿ‡ฒ", - "๐Ÿ‡ฏ๐Ÿ‡ด", - "๐Ÿ‡ฏ๐Ÿ‡ต", - "๐Ÿ‡ฐ๐Ÿ‡ช", - "๐Ÿ‡ฐ๐Ÿ‡ฌ", - "๐Ÿ‡ฐ๐Ÿ‡ญ", - "๐Ÿ‡ฐ๐Ÿ‡ฎ", - "๐Ÿ‡ฐ๐Ÿ‡ฒ", - "๐Ÿ‡ฐ๐Ÿ‡ณ", - "๐Ÿ‡ฐ๐Ÿ‡ต", - "๐Ÿ‡ฐ๐Ÿ‡ท", - "๐Ÿ‡ฐ๐Ÿ‡ผ", - "๐Ÿ‡ฐ๐Ÿ‡พ", - "๐Ÿ‡ฐ๐Ÿ‡ฟ", - "๐Ÿ‡ฑ๐Ÿ‡ฆ", - "๐Ÿ‡ฑ๐Ÿ‡ง", - "๐Ÿ‡ฑ๐Ÿ‡จ", - "๐Ÿ‡ฑ๐Ÿ‡ฎ", - "๐Ÿ‡ฑ๐Ÿ‡ฐ", - "๐Ÿ‡ฑ๐Ÿ‡ท", - "๐Ÿ‡ฑ๐Ÿ‡ธ", - "๐Ÿ‡ฑ๐Ÿ‡น", - "๐Ÿ‡ฑ๐Ÿ‡บ", - "๐Ÿ‡ฑ๐Ÿ‡ป", - "๐Ÿ‡ฑ๐Ÿ‡พ", - "๐Ÿ‡ฒ๐Ÿ‡ฆ", - "๐Ÿ‡ฒ๐Ÿ‡จ", - "๐Ÿ‡ฒ๐Ÿ‡ฉ", - "๐Ÿ‡ฒ๐Ÿ‡ช", - "๐Ÿ‡ฒ๐Ÿ‡ซ", - "๐Ÿ‡ฒ๐Ÿ‡ฌ", - "๐Ÿ‡ฒ๐Ÿ‡ญ", - "๐Ÿ‡ฒ๐Ÿ‡ฐ", - "๐Ÿ‡ฒ๐Ÿ‡ฑ", - "๐Ÿ‡ฒ๐Ÿ‡ฒ", - "๐Ÿ‡ฒ๐Ÿ‡ณ", - "๐Ÿ‡ฒ๐Ÿ‡ด", - "๐Ÿ‡ฒ๐Ÿ‡ต", - "๐Ÿ‡ฒ๐Ÿ‡ถ", - "๐Ÿ‡ฒ๐Ÿ‡ท", - "๐Ÿ‡ฒ๐Ÿ‡ธ", - "๐Ÿ‡ฒ๐Ÿ‡น", - "๐Ÿ‡ฒ๐Ÿ‡บ", - "๐Ÿ‡ฒ๐Ÿ‡ป", - "๐Ÿ‡ฒ๐Ÿ‡ผ", - "๐Ÿ‡ฒ๐Ÿ‡ฝ", - "๐Ÿ‡ฒ๐Ÿ‡พ", - "๐Ÿ‡ฒ๐Ÿ‡ฟ", - "๐Ÿ‡ณ๐Ÿ‡ฆ", - "๐Ÿ‡ณ๐Ÿ‡จ", - "๐Ÿ‡ณ๐Ÿ‡ช", - "๐Ÿ‡ณ๐Ÿ‡ซ", - "๐Ÿ‡ณ๐Ÿ‡ฌ", - "๐Ÿ‡ณ๐Ÿ‡ฎ", - "๐Ÿ‡ณ๐Ÿ‡ฑ", - "๐Ÿ‡ณ๐Ÿ‡ด", - "๐Ÿ‡ณ๐Ÿ‡ต", - "๐Ÿ‡ณ๐Ÿ‡ท", - "๐Ÿ‡ณ๐Ÿ‡บ", - "๐Ÿ‡ณ๐Ÿ‡ฟ", - "๐Ÿ‡ด๐Ÿ‡ฒ", - "๐Ÿ‡ต๐Ÿ‡ฆ", - "๐Ÿ‡ต๐Ÿ‡ช", - "๐Ÿ‡ต๐Ÿ‡ซ", - "๐Ÿ‡ต๐Ÿ‡ฌ", - "๐Ÿ‡ต๐Ÿ‡ญ", - "๐Ÿ‡ต๐Ÿ‡ฐ", - "๐Ÿ‡ต๐Ÿ‡ฑ", - "๐Ÿ‡ต๐Ÿ‡ฒ", - "๐Ÿ‡ต๐Ÿ‡ณ", - "๐Ÿ‡ต๐Ÿ‡ท", - "๐Ÿ‡ต๐Ÿ‡ธ", - "๐Ÿ‡ต๐Ÿ‡น", - "๐Ÿ‡ต๐Ÿ‡ผ", - "๐Ÿ‡ต๐Ÿ‡พ", - "๐Ÿ‡ถ๐Ÿ‡ฆ", - "๐Ÿ‡ท๐Ÿ‡ช", - "๐Ÿ‡ท๐Ÿ‡ด", - "๐Ÿ‡ท๐Ÿ‡ธ", - "๐Ÿ‡ท๐Ÿ‡บ", - "๐Ÿ‡ท๐Ÿ‡ผ", - "๐Ÿ‡ธ๐Ÿ‡ฆ", - "๐Ÿ‡ธ๐Ÿ‡ง", - "๐Ÿ‡ธ๐Ÿ‡จ", - "๐Ÿ‡ธ๐Ÿ‡ฉ", - "๐Ÿ‡ธ๐Ÿ‡ช", - "๐Ÿ‡ธ๐Ÿ‡ฌ", - "๐Ÿ‡ธ๐Ÿ‡ญ", - "๐Ÿ‡ธ๐Ÿ‡ฎ", - "๐Ÿ‡ธ๐Ÿ‡ฏ", - "๐Ÿ‡ธ๐Ÿ‡ฐ", - "๐Ÿ‡ธ๐Ÿ‡ฑ", - "๐Ÿ‡ธ๐Ÿ‡ฒ", - "๐Ÿ‡ธ๐Ÿ‡ณ", - "๐Ÿ‡ธ๐Ÿ‡ด", - "๐Ÿ‡ธ๐Ÿ‡ท", - "๐Ÿ‡ธ๐Ÿ‡ธ", - "๐Ÿ‡ธ๐Ÿ‡น", - "๐Ÿ‡ธ๐Ÿ‡ป", - "๐Ÿ‡ธ๐Ÿ‡ฝ", - "๐Ÿ‡ธ๐Ÿ‡พ", - "๐Ÿ‡ธ๐Ÿ‡ฟ", - "๐Ÿ‡น๐Ÿ‡ฆ", - "๐Ÿ‡น๐Ÿ‡จ", - "๐Ÿ‡น๐Ÿ‡ฉ", - "๐Ÿ‡น๐Ÿ‡ซ", - "๐Ÿ‡น๐Ÿ‡ฌ", - "๐Ÿ‡น๐Ÿ‡ญ", - "๐Ÿ‡น๐Ÿ‡ฏ", - "๐Ÿ‡น๐Ÿ‡ฐ", - "๐Ÿ‡น๐Ÿ‡ฑ", - "๐Ÿ‡น๐Ÿ‡ฒ", - "๐Ÿ‡น๐Ÿ‡ณ", - "๐Ÿ‡น๐Ÿ‡ด", - "๐Ÿ‡น๐Ÿ‡ท", - "๐Ÿ‡น๐Ÿ‡น", - "๐Ÿ‡น๐Ÿ‡ป", - "๐Ÿ‡น๐Ÿ‡ผ", - "๐Ÿ‡น๐Ÿ‡ฟ", - "๐Ÿ‡บ๐Ÿ‡ฆ", - "๐Ÿ‡บ๐Ÿ‡ฌ", - "๐Ÿ‡บ๐Ÿ‡ฒ", - "๐Ÿ‡บ๐Ÿ‡ณ", - "๐Ÿ‡บ๐Ÿ‡ธ", - "๐Ÿ‡บ๐Ÿ‡พ", - "๐Ÿ‡บ๐Ÿ‡ฟ", - "๐Ÿ‡ป๐Ÿ‡ฆ", - "๐Ÿ‡ป๐Ÿ‡จ", - "๐Ÿ‡ป๐Ÿ‡ช", - "๐Ÿ‡ป๐Ÿ‡ฌ", - "๐Ÿ‡ป๐Ÿ‡ฎ", - "๐Ÿ‡ป๐Ÿ‡ณ", - "๐Ÿ‡ป๐Ÿ‡บ", - "๐Ÿ‡ผ๐Ÿ‡ซ", - "๐Ÿ‡ผ๐Ÿ‡ธ", - "๐Ÿ‡ฝ๐Ÿ‡ฐ", - "๐Ÿ‡พ๐Ÿ‡ช", - "๐Ÿ‡พ๐Ÿ‡น", - "๐Ÿ‡ฟ๐Ÿ‡ฆ", - "๐Ÿ‡ฟ๐Ÿ‡ฒ", - "๐Ÿ‡ฟ๐Ÿ‡ผ", - "๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ", - "๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ", - "๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ" - ]; -} \ No newline at end of file diff --git a/Femto.Modules.Files/Domain/Files/File.cs b/Femto.Modules.Files/Domain/Files/File.cs new file mode 100644 index 0000000..9600ceb --- /dev/null +++ b/Femto.Modules.Files/Domain/Files/File.cs @@ -0,0 +1,8 @@ +namespace Femto.Modules.Files.Domain.Files; + +public class File +{ + Guid Id { get; set; } + + +} \ No newline at end of file diff --git a/Femto.Modules.Files/Femto.Modules.Files.csproj b/Femto.Modules.Files/Femto.Modules.Files.csproj new file mode 100644 index 0000000..17b910f --- /dev/null +++ b/Femto.Modules.Files/Femto.Modules.Files.csproj @@ -0,0 +1,9 @@ +๏ปฟ + + + net9.0 + enable + enable + + + diff --git a/scripts/bump-build-push.sh b/scripts/bump-build-push.sh index 3a3e71a..e0e0b87 100755 --- a/scripts/bump-build-push.sh +++ b/scripts/bump-build-push.sh @@ -52,7 +52,7 @@ docker push "$FULL_IMAGE:latest" # Step 5: Commit version bump & tag echo "โœ… Committing and tagging version bump..." git add "$PROPS_FILE" -git commit -m "v$NEW_VERSION" +git commit -m "chore(release): v$NEW_VERSION" git tag "v$NEW_VERSION" git push origin main git push origin "v$NEW_VERSION" diff --git a/scripts/generate-emojis.js b/scripts/generate-emojis.js deleted file mode 100644 index ba3a3b6..0000000 --- a/scripts/generate-emojis.js +++ /dev/null @@ -1,38 +0,0 @@ -try { - console.error('Downloading emoji-test.txt...'); - const response = await fetch('https://unicode.org/Public/emoji/latest/emoji-test.txt'); - const text = await response.text(); - const emojis = extractEmojis(text); - console.error(`Extracted ${emojis.length} fully-qualified emojis.`); - console.log(` - namespace Femto.Modules.Blog.Emoji; - - internal static partial class AllEmoji - { - public static readonly string[] Emojis = [\n${emojis.map(e => `"${e}"`).join(',\n')}\n]; - } - `) -} catch (err) { - console.error('Error:', err); -} - - -function extractEmojis(text) { - const lines = text.split('\n'); - const emojis = []; - - for (const line of lines) { - if (line.startsWith('#') || line.trim() === '') continue; - - const [codePart, descPart] = line.split(';'); - if (!descPart || !descPart.includes('fully-qualified')) continue; - - const match = line.match(/#\s+(.+?)\s+E\d+\.\d+/); - if (match) { - emojis.push(match[1]); - } - } - - return emojis; -} -