I have a Microsoft Chatbot C# code,which has a TimerCallback function, which runs after some inactive time by user. This function gets hit when running code locally, but the same function does not get hit, when deployed on Azure environment.
Appologies for too much logging lines in the code, it was just to verify that function gets hit on deployed environment or not.
Here is the whole code:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Lebara.Crm.Bot.Core.Data;
using Lebara.Crm.Bot.Core.ServiceContracts;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.Extensions.Logging;
namespace Lebara.Crm.Bot.Services
{
public class ChatSessionTimeoutMiddleware : ActivityHandler, IMiddleware
{
private readonly IDistributedCache _distributedCache;
private readonly string _cachekey;
private readonly BotOptions _botOptions;
private readonly ISystemMessageSender _systemMessageSender;
private readonly CustomConversationStateAccessors _customConversationStateAccessors;
private readonly ITelemetryManager _telemetryManager;
private readonly ISessionHandler _sessionHandler;
private readonly IServiceProvider _serviceProvider;
private static ConcurrentDictionary<string, Timer> _sessionTimers = new ConcurrentDictionary<string, Timer>();
private readonly IBotFrameworkHttpAdapter _adapter;
private readonly string _appId;
private readonly ConcurrentDictionary<string, ConversationReference> _conversationReferences;
private string ObjectKey;
private readonly TelemetryClient telemetry = new TelemetryClient();
private readonly ILogger<ChatSessionTimeoutMiddleware> _logger;
// Timer related variables, made global, to avoid collected by Garbage Collector as they were created in memory previously.
private Timer _warningTimer = null;
private Timer _warningTimerGCIssue = null;
private Timer _timer = null;
private Timer _timerGCIssue = null;
public ChatSessionTimeoutMiddleware(
IDistributedCache distributedCache,
IConfiguration configuration,
IOptions<BotOptions> options,
ISystemMessageSender systemMessageSender,
CustomConversationStateAccessors customConversationStateAccessors,
ITelemetryManager telemetryManager,
ISessionHandler sessionHandler,
IServiceProvider serviceProvider,
IBotFrameworkHttpAdapter adapter,
ConcurrentDictionary<string, ConversationReference> conversationReferences,
ILogger<ChatSessionTimeoutMiddleware> logger)
{
_cachekey = $"{configuration["RedisCachingRoot"]}session-timeout:";
_distributedCache = distributedCache;
_botOptions = options.Value;
_systemMessageSender = systemMessageSender;
_customConversationStateAccessors = customConversationStateAccessors;
_telemetryManager = telemetryManager;
_sessionHandler = sessionHandler;
_serviceProvider = serviceProvider;
_adapter = adapter;
_conversationReferences = conversationReferences;
_appId = configuration["MicrosoftAppId"] ?? string.Empty;
_logger = logger;
}
private void AddConversationReference(Activity activity)
{
var conversationReference = activity.GetConversationReference();
_conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate nextTurn, CancellationToken cancellationToken)
{
_logger.LogDebug("chat session timeout middleware");
//telemetry.TrackEvent("ChatSessionMiddleware - OnTurnAsync");
//telemetry.TrackTrace("ChatSessionMiddleware - OnTurnAsync", SeverityLevel.Warning, null);
var customConversationState = await _customConversationStateAccessors.CustomConversationState.GetAsync(turnContext, () => new CustomConversationState());
var hasChatSessionRunning = await _sessionHandler.HasRunningSessionAsync(turnContext.Activity.Conversation.Id);
if (turnContext.Activity.Type == ActivityTypes.Message
&& !string.IsNullOrEmpty(turnContext.Activity.Text)
&& !hasChatSessionRunning)
{
_logger.LogDebug("chatsessiontimeout OnTurnAsync if statement");
var key = _cachekey + turnContext.Activity.Conversation.Id;
_logger.LogDebug($"Key {key}");
var warningKey = "warning_" + key;
_logger.LogDebug($"WarningKey {warningKey}");
var period = _botOptions.InactivityPeriod;
_logger.LogDebug($"chatsessiontimeout period {period}");
var warningPeriod = _botOptions.WarningInactivityPeriod;
_logger.LogDebug($"chatsessiontimeout warningPeriod {warningPeriod}");
var cacheOptions = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = period.Add(period)
};
_logger.LogDebug($"cacheOptions {cacheOptions}");
AddConversationReference(turnContext.Activity as Activity);
await _distributedCache.SetStringAsync(key, JsonConvert.SerializeObject(DateTime.Now.Add(period)), cacheOptions);
await _distributedCache.SetStringAsync(warningKey, JsonConvert.SerializeObject(DateTime.Now.Add(warningPeriod)), cacheOptions);
var timerPeriod = period.Add(TimeSpan.FromSeconds(1));
var warningTimePeriod = warningPeriod.Add(TimeSpan.FromSeconds(1));
_warningTimer = null;
_warningTimerGCIssue = null;
_sessionTimers.TryGetValue(warningKey, out _warningTimer);
_logger.LogDebug($"warningTimer {_warningTimer}");
if (_warningTimer == null)
{
_logger.LogDebug("warningTimer is null");
_warningTimerGCIssue = new Timer(new TimerCallback(WarningCallback), (warningKey, turnContext), warningTimePeriod, warningTimePeriod);
_warningTimer = _sessionTimers.GetOrAdd(warningKey, _warningTimerGCIssue);
}
_timer = null;
_timerGCIssue = null;
_sessionTimers.TryGetValue(key, out _timer);
_logger.LogDebug($"timer {_timer}");
if (_timer == null)
{
_logger.LogDebug("timer is null");
_logger.LogDebug($"key {key}");
_timerGCIssue = new Timer(new TimerCallback(Callback), (key, turnContext), timerPeriod, timerPeriod);
_timer = _sessionTimers.GetOrAdd(key, _timerGCIssue);
}
_warningTimer.Change(warningTimePeriod, warningTimePeriod);
_logger.LogDebug("chatSessionTimeoutMiddleware timer change");
_timer.Change(timerPeriod, timerPeriod);
}
_logger.LogDebug("chatSessionTimeoutMiddleware nextturn");
await nextTurn(cancellationToken).ConfigureAwait(false);
}
private async void Callback(object target)
{
//telemetry.TrackEvent("ChatSessionMiddleware - InactivityCallback");
_logger.LogDebug("ChatSessionMiddleware InactivityCallback");
var tuple = ((string, ITurnContext))target;
ObjectKey = tuple.Item1;
var turnContext = tuple.Item2;
foreach (var conversationReference in _conversationReferences.Values)
{
_logger.LogDebug("ChatSessionMiddleware InactivityCallback for loop");
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, EndOfChatCallback, default(CancellationToken));
}
}
private async void WarningCallback(object target)
{
//telemetry.TrackEvent("ChatSessionMiddleware - WarningCallback");
_logger.LogDebug("ChatSessionMiddleware WarningCallback");
var tuple = ((string, ITurnContext))target;
ObjectKey = tuple.Item1;
var turnContext = tuple.Item2;
foreach (var conversationReference in _conversationReferences.Values)
{
_logger.LogDebug("ChatSessionMiddleware WarningCallback for loop");
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, WarningMessageCallback, default(CancellationToken));
}
}
private async Task WarningMessageCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
//telemetry.TrackEvent("ChatSessionMiddleware - WarningMessageCallback");
_logger.LogDebug("ChatSessionMiddleware WarningMessageCallback");
var customConversationState = await _customConversationStateAccessors.CustomConversationState.GetAsync(turnContext, () => new CustomConversationState());
void DisposeTimer()
{
bool found = _sessionTimers.TryRemove(ObjectKey, out var timer);
if (found)
{
timer.Dispose();
timer = null;
}
}
var json = await _distributedCache.GetStringAsync(ObjectKey);
var hasChatSessionRunning = await _sessionHandler.HasRunningSessionAsync(turnContext.Activity.Conversation.Id);
if (hasChatSessionRunning)
{
DisposeTimer();
return;
}
if (!string.IsNullOrEmpty(json))
{
var sessionEnd = JsonConvert.DeserializeObject<DateTime>(json);
if (DateTime.Now >= sessionEnd)
{
//telemetry.TrackEvent("ChatSessionMiddleware - SendingWarningMessage");
_logger.LogDebug("ChatSessionMiddleware SendingWarningMessage");
await _systemMessageSender.SendSystemMessage(turnContext, customConversationState, turnContext.Activity, ResourceIds.BotWarningEndOfChat);
}
}
DisposeTimer();
}
private async Task EndOfChatCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
//telemetry.TrackEvent("ChatSessionMiddleware - EndOfChatCallback");
_logger.LogDebug("ChatSessionMiddleware EndOfChatCallback");
var chatSdk = (IChatProvider)_serviceProvider.GetService(typeof(IChatProvider));
var customConversationState = await _customConversationStateAccessors.CustomConversationState.GetAsync(turnContext, () => new CustomConversationState());
void DisposeTimer()
{
bool found = _sessionTimers.TryRemove(ObjectKey, out var timer);
if (found)
{
timer.Dispose();
timer = null;
}
}
var json = await _distributedCache.GetStringAsync(ObjectKey);
var hasChatSessionRunning = await _sessionHandler.HasRunningSessionAsync(turnContext.Activity.Conversation.Id);
if (hasChatSessionRunning)
{
DisposeTimer();
return;
}
if (!string.IsNullOrEmpty(json))
{
var sessionEnd = JsonConvert.DeserializeObject<DateTime>(json);
if (DateTime.Now >= sessionEnd)
{
var parts = ObjectKey.Split(new char[] { ':' });
var dict = new Dictionary<string, string>
{
{"EndTime", json },
{"State", JsonConvert.SerializeObject(customConversationState) }
};
_telemetryManager.TrackEvent("AutomaticChatClosing", parts[parts.Length - 1], dict);
DisposeTimer();
//telemetry.TrackEvent("ChatSessionMiddleware - SendingEndOfChatMessage");
_logger.LogDebug("ChatSessionMiddleware SendingEndOfChatMessage");
await _systemMessageSender.SendSystemMessage(turnContext, customConversationState, turnContext.Activity, ResourceIds.BotAutomaticEndOfChat);
await Task.Delay(2000);
await chatSdk.EndChat(customConversationState.ChatContext, turnContext);
}
}
else
{
DisposeTimer();
}
}
}
}
The code creating issue/ or not getting hit:
if (_warningTimer == null)
{
_logger.LogDebug("warningTimer is null");
_warningTimerGCIssue = new Timer(new TimerCallback(WarningCallback), (warningKey, turnContext), warningTimePeriod, warningTimePeriod);
_warningTimer = _sessionTimers.GetOrAdd(warningKey, _warningTimerGCIssue);
}
the above mentioned part should call the WarningCallback function after a specific warning time, it does call it successfully while running the code locally, but does not call on deployed environment.
Related
At the time of the user's connection, everything is going well. When I have a product update (it becomes available for sale), the OnProductStartSales event is called.
But there is one problem, the message does not come to the client because the list of clients in the hub is disposed.
Here is the code of my hub.
public class SalesHub : Hub
{
private readonly ProductDatabaseListener _listener;
public SalesHub(ProductDatabaseListener listener)
{
_listener = listener ?? throw new ArgumentNullException(nameof(listener));
_listener.OnProductStartSales += (s, p) => ProductStartSales(p);
_listener.OnProductDataChanged += (s, p) => ProductDataChanged(p);
}
public async Task ListenProduct(string productId)
{
await this.Groups.AddToGroupAsync(Context.ConnectionId, productId);
}
private async Task ProductStartSales(Product product)
{
await this.Clients.Group(product.Id).SendAsync("StartSales", product.Id);
// await this.Clients.All.SendAsync("StartSales", product.Id);
}
private async Task ProductDataChanged(Product product)
{
await this.Clients.Group(product.Id).SendAsync("DataChanged", product);
}
}
Here is the code of listener.
public class ProductDatabaseListener
{
private readonly IRepository<Product> _repository;
private readonly object _locker = new object();
public ProductDatabaseListener(IServiceProvider serviceProvider)
{
using (var scope = serviceProvider.CreateScope())
{
_repository = scope.ServiceProvider.GetRequiredService<IRepository<Product>>() ?? throw new ArgumentNullException(nameof(_repository));
}
}
public event EventHandler<Product> OnProductStartSales;
public event EventHandler<Product> OnProductDataChanged;
// Need better performance...
public async Task ListenMongo()
{
while (true)
{
var products = await _repository.GetRange(0, int.MaxValue);
var date = DateTime.Now;
List<Task> tasks = new List<Task>();
foreach (var product in products)
{
if (product.IsSalesStart)
{
continue;
}
if (product.StartOfSales <= date)
{
product.IsSalesStart = true;
OnProductStartSales?.Invoke(this, product);
tasks.Add(_repository.Update(product));
}
}
Task.WaitAll(tasks.ToArray());
await Task.Delay(1000);
}
}
}
Here is the client code
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/salesHub").build();
connection.on("ReceiveMessage", function (id) {
var li = document.createElement("li")
document.getElementById("fromHub").appendChild(li)
li.textContent = id;
});
connection.on("startSales", function (id) {
var productId = document.getElementById("objectId").getAttribute("value");
if (productId == id) {
var button = document.getElementById("buy")
button.hidden = false
}
});
connection.logging = true;
connection.start().then(function () {
var productId = document.getElementById("objectId").getAttribute("value");
connection.invoke("ListenProduct", productId).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
}).catch(function (err) {
return console.error(err.toString());
});
This question already has answers here:
The best overloaded method match for System.Threading.Timer.Timer() has some invalid arguments
(3 answers)
Closed 3 years ago.
I'm trying to run function getOrg though hosted services but some how its not working I'm not sure what I'm doing wrong.
Error:
Argument 1: cannot convert from 'method group' to 'TimerCallback'
(CS1503)
public class TokenService : IHostedService
{
public IConfiguration _Configuration { get; }
protected IMemoryCache _cache;
private Timer _timer;
public IHttpClientFactory _clientFactory;
private readonly IServiceScopeFactory _scopeFactory;
public TokenService(IConfiguration configuration, IMemoryCache memoryCache, IHttpClientFactory clientFactory, IServiceScopeFactory scopeFactory)
{
_Configuration = configuration;
_cache = memoryCache;
_clientFactory = clientFactory;
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(getOrg, null, 0, 1000); // getting error here
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//Timer does not have a stop.
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public async Task getOrg()
{
var request = new HttpRequestMessage(HttpMethod.Get, "organizations");
var response = await _client_NP.SendAsync(request);
var json = await response.Content.ReadAsStringAsync();
OrganizationsClass.OrgsRootObject model = JsonConvert.DeserializeObject<OrganizationsClass.OrgsRootObject>(json);
using (var scope = _scopeFactory.CreateScope())
{
var _DBcontext = scope.ServiceProvider.GetRequiredService<DBContext>();
foreach (var item in model.resources)
{
var g = Guid.Parse(item.guid);
var x = _DBcontext.Organizations.FirstOrDefault(o => o.OrgGuid == g);
if (x == null)
{
_DBcontext.Organizations.Add(new Organizations
{
OrgGuid = g,
Name = item.name,
CreatedAt = item.created_at,
UpdatedAt = item.updated_at,
Timestamp = DateTime.Now,
Foundation = 3
});
}
else if (x.UpdatedAt != item.updated_at)
{
x.CreatedAt = item.created_at;
x.UpdatedAt = item.updated_at;
x.Timestamp = DateTime.Now;
}
}
await getSpace();
await _DBcontext.SaveChangesAsync();
}
}
}
TimerCallback takes on object parameter for state. Try changing getOrg to:
public async void getOrg(object state)
You are providing wrong parameters to System.Threading.Timer constructor.
The first parameter should be a delegate type (instead of getOrg):
public delegate void TimerCallback(object state);
So add a delegate to your code:
private void TimerProc(object state)
{
}
Change the constructor:
_timer = new Timer(TimerProc, null, 0, 1000); // getting error here
I have a decorator class that adds the ability to rate limit http requests for hitting an API:
public class RateLimitedHttpClient : IHttpClient
{
public RateLimitedHttpClient(System.Net.Http.HttpClient client)
{
_client = client;
_client.Timeout = TimeSpan.FromMinutes(30);
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
}
public async Task<string> ReadAsync(string url)
{
if (!_sw.IsRunning)
_sw.Start();
await Delay();
using var response = await _client.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
private async Task Delay()
{
var totalElapsed = GetTimeElapsedSinceLastRequest();
while (totalElapsed < MinTimeBetweenRequests)
{
await Task.Delay(MinTimeBetweenRequests - totalElapsed);
totalElapsed = GetTimeElapsedSinceLastRequest();
};
_timeElapsedOfLastHttpRequest = (int)_sw.Elapsed.TotalMilliseconds;
}
private int GetTimeElapsedSinceLastRequest()
{
return (int)_sw.Elapsed.TotalMilliseconds - _timeElapsedOfLastHttpRequest;
}
private readonly System.Net.Http.HttpClient _client;
private readonly Stopwatch _sw = new Stopwatch();
private int _timeElapsedOfLastHttpRequest;
private const int MinTimeBetweenRequests = 100;
}
However, I'm noticing that at the line indicated below I get a message in the debugger that says that the next statement will execute when the current thread returns.
var epsDataPoints = await _downloader.GetEPSData(cik);
foreach (var eps in epsDataPoints)
{
// getting VS2019 debugger message here!
// assuming that the line above is deadlocking....
Console.WriteLine($"{cik} :: {eps.DateInterval} :: {eps.EPS}");
}
When I open up the Task Manager, the network bandwidth goes to 0 and everything stops with the application other than it sitting at the Console.WriteLine above.
The EPSDownloader class that uses the IHttpClient is below:
public class EPSDownloader
{
public EPSDownloader(IHttpClient client)
{
_client = client;
}
public async Task<IEnumerable<EPSDataPoint>> GetEPSData(int cik)
{
var epsDataPoints = new Dictionary<LocalDate, EPSDataPoint>();
var reportLinks = await GetReportLinks(cik);
foreach (var reportLink in reportLinks)
{
var xbrlLink = await GetXBRLLink(reportLink);
var epsData = await GetEPSData(xbrlLink);
foreach (var eps in epsData)
{
if (!epsDataPoints.ContainsKey(eps.DateInterval.End))
epsDataPoints.Add(eps.DateInterval.End, eps);
}
}
var list = epsDataPoints.OrderBy(d => d.Key).Select(e => e.Value).ToList();
return list;
}
private async Task<IList<string>> GetReportLinks(int cik)
{
// move this url elsewhere
var url = "https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=" + cik +
"&type=10&dateb=&owner=include&count=100";
var srBody = await _client.ReadAsync(url); // consider moving this to srPage
var srPage = new SearchResultsPage(srBody);
return srPage.GetAllReportLinks();
}
private async Task<string> GetXBRLLink(string link)
{
var url = SEC_HOSTNAME + link;
var fdBody = await _client.ReadAsync(url);
var fdPage = new FilingDetailsPage(fdBody);
return fdPage.GetInstanceDocumentLink();
}
private async Task<IList<EPSDataPoint>> GetEPSData(string xbrlLink)
{
var xbrlBody = await _client.ReadAsync(SEC_HOSTNAME + xbrlLink);
var xbrlDoc = new XBRLDocument(xbrlBody);
return xbrlDoc.GetAllQuarterlyEPSData();
}
private readonly IHttpClient _client;
private const string SEC_HOSTNAME = "https://www.sec.gov";
}
It seems to be that there is an issue with HttpClient, but I don't know why. No exceptions are being thrown, but I do occasionally see that threads have exited with code 0.
Update: I actually restarted my computer while the application was running and it began running fine again for about 20 minutes before the Task Manager showed 0 for the network speed and the application just sat there.
I'm trying make a telegram bot with reminder. I'm using Telegram.Bot 14.10.0, Quartz 3.0.7, .net core 2.0. The first version should : get message "reminder" from telegram, create job (using Quartz) and send meaasage back in 5 seconds.
My console app with DI looks like:
Program.cs
static IBot _botClient;
public static void Main(string[] args)
{
// it doesn't matter
var servicesProvider = BuildDi(connecionString, section);
_botClient = servicesProvider.GetRequiredService<IBot>();
_botClient.Start(appModel.BotConfiguration.BotToken, httpProxy);
var reminderJob = servicesProvider.GetRequiredService<IReminderJob>();
reminderJob.Bot = _botClient;
Console.ReadLine();
_botClient.Stop();
// it doesn't matter
}
private static ServiceProvider BuildDi(string connectionString, IConfigurationSection section)
{
var rJob = new ReminderJob();
var sCollection = new ServiceCollection()
.AddSingleton<IBot, Bot>()
.AddSingleton<ReminderJob>(rJob)
.AddSingleton<ISchedulerBot>(s =>
{
var schedBor = new SchedulerBot();
schedBor.StartScheduler();
return schedBor;
});
return sCollection.BuildServiceProvider();
}
Bot.cs
public class Bot : IBot
{
static TelegramBotClient _botClient;
public void Start(string botToken, WebProxy httpProxy)
{
_botClient = new TelegramBotClient(botToken, httpProxy);
_botClient.OnReceiveError += BotOnReceiveError;
_botClient.OnMessage += Bot_OnMessage;
_botClient.StartReceiving();
}
private static async void Bot_OnMessage(object sender, MessageEventArgs e)
{
var me = wait _botClient.GetMeAsync();
if (e.Message.Text == "reminder")
{
var map= new Dictionary<string, object> { { ReminderJobConst.ChatId, e.Message.Chat.Id.ToString() }, { ReminderJobConst.HomeWordId, 1} };
var job = JobBuilder.Create<ReminderJob>().WithIdentity($"{prefix}{rnd.Next()}").UsingJobData(new JobDataMap(map)).Build();
var trigger = TriggerBuilder.Create().WithIdentity($"{prefix}{rnd.Next()}").StartAt(DateTime.Now.AddSeconds(5).ToUniversalTime())
.Build();
await bot.Scheduler.ScheduleJob(job, trigger);
}
}
}
Quartz.net not allow use constructor with DI. That's why I'm trying to create property with DI.
ReminderJob.cs
public class ReminderJob : IJob
{
static IBot _bot;
public IBot Bot { get; set; }
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await System.Console.Out.WriteLineAsync("HelloJob is executing.");
}
}
How can I pass _botClient to reminderJob in Program.cs?
If somebody looks for answer, I have one:
Program.cs (in Main)
var schedBor = servicesProvider.GetRequiredService<ISchedulerBot>();
var logger = servicesProvider.GetRequiredService<ILogger<DIJobFactory>>();
schedBor.StartScheduler();
schedBor.Scheduler.JobFactory = new DIJobFactory(logger, servicesProvider);
DIJobFactory.cs
public class DIJobFactory : IJobFactory
{
static ILogger<DIJobFactory> _logger;
static IServiceProvider _serviceProvider;
public DIJobFactory(ILogger<DIJobFactory> logger, IServiceProvider sp)
{
_logger = logger;
_serviceProvider = sp;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
try
{
_logger.LogDebug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
if (jobType == null)
{
throw new ArgumentNullException(nameof(jobType), "Cannot instantiate null");
}
return (IJob)_serviceProvider.GetRequiredService(jobType);
}
catch (Exception e)
{
SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
throw se;
}
}
// get from https://github.com/quartznet/quartznet/blob/139aafa23728892b0a5ebf845ce28c3bfdb0bfe8/src/Quartz/Simpl/SimpleJobFactory.cs
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
ReminderJob.cs
public interface IReminderJob : IJob
{
}
public class ReminderJob : IReminderJob
{
ILogger<ReminderJob> _logger;
IBot _bot;
public ReminderJob(ILogger<ReminderJob> logger, IBot bot)
{
_logger = logger;
_bot = bot;
}
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await _bot.Send(userId.ToString(), "test");
}
}
I'm trying to put all of my commands into their own class in their own .cs file. It clutters the file, as the startup and commands are on the same file. It seems that most others had this from the start, but I was not one of them. Here's what I have now:
Startup:
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Diagnostics;
using Discord;
using Discord.WebSocket;
using Discord.Commands;
using Microsoft.Extensions.DependencyInjection;
namespace LockBot__1._0_
{
public class Program
{
private CommandService _commands;
private DiscordSocketClient _client;
private IServiceProvider _services;
public static void Main(string[] args)
=> new Program().StartAsync().GetAwaiter().GetResult();
public async Task StartAsync()
{
_client = new DiscordSocketClient();
_commands = new CommandService();
_services = new ServiceCollection()
.AddSingleton(_client)
.AddSingleton(_commands)
.BuildServiceProvider();
await InstallCommandsAsync();
string token = "SomeToken";
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
await Task.Delay(-1);
}
public async Task InstallCommandsAsync()
{
_client.Ready += SetGamePlayAsync;
_client.MessageReceived += HandleCommandAsync;
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
}
private async Task HandleCommandAsync(SocketMessage messageParam)
{
var message = messageParam as SocketUserMessage;
if (message == null) return;
int argPos = 0;
if (!(message.HasCharPrefix('~', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return;
var context = new SocketCommandContext(_client, message);
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess)
await context.Channel.SendMessageAsync(result.ErrorReason);
}
public async Task SetGamePlayAsync()
{
await _client.SetGameAsync("locksteel.me | ~help");
}
}
Commands (Shortened):
[Name("Miscellaneous")]
public class Misc : ModuleBase<SocketCommandContext>
{
Random rand = new Random();
[Command("flip")]
[Name("flip")]
[Summary("Flips a coin.")]
[Alias("coin", "coinflip")]
public async Task FlipAsync()
{
string flipToSend;
int flip = rand.Next(1, 3);
if ((flip == 1) && !(flip == 2))
{
flipToSend = "Heads.";
}
else if ((flip == 2) && !(flip == 1))
{
flipToSend = "Tails.";
}
else
{
Debug.WriteLine("~flip error: error establishing random number");
return;
}
await Context.Channel.SendMessageAsync(flipToSend);
Debug.WriteLine("~flip executed successfully in " + Context.Guild.Name);
}
}
Note: I'm still fairly new to coding and even moreso to Discord bot making, so please keep that in mind.
In my program.cs I am similar to you with:
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace Discordbot_Techxas
{
class Program
{
static void Main(string[] args) => new Program().RunBotAsync().GetAwaiter().GetResult();
private DiscordSocketClient _client;
private CommandService _commands;
private IServiceProvider _services;
public async Task RunBotAsync()
{
_client = new DiscordSocketClient();
_commands = new CommandService();
_services = new ServiceCollection()
.AddSingleton(_client)
.AddSingleton(_commands)
.BuildServiceProvider();
//You need to add the Token for your Discord Bot to the code below.
string botToken = "YOUR CODE HERE";
//event subscriptions
_client.Log += Log;
_client.UserJoined += AnnounceUserJoined;
await RegisterCommandsAsync();
await _client.LoginAsync(TokenType.Bot, botToken);
await _client.StartAsync();
await Task.Delay(-1);
}
private async Task AnnounceUserJoined(SocketGuildUser user)
{
var guild = user.Guild;
var channel = guild.DefaultChannel;
await channel.SendMessageAsync($"Welcome, {user.Mention}");
}
private Task Log(LogMessage arg)
{
Console.WriteLine(arg);
return Task.CompletedTask;
}
public async Task RegisterCommandsAsync()
{
_client.MessageReceived += HandleCommandAsync;
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
}
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
if (message is null || message.Author.IsBot) return;
int argPos = 0;
if (message.HasStringPrefix("!", ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))
{
var context = new SocketCommandContext(_client, message);
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess)
Console.WriteLine(result.ErrorReason);
}
}
}
}
Then each of my command files is activated if based on the following. So !ping goes:
using Discord;
using Discord.Commands;
using System.Threading.Tasks;
namespace Discordbot_Techxas.Modules
{
public class Ping : ModuleBase<SocketCommandContext>
{
[Command("ping")]
public async Task PingAsync()
{
EmbedBuilder builder = new EmbedBuilder();
builder.WithTitle("Ping!")
.WithDescription($"PONG back to you {Context.User.Mention} (This will be a never ending game of Ping/Pong)")
.WithColor(Color.DarkRed);
await ReplyAsync("", false, builder.Build());
}
}
}
After that I just create a new class item in my modules folder for each command I want to tweak. I am new and learning too. But you can see the sandbox bot I am creating and the resources I used to get there at: https://github.com/Thanory/Discordbot_Techxas