Add webhook
This commit is contained in:
@@ -87,5 +87,18 @@ public class UserController : BaseController
|
|||||||
var updatedUser = await _userService.UpdateAvatarUrl(user, avatarUrl);
|
var updatedUser = await _userService.UpdateAvatarUrl(user, avatarUrl);
|
||||||
return Ok(updatedUser);
|
return Ok(updatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the Telegram channel for the current user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="telegramChannel">The new Telegram channel to set.</param>
|
||||||
|
/// <returns>The updated user with the new Telegram channel.</returns>
|
||||||
|
[HttpPut("telegram-channel")]
|
||||||
|
public async Task<ActionResult<User>> UpdateTelegramChannel([FromBody] string telegramChannel)
|
||||||
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
var updatedUser = await _userService.UpdateTelegramChannel(user, telegramChannel);
|
||||||
|
return Ok(updatedUser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
"Web3Proxy": {
|
"Web3Proxy": {
|
||||||
"BaseUrl": "http://localhost:4111"
|
"BaseUrl": "http://localhost:4111"
|
||||||
},
|
},
|
||||||
|
"N8n": {
|
||||||
|
"WebhookUrl": "https://n8n.kaigen.managing.live/webhook/fa9308b6-983b-42ec-b085-71599d655951"
|
||||||
|
},
|
||||||
"Sentry": {
|
"Sentry": {
|
||||||
"Dsn": "https://698e00d7cb404b049aff3881e5a47f6b@bugcenter.apps.managing.live/1",
|
"Dsn": "https://698e00d7cb404b049aff3881e5a47f6b@bugcenter.apps.managing.live/1",
|
||||||
"MinimumEventLevel": "Error",
|
"MinimumEventLevel": "Error",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Abstractions.Services;
|
namespace Managing.Application.Abstractions.Services;
|
||||||
@@ -12,7 +13,7 @@ public interface IMessengerService
|
|||||||
Task SendPosition(Position position);
|
Task SendPosition(Position position);
|
||||||
Task SendClosingPosition(Position position);
|
Task SendClosingPosition(Position position);
|
||||||
Task SendMessage(string v);
|
Task SendMessage(string v);
|
||||||
Task SendTradeMessage(string message, bool isBadBehavior = false);
|
Task SendTradeMessage(string message, bool isBadBehavior = false, User user = null);
|
||||||
Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null);
|
Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null);
|
||||||
Task SendClosedPosition(string address, Trade oldTrade);
|
Task SendClosedPosition(string address, Trade oldTrade);
|
||||||
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
|
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ public interface IUserService
|
|||||||
Task<User> GetUserByAddressAsync(string address);
|
Task<User> GetUserByAddressAsync(string address);
|
||||||
Task<User> UpdateAgentName(User user, string agentName);
|
Task<User> UpdateAgentName(User user, string agentName);
|
||||||
Task<User> UpdateAvatarUrl(User user, string avatarUrl);
|
Task<User> UpdateAvatarUrl(User user, string avatarUrl);
|
||||||
|
Task<User> UpdateTelegramChannel(User user, string telegramChannel);
|
||||||
User GetUser(string name);
|
User GetUser(string name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Managing.Domain.Users;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions.Services;
|
||||||
|
|
||||||
|
public interface IWebhookService
|
||||||
|
{
|
||||||
|
Task SendTradeNotification(User user, string message, bool isBadBehavior = false);
|
||||||
|
}
|
||||||
@@ -1163,7 +1163,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
{
|
{
|
||||||
if (!Config.IsForBacktest)
|
if (!Config.IsForBacktest)
|
||||||
{
|
{
|
||||||
await MessengerService.SendTradeMessage(message, isBadBehavior);
|
await MessengerService.SendTradeMessage(message, isBadBehavior, Account?.User);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1322,13 +1322,14 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
var protectedIsForBacktest = Config.IsForBacktest;
|
var protectedIsForBacktest = Config.IsForBacktest;
|
||||||
var protectedName = Config.Name;
|
var protectedName = Config.Name;
|
||||||
|
|
||||||
// Log the configuration update
|
// Log the configuration update (before changing anything)
|
||||||
await LogInformation($"Updating bot configuration. Previous config: " +
|
await LogInformation("⚙️ **Configuration Update**\n" +
|
||||||
$"Balance: {Config.BotTradingBalance}, " +
|
"📊 **Previous Settings:**\n" +
|
||||||
$"MaxTime: {Config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
|
$"💰 Balance: ${Config.BotTradingBalance:F2}\n" +
|
||||||
$"FlipOnlyProfit: {Config.FlipOnlyWhenInProfit}, " +
|
$"⏱️ Max Time: {(Config.MaxPositionTimeHours?.ToString() + "h" ?? "Disabled")}\n" +
|
||||||
$"Cooldown: {Config.CooldownPeriod}, " +
|
$"📈 Flip Only in Profit: {(Config.FlipOnlyWhenInProfit ? "✅" : "❌")}\n" +
|
||||||
$"MaxLoss: {Config.MaxLossStreak}");
|
$"⏳ Cooldown: {Config.CooldownPeriod} candles\n" +
|
||||||
|
$"📉 Max Loss Streak: {Config.MaxLossStreak}");
|
||||||
|
|
||||||
// Update the configuration
|
// Update the configuration
|
||||||
Config = newConfig;
|
Config = newConfig;
|
||||||
@@ -1351,12 +1352,13 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
LoadScenario(Config.ScenarioName);
|
LoadScenario(Config.ScenarioName);
|
||||||
}
|
}
|
||||||
|
|
||||||
await LogInformation($"Bot configuration updated successfully. New config: " +
|
await LogInformation("✅ **Configuration Applied**\n" +
|
||||||
$"Balance: {Config.BotTradingBalance}, " +
|
"🔧 **New Settings:**\n" +
|
||||||
$"MaxTime: {Config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
|
$"💰 Balance: ${Config.BotTradingBalance:F2}\n" +
|
||||||
$"FlipOnlyProfit: {Config.FlipOnlyWhenInProfit}, " +
|
$"⏱️ Max Time: {(Config.MaxPositionTimeHours?.ToString() + "h" ?? "Disabled")}\n" +
|
||||||
$"Cooldown: {Config.CooldownPeriod}, " +
|
$"📈 Flip Only in Profit: {(Config.FlipOnlyWhenInProfit ? "✅" : "❌")}\n" +
|
||||||
$"MaxLoss: {Config.MaxLossStreak}");
|
$"⏳ Cooldown: {Config.CooldownPeriod} candles\n" +
|
||||||
|
$"📉 Max Loss Streak: {Config.MaxLossStreak}");
|
||||||
|
|
||||||
// Save the updated configuration as backup
|
// Save the updated configuration as backup
|
||||||
if (!Config.IsForBacktest)
|
if (!Config.IsForBacktest)
|
||||||
|
|||||||
@@ -2,16 +2,21 @@
|
|||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
|
|
||||||
namespace Managing.Application.Shared;
|
namespace Managing.Application.Shared;
|
||||||
|
|
||||||
public class MessengerService : IMessengerService
|
public class MessengerService : IMessengerService
|
||||||
{
|
{
|
||||||
private readonly IDiscordService _discordService;
|
private readonly IDiscordService _discordService;
|
||||||
|
private readonly IWebhookService _webhookService;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
|
||||||
public MessengerService(IDiscordService discordService)
|
public MessengerService(IDiscordService discordService, IWebhookService webhookService, IUserService userService)
|
||||||
{
|
{
|
||||||
_discordService = discordService;
|
_discordService = discordService;
|
||||||
|
_webhookService = webhookService;
|
||||||
|
_userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendClosedPosition(string address, Trade oldTrade)
|
public async Task SendClosedPosition(string address, Trade oldTrade)
|
||||||
@@ -50,9 +55,24 @@ public class MessengerService : IMessengerService
|
|||||||
await _discordService.SendSignal(message, exchange, ticker, direction, timeframe);
|
await _discordService.SendSignal(message, exchange, ticker, direction, timeframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendTradeMessage(string message, bool isBadBehavior = false)
|
public async Task SendTradeMessage(string message, bool isBadBehavior = false, User user = null)
|
||||||
{
|
{
|
||||||
await _discordService.SendTradeMessage(message, isBadBehavior);
|
// Always send to Discord
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _discordService.SendTradeMessage(message, isBadBehavior);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user is provided, also send to webhook
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
user = _userService.GetUser(user.Name);
|
||||||
|
await _webhookService.SendTradeNotification(user, message, isBadBehavior);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendBestTraders(List<Trader> traders)
|
public async Task SendBestTraders(List<Trader> traders)
|
||||||
|
|||||||
62
src/Managing.Application/Shared/WebhookService.cs
Normal file
62
src/Managing.Application/Shared/WebhookService.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Domain.Users;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Managing.Application.Shared;
|
||||||
|
|
||||||
|
public class WebhookService : IWebhookService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly ILogger<WebhookService> _logger;
|
||||||
|
|
||||||
|
public WebhookService(HttpClient httpClient, IConfiguration configuration, ILogger<WebhookService> logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_configuration = configuration;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendTradeNotification(User user, string message, bool isBadBehavior = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get the n8n webhook URL from configuration
|
||||||
|
var webhookUrl = _configuration["N8n:WebhookUrl"];
|
||||||
|
if (string.IsNullOrEmpty(webhookUrl))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("N8n webhook URL not configured, skipping webhook notification");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the payload for n8n webhook
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
message = message,
|
||||||
|
isBadBehavior = isBadBehavior,
|
||||||
|
timestamp = DateTime.UtcNow,
|
||||||
|
type = "trade_notification",
|
||||||
|
telegramChannel = user.TelegramChannel
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the webhook notification
|
||||||
|
var response = await _httpClient.PostAsJsonAsync(webhookUrl, payload);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Successfully sent webhook notification for user {user.Name}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Failed to send webhook notification. Status: {response.StatusCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Error sending webhook notification for user {user.Name}: {ex.Message}");
|
||||||
|
// Don't throw - webhook failures shouldn't break the main flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -165,4 +165,21 @@ public class UserService : IUserService
|
|||||||
await _userRepository.UpdateUser(user);
|
await _userRepository.UpdateUser(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<User> UpdateTelegramChannel(User user, string telegramChannel)
|
||||||
|
{
|
||||||
|
// Validate Telegram channel format (must start with @ and contain only allowed characters)
|
||||||
|
if (!string.IsNullOrEmpty(telegramChannel))
|
||||||
|
{
|
||||||
|
string pattern = @"^@[a-zA-Z0-9_]{5,32}$";
|
||||||
|
if (!Regex.IsMatch(telegramChannel, pattern))
|
||||||
|
{
|
||||||
|
throw new Exception("Invalid Telegram channel format. Must start with @ and be 5-32 characters long, containing only letters, numbers, and underscores.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.TelegramChannel = telegramChannel;
|
||||||
|
await _userRepository.UpdateUser(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -94,6 +94,7 @@ public static class ApiBootstrap
|
|||||||
services.AddSingleton<IWorkerService, WorkerService>();
|
services.AddSingleton<IWorkerService, WorkerService>();
|
||||||
services.AddTransient<IPrivyService, PrivyService>();
|
services.AddTransient<IPrivyService, PrivyService>();
|
||||||
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
||||||
|
services.AddTransient<IWebhookService, WebhookService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,9 +131,13 @@ public static class WorkersBootstrap
|
|||||||
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
|
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
|
||||||
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
||||||
|
|
||||||
|
// Http Clients
|
||||||
|
services.AddHttpClient();
|
||||||
|
|
||||||
// Messengers
|
// Messengers
|
||||||
services.AddSingleton<IMessengerService, MessengerService>();
|
services.AddSingleton<IMessengerService, MessengerService>();
|
||||||
services.AddSingleton<IDiscordService, DiscordService>();
|
services.AddSingleton<IDiscordService, DiscordService>();
|
||||||
|
services.AddSingleton<IWebhookService, WebhookService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ public class User
|
|||||||
public List<Account> Accounts { get; set; }
|
public List<Account> Accounts { get; set; }
|
||||||
public string AgentName { get; set; }
|
public string AgentName { get; set; }
|
||||||
public string AvatarUrl { get; set; }
|
public string AvatarUrl { get; set; }
|
||||||
|
public string TelegramChannel { get; set; }
|
||||||
}
|
}
|
||||||
@@ -9,4 +9,5 @@ public class UserDto : Document
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string AgentName { get; set; }
|
public string AgentName { get; set; }
|
||||||
public string AvatarUrl { get; set; }
|
public string AvatarUrl { get; set; }
|
||||||
|
public string TelegramChannel { get; set; }
|
||||||
}
|
}
|
||||||
@@ -534,6 +534,7 @@ public static class MongoMappers
|
|||||||
Name = user.Name,
|
Name = user.Name,
|
||||||
AgentName = user.AgentName,
|
AgentName = user.AgentName,
|
||||||
AvatarUrl = user.AvatarUrl,
|
AvatarUrl = user.AvatarUrl,
|
||||||
|
TelegramChannel = user.TelegramChannel
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,6 +545,7 @@ public static class MongoMappers
|
|||||||
Name = user.Name,
|
Name = user.Name,
|
||||||
AgentName = user.AgentName,
|
AgentName = user.AgentName,
|
||||||
AvatarUrl = user.AvatarUrl,
|
AvatarUrl = user.AvatarUrl,
|
||||||
|
TelegramChannel = user.TelegramChannel
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2447,6 +2447,45 @@ export class UserClient extends AuthorizedApiBase {
|
|||||||
}
|
}
|
||||||
return Promise.resolve<User>(null as any);
|
return Promise.resolve<User>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user_UpdateTelegramChannel(telegramChannel: string): Promise<User> {
|
||||||
|
let url_ = this.baseUrl + "/User/telegram-channel";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
const content_ = JSON.stringify(telegramChannel);
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
body: content_,
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||||
|
return this.http.fetch(url_, transformedOptions_);
|
||||||
|
}).then((_response: Response) => {
|
||||||
|
return this.processUser_UpdateTelegramChannel(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processUser_UpdateTelegramChannel(response: Response): Promise<User> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as User;
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<User>(null as any);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkflowClient extends AuthorizedApiBase {
|
export class WorkflowClient extends AuthorizedApiBase {
|
||||||
@@ -2644,6 +2683,7 @@ export interface User {
|
|||||||
accounts?: Account[] | null;
|
accounts?: Account[] | null;
|
||||||
agentName?: string | null;
|
agentName?: string | null;
|
||||||
avatarUrl?: string | null;
|
avatarUrl?: string | null;
|
||||||
|
telegramChannel?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Balance {
|
export interface Balance {
|
||||||
|
|||||||
Reference in New Issue
Block a user