···11+using NetCord;
22+using NetCord.Services;
33+using NetCord.Services.ApplicationCommands;
44+using StreakBot.Services;
55+66+namespace StreakBot.Commands;
77+88+public class CheckStreakCommand : ApplicationCommandModule<ApplicationCommandContext>
99+{
1010+ private readonly StreakService _streakService;
1111+1212+ public CheckStreakCommand(StreakService streakService)
1313+ {
1414+ _streakService = streakService;
1515+ }
1616+1717+ [SlashCommand("streak", "Check your daily streak with another user")]
1818+ public async Task<string> CheckStreak()
1919+ {
2020+ if (Context.Interaction.GuildId is not null)
2121+ {
2222+ return "This command can only be used in a dm!";
2323+ }
2424+2525+ var initiatorId = Context.User.Id;
2626+2727+ var streaks = await _streakService.CheckStreaksAsync(initiatorId);
2828+2929+ if (!streaks.Any())
3030+ {
3131+ return "You have no streaks.";
3232+ }
3333+3434+ var output = "You have the following streaks:";
3535+3636+ foreach (var streak in streaks)
3737+ {
3838+ var targetId = streak.User1Id == initiatorId ? streak.User2Id : streak.User1Id;
3939+ output += $"\n\nYour streak with <@{targetId}> started on {streak.CreatedDate:MMM-dd-yyyy}, and your current streak pet is {streak.StreakNumber} days old!";
4040+ }
4141+4242+ return output;
4343+4444+ }
4545+}
+43
Commands/EndStreakCommand.cs
···11+using NetCord;
22+using NetCord.Services.ApplicationCommands;
33+using StreakBot.Services;
44+55+namespace StreakBot.Commands;
66+77+public class EndStreakCommand : ApplicationCommandModule<ApplicationCommandContext>
88+{
99+ private readonly StreakService _streakService;
1010+1111+ public EndStreakCommand(StreakService streakService)
1212+ {
1313+ _streakService = streakService;
1414+ }
1515+1616+ [SlashCommand("endstreak", "!!!WARNING PERMANENT DESTRUCTIVE ACTION!!! Ends the daily streak you have with another user.")]
1717+ public async Task<string> EndStreak(
1818+ [SlashCommandParameter(Name = "user", Description = "The user to end your streak with")]
1919+ User user)
2020+ {
2121+ var initiatorId = Context.User.Id;
2222+ var targetId = user.Id;
2323+2424+ if (initiatorId == targetId)
2525+ {
2626+ return "You can't end a streak with yourself!";
2727+ }
2828+2929+ if (user.IsBot)
3030+ {
3131+ return "You can't end a streak with a bot!";
3232+ }
3333+3434+ var success = await _streakService.DeleteStreakAsync(initiatorId, targetId);
3535+3636+ if (success == null)
3737+ {
3838+ return $"No streak found between <@{initiatorId}> and <@{targetId}>.";
3939+ }
4040+4141+ return (bool)success ? $"Streak ended between <@{initiatorId}> and <@{targetId}>" : "Failed to end streak. Contact @minito for further information.";
4242+ }
4343+}
+51
Commands/StartCommand.cs
···11+using NetCord;
22+using NetCord.Services.ApplicationCommands;
33+using StreakBot.Data.Entities;
44+using StreakBot.Services;
55+66+namespace StreakBot.Commands;
77+88+public class StartCommand : ApplicationCommandModule<ApplicationCommandContext>
99+{
1010+ private readonly StreakService _streakService;
1111+1212+ public StartCommand(StreakService streakService)
1313+ {
1414+ _streakService = streakService;
1515+ }
1616+1717+ [SlashCommand("start", "Start a daily messaging streak with another user")]
1818+ public async Task<string> Start(
1919+ [SlashCommandParameter(Name = "user", Description = "The user to start a streak with")]
2020+ User user)
2121+ {
2222+ var initiatorId = Context.User.Id;
2323+ var targetId = user.Id;
2424+2525+ if (initiatorId == targetId)
2626+ {
2727+ return "You can't start a streak with yourself!";
2828+ }
2929+3030+ if (user.IsBot)
3131+ {
3232+ return "You can't start a streak with a bot!";
3333+ }
3434+3535+ Streak? success;
3636+ if (Context.Interaction.GuildId is null)
3737+ {
3838+ // Group DM
3939+ var channelId = Context.Channel.Id;
4040+ success = await _streakService.CreateStreakAsync(initiatorId, targetId, 0, channelId);
4141+ }
4242+ else
4343+ {
4444+ // Server
4545+ var serverId = Context.Interaction.GuildId.Value;
4646+ success = await _streakService.CreateStreakAsync(initiatorId, targetId, serverId);
4747+ }
4848+4949+ return success != null ? $"Streak started between <@{initiatorId}> and <@{targetId}>! Send messages daily to keep your streak alive!" : $"Streak already started between <@{initiatorId}> and <@{targetId}>!";
5050+ }
5151+}
+10
Data/Entities/Channel.cs
···11+namespace StreakBot.Data.Entities;
22+33+public class Channel
44+{
55+ public int Id { get; set; }
66+ public ulong ServerId { get; set; }
77+ public ulong ChannelId { get; set; }
88+ public ChannelType ChannelType { get; set; }
99+ public ulong? MessageId { get; set; }
1010+}
···11# StreakBot
22+This is a Discord bot made using NetCord, for the purposes of creating a server to make streaks (a la TikTok or Duolingo). Creates voice channels that display the streak count and a timer of when the daily timer starts again.
33+44+You'll need to do the necessary steps of setting up a Discord bot (Set up a bot at https://discord.com/developers/applications), grab the token that it spits out after creating the bot, and put it into the appsettings.json.
55+66+Start a streak with /start <@username>
77+End a streak with /endstreak <@username>
88+Check your streaks with /streak
+152
Services/ChannelService.cs
···11+using Microsoft.EntityFrameworkCore;
22+using NetCord.Rest;
33+using StreakBot.Data;
44+using StreakBot.Data.Entities;
55+using ChannelEntity = StreakBot.Data.Entities.Channel;
66+using ChannelType = StreakBot.Data.Entities.ChannelType;
77+88+namespace StreakBot.Services;
99+1010+public class ChannelService
1111+{
1212+ private readonly StreakDbContext _context;
1313+ private readonly RestClient _restClient;
1414+1515+ public ChannelService(StreakDbContext context, RestClient restClient)
1616+ {
1717+ _context = context;
1818+ _restClient = restClient;
1919+ }
2020+2121+ public async Task UpdateStreakChannelAsync(ulong serverId, int streakCount, bool endOfDay = false)
2222+ {
2323+ var channel = await _context.Channels
2424+ .FirstOrDefaultAsync(c => c.ServerId == serverId && c.ChannelType == ChannelType.StreakChannel);
2525+2626+ var channelName = endOfDay ? $"🧊 {streakCount}" : $"🔥 {streakCount}";
2727+2828+ if (channel == null)
2929+ {
3030+ var properties = new GuildChannelProperties(channelName, NetCord.ChannelType.VoiceGuildChannel);
3131+ var createdChannel = await _restClient.CreateGuildChannelAsync(serverId, properties);
3232+3333+ channel = new ChannelEntity
3434+ {
3535+ ServerId = serverId,
3636+ ChannelId = createdChannel.Id,
3737+ ChannelType = ChannelType.StreakChannel
3838+ };
3939+4040+ _context.Channels.Add(channel);
4141+ await _context.SaveChangesAsync();
4242+ }
4343+ else
4444+ {
4545+ await _restClient.ModifyGuildChannelAsync(channel.ChannelId, options => options.WithName(channelName));
4646+ }
4747+ }
4848+4949+ public async Task UpdateTimeChannelAsync(ulong serverId, TimeSpan resetTime)
5050+ {
5151+ var channel = await _context.Channels
5252+ .FirstOrDefaultAsync(c => c.ServerId == serverId && c.ChannelType == ChannelType.TimeChannel);
5353+5454+ var timeRemaining = CalculateTimeRemaining(resetTime);
5555+ var channelName = $"⏰ {timeRemaining:hh\\:mm}";
5656+5757+ if (channel == null)
5858+ {
5959+ var properties = new GuildChannelProperties(channelName, NetCord.ChannelType.VoiceGuildChannel);
6060+ var createdChannel = await _restClient.CreateGuildChannelAsync(serverId, properties);
6161+6262+ channel = new ChannelEntity
6363+ {
6464+ ServerId = serverId,
6565+ ChannelId = createdChannel.Id,
6666+ ChannelType = ChannelType.TimeChannel
6767+ };
6868+6969+ _context.Channels.Add(channel);
7070+ await _context.SaveChangesAsync();
7171+ }
7272+ else
7373+ {
7474+ await _restClient.ModifyGuildChannelAsync(channel.ChannelId, options => options.WithName(channelName));
7575+ }
7676+ }
7777+7878+ private static TimeSpan CalculateTimeRemaining(TimeSpan resetTime)
7979+ {
8080+ var now = DateTime.UtcNow.TimeOfDay;
8181+8282+ if (now < resetTime)
8383+ {
8484+ return resetTime - now;
8585+ }
8686+ else
8787+ {
8888+ return TimeSpan.FromHours(24) - now + resetTime;
8989+ }
9090+ }
9191+9292+ public async Task<ChannelEntity?> GetChannelAsync(ulong serverId, ChannelType channelType)
9393+ {
9494+ return await _context.Channels
9595+ .FirstOrDefaultAsync(c => c.ServerId == serverId && c.ChannelType == channelType);
9696+ }
9797+9898+ public async Task<bool> DeleteChannelAsync(ulong channelId)
9999+ {
100100+ var test = await _restClient.DeleteChannelAsync(channelId);
101101+102102+ return true;
103103+ }
104104+105105+ public async Task SendStreakReminderAsync(ulong user1Id, ulong user2Id, bool user1NeedsReminder, bool user2NeedsReminder)
106106+ {
107107+ if (user1NeedsReminder)
108108+ {
109109+ var dmChannel = await _restClient.GetDMChannelAsync(user1Id);
110110+ var message = new MessageProperties()
111111+ .WithContent($"Your streak with <@{user2Id}> will reset in less than an hour! Send a message in the server to keep it alive!");
112112+ await _restClient.SendMessageAsync(dmChannel.Id, message);
113113+ }
114114+115115+ if (user2NeedsReminder)
116116+ {
117117+ var dmChannel = await _restClient.GetDMChannelAsync(user2Id);
118118+ var message = new MessageProperties()
119119+ .WithContent($"Your streak with <@{user1Id}> will reset in less than an hour! Send a message in the server to keep it alive!");
120120+ await _restClient.SendMessageAsync(dmChannel.Id, message);
121121+ }
122122+ }
123123+124124+ public async Task<ChannelEntity> CreateDmStreakMessageAsync(ulong channelId, int streakNumber, TimeSpan resetTime)
125125+ {
126126+ var timeRemaining = CalculateTimeRemaining(resetTime);
127127+ var content = $"🔥 {streakNumber}\n⏰ {timeRemaining:hh\\:mm}";
128128+129129+ var messageProperties = new MessageProperties().WithContent(content);
130130+ var sentMessage = await _restClient.SendMessageAsync(channelId, messageProperties);
131131+132132+ var channel = new ChannelEntity
133133+ {
134134+ ChannelId = channelId,
135135+ ChannelType = ChannelType.DmChannel,
136136+ MessageId = sentMessage.Id
137137+ };
138138+139139+ _context.Channels.Add(channel);
140140+ await _context.SaveChangesAsync();
141141+142142+ return channel;
143143+ }
144144+145145+ public async Task UpdateDmStreakMessageAsync(ulong channelId, ulong messageId, int streakNumber, TimeSpan resetTime)
146146+ {
147147+ var timeRemaining = CalculateTimeRemaining(resetTime);
148148+ var content = $"🔥 {streakNumber}\n⏰ {timeRemaining:hh\\:mm}";
149149+150150+ await _restClient.ModifyMessageAsync(channelId, messageId, message => message.WithContent(content));
151151+ }
152152+}