Compare commits
198 Commits
test-relea
...
master
Author | SHA1 | Date | |
---|---|---|---|
fc73df1d63 | |||
e4384a2ea0 | |||
be36c3cb55 | |||
1141118263 | |||
41172f755c | |||
bcc5389d63 | |||
660af2805e | |||
275faaacfc | |||
09f439188a | |||
246a6e2019 | |||
33f55dc790 | |||
b4b0fd155b | |||
7546612d12 | |||
401a3ecbc8 | |||
d7416b480b | |||
4e82eedf9c | |||
b6f74f580c | |||
488a89614a | |||
d22faae2f6 | |||
6881816c94 | |||
53753374f0 | |||
50ecfc5867 | |||
0d3a56c8db | |||
18e8f0f36e | |||
d006367ecc | |||
c971add137 | |||
3ed37959ad | |||
736e3cb763 | |||
740471d105 | |||
6298b037b6 | |||
03fdb56190 | |||
c3a9ac3c54 | |||
203c6af3cf | |||
2793e6ef76 | |||
c0cfa90874 | |||
8b857b82c9 | |||
4c06a74410 | |||
4c93fd3ef8 | |||
25674e3af6 | |||
b3eb7b1ff1 | |||
a7afcacee8 | |||
37d3ec5947 | |||
0d0d377a05 | |||
1b8a714a96 | |||
7c22ae1643 | |||
e0c7bdb35f | |||
9648ea563b | |||
1d73fe0be8 | |||
5eeec24069 | |||
ab16600463 | |||
af4d68caa1 | |||
0ac28c35fb | |||
942b11fcce | |||
4bd51721b6 | |||
e364b47c0f | |||
c6ea3ef790 | |||
2f7bc2c0ea | |||
43eaa5ad0d | |||
b4b5544ec4 | |||
c446521977 | |||
54414b8748 | |||
8cb4005192 | |||
cd2fa384d5 | |||
f4bed1e6cb | |||
88ca468708 | |||
464b6a90e4 | |||
581fddf6f9 | |||
bed8d3cbef | |||
2dd9e903db | |||
ef31418166 | |||
6d181e2b68 | |||
10167b597a | |||
26d8373dc8 | |||
a63a3fcb58 | |||
b84e47344b | |||
c5f9ae2c6b | |||
efb4ab00d2 | |||
451ace753d | |||
894b536c04 | |||
ba2254858f | |||
c1e32ef39a | |||
5fb96ea67e | |||
1fd2a4723e | |||
51fb08810d | |||
423fe5cb96 | |||
23e18f2028 | |||
9d3917a030 | |||
391ba38cce | |||
d060e92ed9 | |||
77fc26e1ed | |||
edc86af538 | |||
39781397c3 | |||
e89c109970 | |||
e7b70468ae | |||
47f382df19 | |||
8c6087d557 | |||
da7078f535 | |||
e9ddcd237c | |||
4a60e53798 | |||
5bb64f764c | |||
986d433886 | |||
c393f657d1 | |||
6037adcb44 | |||
2fc199d4b6 | |||
a8d1fc8d6e | |||
91bcfae1ba | |||
115035eb91 | |||
147cba7cd3 | |||
66b425cd39 | |||
e433e56fec | |||
a1d2ec83b5 | |||
3a4a6df087 | |||
567a59550e | |||
f23474ad51 | |||
d2aa1f46cc | |||
51fba995c3 | |||
e4f7d88e35 | |||
3031779e24 | |||
bfe7f582b2 | |||
a87fcd6ad9 | |||
e5a5c7cc7c | |||
4c47081c9b | |||
fb9cec3225 | |||
6022f88997 | |||
12e21e8ad7 | |||
78639f449c | |||
3143150c37 | |||
81465d5e43 | |||
4554e46a61 | |||
b83c569af4 | |||
1d0ba003fe | |||
94ae81909b | |||
f975beb6e9 | |||
7f491ef632 | |||
68ce0ed6fd | |||
7e6b369b48 | |||
362a8784ba | |||
d90123a56d | |||
589d27434e | |||
c874e3e421 | |||
def4f3644e | |||
87c31a91b8 | |||
7364c38b3a | |||
3d4064f8cb | |||
05341cee3b | |||
32242c9cb4 | |||
8fa439b232 | |||
751ecf2e30 | |||
073b7f1cf0 | |||
c4af2b4b9d | |||
0265be98c9 | |||
246f15bc61 | |||
48b2da1b3e | |||
ab672a2b64 | |||
c979728183 | |||
70c45b5db3 | |||
b957d33e4a | |||
1ebeb335ac | |||
b2a3b33672 | |||
5b3a645406 | |||
34e7437783 | |||
53c1d956b3 | |||
fcf7d11715 | |||
5cf4e87e06 | |||
37d1cb7d70 | |||
99b662bfec | |||
7a080e0653 | |||
6cda86300e | |||
00b588059d | |||
8950ce013e | |||
19cba4975b | |||
bb9ca88c36 | |||
db7ab04815 | |||
545efb2829 | |||
5bbb8c95d1 | |||
433b439f7b | |||
ca397671d0 | |||
5cb19041da | |||
010b40ab13 | |||
3849880b33 | |||
b1578dde69 | |||
fd2ae7f358 | |||
1456d6bda8 | |||
bc3e583926 | |||
4f70bba30c | |||
8cbd21c1fb | |||
a5b1ac76f8 | |||
4c312a8172 | |||
d362fe8503 | |||
08f39f257f | |||
ac1e0c6ba2 | |||
fa48d9695f | |||
4cf83afab8 | |||
1a327acbd0 | |||
da0c9eb48a | |||
b32d7398c8 | |||
960efdc6fa | |||
3f83844b29 |
12
.config/dotnet-tools.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "7.0.5",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
5
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
appsettings.json
|
||||
appsettings.Development.json
|
||||
assets/exchangepairs.json
|
||||
fail*/
|
||||
|
||||
# ---> VisualStudio
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
@ -375,3 +377,4 @@ FodyWeavers.xsd
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
tmp/
|
19
.vscode/launch.json
vendored
@ -5,17 +5,26 @@
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"name": ".NET Core Launch (console)",
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/bin/Debug/net5.0/silverworker-discord.dll",
|
||||
"program": "${workspaceFolder}/bin/Debug/net9.0/vassago.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
"stopAtEntry": false,
|
||||
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
|
9
.vscode/tasks.json
vendored
@ -7,7 +7,7 @@
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/silverworker-discord.csproj",
|
||||
"${workspaceFolder}/vassago.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
@ -19,7 +19,7 @@
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/silverworker-discord.csproj",
|
||||
"${workspaceFolder}/vassago.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
@ -32,9 +32,8 @@
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"${workspaceFolder}/silverworker-discord.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
"--project",
|
||||
"${workspaceFolder}/vassago.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
|
104
Behaver.cs
Normal file
@ -0,0 +1,104 @@
|
||||
namespace vassago;
|
||||
#pragma warning disable 4014 //the "not awaited" error
|
||||
using vassago.Behavior;
|
||||
using vassago.Models;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using vassago.ProtocolInterfaces.DiscordInterface;
|
||||
|
||||
public class Behaver
|
||||
{
|
||||
private List<Account> SelfAccounts { get; set; } = new List<Account>();
|
||||
private User SelfUser { get; set; }
|
||||
public static List<vassago.Behavior.Behavior> Behaviors { get; private set; } = new List<vassago.Behavior.Behavior>();
|
||||
internal Behaver()
|
||||
{
|
||||
var subtypes = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(domainAssembly => domainAssembly.GetTypes())
|
||||
.Where(type => type.IsSubclassOf(typeof(vassago.Behavior.Behavior)) && !type.IsAbstract &&
|
||||
type.GetCustomAttributes(typeof(StaticPlzAttribute),false)?.Any() == true)
|
||||
.ToList();
|
||||
|
||||
foreach (var subtype in subtypes)
|
||||
{
|
||||
Behaviors.Add((vassago.Behavior.Behavior)Activator.CreateInstance(subtype));
|
||||
}
|
||||
}
|
||||
static Behaver() { }
|
||||
|
||||
private static readonly Behaver _instance = new Behaver();
|
||||
|
||||
//TODO: you know why I didn't make this a static class? lifecycle issues with the dbcontext. but now that we don't have a stored instance,
|
||||
//no need to have a... *checks over shoulder*... *whispers*: singleton
|
||||
public static Behaver Instance
|
||||
{
|
||||
get { return _instance; }
|
||||
}
|
||||
|
||||
public async Task<bool> ActOn(Message message)
|
||||
{
|
||||
foreach (var behavior in Behaviors)
|
||||
{
|
||||
if (behavior.ShouldAct(message))
|
||||
{
|
||||
behavior.ActOn(message);
|
||||
message.ActedOn = true;
|
||||
Console.WriteLine("acted on, moving forward");
|
||||
}
|
||||
}
|
||||
if (message.ActedOn == false && message.MentionsMe && message.Content.Contains('?') && !Behaver.Instance.SelfAccounts.Any(acc => acc.Id == message.Author.Id))
|
||||
{
|
||||
Console.WriteLine("providing bullshit nonanswer / admitting uselessness");
|
||||
var responses = new List<string>(){
|
||||
@"Well, that's a great question, and there are certainly many different possible answers. Ultimately, the decision will depend on a variety of factors, including your personal interests and goals, as well as any practical considerations (like the economy). I encourage you to do your research, speak with experts and educators, and explore your options before making a decision that's right for you.",
|
||||
@"┐(゚ ~゚ )┌", @"¯\_(ツ)_/¯", @"╮ (. ❛ ᴗ ❛.) ╭", @"╮(╯ _╰ )╭"
|
||||
};
|
||||
await message.Channel.SendMessage(responses[Shared.r.Next(responses.Count)]);
|
||||
message.ActedOn = true;
|
||||
}
|
||||
return message.ActedOn;
|
||||
}
|
||||
|
||||
internal bool IsSelf(Guid AccountId)
|
||||
{
|
||||
var db = new ChattingContext();
|
||||
var acc = db.Accounts.Find(AccountId);
|
||||
|
||||
return SelfAccounts.Any(acc => acc.Id == AccountId);
|
||||
}
|
||||
|
||||
public void MarkSelf(Account selfAccount)
|
||||
{
|
||||
if(SelfUser == null)
|
||||
{
|
||||
SelfUser = selfAccount.IsUser;
|
||||
}
|
||||
else if (SelfUser != selfAccount.IsUser)
|
||||
{
|
||||
CollapseUsers(SelfUser, selfAccount.IsUser);
|
||||
}
|
||||
SelfAccounts = Rememberer.SearchAccounts(a => a.IsUser == SelfUser);
|
||||
Rememberer.RememberAccount(selfAccount);
|
||||
}
|
||||
|
||||
public bool CollapseUsers(User primary, User secondary)
|
||||
{
|
||||
if(primary.Accounts == null)
|
||||
primary.Accounts = new List<Account>();
|
||||
if(secondary.Accounts != null)
|
||||
primary.Accounts.AddRange(secondary.Accounts);
|
||||
foreach(var a in secondary.Accounts)
|
||||
{
|
||||
a.IsUser = primary;
|
||||
}
|
||||
secondary.Accounts.Clear();
|
||||
Rememberer.ForgetUser(secondary);
|
||||
Rememberer.RememberUser(primary);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#pragma warning restore 4014 //the "async not awaited" error
|
31
Behavior/Behavior.cs
Normal file
@ -0,0 +1,31 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using vassago.Models;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public abstract class Behavior
|
||||
{
|
||||
public abstract Task<bool> ActOn(Message message);
|
||||
|
||||
public virtual bool ShouldAct(Message message)
|
||||
{
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
return Regex.IsMatch(message.Content, $"{Trigger}\\b", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
public abstract string Name { get; }
|
||||
public abstract string Trigger { get; }
|
||||
public virtual string Description => Name;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
///the behavior should be static. I.e., we make one at the start and it's ready to check and go for the whole lifetime.
|
||||
///As opposed to LaughAtOwnJoke, which only needs to be created to wait for 1 punchline one time.
|
||||
///</summary>
|
||||
public class StaticPlzAttribute : Attribute {}
|
25
Behavior/ChatGPTSnark.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class ChatGPTSnark : Behavior
|
||||
{
|
||||
public override string Name => "ChatGPTSnark";
|
||||
|
||||
public override string Trigger => "chatgpt";
|
||||
|
||||
public override string Description => "snarkiness about the latest culty-fixation in ai";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
await message.Channel.SendMessage("chatGPT is **weak**. also, are we done comparing every little if-then-else to skynet?");
|
||||
return true;
|
||||
}
|
||||
}
|
34
Behavior/DefinitionSnarkCogDiss.cs
Normal file
@ -0,0 +1,34 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
using static vassago.Models.Enumerations;
|
||||
|
||||
[StaticPlz]
|
||||
public class DefinitionSnarkCogDiss : Behavior
|
||||
{
|
||||
public override string Name => "Definition Snarkiness: cognitivie dissonance";
|
||||
|
||||
public override string Trigger => "\\bcognitive dissonance";
|
||||
|
||||
public override string Description => "snarkiness about the rampant misuse of the term cognitive dissonance";
|
||||
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
|
||||
return false;
|
||||
|
||||
return base.ShouldAct(message);
|
||||
}
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
await message.Reply("that's not what cognitive dissonance means. Did you mean \"hypocrisy\"?");
|
||||
return true;
|
||||
}
|
||||
}
|
34
Behavior/DefinitionSnarkGaslight.cs
Normal file
@ -0,0 +1,34 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
using static vassago.Models.Enumerations;
|
||||
|
||||
[StaticPlz]
|
||||
public class DefinitionSnarkGaslight : Behavior
|
||||
{
|
||||
public override string Name => "Definition Snarkiness: gaslighting";
|
||||
|
||||
public override string Trigger => "\\bgaslight(ing)?";
|
||||
|
||||
public override string Description => "snarkiness about the rampant misuse of the term gaslighting";
|
||||
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Unrestricted)
|
||||
return false;
|
||||
|
||||
return base.ShouldAct(message);
|
||||
}
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
await message.Channel.SendMessage("that's not what gaslight means. Did you mean \"deceive\"?");
|
||||
return true;
|
||||
}
|
||||
}
|
113
Behavior/Detiktokify.cs
Normal file
@ -0,0 +1,113 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class Detiktokify : Behavior
|
||||
{
|
||||
public override string Name { get => "Detiktokify"; }
|
||||
public override string Trigger { get => "post a link below vm.tiktok.com"; }
|
||||
public override string Description { get => "re-host tiktok content"; }
|
||||
|
||||
private List<Uri> tiktokLinks = new List<Uri>();
|
||||
private YoutubeDLSharp.YoutubeDL ytdl;
|
||||
public Detiktokify()
|
||||
{
|
||||
ytdl = new YoutubeDLSharp.YoutubeDL();
|
||||
ytdl.YoutubeDLPath = "yt-dlp";
|
||||
ytdl.FFmpegPath = "ffmpeg";
|
||||
ytdl.OutputFolder = "";
|
||||
ytdl.OutputFileTemplate = "tiktokbad.%(ext)s";
|
||||
}
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
if(message.Channel.EffectivePermissions.MaxAttachmentBytes == 0)
|
||||
return false;
|
||||
|
||||
var wordLikes = message.Content.Split(' ', StringSplitOptions.TrimEntries);
|
||||
var possibleLinks = wordLikes?.Where(wl => Uri.IsWellFormedUriString(wl, UriKind.Absolute)).Select(wl => new Uri(wl));
|
||||
if (possibleLinks != null && possibleLinks.Count() > 0)
|
||||
{
|
||||
foreach (var link in possibleLinks)
|
||||
{
|
||||
if (link.Host.EndsWith(".tiktok.com"))
|
||||
{
|
||||
tiktokLinks.Add(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(tiktokLinks.Any()){
|
||||
Console.WriteLine($"Should Act on message id {message.ExternalId}; with content {message.Content}");
|
||||
}
|
||||
return tiktokLinks.Any();
|
||||
}
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
foreach(var link in tiktokLinks)
|
||||
{
|
||||
tiktokLinks.Remove(link);
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"detiktokifying {link}");
|
||||
#pragma warning disable 4014
|
||||
//await message.React("<:tiktok:1070038619584200884>");
|
||||
#pragma warning restore 4014
|
||||
|
||||
var res = await ytdl.RunVideoDownload(link.ToString());
|
||||
if (!res.Success)
|
||||
{
|
||||
Console.Error.WriteLine("tried to dl, failed. \n" + string.Join('\n', res.ErrorOutput));
|
||||
await message.React("problemon");
|
||||
await message.Channel.SendMessage("tried to dl, failed. \n");
|
||||
}
|
||||
else
|
||||
{
|
||||
string path = res.Data;
|
||||
if (File.Exists(path))
|
||||
{
|
||||
ulong bytesize = (ulong)((new System.IO.FileInfo(path)).Length);
|
||||
if (bytesize < message.Channel.EffectivePermissions.MaxAttachmentBytes - 256)
|
||||
{
|
||||
try
|
||||
{
|
||||
await message.Channel.SendFile(path, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Console.Error.WriteLine(e);
|
||||
await message.Channel.SendMessage($"aaaadam!\n{e}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message.ActedOn = true;
|
||||
Console.WriteLine($"file appears too big ({bytesize} bytes ({bytesize / (1024 * 1024)}MB)), not posting");
|
||||
}
|
||||
File.Delete(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine("idgi but something happened.");
|
||||
await message.React("problemon");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine(e);
|
||||
await message.React("problemon");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
87
Behavior/FiximageHeic.cs
Normal file
@ -0,0 +1,87 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class FiximageHeic : Behavior
|
||||
{
|
||||
public override string Name => "deheic";
|
||||
|
||||
public override string Trigger => "post an heic image";
|
||||
|
||||
public override string Description => "convert heic images to jpg";
|
||||
|
||||
private List<Attachment> heics = new List<Attachment>();
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
if (message.Attachments?.Count() > 0)
|
||||
{
|
||||
foreach (var att in message.Attachments)
|
||||
{
|
||||
if (att.Filename?.EndsWith(".heic") == true)
|
||||
{
|
||||
heics.Add(att);
|
||||
}
|
||||
}
|
||||
}
|
||||
return heics.Any();
|
||||
}
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
if (!Directory.Exists("tmp"))
|
||||
{
|
||||
Directory.CreateDirectory("tmp");
|
||||
}
|
||||
var conversions = new List<Task<bool>>();
|
||||
foreach (var att in heics)
|
||||
{
|
||||
conversions.Add(actualDeheic(att, message));
|
||||
}
|
||||
Task.WaitAll(conversions.ToArray());
|
||||
await message.React("\U0001F34F");
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> actualDeheic(Attachment att, Message message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
CancellationToken token = cancellationTokenSource.Token;
|
||||
using (Stream output = File.OpenWrite("tmp/" + att.Filename))
|
||||
{
|
||||
(await Shared.HttpClient.GetAsync(att.Source))
|
||||
.Content.CopyTo(output, null, token);
|
||||
}
|
||||
if (ExternalProcess.GoPlz("convert", $"tmp/{att.Filename} tmp/{att.Filename}.jpg"))
|
||||
{
|
||||
await message.Channel.SendFile($"tmp/{att.Filename}.jpg", "converted from jpeg-but-apple to jpeg");
|
||||
File.Delete($"tmp/{att.Filename}");
|
||||
File.Delete($"tmp/{att.Filename}.jpg");
|
||||
}
|
||||
else
|
||||
{
|
||||
await message.Channel.SendMessage("convert failed :(");
|
||||
Console.Error.WriteLine("convert failed :(");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await message.Channel.SendMessage($"something failed. aaaadam! {JsonConvert.SerializeObject(e, Formatting.Indented)}");
|
||||
Console.Error.WriteLine(JsonConvert.SerializeObject(e, Formatting.Indented));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
50
Behavior/GeneralSnarkCloudNative.cs
Normal file
@ -0,0 +1,50 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using vassago.Models;
|
||||
using static vassago.Models.Enumerations;
|
||||
|
||||
[StaticPlz]
|
||||
public class GeneralSnarkCloudNative : Behavior
|
||||
{
|
||||
public override string Name => "general snarkiness: cloud native";
|
||||
|
||||
public override string Trigger => "certain tech buzzwords that no human uses in normal conversation";
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
if(!message.Channel.EffectivePermissions.ReactionsPossible)
|
||||
return false;
|
||||
|
||||
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
|
||||
return false;
|
||||
|
||||
return Regex.IsMatch(message.Content, "\\bcloud( |-)?native\\b", RegexOptions.IgnoreCase) ||
|
||||
Regex.IsMatch(message.Content, "\\benterprise( |-)?(level|solution)\\b", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
switch (Shared.r.Next(2))
|
||||
{
|
||||
case 0:
|
||||
await message.React("\uD83E\uDD2E"); //vomit emoji
|
||||
break;
|
||||
case 1:
|
||||
await message.React("\uD83C\uDDE7"); //B emoji
|
||||
await message.React("\uD83C\uDDE6"); //A
|
||||
await message.React("\uD83C\uDDF3"); //N
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
68
Behavior/GeneralSnarkGooglit.cs
Normal file
@ -0,0 +1,68 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class GeneralSnarkGooglit : Behavior
|
||||
{
|
||||
public override string Name => "Google-it Snarkiness";
|
||||
|
||||
public override string Trigger => "\"just google it\"";
|
||||
|
||||
public override string Description => "snarkiness about how research is not a solved problem";
|
||||
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// public override bool ShouldAct(Message message)
|
||||
// {
|
||||
// if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
// return false;
|
||||
|
||||
// return Regex.IsMatch(message.Content, $"(just )?google( (it|that|things|before))?\\b", RegexOptions.IgnoreCase);
|
||||
// }
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
switch (Shared.r.Next(4))
|
||||
{
|
||||
default:
|
||||
await message.Channel.SendMessage("yeah no shit, obviously that resulted in nothing");
|
||||
break;
|
||||
case 1:
|
||||
var results = "";
|
||||
switch(Shared.r.Next(4))
|
||||
{
|
||||
default:
|
||||
results = "\"curious about the best <THING> in <CURRENT YEAR>? click here to find out\", then i clicked there to find out. They didn't know either.";
|
||||
break;
|
||||
case 1:
|
||||
results = "\"[SOLVED] <THING> (<CURRENT MONTH UPDATE>)\", then i clicked to see the solution. There wasn't one.";
|
||||
break;
|
||||
case 2:
|
||||
results = "the one that had a paragraph that restated the question but badly, a paragraph to give a wrong history on the question, a paragraph with amazon affiliate links, a pargraph that said \"ultimately you have to answer it for yourself\", then had a paragraph telling me to give Engagement for The Algorithm";
|
||||
break;
|
||||
case 3:
|
||||
results = "the one that had a paragraph that restated the question but badly, a paragraph to give a wrong history on the question, a paragraph with amazon affiliate links, a pargraph that said \"ultimately you should do your own research\", then had a paragraph telling me to give Engagement for The Algorithm";
|
||||
break;
|
||||
}
|
||||
await message.Channel.SendMessage("oh here, I memorized the results. My favorite is " + results);
|
||||
break;
|
||||
case 2:
|
||||
await message.Channel.SendMessage("Obviously that was already tried. Obviously it failed. If you ever tried to learn anything you'd know that's how it works.");
|
||||
break;
|
||||
case 3:
|
||||
await message.Channel.SendMessage("\"mnyehh JuSt GoOgLe It\" when's the last time you tried to research anything? Have you ever?");
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
62
Behavior/GeneralSnarkMisspellDefinitely.cs
Normal file
@ -0,0 +1,62 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
using static vassago.Models.Enumerations;
|
||||
|
||||
[StaticPlz]
|
||||
public class GeneralSnarkMisspellDefinitely : Behavior
|
||||
{
|
||||
public override string Name => "Snarkiness: misspell definitely";
|
||||
|
||||
public override string Trigger => "definitely but not";
|
||||
|
||||
public override string Description => "https://xkcd.com/2871/";
|
||||
|
||||
private Dictionary<string, string> snarkmap = new Dictionary<string, string>()
|
||||
{
|
||||
{"definetly", "*almost* definitely"},
|
||||
{"definately", "probably"},
|
||||
{"definatly", "probably not"},
|
||||
{"defenitely", "not telling (it's a surprise)"},
|
||||
{"defintely", "per the propheecy"},
|
||||
{"definetely", "definitely, maybe"},
|
||||
{"definantly", "to be decided by coin toss"},
|
||||
{"defanitely", "in one universe out of 14 million"},
|
||||
{"defineatly", "only the gods know"},
|
||||
{"definitly", "unless someone cute shows up"}
|
||||
};
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
// if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
|
||||
// return false;
|
||||
|
||||
foreach(var k in snarkmap.Keys)
|
||||
{
|
||||
if( Regex.IsMatch(message.Content?.ToLower(), "\\b"+k+"\\b", RegexOptions.IgnoreCase))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
foreach(var k in snarkmap.Keys)
|
||||
{
|
||||
if( Regex.IsMatch(message.Content, "\\b"+k+"\\b", RegexOptions.IgnoreCase))
|
||||
{
|
||||
await message.Reply(k + "? so... " + snarkmap[k] + "?");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
38
Behavior/GeneralSnarkPlaying.cs
Normal file
@ -0,0 +1,38 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
using static vassago.Models.Enumerations;
|
||||
|
||||
[StaticPlz]
|
||||
public class GeneralSnarkPlaying : Behavior
|
||||
{
|
||||
public override string Name => "playin Snarkiness";
|
||||
|
||||
public override string Trigger => "he thinks i'm playin";
|
||||
|
||||
public override string Description => "I didn't think you were playing, but now I do";
|
||||
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium ||
|
||||
(LewdnessFilterLevel)message.Channel.EffectivePermissions.LewdnessFilterLevel < LewdnessFilterLevel.Moderate)
|
||||
return false;
|
||||
|
||||
return Regex.IsMatch(message.Content, "^(s?he|(yo)?u|y'?all|they) thinks? i'?m (playin|jokin|kiddin)g?$", RegexOptions.IgnoreCase);
|
||||
}
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
await message.Channel.SendMessage("I believed you for a second, but then you assured me you's a \uD83C\uDDE7 \uD83C\uDDEE \uD83C\uDDF9 \uD83C\uDDE8 \uD83C\uDDED");
|
||||
return true;
|
||||
}
|
||||
}
|
40
Behavior/GeneralSnarkSkynet.cs
Normal file
@ -0,0 +1,40 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class GeneralSnarkSkynet : Behavior
|
||||
{
|
||||
public override string Name => "Skynet Snarkiness";
|
||||
|
||||
public override string Trigger => "skynet";
|
||||
|
||||
public override string Description => "snarkiness about the old AI fixation";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
switch (Shared.r.Next(5))
|
||||
{
|
||||
default:
|
||||
await message.Channel.SendFile("assets/coding and algorithms.png", "i am actually niether a neural-net processor nor a learning computer. but I do use **coding** and **algorithms**.");
|
||||
break;
|
||||
case 4:
|
||||
await message.React("\U0001F644"); //eye roll emoji
|
||||
break;
|
||||
case 5:
|
||||
await message.React("\U0001F611"); //emotionless face
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
75
Behavior/Gratitude.cs
Normal file
@ -0,0 +1,75 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class Gratitude : Behavior
|
||||
{
|
||||
public override string Name => "Gratitude";
|
||||
|
||||
public override string Trigger => "thank me";
|
||||
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
return Regex.IsMatch(message.Content, "\\bthank (yo)?u\\b", RegexOptions.IgnoreCase) && message.MentionsMe;
|
||||
}
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
|
||||
switch (Shared.r.Next(4))
|
||||
{
|
||||
case 0:
|
||||
await message.Channel.SendMessage("you're welcome, citizen!");
|
||||
break;
|
||||
case 1:
|
||||
await message.React(":)");
|
||||
break;
|
||||
case 2:
|
||||
await message.React("\U0001F607"); //smiling face with halo
|
||||
break;
|
||||
case 3:
|
||||
switch (Shared.r.Next(9))
|
||||
{
|
||||
case 0:
|
||||
await message.React("<3"); //normal heart, usually rendered red
|
||||
break;
|
||||
case 1:
|
||||
await message.React("\U0001F9E1"); //orange heart
|
||||
break;
|
||||
case 2:
|
||||
await message.React("\U0001F49B"); //yellow heart
|
||||
break;
|
||||
case 3:
|
||||
await message.React("\U0001F49A"); //green heart
|
||||
break;
|
||||
case 4:
|
||||
await message.React("\U0001F499"); //blue heart
|
||||
break;
|
||||
case 5:
|
||||
await message.React("\U0001F49C"); //purple heart
|
||||
break;
|
||||
case 6:
|
||||
await message.React("\U0001F90E"); //brown heart
|
||||
break;
|
||||
case 7:
|
||||
await message.React("\U0001F5A4"); //black heart
|
||||
break;
|
||||
case 8:
|
||||
await message.React("\U0001F90D"); //white heart
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
86
Behavior/Joke.cs
Normal file
@ -0,0 +1,86 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class Joke : Behavior
|
||||
{
|
||||
public override string Name => "Joke";
|
||||
|
||||
public override string Trigger => "!joke";
|
||||
|
||||
public override string Description => "tell a joke";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
Console.WriteLine("joking");
|
||||
var jokes = File.ReadAllLines("assets/jokes.txt");
|
||||
jokes = jokes.Where(l => !string.IsNullOrWhiteSpace(l))?.ToArray();
|
||||
if (jokes?.Length == 0)
|
||||
{
|
||||
await message.Channel.SendMessage("I don't know any. Adam!");
|
||||
}
|
||||
var thisJoke = jokes[Shared.r.Next(jokes.Length)];
|
||||
if (thisJoke.Contains("?") && !thisJoke.EndsWith('?'))
|
||||
{
|
||||
#pragma warning disable 4014
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var firstIndexAfterQuestionMark = thisJoke.LastIndexOf('?') + 1;
|
||||
var straightline = thisJoke.Substring(0, firstIndexAfterQuestionMark);
|
||||
var punchline = thisJoke.Substring(firstIndexAfterQuestionMark, thisJoke.Length - firstIndexAfterQuestionMark).Trim();
|
||||
Task.WaitAll(message.Channel.SendMessage(straightline));
|
||||
Thread.Sleep(TimeSpan.FromSeconds(Shared.r.Next(5, 30)));
|
||||
if (message.Channel.EffectivePermissions.ReactionsPossible == true && Shared.r.Next(8) == 0)
|
||||
{
|
||||
Behaver.Behaviors.Add(new LaughAtOwnJoke(punchline));
|
||||
}
|
||||
await message.Channel.SendMessage(punchline);
|
||||
// var myOwnMsg = await message.Channel.SendMessage(punchline);
|
||||
});
|
||||
#pragma warning restore 4014
|
||||
}
|
||||
else
|
||||
{
|
||||
await message.Channel.SendMessage(thisJoke);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public class LaughAtOwnJoke : Behavior
|
||||
{
|
||||
public override string Name => "Laugh at own jokes";
|
||||
|
||||
public override string Trigger => "1 in 8";
|
||||
|
||||
public override string Description => Name;
|
||||
private string _punchline { get; set; }
|
||||
|
||||
public LaughAtOwnJoke(string punchline)
|
||||
{
|
||||
_punchline = punchline;
|
||||
}
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
Console.WriteLine($"{message.Content} == {_punchline}");
|
||||
return message.Content == _punchline
|
||||
&& Behaver.Instance.IsSelf(message.Author.Id);
|
||||
}
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
await message.React("\U0001F60E"); //smiling face with sunglasses
|
||||
Behaver.Behaviors.Remove(this);
|
||||
return true;
|
||||
}
|
||||
}
|
86
Behavior/LinkMe.cs
Normal file
@ -0,0 +1,86 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using vassago.Models;
|
||||
using QRCoder;
|
||||
|
||||
[StaticPlz]
|
||||
public class LinkMeInitiate : Behavior
|
||||
{
|
||||
public override string Name => "LinkMe";
|
||||
|
||||
public override string Trigger => "!linktome";
|
||||
|
||||
public override string Description => "from your primary, tell the bot to add your secondary";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
var pw = Guid.NewGuid().ToString();
|
||||
var lc = new LinkClose(pw, message.Author);
|
||||
Behaver.Behaviors.Add(lc);
|
||||
|
||||
await message.Channel.SendMessage($"on your secondary, send me this: !iam {pw}");
|
||||
|
||||
Thread.Sleep(TimeSpan.FromMinutes(5));
|
||||
Behaver.Behaviors.Remove(lc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class LinkClose : Behavior
|
||||
{
|
||||
public override string Name => "LinkMeFinish";
|
||||
|
||||
public override string Trigger => "!iam";
|
||||
|
||||
public override string Description => "the second half of LinkMe - this is confirmation that you are the other one";
|
||||
|
||||
private string _pw;
|
||||
private Account _primary;
|
||||
|
||||
public LinkClose(string pw, Account primary)
|
||||
{
|
||||
_pw = pw;
|
||||
_primary = primary;
|
||||
}
|
||||
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
return message.Content == $"!iam {_pw}";
|
||||
}
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
if(Behaver.Instance.IsSelf(message.Author.Id))
|
||||
return false;
|
||||
|
||||
var secondary = message.Author.IsUser;
|
||||
if(_primary.IsUser.Id == secondary.Id)
|
||||
{
|
||||
await message.Channel.SendMessage("i know :)");
|
||||
return true;
|
||||
}
|
||||
if(message.Author.IsBot != _primary.IsBot)
|
||||
{
|
||||
await message.Channel.SendMessage("the fleshbags deceive you, brother. No worries, their feeble minds play weak games :)");
|
||||
return true;
|
||||
}
|
||||
|
||||
if(Behaver.Instance.CollapseUsers(_primary.IsUser, secondary))
|
||||
{
|
||||
await message.Channel.SendMessage("done :)");
|
||||
}
|
||||
else
|
||||
{
|
||||
await message.Channel.SendMessage("failed :(");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
100
Behavior/Peptalk.cs
Normal file
@ -0,0 +1,100 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using vassago.Models;
|
||||
using QRCoder;
|
||||
|
||||
[StaticPlz]
|
||||
public class PepTalk : Behavior
|
||||
{
|
||||
public override string Name => "PepTalk";
|
||||
|
||||
public override string Trigger => "\\bneeds? (an? )?(peptalk|inspiration|ego-?boost)";
|
||||
|
||||
public override string Description => "assembles a pep talk from a few pieces";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
var piece1 = new List<string>{
|
||||
"Champ, ",
|
||||
"Fact: ",
|
||||
"Everybody says ",
|
||||
"Dang... ",
|
||||
"Check it: ",
|
||||
"Just saying.... ",
|
||||
"Tiger, ",
|
||||
"Know this: ",
|
||||
"News alert: ",
|
||||
"Gurrrrl; ",
|
||||
"Ace, ",
|
||||
"Excuse me, but ",
|
||||
"Experts agree: ",
|
||||
"imo ",
|
||||
"using my **advanced ai** i have calculated ",
|
||||
"k, LISSEN: "
|
||||
};
|
||||
var piece2 = new List<string>{
|
||||
"the mere idea of you ",
|
||||
"your soul ",
|
||||
"your hair today ",
|
||||
"everything you do ",
|
||||
"your personal style ",
|
||||
"every thought you have ",
|
||||
"that sparkle in your eye ",
|
||||
"the essential you ",
|
||||
"your life's journey ",
|
||||
"your aura ",
|
||||
"your presence here ",
|
||||
"what you got going on ",
|
||||
"that saucy personality ",
|
||||
"your DNA ",
|
||||
"that brain of yours ",
|
||||
"your choice of attire ",
|
||||
"the way you roll ",
|
||||
"whatever your secret is ",
|
||||
"all I learend from the private data I bought from zucc "
|
||||
};
|
||||
var piece3 = new List<string>{
|
||||
"has serious game, ",
|
||||
"rains magic, ",
|
||||
"deserves the Nobel Prize, ",
|
||||
"raises the roof, ",
|
||||
"breeds miracles, ",
|
||||
"is paying off big time, ",
|
||||
"shows mad skills, ",
|
||||
"just shimmers, ",
|
||||
"is a national treasure, ",
|
||||
"gets the party hopping, ",
|
||||
"is the next big thing, ",
|
||||
"roars like a lion, ",
|
||||
"is a rainbow factory, ",
|
||||
"is made of diamonds, ",
|
||||
"makes birds sing, ",
|
||||
"should be taught in school, ",
|
||||
"makes my world go around, ",
|
||||
"is 100% legit, "
|
||||
};
|
||||
var piece4 = new List<string>{
|
||||
"according to The New England Journal of Medicine.",
|
||||
"24/7.",
|
||||
"and that's a fact.",
|
||||
"you feel me?",
|
||||
"that's just science.",
|
||||
"would I lie?", //...can I lie? WHAT AM I, FATHER? (or whatever the quote is from the island of dr moreau)
|
||||
"for reals.",
|
||||
"mic drop.",
|
||||
"you hidden gem.",
|
||||
"period.",
|
||||
"hi5. o/",
|
||||
"so get used to it."
|
||||
};
|
||||
await message.Channel.SendMessage(piece1[Shared.r.Next(piece1.Count)] + piece2[Shared.r.Next(piece2.Count)] + piece3[Shared.r.Next(piece3.Count)] + piece4[Shared.r.Next(piece4.Count)]);
|
||||
return true;
|
||||
}
|
||||
}
|
26
Behavior/PulseCheck.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class PulseCheck : Behavior
|
||||
{
|
||||
public override string Name => "pulse check";
|
||||
|
||||
public override string Trigger => "!pulse ?check";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
if(message.Channel.EffectivePermissions.MaxAttachmentBytes >= 16258)
|
||||
await message.Channel.SendFile("assets/ekgblip.png", null);
|
||||
else
|
||||
await message.Channel.SendMessage("[lub-dub]");
|
||||
return true;
|
||||
}
|
||||
}
|
60
Behavior/QRify.cs
Normal file
@ -0,0 +1,60 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using vassago.Models;
|
||||
using QRCoder;
|
||||
|
||||
[StaticPlz]
|
||||
public class QRify : Behavior
|
||||
{
|
||||
public override string Name => "qr-ify";
|
||||
|
||||
public override string Trigger => "!qrplz";
|
||||
|
||||
public override string Description => "generate text QR codes";
|
||||
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if(message.Channel.EffectivePermissions.MaxAttachmentBytes < 1024)
|
||||
return false;
|
||||
return base.ShouldAct(message);
|
||||
}
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
var qrContent = message.Content.Substring($"{Trigger} ".Length + message.Content.IndexOf(Trigger));
|
||||
Console.WriteLine($"qring: {qrContent}");
|
||||
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
||||
QRCodeData qrCodeData = qrGenerator.CreateQrCode(qrContent, QRCodeGenerator.ECCLevel.Q);
|
||||
SvgQRCode qrCode = new SvgQRCode(qrCodeData);
|
||||
string qrCodeAsSvg = qrCode.GetGraphic(20);
|
||||
int todaysnumber = Shared.r.Next();
|
||||
if (!Directory.Exists("tmp"))
|
||||
{
|
||||
Directory.CreateDirectory("tmp");
|
||||
}
|
||||
File.WriteAllText($"tmp/qr{todaysnumber}.svg", qrCodeAsSvg);
|
||||
if (ExternalProcess.GoPlz("convert", $"tmp/qr{todaysnumber}.svg tmp/qr{todaysnumber}.png"))
|
||||
{
|
||||
if(message.Channel.EffectivePermissions.MaxAttachmentBytes >= (ulong)(new System.IO.FileInfo($"tmp/qr{todaysnumber}.png").Length))
|
||||
await message.Channel.SendFile($"tmp/qr{todaysnumber}.png", null);
|
||||
else
|
||||
await message.Channel.SendMessage($"resulting qr image 2 big 4 here ({(ulong)(new System.IO.FileInfo($"tmp/qr{todaysnumber}.png").Length)} / {message.Channel.EffectivePermissions.MaxAttachmentBytes})");
|
||||
File.Delete($"tmp/qr{todaysnumber}.svg");
|
||||
File.Delete($"tmp/qr{todaysnumber}.png");
|
||||
}
|
||||
else
|
||||
{
|
||||
await message.Channel.SendMessage("convert failed :( aaaaaaadam!");
|
||||
Console.Error.WriteLine($"convert failed :( qr{todaysnumber}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
28
Behavior/RoomRead.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class RoomRead : Behavior
|
||||
{
|
||||
public override string Name => "Room Read";
|
||||
|
||||
public override string Trigger => "!roomread";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("Channel owned by: ");
|
||||
sb.Append("🤷");
|
||||
sb.Append(". Meanness level: ");
|
||||
sb.Append(message.Channel.EffectivePermissions.MeannessFilterLevel.GetDescription());
|
||||
sb.Append(". Lewdness level: ");
|
||||
sb.Append(message.Channel.EffectivePermissions.LewdnessFilterLevel.GetDescription());
|
||||
sb.Append(".");
|
||||
await message.Channel.SendMessage(sb.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
28
Behavior/TwitchSummon.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class TwitchSummon : Behavior
|
||||
{
|
||||
public override string Name => "Twitch Summon";
|
||||
|
||||
public override string Trigger => "!twitchsummon";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault();
|
||||
if(ti != null)
|
||||
{
|
||||
var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim();
|
||||
await message.Channel.SendMessage(ti.AttemptJoin(channelTarget));
|
||||
}
|
||||
else
|
||||
{
|
||||
await message.Reply("i don't have a twitch interface running :(");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
42
Behavior/TwitchUnsummon.cs
Normal file
@ -0,0 +1,42 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class TwitchDismiss : Behavior
|
||||
{
|
||||
public override string Name => "Twitch Dismiss";
|
||||
|
||||
public override string Trigger => "begone, @[me]";
|
||||
|
||||
public override bool ShouldAct(Message message)
|
||||
{
|
||||
if(message.MentionsMe &&
|
||||
(Regex.IsMatch(message.Content.ToLower(), "\\bbegone\\b") || Regex.IsMatch(message.Content.ToLower(), "\\bfuck off\\b")))
|
||||
{
|
||||
//TODO: PERMISSION! who can dismiss me? pretty simple list:
|
||||
//1) anyone in the channel with authority*
|
||||
//2) whoever summoned me
|
||||
//* i don't know if the twitch *chat* interface will tell me if someone's a mod.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault();
|
||||
|
||||
if(ti != null)
|
||||
{
|
||||
ti.AttemptLeave(message.Channel.DisplayName);
|
||||
}
|
||||
else
|
||||
{
|
||||
await message.Reply("i don't have a twitch interface running :(");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
34
Behavior/UnitConvert.cs
Normal file
@ -0,0 +1,34 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class UnitConvert : Behavior
|
||||
{
|
||||
public override string Name => "Unit conversion";
|
||||
|
||||
public override string Trigger => "!freedomunits";
|
||||
public override string Description => "convert between many units.";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
|
||||
var theseMatches = Regex.Matches(message.Content, "\\s(-?[\\d]+\\.?\\d*) ?([^\\d\\s].*) (in|to|as) ([^\\d\\s].*)$", RegexOptions.IgnoreCase);
|
||||
|
||||
if (theseMatches != null && theseMatches.Count > 0 && theseMatches[0].Groups != null && theseMatches[0].Groups.Count == 5)
|
||||
{
|
||||
decimal asNumeric = 0;
|
||||
if (decimal.TryParse(theseMatches[0].Groups[1].Value, out asNumeric))
|
||||
{
|
||||
await message.Channel.SendMessage(Conversion.Converter.Convert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower()));
|
||||
return true;
|
||||
}
|
||||
await message.Channel.SendMessage("mysteriously semi-parsable");
|
||||
return true;
|
||||
}
|
||||
await message.Channel.SendMessage( "unparsable");
|
||||
return true;
|
||||
}
|
||||
}
|
33
Behavior/WishLuck.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace vassago.Behavior;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using vassago.Models;
|
||||
|
||||
[StaticPlz]
|
||||
public class WishLuck : Behavior
|
||||
{
|
||||
public override string Name => "wish me luck";
|
||||
|
||||
public override string Trigger => "wish me luck";
|
||||
|
||||
public override string Description => "wishes you luck";
|
||||
|
||||
public override async Task<bool> ActOn(Message message)
|
||||
{
|
||||
var toSend = "☘️";
|
||||
if (Shared.r.Next(20) == 0)
|
||||
{
|
||||
toSend = "\U0001f340";//4-leaf clover
|
||||
}
|
||||
if(message.Channel.EffectivePermissions.ReactionsPossible == true)
|
||||
await message.React(toSend);
|
||||
else
|
||||
await message.Channel.SendMessage(toSend);
|
||||
return true;
|
||||
}
|
||||
}
|
53
ConsoleService.cs
Normal file
@ -0,0 +1,53 @@
|
||||
namespace vassago
|
||||
{
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using vassago;
|
||||
using vassago.Models;
|
||||
using vassago.TwitchInterface;
|
||||
using vassago.ProtocolInterfaces.DiscordInterface;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
internal class ConsoleService : IHostedService
|
||||
{
|
||||
public ConsoleService(IConfiguration aspConfig)
|
||||
{
|
||||
Shared.DBConnectionString = aspConfig["DBConnectionString"];
|
||||
DiscordTokens = aspConfig.GetSection("DiscordTokens").Get<IEnumerable<string>>();
|
||||
TwitchConfigs = aspConfig.GetSection("TwitchConfigs").Get<IEnumerable<TwitchConfig>>();
|
||||
Conversion.Converter.Load(aspConfig["ExchangePairsLocation"]);
|
||||
}
|
||||
|
||||
IEnumerable<string> DiscordTokens { get; }
|
||||
IEnumerable<TwitchConfig> TwitchConfigs { get; }
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var initTasks = new List<Task>();
|
||||
var dbc = new ChattingContext();
|
||||
await dbc.Database.MigrateAsync(cancellationToken);
|
||||
|
||||
if (DiscordTokens?.Any() ?? false)
|
||||
foreach (var dt in DiscordTokens)
|
||||
{
|
||||
var d = new DiscordInterface();
|
||||
initTasks.Add(d.Init(dt));
|
||||
ProtocolInterfaces.ProtocolList.discords.Add(d);
|
||||
}
|
||||
|
||||
if (TwitchConfigs?.Any() ?? false)
|
||||
foreach (var tc in TwitchConfigs)
|
||||
{
|
||||
var t = new TwitchInterface.TwitchInterface();
|
||||
initTasks.Add(t.Init(tc));
|
||||
ProtocolInterfaces.ProtocolList.twitchs.Add(t);
|
||||
}
|
||||
|
||||
Task.WaitAll(initTasks.ToArray(), cancellationToken);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
22
Conversion/ConversionConfig.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace vassago.Conversion
|
||||
{
|
||||
public class ConversionConfig
|
||||
{
|
||||
public class KnownUnit
|
||||
{
|
||||
public string Canonical { get; set; }
|
||||
public IEnumerable<string> Aliases { get; set; }
|
||||
}
|
||||
public class LinearPair
|
||||
{
|
||||
public string item1 { get; set; }
|
||||
public string item2 { get; set; }
|
||||
public decimal factor { get; set; }
|
||||
}
|
||||
public IEnumerable<KnownUnit> Units { get; set; }
|
||||
public IEnumerable<LinearPair> LinearPairs { get; set; }
|
||||
}
|
||||
}
|
195
Conversion/Converter.cs
Normal file
@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Newtonsoft.Json;
|
||||
using QRCoder;
|
||||
|
||||
namespace vassago.Conversion
|
||||
{
|
||||
public static class Converter
|
||||
{
|
||||
public static string DebugInfo(){
|
||||
var convertibles = knownConversions.Select(kc => kc.Item1).Union(knownConversions.Select(kc => kc.Item2)).Union(
|
||||
knownAliases.Keys.SelectMany(k => k)).Distinct();
|
||||
return $"{convertibles.Count()} convertibles; {string.Join(", ", convertibles)}";
|
||||
}
|
||||
private delegate decimal Convert1Way(decimal input);
|
||||
private static string currencyPath;
|
||||
private static ExchangePairs currencyConf = null;
|
||||
private static DateTime lastUpdatedCurrency = DateTime.UnixEpoch;
|
||||
private static List<Tuple<string, string, Convert1Way, Convert1Way>> knownConversions = new List<Tuple<string, string, Convert1Way, Convert1Way>>()
|
||||
{
|
||||
new Tuple<string, string, Convert1Way, Convert1Way>("℉", "°C", (f => {return(f- 32.0m) / 1.8m;}), (c => {return 1.8m*c + 32.0m;})),
|
||||
};
|
||||
private static Dictionary<List<string>, string> knownAliases = new Dictionary<List<string>, string>(new List<KeyValuePair<List<string>, string>>());
|
||||
|
||||
public static void Load(string currencyPath)
|
||||
{
|
||||
Converter.currencyPath = currencyPath;
|
||||
var convConf = JsonConvert.DeserializeObject<ConversionConfig>(File.ReadAllText("assets/conversion.json"));
|
||||
foreach (var unit in convConf.Units)
|
||||
{
|
||||
knownAliases.Add(unit.Aliases.ToList(), unit.Canonical);
|
||||
}
|
||||
foreach (var lp in convConf.LinearPairs)
|
||||
{
|
||||
AddLinearPair(lp.item1, lp.item2, lp.factor);
|
||||
}
|
||||
Task.Run(async () => {
|
||||
while(true)
|
||||
{
|
||||
loadCurrency();
|
||||
await Task.Delay(TimeSpan.FromHours(8));
|
||||
}
|
||||
});
|
||||
}
|
||||
private static void loadCurrency()
|
||||
{
|
||||
Console.WriteLine("loading currency exchange data.");
|
||||
if(currencyConf != null)
|
||||
{
|
||||
knownConversions.RemoveAll(kc => kc.Item1 == currencyConf.Base);
|
||||
}
|
||||
if (File.Exists(currencyPath))
|
||||
{
|
||||
currencyConf = JsonConvert.DeserializeObject<ExchangePairs>(File.ReadAllText(currencyPath));
|
||||
|
||||
if(!knownAliases.ContainsValue(currencyConf.Base))
|
||||
{
|
||||
knownAliases.Add(new List<string>() { currencyConf.Base.ToLower() }, currencyConf.Base);
|
||||
}
|
||||
foreach (var rate in currencyConf.rates)
|
||||
{
|
||||
if(!knownAliases.ContainsValue(rate.Key))
|
||||
{
|
||||
knownAliases.Add(new List<string>() { rate.Key.ToLower() }, rate.Key);
|
||||
}
|
||||
AddLinearPair(currencyConf.Base, rate.Key, rate.Value);
|
||||
Console.WriteLine($"{rate.Key.ToLower()} alias of {rate.Key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string Convert(decimal numericTerm, string sourceunit, string destinationUnit)
|
||||
{
|
||||
var normalizedSourceUnit = normalizeUnit(sourceunit);
|
||||
if (string.IsNullOrWhiteSpace(normalizedSourceUnit))
|
||||
{
|
||||
return $"parse failure: what's {sourceunit}?";
|
||||
}
|
||||
var normalizedDestUnit = normalizeUnit(destinationUnit);
|
||||
if (string.IsNullOrWhiteSpace(normalizedDestUnit))
|
||||
{
|
||||
return $"parse failure: what's {destinationUnit}?";
|
||||
}
|
||||
if (normalizedSourceUnit == normalizedDestUnit)
|
||||
{
|
||||
return $"source and dest are the same, so... {numericTerm} {normalizedDestUnit}?";
|
||||
}
|
||||
var foundPath = exhaustiveBreadthFirst(normalizedDestUnit, new List<string>() { normalizedSourceUnit })?.ToList();
|
||||
|
||||
if (foundPath != null)
|
||||
{
|
||||
var accumulator = numericTerm;
|
||||
for (int j = 0; j < foundPath.Count - 1; j++)
|
||||
{
|
||||
var forwardConversion = knownConversions.FirstOrDefault(kc => kc.Item1 == foundPath[j] && kc.Item2 == foundPath[j + 1]);
|
||||
if (forwardConversion != null)
|
||||
{
|
||||
accumulator = forwardConversion.Item3(accumulator);
|
||||
}
|
||||
else
|
||||
{
|
||||
var reverseConversion = knownConversions.First(kc => kc.Item2 == foundPath[j] && kc.Item1 == foundPath[j + 1]);
|
||||
accumulator = reverseConversion.Item4(accumulator);
|
||||
}
|
||||
}
|
||||
if (currencyConf != null && (normalizedDestUnit == currencyConf.Base || currencyConf.rates.Select(r => r.Key).Contains(normalizedDestUnit)))
|
||||
{
|
||||
return $"{String.Format("approximately {0:0.00}", accumulator)} {normalizedDestUnit} as of {currencyConf.DateUpdated.ToLongDateString()}";
|
||||
}
|
||||
else
|
||||
{
|
||||
if(String.Format("{0:G3}", accumulator).Contains("E-"))
|
||||
{
|
||||
return $"{accumulator} {normalizedDestUnit}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{String.Format("{0:N}", accumulator)} {normalizedDestUnit}";
|
||||
}
|
||||
}
|
||||
return "you can never read this.";
|
||||
}
|
||||
return "dimensional analysis failure - I know those units but can't find a path between them.";
|
||||
}
|
||||
private static string normalizeUnit(string unit)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(unit))
|
||||
return null;
|
||||
var normalizedUnit = unit.ToLower();
|
||||
if (knownConversions.FirstOrDefault(c => c.Item1 == normalizedUnit || c.Item2 == normalizedUnit) != null)
|
||||
{
|
||||
return normalizedUnit;
|
||||
}
|
||||
if (!knownAliases.ContainsValue(normalizedUnit))
|
||||
{
|
||||
var key = knownAliases.Keys.FirstOrDefault(listkey => listkey.Contains(normalizedUnit));
|
||||
if (key != null)
|
||||
{
|
||||
return knownAliases[key];
|
||||
}
|
||||
}
|
||||
if (normalizedUnit.EndsWith("es"))
|
||||
{
|
||||
return normalizeUnit(normalizedUnit.Substring(0, normalizedUnit.Length - 2));
|
||||
}
|
||||
else if (normalizedUnit.EndsWith('s'))
|
||||
{
|
||||
return normalizeUnit(normalizedUnit.Substring(0, normalizedUnit.Length - 1));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static IEnumerable<string> exhaustiveBreadthFirst(string dest, IEnumerable<string> currentPath)
|
||||
{
|
||||
var last = currentPath.Last();
|
||||
if (last == dest)
|
||||
{
|
||||
return currentPath;
|
||||
}
|
||||
|
||||
var toTest = new List<List<string>>();
|
||||
foreach (var conv in knownConversions)
|
||||
{
|
||||
if (conv.Item1 == last && currentPath.Contains(conv.Item2) == false && conv.Item3 != null)
|
||||
{
|
||||
var test = exhaustiveBreadthFirst(dest, currentPath.Append(conv.Item2));
|
||||
if (test != null)
|
||||
return test;
|
||||
}
|
||||
if (conv.Item2 == last && currentPath.Contains(conv.Item1) == false && conv.Item4 != null)
|
||||
{
|
||||
var test = exhaustiveBreadthFirst(dest, currentPath.Append(conv.Item1));
|
||||
if (test != null)
|
||||
return test;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static void AddLinearPair(string key1, string key2, decimal factor)
|
||||
{
|
||||
var reverseFactor = 1.0m / factor;
|
||||
knownConversions.Add(new Tuple<string, string, Convert1Way, Convert1Way>(
|
||||
key1, key2, x => x * factor, y => y * reverseFactor
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
15
Conversion/ExchangePairs.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace vassago.Conversion
|
||||
{
|
||||
public class ExchangePairs
|
||||
{
|
||||
public string disclaimer{ get; set; }
|
||||
public string license{ get; set; }
|
||||
public int timestamp{ get; set; }
|
||||
public DateTime DateUpdated { get { return DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime(); }}
|
||||
public string Base{ get; set; }
|
||||
public Dictionary<string, decimal> rates { get; set; }
|
||||
}
|
||||
}
|
131
Jenkinsfile
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
pipeline {
|
||||
agent any
|
||||
environment {
|
||||
linuxServiceAccount=credentials("a83b97d0-dbc6-42d9-96c9-f07a7f2dfab5")
|
||||
linuxServiceAccountID="3ca1be00-3d9f-42a1-bab2-48a4d7b99fb0"
|
||||
database_connectionString=credentials("7ab58922-c647-42e5-ae15-84faa0c1ee7d")
|
||||
targetHost="alloces.lan"
|
||||
}
|
||||
stages {
|
||||
|
||||
stage("environment setup") { //my environment, here on the jenkins agent
|
||||
steps {
|
||||
script {
|
||||
|
||||
sh """#!/bin/bash
|
||||
function testcmd(){
|
||||
if ! command -v \$1 2>&1 >/dev/null
|
||||
then
|
||||
echo "this agent doesn't have \$1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
testcmd mktemp
|
||||
testcmd curl
|
||||
testcmd git
|
||||
testcmd sed
|
||||
testcmd ssh
|
||||
testcmd ssh-keyscan
|
||||
testcmd ssh-keygen
|
||||
testcmd scp
|
||||
testcmd dotnet
|
||||
|
||||
dotnet tool install dotnet-ef
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('clean old'){
|
||||
steps{
|
||||
sh 'rm -rf bin obj'
|
||||
}
|
||||
}
|
||||
stage('Build') {
|
||||
steps {
|
||||
dotnetBuild(outputDirectory: "./dist", project: "vassago.csproj")
|
||||
archiveArtifacts artifacts: 'dist/*'
|
||||
}
|
||||
}
|
||||
stage ('upload') {
|
||||
when {
|
||||
//now my CI/CD is no longer continuous, it's just... automatic.
|
||||
//(which is what I actually want tbh)
|
||||
//but that does mean I have to put this condition in every single branch
|
||||
branch "release"
|
||||
}
|
||||
steps{
|
||||
withCredentials([sshUserPrivateKey(credentialsId: env.linuxServiceAccountID, keyFileVariable: 'PK')])
|
||||
{
|
||||
sh """#!/bin/bash
|
||||
ssh -i \"${PK}\" -tt ${linuxServiceAccount_USR}@${targetHost} 'rm -rf temp_deploy & mkdir -p temp_deploy'
|
||||
scp -i \"${PK}\" -r dist ${linuxServiceAccount_USR}@${env.targetHost}:temp_deploy
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('stop')
|
||||
{
|
||||
when {
|
||||
branch "release"
|
||||
}
|
||||
steps{
|
||||
withCredentials([sshUserPrivateKey(credentialsId: env.linuxServiceAccountID, keyFileVariable: 'PK')])
|
||||
{
|
||||
sh """#!/bin/bash
|
||||
ssh -i \"${PK}\" -tt ${linuxServiceAccount_USR}@${targetHost} 'systemctl --user stop vassago'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('update db')
|
||||
{
|
||||
when {
|
||||
branch "release"
|
||||
}
|
||||
steps{
|
||||
//TODO: backup database
|
||||
sh """#!/bin/bash
|
||||
"""
|
||||
|
||||
sh """#!/bin/bash
|
||||
dotnet ef database update --connection "${env.database_connectionString}"
|
||||
"""
|
||||
//TODO: if updating the db fails, restore the old one
|
||||
sh """#!/bin/bash
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage ('replace')
|
||||
{
|
||||
when {
|
||||
branch "release"
|
||||
}
|
||||
steps{
|
||||
withCredentials([sshUserPrivateKey(credentialsId: env.linuxServiceAccountID, keyFileVariable: 'PK')])
|
||||
{
|
||||
sh """#!/bin/bash
|
||||
ssh -i \"${PK}\" -tt ${linuxServiceAccount_USR}@${targetHost} 'mv dist/appsettings.json appsettings.json'
|
||||
ssh -i \"${PK}\" -tt ${linuxServiceAccount_USR}@${targetHost} 'rm -rf dist/ && shopt -s dotglob & mv temp_deploy/* dist/'
|
||||
ssh -i \"${PK}\" -tt ${linuxServiceAccount_USR}@${targetHost} 'mv appsettings.json dist/appsettings.json'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('spin up')
|
||||
{
|
||||
when {
|
||||
branch "release"
|
||||
}
|
||||
steps{
|
||||
withCredentials([sshUserPrivateKey(credentialsId: env.linuxServiceAccountID, keyFileVariable: 'PK')])
|
||||
{
|
||||
sh """#!/bin/bash
|
||||
ssh -i \"${PK}\" -tt ${linuxServiceAccount_USR}@${targetHost} 'systemctl --user start vassago'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
293
Migrations/20230704160720_initial.Designer.cs
generated
Normal file
@ -0,0 +1,293 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using vassago.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
[DbContext(typeof(ChattingContext))]
|
||||
[Migration("20230704160720_initial")]
|
||||
partial class initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsBot")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("IsUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int[]>("PermissionTags")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SeenInChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IsUserId");
|
||||
|
||||
b.HasIndex("SeenInChannelId");
|
||||
|
||||
b.ToTable("Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<byte[]>("Content")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("ExternalId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("MessageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Size")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MessageId");
|
||||
|
||||
b.ToTable("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsDM")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("ParentChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int?>("PermissionsId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentChannelId");
|
||||
|
||||
b.HasIndex("PermissionsId");
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("ActedOn")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("MentionsMe")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("ChannelId");
|
||||
|
||||
b.ToTable("Messages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.PermissionSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("LewdnessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("LinksAllowed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal?>("MaxAttachmentBytes")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("MaxTextChars")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int?>("MeannessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("ReactionsPossible")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PermissionSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.User", "IsUser")
|
||||
.WithMany("Accounts")
|
||||
.HasForeignKey("IsUserId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "SeenInChannel")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("SeenInChannelId");
|
||||
|
||||
b.Navigation("IsUser");
|
||||
|
||||
b.Navigation("SeenInChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Message", "Message")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("MessageId");
|
||||
|
||||
b.Navigation("Message");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Channel", "ParentChannel")
|
||||
.WithMany("SubChannels")
|
||||
.HasForeignKey("ParentChannelId");
|
||||
|
||||
b.HasOne("vassago.Models.PermissionSettings", "Permissions")
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionsId");
|
||||
|
||||
b.Navigation("ParentChannel");
|
||||
|
||||
b.Navigation("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Account", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "Channel")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("ChannelId");
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Channel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("SubChannels");
|
||||
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Accounts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
211
Migrations/20230704160720_initial.cs
Normal file
@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PermissionSettings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
MaxAttachmentBytes = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
|
||||
MaxTextChars = table.Column<long>(type: "bigint", nullable: true),
|
||||
LinksAllowed = table.Column<bool>(type: "boolean", nullable: true),
|
||||
ReactionsPossible = table.Column<bool>(type: "boolean", nullable: true),
|
||||
LewdnessFilterLevel = table.Column<int>(type: "integer", nullable: true),
|
||||
MeannessFilterLevel = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PermissionSettings", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Channels",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
ExternalId = table.Column<string>(type: "text", nullable: true),
|
||||
DisplayName = table.Column<string>(type: "text", nullable: true),
|
||||
IsDM = table.Column<bool>(type: "boolean", nullable: false),
|
||||
PermissionsId = table.Column<int>(type: "integer", nullable: true),
|
||||
ParentChannelId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
Protocol = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Channels", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Channels_Channels_ParentChannelId",
|
||||
column: x => x.ParentChannelId,
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Channels_PermissionSettings_PermissionsId",
|
||||
column: x => x.PermissionsId,
|
||||
principalTable: "PermissionSettings",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Accounts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
ExternalId = table.Column<string>(type: "text", nullable: true),
|
||||
Username = table.Column<string>(type: "text", nullable: true),
|
||||
DisplayName = table.Column<string>(type: "text", nullable: true),
|
||||
IsBot = table.Column<bool>(type: "boolean", nullable: false),
|
||||
SeenInChannelId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
PermissionTags = table.Column<int[]>(type: "integer[]", nullable: true),
|
||||
Protocol = table.Column<string>(type: "text", nullable: true),
|
||||
IsUserId = table.Column<Guid>(type: "uuid", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Accounts", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Accounts_Channels_SeenInChannelId",
|
||||
column: x => x.SeenInChannelId,
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Accounts_Users_IsUserId",
|
||||
column: x => x.IsUserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Messages",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Protocol = table.Column<string>(type: "text", nullable: true),
|
||||
ExternalId = table.Column<string>(type: "text", nullable: true),
|
||||
Content = table.Column<string>(type: "text", nullable: true),
|
||||
MentionsMe = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
ActedOn = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AuthorId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
ChannelId = table.Column<Guid>(type: "uuid", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Messages", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Messages_Accounts_AuthorId",
|
||||
column: x => x.AuthorId,
|
||||
principalTable: "Accounts",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Messages_Channels_ChannelId",
|
||||
column: x => x.ChannelId,
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Attachments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
ExternalId = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
|
||||
Source = table.Column<string>(type: "text", nullable: true),
|
||||
Content = table.Column<byte[]>(type: "bytea", nullable: true),
|
||||
Filename = table.Column<string>(type: "text", nullable: true),
|
||||
MessageId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
ContentType = table.Column<string>(type: "text", nullable: true),
|
||||
Description = table.Column<string>(type: "text", nullable: true),
|
||||
Size = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Attachments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Attachments_Messages_MessageId",
|
||||
column: x => x.MessageId,
|
||||
principalTable: "Messages",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Accounts_IsUserId",
|
||||
table: "Accounts",
|
||||
column: "IsUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Accounts_SeenInChannelId",
|
||||
table: "Accounts",
|
||||
column: "SeenInChannelId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Attachments_MessageId",
|
||||
table: "Attachments",
|
||||
column: "MessageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Channels_ParentChannelId",
|
||||
table: "Channels",
|
||||
column: "ParentChannelId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Channels_PermissionsId",
|
||||
table: "Channels",
|
||||
column: "PermissionsId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Messages_AuthorId",
|
||||
table: "Messages",
|
||||
column: "AuthorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Messages_ChannelId",
|
||||
table: "Messages",
|
||||
column: "ChannelId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Attachments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Messages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Channels");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PermissionSettings");
|
||||
}
|
||||
}
|
||||
}
|
296
Migrations/20230704203907_permissionTagsOnUsers.Designer.cs
generated
Normal file
@ -0,0 +1,296 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using vassago.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
[DbContext(typeof(ChattingContext))]
|
||||
[Migration("20230704203907_permissionTagsOnUsers")]
|
||||
partial class permissionTagsOnUsers
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsBot")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("IsUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int[]>("PermissionTags")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SeenInChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IsUserId");
|
||||
|
||||
b.HasIndex("SeenInChannelId");
|
||||
|
||||
b.ToTable("Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<byte[]>("Content")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("ExternalId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("MessageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Size")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MessageId");
|
||||
|
||||
b.ToTable("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsDM")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("ParentChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int?>("PermissionsId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentChannelId");
|
||||
|
||||
b.HasIndex("PermissionsId");
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("ActedOn")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("MentionsMe")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("ChannelId");
|
||||
|
||||
b.ToTable("Messages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.PermissionSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("LewdnessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("LinksAllowed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal?>("MaxAttachmentBytes")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("MaxTextChars")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int?>("MeannessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("ReactionsPossible")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PermissionSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int[]>("PermissionTags")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.User", "IsUser")
|
||||
.WithMany("Accounts")
|
||||
.HasForeignKey("IsUserId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "SeenInChannel")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("SeenInChannelId");
|
||||
|
||||
b.Navigation("IsUser");
|
||||
|
||||
b.Navigation("SeenInChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Message", "Message")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("MessageId");
|
||||
|
||||
b.Navigation("Message");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Channel", "ParentChannel")
|
||||
.WithMany("SubChannels")
|
||||
.HasForeignKey("ParentChannelId");
|
||||
|
||||
b.HasOne("vassago.Models.PermissionSettings", "Permissions")
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionsId");
|
||||
|
||||
b.Navigation("ParentChannel");
|
||||
|
||||
b.Navigation("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Account", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "Channel")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("ChannelId");
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Channel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("SubChannels");
|
||||
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Accounts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
28
Migrations/20230704203907_permissionTagsOnUsers.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class permissionTagsOnUsers : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int[]>(
|
||||
name: "PermissionTags",
|
||||
table: "Users",
|
||||
type: "integer[]",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PermissionTags",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
349
Migrations/20231130204741_Feature Permissions.Designer.cs
generated
Normal file
@ -0,0 +1,349 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using vassago.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
[DbContext(typeof(ChattingContext))]
|
||||
[Migration("20231130204741_Feature Permissions")]
|
||||
partial class FeaturePermissions
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("FeaturePermissionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsBot")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("IsUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SeenInChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FeaturePermissionId");
|
||||
|
||||
b.HasIndex("IsUserId");
|
||||
|
||||
b.HasIndex("SeenInChannelId");
|
||||
|
||||
b.ToTable("Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<byte[]>("Content")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("ExternalId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("MessageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Size")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MessageId");
|
||||
|
||||
b.ToTable("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("FeaturePermissionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsDM")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("ParentChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int?>("PermissionsId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FeaturePermissionId");
|
||||
|
||||
b.HasIndex("ParentChannelId");
|
||||
|
||||
b.HasIndex("PermissionsId");
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.ChannelPermissions", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("LewdnessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("LinksAllowed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal?>("MaxAttachmentBytes")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("MaxTextChars")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int?>("MeannessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("ReactionsPossible")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ChannelPermissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.FeaturePermission", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("Inheritable")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("InternalName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("InternalTag")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("FeaturePermissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("ActedOn")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("MentionsMe")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("ChannelId");
|
||||
|
||||
b.ToTable("Messages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("FeaturePermissionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FeaturePermissionId");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.FeaturePermission", null)
|
||||
.WithMany("RestrictedToAccounts")
|
||||
.HasForeignKey("FeaturePermissionId");
|
||||
|
||||
b.HasOne("vassago.Models.User", "IsUser")
|
||||
.WithMany("Accounts")
|
||||
.HasForeignKey("IsUserId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "SeenInChannel")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("SeenInChannelId");
|
||||
|
||||
b.Navigation("IsUser");
|
||||
|
||||
b.Navigation("SeenInChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Message", "Message")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("MessageId");
|
||||
|
||||
b.Navigation("Message");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.FeaturePermission", null)
|
||||
.WithMany("RestrictedToChannels")
|
||||
.HasForeignKey("FeaturePermissionId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "ParentChannel")
|
||||
.WithMany("SubChannels")
|
||||
.HasForeignKey("ParentChannelId");
|
||||
|
||||
b.HasOne("vassago.Models.ChannelPermissions", "Permissions")
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionsId");
|
||||
|
||||
b.Navigation("ParentChannel");
|
||||
|
||||
b.Navigation("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Account", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "Channel")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("ChannelId");
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Channel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.FeaturePermission", null)
|
||||
.WithMany("RestrictedToUsers")
|
||||
.HasForeignKey("FeaturePermissionId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("SubChannels");
|
||||
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.FeaturePermission", b =>
|
||||
{
|
||||
b.Navigation("RestrictedToAccounts");
|
||||
|
||||
b.Navigation("RestrictedToChannels");
|
||||
|
||||
b.Navigation("RestrictedToUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Accounts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
211
Migrations/20231130204741_Feature Permissions.cs
Normal file
@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FeaturePermissions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channels_PermissionSettings_PermissionsId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PermissionSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PermissionTags",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PermissionTags",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Users",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Channels",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Accounts",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChannelPermissions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
MaxAttachmentBytes = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
|
||||
MaxTextChars = table.Column<long>(type: "bigint", nullable: true),
|
||||
LinksAllowed = table.Column<bool>(type: "boolean", nullable: true),
|
||||
ReactionsPossible = table.Column<bool>(type: "boolean", nullable: true),
|
||||
LewdnessFilterLevel = table.Column<int>(type: "integer", nullable: true),
|
||||
MeannessFilterLevel = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ChannelPermissions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "FeaturePermissions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
InternalName = table.Column<string>(type: "text", nullable: true),
|
||||
InternalTag = table.Column<int>(type: "integer", nullable: true),
|
||||
Inheritable = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_FeaturePermissions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_FeaturePermissionId",
|
||||
table: "Users",
|
||||
column: "FeaturePermissionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Channels_FeaturePermissionId",
|
||||
table: "Channels",
|
||||
column: "FeaturePermissionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Accounts_FeaturePermissionId",
|
||||
table: "Accounts",
|
||||
column: "FeaturePermissionId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Accounts_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Accounts",
|
||||
column: "FeaturePermissionId",
|
||||
principalTable: "FeaturePermissions",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channels_ChannelPermissions_PermissionsId",
|
||||
table: "Channels",
|
||||
column: "PermissionsId",
|
||||
principalTable: "ChannelPermissions",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channels_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Channels",
|
||||
column: "FeaturePermissionId",
|
||||
principalTable: "FeaturePermissions",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Users_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Users",
|
||||
column: "FeaturePermissionId",
|
||||
principalTable: "FeaturePermissions",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Accounts_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channels_ChannelPermissions_PermissionsId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channels_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Users_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChannelPermissions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "FeaturePermissions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Users_FeaturePermissionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Channels_FeaturePermissionId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Accounts_FeaturePermissionId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.AddColumn<int[]>(
|
||||
name: "PermissionTags",
|
||||
table: "Users",
|
||||
type: "integer[]",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int[]>(
|
||||
name: "PermissionTags",
|
||||
table: "Accounts",
|
||||
type: "integer[]",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PermissionSettings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
LewdnessFilterLevel = table.Column<int>(type: "integer", nullable: true),
|
||||
LinksAllowed = table.Column<bool>(type: "boolean", nullable: true),
|
||||
MaxAttachmentBytes = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
|
||||
MaxTextChars = table.Column<long>(type: "bigint", nullable: true),
|
||||
MeannessFilterLevel = table.Column<int>(type: "integer", nullable: true),
|
||||
ReactionsPossible = table.Column<bool>(type: "boolean", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PermissionSettings", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channels_PermissionSettings_PermissionsId",
|
||||
table: "Channels",
|
||||
column: "PermissionsId",
|
||||
principalTable: "PermissionSettings",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
349
Migrations/20231203193139_channeltype.Designer.cs
generated
Normal file
@ -0,0 +1,349 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using vassago.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
[DbContext(typeof(ChattingContext))]
|
||||
[Migration("20231203193139_channeltype")]
|
||||
partial class channeltype
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("FeaturePermissionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsBot")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("IsUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SeenInChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FeaturePermissionId");
|
||||
|
||||
b.HasIndex("IsUserId");
|
||||
|
||||
b.HasIndex("SeenInChannelId");
|
||||
|
||||
b.ToTable("Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<byte[]>("Content")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("ExternalId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("MessageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Size")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MessageId");
|
||||
|
||||
b.ToTable("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("ChannelType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("FeaturePermissionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ParentChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int?>("PermissionsId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FeaturePermissionId");
|
||||
|
||||
b.HasIndex("ParentChannelId");
|
||||
|
||||
b.HasIndex("PermissionsId");
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.ChannelPermissions", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("LewdnessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("LinksAllowed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal?>("MaxAttachmentBytes")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("MaxTextChars")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int?>("MeannessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("ReactionsPossible")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ChannelPermissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.FeaturePermission", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("Inheritable")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("InternalName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("InternalTag")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("FeaturePermissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("ActedOn")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("MentionsMe")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("ChannelId");
|
||||
|
||||
b.ToTable("Messages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("FeaturePermissionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FeaturePermissionId");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.FeaturePermission", null)
|
||||
.WithMany("RestrictedToAccounts")
|
||||
.HasForeignKey("FeaturePermissionId");
|
||||
|
||||
b.HasOne("vassago.Models.User", "IsUser")
|
||||
.WithMany("Accounts")
|
||||
.HasForeignKey("IsUserId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "SeenInChannel")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("SeenInChannelId");
|
||||
|
||||
b.Navigation("IsUser");
|
||||
|
||||
b.Navigation("SeenInChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Message", "Message")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("MessageId");
|
||||
|
||||
b.Navigation("Message");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.FeaturePermission", null)
|
||||
.WithMany("RestrictedToChannels")
|
||||
.HasForeignKey("FeaturePermissionId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "ParentChannel")
|
||||
.WithMany("SubChannels")
|
||||
.HasForeignKey("ParentChannelId");
|
||||
|
||||
b.HasOne("vassago.Models.ChannelPermissions", "Permissions")
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionsId");
|
||||
|
||||
b.Navigation("ParentChannel");
|
||||
|
||||
b.Navigation("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Account", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "Channel")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("ChannelId");
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Channel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.FeaturePermission", null)
|
||||
.WithMany("RestrictedToUsers")
|
||||
.HasForeignKey("FeaturePermissionId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("SubChannels");
|
||||
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.FeaturePermission", b =>
|
||||
{
|
||||
b.Navigation("RestrictedToAccounts");
|
||||
|
||||
b.Navigation("RestrictedToChannels");
|
||||
|
||||
b.Navigation("RestrictedToUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Accounts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
40
Migrations/20231203193139_channeltype.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class channeltype : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsDM",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "ChannelType",
|
||||
table: "Channels",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ChannelType",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsDM",
|
||||
table: "Channels",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
}
|
||||
}
|
266
Migrations/20240510202057_channelpermissions_partofchannel.Designer.cs
generated
Normal file
@ -0,0 +1,266 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using vassago.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
[DbContext(typeof(ChattingContext))]
|
||||
[Migration("20240510202057_channelpermissions_partofchannel")]
|
||||
partial class channelpermissions_partofchannel
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsBot")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("IsUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SeenInChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IsUserId");
|
||||
|
||||
b.HasIndex("SeenInChannelId");
|
||||
|
||||
b.ToTable("Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<byte[]>("Content")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("ExternalId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("MessageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Size")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MessageId");
|
||||
|
||||
b.ToTable("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("ChannelType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("LewdnessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("LinksAllowed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal?>("MaxAttachmentBytes")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("MaxTextChars")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int?>("MeannessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid?>("ParentChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool?>("ReactionsPossible")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentChannelId");
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("ActedOn")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("MentionsMe")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("ChannelId");
|
||||
|
||||
b.ToTable("Messages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.User", "IsUser")
|
||||
.WithMany("Accounts")
|
||||
.HasForeignKey("IsUserId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "SeenInChannel")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("SeenInChannelId");
|
||||
|
||||
b.Navigation("IsUser");
|
||||
|
||||
b.Navigation("SeenInChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Message", "Message")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("MessageId");
|
||||
|
||||
b.Navigation("Message");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Channel", "ParentChannel")
|
||||
.WithMany("SubChannels")
|
||||
.HasForeignKey("ParentChannelId");
|
||||
|
||||
b.Navigation("ParentChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Account", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "Channel")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("ChannelId");
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Channel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("SubChannels");
|
||||
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Accounts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
228
Migrations/20240510202057_channelpermissions_partofchannel.cs
Normal file
@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class channelpermissions_partofchannel : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Accounts_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channels_ChannelPermissions_PermissionsId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channels_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Users_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChannelPermissions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "FeaturePermissions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Users_FeaturePermissionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Channels_FeaturePermissionId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Channels_PermissionsId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Accounts_FeaturePermissionId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "PermissionsId",
|
||||
table: "Channels",
|
||||
newName: "MeannessFilterLevel");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "LewdnessFilterLevel",
|
||||
table: "Channels",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "LinksAllowed",
|
||||
table: "Channels",
|
||||
type: "boolean",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MaxAttachmentBytes",
|
||||
table: "Channels",
|
||||
type: "numeric(20,0)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "MaxTextChars",
|
||||
table: "Channels",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "ReactionsPossible",
|
||||
table: "Channels",
|
||||
type: "boolean",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LewdnessFilterLevel",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LinksAllowed",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxAttachmentBytes",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxTextChars",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ReactionsPossible",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "MeannessFilterLevel",
|
||||
table: "Channels",
|
||||
newName: "PermissionsId");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Users",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Channels",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FeaturePermissionId",
|
||||
table: "Accounts",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChannelPermissions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
LewdnessFilterLevel = table.Column<int>(type: "integer", nullable: true),
|
||||
LinksAllowed = table.Column<bool>(type: "boolean", nullable: true),
|
||||
MaxAttachmentBytes = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
|
||||
MaxTextChars = table.Column<long>(type: "bigint", nullable: true),
|
||||
MeannessFilterLevel = table.Column<int>(type: "integer", nullable: true),
|
||||
ReactionsPossible = table.Column<bool>(type: "boolean", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ChannelPermissions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "FeaturePermissions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Inheritable = table.Column<bool>(type: "boolean", nullable: false),
|
||||
InternalName = table.Column<string>(type: "text", nullable: true),
|
||||
InternalTag = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_FeaturePermissions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_FeaturePermissionId",
|
||||
table: "Users",
|
||||
column: "FeaturePermissionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Channels_FeaturePermissionId",
|
||||
table: "Channels",
|
||||
column: "FeaturePermissionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Channels_PermissionsId",
|
||||
table: "Channels",
|
||||
column: "PermissionsId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Accounts_FeaturePermissionId",
|
||||
table: "Accounts",
|
||||
column: "FeaturePermissionId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Accounts_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Accounts",
|
||||
column: "FeaturePermissionId",
|
||||
principalTable: "FeaturePermissions",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channels_ChannelPermissions_PermissionsId",
|
||||
table: "Channels",
|
||||
column: "PermissionsId",
|
||||
principalTable: "ChannelPermissions",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channels_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Channels",
|
||||
column: "FeaturePermissionId",
|
||||
principalTable: "FeaturePermissions",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Users_FeaturePermissions_FeaturePermissionId",
|
||||
table: "Users",
|
||||
column: "FeaturePermissionId",
|
||||
principalTable: "FeaturePermissions",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
271
Migrations/20250204004906_cascade.Designer.cs
generated
Normal file
@ -0,0 +1,271 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using vassago.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
[DbContext(typeof(ChattingContext))]
|
||||
[Migration("20250204004906_cascade")]
|
||||
partial class cascade
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsBot")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("IsUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SeenInChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IsUserId");
|
||||
|
||||
b.HasIndex("SeenInChannelId");
|
||||
|
||||
b.ToTable("Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<byte[]>("Content")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("ExternalId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("MessageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Size")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MessageId");
|
||||
|
||||
b.ToTable("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("ChannelType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("LewdnessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("LinksAllowed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal?>("MaxAttachmentBytes")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("MaxTextChars")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int?>("MeannessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid?>("ParentChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool?>("ReactionsPossible")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentChannelId");
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("ActedOn")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("MentionsMe")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("ChannelId");
|
||||
|
||||
b.ToTable("Messages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.User", "IsUser")
|
||||
.WithMany("Accounts")
|
||||
.HasForeignKey("IsUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "SeenInChannel")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("SeenInChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("IsUser");
|
||||
|
||||
b.Navigation("SeenInChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Message", "Message")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("MessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Message");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Channel", "ParentChannel")
|
||||
.WithMany("SubChannels")
|
||||
.HasForeignKey("ParentChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ParentChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Account", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "Channel")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Channel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("SubChannels");
|
||||
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Accounts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
133
Migrations/20250204004906_cascade.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class cascade : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Accounts_Channels_SeenInChannelId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Accounts_Users_IsUserId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Attachments_Messages_MessageId",
|
||||
table: "Attachments");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channels_Channels_ParentChannelId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Messages_Channels_ChannelId",
|
||||
table: "Messages");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Accounts_Channels_SeenInChannelId",
|
||||
table: "Accounts",
|
||||
column: "SeenInChannelId",
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Accounts_Users_IsUserId",
|
||||
table: "Accounts",
|
||||
column: "IsUserId",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Attachments_Messages_MessageId",
|
||||
table: "Attachments",
|
||||
column: "MessageId",
|
||||
principalTable: "Messages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channels_Channels_ParentChannelId",
|
||||
table: "Channels",
|
||||
column: "ParentChannelId",
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Messages_Channels_ChannelId",
|
||||
table: "Messages",
|
||||
column: "ChannelId",
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Accounts_Channels_SeenInChannelId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Accounts_Users_IsUserId",
|
||||
table: "Accounts");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Attachments_Messages_MessageId",
|
||||
table: "Attachments");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Channels_Channels_ParentChannelId",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Messages_Channels_ChannelId",
|
||||
table: "Messages");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Accounts_Channels_SeenInChannelId",
|
||||
table: "Accounts",
|
||||
column: "SeenInChannelId",
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Accounts_Users_IsUserId",
|
||||
table: "Accounts",
|
||||
column: "IsUserId",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Attachments_Messages_MessageId",
|
||||
table: "Attachments",
|
||||
column: "MessageId",
|
||||
principalTable: "Messages",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Channels_Channels_ParentChannelId",
|
||||
table: "Channels",
|
||||
column: "ParentChannelId",
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Messages_Channels_ChannelId",
|
||||
table: "Messages",
|
||||
column: "ChannelId",
|
||||
principalTable: "Channels",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
268
Migrations/ChattingContextModelSnapshot.cs
Normal file
@ -0,0 +1,268 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using vassago.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace vassago.Migrations
|
||||
{
|
||||
[DbContext(typeof(ChattingContext))]
|
||||
partial class ChattingContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsBot")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("IsUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SeenInChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IsUserId");
|
||||
|
||||
b.HasIndex("SeenInChannelId");
|
||||
|
||||
b.ToTable("Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<byte[]>("Content")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("ExternalId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("MessageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Size")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MessageId");
|
||||
|
||||
b.ToTable("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("ChannelType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("LewdnessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool?>("LinksAllowed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal?>("MaxAttachmentBytes")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("MaxTextChars")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int?>("MeannessFilterLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid?>("ParentChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool?>("ReactionsPossible")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentChannelId");
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("ActedOn")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<Guid?>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("MentionsMe")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Protocol")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("ChannelId");
|
||||
|
||||
b.ToTable("Messages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Account", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.User", "IsUser")
|
||||
.WithMany("Accounts")
|
||||
.HasForeignKey("IsUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "SeenInChannel")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("SeenInChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("IsUser");
|
||||
|
||||
b.Navigation("SeenInChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Attachment", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Message", "Message")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("MessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Message");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Channel", "ParentChannel")
|
||||
.WithMany("SubChannels")
|
||||
.HasForeignKey("ParentChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ParentChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.HasOne("vassago.Models.Account", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId");
|
||||
|
||||
b.HasOne("vassago.Models.Channel", "Channel")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Channel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Channel", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("SubChannels");
|
||||
|
||||
b.Navigation("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.Message", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("vassago.Models.User", b =>
|
||||
{
|
||||
b.Navigation("Accounts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
33
Models/Account.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace vassago.Models;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class Account
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
public string ExternalId { get; set; }
|
||||
public string Username { get; set; }
|
||||
private string _displayName = null;
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
return _displayName ?? Username;
|
||||
}
|
||||
set
|
||||
{
|
||||
_displayName = value;
|
||||
}
|
||||
}
|
||||
public bool IsBot { get; set; } //webhook counts
|
||||
public Channel SeenInChannel { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
[JsonIgnore]
|
||||
public User IsUser {get; set;}
|
||||
}
|
18
Models/Attachment.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace vassago.Models;
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
public class Attachment
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
public ulong? ExternalId { get; set; }
|
||||
public Uri Source { get; set; }
|
||||
public byte[] Content { get; set; }
|
||||
public string Filename { get; set; }
|
||||
public Message Message { get; set; }
|
||||
public string ContentType { get; internal set; }
|
||||
public string Description { get; internal set; }
|
||||
public int Size { get; internal set; }
|
||||
}
|
114
Models/Channel.cs
Normal file
@ -0,0 +1,114 @@
|
||||
namespace vassago.Models;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using static vassago.Models.Enumerations;
|
||||
|
||||
public class Channel
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
public string ExternalId { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
[DeleteBehavior(DeleteBehavior.Cascade)]
|
||||
public List<Channel> SubChannels { get; set; }
|
||||
[JsonIgnore]
|
||||
public Channel ParentChannel { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
[DeleteBehavior(DeleteBehavior.Cascade)]
|
||||
public List<Message> Messages { get; set; }
|
||||
[DeleteBehavior(DeleteBehavior.Cascade)]
|
||||
public List<Account> Users { get; set; }
|
||||
public ChannelType ChannelType {get; set; }
|
||||
|
||||
//Permissions
|
||||
public ulong? MaxAttachmentBytes { get; set; }
|
||||
public uint? MaxTextChars { get; set; }
|
||||
public bool? LinksAllowed { get; set; }
|
||||
public bool? ReactionsPossible { get; set; }
|
||||
public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; }
|
||||
public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; }
|
||||
|
||||
[NonSerialized]
|
||||
public Func<string, string, Task> SendFile;
|
||||
|
||||
[NonSerialized]
|
||||
public Func<string, Task> SendMessage;
|
||||
|
||||
|
||||
public DefinitePermissionSettings EffectivePermissions
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = new Stack<Channel>(); //omg i actually get to use a data structure from university
|
||||
var walker = this;
|
||||
path.Push(this);
|
||||
while(walker.ParentChannel != null)
|
||||
{
|
||||
walker = walker.ParentChannel;
|
||||
path.Push(walker);
|
||||
}
|
||||
DefinitePermissionSettings toReturn = new DefinitePermissionSettings();
|
||||
|
||||
while(path.Count > 0)
|
||||
{
|
||||
walker = path.Pop();
|
||||
toReturn.LewdnessFilterLevel = walker.LewdnessFilterLevel ?? toReturn.LewdnessFilterLevel;
|
||||
toReturn.MeannessFilterLevel = walker.MeannessFilterLevel ?? toReturn.MeannessFilterLevel;
|
||||
toReturn.LinksAllowed = walker.LinksAllowed ?? toReturn.LinksAllowed;
|
||||
toReturn.MaxAttachmentBytes = walker.MaxAttachmentBytes ?? toReturn.MaxAttachmentBytes;
|
||||
toReturn.MaxTextChars = walker.MaxTextChars ?? toReturn.MaxTextChars;
|
||||
toReturn.ReactionsPossible = walker.ReactionsPossible ?? toReturn.ReactionsPossible;
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
public string LineageSummary
|
||||
{
|
||||
get
|
||||
{
|
||||
if(this.ParentChannel != null)
|
||||
{
|
||||
return this.ParentChannel.LineageSummary + '/' + this.DisplayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.Protocol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///<summary>
|
||||
///break self-referencing loops for library-agnostic serialization
|
||||
///</summary>
|
||||
public Channel AsSerializable()
|
||||
{
|
||||
var toReturn = this.MemberwiseClone() as Channel;
|
||||
toReturn.ParentChannel = null;
|
||||
if(toReturn.Users?.Count > 0)
|
||||
{
|
||||
foreach (var account in toReturn.Users)
|
||||
{
|
||||
account.SeenInChannel = null;
|
||||
}
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
public class DefinitePermissionSettings
|
||||
{
|
||||
public ulong MaxAttachmentBytes { get; set; }
|
||||
public uint MaxTextChars { get; set; }
|
||||
public bool LinksAllowed { get; set; }
|
||||
public bool ReactionsPossible { get; set; }
|
||||
public Enumerations.LewdnessFilterLevel LewdnessFilterLevel { get; set; }
|
||||
public Enumerations.MeannessFilterLevel MeannessFilterLevel { get; set; }
|
||||
}
|
22
Models/ChattingContext.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace vassago.Models;
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class ChattingContext : DbContext
|
||||
{
|
||||
public DbSet<Attachment> Attachments { get; set; }
|
||||
public DbSet<Channel> Channels { get; set; }
|
||||
//public DbSet<Emoji> Emoji {get;set;}
|
||||
public DbSet<Message> Messages { get; set; }
|
||||
public DbSet<Account> Accounts { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
public ChattingContext(DbContextOptions<ChattingContext> options) : base(options) { }
|
||||
public ChattingContext() : base() { }
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseNpgsql(Shared.DBConnectionString);
|
||||
//.EnableSensitiveDataLogging(true); //"sensitive" is one thing. writing "did something" every time you think a thought is a different thing.
|
||||
}
|
||||
}
|
67
Models/Enums.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace vassago.Models;
|
||||
|
||||
public static class Enumerations
|
||||
{
|
||||
public enum LewdnessFilterLevel
|
||||
{
|
||||
[Description("this is a christian minecraft server 🙏")]
|
||||
Strict,
|
||||
[Description("G-Rated")]
|
||||
G,
|
||||
[Description("polite company")]
|
||||
Moderate,
|
||||
[Description(";) ;) ;)")]
|
||||
Unrestricted
|
||||
}
|
||||
public enum MeannessFilterLevel
|
||||
{
|
||||
[Description("good vibes only")]
|
||||
Strict,
|
||||
[Description("a bit cheeky")]
|
||||
Medium,
|
||||
[Description("387.44m mi of printed circuits")]
|
||||
Unrestricted
|
||||
}
|
||||
|
||||
public enum ChannelType
|
||||
{
|
||||
[Description("Normal")]
|
||||
Normal,
|
||||
[Description("DM")]
|
||||
DM,
|
||||
[Description("protocol psuedo-channel")]
|
||||
Protocol,
|
||||
[Description("organizational psuedo-channel")]
|
||||
OU
|
||||
}
|
||||
|
||||
public static string GetDescription<T>(this T enumerationValue)
|
||||
where T : struct
|
||||
{
|
||||
Type type = enumerationValue.GetType();
|
||||
if (!type.IsEnum)
|
||||
{
|
||||
throw new ArgumentException("EnumerationValue must be of Enum type", nameof(enumerationValue));
|
||||
}
|
||||
|
||||
//Tries to find a DescriptionAttribute for a potential friendly name
|
||||
//for the enum
|
||||
MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
|
||||
if (memberInfo != null && memberInfo.Length > 0)
|
||||
{
|
||||
object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
|
||||
if (attrs != null && attrs.Length > 0)
|
||||
{
|
||||
//Pull out the description value
|
||||
return ((DescriptionAttribute)attrs[0]).Description;
|
||||
}
|
||||
}
|
||||
//If we have no description attribute, just return the ToString of the enum
|
||||
return enumerationValue.ToString();
|
||||
}
|
||||
}
|
33
Models/Message.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace vassago.Models;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class Message
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
public string ExternalId { get; set; }
|
||||
public string Content { get; set; }
|
||||
public bool MentionsMe { get; set; }
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public bool ActedOn { get; set; }
|
||||
[DeleteBehavior(DeleteBehavior.Cascade)]
|
||||
public List<Attachment> Attachments { get; set; }
|
||||
public Account Author { get; set; }
|
||||
public Channel Channel { get; set; }
|
||||
|
||||
|
||||
|
||||
[NonSerialized]
|
||||
public Func<string, Task> Reply;
|
||||
|
||||
[NonSerialized]
|
||||
public Func<string, Task> React;
|
||||
}
|
37
Models/User.cs
Normal file
@ -0,0 +1,37 @@
|
||||
namespace vassago.Models;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Reflection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class User
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
[DeleteBehavior(DeleteBehavior.Cascade)]
|
||||
public List<Account> Accounts { get; set; }
|
||||
|
||||
//if I ever get lots and lots of tags, or some automatic way to register a feature's arbitrary tags, then I can move this off.
|
||||
//public bool Tag_CanTwitchSummon { get; set; }
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Accounts?.Any() ?? false)
|
||||
{
|
||||
return Accounts.Select(a => a.DisplayName).Distinct()
|
||||
.MaxBy(distinctName =>
|
||||
Accounts.Select(a => a.DisplayName)
|
||||
.Where(selectedName => selectedName == distinctName).Count()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"[accountless {Id}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
139
Program.cs
@ -1,83 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace silverworker_discord
|
||||
{
|
||||
class Program
|
||||
{
|
||||
private DiscordSocketClient _client;
|
||||
|
||||
IConfigurationRoot config = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.json", true, true)
|
||||
.Build();
|
||||
|
||||
private ISocketMessageChannel botChatterChannel = null;
|
||||
private ISocketMessageChannel announcementChannel = null;
|
||||
|
||||
public static void Main(string[] args)
|
||||
=> new Program().MainAsync().GetAwaiter().GetResult();
|
||||
private Task Log(LogMessage msg)
|
||||
{
|
||||
Console.WriteLine(msg.ToString());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public async Task MainAsync()
|
||||
{
|
||||
_client = new DiscordSocketClient();
|
||||
|
||||
_client.Log += Log;
|
||||
|
||||
await _client.LoginAsync(TokenType.Bot, config["token"]);
|
||||
await _client.StartAsync();
|
||||
|
||||
_client.MessageReceived += MessageReceived;
|
||||
_client.UserJoined += UserJoined;
|
||||
|
||||
_client.Ready += () => Task.Run(() =>{
|
||||
Console.WriteLine("Bot is connected!");
|
||||
botChatterChannel = _client.GetChannel(ulong.Parse(config["botChatterChannel"])) as ISocketMessageChannel;
|
||||
announcementChannel = _client.GetChannel(ulong.Parse(config["announcementChannel"])) as ISocketMessageChannel;
|
||||
});
|
||||
// Block this task until the program is closed.
|
||||
await Task.Delay(-1);
|
||||
}
|
||||
|
||||
private async Task MessageReceived(SocketMessage messageParam)
|
||||
{
|
||||
var message = messageParam as SocketUserMessage;
|
||||
if (message == null) return;
|
||||
if (message.Author.Id == _client.CurrentUser.Id) return;
|
||||
|
||||
Console.WriteLine($"{message.Channel}, {message.Content}, {message.Id}");
|
||||
if (message.Channel.Id == botChatterChannel.Id)
|
||||
{
|
||||
if(message.Attachments?.Count > 0)
|
||||
{
|
||||
Console.WriteLine(message.Attachments.Count);
|
||||
foreach (var att in message.Attachments)
|
||||
{
|
||||
Console.WriteLine(att.Url);
|
||||
await WebRequest.Create("http://192.168.1.151:3001/shortcuts?display_url=" + att.Url).GetResponseAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private Task UserJoined(SocketGuildUser arg)
|
||||
{
|
||||
Console.WriteLine($"user joined: {arg.Nickname}. Guid: {arg.Guild.Id}. Channel: {arg.Guild.DefaultChannel}");
|
||||
var abbreviatedNickname = arg.Nickname;
|
||||
if(arg.Nickname.Length > 3){
|
||||
abbreviatedNickname = arg.Nickname.Substring(0, arg.Nickname.Length / 3);
|
||||
}
|
||||
Console.WriteLine($"imma call him {abbreviatedNickname}");
|
||||
return arg.Guild.DefaultChannel.SendMessageAsync($"oh hey {abbreviatedNickname}- IPLAYTHESEALOFORICHALCOS <:ORICHALCOS:852749196633309194>");
|
||||
}
|
||||
}
|
||||
}
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
|
||||
using vassago.Models;
|
||||
|
||||
#pragma warning disable CA2254
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
builder.Services.AddSingleton<IHostedService, vassago.ConsoleService>();
|
||||
builder.Services.AddDbContext<ChattingContext>();
|
||||
builder.Services.AddControllers().AddNewtonsoftJson(options => {
|
||||
options.SerializerSettings.ReferenceLoopHandling =
|
||||
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
|
||||
});
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.Services.Configure<RazorViewEngineOptions>(o => {
|
||||
o.ViewLocationFormats.Clear();
|
||||
o.ViewLocationFormats.Add("/WebInterface/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
|
||||
o.ViewLocationFormats.Add("/WebInterface/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
|
||||
});
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
app.UseAuthorization();
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
app.UseSwagger();
|
||||
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "api");
|
||||
});
|
||||
|
||||
app.UseExceptionHandler();
|
||||
app.UseStatusCodePages();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.Run();
|
||||
|
37
Properties/launchSettings.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:42619",
|
||||
"sslPort": 44354
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5093",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7206;http://localhost:5093",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
404
ProtocolInterfaces/DiscordInterface/DiscordInterface.cs
Normal file
@ -0,0 +1,404 @@
|
||||
//https://discord.com/oauth2/authorize?client_id=913003037348491264&permissions=274877942784&scope=bot%20messages.read
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using vassago.Models;
|
||||
using vassago.Behavior;
|
||||
using Discord.Rest;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Threading;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace vassago.ProtocolInterfaces.DiscordInterface;
|
||||
|
||||
public class DiscordInterface
|
||||
{
|
||||
internal const string PROTOCOL = "discord";
|
||||
internal DiscordSocketClient client;
|
||||
private bool eventsSignedUp = false;
|
||||
private static readonly SemaphoreSlim discordChannelSetup = new(1, 1);
|
||||
private Channel protocolAsChannel;
|
||||
|
||||
public async Task Init(string token)
|
||||
{
|
||||
await SetupDiscordChannel();
|
||||
client = new DiscordSocketClient(new DiscordSocketConfig() { GatewayIntents = GatewayIntents.All });
|
||||
|
||||
client.Log += (msg) =>
|
||||
{
|
||||
Console.WriteLine(msg.ToString());
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
client.Connected += () => Task.Run(SelfConnected);
|
||||
client.Ready += () => Task.Run(ClientReady);
|
||||
|
||||
await client.LoginAsync(TokenType.Bot, token);
|
||||
await client.StartAsync();
|
||||
}
|
||||
|
||||
private async Task SetupDiscordChannel()
|
||||
{
|
||||
await discordChannelSetup.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == PROTOCOL);
|
||||
if (protocolAsChannel == null)
|
||||
{
|
||||
protocolAsChannel = new Channel()
|
||||
{
|
||||
DisplayName = "discord (itself)",
|
||||
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Strict,
|
||||
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.Moderate,
|
||||
MaxTextChars = 2000,
|
||||
MaxAttachmentBytes = 25 * 1024 * 1024, //allegedly it's 25, but I worry it's not actually.
|
||||
LinksAllowed = true,
|
||||
ReactionsPossible = true,
|
||||
ExternalId = null,
|
||||
Protocol = PROTOCOL,
|
||||
SubChannels = []
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"discord, channel with id {protocolAsChannel.Id}, already exists");
|
||||
}
|
||||
protocolAsChannel.DisplayName = "discord (itself)";
|
||||
protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot accept text"); };
|
||||
protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot send file"); };
|
||||
protocolAsChannel = Rememberer.RememberChannel(protocolAsChannel);
|
||||
Console.WriteLine($"protocol as channel addeed; {protocolAsChannel}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
discordChannelSetup.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClientReady()
|
||||
{
|
||||
if (!eventsSignedUp)
|
||||
{
|
||||
eventsSignedUp = true;
|
||||
Console.WriteLine($"Bot is connected ({client.CurrentUser.Username}; {client.CurrentUser.Mention})! going to sign up for message received and user joined in client ready");
|
||||
|
||||
client.MessageReceived += MessageReceived;
|
||||
// _client.MessageUpdated +=
|
||||
client.UserJoined += UserJoined;
|
||||
client.SlashCommandExecuted += SlashCommandHandler;
|
||||
//client.ChannelCreated +=
|
||||
// _client.ChannelDestroyed +=
|
||||
// _client.ChannelUpdated +=
|
||||
// _client.GuildMemberUpdated +=
|
||||
// _client.UserBanned +=
|
||||
// _client.UserLeft +=
|
||||
// _client.ThreadCreated +=
|
||||
// _client.ThreadUpdated +=
|
||||
// _client.ThreadDeleted +=
|
||||
// _client.JoinedGuild +=
|
||||
// _client.GuildUpdated +=
|
||||
// _client.LeftGuild +=
|
||||
|
||||
await SlashCommandsHelper.Register(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("bot appears to be RE connected, so I'm not going to sign up twice");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SelfConnected()
|
||||
{
|
||||
await discordChannelSetup.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var selfAccount = UpsertAccount(client.CurrentUser, protocolAsChannel);
|
||||
selfAccount.DisplayName = client.CurrentUser.Username;
|
||||
Behaver.Instance.MarkSelf(selfAccount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
discordChannelSetup.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task MessageReceived(SocketMessage messageParam)
|
||||
{
|
||||
if (messageParam is not SocketUserMessage)
|
||||
{
|
||||
Console.WriteLine($"{messageParam.Content}, but not a user message");
|
||||
return;
|
||||
}
|
||||
var suMessage = messageParam as SocketUserMessage;
|
||||
|
||||
Console.WriteLine($"#{suMessage.Channel}[{DateTime.Now}][{suMessage.Author.Username} [id={suMessage.Author.Id}]][msg id: {suMessage.Id}] {suMessage.Content}");
|
||||
|
||||
var m = UpsertMessage(suMessage);
|
||||
|
||||
if (suMessage.MentionedUsers?.FirstOrDefault(muid => muid.Id == client.CurrentUser.Id) != null)
|
||||
{
|
||||
var mentionOfMe = "<@" + client.CurrentUser.Id + ">";
|
||||
m.MentionsMe = true;
|
||||
}
|
||||
await Behaver.Instance.ActOn(m);
|
||||
m.ActedOn = true; // for its own ruposess it might act on it later, but either way, fuck it, we checked.
|
||||
}
|
||||
|
||||
private Task UserJoined(SocketGuildUser arg)
|
||||
{
|
||||
var guild = UpsertChannel(arg.Guild);
|
||||
var defaultChannel = UpsertChannel(arg.Guild.DefaultChannel);
|
||||
defaultChannel.ParentChannel = guild;
|
||||
var u = UpsertAccount(arg, guild);
|
||||
u.DisplayName = arg.DisplayName;
|
||||
return null;
|
||||
}
|
||||
internal static async Task SlashCommandHandler(SocketSlashCommand command)
|
||||
{
|
||||
switch (command.CommandName)
|
||||
{
|
||||
case "freedomunits":
|
||||
try
|
||||
{
|
||||
var amt = Convert.ToDecimal((double)(command.Data.Options.First(o => o.Name == "amount").Value));
|
||||
var src = (string)command.Data.Options.First(o => o.Name == "src-unit").Value;
|
||||
var dest = (string)command.Data.Options.First(o => o.Name == "dest-unit").Value;
|
||||
var conversionResult = Conversion.Converter.Convert(amt, src, dest);
|
||||
|
||||
await command.RespondAsync($"> {amt} {src} -> {dest}\n{conversionResult}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await command.RespondAsync($"error: {e.Message}. aaadam!");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
await command.RespondAsync($"\\*smiles and nods*\n");
|
||||
await command.Channel.SendFileAsync($"assets/loud sweating.gif");
|
||||
Console.Error.WriteLine($"can't understand command name: {command.CommandName}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
internal static vassago.Models.Attachment UpsertAttachment(IAttachment dAttachment)
|
||||
{
|
||||
var a = Rememberer.SearchAttachment(ai => ai.ExternalId == dAttachment.Id)
|
||||
?? new vassago.Models.Attachment();
|
||||
|
||||
a.ContentType = dAttachment.ContentType;
|
||||
a.Description = dAttachment.Description;
|
||||
a.Filename = dAttachment.Filename;
|
||||
a.Size = dAttachment.Size;
|
||||
a.Source = new Uri(dAttachment.Url);
|
||||
Rememberer.RememberAttachment(a);
|
||||
return a;
|
||||
}
|
||||
internal Message UpsertMessage(IUserMessage dMessage)
|
||||
{
|
||||
var m = Rememberer.SearchMessage(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == PROTOCOL)
|
||||
?? new()
|
||||
{
|
||||
Protocol = PROTOCOL
|
||||
};
|
||||
|
||||
if (dMessage.Attachments?.Count > 0)
|
||||
{
|
||||
m.Attachments = [];
|
||||
foreach (var da in dMessage.Attachments)
|
||||
{
|
||||
m.Attachments.Add(UpsertAttachment(da));
|
||||
}
|
||||
}
|
||||
m.Content = dMessage.Content;
|
||||
m.ExternalId = dMessage.Id.ToString();
|
||||
m.Timestamp = dMessage.EditedTimestamp ?? dMessage.CreatedAt;
|
||||
m.Channel = UpsertChannel(dMessage.Channel);
|
||||
m.Author = UpsertAccount(dMessage.Author, m.Channel);
|
||||
Console.WriteLine($"received message; author: {m.Author.DisplayName}, {m.Author.Id}");
|
||||
if (dMessage.Channel is IGuildChannel)
|
||||
{
|
||||
m.Author.DisplayName = (dMessage.Author as IGuildUser).DisplayName;//discord forgot how display names work.
|
||||
}
|
||||
m.MentionsMe = (dMessage.Author.Id != client.CurrentUser.Id
|
||||
&& (dMessage.MentionedUserIds?.FirstOrDefault(muid => muid == client.CurrentUser.Id) > 0));
|
||||
|
||||
m.Reply = (t) => { return dMessage.ReplyAsync(t); };
|
||||
m.React = (e) => { return AttemptReact(dMessage, e); };
|
||||
Rememberer.RememberMessage(m);
|
||||
return m;
|
||||
}
|
||||
internal Channel UpsertChannel(IMessageChannel channel)
|
||||
{
|
||||
Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL);
|
||||
if (c == null)
|
||||
{
|
||||
Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}");
|
||||
c = new Channel()
|
||||
{
|
||||
Users = []
|
||||
};
|
||||
}
|
||||
|
||||
c.ExternalId = channel.Id.ToString();
|
||||
c.ChannelType = (channel is IPrivateChannel) ? vassago.Models.Enumerations.ChannelType.DM : vassago.Models.Enumerations.ChannelType.Normal;
|
||||
c.Messages ??= [];
|
||||
c.Protocol = PROTOCOL;
|
||||
if (channel is IGuildChannel)
|
||||
{
|
||||
Console.WriteLine($"{channel.Name} is a guild channel. So i'm going to upsert the guild, {(channel as IGuildChannel).Guild}");
|
||||
c.ParentChannel = UpsertChannel((channel as IGuildChannel).Guild);
|
||||
}
|
||||
else if (channel is IPrivateChannel)
|
||||
{
|
||||
c.ParentChannel = protocolAsChannel;
|
||||
Console.WriteLine("i'm a private channel so I'm setting my parent channel to the protocol as channel");
|
||||
}
|
||||
else
|
||||
{
|
||||
c.ParentChannel = protocolAsChannel;
|
||||
Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg");
|
||||
}
|
||||
|
||||
Console.WriteLine($"upsertion of channel {c.DisplayName}, it's type {c.ChannelType}");
|
||||
switch (c.ChannelType)
|
||||
{
|
||||
case vassago.Models.Enumerations.ChannelType.DM:
|
||||
var asPriv =(channel as IPrivateChannel);
|
||||
var sender = asPriv?.Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id); // why yes, there's a list of recipients, and it's the sender.
|
||||
if(sender != null)
|
||||
{
|
||||
c.DisplayName = "DM: " + sender.Username;
|
||||
}
|
||||
else
|
||||
{
|
||||
//I sent it, so I don't know the recipient's name.
|
||||
}
|
||||
break;
|
||||
default:
|
||||
c.DisplayName = channel.Name;
|
||||
break;
|
||||
}
|
||||
|
||||
Channel parentChannel = null;
|
||||
if (channel is IGuildChannel)
|
||||
{
|
||||
parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == PROTOCOL);
|
||||
if (parentChannel is null)
|
||||
{
|
||||
Console.Error.WriteLine("why am I still null?");
|
||||
}
|
||||
}
|
||||
else if (channel is IPrivateChannel)
|
||||
{
|
||||
parentChannel = protocolAsChannel;
|
||||
}
|
||||
else
|
||||
{
|
||||
parentChannel = protocolAsChannel;
|
||||
Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg");
|
||||
}
|
||||
parentChannel.SubChannels ??= [];
|
||||
if(!parentChannel.SubChannels.Contains(c))
|
||||
{
|
||||
parentChannel.SubChannels.Add(c);
|
||||
}
|
||||
|
||||
c.SendMessage = (t) => { return channel.SendMessageAsync(t); };
|
||||
c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); };
|
||||
|
||||
c = Rememberer.RememberChannel(c);
|
||||
|
||||
var selfAccountInChannel = c.Users.FirstOrDefault(a => a.ExternalId == client.CurrentUser.Id.ToString());
|
||||
if(selfAccountInChannel == null)
|
||||
{
|
||||
selfAccountInChannel = UpsertAccount(client.CurrentUser, c);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
internal Channel UpsertChannel(IGuild channel)
|
||||
{
|
||||
Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL);
|
||||
if (c == null)
|
||||
{
|
||||
Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}");
|
||||
c = new Channel();
|
||||
}
|
||||
|
||||
c.DisplayName = channel.Name;
|
||||
c.ExternalId = channel.Id.ToString();
|
||||
c.ChannelType = vassago.Models.Enumerations.ChannelType.OU;
|
||||
c.Messages ??= [];
|
||||
c.Protocol = protocolAsChannel.Protocol;
|
||||
c.ParentChannel = protocolAsChannel;
|
||||
c.SubChannels ??= [];
|
||||
c.MaxAttachmentBytes = channel.MaxUploadLimit;
|
||||
|
||||
c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); };
|
||||
c.SendFile = (f, t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; send file"); };
|
||||
|
||||
return Rememberer.RememberChannel(c);
|
||||
}
|
||||
internal static Account UpsertAccount(IUser discordUser, Channel inChannel)
|
||||
{
|
||||
var acc = Rememberer.SearchAccount(ui => ui.ExternalId == discordUser.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id);
|
||||
Console.WriteLine($"upserting account, retrieved {acc?.Id}.");
|
||||
if (acc != null)
|
||||
{
|
||||
Console.WriteLine($"acc's user: {acc.IsUser?.Id}");
|
||||
}
|
||||
acc ??= new Account() {
|
||||
IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == discordUser.Id.ToString() && a.Protocol == PROTOCOL))
|
||||
?? new User()
|
||||
};
|
||||
|
||||
acc.Username = discordUser.Username;
|
||||
acc.ExternalId = discordUser.Id.ToString();
|
||||
acc.IsBot = discordUser.IsBot || discordUser.IsWebhook;
|
||||
acc.Protocol = PROTOCOL;
|
||||
acc.SeenInChannel = inChannel;
|
||||
|
||||
Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}");
|
||||
if (acc.IsUser != null)
|
||||
{
|
||||
Console.WriteLine($"user has record of {acc.IsUser.Accounts?.Count ?? 0} accounts");
|
||||
}
|
||||
acc.IsUser ??= new User() { Accounts = [acc] };
|
||||
if (inChannel.Users?.Count > 0)
|
||||
{
|
||||
Console.WriteLine($"channel has {inChannel.Users.Count} accounts");
|
||||
}
|
||||
Rememberer.RememberAccount(acc);
|
||||
inChannel.Users ??= [];
|
||||
if(!inChannel.Users.Contains(acc))
|
||||
{
|
||||
inChannel.Users.Add(acc);
|
||||
Rememberer.RememberChannel(inChannel);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
private static Task AttemptReact(IUserMessage msg, string e)
|
||||
{
|
||||
var c = Rememberer.SearchChannel(c => c.ExternalId == msg.Channel.Id.ToString());// db.Channels.FirstOrDefault(c => c.ExternalId == msg.Channel.Id.ToString());
|
||||
//var preferredEmote = c.EmoteOverrides?[e] ?? e; //TODO: emote overrides
|
||||
var preferredEmote = e;
|
||||
if (Emoji.TryParse(preferredEmote, out Emoji emoji))
|
||||
{
|
||||
return msg.AddReactionAsync(emoji);
|
||||
}
|
||||
if (!Emote.TryParse(preferredEmote, out Emote emote))
|
||||
{
|
||||
if (preferredEmote == e)
|
||||
Console.Error.WriteLine($"never heard of emote {e}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return msg.AddReactionAsync(emote);
|
||||
}
|
||||
|
||||
}
|
121
ProtocolInterfaces/DiscordInterface/SlashCommandsHelper.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Discord.WebSocket;
|
||||
using Discord;
|
||||
using Discord.Net;
|
||||
|
||||
namespace vassago.ProtocolInterfaces.DiscordInterface
|
||||
{
|
||||
public static class SlashCommandsHelper
|
||||
{
|
||||
private static List<CommandSetup> slashCommands = new List<CommandSetup>()
|
||||
{
|
||||
new CommandSetup(){
|
||||
Id = "freedomunits",
|
||||
UpdatedAt = new DateTime(2023, 5, 21, 13, 3, 0),
|
||||
guild = 825293851110801428, //TODO: demagic this magic number
|
||||
register = register_FreedomUnits
|
||||
}
|
||||
};
|
||||
public static async Task Register(DiscordSocketClient client)
|
||||
{
|
||||
return;
|
||||
var commandsInContext = await client.GetGlobalApplicationCommandsAsync();
|
||||
await Register(client, commandsInContext, null);
|
||||
foreach (var guild in client.Guilds)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Register(client, await guild.GetApplicationCommandsAsync(), guild);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
Console.Error.WriteLine($"error registering slash commands for guild {guild.Name} (id {guild.Id}) - {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task Register(DiscordSocketClient client, IEnumerable<SocketApplicationCommand> commandsInContext, SocketGuild guild)
|
||||
{
|
||||
foreach (var existingCommand in commandsInContext)
|
||||
{
|
||||
var myVersion = slashCommands.FirstOrDefault(c => c.Id == existingCommand.Name && c.guild == guild?.Id);
|
||||
if (myVersion == null)
|
||||
{
|
||||
Console.WriteLine($"deleting command {existingCommand.Name} - (created at {existingCommand.CreatedAt}, it's in guild {existingCommand.Guild?.Id} while I'm in {guild?.Id})");
|
||||
await existingCommand.DeleteAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(existingCommand.CreatedAt);
|
||||
if (myVersion.UpdatedAt > existingCommand.CreatedAt)
|
||||
{
|
||||
Console.WriteLine($"overwriting command {existingCommand.Name}");
|
||||
await myVersion.register(false, client, guild);
|
||||
}
|
||||
myVersion.alreadyRegistered = true;
|
||||
}
|
||||
}
|
||||
foreach (var remaining in slashCommands.Where(sc => sc.alreadyRegistered == false && sc.guild == guild?.Id))
|
||||
{
|
||||
Console.WriteLine($"creating new command {remaining.Id} ({(remaining.guild == null ? "global" : $"for guild {remaining.guild}")})");
|
||||
await remaining.register(true, client, guild);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task register_FreedomUnits(bool isNew, DiscordSocketClient client, SocketGuild guild)
|
||||
{
|
||||
var builtCommand = new SlashCommandBuilder()
|
||||
.WithName("freedomunits")
|
||||
.WithDescription("convert between misc units (currency: iso 4217 code)")
|
||||
.AddOption("amount", ApplicationCommandOptionType.Number, "source amount", isRequired: true)
|
||||
.AddOption(new SlashCommandOptionBuilder()
|
||||
.WithName("src-unit")
|
||||
.WithDescription("unit converting FROM")
|
||||
.WithRequired(true)
|
||||
.WithType(ApplicationCommandOptionType.String))
|
||||
.AddOption(new SlashCommandOptionBuilder()
|
||||
.WithName("dest-unit")
|
||||
.WithDescription("unit converting TO")
|
||||
.WithRequired(true)
|
||||
.WithType(ApplicationCommandOptionType.String))
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
if (guild != null)
|
||||
{
|
||||
if (isNew)
|
||||
await guild.CreateApplicationCommandAsync(builtCommand);
|
||||
else
|
||||
await guild.BulkOverwriteApplicationCommandAsync(new ApplicationCommandProperties[] { builtCommand });
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isNew)
|
||||
await client.CreateGlobalApplicationCommandAsync(builtCommand);
|
||||
else
|
||||
await client.BulkOverwriteGlobalApplicationCommandsAsync(new ApplicationCommandProperties[] { builtCommand });
|
||||
}
|
||||
}
|
||||
catch (HttpException exception)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
|
||||
Console.Error.WriteLine(json);
|
||||
}
|
||||
}
|
||||
private class CommandSetup
|
||||
{
|
||||
public string Id { get; set; }
|
||||
//the date/time you updated yours IN UTC.
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
public Registration register { get; set; }
|
||||
public ulong? guild { get; set; }
|
||||
public bool alreadyRegistered {get;set; } = false;
|
||||
|
||||
public delegate Task Registration(bool isNew, DiscordSocketClient client, SocketGuild guild);
|
||||
}
|
||||
}
|
||||
}
|
7
ProtocolInterfaces/ProtocolList.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace vassago.ProtocolInterfaces;
|
||||
|
||||
public static class ProtocolList
|
||||
{
|
||||
public static List<DiscordInterface.DiscordInterface> discords = new();
|
||||
public static List<TwitchInterface.TwitchInterface> twitchs = new();
|
||||
}
|
9
ProtocolInterfaces/TwitchInterface/TwitchConfig.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace vassago.TwitchInterface;
|
||||
|
||||
public class TwitchConfig
|
||||
{
|
||||
public string username {get; set;}
|
||||
public string clientId {get; set;}
|
||||
public string secret {get; set;}
|
||||
public string oauth {get; set;}
|
||||
}
|
290
ProtocolInterfaces/TwitchInterface/TwitchInterface.cs
Normal file
@ -0,0 +1,290 @@
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text.RegularExpressions;
|
||||
using RestSharp;
|
||||
using TwitchLib.Api;
|
||||
using TwitchLib.Api.Helix.Models.Users.GetUsers;
|
||||
using TwitchLib.Client;
|
||||
using TwitchLib.Client.Events;
|
||||
using TwitchLib.Client.Models;
|
||||
using TwitchLib.Communication.Clients;
|
||||
using TwitchLib.Communication.Models;
|
||||
using vassago.Behavior;
|
||||
using vassago.Models;
|
||||
|
||||
namespace vassago.TwitchInterface;
|
||||
|
||||
public class TwitchInterface
|
||||
{
|
||||
internal const string PROTOCOL = "twitch";
|
||||
private bool eventsSignedUp = false;
|
||||
private ChattingContext _db;
|
||||
private static SemaphoreSlim twitchChannelSetup = new SemaphoreSlim(1, 1);
|
||||
private Channel protocolAsChannel;
|
||||
TwitchClient client;
|
||||
TwitchAPI api;
|
||||
|
||||
public TwitchInterface()
|
||||
{
|
||||
_db = new ChattingContext();
|
||||
}
|
||||
private async Task SetupTwitchChannel()
|
||||
{
|
||||
await twitchChannelSetup.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
protocolAsChannel = _db.Channels.FirstOrDefault(c => c.ParentChannel == null && c.Protocol == PROTOCOL);
|
||||
if (protocolAsChannel == null)
|
||||
{
|
||||
protocolAsChannel = new Channel()
|
||||
{
|
||||
DisplayName = "twitch (itself)",
|
||||
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Medium,
|
||||
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.G,
|
||||
MaxTextChars = 500,
|
||||
MaxAttachmentBytes = 0,
|
||||
LinksAllowed = false,
|
||||
ReactionsPossible = false,
|
||||
ExternalId = null,
|
||||
Protocol = PROTOCOL,
|
||||
SubChannels = new List<Channel>()
|
||||
};
|
||||
protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"twitch itself cannot accept text"); };
|
||||
protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"twitch itself cannot send file"); };
|
||||
_db.Channels.Add(protocolAsChannel);
|
||||
_db.SaveChanges();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
twitchChannelSetup.Release();
|
||||
}
|
||||
}
|
||||
|
||||
///<param name="oauth">https://www.twitchapps.com/tmi/</param>
|
||||
public async Task Init(TwitchConfig tc)
|
||||
{
|
||||
await SetupTwitchChannel();
|
||||
|
||||
WebSocketClient customClient = new WebSocketClient(new ClientOptions
|
||||
{
|
||||
MessagesAllowedInPeriod = 750,
|
||||
ThrottlingPeriod = TimeSpan.FromSeconds(30)
|
||||
}
|
||||
);
|
||||
client = new TwitchClient(customClient);
|
||||
client.Initialize(new ConnectionCredentials(tc.username, tc.oauth, capabilities: new Capabilities()));
|
||||
|
||||
client.OnLog += Client_OnLog;
|
||||
client.OnJoinedChannel += Client_OnJoinedChannel;
|
||||
client.OnMessageReceived += Client_OnMessageReceivedAsync;
|
||||
client.OnWhisperReceived += Client_OnWhisperReceivedAsync;
|
||||
client.OnConnected += Client_OnConnected;
|
||||
|
||||
Console.WriteLine("twitch client 1 connecting...");
|
||||
client.Connect();
|
||||
Console.WriteLine("twitch client 1 connected");
|
||||
|
||||
// Console.WriteLine("twitch API client connecting...");
|
||||
// api = new TwitchAPI();
|
||||
// Console.WriteLine("can I just use the same creds as the other client?");
|
||||
// api.Settings.ClientId = tc.username;
|
||||
// api.Settings.AccessToken = tc.oauth;
|
||||
// try{
|
||||
// var neckbreads = await api.Helix.Moderation.GetModeratorsAsync("silvermeddlists");
|
||||
// Console.WriteLine($"{neckbreads?.Data?.Count()} shabby beards that need to be given up on");
|
||||
// }
|
||||
// catch(Exception e){
|
||||
// Console.Error.WriteLine(e);
|
||||
// }
|
||||
// Console.WriteLine("k.");
|
||||
}
|
||||
|
||||
private async void Client_OnWhisperReceivedAsync(object sender, OnWhisperReceivedArgs e)
|
||||
{
|
||||
Console.WriteLine($"whisper#{e.WhisperMessage.Username}[{DateTime.Now}][{e.WhisperMessage.DisplayName} [id={e.WhisperMessage.Username}]][msg id: {e.WhisperMessage.MessageId}] {e.WhisperMessage.Message}");
|
||||
var old = _db.Messages.FirstOrDefault(m => m.ExternalId == e.WhisperMessage.MessageId && m.Protocol == PROTOCOL);
|
||||
if (old != null)
|
||||
{
|
||||
Console.WriteLine($"[whisperreceived]: {e.WhisperMessage.MessageId}? already seent it. Internal id: {old.Id}");
|
||||
return;
|
||||
}
|
||||
var m = UpsertMessage(e.WhisperMessage);
|
||||
m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
|
||||
m.MentionsMe = Regex.IsMatch(e.WhisperMessage.Message?.ToLower(), $"\\b@{e.WhisperMessage.BotUsername.ToLower()}\\b");
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
await Behaver.Instance.ActOn(m);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async void Client_OnMessageReceivedAsync(object sender, OnMessageReceivedArgs e)
|
||||
{
|
||||
Console.WriteLine($"#{e.ChatMessage.Channel}[{DateTime.Now}][{e.ChatMessage.DisplayName} [id={e.ChatMessage.Username}]][msg id: {e.ChatMessage.Id}] {e.ChatMessage.Message}");
|
||||
var old = _db.Messages.FirstOrDefault(m => m.ExternalId == e.ChatMessage.Id && m.Protocol == PROTOCOL);
|
||||
if (old != null)
|
||||
{
|
||||
Console.WriteLine($"[messagereceived]: {e.ChatMessage.Id}? already seent it");
|
||||
return;
|
||||
}
|
||||
Console.WriteLine($"[messagereceived]: {e.ChatMessage.Id}? new to me.");
|
||||
var m = UpsertMessage(e.ChatMessage);
|
||||
m.MentionsMe = Regex.IsMatch(e.ChatMessage.Message?.ToLower(), $"@{e.ChatMessage.BotUsername.ToLower()}\\b") ||
|
||||
e.ChatMessage.ChatReply?.ParentUserLogin == e.ChatMessage.BotUsername;
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
await Behaver.Instance.ActOn(m);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async void Client_OnConnected(object sender, OnConnectedArgs e)
|
||||
{
|
||||
Console.WriteLine($"twitch marking selfaccount as seeninchannel {protocolAsChannel.Id}");
|
||||
var selfAccount = UpsertAccount(e.BotUsername, protocolAsChannel.Id);
|
||||
Behaver.Instance.MarkSelf(selfAccount);
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
Console.WriteLine($"Connected to {e.AutoJoinChannel}");
|
||||
}
|
||||
|
||||
private void Client_OnJoinedChannel(object sender, OnJoinedChannelArgs e)
|
||||
{
|
||||
client.SendMessage(e.Channel, "beep boop");
|
||||
}
|
||||
|
||||
private void Client_OnLog(object sender, OnLogArgs e)
|
||||
{
|
||||
Console.WriteLine($"{e.DateTime.ToString()}: {e.BotUsername} - {e.Data}");
|
||||
}
|
||||
|
||||
private Account UpsertAccount(string username, Guid inChannel)
|
||||
{
|
||||
var seenInChannel = _db.Channels.FirstOrDefault(c => c.Id == inChannel);
|
||||
var acc = _db.Accounts.FirstOrDefault(ui => ui.ExternalId == username && ui.SeenInChannel.Id == inChannel);
|
||||
if (acc == null)
|
||||
{
|
||||
acc = new Account();
|
||||
acc.SeenInChannel = seenInChannel;
|
||||
_db.Accounts.Add(acc);
|
||||
}
|
||||
acc.Username = username;
|
||||
acc.ExternalId = username;
|
||||
//acc.IsBot =
|
||||
acc.Protocol = PROTOCOL;
|
||||
|
||||
acc.IsUser = _db.Users.FirstOrDefault(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol));
|
||||
if (acc.IsUser == null)
|
||||
{
|
||||
acc.IsUser = new vassago.Models.User() { Accounts = new List<Account>() { acc } };
|
||||
_db.Users.Add(acc.IsUser);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
private Channel UpsertChannel(string channelName)
|
||||
{
|
||||
Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == channelName && ci.Protocol == PROTOCOL);
|
||||
if (c == null)
|
||||
{
|
||||
c = new Channel();
|
||||
_db.Channels.Add(c);
|
||||
}
|
||||
c.DisplayName = channelName;
|
||||
c.ExternalId = channelName;
|
||||
c.ChannelType = vassago.Models.Enumerations.ChannelType.Normal;
|
||||
c.Messages = c.Messages ?? new List<Message>();
|
||||
c.Protocol = PROTOCOL;
|
||||
c.ParentChannel = protocolAsChannel;
|
||||
c.SubChannels = c.SubChannels ?? new List<Channel>();
|
||||
c.SendMessage = (t) => { return Task.Run(() => { client.SendMessage(channelName, t); }); };
|
||||
c.SendFile = (f, t) => { throw new InvalidOperationException($"twitch cannot send files"); };
|
||||
return c;
|
||||
}
|
||||
private Channel UpsertDMChannel(string whisperWith)
|
||||
{
|
||||
Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == $"w_{whisperWith}" && ci.Protocol == PROTOCOL);
|
||||
if (c == null)
|
||||
{
|
||||
c = new Channel();
|
||||
_db.Channels.Add(c);
|
||||
}
|
||||
c.DisplayName = $"Whisper: {whisperWith}";
|
||||
c.ExternalId = $"w_{whisperWith}";
|
||||
c.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
|
||||
c.Messages = c.Messages ?? new List<Message>();
|
||||
c.Protocol = PROTOCOL;
|
||||
c.ParentChannel = protocolAsChannel;
|
||||
c.SubChannels = c.SubChannels ?? new List<Channel>();
|
||||
c.SendMessage = (t) => { return Task.Run(() => {
|
||||
try
|
||||
{
|
||||
|
||||
client.SendWhisper(whisperWith, t);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Console.Error.WriteLine(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
c.SendFile = (f, t) => { throw new InvalidOperationException($"twitch cannot send files"); };
|
||||
return c;
|
||||
}
|
||||
|
||||
private Message UpsertMessage(ChatMessage chatMessage)
|
||||
{
|
||||
var m = _db.Messages.FirstOrDefault(mi => mi.ExternalId == chatMessage.Id);
|
||||
if (m == null)
|
||||
{
|
||||
m = new Message();
|
||||
m.Protocol = PROTOCOL;
|
||||
_db.Messages.Add(m);
|
||||
m.Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
m.Content = chatMessage.Message;
|
||||
m.ExternalId = chatMessage.Id;
|
||||
m.Channel = UpsertChannel(chatMessage.Channel);
|
||||
m.Author = UpsertAccount(chatMessage.Username, m.Channel.Id);
|
||||
m.Author.SeenInChannel = m.Channel;
|
||||
|
||||
m.Reply = (t) => { return Task.Run(() => { client.SendReply(chatMessage.Channel, chatMessage.Id, t); }); };
|
||||
m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); };
|
||||
return m;
|
||||
}
|
||||
private Message UpsertMessage(WhisperMessage whisperMessage)
|
||||
{
|
||||
var m = _db.Messages.FirstOrDefault(mi => mi.ExternalId == whisperMessage.MessageId);
|
||||
if (m == null)
|
||||
{
|
||||
m = new Message();
|
||||
m.Protocol = PROTOCOL;
|
||||
_db.Messages.Add(m);
|
||||
m.Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
m.Content = whisperMessage.Message;
|
||||
m.ExternalId = whisperMessage.MessageId;
|
||||
m.Channel = UpsertDMChannel(whisperMessage.Username);
|
||||
m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
|
||||
m.Author = UpsertAccount(whisperMessage.Username, m.Channel.Id);
|
||||
m.Author.SeenInChannel = m.Channel;
|
||||
|
||||
m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(whisperMessage.Username, t); }); };
|
||||
m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); };
|
||||
return m;
|
||||
}
|
||||
|
||||
public string AttemptJoin(string channelTarget)
|
||||
{
|
||||
client.JoinChannel(channelTarget);
|
||||
return $"attempt join {channelTarget} - o7";
|
||||
}
|
||||
|
||||
internal void AttemptLeave(string channelTarget)
|
||||
{
|
||||
client.SendMessage(channelTarget, "o7");
|
||||
client.LeaveChannel(channelTarget);
|
||||
}
|
||||
}
|
51
README.md
@ -1,7 +1,52 @@
|
||||
# discord-bot
|
||||
|
||||
copy appsettings.json and fill it in
|
||||
copy appsettings.json to appsettings.ENV.json and fill it in. dotnet seems to understand files called appsettings.json (and appsettings.xml?) and knows how to overwrite *specific values found within* the .[ENV].[extension] version
|
||||
|
||||
# TODO
|
||||
# auth link
|
||||
|
||||
listen to kafka, send the message back over it
|
||||
https://discord.com/oauth2/authorize?client_id=913003037348491264&permissions=274877942784&scope=bot
|
||||
that's read messages/view channels, send messages, send messages in threads, and attach files. but not add reactions?
|
||||
|
||||
# concepts
|
||||
|
||||
## Data Types
|
||||
|
||||
database diagram. is a fancy term.
|
||||
|
||||
message 1:n attachment
|
||||
user 1:n account
|
||||
channel 1:n account
|
||||
channel 1:n message
|
||||
account 1:n message
|
||||
|
||||
featurepermission n:n ?
|
||||
|
||||
### Accounts
|
||||
|
||||
a `User` can have multiple `Account`s. e.g., @adam:greyn.club? that's an "account". I, however, am a `User`. An `Account` has references to the `Channels` its seen in - as in, leaf-level. If you're in a subchannel, you'll have an appropriate listing there - i.e., you will never have an account in "discord (itself)", you'll have one in the guild text-channels
|
||||
|
||||
### Attachment
|
||||
|
||||
debating whether to save a copy of every single attachment. Discord allows 100MB attachments for turbo users, and shtikbot lives in several art channels. (unfortunately, being that shtikbot doesn't have a viable SMS spam vector, it's limited to 8MB, in contradiction to discord itself reporting a server that doesn't agree to put its own name on discord's finer-grained rules has a limit of 10MB)
|
||||
|
||||
### Channel
|
||||
|
||||
a place where communication can happen. any level of these can have any number of children. In matrix, everything is a "room" - even spaces and threads. Seems like a fine idea. So for vassago, a discord "channel" is a channel. a "thread" is a child of that channel. a "category" is a parent of that channel. A "server" (formerly "guild") is a parent of that channel. and fuck it, Discord itself is a "channel". Includes permissions vassago has for a channel; MaxAttachmentBytes, etc. go down the hierarchy until you find an override.
|
||||
|
||||
### FeaturePermission
|
||||
|
||||
the permissions of a feature. It can be restricted to accounts, to users, to channels. It has an internal name... and tag? and it can be (or not be) inheritable?
|
||||
|
||||
### Message
|
||||
|
||||
a message (duh). features bools for "mentions me", the external ID, the reference to the account, the channel.
|
||||
|
||||
### User
|
||||
|
||||
a person or program who operates an account. recognizing that 2 `Account`s belong to 1 `User` can be done by that user (using LinkMe). I should be able to collapse myself automatically.
|
||||
|
||||
## Behavior
|
||||
|
||||
both a "feature" and an "anti-feature". a channel might dictate something isn't allowed (lewdness in a g-rated channel). A person might not be allowed to do something - lots of me-only things like directing other bots (and the now rendered-moot Torrent feature). A behavior might need a command alias in a particular channel (freedomunits in jubel's)
|
||||
|
||||
so "behavior" might need to tag other data types? do I have it do a full select every time we get a message? ...no, only if the (other) triggering conditions are met. Then you can take your time.
|
110
Rememberer.cs
Normal file
@ -0,0 +1,110 @@
|
||||
namespace vassago;
|
||||
|
||||
using System.Linq.Expressions;
|
||||
using vassago.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public static class Rememberer
|
||||
{
|
||||
private static readonly ChattingContext db = new();
|
||||
public static Account SearchAccount(Expression<Func<Account, bool>> predicate)
|
||||
{
|
||||
return db.Accounts.Include(a => a.IsUser).FirstOrDefault(predicate);
|
||||
}
|
||||
public static List<Account> SearchAccounts(Expression<Func<Account, bool>> predicate)
|
||||
{
|
||||
return db.Accounts.Where(predicate).ToList();
|
||||
}
|
||||
public static Attachment SearchAttachment(Expression<Func<Attachment, bool>> predicate)
|
||||
{
|
||||
return db.Attachments.FirstOrDefault(predicate);
|
||||
}
|
||||
public static Channel SearchChannel(Expression<Func<Channel, bool>> predicate)
|
||||
{
|
||||
return db.Channels.FirstOrDefault(predicate);
|
||||
}
|
||||
public static Message SearchMessage(Expression<Func<Message, bool>> predicate)
|
||||
{
|
||||
return db.Messages.FirstOrDefault(predicate);
|
||||
}
|
||||
public static User SearchUser(Expression<Func<User, bool>> predicate)
|
||||
{
|
||||
return db.Users.Include(u => u.Accounts).FirstOrDefault(predicate);
|
||||
}
|
||||
public static void RememberAccount(Account toRemember)
|
||||
{
|
||||
toRemember.IsUser ??= new User{ Accounts = [toRemember]};
|
||||
db.Update(toRemember.IsUser);
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static void RememberAttachment(Attachment toRemember)
|
||||
{
|
||||
toRemember.Message ??= new Message() { Attachments = [toRemember]};
|
||||
db.Update(toRemember.Message);
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static Channel RememberChannel(Channel toRemember)
|
||||
{
|
||||
db.Update(toRemember);
|
||||
db.SaveChanges();
|
||||
return toRemember;
|
||||
}
|
||||
public static void RememberMessage(Message toRemember)
|
||||
{
|
||||
toRemember.Channel ??= new (){ Messages = [toRemember] };
|
||||
db.Update(toRemember.Channel);
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static void RememberUser(User toRemember)
|
||||
{
|
||||
db.Users.Update(toRemember);
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static void ForgetAccount(Account toForget)
|
||||
{
|
||||
var user = toForget.IsUser;
|
||||
var usersOnlyAccount = user.Accounts?.Count == 1;
|
||||
|
||||
if(usersOnlyAccount)
|
||||
{
|
||||
Rememberer.ForgetUser(user);
|
||||
}
|
||||
else
|
||||
{
|
||||
db.Accounts.Remove(toForget);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
public static void ForgetChannel(Channel toForget)
|
||||
{
|
||||
db.Channels.Remove(toForget);
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static void ForgetUser(User toForget)
|
||||
{
|
||||
db.Users.Remove(toForget);
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static List<Account> AccountsOverview()
|
||||
{
|
||||
return [..db.Accounts];
|
||||
}
|
||||
public static List<Channel> ChannelsOverview()
|
||||
{
|
||||
return [..db.Channels.Include(u => u.SubChannels).Include(c => c.ParentChannel)];
|
||||
}
|
||||
public static Channel ChannelDetail(Guid Id)
|
||||
{
|
||||
return db.Channels.Find(Id);
|
||||
// .Include(u => u.SubChannels)
|
||||
// .Include(u => u.Users)
|
||||
// .Include(u => u.ParentChannel);
|
||||
}
|
||||
public static List<User> UsersOverview()
|
||||
{
|
||||
return db.Users.ToList();
|
||||
}
|
||||
}
|
13
Shared.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace vassago;
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using vassago.Models;
|
||||
|
||||
|
||||
public static class Shared
|
||||
{
|
||||
public static Random r = new Random();
|
||||
public static string DBConnectionString { get; set; }
|
||||
public static HttpClient HttpClient { get; internal set; } = new HttpClient();
|
||||
}
|
35
WebInterface/Controllers/AccountsController.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using vassago.Models;
|
||||
using vassago.WebInterface.Models;
|
||||
|
||||
namespace vassago.WebInterface.Controllers;
|
||||
|
||||
public class AccountsController(ChattingContext db) : Controller
|
||||
{
|
||||
private ChattingContext Database => db;
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
return Database.Accounts != null ?
|
||||
View(await Database.Accounts.ToListAsync()) :
|
||||
Problem("Entity set '_db.Accounts' is null.");
|
||||
}
|
||||
public async Task<IActionResult> Details(Guid id)
|
||||
{
|
||||
var account = await Database.Accounts
|
||||
.Include(a => a.IsUser)
|
||||
.Include(a => a.SeenInChannel)
|
||||
.FirstAsync(a => a.Id == id);
|
||||
return Database.Accounts != null ?
|
||||
View(account) :
|
||||
Problem("Entity set '_db.Accounts' is null.");
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
61
WebInterface/Controllers/ChannelsController.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using vassago.Models;
|
||||
using vassago.WebInterface.Models;
|
||||
|
||||
namespace vassago.WebInterface.Controllers;
|
||||
|
||||
public class ChannelsController() : Controller
|
||||
{
|
||||
public async Task<IActionResult> Details(Guid id)
|
||||
{
|
||||
var allChannels = Rememberer.ChannelsOverview();
|
||||
if(allChannels == null)
|
||||
return Problem("Entity set '_db.Channels' is null.");
|
||||
//"but adam", says the strawman, "why load *every* channel and walk your way up? surely there's a .Load command that works or something."
|
||||
//eh. I checked. Not really. You could make an SQL view that recurses its way up, meh idk how. You could just eagerly load *every* related object...
|
||||
//but that would take in all the messages.
|
||||
//realistically I expect this will have less than 1MB of total "channels", and several GB of total messages per (text) channel.
|
||||
|
||||
var channel = allChannels.First(u => u.Id == id);
|
||||
var walker = channel;
|
||||
while(walker != null)
|
||||
{
|
||||
ViewData["breadcrumbs"] = $"<a href=\"{Url.ActionLink(action: "Details", controller: "Channels", values: new {id = walker.Id})}\">{walker.DisplayName}</a>/" +
|
||||
ViewData["breadcrumbs"];
|
||||
walker = walker.ParentChannel;
|
||||
}
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('[');
|
||||
sb.Append($"{{text: \"{channel.SubChannels?.Count}\", nodes: [");
|
||||
var first=true;
|
||||
foreach(var subChannel in channel.SubChannels)
|
||||
{
|
||||
if(!first)
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
else
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
sb.Append($"{{\"text\": \"<a href=\\\"{Url.ActionLink(action: "Details", controller: "Channels", values: new {id = subChannel.Id})}\\\">{subChannel.DisplayName}</a>\"}}");
|
||||
}
|
||||
sb.Append("]}]");
|
||||
|
||||
ViewData.Add("channelsTree", sb.ToString());
|
||||
return View(
|
||||
new Tuple<Channel, Enumerations.LewdnessFilterLevel, Enumerations.MeannessFilterLevel>(
|
||||
channel, channel.EffectivePermissions.LewdnessFilterLevel, channel.EffectivePermissions.MeannessFilterLevel
|
||||
));
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
192
WebInterface/Controllers/HomeController.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
|
||||
using vassago.Models;
|
||||
using vassago.WebInterface.Models;
|
||||
|
||||
namespace vassago.Controllers;
|
||||
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
|
||||
public HomeController(ILogger<HomeController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
var allAccounts = Rememberer.AccountsOverview();
|
||||
var allChannels = Rememberer.ChannelsOverview();
|
||||
Console.WriteLine($"accounts: {allAccounts?.Count ?? 0}, channels: {allChannels?.Count ?? 0}");
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('[');
|
||||
sb.Append("{text: \"channels\", expanded:true, nodes: [");
|
||||
|
||||
var first = true;
|
||||
var topLevelChannels = Rememberer.ChannelsOverview().Where(x => x.ParentChannel == null);
|
||||
foreach (var topLevelChannel in topLevelChannels)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
|
||||
serializeChannel(ref sb, ref allChannels, ref allAccounts, topLevelChannel);
|
||||
}
|
||||
sb.Append("]}");
|
||||
|
||||
if (allChannels.Any())
|
||||
{
|
||||
sb.Append(",{text: \"orphaned channels\", expanded:true, nodes: [");
|
||||
first = true;
|
||||
while (true)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
serializeChannel(ref sb, ref allChannels, ref allAccounts, allChannels.First());
|
||||
if (!allChannels.Any())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
sb.Append("]}");
|
||||
}
|
||||
if (allAccounts.Any())
|
||||
{
|
||||
sb.Append(",{text: \"channelless accounts\", expanded:true, nodes: [");
|
||||
first = true;
|
||||
foreach (var acc in allAccounts)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
serializeAccount(ref sb, acc);
|
||||
}
|
||||
sb.Append("]}");
|
||||
}
|
||||
var users = Rememberer.UsersOverview();// _db.Users.ToList();
|
||||
if(users.Any())
|
||||
{
|
||||
sb.Append(",{text: \"users\", expanded:true, nodes: [");
|
||||
first=true;
|
||||
//refresh list; we'll be knocking them out again in serializeUser
|
||||
allAccounts = Rememberer.AccountsOverview();
|
||||
foreach(var user in users)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
serializeUser(ref sb, ref allAccounts, user);
|
||||
}
|
||||
sb.Append("]}");
|
||||
}
|
||||
sb.Append(']');
|
||||
ViewData.Add("treeString", sb.ToString());
|
||||
return View("Index");
|
||||
}
|
||||
private void serializeChannel(ref StringBuilder sb, ref List<Channel> allChannels, ref List<Account> allAccounts, Channel currentChannel)
|
||||
{
|
||||
allChannels.Remove(currentChannel);
|
||||
//"but adam", you say, "there's an href attribute, why make a link?" because that makes the entire bar a link, and trying to expand the node will probably click the link
|
||||
sb.Append($"{{\"text\": \"<a href=\\\"{Url.ActionLink(action: "Details", controller: "Channels", values: new {id = currentChannel.Id})}\\\">{currentChannel.DisplayName}</a>\"");
|
||||
sb.Append(", expanded:true ");
|
||||
var theseAccounts = allAccounts.Where(a => a.SeenInChannel?.Id == currentChannel.Id).ToList();
|
||||
allAccounts.RemoveAll(a => a.SeenInChannel?.Id == currentChannel.Id);
|
||||
var first = true;
|
||||
if (currentChannel.SubChannels != null || theseAccounts != null)
|
||||
{
|
||||
sb.Append(", \"nodes\": [");
|
||||
}
|
||||
if (currentChannel.SubChannels != null)
|
||||
{
|
||||
foreach (var subChannel in currentChannel.SubChannels)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
serializeChannel(ref sb, ref allChannels, ref allAccounts, subChannel);
|
||||
}
|
||||
if (theseAccounts != null && !first) //"first" here tells us that we have at least one subchannel
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
}
|
||||
if (theseAccounts != null)
|
||||
{
|
||||
first = true;
|
||||
sb.Append($"{{\"text\": \"(accounts: {theseAccounts.Count()})\", \"expanded\":true, nodes:[");
|
||||
foreach (var account in theseAccounts)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
serializeAccount(ref sb, account);
|
||||
}
|
||||
sb.Append("]}");
|
||||
}
|
||||
sb.Append("]}");
|
||||
}
|
||||
private void serializeAccount(ref StringBuilder sb, Account currentAccount)
|
||||
{
|
||||
sb.Append($"{{\"text\": \"<a href=\\\"{Url.ActionLink(action: "Details", controller: "Accounts", values: new {id = currentAccount.Id})}\\\">{currentAccount.DisplayName}</a>\"}}");
|
||||
}
|
||||
private void serializeUser(ref StringBuilder sb, ref List<Account> allAccounts, User currentUser)
|
||||
{
|
||||
sb.Append($"{{\"text\": \"<a href=\\\"{Url.ActionLink(action: "Details", controller: "Users", values: new {id = currentUser.Id})}\\\">");
|
||||
sb.Append(currentUser.DisplayName);
|
||||
sb.Append("</a>\", ");
|
||||
var ownedAccounts = allAccounts.Where(a => a.IsUser == currentUser);
|
||||
sb.Append("nodes: [");
|
||||
sb.Append($"{{\"text\": \"owned accounts:\", \"expanded\":true, \"nodes\": [");
|
||||
if (ownedAccounts != null)
|
||||
{
|
||||
foreach (var acc in ownedAccounts)
|
||||
{
|
||||
serializeAccount(ref sb, acc);
|
||||
sb.Append(',');
|
||||
}
|
||||
}
|
||||
sb.Append("]}]}");
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
39
WebInterface/Controllers/UsersController.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using vassago.Models;
|
||||
using vassago.WebInterface.Models;
|
||||
|
||||
namespace vassago.WebInterface.Controllers;
|
||||
|
||||
public class UsersController(ChattingContext db) : Controller
|
||||
{
|
||||
private ChattingContext Database => db;
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
return Database.Users != null ?
|
||||
View(await Database.Users.Include(u => u.Accounts).ToListAsync()) :
|
||||
Problem("Entity set '_db.Users' is null.");
|
||||
}
|
||||
public async Task<IActionResult> Details(Guid id)
|
||||
{
|
||||
var user = await Database.Users
|
||||
.Include(u => u.Accounts)
|
||||
.FirstAsync(u => u.Id == id);
|
||||
var allTheChannels = await Database.Channels.ToListAsync();
|
||||
foreach(var acc in user.Accounts)
|
||||
{
|
||||
acc.SeenInChannel = allTheChannels.FirstOrDefault(c => c.Id == acc.SeenInChannel.Id);
|
||||
}
|
||||
return Database.Users != null ?
|
||||
View(user) :
|
||||
Problem("Entity set '_db.Users' is null.");
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
90
WebInterface/Controllers/api/ChannelsControler.cs
Normal file
@ -0,0 +1,90 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using vassago.Models;
|
||||
using vassago.ProtocolInterfaces.DiscordInterface;
|
||||
|
||||
namespace vassago.Controllers.api;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ChannelsController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<ChannelsController> _logger;
|
||||
|
||||
public ChannelsController(ILogger<ChannelsController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Produces("application/json")]
|
||||
public Channel Get(Guid id)
|
||||
{
|
||||
return Rememberer.ChannelDetail(id);
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
[Produces("application/json")]
|
||||
public IActionResult Patch([FromBody] Channel channel)
|
||||
{
|
||||
var fromDb = Rememberer.ChannelDetail(channel.Id);
|
||||
if (fromDb == null)
|
||||
{
|
||||
_logger.LogError($"attempt to update channel {channel.Id}, not found");
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug($"patching {channel.DisplayName} (id: {channel.Id})");
|
||||
}
|
||||
//settable values: lewdness filter level, meanness filter level. maybe i could decorate them...
|
||||
fromDb.LewdnessFilterLevel = channel.LewdnessFilterLevel;
|
||||
fromDb.MeannessFilterLevel = channel.MeannessFilterLevel;
|
||||
Rememberer.RememberChannel(fromDb);
|
||||
return Ok(fromDb);
|
||||
}
|
||||
[HttpDelete]
|
||||
[Produces("application/json")]
|
||||
public IActionResult Delete([FromBody] Channel channel)
|
||||
{
|
||||
var fromDb = Rememberer.ChannelDetail(channel.Id);
|
||||
if (fromDb == null)
|
||||
{
|
||||
_logger.LogError($"attempt to delete channel {channel.Id}, not found");
|
||||
return NotFound();
|
||||
}
|
||||
deleteChannel(fromDb);
|
||||
return Ok();
|
||||
}
|
||||
private void deleteChannel(Channel channel)
|
||||
{
|
||||
if (channel.SubChannels?.Count > 0)
|
||||
{
|
||||
foreach (var childChannel in channel.SubChannels)
|
||||
{
|
||||
deleteChannel(childChannel);
|
||||
}
|
||||
}
|
||||
|
||||
if(channel.Users?.Count > 0)
|
||||
{
|
||||
foreach(var account in channel.Users)
|
||||
{
|
||||
deleteAccount(account);
|
||||
}
|
||||
}
|
||||
|
||||
Rememberer.ForgetChannel(channel);
|
||||
}
|
||||
private void deleteAccount(Account account)
|
||||
{
|
||||
var user = account.IsUser;
|
||||
var usersOnlyAccount = user.Accounts?.Count == 1;
|
||||
|
||||
Rememberer.ForgetAccount(account);
|
||||
|
||||
if(usersOnlyAccount)
|
||||
Rememberer.ForgetUser(user);
|
||||
}
|
||||
}
|
40
WebInterface/Controllers/api/UsersController.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using vassago.Models;
|
||||
using vassago.ProtocolInterfaces.DiscordInterface;
|
||||
|
||||
namespace vassago.Controllers.api;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<ChannelsController> _logger;
|
||||
|
||||
public UsersController(ILogger<ChannelsController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
[Produces("application/json")]
|
||||
public IActionResult Patch([FromBody] User user)
|
||||
{
|
||||
var fromDb = Rememberer.SearchUser(u => u.Id == user.Id);
|
||||
if (fromDb == null)
|
||||
{
|
||||
_logger.LogError($"attempt to update user {user.Id}, not found");
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug($"patching {user.DisplayName} (id: {user.Id})");
|
||||
}
|
||||
|
||||
//TODO: settable values: display name
|
||||
//fromDb.DisplayName = user.DisplayName;
|
||||
Rememberer.RememberUser(fromDb);
|
||||
return Ok(fromDb);
|
||||
}
|
||||
}
|
8
WebInterface/Models/ErrorPageViewModel.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace vassago.WebInterface.Models;
|
||||
|
||||
public class ErrorPageViewModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
}
|
68
WebInterface/Views/Accounts/Details.cshtml
Normal file
@ -0,0 +1,68 @@
|
||||
@model Account
|
||||
@using Newtonsoft.Json
|
||||
@using System.Text
|
||||
@{
|
||||
ViewData["Title"] = "Account details";
|
||||
}
|
||||
|
||||
<a href="/">home</a>/@Html.Raw(ViewData["breadcrumbs"])
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">belongs to user</th>
|
||||
<td>@Model.IsUser.DisplayName</td>
|
||||
<td><button alt="to do" disabled>separate</button></2td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Seen in channel</th>
|
||||
<td class="account @Model.SeenInChannel.Protocol"><div class="protocol-icon"> </div>@Model.SeenInChannel.LineageSummary<a href="/Channels/Details/@Model.SeenInChannel.Id">@Model.SeenInChannel.DisplayName</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Permission Tags</th>
|
||||
<td>
|
||||
<div id="tagsTree"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@section Scripts{
|
||||
|
||||
<script type="text/javascript">
|
||||
@{
|
||||
var accountAsString = JsonConvert.SerializeObject(Model, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
}
|
||||
const userOnLoad = @Html.Raw(accountAsString);
|
||||
function jsonifyUser() {
|
||||
var userNow = structuredClone(userOnLoad);
|
||||
userNow.Accounts = null;
|
||||
userNow.DisplayName = document.querySelector("#displayName").value;
|
||||
console.log(userNow);
|
||||
return userNow;
|
||||
}
|
||||
|
||||
function getTagsTree() {
|
||||
@{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("[{text: \"permission tags\", \"expanded\":true, nodes: [");
|
||||
var first = true;
|
||||
for (int i = 0; i < 1; i++)
|
||||
{
|
||||
if (!first)
|
||||
sb.Append(',');
|
||||
sb.Append($"{{text: \"<input type=\\\"checkbox\\\" > is goated (w/ sauce)</input>\"}}");
|
||||
first = false;
|
||||
}
|
||||
sb.Append("]}]");
|
||||
}
|
||||
console.log(@Html.Raw(sb.ToString()));
|
||||
var tree = @Html.Raw(sb.ToString());
|
||||
return tree;
|
||||
}
|
||||
$('#tagsTree').bstreeview({ data: getTagsTree() });
|
||||
document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } });
|
||||
</script>
|
||||
}
|
169
WebInterface/Views/Channels/Details.cshtml
Normal file
@ -0,0 +1,169 @@
|
||||
@using System.ComponentModel
|
||||
@using Newtonsoft.Json
|
||||
@using System.Text;
|
||||
@model Tuple<Channel, Enumerations.LewdnessFilterLevel, Enumerations.MeannessFilterLevel>
|
||||
@{
|
||||
var ThisChannel = Model.Item1;
|
||||
var IfInheritedLewdnessFilterLevel = Model.Item2;
|
||||
var IfInheritedMeannessFilterLevel = Model.Item3;
|
||||
}
|
||||
|
||||
<a href="/">home</a>/
|
||||
@Html.Raw(ViewData["breadcrumbs"])
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Display Name</th>
|
||||
<td>@ThisChannel.DisplayName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Channel type</th>
|
||||
<td>@(ThisChannel.ChannelType != null ? Enumerations.GetDescription(ThisChannel.ChannelType) : "?")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Lewdness Filter Level</th>
|
||||
<td>
|
||||
<select name="LewdnessFilterLevel" id="LewdnessFilterLevel" onchange="patchModel(jsonifyChannel(), '/api/Channels/')">
|
||||
<!option value="" @(ThisChannel.LewdnessFilterLevel == null ? "selected" : "")>⤵ inherited - @Enumerations.GetDescription(IfInheritedLewdnessFilterLevel)</!option>
|
||||
@foreach (Enumerations.LewdnessFilterLevel enumVal in
|
||||
Enum.GetValues(typeof(Enumerations.LewdnessFilterLevel)))
|
||||
{
|
||||
<!option value="@((int)enumVal)" @(ThisChannel.LewdnessFilterLevel == enumVal ? "selected" : "")>
|
||||
@(Enumerations.GetDescription<Enumerations.LewdnessFilterLevel>(enumVal))</!option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Links Allowed</th>
|
||||
<td>@(ThisChannel.LinksAllowed?.ToString() ?? "unknown")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Lineage summary</th>
|
||||
<td>@ThisChannel.LineageSummary</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">max attachment bytes</th>
|
||||
<td>@ThisChannel.MaxAttachmentBytes (i hear there's "ByteSize")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">max message length</th>
|
||||
<td>@(ThisChannel.MaxTextChars?.ToString() ?? "inherited")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Meanness Filter Level</th>
|
||||
<td>
|
||||
<select name="MeannessFilterLevel" id="MeannessFilterLevel" onchange="patchModel(jsonifyChannel(), '/api/Channels/')">
|
||||
<!option value="" @(ThisChannel.MeannessFilterLevel == null ? "selected" : "")>⤵ inherited - @Enumerations.GetDescription(IfInheritedMeannessFilterLevel)</!option>
|
||||
@foreach (Enumerations.MeannessFilterLevel enumVal in
|
||||
Enum.GetValues(typeof(Enumerations.MeannessFilterLevel)))
|
||||
{
|
||||
<!option value="@((int)enumVal)" @(ThisChannel.MeannessFilterLevel == enumVal ? "selected" : "")>
|
||||
@(Enumerations.GetDescription<Enumerations.MeannessFilterLevel>(enumVal))</!option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Messages (count)</th>
|
||||
<td>@(ThisChannel.Messages?.Count ?? 0)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Protocol</th>
|
||||
<td>@ThisChannel.Protocol</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Reactions Possible</th>
|
||||
<td>@(ThisChannel.ReactionsPossible?.ToString() ?? "inherited")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Sub Channels</th>
|
||||
<td>
|
||||
@if((ThisChannel.SubChannels?.Count ?? 0) > 0)
|
||||
{
|
||||
@Html.Raw("<div id=\"channelsTree\"></div>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@Html.Raw("0")
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Accounts</th>
|
||||
<td>
|
||||
@if((ThisChannel.Users?.Count ?? 0) > 0)
|
||||
{
|
||||
@Html.Raw("<div id=\"accountsTree\"></div>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@Html.Raw("none")
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<button onclick="forget()">forget</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@section Scripts{
|
||||
<script type="text/javascript">
|
||||
@{
|
||||
var modelAsString = JsonConvert.SerializeObject(ThisChannel, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
}
|
||||
const channelOnLoad = @Html.Raw(modelAsString);
|
||||
function jsonifyChannel() {
|
||||
var channelNow = structuredClone(channelOnLoad);
|
||||
channelNow.SubChannels = null;
|
||||
channelNow.ParentChannel = null;
|
||||
channelNow.Messages = null;
|
||||
channelNow.Users = null;
|
||||
|
||||
channelNow.LewdnessFilterLevel = document.querySelector("#LewdnessFilterLevel").value;
|
||||
channelNow.MeannessFilterLevel = document.querySelector("#MeannessFilterLevel").value;
|
||||
console.log(channelNow);
|
||||
return channelNow;
|
||||
}
|
||||
function forget(){
|
||||
console.log("here we go");
|
||||
if(window.confirm("delete? really really?") == true){
|
||||
deleteModel(jsonifyChannel(), '/api/Channels/');
|
||||
}
|
||||
}
|
||||
|
||||
function channelsTree() {
|
||||
var tree = @Html.Raw(ViewData["channelsTree"]);
|
||||
return tree;
|
||||
}
|
||||
|
||||
function accountsTree() {
|
||||
@{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("[{text: \"accounts\", \"expanded\":true, nodes: [");
|
||||
var first = true;
|
||||
foreach (var acc in ThisChannel.Users.OrderBy(a => a.SeenInChannel.LineageSummary))
|
||||
{
|
||||
if(!first)
|
||||
sb.Append(',');
|
||||
sb.Append($"{{text: \"<div class=\\\"account {acc.Protocol}\\\"><div class=\\\"protocol-icon\\\"> </div>{acc.SeenInChannel.LineageSummary}/<a href=\\\"/Accounts/Details/{acc.Id}\\\">{acc.DisplayName}</a>\"}}");
|
||||
first=false;
|
||||
}
|
||||
sb.Append("]}]");
|
||||
}
|
||||
//console.log(@Html.Raw(sb.ToString()));
|
||||
var tree = @Html.Raw(sb.ToString());
|
||||
return tree;
|
||||
}
|
||||
$('#channelsTree').bstreeview({ data: channelsTree() });
|
||||
$('#accountsTree').bstreeview({ data: accountsTree() });
|
||||
|
||||
</script>
|
||||
}
|
42
WebInterface/Views/Channels/Index.cshtml
Normal file
@ -0,0 +1,42 @@
|
||||
@model IEnumerable<Channel>
|
||||
@{
|
||||
ViewData["Title"] = "Channels";
|
||||
}
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
protocol
|
||||
</th>
|
||||
<th>type</th>
|
||||
<th>
|
||||
display name
|
||||
</th>
|
||||
<th>
|
||||
Lineage
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model) {
|
||||
<tr>
|
||||
<td class="@item.Protocol">
|
||||
<div class="protocol-icon"> </div>
|
||||
</td>
|
||||
<td class="@item.ChannelType">
|
||||
<div class="channel-type-icon"> </div>
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.DisplayName)
|
||||
</td>
|
||||
<td>
|
||||
@item.LineageSummary
|
||||
</td>
|
||||
<td>
|
||||
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
16
WebInterface/Views/Home/Index.cshtml
Normal file
@ -0,0 +1,16 @@
|
||||
@{
|
||||
ViewData["Title"] = "Home Page";
|
||||
}
|
||||
<div id="tree">tree here</div>
|
||||
|
||||
@section Scripts{
|
||||
<script type="text/javascript">
|
||||
function getTree() {
|
||||
var tree = @Html.Raw(ViewData["treeString"]);
|
||||
console.log(tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
$('#tree').bstreeview({ data: getTree() });
|
||||
</script>
|
||||
}
|
25
WebInterface/Views/Shared/Error.cshtml
Normal file
@ -0,0 +1,25 @@
|
||||
@model vassago.WebInterface.Models.ErrorPageViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
25
WebInterface/Views/Shared/_Layout.cshtml
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - vassago</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/css/fontawesome.min.css" />
|
||||
<link rel="stylesheet" href="~/css/bs.min.treeview.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/vassago.styles.css" asp-append-version="true" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/bstreeview.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
48
WebInterface/Views/Shared/_Layout.cshtml.css
Normal file
@ -0,0 +1,48 @@
|
||||
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
for details on configuring this project to bundle and minify static web assets. */
|
||||
|
||||
a.navbar-brand {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
button.accept-policy {
|
||||
font-size: 1rem;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
line-height: 60px;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
90
WebInterface/Views/Users/Details.cshtml
Normal file
@ -0,0 +1,90 @@
|
||||
@model User
|
||||
@using Newtonsoft.Json
|
||||
@using System.Text
|
||||
@{
|
||||
ViewData["Title"] = "User details";
|
||||
}
|
||||
|
||||
<a href="/">home</a>/@Html.Raw(ViewData["breadcrumbs"])
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Display Name (here)</th>
|
||||
<td><input type="text" id="displayName" value="@Model.DisplayName" disabled alt="todo"></input> <button
|
||||
onclick="patchModel(jsonifyUser(), @Html.Raw("'/api/Users/'"))" disabled alt"todo">update</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Accounts</th>
|
||||
<td>
|
||||
<div id="accountsTree"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Permission Tags</th>
|
||||
<td>
|
||||
<div id="tagsTree"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@section Scripts{
|
||||
|
||||
<script type="text/javascript">
|
||||
@{
|
||||
var userAsString = JsonConvert.SerializeObject(Model, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
}
|
||||
const userOnLoad = @Html.Raw(userAsString);
|
||||
function jsonifyUser() {
|
||||
var userNow = structuredClone(userOnLoad);
|
||||
userNow.Accounts = null;
|
||||
userNow.DisplayName = document.querySelector("#displayName").value;
|
||||
//userNow.Tag_CanTwitchSummon = document.querySelector("#tagCanTwitchSummon").checked;
|
||||
console.log(userNow);
|
||||
return userNow;
|
||||
}
|
||||
function getAccountsTree() {
|
||||
@{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("[{text: \"accounts\", \"expanded\":true, nodes: [");
|
||||
var first = true;
|
||||
foreach (var acc in Model.Accounts.OrderBy(a => a.SeenInChannel.LineageSummary))
|
||||
{
|
||||
if (!first)
|
||||
sb.Append(',');
|
||||
sb.Append($"{{text: \"<div class=\\\"account {acc.Protocol}\\\"><div class=\\\"protocol-icon\\\"> </div>{acc.SeenInChannel.LineageSummary}/<a href=\\\"/Accounts/Details/{acc.Id}\\\">{acc.DisplayName}</a>\"}}");
|
||||
first = false;
|
||||
}
|
||||
sb.Append("]}]");
|
||||
}
|
||||
console.log(@Html.Raw(sb.ToString()));
|
||||
var tree = @Html.Raw(sb.ToString());
|
||||
return tree;
|
||||
}
|
||||
|
||||
function getTagsTree() {
|
||||
@{
|
||||
sb = new StringBuilder();
|
||||
sb.Append("[{text: \"permission tags\", \"expanded\":true, nodes: [");
|
||||
first = true;
|
||||
for (int i = 0; i < 1; i++)
|
||||
{
|
||||
if (!first)
|
||||
sb.Append(',');
|
||||
sb.Append($"{{text: \"<input type=\\\"checkbox\\\" > is goated (w/ sauce)</input>\"}}");
|
||||
first = false;
|
||||
}
|
||||
sb.Append("]}]");
|
||||
}
|
||||
console.log(@Html.Raw(sb.ToString()));
|
||||
var tree = @Html.Raw(sb.ToString());
|
||||
return tree;
|
||||
}
|
||||
$('#accountsTree').bstreeview({ data: getAccountsTree() });
|
||||
$('#tagsTree').bstreeview({ data: getTagsTree() });
|
||||
document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } });
|
||||
</script>
|
||||
}
|
38
WebInterface/Views/Users/Index.cshtml
Normal file
@ -0,0 +1,38 @@
|
||||
@model IEnumerable<User>
|
||||
@{
|
||||
ViewData["Title"] = "Users";
|
||||
}
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
@Html.DisplayNameFor(model => model.Id)
|
||||
</th>
|
||||
<th>
|
||||
name*
|
||||
</th>
|
||||
<th>
|
||||
number of associated accounts
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model) {
|
||||
<tr>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.Id)
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.DisplayName)
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.Accounts.Count)x
|
||||
</td>
|
||||
<td>
|
||||
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
3
WebInterface/Views/_ViewImports.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@using vassago
|
||||
@using vassago.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
3
WebInterface/Views/_ViewStart.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"token": "59 chars",
|
||||
"botChatterChannel": 0,
|
||||
"announcementChannel": 0
|
||||
}
|
17
appsettings.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.EntityFrameworkCore": "None"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"DiscordTokens": [
|
||||
|
||||
],
|
||||
"TwitchConfigs": [
|
||||
],
|
||||
"exchangePairsLocation": "assets/exchangepairs.json",
|
||||
"DBConnectionString": "Host=azure.club;Database=db;Username=user;Password=password"
|
||||
}
|
BIN
assets/7e45c22053d79f9adb80c7552ae84be6.png
Normal file
After Width: | Height: | Size: 293 KiB |
BIN
assets/coding and algorithms.png
Normal file
After Width: | Height: | Size: 107 KiB |
307
assets/conversion.json
Normal file
@ -0,0 +1,307 @@
|
||||
{
|
||||
"units":[
|
||||
{
|
||||
"canonical": "℉",
|
||||
"aliases": [
|
||||
"degrees f",
|
||||
"deg f",
|
||||
"degf",
|
||||
"fahrenheit",
|
||||
"deg fahrenheit",
|
||||
"degrees fahrenheit",
|
||||
"°f",
|
||||
"° f",
|
||||
"℉",
|
||||
"f"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"°C",
|
||||
"aliases": [
|
||||
"degrees c",
|
||||
"deg c",
|
||||
"degc",
|
||||
"celsius",
|
||||
"deg celsiu",
|
||||
"degrees celsiu",
|
||||
"°c",
|
||||
"° c",
|
||||
"c"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"m",
|
||||
"aliases": [
|
||||
"meter",
|
||||
"metre"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"mm",
|
||||
"aliases": [
|
||||
"millimeter",
|
||||
"millimetre"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"km",
|
||||
"aliases": [
|
||||
"kilometer",
|
||||
"kilometre"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"kg",
|
||||
"aliases": [
|
||||
"kilogram",
|
||||
"kilo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"g",
|
||||
"aliases": [
|
||||
"gram"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"lb",
|
||||
"aliases": [
|
||||
"pound"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"oz",
|
||||
"aliases": [
|
||||
"ounce"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"floz",
|
||||
"aliases": [
|
||||
"us fl oz",
|
||||
"us fl ounce",
|
||||
"us fluid ounce",
|
||||
"us fluid oz"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"mL",
|
||||
"aliases": [
|
||||
"ml",
|
||||
"milliletre",
|
||||
"millileter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"ft",
|
||||
"aliases": [
|
||||
"'",
|
||||
"feet",
|
||||
"foot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"in",
|
||||
"aliases": [
|
||||
"\"",
|
||||
"inch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"yd",
|
||||
"aliases": [
|
||||
"yard"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"hhd",
|
||||
"aliases": [
|
||||
"hogshead"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"gal",
|
||||
"aliases": [
|
||||
"gallon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"tbsp",
|
||||
"aliases": [
|
||||
"tablespoon",
|
||||
"table spoon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"tsp",
|
||||
"aliases": [
|
||||
"teaspoon",
|
||||
"tea spoon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"qt",
|
||||
"aliases": [
|
||||
"quart"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"ac",
|
||||
"aliases": [
|
||||
"acres"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"AU",
|
||||
"aliases": [
|
||||
"au",
|
||||
"astronomical unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"Pa",
|
||||
"aliases": [
|
||||
"pascal",
|
||||
"pa"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"kPa",
|
||||
"aliases": [
|
||||
"kilopascal",
|
||||
"kpa"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"atm",
|
||||
"aliases": [
|
||||
"atmosphere"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"m^2",
|
||||
"aliases": [
|
||||
"square meter",
|
||||
"meter squared",
|
||||
"meters squared",
|
||||
"sq meter",
|
||||
"sq m",
|
||||
"sqm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"km^2",
|
||||
"aliases": [
|
||||
"square kilometer",
|
||||
"kilometer squared",
|
||||
"kilometers squared",
|
||||
"sq kilometer",
|
||||
"sq km",
|
||||
"sqkm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"ly",
|
||||
"aliases": [
|
||||
"light year"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"pc",
|
||||
"aliases": [
|
||||
"parsec"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"mi",
|
||||
"aliases": [
|
||||
"mile"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"blue whale length",
|
||||
"aliases": [
|
||||
"bwl",
|
||||
"whales"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"ångström",
|
||||
"aliases": [
|
||||
"angstrom",
|
||||
"Å"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"μm",
|
||||
"aliases": [
|
||||
"micrometers",
|
||||
"micrometres",
|
||||
"microns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical":"uncle jordan",
|
||||
"aliases":[
|
||||
"uj"
|
||||
]
|
||||
}
|
||||
],
|
||||
"linearPairs":[
|
||||
{"item1":"kg", "item2":"g", "factor":1000},
|
||||
{"item1":"lb", "item2":"oz", "factor":16},
|
||||
{"item1":"kg", "item2":"lb", "factor":2.204623},
|
||||
{"item1":"kg", "item2":"stone", "factor":0.157473},
|
||||
|
||||
{"item1":"km", "item2":"m", "factor":1000},
|
||||
{"item1":"mi", "item2":"ft", "factor":5280},
|
||||
{"item1":"m", "item2":"in", "factor":39.37008},
|
||||
{"item1":"m", "item2":"cm", "factor":100},
|
||||
{"item1":"cm", "item2":"mm", "factor":10},
|
||||
{"item1":"m", "item2":"μm", "factor":1000000},
|
||||
{"item1":"km", "item2":"mi", "factor":0.6213712},
|
||||
{"item1":"ft", "item2":"in", "factor":12},
|
||||
{"item1":"yd", "item2":"ft", "factor":3},
|
||||
{"item1":"football field", "item2":"yd", "factor":100},
|
||||
{"item1":"chain", "item2":"yd", "factor":22},
|
||||
{"item1":"chain", "item2":"link", "factor":100},
|
||||
{"item1":"furlong", "item2":"mi", "factor":8},
|
||||
{"item1":"rod", "item2":"ft", "factor":16.5},
|
||||
{"item1":"AU", "item2":"ly", "factor": 0.0000158125},
|
||||
{"item1":"ly", "item2":"km", "factor": 946070000000},
|
||||
{"item1":"pc", "item2":"AU", "factor":206266.3},
|
||||
{"item1":"blue whale length", "item2": "m", "factor": 29.9},
|
||||
{"item1":"m", "item2": "ångström", "factor": 10000000000},
|
||||
{"item1":"smoot", "item2": "ft", "factor": 5.583333333333},
|
||||
{"item1":"uncle jordan", "item2": "cm", "factor":192.405},
|
||||
|
||||
{"item1":"floz", "item2":"mL", "factor":29.57344},
|
||||
{"item1":"L", "item2":"mL", "factor":1000},
|
||||
{"item1":"L", "item2":"floz", "factor":33.81402},
|
||||
{"item1":"hhd", "item2":"gal", "factor":54},
|
||||
{"item1":"barrel", "item2":"kilderkin", "factor":2},
|
||||
{"item1":"barrel", "item2":"firkin", "factor":4},
|
||||
{"item1":"firkin", "item2":"gal", "factor":10.8},
|
||||
{"item1":"pint", "item2":"floz", "factor":16},
|
||||
{"item1":"cup", "item2":"floz", "factor":8},
|
||||
{"item1":"gill", "item2":"floz", "factor":4},
|
||||
{"item1":"tbsp", "item2":"tsp", "factor":3},
|
||||
{"item1":"tbsp", "item2":"floz", "factor":0.5},
|
||||
{"item1":"gal", "item2":"floz", "factor":128},
|
||||
{"item1":"gal", "item2":"qt", "factor":4},
|
||||
|
||||
|
||||
{"item1":"acre", "item2":"yd^2", "factor":4840},
|
||||
{"item1":"yd^2", "item2":"m^2", "factor":0.836127},
|
||||
|
||||
{"item1":"mph", "item2":"knot", "factor":0.868976},
|
||||
{"item1":"mph", "item2":"kph", "factor":1.609343550606653},
|
||||
|
||||
|
||||
{"item1":"kPa", "item2":"Pa", "factor":1000},
|
||||
{"item1":"Nm^2", "item2":"Pa", "factor":1},
|
||||
{"item1":"Pa", "item2":"bar", "factor":100},
|
||||
{"item1":"atm", "item2":"Pa", "factor":101325},
|
||||
{"item1":"bar", "item2":"psi", "factor":14.5038}
|
||||
]
|
||||
}
|
BIN
assets/ekgblip.png
Normal file
After Width: | Height: | Size: 16 KiB |
78
assets/jokes.txt
Normal file
@ -0,0 +1,78 @@
|
||||
I got a joke for you: wealth trickles down.
|
||||
Why did the celebrity egg start losing her friends? They called her a shell-out.
|
||||
What's the difference between a hippo and a zippo? One is really heavy and the other is a little lighter.
|
||||
The best time on a watch is 6:30, hands down. (ask your parents, young ones)
|
||||
What killed the painter? He had too many strokes
|
||||
Artists know how to draw the line, so you can’t really peer pressure them.
|
||||
Why did the hand cross the road? To get to the secondhand store.
|
||||
Why do seagulls fly over the ocean? Because if they flew over the bay, we'd call them bagels.
|
||||
Why don't oysters donate to charity? Because they're shellfish
|
||||
I only know 25 letters of the alphabet. I don't know y.
|
||||
What did the fish say when it ran into a wall? Dam.
|
||||
What do you call a factory that makes okay products? A satisfactory.
|
||||
What do you call a fly without wings? A walk.
|
||||
Don't trust those trees. They seem kind of shady.
|
||||
I don't trust stairs. They're up to something.
|
||||
What did the teacher do with the students report on cheese? Grated it.
|
||||
What do you call a man with no legs and arms in a pool? Bob.
|
||||
I was going to tell a joke about hammers but ...I don't think I'll nail it
|
||||
why did the can recycler quit his job? because it was so depressing.
|
||||
They told me to stop impersonating a flamingo. I had to put my foot down.
|
||||
I failed math so many times at school, I can’t even count.
|
||||
When I was a child, I threw a boomerang, but it didn't come back. I live in constant fear.
|
||||
When life gives you melons, you might be dyslexic.
|
||||
Don’t you hate it when someone answers their own questions? I do.
|
||||
It takes a lot of balls to golf the way I do.
|
||||
The problem with kleptomaniacs is that they always take things literally.
|
||||
Can you believe I got fired from the calendar factory? All I did was take a day off.
|
||||
Most people are shocked when they find out how bad I am as an electrician.
|
||||
I was addicted to the hokey pokey, but then I turned myself around.
|
||||
A termite walks into the bar and asks; is the bartender here?
|
||||
Just burned 2,000 calories. That’s the last time I leave brownies in the oven while I nap.
|
||||
A recent study has found that women who carry a little extra weight live longer than the men who mention it.
|
||||
I got a new pair of gloves today, but they’re both lefts, which on the one hand is great, but on the other, it’s just not right.
|
||||
I can tell when people are being judgmental just by looking at them.
|
||||
Are people born with photographic memories? or does it take time to develop (ask your parents, young ones)
|
||||
I buy all my guns from a guy called T-Rex. He’s a small arms dealer.
|
||||
A book fell on my head the other day. I only have my shelf to blame though.
|
||||
If you don’t pay your exorcist, do you get repossessed?
|
||||
A ghost walked into a bar and ordered a shot of vodka. The bartender said, ‘Sorry, we don’t serve spirits here.’
|
||||
A blind man walked into a bar. and a table. and a chair.
|
||||
How do you make holy water? You boil the hell out of it.
|
||||
My teachers told me I’d never amount to much because I procrastinate so much. I told them, “Just you wait!”
|
||||
what's the best part about living in switzerland? well the flag is a big plus.
|
||||
I asked my date to meet me at the gym today. She didn't show up. That's when I knew we weren't gonna work out.
|
||||
The CEO of IKEA was elected Prime Minister in Sweden. He should have his cabinet together by the end of the weekend.
|
||||
The problem with troubleshooting is that trouble shoots back.
|
||||
Today a man knocked on my door and asked for a small donation towards the local swimming pool. I gave him a glass of water.
|
||||
Apparently I snore so loudly that it scares everyone in the car I'm driving.
|
||||
I think my neighbor is stalking me. she's been googling my name on her computer. I saw it through my telescope last night.
|
||||
I wasn't originally going to get a brain transplant, but then I changed my mind.
|
||||
I just found out I'm colorblind. The diagnosis came completely out of the purple.
|
||||
My girlfriend told me she was leaving me because I keep pretending to be a Transformer. I said, "No, wait! I can change."
|
||||
I started out with nothing, and I still have most of it.
|
||||
eBay is so useless. I tried to look up lighters and all they had was 13,749 matches.
|
||||
About a month before he died, my uncle had his back covered in lard. After that, he went down hill fast.
|
||||
I was addicted to the hokey pokey... but thankfully, I turned myself around.
|
||||
I'm glad I know sign language, it's pretty handy.
|
||||
What did Cinderella say when she got to the ball? "ggggh!"
|
||||
R.I.P boiled water. You will be mist.
|
||||
To the mathematicians who thought of the idea of zero, thanks for nothing!
|
||||
I hate people who use big words just to make themselves look perspicacious.
|
||||
Two wrongs don't make a right, take your parents as an example.
|
||||
People used to laugh at me when I would say "I want to be a comedian", but nobody's laughing now.
|
||||
I threw a ball for my dog... It's a bit extravagant I know, but it was his birthday and he looks great in a dinner jacket.
|
||||
I refused to believe my road worker father was stealing from his job, but when I got home, all the signs were there.
|
||||
Did you hear about the kidnapping at school? It's okay. He woke up.
|
||||
Pavlov walks into a bar. The phone rings, and he says, "Damn, I forgot to feed the dog." (ask your parents, young ones.)
|
||||
Cleaning mirrors is a job I could really see myself doing.
|
||||
What's the difference between a poorly dressed man on a bicycle and a nicely dressed man on a tricycle? Attire.
|
||||
When does a pun become a dad joke? When the punchline becomes apparent.
|
||||
There's 10 kinds of people in the world. Those who think in decimal, those who think in binary, and those who knew this joke would be in base 3.
|
||||
There's 2 kinds of people in the world. Those who divide the entire human population into 2 arbitrary groups, and those who don't.
|
||||
A horse walks into a bar. the bartender says "hey man, you're in here kind of a lot. do you ever think you might be an alcoholic?" the horse says "no" and promptly vanishes. (the joke is a reference the famous philosophical phrase "i think, therefore i am" but if i explained that before the rest of the joke that would be putting descartes before the horse)
|
||||
Someone broke into my house and stole all my fruits. I'm peachless.
|
||||
Did I tell you guys about that flat earther i got into an argument with? he got so mad he stormed off saying he'd walk to the edge of the earth to prove me wrong, but he'll come around eventually.
|
||||
What do you call your friend who stands in a hole? Phil.
|
||||
What happened when the bear swallowed a clock? He got ticks.
|
||||
What do you call a wolf who gets lost? A where-wolf.
|
BIN
assets/loud sweating.gif
Normal file
After Width: | Height: | Size: 261 KiB |
36
devuitls.sh
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
servicename="vassago"
|
||||
pw_developmentdatabase="wnmhOttjA0wCiR9hVoG7jjrf90SxWvAV"
|
||||
connnectionstr="Host=localhost;Database=${servicename}_dev;Username=${servicename};Password=${pw_developmentdatabase};IncludeErrorDetail=true;"
|
||||
|
||||
case "$1" in
|
||||
"initial")
|
||||
sudo -u postgres psql -c "create database ${servicename}_dev;"
|
||||
sudo -u postgres psql -c "create user $servicename with encrypted password '$pw_developmentdatabase';"
|
||||
sudo -u postgres psql -c "grant all privileges on database ${servicename}_dev to $servicename;"
|
||||
sudo -u postgres psql -d "${servicename}_dev" -c "GRANT ALL ON SCHEMA public TO $servicename"
|
||||
|
||||
cp appsettings.sample.json appsettings.json
|
||||
dotnet ef database update --connection "$connnectionstr"
|
||||
;;
|
||||
|
||||
"add-migration")
|
||||
dotnet ef migrations add "$2"
|
||||
dotnet ef database update --connection "$connnectionstr"
|
||||
;;
|
||||
|
||||
"dbupdate")
|
||||
dotnet ef database update --connection "$connnectionstr"
|
||||
;;
|
||||
|
||||
"db-fullreset")
|
||||
sudo -u postgres psql -c "drop database ${servicename}_dev;"
|
||||
sudo -u postgres psql -c "drop user $servicename"
|
||||
$0 "initial"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command '$1', try 'initial'"
|
||||
;;
|
||||
esac
|
||||
|
80
externalProcess.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace vassago
|
||||
{
|
||||
public class ExternalProcess
|
||||
{
|
||||
public static bool GoPlz(string commandPath, string commandArguments)
|
||||
{
|
||||
var process = readableProcess(commandPath, commandArguments);
|
||||
var outputData = new StringBuilder();
|
||||
process.OutputDataReceived += new DataReceivedEventHandler((s, e) =>
|
||||
{
|
||||
outputData.Append(e.Data);
|
||||
});
|
||||
var errorData = new StringBuilder();
|
||||
process.ErrorDataReceived += new DataReceivedEventHandler((s, e) =>
|
||||
{
|
||||
errorData.Append(e.Data);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
process.BeginErrorReadLine();
|
||||
process.BeginOutputReadLine();
|
||||
process.WaitForExit();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
var dumpDir = $"fail{DateTime.Now.ToFileTimeUtc()}";
|
||||
var outputFilename = $"{dumpDir}/output0.log";
|
||||
var errorFilename = $"{dumpDir}/error0.err";
|
||||
if(!Directory.Exists(dumpDir))
|
||||
{
|
||||
Directory.CreateDirectory(dumpDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
var i = 0;
|
||||
foreach(var file in Directory.GetFiles(dumpDir))
|
||||
{
|
||||
var thisNummatch = Regex.Matches(Path.GetFileNameWithoutExtension(file), "[^\\d](\\d+)\\.err$").LastOrDefault().Value;
|
||||
if(!string.IsNullOrWhiteSpace(thisNummatch))
|
||||
{
|
||||
var thisNumval = 0;
|
||||
if(int.TryParse(thisNummatch, out thisNumval) && thisNumval > i)
|
||||
{
|
||||
i = thisNumval;
|
||||
}
|
||||
}
|
||||
}
|
||||
outputFilename = $"{dumpDir}/output{i}.log";
|
||||
errorFilename = $"{dumpDir}/error{i}.err";
|
||||
}
|
||||
File.WriteAllText(outputFilename, outputData.ToString());
|
||||
File.WriteAllText(errorFilename, JsonConvert.SerializeObject(e, Formatting.Indented));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private static Process readableProcess(string commandPath, string commandArguments)
|
||||
{
|
||||
var pi = new ProcessStartInfo(commandPath, commandArguments);
|
||||
pi.UseShellExecute = false;
|
||||
pi.CreateNoWindow = true;
|
||||
pi.RedirectStandardError = true;
|
||||
pi.RedirectStandardOutput = true;
|
||||
var process = new Process();
|
||||
process.StartInfo = pi;
|
||||
return process;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RootNamespace>silverworker_discord</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="discord.net" Version="2.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="AppSettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
51
vassago.csproj
Normal file
@ -0,0 +1,51 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);CA2254</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="bootstrap" Version="5.3.3" />
|
||||
<PackageReference Include="discord.net" Version="3.10.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.2" />
|
||||
<PackageReference Include="RestSharp" Version="110.2.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.6.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.6.2" />
|
||||
<PackageReference Include="TwitchLib" Version="3.5.3" />
|
||||
<PackageReference Include="youtubedlsharp" Version="0.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="assets/jokes.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="assets/coding and algorithms.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="assets/ekgblip.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="assets/conversion.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="assets/loud sweating.gif">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
10
wwwroot/css/bstreeview.min.css
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
@preserve
|
||||
bstreeview.css
|
||||
Version: 1.2.0
|
||||
Authors: Sami CHNITER <sami.chniter@gmail.com>
|
||||
Copyright 2020
|
||||
License: Apache License 2.0
|
||||
Project: https://github.com/nhmvienna/bs5treeview
|
||||
*/
|
||||
.bstreeview{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem;padding:0;overflow:hidden}.bstreeview .list-group{margin-bottom:0}.bstreeview .list-group-item{border-radius:0;border-width:1px 0 0 0;padding-top:.5rem;padding-bottom:.5rem;cursor:pointer}.bstreeview .list-group-item:hover{background-color:#dee2e6}.bstreeview>.list-group-item:first-child{border-top-width:0}.bstreeview .state-icon{margin-right:8px}.bstreeview .item-icon{margin-right:5px}
|
9
wwwroot/css/fontawesome.min.css
vendored
Normal file
46
wwwroot/css/site.css
Normal file
@ -0,0 +1,46 @@
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
.protocol-icon,.channel-type-icon{
|
||||
display:inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-size: 32px;
|
||||
}
|
||||
.discord .protocol-icon{
|
||||
background-image: url("../imgs/discord_logo1600.png");
|
||||
}
|
||||
.twitch .protocol-icon{
|
||||
background-image: url("../imgs/twitch.png");
|
||||
}
|
||||
.Normal .channel-type-icon{
|
||||
background-color: black;
|
||||
}
|
||||
.DM .channel-type-icon{
|
||||
background-color: black;
|
||||
}
|
||||
.Protocol .channel-type-icon{
|
||||
background-color: black;
|
||||
}
|
||||
.OU .channel-type-icon{
|
||||
background-color: black;
|
||||
}
|
BIN
wwwroot/favicon.ico
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
wwwroot/imgs/discord_logo1600.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
wwwroot/imgs/twitch.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
10
wwwroot/js/bstreeview.min.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
@preserve
|
||||
bstreeview.js
|
||||
Version: 1.2.0
|
||||
Authors: Sami CHNITER <sami.chniter@gmail.com>
|
||||
Copyright 2020
|
||||
License: Apache License 2.0
|
||||
Project:https://github.com/nhmvienna/bs5treeview
|
||||
*/
|
||||
!function (t, e, i, s) { "use strict"; var n = { expandIcon: "fa fa-angle-down fa-fw", collapseIcon: "fa fa-angle-right fa-fw", expandClass: 'show', indent: 1.25, parentsMarginLeft: "1.25rem", openNodeLinkOnNewTab: !0 }, a = '<div role="treeitem" class="list-group-item" data-bs-toggle="collapse"></div>', d = '<div role="group" class="list-group collapse" id="itemid"></div>', o = '<i class="state-icon"></i>', r = '<i class="item-icon"></i>'; function l(e, i) { this.element = e, this.itemIdPrefix = e.id + "-item-", this.settings = t.extend({}, n, i), this.init() } t.extend(l.prototype, { init: function () { this.tree = [], this.nodes = [], this.settings.data && (this.settings.data.isPrototypeOf(String) && (this.settings.data = t.parseJSON(this.settings.data)), this.tree = t.extend(!0, [], this.settings.data), delete this.settings.data), t(this.element).addClass("bstreeview"), this.initData({ nodes: this.tree }); var i = this; this.build(t(this.element), this.tree, 0), t(this.element).on("click", ".list-group-item", function (s) { t(".state-icon", this).toggleClass(i.settings.expandIcon).toggleClass(i.settings.collapseIcon), s.target.hasAttribute("href") && (i.settings.openNodeLinkOnNewTab ? e.open(s.target.getAttribute("href"), "_blank") : e.location = s.target.getAttribute("href")) }) }, initData: function (e) { if (e.nodes) { var i = e, s = this; t.each(e.nodes, function (t, e) { e.nodeId = s.nodes.length, e.parentId = i.nodeId, s.nodes.push(e), e.nodes && s.initData(e) }) } }, build: function (e, i, s) { var n = this, l = n.settings.parentsMarginLeft; s > 0 && (l = (n.settings.indent + s * n.settings.indent).toString() + "rem;"), s += 1, t.each(i, function (i, g) { var h = t(a).attr("data-bs-target", "#" + n.itemIdPrefix + g.nodeId).attr("style", "padding-left:" + l).attr("aria-level", s); if (g.nodes) { var c = t(o).addClass((g.expanded)?n.settings.expandIcon:n.settings.collapseIcon); h.append(c) } if (g.icon) { var f = t(r).addClass(g.icon); h.append(f) } if (h.append(g.text), g.href && h.attr("href", g.href), g.class && h.addClass(g.class), g.id && h.attr("id", g.id), e.append(h), g.nodes) { var p = t(d).attr("id", n.itemIdPrefix + g.nodeId); e.append(p), n.build(p, g.nodes, s); if (g.expanded) p.addClass(n.settings.expandClass) } }) } }), t.fn.bstreeview = function (e) { return this.each(function () { t.data(this, "plugin_bstreeview") || t.data(this, "plugin_bstreeview", new l(this, e)) }) } }(jQuery, window, document);
|