Translate Interface

This commit is contained in:
linmeideli committed 2026-05-23 20:15:22 +08:00
1 parent 77e3030dc0
commit f94c0e052c
43 files changed
+1742 -2738

No files matched your search

+38 -8
View File
@@ -1,9 +1,39 @@
.dockerignore
.env
# Build output
**/bin/
**/obj/
**/publish/
# IDE
.idea/
.vs/
.vscode/
*.user
*.suo
# OS files
.DS_Store
Thumbs.db
# Git
.git/
.gitignore
.vs
.vscode
**/.git
**/.idea
**/bin
**/obj
.gitattributes
# Docs
docs/
*.md
# Docker
Dockerfile*
.dockerignore
compose.yml
docker-compose*.yml
# Logs
Log/
*.log
# Temp
tmp/
temp/
*.tmp
+107
View File
@@ -0,0 +1,107 @@
# ============================================
# Stage 1: Build server + plugins from source
# ============================================
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG TARGETARCH
WORKDIR /src
# Copy solution and project files first (layer caching)
COPY src/Empostor.sln ./
COPY src/Directory.Build.props ./ 2>/dev/null || true
COPY src/ProjectRules.ruleset ./ 2>/dev/null || true
# Server + API + Hazel
COPY src/Empostor.Server/Empostor.Server.csproj ./Empostor.Server/
COPY src/Empostor.Api/Empostor.Api.csproj ./Empostor.Api/
COPY src/Empostor.Api.Innersloth.Generator/Empostor.Api.Innersloth.Generator.csproj ./Empostor.Api.Innersloth.Generator/
COPY src/NextFast.Hazel-New/Next.Hazel/Next.Hazel.csproj ./NextFast.Hazel-New/Next.Hazel/
COPY src/NextFast.Hazel-New/Next.Hazel.Abstractions/Next.Hazel.Abstractions.csproj ./NextFast.Hazel-New/Next.Hazel.Abstractions/
# Plugins
COPY src/Empostor.Plugin.Chat/Empostor.Plugin.Chat.csproj ./Empostor.Plugin.Chat/
COPY src/Empostor.Plugin.Code/Empostor.Plugin.Code.csproj ./Empostor.Plugin.Code/
COPY src/Empostor.Plugin.Narrator/Empostor.Plugin.Narrator.csproj ./Empostor.Plugin.Narrator/
COPY src/Empostor.Plugins.Example/Empostor.Plugins.Example.csproj ./Empostor.Plugins.Example/
COPY src/Empostor.Plugins.FixedCode/Empostor.Plugins.FixedCode.csproj ./Empostor.Plugins.FixedCode/
COPY src/Empostor.Plugins.FriendCodeValidator/Empostor.Plugins.FriendCodeValidator.csproj ./Empostor.Plugins.FriendCodeValidator/
COPY src/Empostor.Plugins.PlayerChannel/Empostor.Plugins.PlayerChannel.csproj ./Empostor.Plugins.PlayerChannel/
COPY src/Empostor.Plugins.QqVerify/Empostor.Plugins.QqVerify.csproj ./Empostor.Plugins.QqVerify/
COPY src/Empostor.Plugins.Titles/Empostor.Plugins.Titles.csproj ./Empostor.Plugins.Titles/
COPY src/Empostor.Plugins.Welcome/Empostor.Plugins.Welcome.csproj ./Empostor.Plugins.Welcome/
COPY src/Empostor.Plugins.MapVote/Empostor.Plugins.MapVote.csproj ./Empostor.Plugins.MapVote/
# Restore dependencies
RUN dotnet restore Empostor.Server/Empostor.Server.csproj -a $TARGETARCH
# Copy all source
COPY src/ ./
# Publish server (self-contained single file)
RUN dotnet publish Empostor.Server/Empostor.Server.csproj \
-c Release \
-a $TARGETARCH \
--self-contained false \
-o /publish/server
# Publish plugins
RUN dotnet publish Empostor.Plugin.Chat/Empostor.Plugin.Chat.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugin.Code/Empostor.Plugin.Code.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugin.Narrator/Empostor.Plugin.Narrator.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.FixedCode/Empostor.Plugins.FixedCode.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.FriendCodeValidator/Empostor.Plugins.FriendCodeValidator.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.PlayerChannel/Empostor.Plugins.PlayerChannel.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.QqVerify/Empostor.Plugins.QqVerify.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.Titles/Empostor.Plugins.Titles.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.Welcome/Empostor.Plugins.Welcome.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.MapVote/Empostor.Plugins.MapVote.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore
# ============================================
# Stage 2: Runtime image
# ============================================
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
# Create directory structure
RUN mkdir -p /app/plugins /app/libraries /app/Languages /app/Log /app/config
# Copy server
COPY --from=build /publish/server/ ./
# Copy plugins (only .dll files, skip dependency DLLs already in server)
COPY --from=build /publish/plugins/Empostor.Plugin.Chat.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugin.Code.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugin.Narrator.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.Example.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.FixedCode.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.FriendCodeValidator.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.PlayerChannel.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.QqVerify.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.Titles.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.Welcome.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.MapVote.dll ./plugins/
# Copy marketplace data
COPY marketplace/ ./marketplace/
ENV ASPNETCORE_URLS=http://0.0.0.0:80
ENV EMPOSTOR_Server__PublicIp=0.0.0.0
EXPOSE 80/tcp
EXPOSE 22023/tcp
EXPOSE 22023/udp
VOLUME ["/app/config", "/app/plugins", "/app/Languages", "/app/Log", "/app/marketplace"]
ENTRYPOINT ["./Empostor.Server"]
+23 -5
View File
@@ -1,16 +1,34 @@
# ============================================
# Usage:
# 1. Publish the server:
# dotnet publish src/Empostor.Server -c Release -o publish
# 2. Build and run:
# docker compose -f compose.publish.yml up -d
# ============================================
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# Copy pre-published output from host
# Copy pre-published server output
COPY ./publish/ ./
# Create directories for runtime data
RUN mkdir -p /app/Log /app/plugins /app/libraries
# Create runtime directories
RUN mkdir -p /app/plugins /app/libraries /app/Languages /app/Log
ENV ASPNETCORE_URLS=
# Copy plugins from build output (built separately)
# Place your plugin DLLs in ./plugins/ before building the image
COPY ./plugins/ ./plugins/ 2>/dev/null || true
# Copy marketplace data
COPY ./marketplace/ ./marketplace/ 2>/dev/null || true
ENV ASPNETCORE_URLS=http://0.0.0.0:80
ENV EMPOSTOR_Server__PublicIp=0.0.0.0
EXPOSE 22023/tcp 22023/udp
EXPOSE 80/tcp
EXPOSE 22023/tcp
EXPOSE 22023/udp
VOLUME ["/app/config", "/app/plugins", "/app/Languages", "/app/Log"]
ENTRYPOINT ["./Empostor.Server"]
+11 -2
View File
@@ -2,7 +2,7 @@ services:
empostor:
build:
context: .
dockerfile: Dockerfile.publish
dockerfile: Dockerfile
container_name: empostor-server
restart: unless-stopped
ports:
@@ -12,5 +12,14 @@ services:
- ./config.json:/app/config.json:ro
- ./Log:/app/Log
- ./plugins:/app/plugins
- ./libraries:/app/libraries
- ./Languages:/app/Languages
- ./marketplace:/app/marketplace
environment:
- ASPNETCORE_URLS=http://0.0.0.0:80
- EMPOSTOR_Server__PublicIp=0.0.0.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/api/admin/update/check"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
@@ -1,369 +0,0 @@
{
"runtimeTarget": {
"name": ".NETStandard,Version=v2.0/",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETStandard,Version=v2.0": {},
".NETStandard,Version=v2.0/": {
"Empostor.Api.Innersloth.Generator/1.10.6-dev": {
"dependencies": {
"CSharpPoet": "0.3.0",
"Microsoft.CodeAnalysis.CSharp": "4.7.0",
"System.Text.Encodings.Web": "8.0.0",
"System.Text.Json": "8.0.5"
},
"runtime": {
"Empostor.Api.Innersloth.Generator.dll": {}
}
},
"CSharpPoet/0.3.0": {
"runtime": {
"lib/netstandard2.0/CSharpPoet.dll": {
"assemblyVersion": "0.3.0.0",
"fileVersion": "0.3.0.0"
}
}
},
"Microsoft.Bcl.AsyncInterfaces/8.0.0": {
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.CodeAnalysis.Common/4.7.0": {
"dependencies": {
"System.Collections.Immutable": "7.0.0",
"System.Memory": "4.5.5",
"System.Reflection.Metadata": "7.0.0",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encoding.CodePages": "7.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/Microsoft.CodeAnalysis.dll": {
"assemblyVersion": "4.7.0.0",
"fileVersion": "4.700.23.41609"
}
},
"resources": {
"lib/netstandard2.0/cs/Microsoft.CodeAnalysis.resources.dll": {
"locale": "cs"
},
"lib/netstandard2.0/de/Microsoft.CodeAnalysis.resources.dll": {
"locale": "de"
},
"lib/netstandard2.0/es/Microsoft.CodeAnalysis.resources.dll": {
"locale": "es"
},
"lib/netstandard2.0/fr/Microsoft.CodeAnalysis.resources.dll": {
"locale": "fr"
},
"lib/netstandard2.0/it/Microsoft.CodeAnalysis.resources.dll": {
"locale": "it"
},
"lib/netstandard2.0/ja/Microsoft.CodeAnalysis.resources.dll": {
"locale": "ja"
},
"lib/netstandard2.0/ko/Microsoft.CodeAnalysis.resources.dll": {
"locale": "ko"
},
"lib/netstandard2.0/pl/Microsoft.CodeAnalysis.resources.dll": {
"locale": "pl"
},
"lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.resources.dll": {
"locale": "pt-BR"
},
"lib/netstandard2.0/ru/Microsoft.CodeAnalysis.resources.dll": {
"locale": "ru"
},
"lib/netstandard2.0/tr/Microsoft.CodeAnalysis.resources.dll": {
"locale": "tr"
},
"lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll": {
"locale": "zh-Hans"
},
"lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll": {
"locale": "zh-Hant"
}
}
},
"Microsoft.CodeAnalysis.CSharp/4.7.0": {
"dependencies": {
"Microsoft.CodeAnalysis.Common": "4.7.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.dll": {
"assemblyVersion": "4.7.0.0",
"fileVersion": "4.700.23.41609"
}
},
"resources": {
"lib/netstandard2.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "cs"
},
"lib/netstandard2.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "de"
},
"lib/netstandard2.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "es"
},
"lib/netstandard2.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "fr"
},
"lib/netstandard2.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "it"
},
"lib/netstandard2.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "ja"
},
"lib/netstandard2.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "ko"
},
"lib/netstandard2.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "pl"
},
"lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "pt-BR"
},
"lib/netstandard2.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "ru"
},
"lib/netstandard2.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "tr"
},
"lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "zh-Hans"
},
"lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "zh-Hant"
}
}
},
"System.Buffers/4.5.1": {
"runtime": {
"lib/netstandard2.0/System.Buffers.dll": {
"assemblyVersion": "4.0.3.0",
"fileVersion": "4.6.28619.1"
}
}
},
"System.Collections.Immutable/7.0.0": {
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Collections.Immutable.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"System.Memory/4.5.5": {
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.4.0",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Memory.dll": {
"assemblyVersion": "4.0.1.2",
"fileVersion": "4.6.31308.1"
}
}
},
"System.Numerics.Vectors/4.4.0": {
"runtime": {
"lib/netstandard2.0/System.Numerics.Vectors.dll": {
"assemblyVersion": "4.1.3.0",
"fileVersion": "4.6.25519.3"
}
}
},
"System.Reflection.Metadata/7.0.0": {
"dependencies": {
"System.Collections.Immutable": "7.0.0",
"System.Memory": "4.5.5"
},
"runtime": {
"lib/netstandard2.0/System.Reflection.Metadata.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"System.Runtime.CompilerServices.Unsafe/6.0.0": {
"runtime": {
"lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": {
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.21.52210"
}
}
},
"System.Text.Encoding.CodePages/7.0.0": {
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Text.Encoding.CodePages.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"System.Text.Encodings.Web/8.0.0": {
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Text.Encodings.Web.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"System.Text.Json/8.0.5": {
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
"System.Buffers": "4.5.1",
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "8.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/System.Text.Json.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.1024.46610"
}
}
},
"System.Threading.Tasks.Extensions/4.5.4": {
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Threading.Tasks.Extensions.dll": {
"assemblyVersion": "4.2.0.1",
"fileVersion": "4.6.28619.1"
}
}
}
}
},
"libraries": {
"Empostor.Api.Innersloth.Generator/1.10.6-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"CSharpPoet/0.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3QDh9rMuiNhMtNWOLj0FItiZruON7lj3zD0CXC9IHq4JWpF5OjbaZ2s1X0u6gftE73KCoTezXIgKvxacps78Fw==",
"path": "csharppoet/0.3.0",
"hashPath": "csharppoet.0.3.0.nupkg.sha512"
},
"Microsoft.Bcl.AsyncInterfaces/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
"path": "microsoft.bcl.asyncinterfaces/8.0.0",
"hashPath": "microsoft.bcl.asyncinterfaces.8.0.0.nupkg.sha512"
},
"Microsoft.CodeAnalysis.Common/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pD5S14xMUebSGYe75kt0q/aaS/ftvktSo/pEv7aX7hNPHfdZS+SZeXvkvcffGxWkunYOyRF9m1oN7zzSdYj9dQ==",
"path": "microsoft.codeanalysis.common/4.7.0",
"hashPath": "microsoft.codeanalysis.common.4.7.0.nupkg.sha512"
},
"Microsoft.CodeAnalysis.CSharp/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JHCP2L6lB0oJ3tQoHkC67SFZxW+KbJVOnAo+6L01K5r/NlBlSUhTk5nUAldWhTVwGdzqNeHqGtnEqpsCmGSwQA==",
"path": "microsoft.codeanalysis.csharp/4.7.0",
"hashPath": "microsoft.codeanalysis.csharp.4.7.0.nupkg.sha512"
},
"System.Buffers/4.5.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
"path": "system.buffers/4.5.1",
"hashPath": "system.buffers.4.5.1.nupkg.sha512"
},
"System.Collections.Immutable/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==",
"path": "system.collections.immutable/7.0.0",
"hashPath": "system.collections.immutable.7.0.0.nupkg.sha512"
},
"System.Memory/4.5.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"path": "system.memory/4.5.5",
"hashPath": "system.memory.4.5.5.nupkg.sha512"
},
"System.Numerics.Vectors/4.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==",
"path": "system.numerics.vectors/4.4.0",
"hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512"
},
"System.Reflection.Metadata/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==",
"path": "system.reflection.metadata/7.0.0",
"hashPath": "system.reflection.metadata.7.0.0.nupkg.sha512"
},
"System.Runtime.CompilerServices.Unsafe/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
"path": "system.runtime.compilerservices.unsafe/6.0.0",
"hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
},
"System.Text.Encoding.CodePages/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==",
"path": "system.text.encoding.codepages/7.0.0",
"hashPath": "system.text.encoding.codepages.7.0.0.nupkg.sha512"
},
"System.Text.Encodings.Web/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==",
"path": "system.text.encodings.web/8.0.0",
"hashPath": "system.text.encodings.web.8.0.0.nupkg.sha512"
},
"System.Text.Json/8.0.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==",
"path": "system.text.json/8.0.5",
"hashPath": "system.text.json.8.0.5.nupkg.sha512"
},
"System.Threading.Tasks.Extensions/4.5.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"path": "system.threading.tasks.extensions/4.5.4",
"hashPath": "system.threading.tasks.extensions.4.5.4.nupkg.sha512"
}
}
}
-316
View File
@@ -1,316 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"Empostor.Api/1.10.6-dev": {
"dependencies": {
"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",
"Next.Hazel.Abstractions": "1.0.2.0",
"Next.Hazel": "1.0.2.0"
},
"runtime": {
"Empostor.Api.dll": {}
}
},
"BouncyCastle.Cryptography/2.6.1": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.6.1.59591"
}
}
},
"Microsoft.AspNetCore.Http.Abstractions/2.2.0": {
"dependencies": {
"Microsoft.AspNetCore.Http.Features": "2.2.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": {
"assemblyVersion": "2.2.0.0",
"fileVersion": "2.2.0.18316"
}
}
},
"Microsoft.AspNetCore.Http.Features/2.2.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {
"assemblyVersion": "2.2.0.0",
"fileVersion": "2.2.0.18316"
}
}
},
"Microsoft.Extensions.Configuration.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/9.0.6": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.625.26613"
}
}
},
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
"Microsoft.Extensions.Options": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.FileProviders.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Hosting.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.ObjectPool/8.0.1": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.ObjectPool.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.123.58008"
}
}
},
"Microsoft.Extensions.Options/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Primitives/8.0.0": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Serilog/4.3.1-dev-02373": {
"runtime": {
"lib/net8.0/Serilog.dll": {
"assemblyVersion": "4.3.0.0",
"fileVersion": "4.3.0.0"
}
}
},
"NextFast.Hazel/1.0.2-dev": {
"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"
},
"runtime": {
"Next.Hazel.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
},
"NextFast.Hazel.Abstractions/1.0.2-dev": {
"runtime": {
"Next.Hazel.Abstractions.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
},
"Next.Hazel.Abstractions/1.0.2.0": {
"runtime": {
"Next.Hazel.Abstractions.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
},
"Next.Hazel/1.0.2.0": {
"runtime": {
"Next.Hazel.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
}
}
},
"libraries": {
"Empostor.Api/1.10.6-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"BouncyCastle.Cryptography/2.6.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA==",
"path": "bouncycastle.cryptography/2.6.1",
"hashPath": "bouncycastle.cryptography.2.6.1.nupkg.sha512"
},
"Microsoft.AspNetCore.Http.Abstractions/2.2.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
"path": "microsoft.aspnetcore.http.abstractions/2.2.0",
"hashPath": "microsoft.aspnetcore.http.abstractions.2.2.0.nupkg.sha512"
},
"Microsoft.AspNetCore.Http.Features/2.2.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
"path": "microsoft.aspnetcore.http.features/2.2.0",
"hashPath": "microsoft.aspnetcore.http.features.2.2.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
"path": "microsoft.extensions.configuration.abstractions/8.0.0",
"hashPath": "microsoft.extensions.configuration.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/9.0.6": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA==",
"path": "microsoft.extensions.dependencyinjection.abstractions/9.0.6",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.6.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
"path": "microsoft.extensions.diagnostics.abstractions/8.0.0",
"hashPath": "microsoft.extensions.diagnostics.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.FileProviders.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
"path": "microsoft.extensions.fileproviders.abstractions/8.0.0",
"hashPath": "microsoft.extensions.fileproviders.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Hosting.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==",
"path": "microsoft.extensions.hosting.abstractions/8.0.0",
"hashPath": "microsoft.extensions.hosting.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
"path": "microsoft.extensions.logging.abstractions/8.0.0",
"hashPath": "microsoft.extensions.logging.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.ObjectPool/8.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA==",
"path": "microsoft.extensions.objectpool/8.0.1",
"hashPath": "microsoft.extensions.objectpool.8.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Options/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
"path": "microsoft.extensions.options/8.0.0",
"hashPath": "microsoft.extensions.options.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==",
"path": "microsoft.extensions.primitives/8.0.0",
"hashPath": "microsoft.extensions.primitives.8.0.0.nupkg.sha512"
},
"Serilog/4.3.1-dev-02373": {
"type": "package",
"serviceable": true,
"sha512": "sha512-f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ==",
"path": "serilog/4.3.1-dev-02373",
"hashPath": "serilog.4.3.1-dev-02373.nupkg.sha512"
},
"NextFast.Hazel/1.0.2-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"NextFast.Hazel.Abstractions/1.0.2-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Next.Hazel.Abstractions/1.0.2.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
},
"Next.Hazel/1.0.2.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
}
}
}
Binary file not shown.
File diff suppressed because it is too large. Load diff
@@ -1,332 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"Empostor.Plugin.Narrator/1.10.6-dev": {
"dependencies": {
"Empostor.Api": "1.10.6-dev",
"Next.Hazel.Abstractions": "1.0.2.0",
"Next.Hazel": "1.0.2.0"
},
"runtime": {
"Empostor.Plugin.Narrator.dll": {}
}
},
"BouncyCastle.Cryptography/2.6.1": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.6.1.59591"
}
}
},
"Microsoft.AspNetCore.Http.Abstractions/2.2.0": {
"dependencies": {
"Microsoft.AspNetCore.Http.Features": "2.2.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": {
"assemblyVersion": "2.2.0.0",
"fileVersion": "2.2.0.18316"
}
}
},
"Microsoft.AspNetCore.Http.Features/2.2.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {
"assemblyVersion": "2.2.0.0",
"fileVersion": "2.2.0.18316"
}
}
},
"Microsoft.Extensions.Configuration.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/9.0.6": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.625.26613"
}
}
},
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
"Microsoft.Extensions.Options": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.FileProviders.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Hosting.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.ObjectPool/8.0.1": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.ObjectPool.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.123.58008"
}
}
},
"Microsoft.Extensions.Options/8.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.6",
"Microsoft.Extensions.Primitives": "8.0.0"
},
"runtime": {
"lib/net8.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Microsoft.Extensions.Primitives/8.0.0": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.23.53103"
}
}
},
"Serilog/4.3.1-dev-02373": {
"runtime": {
"lib/net8.0/Serilog.dll": {
"assemblyVersion": "4.3.0.0",
"fileVersion": "4.3.0.0"
}
}
},
"Empostor.Api/1.10.6-dev": {
"dependencies": {
"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"
},
"runtime": {
"Empostor.Api.dll": {
"assemblyVersion": "1.10.6.0",
"fileVersion": "1.10.6.0"
}
}
},
"NextFast.Hazel/1.0.2-dev": {
"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"
},
"runtime": {
"Next.Hazel.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
},
"NextFast.Hazel.Abstractions/1.0.2-dev": {
"runtime": {
"Next.Hazel.Abstractions.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
},
"Next.Hazel.Abstractions/1.0.2.0": {
"runtime": {
"Next.Hazel.Abstractions.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
},
"Next.Hazel/1.0.2.0": {
"runtime": {
"Next.Hazel.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
}
}
},
"libraries": {
"Empostor.Plugin.Narrator/1.10.6-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"BouncyCastle.Cryptography/2.6.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA==",
"path": "bouncycastle.cryptography/2.6.1",
"hashPath": "bouncycastle.cryptography.2.6.1.nupkg.sha512"
},
"Microsoft.AspNetCore.Http.Abstractions/2.2.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
"path": "microsoft.aspnetcore.http.abstractions/2.2.0",
"hashPath": "microsoft.aspnetcore.http.abstractions.2.2.0.nupkg.sha512"
},
"Microsoft.AspNetCore.Http.Features/2.2.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
"path": "microsoft.aspnetcore.http.features/2.2.0",
"hashPath": "microsoft.aspnetcore.http.features.2.2.0.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
"path": "microsoft.extensions.configuration.abstractions/8.0.0",
"hashPath": "microsoft.extensions.configuration.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/9.0.6": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA==",
"path": "microsoft.extensions.dependencyinjection.abstractions/9.0.6",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.6.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
"path": "microsoft.extensions.diagnostics.abstractions/8.0.0",
"hashPath": "microsoft.extensions.diagnostics.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.FileProviders.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
"path": "microsoft.extensions.fileproviders.abstractions/8.0.0",
"hashPath": "microsoft.extensions.fileproviders.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Hosting.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==",
"path": "microsoft.extensions.hosting.abstractions/8.0.0",
"hashPath": "microsoft.extensions.hosting.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
"path": "microsoft.extensions.logging.abstractions/8.0.0",
"hashPath": "microsoft.extensions.logging.abstractions.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.ObjectPool/8.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA==",
"path": "microsoft.extensions.objectpool/8.0.1",
"hashPath": "microsoft.extensions.objectpool.8.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Options/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
"path": "microsoft.extensions.options/8.0.0",
"hashPath": "microsoft.extensions.options.8.0.0.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/8.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==",
"path": "microsoft.extensions.primitives/8.0.0",
"hashPath": "microsoft.extensions.primitives.8.0.0.nupkg.sha512"
},
"Serilog/4.3.1-dev-02373": {
"type": "package",
"serviceable": true,
"sha512": "sha512-f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ==",
"path": "serilog/4.3.1-dev-02373",
"hashPath": "serilog.4.3.1-dev-02373.nupkg.sha512"
},
"Empostor.Api/1.10.6-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"NextFast.Hazel/1.0.2-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"NextFast.Hazel.Abstractions/1.0.2-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Next.Hazel.Abstractions/1.0.2.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
},
"Next.Hazel/1.0.2.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
}
}
}
Binary file not shown.
@@ -1,23 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"Next.Hazel.Abstractions/1.0.2-dev": {
"runtime": {
"Next.Hazel.Abstractions.dll": {}
}
}
}
},
"libraries": {
"Next.Hazel.Abstractions/1.0.2-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}
Binary file not shown.
-117
View File
@@ -1,117 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"Next.Hazel/1.0.2-dev": {
"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",
"Next.Hazel.Abstractions": "1.0.2.0"
},
"runtime": {
"Next.Hazel.dll": {}
}
},
"BouncyCastle.Cryptography/2.6.1": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.6.1.59591"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/9.0.6": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.625.26613"
}
}
},
"Microsoft.Extensions.ObjectPool/8.0.1": {
"runtime": {
"lib/net8.0/Microsoft.Extensions.ObjectPool.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.0.123.58008"
}
}
},
"Serilog/4.3.1-dev-02373": {
"runtime": {
"lib/net8.0/Serilog.dll": {
"assemblyVersion": "4.3.0.0",
"fileVersion": "4.3.0.0"
}
}
},
"NextFast.Hazel.Abstractions/1.0.2-dev": {
"runtime": {
"Next.Hazel.Abstractions.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
},
"Next.Hazel.Abstractions/1.0.2.0": {
"runtime": {
"Next.Hazel.Abstractions.dll": {
"assemblyVersion": "1.0.2.0",
"fileVersion": "1.0.2.0"
}
}
}
}
},
"libraries": {
"Next.Hazel/1.0.2-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"BouncyCastle.Cryptography/2.6.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA==",
"path": "bouncycastle.cryptography/2.6.1",
"hashPath": "bouncycastle.cryptography.2.6.1.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/9.0.6": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0Zn6nR/6g+90MxskZyOOMPQvnPnrrGu6bytPwkV+azDcTtCSuQ1+GJUrg8Klmnrjk1i6zMpw2lXijl+tw7Q3kA==",
"path": "microsoft.extensions.dependencyinjection.abstractions/9.0.6",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.6.nupkg.sha512"
},
"Microsoft.Extensions.ObjectPool/8.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA==",
"path": "microsoft.extensions.objectpool/8.0.1",
"hashPath": "microsoft.extensions.objectpool.8.0.1.nupkg.sha512"
},
"Serilog/4.3.1-dev-02373": {
"type": "package",
"serviceable": true,
"sha512": "sha512-f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ==",
"path": "serilog/4.3.1-dev-02373",
"hashPath": "serilog.4.3.1-dev-02373.nupkg.sha512"
},
"NextFast.Hazel.Abstractions/1.0.2-dev": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Next.Hazel.Abstractions/1.0.2.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
}
}
}
Binary file not shown.
+29 -39
View File
@@ -62,6 +62,32 @@ public sealed class LanguageService
_logger.LogInformation("[Language] Reloaded all language files.");
}
/// <summary>
/// Register translations provided by a plugin.
/// Merges into existing language tables without overwriting existing keys.
/// </summary>
public void AddPluginTranslations(IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> translations)
{
foreach (var (langCode, table) in translations)
{
if (!_tables.TryGetValue(langCode, out var existing))
{
existing = new Dictionary<string, string>();
_tables[langCode] = existing;
}
foreach (var (key, value) in table)
{
if (!existing.ContainsKey(key))
{
existing[key] = value;
}
}
_logger.LogDebug("[Language] Plugin added {Count} keys for {Lang}", table.Count, langCode);
}
}
private string? Lookup(string key, string langCode)
{
return _tables.TryGetValue(langCode, out var table) && table.TryGetValue(key, out var val)
@@ -163,19 +189,7 @@ public sealed class LanguageService
"command.players.not_in_lobby": "This command can only be used in the lobby.",
"command.players.header": "=== Players ({0}) ===",
"command.players.entry": "{0} | {1} | {2}ms",
"command.ping.result": "Your ping: {0}ms",
"narrator.meeting_only": "You can only use #narrator during a meeting.",
"narrator.disabled": "The narrator is currently disabled by the host.",
"narrator.status_enabled": "enabled",
"narrator.status_disabled": "disabled",
"narrator.host_status": "Status: {0} | Max uses per player per game: {1}",
"narrator.host_commands": "Host commands: #narrator enable | disable | limit <number>",
"narrator.host_enabled": "Narrator enabled for this game.",
"narrator.host_disabled": "Narrator disabled for this game.",
"narrator.host_limit_set": "Narrator usage limit set to {0} per player per game.",
"narrator.host_limit_zero": "Narrator usage limit set to 0 (effectively disabled).",
"narrator.host_limit_usage": "Usage: #narrator limit <number>\nExample: #narrator limit 3",
"narrator.usage": "Usage: #narrator <your question or statement>\nExample: #narrator I'm being accused but I have a visual task, how do I prove myself?"
"command.ping.result": "Your ping: {0}ms"
}
""";
@@ -214,19 +228,7 @@ 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",
"narrator.meeting_only": "只能在会议中使用 #narrator。",
"narrator.disabled": "房主已禁用旁白功能。",
"narrator.status_enabled": "已启用",
"narrator.status_disabled": "已禁用",
"narrator.host_status": "状态:{0} | 每局每人最大次数:{1}",
"narrator.host_commands": "房主指令:#narrator enable | disable | limit <次数>",
"narrator.host_enabled": "旁白已在此局中启用。",
"narrator.host_disabled": "旁白已在此局中禁用。",
"narrator.host_limit_set": "旁白使用次数限制已设为每人每局 {0} 次。",
"narrator.host_limit_zero": "旁白使用次数限制已设为 0(等效禁用)。",
"narrator.host_limit_usage": "用法:#narrator limit <次数>\n示例:#narrator limit 3",
"narrator.usage": "用法:#narrator <你要说的话>\n示例:#narrator 我被指控了但我有可视任务,怎么证明自己?"
"command.ping.result": "你的延迟:{0}ms"
}
""";
@@ -265,19 +267,7 @@ 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",
"narrator.meeting_only": "只能在會議中使用 #narrator。",
"narrator.disabled": "房主已停用旁白功能。",
"narrator.status_enabled": "已啟用",
"narrator.status_disabled": "已停用",
"narrator.host_status": "狀態:{0} | 每局每人最大次數:{1}",
"narrator.host_commands": "房主指令:#narrator enable | disable | limit <次數>",
"narrator.host_enabled": "旁白已在此局中啟用。",
"narrator.host_disabled": "旁白已在此局中停用。",
"narrator.host_limit_set": "旁白使用次數限制已設為每人每局 {0} 次。",
"narrator.host_limit_zero": "旁白使用次數限制已設為 0(等效停用)。",
"narrator.host_limit_usage": "用法:#narrator limit <次數>\n範例:#narrator limit 3",
"narrator.usage": "用法:#narrator <你要說的話>\n範例:#narrator 我被指控了但我有可視任務,怎麼證明自己?"
"command.ping.result": "你的延遲:{0}ms"
}
""";
@@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace Empostor.Api.Plugins;
/// <summary>
/// Implement this on your plugin class to provide translations that are
/// automatically registered with the language service on enable.
/// </summary>
public interface IPluginLanguageProvider
{
/// <summary>
/// Returns translations grouped by language code.
/// </summary>
IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetTranslations();
}
+54 -1
View File
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Empostor.Api.Plugins;
using Microsoft.Extensions.Logging;
@@ -5,7 +6,7 @@ using Microsoft.Extensions.Logging;
namespace Empostor.Plugin.Narrator;
[EmpostorPlugin("cn.hayashiume.narrator")]
public sealed class NarratorPlugin : PluginBase
public sealed class NarratorPlugin : PluginBase, IPluginLanguageProvider
{
private readonly ILogger<NarratorPlugin> _logger;
@@ -21,4 +22,56 @@ public sealed class NarratorPlugin : PluginBase
}
public override ValueTask DisableAsync() => default;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetTranslations()
{
return new Dictionary<string, IReadOnlyDictionary<string, string>>
{
["en"] = new Dictionary<string, string>
{
["narrator.meeting_only"] = "You can only use #narrator during a meeting.",
["narrator.disabled"] = "The narrator is currently disabled by the host.",
["narrator.status_enabled"] = "enabled",
["narrator.status_disabled"] = "disabled",
["narrator.host_status"] = "Status: {0} | Max uses per player per game: {1}",
["narrator.host_commands"] = "Host commands: #narrator enable | disable | limit <number>",
["narrator.host_enabled"] = "Narrator enabled for this game.",
["narrator.host_disabled"] = "Narrator disabled for this game.",
["narrator.host_limit_set"] = "Narrator usage limit set to {0} per player per game.",
["narrator.host_limit_zero"] = "Narrator usage limit set to 0 (effectively disabled).",
["narrator.host_limit_usage"] = "Usage: #narrator limit <number>\nExample: #narrator limit 3",
["narrator.usage"] = "Usage: #narrator <your question or statement>\nExample: #narrator I'm being accused but I have a visual task, how do I prove myself?",
},
["zh_CN"] = new Dictionary<string, string>
{
["narrator.meeting_only"] = "只能在会议中使用 #narrator。",
["narrator.disabled"] = "房主已禁用旁白功能。",
["narrator.status_enabled"] = "已启用",
["narrator.status_disabled"] = "已禁用",
["narrator.host_status"] = "状态:{0} | 每局每人最大次数:{1}",
["narrator.host_commands"] = "房主指令:#narrator enable | disable | limit <次数>",
["narrator.host_enabled"] = "旁白已在此局中启用。",
["narrator.host_disabled"] = "旁白已在此局中禁用。",
["narrator.host_limit_set"] = "旁白使用次数限制已设为每人每局 {0} 次。",
["narrator.host_limit_zero"] = "旁白使用次数限制已设为 0(等效禁用)。",
["narrator.host_limit_usage"] = "用法:#narrator limit <次数>\n示例:#narrator limit 3",
["narrator.usage"] = "用法:#narrator <你要说的话>\n示例:#narrator 我被指控了但我有可视任务,怎么证明自己?",
},
["zh_TW"] = new Dictionary<string, string>
{
["narrator.meeting_only"] = "只能在會議中使用 #narrator。",
["narrator.disabled"] = "房主已停用旁白功能。",
["narrator.status_enabled"] = "已啟用",
["narrator.status_disabled"] = "已停用",
["narrator.host_status"] = "狀態:{0} | 每局每人最大次數:{1}",
["narrator.host_commands"] = "房主指令:#narrator enable | disable | limit <次數>",
["narrator.host_enabled"] = "旁白已在此局中啟用。",
["narrator.host_disabled"] = "旁白已在此局中停用。",
["narrator.host_limit_set"] = "旁白使用次數限制已設為每人每局 {0} 次。",
["narrator.host_limit_zero"] = "旁白使用次數限制已設為 0(等效停用)。",
["narrator.host_limit_usage"] = "用法:#narrator limit <次數>\n範例:#narrator limit 3",
["narrator.usage"] = "用法:#narrator <你要說的話>\n範例:#narrator 我被指控了但我有可視任務,怎麼證明自己?",
},
};
}
}
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Empostor.Api.Plugins;
using Microsoft.Extensions.Logging;
@@ -6,7 +7,7 @@ namespace Empostor.Plugins.FriendCodeValidator
{
// MatchDuck offered the Friendcode safenames.txt lol
[EmpostorPlugin("duck.Empostor.friendcodevalidator", "Friend Code Validator", "MatchDuck & Empostor", "1.0.0")]
public sealed class FriendCodeValidatorPlugin : PluginBase
public sealed class FriendCodeValidatorPlugin : PluginBase, IPluginLanguageProvider
{
private readonly ILogger<FriendCodeValidatorPlugin> _logger;
@@ -20,5 +21,24 @@ namespace Empostor.Plugins.FriendCodeValidator
}
public override ValueTask DisableAsync() => default;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetTranslations()
{
return new Dictionary<string, IReadOnlyDictionary<string, string>>
{
["en"] = new Dictionary<string, string>
{
["friendcode.invalid_reason"] = "Your friend code contains inappropriate words and cannot be used on this server.",
},
["zh_CN"] = new Dictionary<string, string>
{
["friendcode.invalid_reason"] = "您的好友代码包含不当词汇,无法在此服务器使用。",
},
["zh_TW"] = new Dictionary<string, string>
{
["friendcode.invalid_reason"] = "您的好友代碼包含不當詞彙,無法在此伺服器使用。",
},
};
}
}
}
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>Empostor.Plugins.MapVote</AssemblyName>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Empostor.Api\Empostor.Api.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,135 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Empostor.Api.Commands;
namespace Empostor.Plugins.MapVote;
public sealed class MapVoteCommand : ICommand
{
private readonly MapVoteService _service;
private readonly MapVoteConfig _config;
public MapVoteCommand(MapVoteService service, MapVoteConfig config)
{
_service = service;
_config = config;
}
public string Name => "votemap";
public string[] Aliases => new[] { "vm" };
public string Description => "Vote for the next map.";
public string Usage => "votemap <map>";
public async ValueTask<bool> ExecuteAsync(CommandContext ctx)
{
var gameCode = ctx.Game.Code.ToString();
// Host control commands
if (ctx.Args.Length > 0)
{
var sub = ctx.Args[0].ToLowerInvariant();
if (sub == "enable" || sub == "on")
{
if (ctx.Sender.IsHost)
{
_service.SetEnabled(gameCode, true);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.host_enabled", "Map voting enabled."), ctx.PlayerControl);
}
return true;
}
if (sub == "disable" || sub == "off")
{
if (ctx.Sender.IsHost)
{
_service.SetEnabled(gameCode, false);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.host_disabled", "Map voting disabled."), ctx.PlayerControl);
}
return true;
}
if (sub == "results" || sub == "result")
{
await ShowResultsAsync(ctx);
return true;
}
}
// Check if voting is enabled
if (!_service.IsEnabled(gameCode) && !ctx.Sender.IsHost)
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.disabled", "Map voting is currently disabled by the host."), ctx.PlayerControl);
return true;
}
// Voting requires a map argument
if (ctx.Args.Length == 0)
{
await ShowUsageAsync(ctx);
return true;
}
if (!_service.TryParseMap(ctx.RawArgs!, out var map))
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.unknown_map", "Unknown map.").Replace("{0}", ctx.RawArgs ?? "?"), ctx.PlayerControl);
return true;
}
var playerName = ctx.Sender.Character?.PlayerInfo?.PlayerName ?? ctx.Sender.Client.Name ?? "Unknown";
_service.CastVote(gameCode, playerName, map);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.voted", "{0} voted for {1}.")
.Replace("{0}", playerName)
.Replace("{1}", MapVoteService.MapDisplayName(map)),
ctx.PlayerControl);
return true;
}
private async ValueTask ShowResultsAsync(CommandContext ctx)
{
var gameCode = ctx.Game.Code.ToString();
var tally = _service.TallyVotes(gameCode);
if (tally.Count == 0)
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.no_votes", "No map votes yet."), ctx.PlayerControl);
return;
}
var sb = new StringBuilder();
sb.AppendLine(T(ctx, "mapvote.results_header", "=== Map Vote Results ==="));
foreach (var (map, count) in tally)
{
sb.AppendLine(T(ctx, "mapvote.results_entry", " {0}: {1} vote(s)")
.Replace("{0}", MapVoteService.MapDisplayName(map))
.Replace("{1}", count.ToString()));
}
await ctx.PlayerControl.SendChatToPlayerAsync(sb.ToString(), ctx.PlayerControl);
}
private async ValueTask ShowUsageAsync(CommandContext ctx)
{
var sb = new StringBuilder();
sb.AppendLine(T(ctx, "mapvote.usage", "Usage: #votemap <map>"));
if (ctx.Sender.IsHost)
sb.AppendLine(T(ctx, "mapvote.host_commands", "Host commands: ..."));
await ctx.PlayerControl.SendChatToPlayerAsync(sb.ToString(), ctx.PlayerControl);
}
private static string T(CommandContext ctx, string key, string defaultText)
{
string result = ctx.Lang.Get(key, ctx.SenderLanguage);
return result == key ? defaultText : result;
}
}
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace Empostor.Plugins.MapVote;
public sealed class MapVoteConfig
{
[JsonPropertyName("enabled")]
public bool Enabled { get; set; } = true;
[JsonPropertyName("require_majority")]
public bool RequireMajority { get; set; } = false;
[JsonPropertyName("allow_host_override")]
public bool AllowHostOverride { get; set; } = true;
}
@@ -0,0 +1,62 @@
using System.Text;
using System.Threading.Tasks;
using Empostor.Api.Events;
using Empostor.Api.Events.Game;
namespace Empostor.Plugins.MapVote;
public sealed class MapVoteEventListener : IEventListener
{
private readonly MapVoteService _service;
private readonly MapVoteConfig _config;
public MapVoteEventListener(MapVoteService service, MapVoteConfig config)
{
_service = service;
_config = config;
}
[EventListener]
public async ValueTask OnGameStarting(IGameStartingEvent e)
{
if (!_config.Enabled) return;
var gameCode = e.Game.Code.ToString();
if (!_service.IsEnabled(gameCode)) return;
var winner = _service.GetWinner(gameCode);
// If host set a specific map and override is allowed, respect it
if (_config.AllowHostOverride && e.Game.Options.Map != winner)
{
var tally = _service.TallyVotes(gameCode);
if (tally.Count == 0)
{
// No votes at all — host's choice stands
_service.ResetVotes(gameCode);
return;
}
}
e.Game.Options.Map = winner;
await e.Game.SyncSettingsAsync();
// Notify players
foreach (var player in e.Game.Players)
{
var ctrl = player.Character;
if (ctrl == null) continue;
await ctrl.SendChatToPlayerAsync(
$"Map set to {MapVoteService.MapDisplayName(winner)} by vote.", ctrl);
}
_service.ResetVotes(gameCode);
}
[EventListener]
public void OnGameDestroyed(IGameDestroyedEvent e)
{
_service.Remove(e.Game.Code.ToString());
}
}
@@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Empostor.Api.Plugins;
using Microsoft.Extensions.Logging;
namespace Empostor.Plugins.MapVote;
[EmpostorPlugin("cn.hayashiume.mapvote")]
public sealed class MapVotePlugin : PluginBase, IPluginLanguageProvider
{
private readonly ILogger<MapVotePlugin> _logger;
public MapVotePlugin(ILogger<MapVotePlugin> logger)
{
_logger = logger;
}
public override ValueTask EnableAsync()
{
_logger.LogInformation("[MapVote] Plugin enabled.");
return default;
}
public override ValueTask DisableAsync() => default;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetTranslations()
{
return new Dictionary<string, IReadOnlyDictionary<string, string>>
{
["en"] = new Dictionary<string, string>
{
["mapvote.disabled"] = "Map voting is currently disabled by the host.",
["mapvote.unknown_map"] = "Unknown map: {0}. Available: Skeld, Mira, Polus, Airship, Fungle",
["mapvote.voted"] = "{0} voted for {1}.",
["mapvote.already_voted"] = "You already voted for {0}. Use #votemap <map> to change your vote.",
["mapvote.no_votes"] = "No map votes yet. Use #votemap <map> to vote!",
["mapvote.results_header"] = "=== Map Vote Results ===",
["mapvote.results_entry"] = " {0}: {1} vote(s)",
["mapvote.results_winner"] = "Winner: {0} ({1} votes)",
["mapvote.results_random"] = "No votes cast, using random map: {0}",
["mapvote.usage"] = "Usage: #votemap <map>\nMaps: Skeld, Mira, Polus, Airship, Fungle\nExample: #votemap polus",
["mapvote.host_commands"] = "Host commands: #votemap enable | disable | results",
["mapvote.host_enabled"] = "Map voting enabled.",
["mapvote.host_disabled"] = "Map voting disabled.",
["mapvote.map_set"] = "Map set to {0} by vote.",
},
["zh_CN"] = new Dictionary<string, string>
{
["mapvote.disabled"] = "投票选图功能已被房主禁用。",
["mapvote.unknown_map"] = "未知地图:{0}。可选:Skeld, Mira, Polus, Airship, Fungle",
["mapvote.voted"] = "{0} 投票选择了 {1}。",
["mapvote.already_voted"] = "你已经投票了 {0}。使用 #votemap <地图> 来修改投票。",
["mapvote.no_votes"] = "还没有人投票。使用 #votemap <地图> 来投票!",
["mapvote.results_header"] = "=== 地图投票结果 ===",
["mapvote.results_entry"] = " {0}:{1} 票",
["mapvote.results_winner"] = "胜出:{0}({1} 票)",
["mapvote.results_random"] = "无人投票,随机选择地图:{0}",
["mapvote.usage"] = "用法:#votemap <地图>\n可选地图:Skeld, Mira, Polus, Airship, Fungle\n示例:#votemap polus",
["mapvote.host_commands"] = "房主指令:#votemap enable | disable | results",
["mapvote.host_enabled"] = "投票选图已启用。",
["mapvote.host_disabled"] = "投票选图已禁用。",
["mapvote.map_set"] = "投票结果为 {0}。",
},
["zh_TW"] = new Dictionary<string, string>
{
["mapvote.disabled"] = "投票選圖功能已被房主停用。",
["mapvote.unknown_map"] = "未知地圖:{0}。可選:Skeld, Mira, Polus, Airship, Fungle",
["mapvote.voted"] = "{0} 投票選擇了 {1}。",
["mapvote.already_voted"] = "你已經投票了 {0}。使用 #votemap <地圖> 來修改投票。",
["mapvote.no_votes"] = "還沒有人投票。使用 #votemap <地圖> 來投票!",
["mapvote.results_header"] = "=== 地圖投票結果 ===",
["mapvote.results_entry"] = " {0}:{1} 票",
["mapvote.results_winner"] = "勝出:{0}({1} 票)",
["mapvote.results_random"] = "無人投票,隨機選擇地圖:{0}",
["mapvote.usage"] = "用法:#votemap <地圖>\n可選地圖:Skeld, Mira, Polus, Airship, Fungle\n範例:#votemap polus",
["mapvote.host_commands"] = "房主指令:#votemap enable | disable | results",
["mapvote.host_enabled"] = "投票選圖已啟用。",
["mapvote.host_disabled"] = "投票選圖已停用。",
["mapvote.map_set"] = "投票結果為 {0}。",
},
};
}
}
@@ -0,0 +1,118 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Empostor.Api.Innersloth;
namespace Empostor.Plugins.MapVote;
public sealed class MapVoteService
{
private static readonly MapTypes[] AllMaps =
{
MapTypes.Skeld, MapTypes.MiraHQ, MapTypes.Polus, MapTypes.Airship, MapTypes.Fungle
};
private static readonly Dictionary<string, MapTypes> MapAliases = new(StringComparer.OrdinalIgnoreCase)
{
["skeld"] = MapTypes.Skeld,
["the skeld"] = MapTypes.Skeld,
["mira"] = MapTypes.MiraHQ,
["mirahq"] = MapTypes.MiraHQ,
["mira hq"] = MapTypes.MiraHQ,
["polus"] = MapTypes.Polus,
["airship"] = MapTypes.Airship,
["fungle"] = MapTypes.Fungle,
// Chinese aliases
["骷髅"] = MapTypes.Skeld,
["米拉"] = MapTypes.MiraHQ,
["波鲁斯"] = MapTypes.Polus,
["飞船"] = MapTypes.Airship,
["真菌"] = MapTypes.Fungle,
};
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MapTypes>> _votes = new();
private readonly ConcurrentDictionary<string, bool> _enabled = new();
public bool IsEnabled(string gameCode) => _enabled.GetValueOrDefault(gameCode, true);
public void SetEnabled(string gameCode, bool enabled) => _enabled[gameCode] = enabled;
public bool TryParseMap(string input, out MapTypes map)
{
if (MapAliases.TryGetValue(input.Trim(), out map))
return true;
if (int.TryParse(input.Trim(), out var id) && Enum.IsDefined(typeof(MapTypes), (byte)id))
{
map = (MapTypes)(byte)id;
if (map != MapTypes.Dleks) // Exclude Dleks (mirrored Skeld)
return true;
}
return false;
}
public static string MapDisplayName(MapTypes map) => map switch
{
MapTypes.Skeld => "The Skeld",
MapTypes.MiraHQ => "Mira HQ",
MapTypes.Polus => "Polus",
MapTypes.Airship => "Airship",
MapTypes.Fungle => "Fungle",
_ => map.ToString(),
};
public void CastVote(string gameCode, string playerName, MapTypes map)
{
var gameVotes = _votes.GetOrAdd(gameCode, _ => new ConcurrentDictionary<string, MapTypes>());
gameVotes[playerName] = map;
}
public MapTypes? GetPlayerVote(string gameCode, string playerName)
{
if (_votes.TryGetValue(gameCode, out var gameVotes) && gameVotes.TryGetValue(playerName, out var map))
return map;
return null;
}
public IReadOnlyDictionary<MapTypes, int> TallyVotes(string gameCode)
{
if (!_votes.TryGetValue(gameCode, out var gameVotes) || gameVotes.IsEmpty)
return new Dictionary<MapTypes, int>();
return gameVotes.Values
.GroupBy(m => m)
.ToDictionary(g => g.Key, g => g.Count());
}
public MapTypes GetWinner(string gameCode)
{
var tally = TallyVotes(gameCode);
if (tally.Count == 0)
{
var rng = new Random();
return AllMaps[rng.Next(AllMaps.Length)];
}
var maxVotes = tally.Values.Max();
var winners = tally.Where(kv => kv.Value == maxVotes).Select(kv => kv.Key).ToList();
if (winners.Count == 1)
return winners[0];
var rng2 = new Random();
return winners[rng2.Next(winners.Count)];
}
public void ResetVotes(string gameCode)
{
_votes.TryRemove(gameCode, out _);
}
internal void Remove(string gameCode)
{
_votes.TryRemove(gameCode, out _);
_enabled.TryRemove(gameCode, out _);
}
}
@@ -0,0 +1,26 @@
using Empostor.Api.Commands;
using Empostor.Api.Events;
using Empostor.Api.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Empostor.Plugins.MapVote;
public sealed class MapVoteStartup : IPluginStartup
{
private string _configPath = "config";
public void SetConfigPath(string configPath) => _configPath = configPath;
public void ConfigureHost(IHostBuilder host) { }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_ => PluginConfigLoader.Load<MapVoteConfig>(_configPath));
services.AddSingleton<MapVoteService>();
services.AddSingleton<MapVoteCommand>();
services.AddSingleton<ICommand, MapVoteCommand>(sp => sp.GetRequiredService<MapVoteCommand>());
services.AddSingleton<MapVoteEventListener>();
services.AddSingleton<IEventListener, MapVoteEventListener>(sp => sp.GetRequiredService<MapVoteEventListener>());
}
}
@@ -0,0 +1,137 @@
{
"version": 1,
"dependencies": {
"net8.0": {
"BouncyCastle.Cryptography": {
"type": "Transitive",
"resolved": "2.6.1",
"contentHash": "vZsG2YILhthgRqO+ZVgRff4ZFKKTl0v7kqaVBLCtRvpREhfBP33pcWrdA3PRYgWuFL1RxiUFvjMUHTdBZlJcoA=="
},
"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-dev-02373",
"contentHash": "f0HNPaleOox5++8zqxAdi9afnUUkoznLOmd0ur/UnLbzl8bIaPyyBGOpbIJmC1kz2vHfkQ0fXh6KXMdlz7bcLQ=="
},
"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": {
"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, )"
}
},
"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,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>Empostor.Plugins.Message</AssemblyName>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Empostor.Api\Empostor.Api.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,109 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Empostor.Api.Commands;
namespace Empostor.Plugins.Message;
public sealed class MessageCommand : ICommand
{
private readonly MessageStore _store;
private readonly MessageConfig _config;
public MessageCommand(MessageStore store, MessageConfig config)
{
_store = store;
_config = config;
}
public string Name => "msg";
public string[] Aliases => new[] { "message", "leave" };
public string Description => "Leave a message for a player by friend code.";
public string Usage => "msg <friendcode> <message>";
public async ValueTask<bool> ExecuteAsync(CommandContext ctx)
{
if (ctx.Args.Length < 2)
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "message.usage", "Usage: #msg <friendcode> <message>"), ctx.PlayerControl);
return true;
}
var targetFc = ctx.Args[0].Trim().ToUpperInvariant();
var content = ctx.RawArgs!;
// Remove the friend code prefix from the raw args to get the message content
var contentStart = ctx.RawArgs!.IndexOf(targetFc, StringComparison.OrdinalIgnoreCase);
if (contentStart >= 0)
content = ctx.RawArgs[(contentStart + ctx.Args[0].Length)..].Trim();
if (string.IsNullOrWhiteSpace(content))
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "message.empty", "Message cannot be empty."), ctx.PlayerControl);
return true;
}
// Validate friend code format
if (targetFc.Length < 4)
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "message.invalid_fc", "Invalid friend code format."), ctx.PlayerControl);
return true;
}
var senderFc = ctx.Sender.Client.FriendCode ?? "";
if (string.Equals(senderFc, targetFc, StringComparison.OrdinalIgnoreCase))
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "message.self", "You cannot send a message to yourself."), ctx.PlayerControl);
return true;
}
if (content.Length > _config.MessageMaxLength)
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "message.too_long", "Message is too long.")
.Replace("{0}", _config.MessageMaxLength.ToString()),
ctx.PlayerControl);
return true;
}
if (_store.Count(targetFc) >= _config.MaxMessagesPerTarget)
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "message.full", "Already has too many pending messages.")
.Replace("{0}", targetFc),
ctx.PlayerControl);
return true;
}
var senderName = ctx.Sender.Character?.PlayerInfo?.PlayerName
?? ctx.Sender.Client.Name
?? "Unknown";
var msg = new PendingMessage
{
SenderName = senderName,
SenderFc = senderFc,
TargetFc = targetFc,
Content = content,
Timestamp = DateTime.UtcNow,
};
_store.Add(msg);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "message.sent", "Message sent to {0}.").Replace("{0}", targetFc),
ctx.PlayerControl);
return true;
}
private static string T(CommandContext ctx, string key, string defaultText)
{
string result = ctx.Lang.Get(key, ctx.SenderLanguage);
return result == key ? defaultText : result;
}
}
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace Empostor.Plugins.Message;
public sealed class MessageConfig
{
[JsonPropertyName("max_messages_per_target")]
public int MaxMessagesPerTarget { get; set; } = 10;
[JsonPropertyName("message_max_length")]
public int MessageMaxLength { get; set; } = 500;
}
@@ -0,0 +1,71 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Empostor.Api.Events;
using Empostor.Api.Events.Client;
using Empostor.Api.Events.Game;
namespace Empostor.Plugins.Message;
public sealed class MessageEventListener : IEventListener
{
private readonly MessageStore _store;
public MessageEventListener(MessageStore store)
{
_store = store;
}
[EventListener]
public async ValueTask OnClientConnected(IClientConnectedEvent e)
{
await DeliverMessages(e.Client.FriendCode, e.Client.Language, null);
}
[EventListener]
public async ValueTask OnPlayerJoined(IGamePlayerJoinedEvent e)
{
var fc = e.Player.Client.FriendCode;
if (string.IsNullOrEmpty(fc)) return;
// Deliver any undelivered messages when player joins a game
var messages = _store.TakeAll(fc);
if (messages.Count == 0) return;
var ctrl = e.Player.Character;
if (ctrl == null) return;
await DeliverToPlayer(messages, ctrl, e.Player.Client.Name ?? "Player");
}
private async ValueTask DeliverMessages(string? friendCode, Api.Languages.Language language, Api.Innersloth.IInnerPlayerControl? ctrl)
{
if (string.IsNullOrEmpty(friendCode)) return;
var messages = _store.TakeAll(friendCode);
if (messages.Count == 0) return;
// Messages were already delivered via OnPlayerJoined with character reference
// OnClientConnected happens before player has a character, so store them back and wait for game join
foreach (var msg in messages)
_store.Add(msg);
}
private async ValueTask DeliverToPlayer(
System.Collections.Generic.IReadOnlyList<PendingMessage> messages,
Empostor.Api.Innersloth.IInnerPlayerControl ctrl,
string playerName)
{
var sb = new StringBuilder();
sb.AppendLine(string.Format("--- You have {0} pending message(s) ---", messages.Count));
foreach (var msg in messages.OrderBy(m => m.Timestamp))
{
var time = msg.Timestamp.ToLocalTime().ToString("MM-dd HH:mm");
sb.AppendLine($"[{time}] <{msg.SenderName}> {msg.Content}");
}
await ctrl.SendChatToPlayerAsync(sb.ToString(), ctrl);
}
}
@@ -0,0 +1,68 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Empostor.Api.Plugins;
using Microsoft.Extensions.Logging;
namespace Empostor.Plugins.Message;
[EmpostorPlugin("cn.hayashiume.message")]
public sealed class MessagePlugin : PluginBase, IPluginLanguageProvider
{
private readonly ILogger<MessagePlugin> _logger;
public MessagePlugin(ILogger<MessagePlugin> logger)
{
_logger = logger;
}
public override ValueTask EnableAsync()
{
_logger.LogInformation("[Message] Plugin enabled.");
return default;
}
public override ValueTask DisableAsync() => default;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetTranslations()
{
return new Dictionary<string, IReadOnlyDictionary<string, string>>
{
["en"] = new Dictionary<string, string>
{
["message.usage"] = "Usage: #msg <friendcode> <message>\nExample: #msg ABCDEFG Hey, let's play again!",
["message.sent"] = "Message sent to {0}.",
["message.too_long"] = "Message is too long. Max {0} characters.",
["message.empty"] = "Message cannot be empty.",
["message.invalid_fc"] = "Invalid friend code format.",
["message.self"] = "You cannot send a message to yourself.",
["message.full"] = "{0} already has too many pending messages. Try again later.",
["message.delivery_header"] = "--- You have {0} pending message(s) ---",
["message.delivery_entry"] = "[{0}] <{1}> {2}: {3}",
},
["zh_CN"] = new Dictionary<string, string>
{
["message.usage"] = "用法:#msg <好友代码> <留言内容>\n示例:#msg ABCDEFG 下次一起玩!",
["message.sent"] = "留言已发送给 {0}。",
["message.too_long"] = "留言内容过长,最多 {0} 个字符。",
["message.empty"] = "留言内容不能为空。",
["message.invalid_fc"] = "好友代码格式无效。",
["message.self"] = "不能给自己留言。",
["message.full"] = "{0} 的待收留言已满,请稍后再试。",
["message.delivery_header"] = "--- 你有 {0} 条待收留言 ---",
["message.delivery_entry"] = "[{0}] <{1}> {2}: {3}",
},
["zh_TW"] = new Dictionary<string, string>
{
["message.usage"] = "用法:#msg <好友代碼> <留言內容>\n範例:#msg ABCDEFG 下次一起玩!",
["message.sent"] = "留言已發送給 {0}。",
["message.too_long"] = "留言內容過長,最多 {0} 個字元。",
["message.empty"] = "留言內容不能為空。",
["message.invalid_fc"] = "好友代碼格式無效。",
["message.self"] = "不能給自己留言。",
["message.full"] = "{0} 的待收留言已滿,請稍後再試。",
["message.delivery_header"] = "--- 你有 {0} 條待收留言 ---",
["message.delivery_entry"] = "[{0}] <{1}> {2}: {3}",
},
};
}
}
@@ -0,0 +1,26 @@
using Empostor.Api.Commands;
using Empostor.Api.Events;
using Empostor.Api.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Empostor.Plugins.Message;
public sealed class MessageStartup : IPluginStartup
{
private string _configPath = "config";
public void SetConfigPath(string configPath) => _configPath = configPath;
public void ConfigureHost(IHostBuilder host) { }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_ => PluginConfigLoader.Load<MessageConfig>(_configPath));
services.AddSingleton<MessageStore>();
services.AddSingleton<MessageCommand>();
services.AddSingleton<ICommand, MessageCommand>(sp => sp.GetRequiredService<MessageCommand>());
services.AddSingleton<MessageEventListener>();
services.AddSingleton<IEventListener, MessageEventListener>(sp => sp.GetRequiredService<MessageEventListener>());
}
}
@@ -0,0 +1,121 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.Logging;
namespace Empostor.Plugins.Message;
public sealed class MessageStore
{
private readonly ConcurrentDictionary<string, List<PendingMessage>> _messages = new(StringComparer.OrdinalIgnoreCase);
private readonly ILogger<MessageStore> _logger;
private readonly string _filePath;
private readonly object _saveLock = new();
public MessageStore(ILogger<MessageStore> logger, MessageConfig config)
{
_logger = logger;
_filePath = Path.Combine(Directory.GetCurrentDirectory(), "config", "messages.json");
Load();
}
public int Count(string targetFc)
{
if (_messages.TryGetValue(targetFc, out var list))
return list.Count;
return 0;
}
public void Add(PendingMessage msg)
{
var list = _messages.GetOrAdd(msg.TargetFc, _ => new List<PendingMessage>());
lock (list)
{
list.Add(msg);
}
Save();
}
public IReadOnlyList<PendingMessage> TakeAll(string targetFc)
{
if (!_messages.TryGetValue(targetFc, out var list) || list.Count == 0)
return Array.Empty<PendingMessage>();
List<PendingMessage> taken;
lock (list)
{
taken = list.ToList();
list.Clear();
}
_messages.TryRemove(targetFc, out _);
Save();
return taken;
}
private void Load()
{
try
{
var dir = Path.GetDirectoryName(_filePath);
if (dir != null && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
if (!File.Exists(_filePath))
return;
var json = File.ReadAllText(_filePath);
var data = JsonSerializer.Deserialize<Dictionary<string, List<PendingMessage>>>(json);
if (data == null) return;
foreach (var (fc, msgs) in data)
{
if (msgs.Count > 0)
_messages[fc] = msgs;
}
var total = _messages.Values.Sum(v => v.Count);
_logger.LogInformation("[MessageStore] Loaded {Count} pending messages.", total);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "[MessageStore] Failed to load messages.");
}
}
private void Save()
{
lock (_saveLock)
{
try
{
var dir = Path.GetDirectoryName(_filePath);
if (dir != null && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
var data = new Dictionary<string, List<PendingMessage>>(StringComparer.OrdinalIgnoreCase);
foreach (var (fc, list) in _messages)
{
List<PendingMessage> copy;
lock (list)
{
copy = list.ToList();
}
if (copy.Count > 0)
data[fc] = copy;
}
var json = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_filePath, json);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "[MessageStore] Failed to save messages.");
}
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Text.Json.Serialization;
namespace Empostor.Plugins.Message;
public sealed class PendingMessage
{
[JsonPropertyName("id")]
public string Id { get; set; } = Guid.NewGuid().ToString("N");
[JsonPropertyName("sender_name")]
public string SenderName { get; set; } = "";
[JsonPropertyName("sender_fc")]
public string SenderFc { get; set; } = "";
[JsonPropertyName("target_fc")]
public string TargetFc { get; set; } = "";
[JsonPropertyName("content")]
public string Content { get; set; } = "";
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}
@@ -38,18 +38,22 @@ public sealed class PlayerChannelCommand : ICommand
if (string.IsNullOrEmpty(senderFc))
{
await ctx.PlayerControl.SendChatToPlayerAsync(
"[Refuse Channel] Unnkown Friendcode", ctx.PlayerControl);
T(ctx, "playerchannel.unknown_friendcode", "[Refuse Channel] Unknown Friendcode"),
ctx.PlayerControl);
return true;
}
if (!_fcToChannel.TryGetValue(senderFc, out var channel))
{
await ctx.PlayerControl.SendChatToPlayerAsync(
"[Refuse Channel] No exsit in any Channnel", ctx.PlayerControl);
T(ctx, "playerchannel.not_in_channel", "[Refuse Channel] Not in any channel"),
ctx.PlayerControl);
return true;
}
var prefixed = $"[{channel.Name}] {text}";
var prefixed = T(ctx, "playerchannel.message_format", "[{0}] {1}")
.Replace("{0}", channel.Name)
.Replace("{1}", text);
foreach (var player in ctx.Game.Players)
{
@@ -65,4 +69,10 @@ public sealed class PlayerChannelCommand : ICommand
return true;
}
private static string T(CommandContext ctx, string key, string defaultText)
{
string result = ctx.Lang.Get(key, ctx.SenderLanguage);
return result == key ? defaultText : result;
}
}
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Empostor.Api.Plugins;
using Microsoft.Extensions.Logging;
@@ -5,7 +6,7 @@ using Microsoft.Extensions.Logging;
namespace Empostor.Plugins.PlayerChannel;
[EmpostorPlugin("cn.hayashiume.playerchannel", "Player Channel", "HayashiUme", "1.0.0")]
public sealed class PlayerChannelPlugin : PluginBase
public sealed class PlayerChannelPlugin : PluginBase, IPluginLanguageProvider
{
private readonly ILogger<PlayerChannelPlugin> _logger;
@@ -21,4 +22,29 @@ public sealed class PlayerChannelPlugin : PluginBase
}
public override ValueTask DisableAsync() => default;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetTranslations()
{
return new Dictionary<string, IReadOnlyDictionary<string, string>>
{
["en"] = new Dictionary<string, string>
{
["playerchannel.unknown_friendcode"] = "[Refuse Channel] Unknown Friendcode",
["playerchannel.not_in_channel"] = "[Refuse Channel] Not in any channel",
["playerchannel.message_format"] = "[{0}] {1}",
},
["zh_CN"] = new Dictionary<string, string>
{
["playerchannel.unknown_friendcode"] = "[Refuse Channel] 未知的好友代码",
["playerchannel.not_in_channel"] = "[Refuse Channel] 不在任何频道中",
["playerchannel.message_format"] = "[{0}] {1}",
},
["zh_TW"] = new Dictionary<string, string>
{
["playerchannel.unknown_friendcode"] = "[Refuse Channel] 未知的好友代碼",
["playerchannel.not_in_channel"] = "[Refuse Channel] 不在任何頻道中",
["playerchannel.message_format"] = "[{0}] {1}",
},
};
}
}
@@ -26,7 +26,9 @@ public sealed class QqVerifyCommand : ICommand
if (!ctx.IsSenderChinese())
{
await ctx.PlayerControl.SendChatToPlayerAsync(
"此功能仅支持简体/繁体中文玩家使用。\nThis command is only available for Chinese language players.", ctx.PlayerControl);
T(ctx, "qqverify.chinese_only",
"This command is only available for Chinese language players.\n此功能仅支持简体/繁体中文玩家使用。"),
ctx.PlayerControl);
return true;
}
@@ -34,7 +36,9 @@ public sealed class QqVerifyCommand : ICommand
if (string.IsNullOrEmpty(fc))
{
await ctx.PlayerControl.SendChatToPlayerAsync(
"无法获取你的好友代码,请确保已登录账号。", ctx.PlayerControl);
T(ctx, "qqverify.no_friendcode",
"Unable to get your friend code. Please make sure you are logged in."),
ctx.PlayerControl);
return true;
}
@@ -42,15 +46,26 @@ public sealed class QqVerifyCommand : ICommand
if (string.IsNullOrEmpty(qqNumber) || !long.TryParse(qqNumber, out _))
{
await ctx.PlayerControl.SendChatToPlayerAsync(
"用法:/verify <你的QQ号>\n示例:/verify 123456789", ctx.PlayerControl);
T(ctx, "qqverify.usage_message",
"Usage: #verify <your QQ number>\nExample: #verify 123456789"),
ctx.PlayerControl);
return true;
}
_store.AddPending(fc, qqNumber);
await ctx.PlayerControl.SendChatToPlayerAsync(
$"已记录验证请求!请私聊QQ机器人发送:/验证 {fc}\n注意:验证码10分钟内有效。", ctx.PlayerControl);
T(ctx, "qqverify.recorded",
"Verification request recorded! Send /验证 {0} to the QQ bot.\nNote: The code is valid for 10 minutes.")
.Replace("{0}", fc),
ctx.PlayerControl);
return true;
}
private static string T(CommandContext ctx, string key, string defaultText)
{
string result = ctx.Lang.Get(key, ctx.SenderLanguage);
return result == key ? defaultText : result;
}
}
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Empostor.Api.Plugins;
using Microsoft.Extensions.Logging;
@@ -5,7 +6,7 @@ using Microsoft.Extensions.Logging;
namespace Empostor.Plugins.QqVerify;
[EmpostorPlugin("cn.hayashiume.qqverify", "QQ Verify", "ELinmei", "1.0.0")]
public sealed class QqVerifyPlugin : PluginBase
public sealed class QqVerifyPlugin : PluginBase, IPluginLanguageProvider
{
private readonly ILogger<QqVerifyPlugin> _logger;
@@ -16,9 +17,37 @@ public sealed class QqVerifyPlugin : PluginBase
public override ValueTask EnableAsync()
{
_logger.LogInformation("[QQVerify] Plugin enabled. /verify command registered.");
_logger.LogInformation("[QQVerify] Plugin enabled. #verify command registered.");
return default;
}
public override ValueTask DisableAsync() => default;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetTranslations()
{
return new Dictionary<string, IReadOnlyDictionary<string, string>>
{
["en"] = new Dictionary<string, string>
{
["qqverify.chinese_only"] = "This command is only available for Chinese language players.\n此功能仅支持简体/繁体中文玩家使用。",
["qqverify.no_friendcode"] = "Unable to get your friend code. Please make sure you are logged in.",
["qqverify.usage_message"] = "Usage: #verify <your QQ number>\nExample: #verify 123456789",
["qqverify.recorded"] = "Verification request recorded! Send /验证 {0} to the QQ bot.\nNote: The code is valid for 10 minutes.",
},
["zh_CN"] = new Dictionary<string, string>
{
["qqverify.chinese_only"] = "此功能仅支持简体/繁体中文玩家使用。\nThis command is only available for Chinese language players.",
["qqverify.no_friendcode"] = "无法获取你的好友代码,请确保已登录账号。",
["qqverify.usage_message"] = "用法:#verify <你的QQ号>\n示例:#verify 123456789",
["qqverify.recorded"] = "已记录验证请求!请私聊QQ机器人发送:/验证 {0}\n注意:验证码10分钟内有效。",
},
["zh_TW"] = new Dictionary<string, string>
{
["qqverify.chinese_only"] = "此功能僅支援簡體/繁體中文玩家使用。\nThis command is only available for Chinese language players.",
["qqverify.no_friendcode"] = "無法獲取你的好友代碼,請確保已登入帳號。",
["qqverify.usage_message"] = "用法:#verify <你的QQ號>\n範例:#verify 123456789",
["qqverify.recorded"] = "已記錄驗證請求!請私聊QQ機器人發送:/驗證 {0}\n注意:驗證碼10分鐘內有效。",
},
};
}
}
+21 -1
View File
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Empostor.Api.Plugins;
using Microsoft.Extensions.Logging;
@@ -5,7 +6,7 @@ using Microsoft.Extensions.Logging;
namespace Empostor.Plugins.Titles;
[EmpostorPlugin("cn.Empostor.titles", "Title System", "Empostor", "1.0.0")]
public sealed class TitlesPlugin : PluginBase
public sealed class TitlesPlugin : PluginBase, IPluginLanguageProvider
{
private readonly ILogger<TitlesPlugin> _logger;
@@ -18,4 +19,23 @@ public sealed class TitlesPlugin : PluginBase
}
public override ValueTask DisableAsync() => default;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetTranslations()
{
return new Dictionary<string, IReadOnlyDictionary<string, string>>
{
["en"] = new Dictionary<string, string>
{
["titles.display_format"] = "[{0}] {1}",
},
["zh_CN"] = new Dictionary<string, string>
{
["titles.display_format"] = "[{0}] {1}",
},
["zh_TW"] = new Dictionary<string, string>
{
["titles.display_format"] = "[{0}] {1}",
},
};
}
}
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Empostor.Api.Games.Managers;
using Empostor.Api.Net.Manager;
using Microsoft.AspNetCore.Mvc;
namespace Empostor.Server.Http;
[ApiController]
public sealed class MonitorController : ControllerBase
{
private static readonly DateTime StartTime = Process.GetCurrentProcess().StartTime.ToUniversalTime();
private readonly IGameManager _gameManager;
private readonly IClientManager _clientManager;
public MonitorController(IGameManager gameManager, IClientManager clientManager)
{
_gameManager = gameManager;
_clientManager = clientManager;
}
[HttpGet("/api/monitor/status")]
public IActionResult GetStatus()
{
var process = Process.GetCurrentProcess();
var uptime = DateTime.UtcNow - StartTime;
var games = _gameManager.Games.ToList();
var clients = _clientManager.Clients.ToList();
var status = new MonitorStatus
{
Status = "running",
Timestamp = DateTime.UtcNow.ToString("o"),
UptimeSeconds = (long)uptime.TotalSeconds,
UptimeDisplay = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m",
Version = GetVersion(),
Runtime = RuntimeInformation.FrameworkDescription,
Platform = RuntimeInformation.OSDescription,
Processors = Environment.ProcessorCount,
MemoryMb = process.WorkingSet64 / 1024 / 1024,
GameCount = games.Count,
PlayerCount = clients.Count(c => c.Player != null),
ActiveConnections = clients.Count,
Games = games.Select(g => new MonitorGame
{
Code = g.Code.ToString(),
State = g.GameState.ToString(),
Map = g.Options.Map.ToString(),
PlayerCount = g.PlayerCount,
Host = g.Host?.Character?.PlayerInfo?.PlayerName ?? "(none)",
}).ToList(),
};
return new JsonResult(status);
}
[HttpGet("/api/monitor/health")]
public IActionResult GetHealth()
{
return Ok(new { status = "healthy", timestamp = DateTime.UtcNow.ToString("o") });
}
private static string GetVersion()
{
try
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var info = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location);
return info.ProductVersion ?? "unknown";
}
catch
{
return "unknown";
}
}
}
public sealed class MonitorStatus
{
[JsonPropertyName("status")]
public string Status { get; set; } = "";
[JsonPropertyName("timestamp")]
public string Timestamp { get; set; } = "";
[JsonPropertyName("uptime_seconds")]
public long UptimeSeconds { get; set; }
[JsonPropertyName("uptime_display")]
public string UptimeDisplay { get; set; } = "";
[JsonPropertyName("version")]
public string Version { get; set; } = "";
[JsonPropertyName("runtime")]
public string Runtime { get; set; } = "";
[JsonPropertyName("platform")]
public string Platform { get; set; } = "";
[JsonPropertyName("processors")]
public int Processors { get; set; }
[JsonPropertyName("memory_mb")]
public long MemoryMb { get; set; }
[JsonPropertyName("game_count")]
public int GameCount { get; set; }
[JsonPropertyName("player_count")]
public int PlayerCount { get; set; }
[JsonPropertyName("active_connections")]
public int ActiveConnections { get; set; }
[JsonPropertyName("games")]
public List<MonitorGame> Games { get; set; } = new();
}
public sealed class MonitorGame
{
[JsonPropertyName("code")]
public string Code { get; set; } = "";
[JsonPropertyName("state")]
public string State { get; set; } = "";
[JsonPropertyName("map")]
public string Map { get; set; } = "";
[JsonPropertyName("player_count")]
public int PlayerCount { get; set; }
[JsonPropertyName("host")]
public string Host { get; set; } = "";
}
@@ -0,0 +1,135 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Empostor.Server.Http;
[ApiController]
[Route("/privacy")]
public sealed class PrivacyController : ControllerBase
{
private static readonly string PagesDir = Path.Combine(Directory.GetCurrentDirectory(), "Pages");
private static readonly string PrivacyFile = Path.Combine(PagesDir, "privacy.html");
private static bool _initialized;
private static readonly object _initLock = new();
private readonly ILogger<PrivacyController> _logger;
public PrivacyController(ILogger<PrivacyController> logger)
{
_logger = logger;
EnsureFile();
}
[HttpGet]
public IActionResult GetPrivacy()
{
if (!System.IO.File.Exists(PrivacyFile))
return Content(DefaultPrivacyHtml(), "text/html; charset=utf-8");
var html = System.IO.File.ReadAllText(PrivacyFile);
return Content(html, "text/html; charset=utf-8");
}
[HttpPost]
[Route("/admin/api/privacy")]
public async Task<IActionResult> UpdatePrivacy()
{
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
try
{
var doc = System.Text.Json.JsonDocument.Parse(body);
var content = doc.RootElement.GetProperty("content").GetString() ?? "";
var token = doc.RootElement.GetProperty("token").GetString() ?? "";
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
?? "empostor";
if (token != adminToken)
return Unauthorized(new { error = "Invalid token." });
System.IO.File.WriteAllText(PrivacyFile, content);
_logger.LogInformation("[PrivacyController] Privacy policy updated.");
return Ok(new { success = true });
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}
private void EnsureFile()
{
if (_initialized) return;
lock (_initLock)
{
if (_initialized) return;
_initialized = true;
if (!Directory.Exists(PagesDir))
Directory.CreateDirectory(PagesDir);
if (!System.IO.File.Exists(PrivacyFile))
{
System.IO.File.WriteAllText(PrivacyFile, DefaultPrivacyHtml());
_logger.LogInformation("[PrivacyController] Written default Pages/privacy.html");
}
}
}
private static string DefaultPrivacyHtml() => """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Privacy Policy - Empostor Server</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.6; color: #333; }
h1 { border-bottom: 2px solid #eee; padding-bottom: 0.5rem; }
h2 { margin-top: 2rem; }
</style>
</head>
<body>
<h1>Privacy Policy</h1>
<p>Last updated: 2026-05-23</p>
<h2>1. Data Collection</h2>
<p>This is a private Among Us server. We collect the minimum data required for the game to function:</p>
<ul>
<li><strong>Friend codes</strong> — used for player identification and game invitations.</li>
<li><strong>Chat messages</strong> — processed in-game and may be logged for moderation purposes.</li>
<li><strong>IP addresses</strong> — used for network connectivity and may be logged for security.</li>
<li><strong>Gameplay data</strong> — tasks completed, kills, votes, and other in-game actions.</li>
</ul>
<h2>2. Data Usage</h2>
<p>Collected data is used solely for:</p>
<ul>
<li>Providing the game service</li>
<li>Moderation and anti-cheat enforcement</li>
<li>Service improvement and debugging</li>
</ul>
<h2>3. Data Sharing</h2>
<p>We do not sell or share your personal data with third parties. Data may be disclosed if required by law.</p>
<h2>4. Data Retention</h2>
<p>Game logs are retained for up to 31 days. Player statistics are retained while your account is active.</p>
<h2>5. Contact</h2>
<p>If you have questions about this privacy policy, contact the server administrator.</p>
<h2>6. Third-Party Services</h2>
<p>This server may use third-party plugins that process data. Each plugin operates under its own privacy terms.</p>
<p>In-game chat filters are applied for content moderation purposes.</p>
</body>
</html>
""";
}
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Empostor.Api.Languages;
using Empostor.Api.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -42,6 +43,17 @@ namespace Empostor.Server.Plugins
plugin.Instance = instance;
await plugin.Instance.EnableAsync();
// Register plugin-provided translations.
if (instance is IPluginLanguageProvider langProvider)
{
var translations = langProvider.GetTranslations();
if (translations.Count > 0)
{
_serviceProvider.GetRequiredService<LanguageService>()
.AddPluginTranslations(translations);
}
}
}
_logger.LogInformation(
+15
View File
@@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugin.Code", "Emp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugin.Narrator", "Empostor.Plugin.Narrator\Empostor.Plugin.Narrator.csproj", "{FE6D40CC-4BE5-4E38-9DCA-E6832709FE3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Empostor.Plugins.MapVote", "Empostor.Plugins.MapVote\Empostor.Plugins.MapVote.csproj", "{9D67CAC6-D296-404C-9189-2FF9B86D287C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -227,6 +229,18 @@ Global
{FE6D40CC-4BE5-4E38-9DCA-E6832709FE3F}.Release|x64.Build.0 = Release|Any CPU
{FE6D40CC-4BE5-4E38-9DCA-E6832709FE3F}.Release|x86.ActiveCfg = Release|Any CPU
{FE6D40CC-4BE5-4E38-9DCA-E6832709FE3F}.Release|x86.Build.0 = Release|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Debug|x64.ActiveCfg = Debug|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Debug|x64.Build.0 = Debug|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Debug|x86.ActiveCfg = Debug|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Debug|x86.Build.0 = Debug|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Release|Any CPU.Build.0 = Release|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Release|x64.ActiveCfg = Release|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Release|x64.Build.0 = Release|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Release|x86.ActiveCfg = Release|Any CPU
{9D67CAC6-D296-404C-9189-2FF9B86D287C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -243,6 +257,7 @@ Global
{D4B6C8A2-5F3E-4C1D-9B7A-6E2F8A4C1D3F} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
{9D1FD146-E7D9-4AFF-9B8E-25E6021A69D7} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
{ADC4F301-51C0-45DB-B97A-BE3844DFD19B} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
{9D67CAC6-D296-404C-9189-2FF9B86D287C} = {E0009DCB-32A6-45C4-A06C-7F62D4091A29}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DAF5625C-7E46-4DCC-AF2C-11D20D965454}