First commit.

This commit is contained in:
1whatleytay 2019-04-28 20:07:29 -04:00
commit e1489d4aa7
8 changed files with 360 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea
Vita3KBot/obj
Vita3KBot/bin
Vita3KBot/token.txt
Vita3KBot.sln.DotSettings.user
Vita3KBot.sln.DotSettings

16
Vita3KBot.sln Normal file
View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vita3KBot", "Vita3KBot\Vita3KBot.csproj", "{15AA3D43-CB5E-4F80-B03C-EAE8B1E40F2E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{15AA3D43-CB5E-4F80-B03C-EAE8B1E40F2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15AA3D43-CB5E-4F80-B03C-EAE8B1E40F2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15AA3D43-CB5E-4F80-B03C-EAE8B1E40F2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15AA3D43-CB5E-4F80-B03C-EAE8B1E40F2E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

51
Vita3KBot/Bot.cs Normal file
View File

@ -0,0 +1,51 @@
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
namespace Vita3KBot {
public class Bot {
private readonly string _token;
private DiscordSocketClient _client;
private MessageHandler _handler;
// Initializes Discord.Net
private async Task Start() {
_client = new DiscordSocketClient();
_handler = new MessageHandler(_client);
await _handler.Init();
await _client.LoginAsync(TokenType.Bot, _token);
await _client.StartAsync();
await Task.Delay(-1);
}
private Bot(string token) {
_token = token;
}
public static void Main(string[] args) {
// Init command with token.
if (args.Length >= 2 && args[0] == "init") {
File.WriteAllText("token.txt", args[1]);
}
// Start bot with token from "token.txt" in working folder.
try {
var bot = new Bot(File.ReadAllText("token.txt"));
bot.Start().GetAwaiter().GetResult();
} catch (IOException e) {
Console.WriteLine("Could not read from token.txt. Did you run `init <token>`?");
}
}
}
}

View File

@ -0,0 +1,144 @@
using System;
using System.Threading.Tasks;
using System.Linq;
using System.Text;
using Discord;
using Discord.Commands;
using Octokit;
namespace Vita3KBot.Commands {
public class Compatibility: ModuleBase<SocketCommandContext> {
// Config
private const int MaxItemsToDisplay = 8;
private const string HomebrewRepo = "homebrew-compatibility";
private const string CommercialRepo = "compatibility";
private class TitleInfo {
private static readonly string[] StatusNames = {
// Priority, display when possible.
"Playable",
"Ingame",
"Intro",
"Crash",
"Nothing",
// Secondary, display if nothing else.
"Slow",
"Black Screen",
"NID Missing",
"Module Loading Bug",
"IO Bug",
"Softlock Bug",
"Graphics Bug",
"Shader Bug",
"Audio Bug",
"Input Bug",
"Touch Bug",
"Savedata Bug",
"Trophy Bug",
"Networking Bug",
// Invalid
"Invalid",
"Unknown",
};
private readonly Issue _issue;
public readonly bool IsHomebrew;
public readonly string Status;
public string LatestComment;
public string LatestProfileImage;
public async Task FetchCommentInfo(GitHubClient client) {
if (_issue.Comments == 0) return;
var comments = await client.Issue.Comment.GetAllForIssue("Vita3K",
IsHomebrew ? HomebrewRepo : CommercialRepo, _issue.Number);
var lastComment = comments[_issue.Comments - 1];
LatestComment = "**" + lastComment.User.Login + "**: " + lastComment.Body;
LatestProfileImage = lastComment.User.AvatarUrl;
}
public TitleInfo(Issue issue) {
_issue = issue;
// Repository object is sometimes null on searches. Just guess the repo by the URL.
IsHomebrew = issue.Url.Contains(HomebrewRepo);
Console.WriteLine(issue.CommentsUrl + " " + issue.Comments);
Status = "Unknown";
var foundStatus = false;
foreach (var label in issue.Labels) {
foreach (var name in StatusNames) {
if (name.ToLower().Equals(label.Name.ToLower())) {
Status = name;
foundStatus = true;
break;
}
}
if (foundStatus) break;
}
LatestComment = "*No updates on this title.*";
LatestProfileImage = "";
}
}
[Command("compat")]
public async Task Compatability([Remainder]string keyword) {
var github = new GitHubClient(new ProductHeaderValue("Vita3KBot"));
var search = new SearchIssuesRequest(keyword) {
Repos = new RepositoryCollection {
"Vita3K/homebrew-compatibility",
"Vita3K/compatibility"
}
};
var result = await github.Search.SearchIssues(search);
switch (result.Items.Count) {
case 0:
await ReplyAsync("No games found for search term " + keyword + ".");
break;
case 1: {
var issue = result.Items.First();
var info = new TitleInfo(issue);
await info.FetchCommentInfo(github);
var builder = new EmbedBuilder()
.WithTitle("*" + issue.Title + "* (" + (info.IsHomebrew ? "Homebrew" : "Commercial") + ")")
.WithDescription("Status: **" + info.Status + "**\n\n" + info.LatestComment)
.WithColor(Color.Red)
.WithUrl(issue.Url)
.WithCurrentTimestamp();
if (info.LatestProfileImage.Length > 0) builder.WithImageUrl(info.LatestProfileImage);
await ReplyAsync("", false, builder.Build());
break;
}
default: {
var description = new StringBuilder();
for (var a = 0; a < Math.Min(result.Items.Count, MaxItemsToDisplay); a++) {
var issue = result.Items[a];
var info = new TitleInfo(issue);
description.Append("*" + issue.Title + "* (" + (info.IsHomebrew ? "Homebrew" : "Commercial")
+ "): **" + info.Status + "**\n");
}
if (result.Items.Count > MaxItemsToDisplay) description.Append("...");
var builder = new EmbedBuilder()
.WithTitle("Found " + result.Items.Count + " issues for search term " + keyword + ".")
.WithDescription(description.ToString())
.WithColor(Color.Orange)
.WithCurrentTimestamp();
await ReplyAsync("", false, builder.Build());
break;
}
}
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord.Commands;
using Discord.WebSocket;
namespace Vita3KBot.Commands {
public class Debug : ModuleBase<SocketCommandContext> {
// Get id for a role. Helpful when creating commands that might query, give, remove roles.
[Command("probe-role")]
public async Task ProbeRole([Remainder] string roleName) {
await ReplyAsync(roleName + ": " + Context.Guild.Roles.First(x => x.Name == roleName).Id);
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Data;
using System.Reflection;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
namespace Vita3KBot {
public class MessageHandler {
// Config
private const char Prefix = '-';
private const bool ShowStackTrace = true;
private readonly DiscordSocketClient _client;
private readonly CommandService _commands;
private readonly ServiceProvider _services;
// Called for each user message. Use it to collect stats, or silently observe stuff, etc.
private static async Task MonitorMessage(SocketUserMessage message) {
if (!(message.Author is SocketGuildUser user) || message.Author.IsBot) return;
//TODO: Put Persona 4 Golden monitoring here.
}
// Called by Discord.Net when it wants to log something.
private static Task Log(LogMessage message) {
Console.WriteLine(message.Message);
return Task.CompletedTask;
}
// Called by Discord.Net when the bot receives a message.
private async Task CheckMessage(SocketMessage message) {
if (!(message is SocketUserMessage userMessage)) return;
await MonitorMessage(userMessage);
var prefixStart = 0;
if (userMessage.HasCharPrefix(Prefix, ref prefixStart)) {
// Create Context and Execute Commands
var context = new SocketCommandContext(_client, userMessage);
var result = await _commands.ExecuteAsync(context, prefixStart, _services);
// Handle any errors.
if (!result.IsSuccess && result.Error != CommandError.UnknownCommand) {
if (ShowStackTrace && result.Error == CommandError.Exception
&& result is ExecuteResult execution) {
await userMessage.Channel.SendMessageAsync(
Utils.Code(execution.Exception.Message + "\n\n" + execution.Exception.StackTrace));
} else {
await userMessage.Channel.SendMessageAsync(
"Halt! We've hit an error." + Utils.Code(result.ErrorReason));
}
}
}
}
// Initializes the Message Handler, subscribe to events, etc.
public async Task Init() {
_client.Log += Log;
_client.MessageReceived += CheckMessage;
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
}
public MessageHandler(DiscordSocketClient client) {
_client = client;
_commands = new CommandService();
_services = new ServiceCollection()
.AddSingleton(_client)
.AddSingleton(_commands)
.BuildServiceProvider();
}
}
}

31
Vita3KBot/Utils.cs Normal file
View File

@ -0,0 +1,31 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Vita3KBot {
public static class Utils {
// Sends an HTTP Get request. Might not be the best solution.
public static async Task<string> HttpGet(string address, string parameters = "") {
var client = new HttpClient{ BaseAddress = new Uri(address) };
// Add User-Agent in header so Github API allows our requests.
client.DefaultRequestHeaders.Add("User-Agent", "Vita3KBot");
var response = await client.GetAsync(parameters);
if (response.IsSuccessStatusCode) {
var result = await response.Content.ReadAsStringAsync();
client.Dispose();
return result;
}
client.Dispose();
throw new Exception("Received " + response.StatusCode
+ " status code from " + address + parameters + ".");
}
// Discord Markdown Code.
public static string Code(string code, string lang = "") {
return "```" + lang + "\n" + code + "\n```";
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Octokit" Version="0.32.0" />
</ItemGroup>
</Project>