Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions csharp/Bot.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraderBot", "TraderBot\TraderBot.csproj", "{FAE89FE2-17C5-4AD6-98EC-84002CC4C672}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBot", "DiscordBot\DiscordBot.csproj", "{8B5F7E91-2D44-4F6C-8A5A-1E9C3B4D5F7E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -36,5 +38,9 @@ Global
{FAE89FE2-17C5-4AD6-98EC-84002CC4C672}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FAE89FE2-17C5-4AD6-98EC-84002CC4C672}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FAE89FE2-17C5-4AD6-98EC-84002CC4C672}.Release|Any CPU.Build.0 = Release|Any CPU
{8B5F7E91-2D44-4F6C-8A5A-1E9C3B4D5F7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B5F7E91-2D44-4F6C-8A5A-1E9C3B4D5F7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B5F7E91-2D44-4F6C-8A5A-1E9C3B4D5F7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B5F7E91-2D44-4F6C-8A5A-1E9C3B4D5F7E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
22 changes: 22 additions & 0 deletions csharp/DiscordBot/DiscordBot.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.15.3" />
<PackageReference Include="Octokit" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Storage\Storage.csproj" />
<ProjectReference Include="..\Interfaces\Interfaces.csproj" />
</ItemGroup>
</Project>
14 changes: 14 additions & 0 deletions csharp/DiscordBot/DiscordBotSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace DiscordBot
{
public class DiscordBotSettings
{
public string Token { get; set; } = string.Empty;
public string GitHubToken { get; set; } = string.Empty;
public ulong GuildId { get; set; }
public Dictionary<string, ulong> LanguageRoles { get; set; } = new();
public string DatabasePath { get; set; } = "discord_bot.db";
public int SyncIntervalHours { get; set; } = 24;
}
}
227 changes: 227 additions & 0 deletions csharp/DiscordBot/Modules/LanguageRoleModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
using Discord;
using Discord.Commands;
using DiscordBot.Services;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace DiscordBot.Modules
{
public class LanguageRoleModule : ModuleBase<SocketCommandContext>
{
private readonly DiscordBotService _botService;
private readonly GitHubLanguageDetectionService _languageService;
private readonly ILogger<LanguageRoleModule> _logger;

public LanguageRoleModule(
DiscordBotService botService,
GitHubLanguageDetectionService languageService,
ILogger<LanguageRoleModule> logger)
{
_botService = botService;
_languageService = languageService;
_logger = logger;
}

[Command("sync-roles")]
[Summary("Synchronizes your Discord roles with programming languages from your GitHub profile")]
public async Task SyncRolesAsync([Summary("Your GitHub username")] string githubUsername)
{
try
{
await Context.Channel.TriggerTypingAsync();

_logger.LogInformation("User {DiscordUser} requested role sync with GitHub user {GitHubUser}",
Context.User.Username, githubUsername);

var embed = new EmbedBuilder()
.WithTitle("🔄 Synchronizing Roles...")
.WithDescription($"Analyzing GitHub profile: **{githubUsername}**\nThis may take a moment...")
.WithColor(Color.Blue)
.WithTimestamp(DateTimeOffset.Now)
.Build();

var message = await ReplyAsync(embed: embed);

_botService.StoreUserGitHubMapping(Context.User.Id, githubUsername);

var languageStats = await _languageService.GetUserProgrammingLanguagesAsync(githubUsername);

if (!languageStats.Any())
{
var errorEmbed = new EmbedBuilder()
.WithTitle("❌ No Programming Languages Found")
.WithDescription($"No programming languages found for GitHub user **{githubUsername}**.\n\n" +
"This could mean:\n" +
"• The user doesn't exist\n" +
"• The user has no public repositories\n" +
"• The repositories don't contain detectable programming languages")
.WithColor(Color.Red)
.WithTimestamp(DateTimeOffset.Now)
.Build();

await message.ModifyAsync(m => m.Embed = errorEmbed);
return;
}

await _botService.SyncUserRolesAsync(Context.User.Id, githubUsername);

var topLanguages = GitHubLanguageDetectionService.GetTopLanguages(languageStats);
var languageList = topLanguages.Take(10).Select(lang =>
{
var bytes = languageStats[lang];
var size = bytes < 1024 ? $"{bytes} B" :
bytes < 1024 * 1024 ? $"{bytes / 1024:N0} KB" :
$"{bytes / (1024 * 1024):N1} MB";
return $"• **{lang}** ({size})";
});

var successEmbed = new EmbedBuilder()
.WithTitle("✅ Roles Synchronized Successfully!")
.WithDescription($"**GitHub Profile:** {githubUsername}\n\n" +
$"**Top Programming Languages:**\n{string.Join("\n", languageList)}\n\n" +
"Your Discord roles have been updated to reflect your programming language expertise!")
.WithColor(Color.Green)
.WithTimestamp(DateTimeOffset.Now)
.WithFooter("Roles are synchronized based on your public repositories")
.Build();

await message.ModifyAsync(m => m.Embed = successEmbed);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during role synchronization for user {User} with GitHub {GitHub}",
Context.User.Username, githubUsername);

var errorEmbed = new EmbedBuilder()
.WithTitle("❌ Synchronization Failed")
.WithDescription("An error occurred while synchronizing your roles. Please try again later or contact an administrator.")
.WithColor(Color.Red)
.WithTimestamp(DateTimeOffset.Now)
.Build();

await ReplyAsync(embed: errorEmbed);
}
}

[Command("check-languages")]
[Summary("Shows programming languages detected from a GitHub profile without changing roles")]
public async Task CheckLanguagesAsync([Summary("GitHub username to check")] string githubUsername)
{
try
{
await Context.Channel.TriggerTypingAsync();

_logger.LogInformation("User {DiscordUser} requested language check for GitHub user {GitHubUser}",
Context.User.Username, githubUsername);

var embed = new EmbedBuilder()
.WithTitle("🔍 Analyzing GitHub Profile...")
.WithDescription($"Scanning repositories for: **{githubUsername}**")
.WithColor(Color.Blue)
.WithTimestamp(DateTimeOffset.Now)
.Build();

var message = await ReplyAsync(embed: embed);

var languageStats = await _languageService.GetUserProgrammingLanguagesAsync(githubUsername);

if (!languageStats.Any())
{
var errorEmbed = new EmbedBuilder()
.WithTitle("❌ No Programming Languages Found")
.WithDescription($"No programming languages detected for GitHub user **{githubUsername}**.")
.WithColor(Color.Red)
.WithTimestamp(DateTimeOffset.Now)
.Build();

await message.ModifyAsync(m => m.Embed = errorEmbed);
return;
}

var topLanguages = GitHubLanguageDetectionService.GetTopLanguages(languageStats, maxLanguages: 15);
var totalBytes = languageStats.Values.Sum();

var languageList = topLanguages.Select((lang, index) =>
{
var bytes = languageStats[lang];
var percentage = (double)bytes / totalBytes * 100;
var size = bytes < 1024 ? $"{bytes} B" :
bytes < 1024 * 1024 ? $"{bytes / 1024:N0} KB" :
$"{bytes / (1024 * 1024):N1} MB";
return $"{index + 1}. **{lang}** - {percentage:F1}% ({size})";
});

var resultEmbed = new EmbedBuilder()
.WithTitle($"📊 Programming Languages for {githubUsername}")
.WithDescription($"**Total Repositories Analyzed:** {languageStats.Count}\n\n" +
$"**Languages Found:**\n{string.Join("\n", languageList)}")
.WithColor(Color.Blue)
.WithTimestamp(DateTimeOffset.Now)
.WithFooter("Analysis based on public repositories only")
.Build();

await message.ModifyAsync(m => m.Embed = resultEmbed);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during language check for GitHub user {GitHub}", githubUsername);

var errorEmbed = new EmbedBuilder()
.WithTitle("❌ Analysis Failed")
.WithDescription("An error occurred while analyzing the GitHub profile. Please check the username and try again.")
.WithColor(Color.Red)
.WithTimestamp(DateTimeOffset.Now)
.Build();

await ReplyAsync(embed: errorEmbed);
}
}

[Command("my-sync")]
[Summary("Synchronizes your roles using your previously linked GitHub account")]
public async Task MySyncAsync()
{
var githubUsername = _botService.GetUserGitHubUsername(Context.User.Id);

if (string.IsNullOrEmpty(githubUsername))
{
var embed = new EmbedBuilder()
.WithTitle("❌ No GitHub Account Linked")
.WithDescription("You haven't linked a GitHub account yet. Use:\n`!sync-roles <your-github-username>`")
.WithColor(Color.Red)
.WithTimestamp(DateTimeOffset.Now)
.Build();

await ReplyAsync(embed: embed);
return;
}

await SyncRolesAsync(githubUsername);
}

[Command("help")]
[Summary("Shows available commands for role synchronization")]
public async Task HelpAsync()
{
var embed = new EmbedBuilder()
.WithTitle("🤖 Programming Language Role Bot - Commands")
.WithDescription("This bot automatically assigns Discord roles based on your GitHub programming language usage.")
.WithColor(Color.Blue)
.AddField("!sync-roles <github-username>",
"Links your GitHub account and synchronizes your Discord roles", false)
.AddField("!my-sync",
"Re-synchronizes your roles using your previously linked GitHub account", false)
.AddField("!check-languages <github-username>",
"Shows programming languages for a GitHub user without changing roles", false)
.AddField("!help",
"Shows this help message", false)
.WithTimestamp(DateTimeOffset.Now)
.WithFooter("LinksPlatform Discord Bot")
.Build();

await ReplyAsync(embed: embed);
}
}
}
40 changes: 40 additions & 0 deletions csharp/DiscordBot/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using DiscordBot.Services;
using System.Threading.Tasks;

namespace DiscordBot
{
internal class Program
{
private static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
await host.RunAsync();
}

private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
config.AddCommandLine(args);
config.AddEnvironmentVariables();
})
.ConfigureServices((context, services) =>
{
services.Configure<DiscordBotSettings>(context.Configuration.GetSection("DiscordBot"));
services.AddSingleton<DiscordBotService>();
services.AddSingleton<ProgrammingLanguageRoleService>();
services.AddSingleton<GitHubLanguageDetectionService>();
services.AddHostedService<DiscordBotHostedService>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
});
}
}
Loading
Loading