mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-20 21:35:38 +00:00
Compare commits
41
Commits
40p-support
..
2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c83b127d3 | ||
|
|
b9e40852f4 | ||
|
|
f3043cfc5e | ||
|
|
b675d07d2e | ||
|
|
bcd18b7d85 | ||
|
|
2651a93230 | ||
|
|
211c79f9d0 | ||
|
|
dc0f115948 | ||
|
|
2d3a5ca800 | ||
|
|
1ced63af8d | ||
|
|
ca54e6f2ed | ||
|
|
a2daa01993 | ||
|
|
edfff9ae19 | ||
|
|
8767bcbd4d | ||
|
|
1d7252aa36 | ||
|
|
90e995261d | ||
|
|
cc8085f138 | ||
|
|
34e6751d33 | ||
|
|
f72a8aa59f | ||
|
|
a5dd48a5ed | ||
|
|
490a59be62 | ||
|
|
e1285972d7 | ||
|
|
d761f31fa3 | ||
|
|
4ca2efc6cd | ||
|
|
e747f8d71a | ||
|
|
6a21cde5ac | ||
|
|
9fcc718f63 | ||
|
|
f3a7463949 | ||
|
|
5c650e8970 | ||
|
|
43f8281486 | ||
|
|
07f81c5d75 | ||
|
|
f0cb36f118 | ||
|
|
2ba175d16c | ||
|
|
068cbcabc4 | ||
|
|
6498752430 | ||
|
|
031b6c1388 | ||
|
|
3efbb19785 | ||
|
|
989c1b5ff2 | ||
|
|
f8eeeaae8b | ||
|
|
40bbc2027b | ||
|
|
7c8c629ea5 |
No files matched your search
@@ -0,0 +1,70 @@
|
||||
name: nuget
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "src/Empostor.Api/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET 8
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
# Package version = <VersionPrefix>-ci.<CI run_number + 500>, mirroring
|
||||
# the nightly build numbering. The floor (max already-published ci.N + 1)
|
||||
# keeps versions monotonic; push is skipped by --skip-duplicate if the
|
||||
# exact version already exists.
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PROJ=src/Empostor.Api/Empostor.Api.csproj
|
||||
BASE=$(sed -n 's/.*<VersionPrefix>\([^<]*\)<\/VersionPrefix>.*/\1/p' "$PROJ" | head -1)
|
||||
CI_RUN=$(curl -sf -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/actions/workflows/ci.yml/runs?per_page=1" \
|
||||
| jq -r '.workflow_runs[0].run_number')
|
||||
N=$(( CI_RUN + 500 ))
|
||||
MAXN=$(curl -sf "https://api.nuget.org/v3-flatcontainer/empostor.api/index.json" \
|
||||
| jq -r '.versions[]' | grep -oE 'ci\.[0-9]+$' | grep -oE '[0-9]+$' | sort -n | tail -1 || true)
|
||||
MAXN=${MAXN:-0}
|
||||
if [ "$N" -le "$MAXN" ]; then N=$((MAXN + 1)); fi
|
||||
echo "suffix=ci.${N}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${BASE}-ci.${N}" >> "$GITHUB_OUTPUT"
|
||||
echo "Publishing version: ${BASE}-ci.${N}"
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack src/Empostor.Api/Empostor.Api.csproj -c Release -p:VersionSuffix=${{ steps.ver.outputs.suffix }} -p:GeneratePackageOnBuild=false -o artifacts --nologo
|
||||
|
||||
# Trusted publishing: exchanges the GitHub OIDC token for a short-lived
|
||||
# nuget.org API key tied to the policy (no secret needed).
|
||||
- name: NuGet login (trusted publishing)
|
||||
uses: NuGet/login@v1
|
||||
id: login
|
||||
with:
|
||||
user: ELinmei
|
||||
|
||||
- name: Push to nuget.org
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ls -la artifacts
|
||||
dotnet nuget push "artifacts/Empostor.Api.*.nupkg" \
|
||||
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--skip-duplicate
|
||||
dotnet nuget push "artifacts/Empostor.Api.*.snupkg" \
|
||||
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--skip-duplicate || true
|
||||
+6
-4
@@ -25,8 +25,7 @@ WORKDIR /source
|
||||
COPY src/Empostor.Server/Empostor.Server.csproj ./src/Empostor.Server/
|
||||
COPY src/Empostor.Api/Empostor.Api.csproj ./src/Empostor.Api/
|
||||
COPY src/Empostor.Api.Innersloth.Generator/Empostor.Api.Innersloth.Generator.csproj ./src/Empostor.Api.Innersloth.Generator/
|
||||
COPY src/NextFast.Hazel-New/Next.Hazel/Next.Hazel.csproj ./src/NextFast.Hazel-New/Next.Hazel/
|
||||
COPY src/NextFast.Hazel-New/Next.Hazel.Abstractions/Next.Hazel.Abstractions.csproj ./src/NextFast.Hazel-New/Next.Hazel.Abstractions/
|
||||
COPY src/Empostor.Plugins.PlayerLog/Empostor.Plugins.PlayerLog.csproj ./src/Empostor.Plugins.PlayerLog/
|
||||
COPY src/Directory.Build.props ./src/
|
||||
|
||||
RUN case "$TARGETARCH" in \
|
||||
@@ -76,11 +75,14 @@ RUN mkdir -p /app/plugins /app/libraries /app/Languages /app/Log /app/Data /app/
|
||||
ENV ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
ENV EMPOSTOR_Server__PublicIp=0.0.0.0
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV DOTNET_RUNNING_IN_CONTAINER=1
|
||||
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 22023/tcp
|
||||
# Delta UDP port pool (no static UDP port when delta mode is enabled).
|
||||
EXPOSE 22024-22099/udp
|
||||
# Game / matchmaker UDP (Server.ListenPort) — always bound, even in delta mode.
|
||||
EXPOSE 22023/udp
|
||||
# Delta UDP port pool (Server.DeltaPortStart/End). Keep in sync with config.json.
|
||||
EXPOSE 22024-42024/udp
|
||||
|
||||
VOLUME ["/app/config", "/app/plugins", "/app/libraries", "/app/Languages", "/app/Log", "/app/Data", "/app/marketplace"]
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Empostor
|
||||
Empostor is an open-source private server implementation for Among Us.
|
||||
|
||||
[](https://dsc.gg/empostor)
|
||||
[](https://qm.qq.com/q/GeX3Q0Ft0k)
|
||||
[](https://github.com/Empostor/Empostor/blob/main/LICENSE)
|
||||
[](https://github.com/Empostor/Empostor/commit/)
|
||||
[](https://github.com/Empostor/Empostor/releases/)
|
||||
[](https://github.com/Empostor/Empostor/graphs/contributors/)
|
||||
[](https://github.com/Empostor/Empostor/pull/)
|
||||
|
||||
## Features
|
||||
- FriendCode support (authentication)
|
||||
- Dynamic ports
|
||||
- Admin Panel (dashboard)
|
||||
- Plugin support
|
||||
- Server-side anticheat
|
||||
|
||||
## Getting Started
|
||||
|
||||
To run the server, you need:
|
||||
|
||||
- [.NET 8.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
|
||||
Download the build you'd like from the [releases](https://github.com/Empostor/Empostor/releases). You most likely want the x64 build. Use the arm64 build if you are running Empostor on a Raspberry Pi, another ARM-based SBC, or an ARM VPS.
|
||||
|
||||
Unzip the file and go to config.json. To make your server playable for other devices, replace the "PublicIp" field with your actual public IP address.
|
||||
|
||||
It is also recommended to [set up a Reverse Proxy](https://empostor.github.io/get-started/reverse-proxy#use-a-reverse-proxy) (for HTTPS connections).
|
||||
|
||||
## Client Setup
|
||||
|
||||
### Windows
|
||||
1. Go [here](https://empostor.github.io/empostor) and enter your server's IP/domain, port and name. Press "Download server file"
|
||||
2. Press Win + R and enter this (leave the quotation marks):
|
||||
```cmd
|
||||
"%userprofile%\AppData\LocalLow\Innersloth\Among Us"
|
||||
```
|
||||
3. Put the new regionInfo.json file you downloaded in the folder. Overwrite the existing file in the folder.
|
||||
4. Launch Among Us. If everything worked, your server should appear in the regions list!
|
||||
|
||||
### Android/iOS
|
||||
1. Launch Among Us. When you reach the main menu, close the app.
|
||||
2. Go [here](https://empostor.github.io/empostor) and enter your server's IP/domain, port and name.
|
||||
3. Scroll until you see Instructions and press the Android or Apple logo.
|
||||
4. Press "Open in Among Us". Among Us should open.
|
||||
5. Your server should appear in the regions list!
|
||||
|
||||
## Contributing
|
||||
Please read the [contributing guidelines](https://github.com/Empostor/Empostor/blob/main/CONTRIBUTING.md).
|
||||
|
||||
You're welcome to open a pull request/issue!
|
||||
|
||||
## Documentation
|
||||
The documentation is available [here](https://empostor.github.io)!
|
||||
|
||||
## License
|
||||
This project is licensed under the [GPL-v3.0 License](https://github.com/Empostor/Empostor/blob/main/LICENSE).
|
||||
|
||||
## Credits
|
||||
|
||||
- [Impostor](https://github.com/Impostor/Impostor)
|
||||
- [Next-Impostor](https://github.com/BunchHanpiDev/Next-Impostor)
|
||||
- [NextFast.Hazel](https://github.com/Next-Fast/NextFast.Hazel)
|
||||
- [Reactor.Impostor](https://github.com/NuclearPowered/Reactor.Impostor)
|
||||
+16
-6
@@ -7,18 +7,24 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
# HTTP API / admin panel (TCP)
|
||||
# HTTP API / Admin panel (TCP)
|
||||
- "22023:22023/tcp"
|
||||
# Delta UDP port pool. With DeltaPortStart/End enabled, the server does
|
||||
# NOT bind a static UDP port at startup — each player gets a unique port
|
||||
# from this range, and when only one port is left it is shared by all
|
||||
# new players (IP-based matching). Adjust to match config.json.
|
||||
# Game / Matchmaker UDP — REQUIRED. The matchmaker always binds this UDP
|
||||
# port (Server.ListenPort) so players can connect; without it the server
|
||||
# is unreachable even in delta mode.
|
||||
- "22023:22023/udp"
|
||||
# Delta UDP port pool. With DeltaPortStart/End enabled, each player is
|
||||
# assigned a unique UDP port from this range (Server.DeltaPortStart/End).
|
||||
# Must stay in sync with config.json.
|
||||
- "22024-42024:22024-42024/udp"
|
||||
# Uncomment to expose admin HTTP panel:
|
||||
# - "80:80/tcp"
|
||||
|
||||
volumes:
|
||||
# Server config (can be read-only if you only edit via file)
|
||||
# Server config. NOTE: if ./config.json does NOT exist on the host, Docker
|
||||
# creates an EMPTY DIRECTORY at that path and mounts it over the baked-in
|
||||
# /app/config.json, which breaks startup. Create the file first (or remove
|
||||
# this line to use the image's default config) before running.
|
||||
- ./config.json:/app/config.json:ro
|
||||
# Writable runtime data
|
||||
- empostor_plugins:/app/plugins
|
||||
@@ -31,8 +37,12 @@ services:
|
||||
- ./discord_webhook.json:/app/discord_webhook.json
|
||||
|
||||
environment:
|
||||
- DOTNET_RUNNING_IN_CONTAINER=1
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
# PublicIp MUST be your real public IP / domain, or remote players can't
|
||||
# connect. 0.0.0.0 / 127.0.0.1 will NOT work for anyone but localhost.
|
||||
- EMPOSTOR_Server__PublicIp=0.0.0.0
|
||||
- EMPOSTOR_Server__ListenIp=0.0.0.0
|
||||
|
||||
volumes:
|
||||
empostor_plugins:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+78
-20
@@ -9,11 +9,6 @@
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugin.Chat.dll"
|
||||
},
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugin.Chat1.1.0.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -40,16 +35,6 @@
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Welcome.dll"
|
||||
},
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Welcome1.1.0.dll"
|
||||
},
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Welcome1.2.0.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -89,11 +74,6 @@
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Titles.dll"
|
||||
},
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Titles1.1.0.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -174,5 +154,83 @@
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Reactor.Empostor.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cn.hayashiume.chatfilter",
|
||||
"name": "Chat Filter",
|
||||
"description": "Filter the message depend on the banwords",
|
||||
"author": "HayashiUme",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.ChatFilter.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cn.hayashiume.discordwebhook",
|
||||
"name": "Discord Webhook",
|
||||
"description": "Send lobby and admin message to dicord by webhook",
|
||||
"author": "HayashiUme",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.DiscordWebhook.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cn.hayashiume.playerstats",
|
||||
"name": "Player Stats",
|
||||
"description": "Stat player logs with #stats command",
|
||||
"author": "HayashiUme",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.PlayerStats.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.playerlog",
|
||||
"name": "Player Log",
|
||||
"description": "Logs all player activity (chat, kills, meetings, joins, leaves) and provides an admin panel to view, search, and export logs.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.PlayerLog.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.monitor",
|
||||
"name": "Monitor",
|
||||
"description": "Provides /api/monitor/status and /api/monitor/health endpoints for server health checks and uptime monitoring.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.Monitor.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.privacy",
|
||||
"name": "Privacy Policy",
|
||||
"description": "Serves a customizable privacy policy page at /privacy with an admin panel editor.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.Privacy.dll"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -85,6 +85,14 @@ public sealed class SourceGenerator : IIncrementalGenerator
|
||||
enumGenerator.Generate("SystemTypes", underlyingType: CSharpEnumUnderlyingType.Byte);
|
||||
enumGenerator.Generate("Language", sourceName: "SupportedLangs");
|
||||
enumGenerator.Generate("TaskTypes");
|
||||
enumGenerator.Generate("BoolOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("ByteOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("CategorizedOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("FloatArrayOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("FloatOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("Int32ArrayOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("Int32OptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("UInt32OptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
|
||||
enumGenerator.Generate("RpcCalls", "Empostor.Api.Net.Inner", underlyingType: CSharpEnumUnderlyingType.Byte);
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
public delegate ValueTask<AdminActionResult> AdminAction(AdminActionContext context);
|
||||
|
||||
/// <summary>
|
||||
/// The result of an admin action. Tells the renderer what to do next.
|
||||
/// </summary>
|
||||
/// <param name="Success">Whether the action completed successfully.</param>
|
||||
/// <param name="Message">An optional toast message shown to the operator.</param>
|
||||
/// <param name="Refresh">When true, the client re-fetches the panel to reflect new state.</param>
|
||||
public sealed record AdminActionResult(bool Success, string? Message = null, bool Refresh = true)
|
||||
{
|
||||
public static AdminActionResult Ok(string? message = null, bool refresh = true) => new(true, message, refresh);
|
||||
|
||||
public static AdminActionResult Fail(string message) => new(false, message, true);
|
||||
}
|
||||
|
||||
public sealed class AdminActionContext
|
||||
{
|
||||
public AdminActionContext(
|
||||
string extensionId,
|
||||
string actionId,
|
||||
string? value,
|
||||
JsonElement? payload,
|
||||
IServiceProvider services)
|
||||
{
|
||||
ExtensionId = extensionId;
|
||||
ActionId = actionId;
|
||||
Value = value;
|
||||
Payload = payload;
|
||||
Services = services;
|
||||
}
|
||||
|
||||
/// <summary>The extension that owns the invoked action.</summary>
|
||||
public string ExtensionId { get; }
|
||||
|
||||
/// <summary>The action id that was invoked.</summary>
|
||||
public string ActionId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The new value submitted by the widget (textbox text, toggle on/off, select value,
|
||||
/// number value, chip value). <c>null</c> for plain button clicks.
|
||||
/// </summary>
|
||||
public string? Value { get; }
|
||||
|
||||
/// <summary>The full JSON body of the request, if the widget sent structured data.</summary>
|
||||
public JsonElement? Payload { get; }
|
||||
|
||||
/// <summary>The DI container, for resolving services the handler may need.</summary>
|
||||
public IServiceProvider Services { get; }
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// Fluent builder used by an <see cref="IAdminExtension"/> to declare its admin-panel content.
|
||||
/// Register* methods add declarative widgets; widgets that carry a callback get a stable,
|
||||
/// auto-assigned action id that the client sends back on interaction.
|
||||
/// </summary>
|
||||
public sealed class AdminPanelBuilder
|
||||
{
|
||||
private readonly Dictionary<string, AdminAction> _actions = new(StringComparer.Ordinal);
|
||||
private int _actionCounter;
|
||||
|
||||
public AdminPanelBuilder(string extensionId, string title, string icon, string? section)
|
||||
{
|
||||
ExtensionId = extensionId;
|
||||
Title = title;
|
||||
Icon = icon;
|
||||
Section = string.IsNullOrWhiteSpace(section) ? "Plugins" : section;
|
||||
}
|
||||
|
||||
public string ExtensionId { get; }
|
||||
|
||||
public string Title { get; }
|
||||
|
||||
public string Icon { get; }
|
||||
|
||||
public string Section { get; }
|
||||
|
||||
public List<AdminWidget> Widgets { get; } = new();
|
||||
|
||||
public IReadOnlyDictionary<string, AdminAction> Actions => _actions;
|
||||
|
||||
public AdminPanelBuilder RegisterButton(Action<AdminButton> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterText(string content, string tone = "default", bool monospace = false)
|
||||
{
|
||||
Widgets.Add(new AdminText { Content = content, Tone = tone, Monospace = monospace });
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminPanelBuilder RegisterText(Action<AdminText> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterBlock(Action<AdminBlock> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterTextbox(Action<AdminTextbox> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterToggle(Action<AdminToggle> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterSelect(Action<AdminSelect> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterNumber(Action<AdminNumber> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterTable(Action<AdminTable> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterChips(Action<AdminChips> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterDivider()
|
||||
{
|
||||
Widgets.Add(new AdminDivider());
|
||||
return this;
|
||||
}
|
||||
|
||||
private AdminPanelBuilder Register<T>(Action<T> configure)
|
||||
where T : AdminWidget, new()
|
||||
{
|
||||
var widget = new T();
|
||||
configure(widget);
|
||||
Bind(widget);
|
||||
Widgets.Add(widget);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void Bind(AdminWidget widget)
|
||||
{
|
||||
if (widget.Handler != null)
|
||||
{
|
||||
widget.Action = NextAction();
|
||||
_actions[widget.Action] = widget.Handler;
|
||||
}
|
||||
|
||||
if (widget is AdminChips chips)
|
||||
{
|
||||
if (chips.AddHandler != null)
|
||||
{
|
||||
chips.AddAction = NextAction();
|
||||
_actions[chips.AddAction] = chips.AddHandler;
|
||||
}
|
||||
|
||||
if (chips.RemoveHandler != null)
|
||||
{
|
||||
chips.RemoveAction = NextAction();
|
||||
_actions[chips.RemoveAction] = chips.RemoveHandler;
|
||||
}
|
||||
}
|
||||
|
||||
if (widget is AdminBlock block)
|
||||
{
|
||||
foreach (var child in block.Children)
|
||||
{
|
||||
Bind(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string NextAction() => $"{ExtensionId}.a{_actionCounter++}";
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// A theme that reskins the admin panel, in the spirit of VuePress's "theme as plugin".
|
||||
/// A theme can also be shipped as a folder under <c>Pages/themes/{Id}/theme.json</c>.
|
||||
/// </summary>
|
||||
public interface IAdminTheme
|
||||
{
|
||||
/// <summary>Unique id. Also the folder name for disk-based themes.</summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>Human-readable name.</summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>Id of a parent theme whose tokens/css/layout this theme inherits.</summary>
|
||||
string? Extends => null;
|
||||
|
||||
/// <summary>Build the theme definition (tokens, palette, css, layout).</summary>
|
||||
AdminThemeDefinition Define();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declarative description of a theme's styling layers.
|
||||
/// </summary>
|
||||
public sealed class AdminThemeDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Design tokens for the default (light) appearance, keyed by CSS custom-property name
|
||||
/// (e.g. <c>--bg</c>, <c>--a</c>). Use the names in <see cref="AdminThemeTokens"/>.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Tokens { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dark-appearance overrides applied under <c>[data-theme="dark"]</c>. When omitted,
|
||||
/// the framework keeps the light tokens for both modes.
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? DarkTokens { get; set; }
|
||||
|
||||
/// <summary>Raw CSS appended after the tokens (palette/component styles, animations).</summary>
|
||||
public string? CustomCss { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional layout template. Slots are <c>{{slot:header}}</c>, <c>{{slot:nav}}</c>,
|
||||
/// <c>{{slot:content}}</c> and <c>{{slot:footer}}</c>. When omitted the built-in layout is used.
|
||||
/// </summary>
|
||||
public string? Layout { get; set; }
|
||||
|
||||
/// <summary>Named component aliases (component name → HTML fragment) usable in the layout.</summary>
|
||||
public Dictionary<string, string> Components { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The standard set of design-token names the admin panel consumes. Themes should fill
|
||||
/// these keys so their palette applies to every built-in control.
|
||||
/// </summary>
|
||||
public static class AdminThemeTokens
|
||||
{
|
||||
public const string Background = "--bg";
|
||||
public const string Surface = "--s";
|
||||
public const string Border = "--b";
|
||||
public const string Text = "--t";
|
||||
public const string Muted = "--m";
|
||||
public const string Accent = "--a";
|
||||
public const string Success = "--g";
|
||||
public const string Warning = "--y";
|
||||
public const string Danger = "--r";
|
||||
public const string Purple = "--p";
|
||||
public const string Orange = "--o";
|
||||
public const string Field = "--field";
|
||||
public const string Hover = "--hover";
|
||||
public const string OnAccent = "--on-a";
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
|
||||
[JsonDerivedType(typeof(AdminButton), "button")]
|
||||
[JsonDerivedType(typeof(AdminText), "text")]
|
||||
[JsonDerivedType(typeof(AdminBlock), "block")]
|
||||
[JsonDerivedType(typeof(AdminTextbox), "textbox")]
|
||||
[JsonDerivedType(typeof(AdminToggle), "toggle")]
|
||||
[JsonDerivedType(typeof(AdminSelect), "select")]
|
||||
[JsonDerivedType(typeof(AdminNumber), "number")]
|
||||
[JsonDerivedType(typeof(AdminTable), "table")]
|
||||
[JsonDerivedType(typeof(AdminChips), "chips")]
|
||||
[JsonDerivedType(typeof(AdminDivider), "divider")]
|
||||
public abstract class AdminWidget
|
||||
{
|
||||
public string? Action { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? Handler { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AdminButton : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public string Style { get; set; } = "primary";
|
||||
|
||||
public AdminButton OnClick(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminText : AdminWidget
|
||||
{
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
public string Tone { get; set; } = "default";
|
||||
|
||||
public bool Monospace { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AdminBlock : AdminWidget
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public List<AdminWidget> Children { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class AdminTextbox : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public string? Value { get; set; }
|
||||
|
||||
public string? Placeholder { get; set; }
|
||||
|
||||
public bool Secret { get; set; }
|
||||
|
||||
public bool Multiline { get; set; }
|
||||
|
||||
public bool SubmitOnChange { get; set; }
|
||||
|
||||
public AdminTextbox OnSubmit(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminToggle : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public bool Value { get; set; }
|
||||
|
||||
public AdminToggle OnChange(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminSelect : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public List<AdminOption> Options { get; set; } = new();
|
||||
|
||||
public string? Value { get; set; }
|
||||
|
||||
public AdminSelect OnChange(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminNumber : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public int Value { get; set; }
|
||||
|
||||
public int? Min { get; set; }
|
||||
|
||||
public int? Max { get; set; }
|
||||
|
||||
public AdminNumber OnChange(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminTable : AdminWidget
|
||||
{
|
||||
public List<string> Columns { get; set; } = new();
|
||||
|
||||
public List<List<AdminTableCell>> Rows { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class AdminChips : AdminWidget
|
||||
{
|
||||
public List<AdminChip> Items { get; set; } = new();
|
||||
|
||||
public string? Placeholder { get; set; }
|
||||
|
||||
public string? AddLabel { get; set; }
|
||||
|
||||
public string? AddAction { get; set; }
|
||||
|
||||
public string? RemoveAction { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? AddHandler { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? RemoveHandler { get; set; }
|
||||
|
||||
public AdminChips OnAdd(AdminAction handler)
|
||||
{
|
||||
AddHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminChips OnRemove(AdminAction handler)
|
||||
{
|
||||
RemoveHandler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminDivider : AdminWidget
|
||||
{
|
||||
}
|
||||
|
||||
public sealed record AdminOption(string Label, string Value);
|
||||
|
||||
public sealed class AdminTableCell
|
||||
{
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
public string? Tone { get; set; }
|
||||
|
||||
public bool Monospace { get; set; }
|
||||
}
|
||||
|
||||
public sealed record AdminChip(string Value, string Label);
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// Implemented by a plugin to contribute a panel to the admin interface
|
||||
/// </summary>
|
||||
public interface IAdminExtension
|
||||
{
|
||||
/// <summary>Unique, stable id. Also the last path segment of <c>/api/admin/ext/{Id}</c>.</summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>Display title shown in the sidebar and panel header.</summary>
|
||||
string Title { get; }
|
||||
|
||||
/// <summary>Icon name (from the built-in icon set), rendered as an inline glyph.</summary>
|
||||
string Icon => "puzzle";
|
||||
|
||||
/// <summary>Sidebar grouping label. Defaults to "Plugins".</summary>
|
||||
string Section => "Plugins";
|
||||
|
||||
/// <summary>Declare the panel's widgets and callbacks.</summary>
|
||||
void Build(AdminPanelBuilder builder);
|
||||
}
|
||||
@@ -8,5 +8,11 @@ namespace Empostor.Api.Config
|
||||
|
||||
public string MarketplaceUrl { get; set; } =
|
||||
"https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/plugins.json";
|
||||
|
||||
/// <summary>Active admin theme id. Themes live under <c>Pages/themes/{Id}</c> or as plugins.</summary>
|
||||
public string Theme { get; set; } = "default";
|
||||
|
||||
/// <summary>Initial color mode ("light" or "dark"). Operators can still toggle it in the panel.</summary>
|
||||
public string ThemeMode { get; set; } = "dark";
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,7 @@ namespace Empostor.Api.Config;
|
||||
public enum AuthApiMode
|
||||
{
|
||||
Innersloth,
|
||||
Niko,
|
||||
Both,
|
||||
Relay,
|
||||
Ume,
|
||||
}
|
||||
|
||||
@@ -15,14 +13,6 @@ public class AuthApiConfig
|
||||
|
||||
public AuthApiMode Mode { get; set; } = AuthApiMode.Innersloth;
|
||||
|
||||
public string NikoApiKey { get; set; } = string.Empty;
|
||||
|
||||
public string NikoApiBaseUrl { get; set; } = "https://au-verify.niko233.top";
|
||||
|
||||
public string RelayApiBaseUrl { get; set; } = "http://localhost:5100";
|
||||
|
||||
public string RelayApiKey { get; set; } = string.Empty;
|
||||
|
||||
public string UmeApiBaseUrl { get; set; } = "https://auverify.hayashiume.top";
|
||||
|
||||
public string UmeApiKey { get; set; } = "sk-empostor-globalapikey";
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Empostor.Api.Config
|
||||
{
|
||||
public class ChatFilterConfig
|
||||
{
|
||||
public const string Section = "ChatFilter";
|
||||
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
public List<string> BlockedWords { get; set; } = new();
|
||||
|
||||
public bool BlockMessage { get; set; } = true;
|
||||
|
||||
public int SpamThreshold { get; set; } = 5;
|
||||
|
||||
public int SpamWindowSeconds { get; set; } = 10;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace Empostor.Api.Config
|
||||
{
|
||||
public class DiscordWebhookConfig
|
||||
{
|
||||
public const string Section = "DiscordWebhook";
|
||||
|
||||
public string MatchmakerUrl { get; set; } = string.Empty;
|
||||
|
||||
public string AdminUrl { get; set; } = string.Empty;
|
||||
public bool? Enabled { get; set; }
|
||||
public string? WebhookUrl { get; set; }
|
||||
public bool? NotifyOnGameCreated { get; set; }
|
||||
public bool? NotifyOnBan { get; set; }
|
||||
public bool? NotifyOnReport { get; set; }
|
||||
public bool? NotifyOnPlayerJoin { get; set; }
|
||||
public bool? NotifyOnGameEnded { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Empostor.Api.Config
|
||||
{
|
||||
public class PlayerStatsConfig
|
||||
{
|
||||
public const string Section = "PlayerStats";
|
||||
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
public bool PersistToFile { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,20 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<VersionPrefix>2.0.0</VersionPrefix>
|
||||
<AssemblyName>Empostor.Api</AssemblyName>
|
||||
<AssemblyTitle>Empostor.Api</AssemblyTitle>
|
||||
<Authors>AeonLucid</Authors>
|
||||
<Description>An api library for Empostor, an Among Us private server. You need this package to write plugins for Empostor.</Description>
|
||||
<PackageId>Empostor.Api</PackageId>
|
||||
<PackageTags>Among Us;Empostor;Empostor Plugin</PackageTags>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/Empostor/Empostor/dev/docs/images/logo_100.png</PackageIconUrl>
|
||||
<PackageIcon>logo_100.png</PackageIcon>
|
||||
<PackageProjectUrl>https://github.com/Empostor/Empostor</PackageProjectUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/Empostor/Empostor</RepositoryUrl>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -25,16 +28,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Empostor.Hazel" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
|
||||
|
||||
<ProjectReference Include="..\Empostor.Api.Innersloth.Generator\Empostor.Api.Innersloth.Generator.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
|
||||
|
||||
<ProjectReference Include="..\NextFast.Hazel-New\Next.Hazel.Abstractions\Next.Hazel.Abstractions.csproj" />
|
||||
|
||||
<ProjectReference Include="..\NextFast.Hazel-New\Next.Hazel\Next.Hazel.csproj" />
|
||||
<ProjectReference Include="..\Empostor.Api.Innersloth.Generator\Empostor.Api.Innersloth.Generator.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" PrivateAssets="all" />
|
||||
<AdditionalFiles Include="Innersloth/Data/**/*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
|
||||
namespace Empostor.Api.Events.Meeting
|
||||
{
|
||||
@@ -13,5 +13,15 @@ namespace Empostor.Api.Events.Meeting
|
||||
/// Gets a value indicating whether meeting result is a tie.
|
||||
/// </summary>
|
||||
bool IsTie { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a Judge overruled the vote result.
|
||||
/// </summary>
|
||||
bool WasOverruled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nonce of the winning Judge overrule, or 0 if the meeting wasn't overruled.
|
||||
/// </summary>
|
||||
ushort OverrideId { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
@@ -49,6 +50,11 @@ namespace Empostor.Api.Games
|
||||
/// </remarks>
|
||||
bool IsHostAuthoritive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mod GUID that the game was registered with through the AMCI.
|
||||
/// </summary>
|
||||
Guid? ModGuid { get; }
|
||||
|
||||
IClientPlayer? GetClientPlayer(int clientId);
|
||||
|
||||
T? FindObjectByNetId<T>(uint netId)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"VisualTasks": 1,
|
||||
"GhostsDoTasks": 2,
|
||||
"ConfirmImpostor": 3,
|
||||
"AnonymousVotes": 4,
|
||||
"IsDefaults": 5,
|
||||
"UseFlashlight": 6,
|
||||
"SeekerFinalVents": 7,
|
||||
"SeekerFinalMap": 8,
|
||||
"SeekerPings": 9,
|
||||
"ShowCrewmateNames": 10,
|
||||
"Roles": 11,
|
||||
"ShapeshifterLeaveSkin": 1000,
|
||||
"ImpostorsCanSeeProtect": 1100,
|
||||
"NoisemakerImpostorAlert": 1300
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"MapId": 1
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"KillCooldown": 0,
|
||||
"PlayerSpeed": 1,
|
||||
"EmergencyMeetingCount": 2,
|
||||
"EmergencyMeetingCooldown": 3,
|
||||
"DiscussionTime": 4,
|
||||
"VotingTime": 5,
|
||||
"CrewmateVision": 6,
|
||||
"HidingTime": 7,
|
||||
"MaxTimeInVent": 8,
|
||||
"ImposterVision": 9,
|
||||
"FinalHideTime": 10,
|
||||
"FinalHideImposterSpeed": 11
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"KillDistances": 1
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"KillCooldown": 1,
|
||||
"PlayerSpeedMod": 2,
|
||||
"ImpostorLightMod": 3,
|
||||
"CrewLightMod": 4,
|
||||
"CrewmateTimeInVent": 100,
|
||||
"FinalEscapeTime": 101,
|
||||
"EscapeTime": 102,
|
||||
"SeekerFinalSpeed": 103,
|
||||
"MaxPingTime": 104,
|
||||
"CrewmateFlashlightSize": 105,
|
||||
"ImpostorFlashlightSize": 106,
|
||||
"ShapeshifterCooldown": 1000,
|
||||
"ShapeshifterDuration": 1001,
|
||||
"ProtectionDurationSeconds": 1100,
|
||||
"GuardianAngelCooldown": 1101,
|
||||
"ScientistCooldown": 1200,
|
||||
"ScientistBatteryCharge": 1201,
|
||||
"EngineerCooldown": 1300,
|
||||
"EngineerInVentMaxTime": 1301,
|
||||
"PhantomCooldown": 1500,
|
||||
"PhantomDuration": 1501,
|
||||
"TrackerCooldown": 1550,
|
||||
"TrackerDuration": 1551,
|
||||
"TrackerDelay": 1552,
|
||||
"NoisemakerAlertDuration": 1600,
|
||||
"ViperDissolveTime": 1601,
|
||||
"DetectiveSuspectLimit": 1602,
|
||||
"JudgeTaskRequirementPercentage": 1620
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"MaxImpostors": 1,
|
||||
"MinPlayers": 2
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"NumImpostors": 1,
|
||||
"KillDistance": 2,
|
||||
"NumEmergencyMeetings": 3,
|
||||
"EmergencyCooldown": 4,
|
||||
"DiscussionTime": 5,
|
||||
"VotingTime": 6,
|
||||
"MaxImpostors": 7,
|
||||
"MinPlayers": 8,
|
||||
"MaxPlayers": 9,
|
||||
"NumCommonTasks": 10,
|
||||
"NumShortTasks": 11,
|
||||
"NumLongTasks": 12,
|
||||
"TaskBarMode": 13,
|
||||
"Tag": 14,
|
||||
"CrewmatesRemainingForVitals": 100,
|
||||
"CrewmateVentUses": 101,
|
||||
"ImpostorPlayerID": 102,
|
||||
"RulePreset": 200
|
||||
}
|
||||
@@ -11,5 +11,6 @@
|
||||
"Phantom": 9,
|
||||
"Tracker": 10,
|
||||
"Detective": 12,
|
||||
"Viper": 18
|
||||
"Viper": 18,
|
||||
"Judge": 19
|
||||
}
|
||||
@@ -59,5 +59,6 @@
|
||||
"CheckVanish": 62,
|
||||
"StartVanish": 63,
|
||||
"CheckAppear": 64,
|
||||
"StartAppear": 65
|
||||
"StartAppear": 65,
|
||||
"QueueOverruleVotes": 66
|
||||
}
|
||||
@@ -626,6 +626,14 @@
|
||||
"SanctionSelfHarmPromotion": 1720,
|
||||
"SanctionRepeatOffender10": 1721,
|
||||
"SanctionUnknown": 1722,
|
||||
"ReplicatorAbility": 1800,
|
||||
"ReplicatorRole": 1801,
|
||||
"ReplicatorBlurb": 1802,
|
||||
"ReplicatorBlurbMed": 1803,
|
||||
"ReplicatorBlurbLong": 1804,
|
||||
"ReplicatorCooldown": 1805,
|
||||
"ReplicatorDuration": 1806,
|
||||
"ReplicatorQuantity": 1807,
|
||||
"ScreenShakeOption": 1900,
|
||||
"FeaturedItems": 1901,
|
||||
"FeaturedBundles": 1902,
|
||||
@@ -1576,6 +1584,14 @@
|
||||
"XboxCommsCreateGameError": 3702,
|
||||
"XboxCommsSearchError": 3703,
|
||||
"XboxCommsJoinError": 3704,
|
||||
"WatchAdBody": 3800,
|
||||
"WatchAdTitle": 3801,
|
||||
"WatchAd": 3802,
|
||||
"RewardAdTitle": 3803,
|
||||
"RewardAdBody1": 3804,
|
||||
"RewardAdBody2": 3805,
|
||||
"WatchAdErrorBody": 3806,
|
||||
"WatchAdErrorTitle": 3807,
|
||||
"DetectiveRole": 4000,
|
||||
"DetectiveAbility": 4001,
|
||||
"DetectiveAbilityNotes": 4002,
|
||||
@@ -1623,5 +1639,28 @@
|
||||
"DetectiveNotesPrepositionMaybe": 4079,
|
||||
"DetectiveNotesTitlePostIt": 4080,
|
||||
"DetectiveNotesPostIt": 4081,
|
||||
"DetectiveNotesSuspectNumber": 4082
|
||||
"DetectiveNotesSuspectNumber": 4082,
|
||||
"AprilFoolsMode": 4083,
|
||||
"MatchInfoGuideTitle": 4084,
|
||||
"MatchInfoGuidePlayersLabel": 4085,
|
||||
"MatchInfoGuideNoRolesMessage": 4086,
|
||||
"JudgeRole": 4090,
|
||||
"JudgeAbility": 4091,
|
||||
"JudgeBlurb": 4092,
|
||||
"JudgeBlurbMed": 4093,
|
||||
"JudgeBlurbLong": 4094,
|
||||
"JudgeTaskRequirement": 4095,
|
||||
"RolesHelp_Judge_01": 4096,
|
||||
"RolesHelp_Judge_02": 4097,
|
||||
"JudgeAbilityUnlocked": 4098,
|
||||
"JudgeAbilityProgress": 4099,
|
||||
"JudgeCourtAdjourned": 4100,
|
||||
"JudgeHasSpoken": 4101,
|
||||
"StatsJudgeSuccessfulOverrules": 4102,
|
||||
"StatsJudgeFailedOverrules": 4103,
|
||||
"QCJudges": 4104,
|
||||
"QCJudgeOverrule": 4105,
|
||||
"QCJudgePlease": 4106,
|
||||
"QCJudgeWillOverrule": 4107,
|
||||
"JudgeBeatYouToIt": 4108
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"Keywords": 1
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dumpostorVersion": "1.0.1-dev",
|
||||
"gameVersion": "2025.10.14",
|
||||
"gameVersion": "2026.8.18",
|
||||
"platformType": "StandaloneItch"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"hatIds": [
|
||||
"hat_pk04_beanie",
|
||||
"hat_pk05_Cheese",
|
||||
"hat_pk05_Cherry",
|
||||
"hat_Chocolate",
|
||||
"hat_Zipper",
|
||||
"hat_pk02_Crown",
|
||||
"hat_babybean",
|
||||
"hat_Sorry",
|
||||
"hat_mareLwyd",
|
||||
"hat_fishingHat",
|
||||
"hat_schnapp",
|
||||
"hat_pk03_Traffic",
|
||||
"hat_Pot",
|
||||
"hat_AbominalHat",
|
||||
"hat_tombstone",
|
||||
"hat_pk04_MiniCrewmate",
|
||||
"hat_pk05_Wizardhat",
|
||||
"hat_pk05_Ellryhat",
|
||||
"hat_pk05_EllieToppat"
|
||||
],
|
||||
"visorIds": [
|
||||
"visor_pk01_AngeryVisor",
|
||||
"visor_pk01_DumStickerVisor",
|
||||
"visor_vr_Vr-Black",
|
||||
"visor_pk01_PaperMaskVisor",
|
||||
"visor_wash"
|
||||
],
|
||||
"petIds": [
|
||||
"pet_YuleGoatPet",
|
||||
"pet_HamPet",
|
||||
"pet_Lava",
|
||||
"pet_Snow",
|
||||
"pet_Bush"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class BoolGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "bool";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<bool> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required BoolOptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class CategorizedGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "cat";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<int> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required CategorizedOptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class ChatModeGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "chat";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required byte AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFilter
|
||||
{
|
||||
[JsonConstructor]
|
||||
public GameFilter(string optionType, string key, string subFilterString)
|
||||
{
|
||||
this.OptionType = optionType;
|
||||
this.Key = key;
|
||||
this.SubFilterString = subFilterString;
|
||||
this.SubFilter = ResolveSubFilter(OptionType, SubFilterString);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public ISubFilter SubFilter { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionType")]
|
||||
public required string OptionType { get; set; }
|
||||
|
||||
[JsonPropertyName("Key")]
|
||||
public required string Key { get; set; }
|
||||
|
||||
[JsonPropertyName("SubFilterString")]
|
||||
public required string SubFilterString { get; set; }
|
||||
|
||||
public void ModifySubFilter(ISubFilter subFilter)
|
||||
{
|
||||
this.SubFilter = subFilter;
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
internal void OnSerializing(StreamingContext context)
|
||||
{
|
||||
this.SubFilterString = JsonSerializer.Serialize(this.SubFilter);
|
||||
}
|
||||
|
||||
[OnDeserialized]
|
||||
internal void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
this.SubFilter = this.ResolveSubFilter(this.OptionType, this.SubFilterString);
|
||||
}
|
||||
|
||||
private ISubFilter ResolveSubFilter(string type, string filterString)
|
||||
{
|
||||
if (type != null)
|
||||
{
|
||||
switch (type.ToLower())
|
||||
{
|
||||
case "int":
|
||||
return JsonSerializer.Deserialize<IntGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for IntGameFilter");
|
||||
case "platform":
|
||||
return JsonSerializer.Deserialize<PlatformGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for PlatformGameFilter");
|
||||
case "cat":
|
||||
return JsonSerializer.Deserialize<CategorizedGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for CategorizedGameFilter");
|
||||
case "languages":
|
||||
return JsonSerializer.Deserialize<LanguageFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for LanguageFilter");
|
||||
case "chat":
|
||||
return JsonSerializer.Deserialize<ChatModeGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for ChatModeGameFilter");
|
||||
case "map":
|
||||
return JsonSerializer.Deserialize<MapGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for MapGameFilter");
|
||||
case "bool":
|
||||
return JsonSerializer.Deserialize<BoolGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for BoolGameFilter");
|
||||
case "mod":
|
||||
return JsonSerializer.Deserialize<ModFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for ModFilter");
|
||||
default:
|
||||
throw new InvalidOperationException("No type matches subfilter");
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Must provide type for sub filter");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFilterSet
|
||||
{
|
||||
[JsonConstructor]
|
||||
public GameFilterSet(GameModes gameMode, List<GameFilter> filters)
|
||||
{
|
||||
GameMode = gameMode;
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
[JsonPropertyName("GameMode")]
|
||||
public required GameModes GameMode { get; set; }
|
||||
|
||||
[JsonPropertyName("Filters")]
|
||||
public required List<GameFilter> Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFiltersList
|
||||
{
|
||||
[JsonPropertyName("FilterSets")]
|
||||
public required List<GameFilterSet> FilterSets { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
public interface ISubFilter
|
||||
{
|
||||
string FilterType { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class IntGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "int";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<int> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required Int32OptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class LanguageFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "languages";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required uint AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class MapGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "map";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required byte AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
// https://github.com/Innersloth-LLC/AmongUsModdingInformation
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class ModFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "mod";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required Guid AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class PlatformGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "platform";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required uint AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
public class HideNSeekGameOptions : IGameOptions
|
||||
{
|
||||
public const int LatestVersion = 10;
|
||||
public const int LatestVersion = 11;
|
||||
|
||||
public HideNSeekGameOptions(byte version = LatestVersion)
|
||||
{
|
||||
@@ -107,6 +107,11 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
|
||||
public void Deserialize(IMessageReader reader)
|
||||
{
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
|
||||
}
|
||||
|
||||
if (Version >= 8)
|
||||
{
|
||||
SpecialMode = (SpecialGameModes)reader.ReadByte();
|
||||
@@ -142,11 +147,6 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
{
|
||||
Tag = (GameTags)reader.ReadByte();
|
||||
}
|
||||
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
|
||||
}
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
|
||||
@@ -49,6 +49,8 @@ public interface IGameOptions
|
||||
/// </summary>
|
||||
public bool IsDefaults { get; set; }
|
||||
|
||||
public GameTags Tag { get; set; }
|
||||
|
||||
public void Serialize(IMessageWriter writer);
|
||||
|
||||
public static void EnsureVersionIsModular<TCaller>(byte version)
|
||||
|
||||
@@ -145,6 +145,8 @@ public class LegacyGameOptionsData : IGameOptions
|
||||
/// </summary>
|
||||
public bool IsDefaults { get; set; } = true;
|
||||
|
||||
public GameTags Tag { get; set; } = 0;
|
||||
|
||||
public static LegacyGameOptionsData Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new LegacyGameOptionsData(version);
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
public class NormalGameOptions : IGameOptions
|
||||
{
|
||||
public const int LatestVersion = 10;
|
||||
public const int LatestVersion = 11;
|
||||
|
||||
public NormalGameOptions(byte version = LatestVersion)
|
||||
{
|
||||
@@ -144,6 +144,11 @@ public class NormalGameOptions : IGameOptions
|
||||
|
||||
public void Deserialize(IMessageReader reader)
|
||||
{
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
|
||||
}
|
||||
|
||||
if (Version >= 8)
|
||||
{
|
||||
SpecialMode = (SpecialGameModes)reader.ReadByte();
|
||||
@@ -184,11 +189,6 @@ public class NormalGameOptions : IGameOptions
|
||||
}
|
||||
|
||||
RoleOptions.Deserialize(reader);
|
||||
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
|
||||
}
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Empostor.Api.Innersloth.GameOptions.RoleOptions;
|
||||
|
||||
public class JudgeRoleOptions : IRoleOptions
|
||||
{
|
||||
public JudgeRoleOptions(byte version)
|
||||
{
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public byte Version { get; }
|
||||
|
||||
public RoleTypes Type => RoleTypes.Judge;
|
||||
|
||||
public float JudgeTaskRequirementPercentage { get; set; } = 50f;
|
||||
|
||||
public static JudgeRoleOptions Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new JudgeRoleOptions(version);
|
||||
|
||||
options.JudgeTaskRequirementPercentage = (int)reader.ReadByte();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
{
|
||||
writer.Write((byte)JudgeTaskRequirementPercentage);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public class RoleOptionsCollection
|
||||
RoleTypes.Tracker => TrackerRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Detective => DetectiveRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Viper => ViperRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Judge => JudgeRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(roleType), roleType, null),
|
||||
};
|
||||
|
||||
|
||||
@@ -166,7 +166,6 @@ public sealed class LanguageService
|
||||
"command.note.set": "Note set: {0}",
|
||||
"command.color.invalid": "Invalid color ID. Valid range: 0–17.",
|
||||
"command.color.set": "Color changed to {0} ({1}).",
|
||||
|
||||
"command.title.too_long": "Title too long. Max 12 characters.",
|
||||
"command.title.cleared": "Title removed.",
|
||||
"command.title.set": "Title set: {0}",
|
||||
@@ -205,7 +204,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "Set or clear a lobby note (host only).",
|
||||
"command.note.usage": "note <text> | note clear",
|
||||
"command.max.description": "Set max players for the lobby (host only).",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "You are banned from this server.",
|
||||
"ban.notice.reason": "Reason: {0}",
|
||||
"ban.notice.unban": "Unban time: {0}",
|
||||
"ban.notice.unban_permanent": "Unban time: Permanent",
|
||||
"ban.notice.contact": "If you have any questions, please contact the administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -260,7 +264,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "设置或清除房间备注(仅房主)。",
|
||||
"command.note.usage": "note <文字> | note clear",
|
||||
"command.max.description": "设置房间最大人数(仅房主)。",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "您已被服务器封禁",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "解封时间:{0}",
|
||||
"ban.notice.unban_permanent": "解封时间:永久",
|
||||
"ban.notice.contact": "如有问题请联系管理员"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -315,7 +324,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "設置或清除房間備註(僅房主)。",
|
||||
"command.note.usage": "note <文字> | note clear",
|
||||
"command.max.description": "設置房間最大人數(僅房主)。",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "您已被伺服器封禁",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "解封時間:{0}",
|
||||
"ban.notice.unban_permanent": "解封時間:永久",
|
||||
"ban.notice.contact": "如有問題請聯絡管理員"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -356,7 +370,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "이 명령어는 로비에서만 사용할 수 있습니다.",
|
||||
"command.players.header": "=== 플레이어 ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "내 핑: {0}ms"
|
||||
"command.ping.result": "내 핑: {0}ms",
|
||||
"ban.notice.header": "이 서버에서 차단되었습니다.",
|
||||
"ban.notice.reason": "사유: {0}",
|
||||
"ban.notice.unban": "차단 해제 시간: {0}",
|
||||
"ban.notice.unban_permanent": "차단 해제 시간: 영구",
|
||||
"ban.notice.contact": "문의 사항이 있으면 관리자에게 연락해 주세요."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -397,7 +416,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Эта команда может использоваться только в лобби.",
|
||||
"command.players.header": "=== Игроки ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}мс",
|
||||
"command.ping.result": "Ваш пинг: {0}мс"
|
||||
"command.ping.result": "Ваш пинг: {0}мс",
|
||||
"ban.notice.header": "Вы забанены на этом сервере.",
|
||||
"ban.notice.reason": "Причина: {0}",
|
||||
"ban.notice.unban": "Время разбана: {0}",
|
||||
"ban.notice.unban_permanent": "Время разбана: навсегда",
|
||||
"ban.notice.contact": "Если у вас есть вопросы, обратитесь к администратору."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -438,7 +462,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Dieser Befehl kann nur in der Lobby verwendet werden.",
|
||||
"command.players.header": "=== Spieler ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Dein Ping: {0}ms"
|
||||
"command.ping.result": "Dein Ping: {0}ms",
|
||||
"ban.notice.header": "Du wurdest von diesem Server gebannt.",
|
||||
"ban.notice.reason": "Grund: {0}",
|
||||
"ban.notice.unban": "Entbannungszeit: {0}",
|
||||
"ban.notice.unban_permanent": "Entbannungszeit: Dauerhaft",
|
||||
"ban.notice.contact": "Bei Fragen wende dich an den Administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -479,7 +508,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Cette commande ne peut être utilisée que dans le lobby.",
|
||||
"command.players.header": "=== Joueurs ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Votre ping : {0}ms"
|
||||
"command.ping.result": "Votre ping : {0}ms",
|
||||
"ban.notice.header": "Vous êtes banni de ce serveur.",
|
||||
"ban.notice.reason": "Raison : {0}",
|
||||
"ban.notice.unban": "Débannissement : {0}",
|
||||
"ban.notice.unban_permanent": "Débannissement : Permanent",
|
||||
"ban.notice.contact": "En cas de question, contactez un administrateur."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -520,7 +554,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "このコマンドはロビーでのみ使用できます。",
|
||||
"command.players.header": "=== プレイヤー ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "あなたのPing: {0}ms"
|
||||
"command.ping.result": "あなたのPing: {0}ms",
|
||||
"ban.notice.header": "このサーバーからBANされています。",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "BAN解除日時:{0}",
|
||||
"ban.notice.unban_permanent": "BAN解除日時:永久",
|
||||
"ban.notice.contact": "ご不明な点があれば管理者にお問い合わせください。"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -561,7 +600,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando só pode ser usado no lobby.",
|
||||
"command.players.header": "=== Jogadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Seu ping: {0}ms"
|
||||
"command.ping.result": "Seu ping: {0}ms",
|
||||
"ban.notice.header": "Você foi banido deste servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Desbanimento: {0}",
|
||||
"ban.notice.unban_permanent": "Desbanimento: Permanente",
|
||||
"ban.notice.contact": "Em caso de dúvidas, contacte o administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -600,7 +644,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando só pode ser usado no lobby.",
|
||||
"command.players.header": "=== Jogadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Seu ping: {0}ms"
|
||||
"command.ping.result": "Seu ping: {0}ms",
|
||||
"ban.notice.header": "Você foi banido deste servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Desbanimento: {0}",
|
||||
"ban.notice.unban_permanent": "Desbanimento: Permanente",
|
||||
"ban.notice.contact": "Em caso de dúvidas, contate o administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -639,7 +688,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando solo puede usarse en el lobby.",
|
||||
"command.players.header": "=== Jugadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Tu ping: {0}ms"
|
||||
"command.ping.result": "Tu ping: {0}ms",
|
||||
"ban.notice.header": "Has sido baneado de este servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Fin del baneo: {0}",
|
||||
"ban.notice.unban_permanent": "Fin del baneo: Permanente",
|
||||
"ban.notice.contact": "Si tienes dudas, contacta con un administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -678,7 +732,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Questo comando può essere usato solo nella lobby.",
|
||||
"command.players.header": "=== Giocatori ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Il tuo ping: {0}ms"
|
||||
"command.ping.result": "Il tuo ping: {0}ms",
|
||||
"ban.notice.header": "Sei stato bannato da questo server.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Sbanno: {0}",
|
||||
"ban.notice.unban_permanent": "Sbanno: Permanente",
|
||||
"ban.notice.contact": "Per qualsiasi domanda, contatta l'amministratore."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -717,7 +776,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Dit commando kan alleen in de lobby worden gebruikt.",
|
||||
"command.players.header": "=== Spelers ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Jouw ping: {0}ms"
|
||||
"command.ping.result": "Jouw ping: {0}ms",
|
||||
"ban.notice.header": "Je bent geband van deze server.",
|
||||
"ban.notice.reason": "Reden: {0}",
|
||||
"ban.notice.unban": "Ontbantijd: {0}",
|
||||
"ban.notice.unban_permanent": "Ontbantijd: Permanent",
|
||||
"ban.notice.contact": "Neem bij vragen contact op met de beheerder."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -756,7 +820,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Ang command na ito ay maaari lamang gamitin sa lobby.",
|
||||
"command.players.header": "=== Mga Manlalaro ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Ang iyong ping: {0}ms"
|
||||
"command.ping.result": "Ang iyong ping: {0}ms",
|
||||
"ban.notice.header": "Banned ka sa server na ito.",
|
||||
"ban.notice.reason": "Dahilan: {0}",
|
||||
"ban.notice.unban": "Oras ng pag-unban: {0}",
|
||||
"ban.notice.unban_permanent": "Oras ng pag-unban: Permanente",
|
||||
"ban.notice.contact": "Kung may tanong, makipag-ugnayan sa administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -795,7 +864,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Ní féidir an t-ordú seo a úsáid ach sa stocaireacht.",
|
||||
"command.players.header": "=== Imreoirí ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Do phing: {0}ms"
|
||||
"command.ping.result": "Do phing: {0}ms",
|
||||
"ban.notice.header": "Tá cosc ort ar an bhfreastalaí seo.",
|
||||
"ban.notice.reason": "Cúis: {0}",
|
||||
"ban.notice.unban": "Am dí-chosc: {0}",
|
||||
"ban.notice.unban_permanent": "Am dí-chosc: Buan",
|
||||
"ban.notice.contact": "Má tá ceist agat, déan teagmháil le riarthóir."
|
||||
}
|
||||
""";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Empostor.Api.Events.Player;
|
||||
|
||||
@@ -6,6 +6,24 @@ namespace Empostor.Api.Net.Inner.Objects
|
||||
{
|
||||
public interface IInnerMeetingHud : IInnerNetObject
|
||||
{
|
||||
public interface IJudgeOverrule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the player id of the Judge that queued this overrule.
|
||||
/// </summary>
|
||||
byte JudgePlayerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player id of the player the Judge wants to overrule.
|
||||
/// </summary>
|
||||
byte OverruledPlayerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the overrule nonce used to match this overrule to the voting result.
|
||||
/// </summary>
|
||||
ushort OverruleNonce { get; }
|
||||
}
|
||||
|
||||
public interface IPlayerVoteArea
|
||||
{
|
||||
/// <summary>
|
||||
@@ -55,5 +73,10 @@ namespace Empostor.Api.Net.Inner.Objects
|
||||
/// Gets the player that started the meeting.
|
||||
/// </summary>
|
||||
IInnerPlayerInfo? Reporter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Judge overrules queued in this meeting.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<IJudgeOverrule> JudgeOverrules { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Empostor.Api.Innersloth;
|
||||
using Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
namespace Empostor.Api.Net.Messages.C2S
|
||||
{
|
||||
/// <summary>
|
||||
/// Special serialization rules for AU MCI HostGame messages.
|
||||
/// <see href="https://github.com/Innersloth-LLC/AmongUsModdingInformation?tab=readme-ov-file#all-client-mod-client-identification"/>
|
||||
/// for more information.
|
||||
/// </summary>
|
||||
public static class Message25HostModdedGameC2S
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, IGameOptions gameOptions, CrossplayFlags crossplayFlags, GameFilterOptions gameFilterOptions)
|
||||
{
|
||||
Message00HostGameC2S.Serialize(writer, gameOptions, crossplayFlags, gameFilterOptions);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IGameOptions gameOptions, out CrossplayFlags crossplayFlags, out GameFilterOptions gameFilterOptions, out Guid modGuid)
|
||||
{
|
||||
Message00HostGameC2S.Deserialize(reader, out gameOptions, out crossplayFlags, out gameFilterOptions);
|
||||
modGuid = new Guid(reader.ReadBytes(16).Span);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Empostor.Api.Net.Messages
|
||||
public const byte QueryPlatformIds = 22;
|
||||
public const byte QueryLobbyInfo = 23;
|
||||
public const byte EndGameHostMigration = 24;
|
||||
public const byte HostModdedGame = 25;
|
||||
public const byte PackedGameDataTo = 26;
|
||||
|
||||
private static readonly Dictionary<byte, string> FlagCache;
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace Empostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc23VotingComplete
|
||||
{
|
||||
// Pre 18.0 version
|
||||
// TODO clean up when 17.4 and below are unsupported
|
||||
public static void Serialize(IMessageWriter writer, byte[] states, byte playerId, bool tie)
|
||||
{
|
||||
writer.WriteBytesAndSize(states);
|
||||
@@ -9,6 +11,15 @@ namespace Empostor.Api.Net.Messages.Rpcs
|
||||
writer.Write(tie);
|
||||
}
|
||||
|
||||
public static void Serialize(IMessageWriter writer, byte[] states, byte playerId, bool tie, bool wasOverruled, ushort overrideId)
|
||||
{
|
||||
writer.WriteBytesAndSize(states);
|
||||
writer.Write(playerId);
|
||||
writer.Write(tie);
|
||||
writer.Write(wasOverruled);
|
||||
writer.Write(overrideId);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IMessageReader[] states, out byte playerId, out bool tie)
|
||||
{
|
||||
var length = reader.ReadPackedInt32();
|
||||
@@ -21,5 +32,21 @@ namespace Empostor.Api.Net.Messages.Rpcs
|
||||
playerId = reader.ReadByte();
|
||||
tie = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IMessageReader[] states, out byte playerId, out bool tie, out bool wasOverruled, out ushort overrideId)
|
||||
{
|
||||
var length = reader.ReadPackedInt32();
|
||||
states = new IMessageReader[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
states[i] = reader.ReadMessage();
|
||||
}
|
||||
|
||||
playerId = reader.ReadByte();
|
||||
tie = reader.ReadBoolean();
|
||||
|
||||
wasOverruled = reader.ReadBoolean();
|
||||
overrideId = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Empostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc66QueueOverruleVotes
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
|
||||
{
|
||||
writer.Write(judgePlayerId);
|
||||
writer.Write(targetPlayerId);
|
||||
writer.Write(overruleNonce);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out byte judgePlayerId, out byte targetPlayerId, out ushort overruleNonce)
|
||||
{
|
||||
judgePlayerId = reader.ReadByte();
|
||||
targetPlayerId = reader.ReadByte();
|
||||
overruleNonce = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
## Empostor.Api
|
||||
An api library for Empostor, an Among Us private server. You need this package to write plugins for Empostor
|
||||
@@ -2,6 +2,19 @@
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"Empostor.Hazel": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.2, )",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "cblWCgvLcQ2xK6ztV04RVrghVEnJpCWe4N6G3LCNaJhuUq367a1/bbtQxUWAflqvgSvGwY3g3SClhh9eWTCBDA==",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "2.6.1",
|
||||
"Empostor.Hazel.Abstractions": "1.0.2",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
|
||||
"Microsoft.Extensions.ObjectPool": "8.0.1",
|
||||
"Serilog": "4.3.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.2.0, )",
|
||||
@@ -48,6 +61,11 @@
|
||||
"resolved": "2.6.1",
|
||||
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
|
||||
},
|
||||
"Empostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "uJqGMASOGj+ELffmIMqubyvJ/FHjOXA1RtQdS38s1LRhP+Eu4zJMTTndr12+Q3vS0kHggFFyuExFmNxWyzWmig=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Features": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
@@ -108,8 +126,8 @@
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.1-dev-02373",
|
||||
"contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ=="
|
||||
"resolved": "4.3.1",
|
||||
"contentHash": "savYe7h5yRlkqBVOwP8cIRDOdqKiPmYCU4W87JH38sBmcKD5EBoXvQIw6bNEvZ/pTe1gsiye3VFCzBsoppGkXQ=="
|
||||
},
|
||||
"StyleCop.Analyzers.Unstable": {
|
||||
"type": "Transitive",
|
||||
@@ -125,19 +143,6 @@
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
|
||||
},
|
||||
"NextFast.Hazel": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "[2.6.1, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )",
|
||||
"Serilog": "[4.3.1-dev-02373, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,23 @@
|
||||
"resolved": "2.6.1",
|
||||
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
|
||||
},
|
||||
"Empostor.Hazel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "cblWCgvLcQ2xK6ztV04RVrghVEnJpCWe4N6G3LCNaJhuUq367a1/bbtQxUWAflqvgSvGwY3g3SClhh9eWTCBDA==",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "2.6.1",
|
||||
"Empostor.Hazel.Abstractions": "1.0.2",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
|
||||
"Microsoft.Extensions.ObjectPool": "8.0.1",
|
||||
"Serilog": "4.3.1"
|
||||
}
|
||||
},
|
||||
"Empostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "uJqGMASOGj+ELffmIMqubyvJ/FHjOXA1RtQdS38s1LRhP+Eu4zJMTTndr12+Q3vS0kHggFFyuExFmNxWyzWmig=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
@@ -96,8 +113,8 @@
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.1-dev-02373",
|
||||
"contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ=="
|
||||
"resolved": "4.3.1",
|
||||
"contentHash": "savYe7h5yRlkqBVOwP8cIRDOdqKiPmYCU4W87JH38sBmcKD5EBoXvQIw6bNEvZ/pTe1gsiye3VFCzBsoppGkXQ=="
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
@@ -112,25 +129,11 @@
|
||||
"empostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Empostor.Hazel": "[1.0.2, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )",
|
||||
"NextFast.Hazel": "[1.0.2-dev, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )"
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "[2.6.1, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )",
|
||||
"Serilog": "[4.3.1-dev-02373, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Empostor.Api.Admin;
|
||||
|
||||
namespace Empostor.Plugin.Code;
|
||||
|
||||
public sealed class CodeAdminExtension : IAdminExtension
|
||||
{
|
||||
private readonly IGameCodeManager _manager;
|
||||
|
||||
public CodeAdminExtension(IGameCodeManager manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
public string Id => "game-codes";
|
||||
|
||||
public string Title => "Game Codes";
|
||||
|
||||
public string Icon => "database";
|
||||
|
||||
public void Build(AdminPanelBuilder b)
|
||||
{
|
||||
b.RegisterText(
|
||||
$"Loaded {_manager.SixCharCodes} six-character and {_manager.FourCharCodes} four-character code(s).",
|
||||
"default");
|
||||
b.RegisterText($"Source directory: {_manager.Path}", "muted", monospace: true);
|
||||
b.RegisterText(
|
||||
"Codes are read from .txt files at startup. Add or edit files in the directory, then restart the server to reload.",
|
||||
"muted");
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Empostor.Api.Admin;
|
||||
using Empostor.Api.Events;
|
||||
using Empostor.Api.Plugins;
|
||||
using Empostor.Plugin.Code.Handlers;
|
||||
@@ -14,5 +15,6 @@ public sealed class CodeStartup : IPluginStartup
|
||||
{
|
||||
services.AddSingleton<IGameCodeManager, GameCodeManager>();
|
||||
services.AddSingleton<IEventListener, GameEventListener>();
|
||||
services.AddSingleton<IAdminExtension, CodeAdminExtension>();
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Empostor.Api\Empostor.Api.csproj" />
|
||||
</ItemGroup>
|
||||
<Target Name="CopyToMarketplace" AfterTargets="Build">
|
||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="E:\WindowsFile\Empostor\Empostor\marketplace" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -7,6 +7,23 @@
|
||||
"resolved": "2.6.1",
|
||||
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
|
||||
},
|
||||
"Empostor.Hazel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "cblWCgvLcQ2xK6ztV04RVrghVEnJpCWe4N6G3LCNaJhuUq367a1/bbtQxUWAflqvgSvGwY3g3SClhh9eWTCBDA==",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "2.6.1",
|
||||
"Empostor.Hazel.Abstractions": "1.0.2",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
|
||||
"Microsoft.Extensions.ObjectPool": "8.0.1",
|
||||
"Serilog": "4.3.1"
|
||||
}
|
||||
},
|
||||
"Empostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "uJqGMASOGj+ELffmIMqubyvJ/FHjOXA1RtQdS38s1LRhP+Eu4zJMTTndr12+Q3vS0kHggFFyuExFmNxWyzWmig=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
@@ -96,8 +113,8 @@
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.1-dev-02373",
|
||||
"contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ=="
|
||||
"resolved": "4.3.1",
|
||||
"contentHash": "savYe7h5yRlkqBVOwP8cIRDOdqKiPmYCU4W87JH38sBmcKD5EBoXvQIw6bNEvZ/pTe1gsiye3VFCzBsoppGkXQ=="
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
@@ -112,25 +129,11 @@
|
||||
"empostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Empostor.Hazel": "[1.0.2, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )",
|
||||
"NextFast.Hazel": "[1.0.2-dev, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )"
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "[2.6.1, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )",
|
||||
"Serilog": "[4.3.1-dev-02373, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Empostor.Api\Empostor.Api.csproj" />
|
||||
</ItemGroup>
|
||||
<Target Name="CopyToMarketplace" AfterTargets="Build">
|
||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="E:\WindowsFile\Empostor\Empostor\marketplace" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,97 @@
|
||||
using Empostor.Api.Admin;
|
||||
using Empostor.Api.Plugins;
|
||||
|
||||
namespace Empostor.Plugin.Narrator;
|
||||
|
||||
public sealed class NarratorAdminExtension : IAdminExtension
|
||||
{
|
||||
private readonly NarratorConfig _config;
|
||||
private readonly string _configPath;
|
||||
|
||||
public NarratorAdminExtension(NarratorConfig config, string configPath)
|
||||
{
|
||||
_config = config;
|
||||
_configPath = configPath;
|
||||
}
|
||||
|
||||
public string Id => "narrator";
|
||||
|
||||
public string Title => "Narrator";
|
||||
|
||||
public string Icon => "chat";
|
||||
|
||||
public void Build(AdminPanelBuilder b)
|
||||
{
|
||||
b.RegisterTextbox(t =>
|
||||
{
|
||||
t.Label = "DeepSeek API key";
|
||||
t.Value = _config.ApiKey;
|
||||
t.Secret = true;
|
||||
t.Placeholder = "sk-...";
|
||||
t.OnSubmit(async ctx =>
|
||||
{
|
||||
_config.ApiKey = ctx.Value ?? string.Empty;
|
||||
PluginConfigLoader.Save(_configPath, _config);
|
||||
return AdminActionResult.Ok("API key saved.");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterTextbox(t =>
|
||||
{
|
||||
t.Label = "Model";
|
||||
t.Value = _config.Model;
|
||||
t.OnSubmit(async ctx =>
|
||||
{
|
||||
_config.Model = ctx.Value ?? string.Empty;
|
||||
PluginConfigLoader.Save(_configPath, _config);
|
||||
return AdminActionResult.Ok("Model saved.");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterTextbox(t =>
|
||||
{
|
||||
t.Label = "API endpoint";
|
||||
t.Value = _config.ApiEndpoint;
|
||||
t.OnSubmit(async ctx =>
|
||||
{
|
||||
_config.ApiEndpoint = ctx.Value ?? string.Empty;
|
||||
PluginConfigLoader.Save(_configPath, _config);
|
||||
return AdminActionResult.Ok("API endpoint saved.");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterNumber(n =>
|
||||
{
|
||||
n.Label = "Max uses per game";
|
||||
n.Value = _config.MaxUsesPerGame;
|
||||
n.Min = 0;
|
||||
n.OnChange(async ctx =>
|
||||
{
|
||||
if (int.TryParse(ctx.Value, out var v) && v >= 0)
|
||||
{
|
||||
_config.MaxUsesPerGame = v;
|
||||
PluginConfigLoader.Save(_configPath, _config);
|
||||
}
|
||||
|
||||
return AdminActionResult.Ok("Max uses per game saved (applies to new games).");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterNumber(n =>
|
||||
{
|
||||
n.Label = "Max uses per meeting";
|
||||
n.Value = _config.MaxUsesPerMeeting;
|
||||
n.Min = 0;
|
||||
n.OnChange(async ctx =>
|
||||
{
|
||||
if (int.TryParse(ctx.Value, out var v) && v >= 0)
|
||||
{
|
||||
_config.MaxUsesPerMeeting = v;
|
||||
PluginConfigLoader.Save(_configPath, _config);
|
||||
}
|
||||
|
||||
return AdminActionResult.Ok("Max uses per meeting saved (applies to new games).");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Empostor.Api.Admin;
|
||||
using Empostor.Api.Commands;
|
||||
using Empostor.Api.Events;
|
||||
using Empostor.Api.Plugins;
|
||||
@@ -16,7 +17,8 @@ public sealed class NarratorStartup : IPluginStartup
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(_ => PluginConfigLoader.Load<NarratorConfig>(_configPath));
|
||||
var config = PluginConfigLoader.Load<NarratorConfig>(_configPath);
|
||||
services.AddSingleton(config);
|
||||
services.AddSingleton<NarratorService>();
|
||||
services.AddSingleton<NarratorCommand>();
|
||||
services.AddSingleton<ICommand, NarratorCommand>(
|
||||
@@ -24,5 +26,7 @@ public sealed class NarratorStartup : IPluginStartup
|
||||
services.AddSingleton<NarratorEventListener>();
|
||||
services.AddSingleton<IEventListener, NarratorEventListener>(
|
||||
sp => sp.GetRequiredService<NarratorEventListener>());
|
||||
services.AddSingleton<IAdminExtension, NarratorAdminExtension>(
|
||||
_ => new NarratorAdminExtension(config, _configPath));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,23 @@
|
||||
"resolved": "2.6.1",
|
||||
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
|
||||
},
|
||||
"Empostor.Hazel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "cblWCgvLcQ2xK6ztV04RVrghVEnJpCWe4N6G3LCNaJhuUq367a1/bbtQxUWAflqvgSvGwY3g3SClhh9eWTCBDA==",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "2.6.1",
|
||||
"Empostor.Hazel.Abstractions": "1.0.2",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
|
||||
"Microsoft.Extensions.ObjectPool": "8.0.1",
|
||||
"Serilog": "4.3.1"
|
||||
}
|
||||
},
|
||||
"Empostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "uJqGMASOGj+ELffmIMqubyvJ/FHjOXA1RtQdS38s1LRhP+Eu4zJMTTndr12+Q3vS0kHggFFyuExFmNxWyzWmig=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
@@ -96,8 +113,8 @@
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.1-dev-02373",
|
||||
"contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ=="
|
||||
"resolved": "4.3.1",
|
||||
"contentHash": "savYe7h5yRlkqBVOwP8cIRDOdqKiPmYCU4W87JH38sBmcKD5EBoXvQIw6bNEvZ/pTe1gsiye3VFCzBsoppGkXQ=="
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
@@ -112,25 +129,11 @@
|
||||
"empostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Empostor.Hazel": "[1.0.2, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )",
|
||||
"NextFast.Hazel": "[1.0.2-dev, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )"
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "[2.6.1, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.6, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
"NextFast.Hazel.Abstractions": "[1.0.2-dev, )",
|
||||
"Serilog": "[4.3.1-dev-02373, )"
|
||||
}
|
||||
},
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Linq;
|
||||
using Empostor.Api.Admin;
|
||||
|
||||
namespace Empostor.Plugins.ChatFilter;
|
||||
|
||||
public sealed class ChatFilterAdminExtension : IAdminExtension
|
||||
{
|
||||
private readonly ChatFilterStore _store;
|
||||
|
||||
public ChatFilterAdminExtension(ChatFilterStore store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
|
||||
public string Id => "chat-filter";
|
||||
|
||||
public string Title => "Chat Filter";
|
||||
|
||||
public string Icon => "filter";
|
||||
|
||||
public void Build(AdminPanelBuilder b)
|
||||
{
|
||||
b.RegisterToggle(t =>
|
||||
{
|
||||
t.Label = "Enable filtering";
|
||||
t.Value = _store.Enabled;
|
||||
t.OnChange(async ctx =>
|
||||
{
|
||||
_store.Enabled = ctx.Value == "true";
|
||||
await _store.SaveAsync();
|
||||
return AdminActionResult.Ok("Chat filter " + (_store.Enabled ? "enabled." : "disabled."));
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterToggle(t =>
|
||||
{
|
||||
t.Label = "Block messages (off = log only)";
|
||||
t.Value = _store.BlockMessage;
|
||||
t.OnChange(async ctx =>
|
||||
{
|
||||
_store.BlockMessage = ctx.Value == "true";
|
||||
await _store.SaveAsync();
|
||||
return AdminActionResult.Ok("Saved.");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterNumber(n =>
|
||||
{
|
||||
n.Label = "Spam threshold (messages)";
|
||||
n.Value = _store.SpamThreshold;
|
||||
n.Min = 0;
|
||||
n.OnChange(async ctx =>
|
||||
{
|
||||
if (int.TryParse(ctx.Value, out var v))
|
||||
{
|
||||
_store.SpamThreshold = v;
|
||||
await _store.SaveAsync();
|
||||
}
|
||||
|
||||
return AdminActionResult.Ok("Spam threshold saved.");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterNumber(n =>
|
||||
{
|
||||
n.Label = "Spam window (seconds)";
|
||||
n.Value = _store.SpamWindowSeconds;
|
||||
n.Min = 1;
|
||||
n.OnChange(async ctx =>
|
||||
{
|
||||
if (int.TryParse(ctx.Value, out var v) && v > 0)
|
||||
{
|
||||
_store.SpamWindowSeconds = v;
|
||||
await _store.SaveAsync();
|
||||
}
|
||||
|
||||
return AdminActionResult.Ok("Spam window saved.");
|
||||
});
|
||||
});
|
||||
|
||||
b.RegisterChips(c =>
|
||||
{
|
||||
c.Placeholder = "Add a blocked word…";
|
||||
c.AddLabel = "Add";
|
||||
c.Items = _store.BlockedWords.Select(w => new AdminChip(w, w)).ToList();
|
||||
c.OnAdd(async ctx =>
|
||||
{
|
||||
_store.AddWord(ctx.Value ?? string.Empty);
|
||||
return AdminActionResult.Ok("Word added.");
|
||||
});
|
||||
c.OnRemove(async ctx =>
|
||||
{
|
||||
_store.RemoveWord(ctx.Value ?? string.Empty);
|
||||
return AdminActionResult.Ok("Word removed.");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Empostor.Plugins.ChatFilter;
|
||||
|
||||
public sealed class ChatFilterConfig
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public List<string> BlockedWords { get; set; } = new();
|
||||
|
||||
public bool BlockMessage { get; set; } = true;
|
||||
|
||||
public int SpamThreshold { get; set; } = 5;
|
||||
|
||||
public int SpamWindowSeconds { get; set; } = 10;
|
||||
}
|
||||
+2
-4
@@ -5,9 +5,9 @@ using Empostor.Api.Events;
|
||||
using Empostor.Api.Events.Player;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Admin.Chat;
|
||||
namespace Empostor.Plugins.ChatFilter;
|
||||
|
||||
internal sealed class ChatFilterListener : IEventListener
|
||||
public sealed class ChatFilterListener : IEventListener
|
||||
{
|
||||
private readonly ILogger<ChatFilterListener> _logger;
|
||||
private readonly ChatFilterStore _store;
|
||||
@@ -30,7 +30,6 @@ internal sealed class ChatFilterListener : IEventListener
|
||||
var playerKey = e.ClientPlayer.Client.FriendCode ?? e.ClientPlayer.Client.Name;
|
||||
var message = e.Message;
|
||||
|
||||
// Word filter
|
||||
var words = _store.BlockedWords;
|
||||
if (words.Count > 0)
|
||||
{
|
||||
@@ -63,7 +62,6 @@ internal sealed class ChatFilterListener : IEventListener
|
||||
}
|
||||
}
|
||||
|
||||
// Spam rate limit
|
||||
var threshold = _store.SpamThreshold;
|
||||
if (threshold > 0)
|
||||
{
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Plugins.ChatFilter;
|
||||
|
||||
[EmpostorPlugin("cn.hayashiume.chatfilter", "Chat Filter", "HayashiUme", "2.0.0")]
|
||||
public sealed class ChatFilterPlugin : PluginBase
|
||||
{
|
||||
private readonly ILogger<ChatFilterPlugin> _logger;
|
||||
|
||||
public ChatFilterPlugin(ILogger<ChatFilterPlugin> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override ValueTask EnableAsync()
|
||||
{
|
||||
_logger.LogInformation("[ChatFilter] Enabled. Configure rules in the admin panel (Plugins → Chat Filter).");
|
||||
return default;
|
||||
}
|
||||
|
||||
public override ValueTask DisableAsync() => default;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Empostor.Api.Admin;
|
||||
using Empostor.Api.Events;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Empostor.Plugins.ChatFilter;
|
||||
|
||||
public sealed class ChatFilterPluginStartup : IPluginStartup
|
||||
{
|
||||
public void ConfigureHost(IHostBuilder host) { }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<ChatFilterStore>();
|
||||
services.AddSingleton<IEventListener, ChatFilterListener>();
|
||||
services.AddSingleton<IAdminExtension, ChatFilterAdminExtension>();
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -1,28 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Config;
|
||||
using Empostor.Api.Plugins;
|
||||
using Empostor.Api.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Empostor.Server.Service.Admin.Chat;
|
||||
namespace Empostor.Plugins.ChatFilter;
|
||||
|
||||
public sealed class ChatFilterStore : JsonDataStore<ChatFilterConfig>
|
||||
{
|
||||
private readonly object _wordsLock = new();
|
||||
private const string ConfigFile = "[Empostor.Plugins.ChatFilter]Config.json";
|
||||
|
||||
private readonly object _wordsLock = new();
|
||||
private List<string> _blockedWords = new();
|
||||
|
||||
public ChatFilterStore(ILogger<ChatFilterStore> logger, IOptions<ChatFilterConfig> config)
|
||||
public ChatFilterStore(ILogger<ChatFilterStore> logger)
|
||||
: base(logger, legacyPath: null)
|
||||
{
|
||||
Load();
|
||||
|
||||
// If no persisted file was loaded, fall back to config.json defaults
|
||||
if (_blockedWords.Count == 0 && !Enabled)
|
||||
{
|
||||
var cfg = config.Value;
|
||||
var cfg = PluginConfigLoader.Load<ChatFilterConfig>(ConfigPath());
|
||||
Enabled = cfg.Enabled;
|
||||
BlockMessage = cfg.BlockMessage;
|
||||
SpamThreshold = cfg.SpamThreshold;
|
||||
@@ -121,4 +121,6 @@ public sealed class ChatFilterStore : JsonDataStore<ChatFilterConfig>
|
||||
_blockedWords = new List<string>(data.BlockedWords ?? new List<string>());
|
||||
}
|
||||
}
|
||||
|
||||
private static string ConfigPath() => Path.Combine(Directory.GetCurrentDirectory(), ConfigFile);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>Empostor.Plugins.ChatFilter</AssemblyName>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Empostor.Api\Empostor.Api.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"BouncyCastle.Cryptography": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.6.1",
|
||||
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
|
||||
},
|
||||
"Empostor.Hazel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "cblWCgvLcQ2xK6ztV04RVrghVEnJpCWe4N6G3LCNaJhuUq367a1/bbtQxUWAflqvgSvGwY3g3SClhh9eWTCBDA==",
|
||||
"dependencies": {
|
||||
"BouncyCastle.Cryptography": "2.6.1",
|
||||
"Empostor.Hazel.Abstractions": "1.0.2",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
|
||||
"Microsoft.Extensions.ObjectPool": "8.0.1",
|
||||
"Serilog": "4.3.1"
|
||||
}
|
||||
},
|
||||
"Empostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
"contentHash": "uJqGMASOGj+ELffmIMqubyvJ/FHjOXA1RtQdS38s1LRhP+Eu4zJMTTndr12+Q3vS0kHggFFyuExFmNxWyzWmig=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Http.Features": "2.2.0",
|
||||
"System.Text.Encodings.Web": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Features": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.6",
|
||||
"contentHash": "0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Options": "8.0.0",
|
||||
"System.Diagnostics.DiagnosticSource": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Hosting.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.1",
|
||||
"contentHash": "savYe7h5yRlkqBVOwP8cIRDOdqKiPmYCU4W87JH38sBmcKD5EBoXvQIw6bNEvZ/pTe1gsiye3VFCzBsoppGkXQ=="
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ=="
|
||||
},
|
||||
"System.Text.Encodings.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
|
||||
},
|
||||
"empostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Empostor.Hazel": "[1.0.2, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Empostor.Api.Admin;
|
||||
|
||||
namespace Empostor.Plugins.DiscordWebhook;
|
||||
|
||||
public sealed class DiscordWebhookAdminExtension : IAdminExtension
|
||||
{
|
||||
private readonly DiscordWebhookStore _store;
|
||||
|
||||
public DiscordWebhookAdminExtension(DiscordWebhookStore store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
|
||||
public string Id => "discord-webhook";
|
||||
|
||||
public string Title => "Discord Webhook";
|
||||
|
||||
public string Icon => "webhook";
|
||||
|
||||
public void Build(AdminPanelBuilder b)
|
||||
{
|
||||
b.RegisterBlock(block =>
|
||||
{
|
||||
block.Title = "Webhook URLs";
|
||||
block.Description = "Leave a URL empty to disable its notifications.";
|
||||
block.Children.Add(new AdminTextbox
|
||||
{
|
||||
Label = "Matchmaker URL",
|
||||
Value = _store.MatchmakerUrl,
|
||||
Placeholder = "https://discord.com/api/webhooks/...",
|
||||
}.OnSubmit(async ctx =>
|
||||
{
|
||||
_store.MatchmakerUrl = ctx.Value ?? string.Empty;
|
||||
await _store.SaveAsync();
|
||||
return AdminActionResult.Ok("Matchmaker webhook saved.");
|
||||
}));
|
||||
|
||||
block.Children.Add(new AdminTextbox
|
||||
{
|
||||
Label = "Admin URL",
|
||||
Value = _store.AdminUrl,
|
||||
Placeholder = "https://discord.com/api/webhooks/...",
|
||||
}.OnSubmit(async ctx =>
|
||||
{
|
||||
_store.AdminUrl = ctx.Value ?? string.Empty;
|
||||
await _store.SaveAsync();
|
||||
return AdminActionResult.Ok("Admin webhook saved.");
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Empostor.Plugins.DiscordWebhook;
|
||||
|
||||
public sealed class DiscordWebhookConfig
|
||||
{
|
||||
public string MatchmakerUrl { get; set; } = string.Empty;
|
||||
|
||||
public string AdminUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Legacy single-webhook fallback used when both URLs are empty.</summary>
|
||||
public string WebhookUrl { get; set; } = string.Empty;
|
||||
}
|
||||
+3
-6
@@ -7,11 +7,12 @@ using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Events;
|
||||
using Empostor.Api.Events.Player;
|
||||
using Empostor.Api.Innersloth;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Api;
|
||||
namespace Empostor.Plugins.DiscordWebhook;
|
||||
|
||||
internal sealed class DiscordWebhookListener : IEventListener
|
||||
public sealed class DiscordWebhookListener : IEventListener
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = false };
|
||||
|
||||
@@ -29,8 +30,6 @@ internal sealed class DiscordWebhookListener : IEventListener
|
||||
_config = config;
|
||||
}
|
||||
|
||||
// ── Matchmaker events (fire-and-forget, do not block game flow) ──
|
||||
|
||||
[EventListener]
|
||||
public void OnGameCreated(IGameCreatedEvent e)
|
||||
{
|
||||
@@ -90,8 +89,6 @@ internal sealed class DiscordWebhookListener : IEventListener
|
||||
});
|
||||
}
|
||||
|
||||
// ── Admin events (fire-and-forget) ──
|
||||
|
||||
[EventListener]
|
||||
public void OnPlayerLeft(IGamePlayerLeftEvent e)
|
||||
{
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Plugins.DiscordWebhook;
|
||||
|
||||
[EmpostorPlugin("cn.hayashiume.discordwebhook", "Discord Webhook", "HayashiUme", "2.0.0")]
|
||||
public sealed class DiscordWebhookPlugin : PluginBase
|
||||
{
|
||||
private readonly ILogger<DiscordWebhookPlugin> _logger;
|
||||
|
||||
public DiscordWebhookPlugin(ILogger<DiscordWebhookPlugin> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override ValueTask EnableAsync()
|
||||
{
|
||||
_logger.LogInformation("[DiscordWebhook] Enabled. Configure URLs in the admin panel (Plugins → Discord Webhook).");
|
||||
return default;
|
||||
}
|
||||
|
||||
public override ValueTask DisableAsync() => default;
|
||||
}
|
||||
Loaded 100 of 299 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user