This commit is contained in:
2026-03-04 15:47:38 -08:00
commit 781b4d19d4
9 changed files with 311 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
localSecrets.json
bin
obj
.vscode
+25
View File
@@ -0,0 +1,25 @@
using System.Numerics;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
public class AllRooms
{
public Dictionary<string, Room> roomList { get; private set; } = new Dictionary<string, Room>();
public async Task PopulateRooms()
{
string result = await Query.Get("_matrix/client/r0/joined_rooms", true);
if (result != null)
{
JObject json = JObject.Parse(result);
JArray rooms = (JArray) json["joined_rooms"];
foreach (string internalID in rooms)
{
//Console.WriteLine("Room: " + roomName);
var room = new Room(internalID);
await room.GetName();
roomList.Add(internalID, room);
}
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using Newtonsoft.Json.Linq;
public class LocalConfig
{
public static string authToken
{
get
{
if (string.IsNullOrEmpty(_authToken))
{
JObject localSecrets = JObject.Parse(File.ReadAllText("localSecrets.json"));
_authToken = (string) ((JObject) localSecrets["matrix"])["auth_token"];
}
return _authToken;
}
}
private static string _authToken;
public static string homeserver
{
get
{
if (string.IsNullOrEmpty(_homeserver))
{
JObject localSecrets = JObject.Parse(File.ReadAllText("localSecrets.json"));
_homeserver = (string) ((JObject) localSecrets["matrix"])["homeserver"];
}
return _homeserver;
}
}
private static string _homeserver;
}
+60
View File
@@ -0,0 +1,60 @@
using System.Threading.Tasks;
public class Menu
{
public async Task ShowMenu(AllRooms allRooms)
{
bool quit = false;
while (!quit)
{
Console.WriteLine("Choose room to kick t2bots");
foreach ((string internalID, Room room) in allRooms.roomList)
{
Console.WriteLine($"{room.name} -- {room.internalID}");
}
Console.WriteLine("Type/paste internal ID:");
string? selectedInternalID = Console.ReadLine();
if (selectedInternalID == null)
continue;
selectedInternalID = selectedInternalID.Trim();
if (selectedInternalID == "")
continue;
if (allRooms.roomList.ContainsKey(selectedInternalID))
{
var room = allRooms.roomList[selectedInternalID];
if (room != null)
{
Console.WriteLine("Enter 'd' for dry run or 'r' for real run");
string key = Console.ReadLine();
if (key == "d")
{
await room.KickT2Bots(true);
Console.WriteLine("Do you want to kick for real? Enter 'r' or 'y'");
string forReal = Console.ReadLine();
if (forReal == "r" || forReal == "y")
{
await room.KickT2Bots(false);
Console.WriteLine("Press enter to continue.");
Console.ReadLine();
}
}
else if (key == "r")
{
await room.KickT2Bots(false);
Console.WriteLine("Press enter to continue.");
Console.ReadLine();
}
else
Console.WriteLine("unknown input");
Console.WriteLine("\n\n");
}
} else {
Console.WriteLine("No such ID\n\n");
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
Console.WriteLine("Loading...");
var allRooms = new AllRooms();
await allRooms.PopulateRooms();
Console.WriteLine("Load done.");
var menu = new Menu();
await menu.ShowMenu(allRooms);
+75
View File
@@ -0,0 +1,75 @@
using System.Threading.Tasks;
public class Query
{
public static async Task<string> Get(string urlPath, bool withAuth, Dictionary<string,string> extraParameters = null)
{
return await Call("GET", urlPath, withAuth, extraParameters);
}
public static async Task<string> Post(string urlPath, bool withAuth, string postData, Dictionary<string,string> extraParameters = null)
{
return await Call("POST", urlPath, withAuth, extraParameters, postData);
}
public static async Task<string> Call(string method, string urlPath, bool withAuth,
Dictionary<string,string> extraParameters = null, string postData = null)
{
string finalPath = LocalConfig.homeserver + urlPath;
for (int i=0; i<10; i++)
{
using (var client = new HttpClient())
{
if (withAuth)
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + LocalConfig.authToken);
if (extraParameters != null)
{
foreach ((string key, string value) in extraParameters)
{
client.DefaultRequestHeaders.Add(key, value);
}
}
HttpResponseMessage response;
if (method == "POST")
{
response = await client.PostAsync(finalPath, new StringContent(postData));
} else {
response = await client.GetAsync(finalPath);
}
if (response.IsSuccessStatusCode)
return await response.Content.ReadAsStringAsync();
// Retry logic
// ex: https://spec.matrix.org/v1.14/client-server-api/#rate-limiting
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
if (i != 9)
await Task.Delay(i*1000 + 100);
else
Console.WriteLine("FATAL - too many requests even though we delayed");
} else
{
Console.WriteLine("Problem with API request:" + response.StatusCode);
if ((int) response.StatusCode >= 400 && (int) response.StatusCode < 500)
{
Console.WriteLine("Giving up on this one due to status code.");
return null;
}
if (i != 9)
Console.WriteLine("Retry...");
else
Console.WriteLine("FATAL");
}
}
}
return null;
}
}
+6
View File
@@ -0,0 +1,6 @@
# Skye's Matrix Tools
Tools for moderating Matrix rooms. Current features:
- Ability to batch kick all t2 bots (for after turning off a discord server)
- End of list.
+86
View File
@@ -0,0 +1,86 @@
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
public class Room
{
public string internalID { get; private set; } = "";
public string name { get; private set; } = "";
public List<string> users { get; private set; } = new List<string>();
public Room(string internalID)
{
this.internalID = internalID;
}
public async Task GetName()
{
// https://rawrly.com/_matrix/client/r0/rooms/!IhFsOrvvOXcSaoMqDB:rawrly.com/state/m.room.name
string result = await Query.Get("_matrix/client/r0/rooms/" + internalID + "/state/m.room.name", true);
if (result != null)
{
JObject json = JObject.Parse(result);
name = (string) json["name"];
}
}
public void GetSpaceName()
{
}
public async Task GetMembers()
{
// https://rawrly.com/_matrix/client/r0/rooms/!IhFsOrvvOXcSaoMqDB:rawrly.com/members
string at = "";
while (true)
{
Dictionary<string, string> extraParameters = new Dictionary<string, string>();
if (at != "")
{
extraParameters.Add("at", at);
}
extraParameters.Add("membership", "join");
string result = await Query.Get($"_matrix/client/r0/rooms/{internalID}/members", true, extraParameters);
JObject json = JObject.Parse(result);
if (json.ContainsKey("chunk"))
{
foreach (JObject member in (JArray) json["chunk"])
{
string userID = (string) member["user_id"];
if (!users.Contains(userID))
users.Add(userID);
}
}
// if (json.ContainsKey(""))
// (Not sure how pagination is handled, seems to just send everything for this one?)
return;
}
}
internal async Task KickT2Bots(bool dryRun)
{
// Clear members list
users.Clear();
await GetMembers();
foreach (var user in users)
{
if (user.EndsWith(":t2bot.io"))
{
if (dryRun)
Console.WriteLine($"Would kick: {user}");
else
{
// KICK code
Console.WriteLine($"Kicking: {user}");
JObject kickData = new JObject();
kickData.Add("user_id", user);
string result = await Query.Post($"_matrix/client/v3/rooms/{internalID}/kick", true, kickData.ToString());
}
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
</Project>