add voice service call service and more

This commit is contained in:
2025-11-25 16:49:18 +03:30
parent 60d20a2734
commit 9ba81d944f
49 changed files with 4428 additions and 19 deletions

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,26 @@
using static GreenHome.Sms.Ippanel.IppanelSmsService;
namespace GreenHome.Sms.Ippanel;
/// <summary>
/// Interface for SMS sending service
/// </summary>
public interface ISmsService
{
/// <summary>
/// Sends a webservice SMS message
/// </summary>
/// <param name="request">SMS request details</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>SMS response with bulk ID</returns>
Task<IppanelApiResponse<IppanelData>?> SendWebserviceSmsAsync(WebserviceSmsRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// Sends a pattern SMS message
/// </summary>
/// <param name="request">Pattern SMS request details</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>SMS response with bulk ID</returns>
Task<IppanelApiResponse<IppanelData>?> SendPatternSmsAsync(PatternSmsRequest request, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,23 @@
namespace GreenHome.Sms.Ippanel;
/// <summary>
/// Configuration options for IPPanel SMS service
/// </summary>
public sealed class IppanelSmsOptions
{
/// <summary>
/// IPPanel API base URL
/// </summary>
public string BaseUrl { get; set; } = "https://edge.ippanel.com/v1";
/// <summary>
/// IPPanel authorization token
/// </summary>
public required string AuthorizationToken { get; set; }
/// <summary>
/// Default sender number for webservice SMS
/// </summary>
public string? DefaultSender { get; set; }
}

View File

@@ -0,0 +1,160 @@
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
namespace GreenHome.Sms.Ippanel;
/// <summary>
/// IPPanel SMS service implementation
/// </summary>
public sealed class IppanelSmsService : ISmsService
{
private readonly HttpClient httpClient;
private readonly IppanelSmsOptions options;
private readonly ILogger<IppanelSmsService> logger;
public IppanelSmsService(
HttpClient httpClient,
IppanelSmsOptions options,
ILogger<IppanelSmsService> logger)
{
this.httpClient = httpClient;
this.options = options;
this.logger = logger;
}
public async Task<IppanelApiResponse<IppanelData>?> SendWebserviceSmsAsync(
WebserviceSmsRequest request,
CancellationToken cancellationToken = default)
{
try
{
// Split recipients by comma and create array
var recipients = request.Recipient
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToList();
var payload = new
{
sending_type = "webservice",
from_number = request.Sender ?? options.DefaultSender,
@params = new
{
recipients
},
message = request.Message
};
var response = await httpClient.PostAsJsonAsync(
"api/send",
payload,
cancellationToken);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<IppanelApiResponse<IppanelData>>(
cancellationToken: cancellationToken);
return result;
}
catch (HttpRequestException ex)
{
logger.LogError(ex, "Error sending webservice SMS to {Recipient}", request.Recipient);
throw;
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error sending webservice SMS");
throw;
}
}
public async Task<IppanelApiResponse<IppanelData>?> SendPatternSmsAsync(
PatternSmsRequest request,
CancellationToken cancellationToken = default)
{
try
{
var payload = new
{
sending_type = "pattern",
code = request.PatternCode,
from_number = request.Originator ?? options.DefaultSender ?? string.Empty,
recipients = request.Recipients,
@params = ToAnonymousObject(request.Variables)
};
var response = await httpClient.PostAsJsonAsync(
"api/send",
payload,
cancellationToken);
//response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<IppanelApiResponse<IppanelData>>(
cancellationToken: cancellationToken);
return result;
}
catch (HttpRequestException ex)
{
logger.LogError(ex, "Error sending pattern SMS to {Recipient}", request.Recipients);
throw;
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error sending pattern SMS");
throw;
}
}
public object ToAnonymousObject(Dictionary<string, string> dictionary)
{
var type = dictionary.GetType();
var obj = new System.Dynamic.ExpandoObject();
var dict = obj as IDictionary<string, object>;
foreach (var item in dictionary)
{
dict.Add(item.Key, item.Value);
}
return obj;
}
public class IppanelApiResponse<T>
{
[JsonPropertyName("data")]
public T? Data { get; set; }
[JsonPropertyName("meta")]
public IppanelMeta Meta { get; set; }
}
public class IppanelMeta
{
[JsonPropertyName("status")]
public bool Status { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
[JsonPropertyName("message_parameters")]
public List<object> MessageParameters { get; set; } = new List<object>();
[JsonPropertyName("message_code")]
public string? MessageCode { get; set; }
[JsonPropertyName("errors")]
public Dictionary<string, List<string>> Errors { get; set; } = new Dictionary<string, List<string>>();
}
// Specific data models for different response types
public class IppanelData
{
[JsonPropertyName("message_outbox_ids")]
public List<long> MessageOutboxIds { get; set; } = new List<long>();
}
}

View File

@@ -0,0 +1,63 @@
namespace GreenHome.Sms.Ippanel;
// SMS DTOs
public sealed class WebserviceSmsRequest
{
/// <summary>
/// Sender number (must be registered in IPPanel)
/// </summary>
public string? Sender { get; set; }
/// <summary>
/// Recipient phone number(s) - comma separated for multiple recipients
/// </summary>
public required string Recipient { get; set; }
/// <summary>
/// SMS message content
/// </summary>
public required string Message { get; set; }
}
public sealed class PatternSmsRequest
{
/// <summary>
/// Pattern code (must be registered in IPPanel)
/// </summary>
public required string PatternCode { get; set; }
/// <summary>
/// Recipient phone number(s) - comma separated for multiple recipients
/// Note: Pattern SMS typically supports single recipient per request
/// </summary>
public required List<string> Recipients { get; set; }
/// <summary>
/// Pattern variables as key-value pairs
/// </summary>
public required Dictionary<string, string> Variables { get; set; }
/// <summary>
/// Optional sender/originator number (if not provided, uses default from configuration)
/// </summary>
public string? Originator { get; set; }
}
public sealed class SmsResponse
{
/// <summary>
/// Bulk ID for tracking the SMS
/// </summary>
public required List<long> BulkId { get; set; }
/// <summary>
/// Response status
/// </summary>
public required string Status { get; set; }
/// <summary>
/// Response message
/// </summary>
public string? Message { get; set; }
}

View File

@@ -0,0 +1,130 @@
# GreenHome.Sms.Ippanel
سرویس ارسال پیامک IPPanel برای پروژه GreenHome
## نصب و راه‌اندازی
### 1. اضافه کردن Reference به پروژه
در فایل `.csproj` پروژه خود:
```xml
<ItemGroup>
<ProjectReference Include="..\GreenHome.Sms.Ippanel\GreenHome.Sms.Ippanel.csproj" />
</ItemGroup>
```
### 2. ثبت سرویس در Program.cs
```csharp
using GreenHome.Sms.Ippanel;
// روش 1: استفاده از Configuration (پیشنهادی)
builder.Services.AddIppanelSms(builder.Configuration);
// روش 2: استفاده از Configuration Section
builder.Services.AddIppanelSms(builder.Configuration.GetSection("IppanelSms"));
// روش 3: تنظیم دستی
builder.Services.AddIppanelSms(options =>
{
options.BaseUrl = "https://edge.ippanel.com/v1";
options.AuthorizationToken = "YOUR_TOKEN_HERE";
options.DefaultSender = "10001001"; // اختیاری
});
```
### 3. تنظیمات در appsettings.json
```json
{
"IppanelSms": {
"BaseUrl": "https://edge.ippanel.com/v1",
"AuthorizationToken": "YOUR_IPPANEL_TOKEN_HERE",
"DefaultSender": "10001001"
}
}
```
## استفاده
### تزریق سرویس
```csharp
public class MyController : ControllerBase
{
private readonly ISmsService smsService;
public MyController(ISmsService smsService)
{
this.smsService = smsService;
}
}
```
### ارسال Webservice SMS
```csharp
var request = new WebserviceSmsRequest
{
Sender = "10001001",
Recipient = "09123456789", // یا چند شماره با کاما: "09123456789,09187654321"
Message = "سلام، این یک پیامک تست است"
};
var result = await smsService.SendWebserviceSmsAsync(request);
// result.BulkId برای ردیابی پیامک استفاده می‌شود
```
### ارسال Pattern SMS
```csharp
var request = new PatternSmsRequest
{
PatternCode = "your-pattern-code",
Recipient = "09123456789",
Variables = new Dictionary<string, string>
{
{ "code", "12345" },
{ "name", "کاربر" }
},
Originator = "10001001" // اختیاری، اگر نباشد از DefaultSender استفاده می‌شود
};
var result = await smsService.SendPatternSmsAsync(request);
```
## ساختار پروژه
```
GreenHome.Sms.Ippanel/
├── IppanelSmsService.cs # پیاده‌سازی سرویس
├── IppanelSmsOptions.cs # تنظیمات
└── ServiceCollectionExtensions.cs # Extension Methods برای ثبت سرویس
```
## نکات مهم
1. **توکن IPPanel**: حتماً توکن معتبر خود را از پنل IPPanel دریافت و در تنظیمات قرار دهید
2. **Webservice SMS**: می‌توانید چند گیرنده را با کاما جدا کنید
3. **Pattern SMS**: معمولاً یک گیرنده در هر درخواست پشتیبانی می‌شود
4. **خطاها**: تمام خطاها لاگ می‌شوند و به صورت Exception پرتاب می‌شوند
## استفاده در پروژه‌های دیگر
این پروژه مستقل است و می‌توانید آن را در هر پروژه .NET دیگری استفاده کنید:
1. پروژه را به Solution خود اضافه کنید
2. Reference را اضافه کنید
3. `GreenHome.Application` را نیز Reference کنید (برای Interface و DTOs)
4. سرویس را ثبت کنید
## تغییر سرویس SMS
اگر بخواهید از سرویس SMS دیگری استفاده کنید:
1. یک پیاده‌سازی جدید از `ISmsService` ایجاد کنید
2. در `Program.cs` به جای `AddIppanelSms` از متد ثبت سرویس جدید استفاده کنید
Interface و DTOs در `GreenHome.Application` باقی می‌مانند و نیازی به تغییر کدهای استفاده‌کننده نیست.

View File

@@ -0,0 +1,101 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace GreenHome.Sms.Ippanel;
/// <summary>
/// Extension methods for registering IPPanel SMS service
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds IPPanel SMS service to the service collection
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="configuration">Configuration section for IppanelSms</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddIppanelSms(
this IServiceCollection services,
IConfiguration configuration)
{
return services.AddIppanelSms(configuration.GetSection("IppanelSms"));
}
/// <summary>
/// Adds IPPanel SMS service to the service collection
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="configurationSection">Configuration section for IppanelSms</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddIppanelSms(
this IServiceCollection services,
IConfigurationSection? configurationSection = null)
{
// Configure options
IppanelSmsOptions? options = null;
if (configurationSection != null)
{
options = configurationSection.Get<IppanelSmsOptions>();
}
if (options == null)
{
throw new InvalidOperationException(
"IppanelSms configuration section is missing. " +
"Please add 'IppanelSms' section to your appsettings.json with 'AuthorizationToken' property.");
}
// Register options as singleton
services.AddSingleton(options);
// Register HttpClient and service
services.AddHttpClient<ISmsService, IppanelSmsService>(client =>
{
// Ensure BaseUrl ends with / for proper relative path handling
var baseUrl = options.BaseUrl.TrimEnd('/') + "/";
client.BaseAddress = new Uri(baseUrl);
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", options.AuthorizationToken);
client.Timeout = TimeSpan.FromSeconds(30);
});
return services;
}
/// <summary>
/// Adds IPPanel SMS service to the service collection with explicit options
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="configureOptions">Action to configure options</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddIppanelSms(
this IServiceCollection services,
Action<IppanelSmsOptions> configureOptions)
{
var options = new IppanelSmsOptions
{
AuthorizationToken = string.Empty // Will be set by configureOptions
};
configureOptions(options);
if (string.IsNullOrWhiteSpace(options.AuthorizationToken))
{
throw new InvalidOperationException(
"IppanelSms AuthorizationToken is required.");
}
// Register options as singleton
services.AddSingleton(options);
// Register HttpClient and service
services.AddHttpClient<ISmsService, IppanelSmsService>(client =>
{
client.BaseAddress = new Uri(options.BaseUrl);
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", options.AuthorizationToken);
client.Timeout = TimeSpan.FromSeconds(30);
});
return services;
}
}