mirror of
https://github.com/RaidMax/IW4M-Admin.git
synced 2025-06-07 21:58:06 -05:00
Merge branch 'develop' into release/pre
This commit is contained in:
commit
4236ae5a6e
257
.github/workflows/build_application.yml
vendored
Normal file
257
.github/workflows/build_application.yml
vendored
Normal file
@ -0,0 +1,257 @@
|
||||
name: Application build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop, release/pre, master ]
|
||||
paths:
|
||||
- Application/**
|
||||
- WebfrontCore/**
|
||||
- Data/**
|
||||
- SharedLibraryCore/**
|
||||
- Plugins/**
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
paths:
|
||||
- Application/**
|
||||
- WebfrontCore/**
|
||||
- Data/**
|
||||
- SharedLibraryCore/**
|
||||
- Plugins/**
|
||||
|
||||
env:
|
||||
releaseType: prerelease
|
||||
|
||||
jobs:
|
||||
update_revision_number:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
revision_number: ${{ steps.revision.outputs.revision_number }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: cache_dir
|
||||
key: revision-number
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "current_date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
|
||||
|
||||
- name: Check and update revision number
|
||||
id: revision
|
||||
run: |
|
||||
FILENAME=cache_dir/revision_number.txt
|
||||
DATEFILE=cache_dir/previous_date.txt
|
||||
|
||||
mkdir -p cache_dir
|
||||
|
||||
if [ -f "$DATEFILE" ]; then
|
||||
prev_date=$(cat "$DATEFILE")
|
||||
rev_number=$(cat "$FILENAME")
|
||||
else
|
||||
prev_date=""
|
||||
rev_number=0
|
||||
fi
|
||||
|
||||
if [ "$current_date" = "$prev_date" ]; then
|
||||
rev_number=$((rev_number + 1))
|
||||
else
|
||||
rev_number=1
|
||||
fi
|
||||
|
||||
echo "New revision number: $rev_number"
|
||||
echo $rev_number > "$FILENAME"
|
||||
echo $current_date > "$DATEFILE"
|
||||
echo "revision_number=$rev_number" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Save cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: cache_dir
|
||||
key: revision-number
|
||||
|
||||
make_version:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ update_revision_number ]
|
||||
|
||||
outputs:
|
||||
build_num: ${{ steps.generate_build_number.outputs.build_num }}
|
||||
env:
|
||||
revisionNumber: ${{ needs.update_revision_number.outputs.revision_number }}
|
||||
|
||||
steps:
|
||||
- name: Make build number
|
||||
id: generate_build_number
|
||||
run: |
|
||||
build_num=$(date +'%Y.%-m.%-d').${{ env.revisionNumber }}
|
||||
echo "build_num=$build_num" >> $GITHUB_OUTPUT
|
||||
echo "Build number is $build_num"
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ make_version ]
|
||||
|
||||
env:
|
||||
solution: IW4MAdmin.sln
|
||||
buildConfiguration: Prerelease
|
||||
isPreRelease: false
|
||||
|
||||
buildPlatform: Any CPU
|
||||
outputFolder: ${{ github.workspace }}/Publish/Prerelease
|
||||
buildNumber: ${{ needs.make_version.outputs.build_num }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore NuGet packages
|
||||
run: dotnet restore ${{ env.solution }}
|
||||
|
||||
- name: Preload external resources
|
||||
run: |
|
||||
echo "Build Configuration is ${{ env.buildConfiguration }}, Release Type is ${{ env.releaseType }}"
|
||||
mkdir -p WebfrontCore/wwwroot/lib/open-iconic/font/css
|
||||
curl -o WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss
|
||||
sed -i 's#../fonts/#/font/#g' WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss
|
||||
|
||||
- name: Build webfront
|
||||
run: dotnet build WebfrontCore/WebfrontCore.csproj -c ${{ env.buildConfiguration }} /p:Configuration=${{ env.buildConfiguration }} /p:Platform="x64"
|
||||
|
||||
- name: Compile SCSS files
|
||||
run: |
|
||||
dotnet tool install Excubo.WebCompiler --global
|
||||
webcompiler -r WebfrontCore/wwwroot/css/src -o WebfrontCore/wwwroot/css/ -m disable -z disable
|
||||
webcompiler WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss -o WebfrontCore/wwwroot/css/ -m disable -z disable
|
||||
|
||||
- name: Bundle JS files
|
||||
run: |
|
||||
echo 'Getting dotnet bundle'
|
||||
curl -o ${{ github.workspace }}/dotnet-bundle.zip https://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip
|
||||
echo 'Unzipping download'
|
||||
unzip ${{ github.workspace }}/dotnet-bundle.zip -d ${{ github.workspace }}/bundle
|
||||
echo 'Executing dotnet-bundle'
|
||||
cd ${{ github.workspace }}/bundle
|
||||
dotnet dotnet-bundle.dll clean ${{ github.workspace }}/WebfrontCore/bundleconfig.json
|
||||
dotnet dotnet-bundle.dll ${{ github.workspace }}/WebfrontCore/bundleconfig.json
|
||||
|
||||
- name: Build plugins
|
||||
run: |
|
||||
cd Plugins
|
||||
find . -name "*.csproj" -print0 | xargs -0 -I {} dotnet publish {} -c ${{ env.buildConfiguration }} -o ../BUILD/Plugins /p:Configuration=${{ env.buildConfiguration }} /p:Platform="x64" /p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:Version=${{ env.buildNumber }} --no-restore
|
||||
|
||||
- name: Build application
|
||||
run: dotnet publish Application/Application.csproj -c ${{ env.buildConfiguration }} -o ${{ env.outputFolder }} /p:Version=${{ env.buildNumber }} /p:Configuration=${{ env.buildConfiguration }} /p:Platform="x64" --no-restore
|
||||
|
||||
- name: Download translations
|
||||
run: |
|
||||
mkdir -p "${{ env.outputFolder }}/Localization"
|
||||
localizations=("en-US" "ru-RU" "es-EC" "pt-BR" "de-DE")
|
||||
for localization in "${localizations[@]}"
|
||||
do
|
||||
url="https://master.iw4.zip/localization/$localization"
|
||||
filePath="${{ env.outputFolder }}/Localization/IW4MAdmin.$localization.json"
|
||||
curl -s "$url" -o "$filePath"
|
||||
done
|
||||
|
||||
- name: Clean up publish files
|
||||
run: |
|
||||
chmod +x ${{ github.workspace }}/Application/BuildScripts/PostBuild.sh
|
||||
bash ${{ github.workspace }}/Application/BuildScripts/PostBuild.sh ${{ env.outputFolder }} ${{ github.workspace }}
|
||||
|
||||
- name: Generate start scripts
|
||||
run: |
|
||||
cat << EOF > "${{ env.outputFolder }}/StartIW4MAdmin.cmd"
|
||||
@echo off
|
||||
@title IW4MAdmin
|
||||
set DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
dotnet Lib\IW4MAdmin.dll
|
||||
pause
|
||||
EOF
|
||||
|
||||
cat << EOF > "${{ env.outputFolder }}/StartIW4MAdmin.sh"
|
||||
#!/bin/bash
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
dotnet Lib/IW4MAdmin.dll
|
||||
EOF
|
||||
|
||||
- name: Move extra content into publish directory
|
||||
run: |
|
||||
cp ${{ github.workspace }}/Plugins/ScriptPlugins/*.js ${{ env.outputFolder }}/Plugins/
|
||||
cp ${{ github.workspace }}/BUILD/Plugins/*.dll ${{ env.outputFolder }}/Plugins/
|
||||
mkdir -p ${{ env.outputFolder }}/wwwroot/css
|
||||
cp ${{ github.workspace }}/WebfrontCore/wwwroot/css/global.min.css ${{ env.outputFolder }}/wwwroot/css/global.min.css
|
||||
mkdir -p ${{ env.outputFolder }}/wwwroot/js
|
||||
cp ${{ github.workspace }}/WebfrontCore/wwwroot/js/global.min.js ${{ env.outputFolder }}/wwwroot/js/global.min.js
|
||||
mkdir -p ${{ env.outputFolder }}/wwwroot/font
|
||||
rsync -ar ${{ github.workspace }}/WebfrontCore/wwwroot/lib/open-iconic/font/fonts/ ${{ env.outputFolder }}/wwwroot/font
|
||||
mkdir -p ${{ env.outputFolder }}/GameFiles
|
||||
rsync -ar ${{ github.workspace }}/GameFiles/ ${{ env.outputFolder }}/GameFiles
|
||||
mkdir -p ${{ env.outputFolder }}/wwwroot/images/
|
||||
rsync -ar ${{ github.workspace }}/WebfrontCore/wwwroot/images/ ${{ env.outputFolder }}/wwwroot/images/
|
||||
rsync -ar ${{ github.workspace }}/BUILD/Plugins/wwwroot/ ${{ env.outputFolder }}/wwwroot/
|
||||
|
||||
- name: Upload artifact for analysis
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: IW4MAdmin-${{ env.buildNumber }}-${{ env.releaseType }}
|
||||
path: ${{ env.outputFolder }}
|
||||
|
||||
release_github:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ make_version, build ]
|
||||
permissions:
|
||||
contents: write
|
||||
environment: prerelease
|
||||
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release/pre' }}
|
||||
|
||||
env:
|
||||
buildNumber: ${{ needs.make_version.outputs.build_num }}
|
||||
|
||||
steps:
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: IW4MAdmin-${{ env.buildNumber }}-${{ env.releaseType }}
|
||||
path: ${{ github.workspace }}
|
||||
|
||||
- name: Zip build
|
||||
run: zip -r IW4MAdmin-${{ env.buildNumber }}.zip ${{ github.workspace }}/*
|
||||
|
||||
- name: Make release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: ${{ env.buildNumber }}-${{ env.releaseType }}
|
||||
name: IW4MAdmin ${{ env.buildNumber }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
body: Automated rolling release - changelog below. [Updating Instructions](https://github.com/RaidMax/IW4M-Admin/wiki/Getting-Started#updating)
|
||||
generateReleaseNotes: true
|
||||
artifacts: ${{ github.workspace }}/*.zip
|
||||
artifactErrorsFailBuild: true
|
||||
|
||||
update_master_version:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ make_version, build, release_github ]
|
||||
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release/pre' }}
|
||||
|
||||
env:
|
||||
buildNumber: ${{ needs.make_version.outputs.build_num }}
|
||||
|
||||
steps:
|
||||
- name: Update master version
|
||||
run: |
|
||||
curl --header "Content-Type: application/json" \
|
||||
--request POST \
|
||||
--data '{"current-version-${{ env.releaseType }}":"${{ env.buildNumber }}","jwt-secret": "${{ secrets.JWTSecret }}"}' \
|
||||
http://api.raidmax.org:5000/version
|
93
.github/workflows/shared_library_nuget.yml
vendored
Normal file
93
.github/workflows/shared_library_nuget.yml
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
name: SharedLibraryCore NuGet
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop, release/pre, master ]
|
||||
paths:
|
||||
- SharedLibraryCore/**
|
||||
- Data/**
|
||||
- .github/workflows/shared_library_nuget.yml
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
paths:
|
||||
- SharedLibraryCore/**
|
||||
- Data/**
|
||||
|
||||
env:
|
||||
outputDirectory: ${{ github.workspace}}/nuget
|
||||
|
||||
jobs:
|
||||
make_version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
build_num: ${{ steps.generate_build_number.outputs.build_num }}
|
||||
|
||||
steps:
|
||||
- name: Make build number
|
||||
id: generate_build_number
|
||||
run: |
|
||||
run_number=$(git log --since=$(date +'%Y-%m-%dT00:00:00') --oneline | grep -c 'workflow_run')
|
||||
run_number=$((run_number + 1))
|
||||
build_num=$(date +'%Y.%-m.%-d').$(run_number)
|
||||
echo "build_num=$build_num" >> $GITHUB_OUTPUT
|
||||
echo "Build number is $build_num"
|
||||
|
||||
build_pack:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ make_version ]
|
||||
|
||||
env:
|
||||
buildNumber: ${{ needs.make_version.outputs.build_num }}
|
||||
packageTag: ${{ github.event_name == 'pull_request' && '-beta' || '-preview' }}
|
||||
buildConfiguration: Prerelease
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build data
|
||||
run: dotnet build **/Data.csproj -c ${{env.buildConfiguration}} /p:Version=${{ env.buildNumber }} --no-restore
|
||||
|
||||
- name: Build SLC
|
||||
run: dotnet build **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} /p:Version=${{ env.buildNumber }} --no-restore
|
||||
|
||||
- name: Pack SLC
|
||||
run: dotnet pack **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} -p:PackageVersion=${{ env.buildNumber }}${{ env.packageTag }} -o ${{ env.outputDirectory }} --no-restore
|
||||
|
||||
- name: Publish nuget package artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SharedLibraryCore-${{ env.buildNumber }}
|
||||
path: ${{ env.outputDirectory }}/*.nupkg
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs: [ make_version, build_pack ]
|
||||
environment: prerelease
|
||||
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release/pre' || github.ref == 'refs/heads/develop' }}
|
||||
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: SharedLibraryCore-${{ needs.make_version.outputs.build_num }}
|
||||
path: ${{ env.outputDirectory }}
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Publish NuGet package
|
||||
run: |
|
||||
for file in ${{ env.outputDirectory }}/*.nupkg; do
|
||||
dotnet nuget push "$file" --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
done
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -247,3 +247,4 @@ launchSettings.json
|
||||
*.db
|
||||
/Data/IW4MAdmin_Migration.db-shm
|
||||
/Data/IW4MAdmin_Migration.db-wal
|
||||
bundle/
|
@ -1,12 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using RestEase;
|
||||
using Refit;
|
||||
|
||||
namespace IW4MAdmin.Application.API.GameLogServer
|
||||
{
|
||||
[Header("User-Agent", "IW4MAdmin-RestEase")]
|
||||
[Headers("User-Agent: IW4MAdmin-RestEase")]
|
||||
public interface IGameLogServer
|
||||
{
|
||||
[Get("log/{path}/{key}")]
|
||||
Task<LogInfo> Log([Path] string path, [Path] string key);
|
||||
[Get("/log/{path}/{key}")]
|
||||
Task<LogInfo> Log(string path, string key);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,16 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace IW4MAdmin.Application.API.GameLogServer
|
||||
{
|
||||
public class LogInfo
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; set; }
|
||||
[JsonProperty("length")]
|
||||
[JsonPropertyName("length")]
|
||||
public int Length { get; set; }
|
||||
[JsonProperty("data")]
|
||||
[JsonPropertyName("data")]
|
||||
public string Data { get; set; }
|
||||
[JsonProperty("next_key")]
|
||||
[JsonPropertyName("next_key")]
|
||||
public string NextKey { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SharedLibraryCore.Helpers;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
@ -12,32 +12,32 @@ namespace IW4MAdmin.Application.API.Master
|
||||
/// <summary>
|
||||
/// Unique ID of the instance
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates how long the instance has been running
|
||||
/// </summary>
|
||||
[JsonProperty("uptime")]
|
||||
[JsonPropertyName("uptime")]
|
||||
public int Uptime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the version of the instance
|
||||
/// </summary>
|
||||
[JsonProperty("version")]
|
||||
[JsonPropertyName("version")]
|
||||
[JsonConverter(typeof(BuildNumberJsonConverter))]
|
||||
public BuildNumber Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of servers the instance is monitoring
|
||||
/// </summary>
|
||||
[JsonProperty("servers")]
|
||||
[JsonPropertyName("servers")]
|
||||
public List<ApiServer> Servers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Url IW4MAdmin is listening on
|
||||
/// </summary>
|
||||
[JsonProperty("webfront_url")]
|
||||
[JsonPropertyName("webfront_url")]
|
||||
public string WebfrontUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,28 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
public class ApiServer
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
[JsonProperty("ip")]
|
||||
[JsonPropertyName("ip")]
|
||||
public string IPAddress { get; set; }
|
||||
[JsonProperty("port")]
|
||||
[JsonPropertyName("port")]
|
||||
public short Port { get; set; }
|
||||
[JsonProperty("version")]
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; set; }
|
||||
[JsonProperty("gametype")]
|
||||
[JsonPropertyName("gametype")]
|
||||
public string Gametype { get; set; }
|
||||
[JsonProperty("map")]
|
||||
[JsonPropertyName("map")]
|
||||
public string Map { get; set; }
|
||||
[JsonProperty("game")]
|
||||
[JsonPropertyName("game")]
|
||||
public string Game { get; set; }
|
||||
[JsonProperty("hostname")]
|
||||
[JsonPropertyName("hostname")]
|
||||
public string Hostname { get; set; }
|
||||
[JsonProperty("clientnum")]
|
||||
[JsonPropertyName("clientnum")]
|
||||
public int ClientNum { get; set; }
|
||||
[JsonProperty("maxclientnum")]
|
||||
[JsonPropertyName("maxclientnum")]
|
||||
public int MaxClientNum { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,79 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using IW4MAdmin.Application.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using RestEase;
|
||||
using Refit;
|
||||
using SharedLibraryCore.Helpers;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
namespace IW4MAdmin.Application.API.Master;
|
||||
|
||||
public class AuthenticationId
|
||||
{
|
||||
public class AuthenticationId
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
public class TokenId
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
}
|
||||
|
||||
public class VersionInfo
|
||||
{
|
||||
[JsonProperty("current-version-stable")]
|
||||
[JsonConverter(typeof(BuildNumberJsonConverter))]
|
||||
public BuildNumber CurrentVersionStable { get; set; }
|
||||
|
||||
[JsonProperty("current-version-prerelease")]
|
||||
[JsonConverter(typeof(BuildNumberJsonConverter))]
|
||||
public BuildNumber CurrentVersionPrerelease { get; set; }
|
||||
}
|
||||
|
||||
public class ResultMessage
|
||||
{
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class PluginSubscriptionContent
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public PluginType Type { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the capabilities of the master API
|
||||
/// </summary>
|
||||
[Header("User-Agent", "IW4MAdmin-RestEase")]
|
||||
public interface IMasterApi
|
||||
{
|
||||
[Header("Authorization")]
|
||||
string AuthorizationToken { get; set; }
|
||||
|
||||
[Post("authenticate")]
|
||||
Task<TokenId> Authenticate([Body] AuthenticationId Id);
|
||||
|
||||
[Post("instance/")]
|
||||
[AllowAnyStatusCode]
|
||||
Task<Response<ResultMessage>> AddInstance([Body] ApiInstance instance);
|
||||
|
||||
[Put("instance/{id}")]
|
||||
[AllowAnyStatusCode]
|
||||
Task<Response<ResultMessage>> UpdateInstance([Path] string id, [Body] ApiInstance instance);
|
||||
|
||||
[Get("version/{apiVersion}")]
|
||||
Task<VersionInfo> GetVersion([Path] int apiVersion);
|
||||
|
||||
[Get("localization")]
|
||||
Task<List<SharedLibraryCore.Localization.Layout>> GetLocalization();
|
||||
|
||||
[Get("localization/{languageTag}")]
|
||||
Task<SharedLibraryCore.Localization.Layout> GetLocalization([Path("languageTag")] string languageTag);
|
||||
|
||||
[Get("plugin_subscriptions")]
|
||||
Task<IEnumerable<PluginSubscriptionContent>> GetPluginSubscription([Query("instance_id")] Guid instanceId, [Query("subscription_id")] string subscription_id);
|
||||
}
|
||||
[JsonPropertyName("id")] public string Id { get; set; }
|
||||
}
|
||||
|
||||
public class TokenId
|
||||
{
|
||||
[JsonPropertyName("access_token")] public string AccessToken { get; set; }
|
||||
}
|
||||
|
||||
public class VersionInfo
|
||||
{
|
||||
[JsonPropertyName("current-version-stable")]
|
||||
[JsonConverter(typeof(BuildNumberJsonConverter))]
|
||||
public BuildNumber CurrentVersionStable { get; set; }
|
||||
|
||||
[JsonPropertyName("current-version-prerelease")]
|
||||
[JsonConverter(typeof(BuildNumberJsonConverter))]
|
||||
public BuildNumber CurrentVersionPrerelease { get; set; }
|
||||
}
|
||||
|
||||
public class ResultMessage
|
||||
{
|
||||
[JsonPropertyName("message")] public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class PluginSubscriptionContent
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public PluginType Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the capabilities of the master API
|
||||
/// </summary>
|
||||
[Headers("User-Agent: IW4MAdmin-RestEase")]
|
||||
public interface IMasterApi
|
||||
{
|
||||
[Post("/authenticate")]
|
||||
Task<TokenId> Authenticate([Body] AuthenticationId Id);
|
||||
|
||||
[Post("/instance/")]
|
||||
Task<IApiResponse<ResultMessage>> AddInstance([Body] ApiInstance instance, [Header("Authorization")] string authorization);
|
||||
|
||||
[Put("/instance/{id}")]
|
||||
Task<IApiResponse<ResultMessage>> UpdateInstance(string id, [Body] ApiInstance instance, [Header("Authorization")] string authorization);
|
||||
|
||||
[Get("/version/{apiVersion}")]
|
||||
Task<VersionInfo> GetVersion(int apiVersion);
|
||||
|
||||
[Get("/localization")]
|
||||
Task<List<SharedLibraryCore.Localization.Layout>> GetLocalization();
|
||||
|
||||
[Get("/localization/{languageTag}")]
|
||||
Task<SharedLibraryCore.Localization.Layout> GetLocalization(string languageTag);
|
||||
|
||||
[Get("/plugin_subscriptions")]
|
||||
Task<IEnumerable<PluginSubscriptionContent>> GetPluginSubscription([Query] string instance_id,
|
||||
[Query] string subscription_id);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||
<Version>2020.0.0.0</Version>
|
||||
@ -21,20 +21,21 @@
|
||||
<Win32Resource />
|
||||
<RootNamespace>IW4MAdmin.Application</RootNamespace>
|
||||
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2049" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
|
||||
<PackageReference Include="Jint" Version="3.1.3" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="RestEase" Version="1.5.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Refit" Version="7.1.0" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageReference Include="System.CommandLine.DragonFruit" Version="0.4.0-alpha.22272.1" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@ -72,18 +73,8 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="if $(ConfigurationName) == Debug call $(ProjectDir)BuildScripts\PreBuild.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(OutDir)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
|
||||
<Output TaskParameter="Assemblies" ItemName="CurrentAssembly" />
|
||||
</GetAssemblyIdentity>
|
||||
<Exec Command="if $(ConfigurationName) == Debug call $(ProjectDir)BuildScripts\PostBuild.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(OutDir) %25(CurrentAssembly.Version)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PostPublish" AfterTargets="Publish">
|
||||
<Exec Command="if $(ConfigurationName) == Debug call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(ConfigurationName)" />
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(ConfigurationName)'=='Debug'">
|
||||
<Exec Command="powershell.exe $(ProjectDir)BuildScripts\PreBuild.ps1 $(SolutionDir) $(TargetDir)" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@ -26,6 +26,7 @@ using Data.Abstractions;
|
||||
using Data.Context;
|
||||
using Data.Models;
|
||||
using IW4MAdmin.Application.Configuration;
|
||||
using IW4MAdmin.Application.IO;
|
||||
using IW4MAdmin.Application.Migration;
|
||||
using IW4MAdmin.Application.Plugin.Script;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -68,6 +69,7 @@ namespace IW4MAdmin.Application
|
||||
private readonly ClientService ClientSvc;
|
||||
readonly PenaltyService PenaltySvc;
|
||||
private readonly IAlertManager _alertManager;
|
||||
private readonly ConfigurationWatcher _watcher;
|
||||
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
||||
readonly IPageList PageList;
|
||||
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
|
||||
@ -94,7 +96,8 @@ namespace IW4MAdmin.Application
|
||||
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
|
||||
ICoreEventHandler coreEventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory,
|
||||
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider,
|
||||
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration, IEnumerable<IPluginV2> v2PLugins)
|
||||
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration, IEnumerable<IPluginV2> v2PLugins,
|
||||
ConfigurationWatcher watcher)
|
||||
{
|
||||
MiddlewareActionHandler = actionHandler;
|
||||
_servers = new ConcurrentBag<Server>();
|
||||
@ -102,10 +105,11 @@ namespace IW4MAdmin.Application
|
||||
ClientSvc = clientService;
|
||||
PenaltySvc = penaltyService;
|
||||
_alertManager = alertManager;
|
||||
_watcher = watcher;
|
||||
ConfigHandler = appConfigHandler;
|
||||
StartTime = DateTime.UtcNow;
|
||||
PageList = new PageList();
|
||||
AdditionalEventParsers = new List<IEventParser> { new BaseEventParser(parserRegexFactory, logger, _appConfig) };
|
||||
AdditionalEventParsers = new List<IEventParser> { new BaseEventParser(parserRegexFactory, logger, _appConfig, serviceProvider.GetRequiredService<IGameScriptEventFactory>()) };
|
||||
AdditionalRConParsers = new List<IRConParser> { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) };
|
||||
TokenAuthenticator = new TokenAuthentication();
|
||||
_logger = logger;
|
||||
@ -529,6 +533,7 @@ namespace IW4MAdmin.Application
|
||||
|
||||
Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]);
|
||||
await InitializeServers();
|
||||
_watcher.Enable();
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
@ -710,7 +715,7 @@ namespace IW4MAdmin.Application
|
||||
|
||||
public IEventParser GenerateDynamicEventParser(string name)
|
||||
{
|
||||
return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration())
|
||||
return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration(), _serviceProvider.GetRequiredService<IGameScriptEventFactory>())
|
||||
{
|
||||
Name = name
|
||||
};
|
||||
|
@ -1,12 +0,0 @@
|
||||
param (
|
||||
[string]$OutputDir = $(throw "-OutputDir is required.")
|
||||
)
|
||||
|
||||
$localizations = @("en-US", "ru-RU", "es-EC", "pt-BR", "de-DE")
|
||||
foreach($localization in $localizations)
|
||||
{
|
||||
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
||||
$filePath = "{0}Localization\IW4MAdmin.{1}.json" -f $OutputDir, $localization
|
||||
$response = Invoke-WebRequest $url -UseBasicParsing
|
||||
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
set SolutionDir=%1
|
||||
set ProjectDir=%2
|
||||
set TargetDir=%3
|
||||
set OutDir=%4
|
||||
set Version=%5
|
||||
|
||||
echo Copying dependency configs
|
||||
copy "%SolutionDir%WebfrontCore\%OutDir%*.deps.json" "%TargetDir%"
|
||||
copy "%SolutionDir%SharedLibraryCore\%OutDir%*.deps.json" "%TargetDir%"
|
||||
|
||||
if not exist "%TargetDir%Plugins" (
|
||||
echo "Making plugin dir"
|
||||
md "%TargetDir%Plugins"
|
||||
)
|
||||
|
||||
xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\"
|
||||
del "%TargetDir%Plugins\SQLite*"
|
50
Application/BuildScripts/PostBuild.sh
Normal file
50
Application/BuildScripts/PostBuild.sh
Normal file
@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
PublishDir="$1"
|
||||
SourceDir="$2"
|
||||
|
||||
if [ -z "$PublishDir" ] || [ -z "$SourceDir" ]; then
|
||||
echo "Usage: $0 <PublishDir> <SourceDir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deleting extra runtime files"
|
||||
declare -a runtimes=("linux-arm" "linux-arm64" "linux-armel" "osx" "osx-x64" "win-arm" "win-arm64" "alpine-x64" "linux-musl-x64")
|
||||
for runtime in "${runtimes[@]}"; do
|
||||
if [ -d "$PublishDir/runtimes/$runtime" ]; then
|
||||
rm -rf "$PublishDir/runtimes/$runtime"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Deleting misc files"
|
||||
if [ -f "$PublishDir/web.config" ]; then rm "$PublishDir/web.config"; fi
|
||||
if [ -f "$PublishDir/libman.json" ]; then rm "$PublishDir/libman.json"; fi
|
||||
rm -f "$PublishDir"/*.exe
|
||||
rm -f "$PublishDir"/*.pdb
|
||||
rm -f "$PublishDir"/IW4MAdmin
|
||||
|
||||
echo "Setting up default folders"
|
||||
mkdir -p "$PublishDir/Plugins"
|
||||
mkdir -p "$PublishDir/Configuration"
|
||||
mv "$PublishDir/DefaultSettings.json" "$PublishDir/Configuration/"
|
||||
|
||||
mkdir -p "$PublishDir/Lib"
|
||||
rm -f "$PublishDir/Microsoft.CodeAnalysis*.dll"
|
||||
mv "$PublishDir"/*.dll "$PublishDir/Lib/"
|
||||
mv "$PublishDir"/*.json "$PublishDir/Lib/"
|
||||
mv "$PublishDir/runtimes" "$PublishDir/Lib/runtimes"
|
||||
mv "$PublishDir/ru" "$PublishDir/Lib/ru"
|
||||
mv "$PublishDir/de" "$PublishDir/Lib/de"
|
||||
mv "$PublishDir/pt" "$PublishDir/Lib/pt"
|
||||
mv "$PublishDir/es" "$PublishDir/Lib/es"
|
||||
rm -rf "$PublishDir/cs"
|
||||
rm -rf "$PublishDir/fr"
|
||||
rm -rf "$PublishDir/it"
|
||||
rm -rf "$PublishDir/ja"
|
||||
rm -rf "$PublishDir/ko"
|
||||
rm -rf "$PublishDir/pl"
|
||||
rm -rf "$PublishDir/pt-BR"
|
||||
rm -rf "$PublishDir/tr"
|
||||
rm -rf "$PublishDir/zh-Hans"
|
||||
rm -rf "$PublishDir/zh-Hant"
|
||||
if [ -d "$PublishDir/refs" ]; then mv "$PublishDir/refs" "$PublishDir/Lib/refs"; fi
|
@ -1,67 +0,0 @@
|
||||
set PublishDir=%1
|
||||
set SourceDir=%2
|
||||
SET COPYCMD=/Y
|
||||
|
||||
echo deleting extra runtime files
|
||||
if exist "%PublishDir%\runtimes\linux-arm" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\linux-arm'
|
||||
if exist "%PublishDir%\runtimes\linux-arm64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\linux-arm64'
|
||||
if exist "%PublishDir%\runtimes\linux-armel" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\linux-armel'
|
||||
if exist "%PublishDir%\runtimes\osx" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\osx'
|
||||
if exist "%PublishDir%\runtimes\osx-x64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\osx-x64'
|
||||
if exist "%PublishDir%\runtimes\win-arm" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\win-arm'
|
||||
if exist "%PublishDir%\runtimes\win-arm64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\win-arm64'
|
||||
if exist "%PublishDir%\runtimes\alpine-x64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\alpine-x64'
|
||||
if exist "%PublishDir%\runtimes\linux-musl-x64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\linux-musl-x64'
|
||||
|
||||
echo deleting misc files
|
||||
if exist "%PublishDir%\web.config" del "%PublishDir%\web.config"
|
||||
if exist "%PublishDir%\libman.json" del "%PublishDir%\libman.json"
|
||||
del "%PublishDir%\*.exe"
|
||||
del "%PublishDir%\*.pdb"
|
||||
|
||||
echo setting up default folders
|
||||
if not exist "%PublishDir%\Configuration" md "%PublishDir%\Configuration"
|
||||
move "%PublishDir%\DefaultSettings.json" "%PublishDir%\Configuration\"
|
||||
if not exist "%PublishDir%\Lib\" md "%PublishDir%\Lib\"
|
||||
del "%PublishDir%\Microsoft.CodeAnalysis*.dll" /F /Q
|
||||
move "%PublishDir%\*.dll" "%PublishDir%\Lib\"
|
||||
move "%PublishDir%\*.json" "%PublishDir%\Lib\"
|
||||
move "%PublishDir%\runtimes" "%PublishDir%\Lib\runtimes"
|
||||
move "%PublishDir%\ru" "%PublishDir%\Lib\ru"
|
||||
move "%PublishDir%\de" "%PublishDir%\Lib\de"
|
||||
move "%PublishDir%\pt" "%PublishDir%\Lib\pt"
|
||||
move "%PublishDir%\es" "%PublishDir%\Lib\es"
|
||||
rmdir /Q /S "%PublishDir%\cs"
|
||||
rmdir /Q /S "%PublishDir%\fr"
|
||||
rmdir /Q /S "%PublishDir%\it"
|
||||
rmdir /Q /S "%PublishDir%\ja"
|
||||
rmdir /Q /S "%PublishDir%\ko"
|
||||
rmdir /Q /S "%PublishDir%\pl"
|
||||
rmdir /Q /S "%PublishDir%\pt-BR"
|
||||
rmdir /Q /S "%PublishDir%\tr"
|
||||
rmdir /Q /S "%PublishDir%\zh-Hans"
|
||||
rmdir /Q /S "%PublishDir%\zh-Hant"
|
||||
if exist "%PublishDir%\refs" move "%PublishDir%\refs" "%PublishDir%\Lib\refs"
|
||||
|
||||
echo making start scripts
|
||||
@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%PublishDir%\StartIW4MAdmin.cmd"
|
||||
@(echo #!/bin/bash&& echo export DOTNET_CLI_TELEMETRY_OPTOUT=1&& echo dotnet Lib/IW4MAdmin.dll) > "%PublishDir%\StartIW4MAdmin.sh"
|
||||
|
||||
echo copying update scripts
|
||||
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.ps1" "%PublishDir%\UpdateIW4MAdmin.ps1"
|
||||
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.sh" "%PublishDir%\UpdateIW4MAdmin.sh"
|
||||
|
||||
echo moving front-end library dependencies
|
||||
if not exist "%PublishDir%\wwwroot\font" mkdir "%PublishDir%\wwwroot\font"
|
||||
move "WebfrontCore\wwwroot\lib\open-iconic\font\fonts\*.*" "%PublishDir%\wwwroot\font\"
|
||||
if exist "%PublishDir%\wwwroot\lib" rd /s /q "%PublishDir%\wwwroot\lib"
|
||||
if not exist "%PublishDir%\wwwroot\css" mkdir "%PublishDir%\wwwroot\css"
|
||||
move "WebfrontCore\wwwroot\css\global.min.css" "%PublishDir%\wwwroot\css\global.min.css"
|
||||
if not exist "%PublishDir%\wwwroot\js" mkdir "%PublishDir%\wwwroot\js"
|
||||
move "%SourceDir%\WebfrontCore\wwwroot\js\global.min.js" "%PublishDir%\wwwroot\js\global.min.js"
|
||||
if not exist "%PublishDir%\wwwroot\images" mkdir "%PublishDir%\wwwroot\images"
|
||||
xcopy "%SourceDir%\WebfrontCore\wwwroot\images" "%PublishDir%\wwwroot\images" /E /H /C /I
|
||||
|
||||
|
||||
echo setting permissions...
|
||||
cacls "%PublishDir%" /t /e /p Everyone:F
|
@ -1,6 +0,0 @@
|
||||
set SolutionDir=%1
|
||||
set ProjectDir=%2
|
||||
set TargetDir=%3
|
||||
|
||||
echo D | xcopy "%SolutionDir%Plugins\ScriptPlugins\*.js" "%TargetDir%Plugins" /y
|
||||
powershell -File "%ProjectDir%BuildScripts\DownloadTranslations.ps1" %TargetDir%
|
59
Application/BuildScripts/PreBuild.ps1
Normal file
59
Application/BuildScripts/PreBuild.ps1
Normal file
@ -0,0 +1,59 @@
|
||||
param ( [string]$SolutionDir, [string]$OutputDir )
|
||||
|
||||
if (-not (Test-Path "$SolutionDir/WebfrontCore/wwwroot/font")) {
|
||||
Write-Output "restoring web dependencies"
|
||||
dotnet tool install Microsoft.Web.LibraryManager.Cli --global
|
||||
Set-Location "$SolutionDir/WebfrontCore"
|
||||
libman restore
|
||||
Set-Location $SolutionDir
|
||||
Copy-Item -Recurse -Force -Path "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/fonts" "$SolutionDir/WebfrontCore/wwwroot/font"
|
||||
}
|
||||
|
||||
if (-not (Test-Path "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss")) {
|
||||
Write-Output "load external resources"
|
||||
New-Item -ItemType Directory -Force -Path "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css"
|
||||
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss" -OutFile "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss"
|
||||
(Get-Content "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss") -replace '../fonts/', '/font/' | Set-Content "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss"
|
||||
}
|
||||
|
||||
Write-Output "compiling scss files"
|
||||
|
||||
dotnet tool install Excubo.WebCompiler --global
|
||||
webcompiler -r "$SolutionDir/WebfrontCore/wwwroot/css/src" -o WebfrontCore/wwwroot/css/ -m disable -z disable
|
||||
webcompiler "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" -o "$SolutionDir/WebfrontCore/wwwroot/css/" -m disable -z disable
|
||||
|
||||
if (-not (Test-Path "$SolutionDir/bundle/dotnet-bundle.dll")) {
|
||||
New-Item -ItemType Directory -Force -Path "$SolutionDir/bundle"
|
||||
Write-Output "getting dotnet bundle"
|
||||
Invoke-WebRequest -Uri "https://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip" -OutFile "$SolutionDir/bundle/dotnet-bundle.zip"
|
||||
Write-Output "unzipping download"
|
||||
Expand-Archive -Path "$SolutionDir/bundle/dotnet-bundle.zip" -DestinationPath "$SolutionDir/bundle" -Force
|
||||
}
|
||||
|
||||
Write-Output "executing dotnet-bundle"
|
||||
Set-Location "$SolutionDir/bundle"
|
||||
dotnet "dotnet-bundle.dll" clean "$SolutionDir/WebfrontCore/bundleconfig.json"
|
||||
dotnet "dotnet-bundle.dll" "$SolutionDir/WebfrontCore/bundleconfig.json"
|
||||
Set-Location $SolutionDir
|
||||
|
||||
New-Item -ItemType Directory -Force -Path "$SolutionDir/BUILD/Plugins"
|
||||
Write-Output "building plugins"
|
||||
Set-Location "$SolutionDir/Plugins"
|
||||
Get-ChildItem -Recurse -Filter *.csproj | ForEach-Object { dotnet publish $_.FullName -o "$SolutionDir/BUILD/Plugins" --no-restore }
|
||||
Set-Location $SolutionDir
|
||||
|
||||
if (-not (Test-Path "$OutputDir/Localization")) {
|
||||
Write-Output "downloading translations"
|
||||
New-Item -ItemType Directory -Force -Path "$OutputDir/Localization"
|
||||
$localizations = @("en-US", "ru-RU", "es-EC", "pt-BR", "de-DE")
|
||||
foreach ($localization in $localizations) {
|
||||
$url = "https://master.iw4.zip/localization/$localization"
|
||||
$filePath = "$OutputDir/Localization/IW4MAdmin.$localization.json"
|
||||
Invoke-WebRequest -Uri $url -OutFile $filePath -UseBasicParsing
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "copying plugins to build dir"
|
||||
New-Item -ItemType Directory -Force -Path "$OutputDir/Plugins"
|
||||
Copy-Item -Recurse -Force -Path "$SolutionDir/BUILD/Plugins/*.dll" -Destination "$OutputDir/Plugins/"
|
||||
Copy-Item -Recurse -Force -Path "$SolutionDir/Plugins/ScriptPlugins/*.js" -Destination "$OutputDir/Plugins/"
|
63
Application/BuildScripts/PreBuild.sh
Normal file
63
Application/BuildScripts/PreBuild.sh
Normal file
@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
SolutionDir="$1"
|
||||
OutputDir="$2"
|
||||
export PATH="$PATH:~/.dotnet/tools"
|
||||
|
||||
if [ ! -d "$SolutionDir/WebfrontCore/wwwroot/lib/font" ]; then
|
||||
echo restoring web dependencies
|
||||
dotnet tool install Microsoft.Web.LibraryManager.Cli --global
|
||||
cd "$SolutionDir/WebfrontCore" || exit
|
||||
libman restore
|
||||
cd "$SolutionDir" || exit
|
||||
cp -r "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/fonts" "$SolutionDir/WebfrontCore/wwwroot/font"
|
||||
fi
|
||||
|
||||
if [ ! -f "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" ]; then
|
||||
echo load external resources
|
||||
mkdir -p "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css"
|
||||
curl -o "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss
|
||||
sed -i 's#../fonts/#/font/#g' "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss"
|
||||
fi
|
||||
|
||||
echo compiling scss files
|
||||
|
||||
dotnet tool install Excubo.WebCompiler --global
|
||||
webcompiler -r "$SolutionDir/WebfrontCore/wwwroot/css/src" -o "$SolutionDir/WebfrontCore/wwwroot/css/" -m disable -z disable
|
||||
webcompiler "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" -o "$SolutionDir/WebfrontCore/wwwroot/css/" -m disable -z disable
|
||||
|
||||
if [ ! -f "$SolutionDir/bundle/dotnet-bundle.dll" ]; then
|
||||
mkdir -p "$SolutionDir/bundle"
|
||||
echo getting dotnet bundle
|
||||
curl -o "$SolutionDir/bundle/dotnet-bundle.zip" https://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip
|
||||
echo unzipping download
|
||||
unzip "$SolutionDir/bundle/dotnet-bundle.zip" -d "$SolutionDir/bundle"
|
||||
fi
|
||||
echo executing dotnet-bundle
|
||||
cd "$SolutionDir/bundle" || exit
|
||||
dotnet dotnet-bundle.dll clean "$SolutionDir/WebfrontCore/bundleconfig.json"
|
||||
dotnet dotnet-bundle.dll "$SolutionDir/WebfrontCore/bundleconfig.json"
|
||||
cd "$SolutionDir" || exit
|
||||
|
||||
mkdir -p "$SolutionDir/BUILD/Plugins"
|
||||
echo building plugins
|
||||
cd "$SolutionDir/Plugins" || exit
|
||||
find . -name "*.csproj" -print0 | xargs -0 -I {} dotnet publish {} -o "$SolutionDir/BUILD/Plugins" --no-restore
|
||||
cd "$SolutionDir" || exit
|
||||
|
||||
if [ ! -d "$OutputDir/Localization" ]; then
|
||||
echo downloading translations
|
||||
mkdir -p "$OutputDir/Localization"
|
||||
localizations=("en-US" "ru-RU" "es-EC" "pt-BR" "de-DE")
|
||||
for localization in "${localizations[@]}"
|
||||
do
|
||||
url="https://master.iw4.zip/localization/$localization"
|
||||
filePath="$OutputDir/Localization/IW4MAdmin.$localization.json"
|
||||
curl -s "$url" -o "$filePath"
|
||||
done
|
||||
fi
|
||||
|
||||
echo copying plugins to buld dir
|
||||
mkdir -p "$OutputDir/Plugins"
|
||||
cp -r "$SolutionDir/BUILD/Plugins/*.dll" "$OutputDir/Plugins/"
|
||||
cp -r "$SolutionDir/Plugins/ScriptPlugins/*.js" "$OutputDir/Plugins/"
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||
using Data.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Events.Game;
|
||||
using SharedLibraryCore.Interfaces.Events;
|
||||
using static System.Int32;
|
||||
using static SharedLibraryCore.Server;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
@ -21,16 +22,18 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly IGameScriptEventFactory _gameScriptEventFactory;
|
||||
private readonly Dictionary<ParserRegex, GameEvent.EventType> _regexMap;
|
||||
private readonly Dictionary<string, GameEvent.EventType> _eventTypeMap;
|
||||
|
||||
public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger,
|
||||
ApplicationConfiguration appConfig)
|
||||
ApplicationConfiguration appConfig, IGameScriptEventFactory gameScriptEventFactory)
|
||||
{
|
||||
_customEventRegistrations =
|
||||
new Dictionary<string, (string, Func<string, IEventParserConfiguration, GameEvent, GameEvent>)>();
|
||||
_logger = logger;
|
||||
_appConfig = appConfig;
|
||||
_gameScriptEventFactory = gameScriptEventFactory;
|
||||
|
||||
Configuration = new DynamicEventParserConfiguration(parserRegexFactory)
|
||||
{
|
||||
@ -183,14 +186,28 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
if (logLine.StartsWith("GSE;"))
|
||||
{
|
||||
return new GameScriptEvent
|
||||
var gscEvent = new GameScriptEvent
|
||||
{
|
||||
ScriptData = logLine,
|
||||
GameTime = gameTime,
|
||||
Source = GameEvent.EventSource.Log
|
||||
};
|
||||
return gscEvent;
|
||||
}
|
||||
|
||||
var split = logLine.Split(";", StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (split.Length > 1)
|
||||
{
|
||||
var createdEvent = _gameScriptEventFactory.Create(split[0], logLine.Replace(split[0], ""));
|
||||
if (createdEvent is not null)
|
||||
{
|
||||
createdEvent.ParseArguments();
|
||||
return createdEvent as GameEventV2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (eventKey is null || !_customEventRegistrations.ContainsKey(eventKey))
|
||||
{
|
||||
return GenerateDefaultEvent(logLine, gameTime);
|
||||
@ -218,12 +235,13 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
return GenerateDefaultEvent(logLine, gameTime);
|
||||
}
|
||||
|
||||
private static GameEvent GenerateDefaultEvent(string logLine, long gameTime)
|
||||
private static GameLogEvent GenerateDefaultEvent(string logLine, long gameTime)
|
||||
{
|
||||
return new GameEvent
|
||||
return new GameLogEvent
|
||||
{
|
||||
Type = GameEvent.EventType.Unknown,
|
||||
Data = logLine,
|
||||
LogLine = logLine,
|
||||
Origin = Utilities.IW4MAdminClient(),
|
||||
Target = Utilities.IW4MAdminClient(),
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||
|
@ -1,5 +1,6 @@
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Interfaces.Events;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
@ -10,7 +11,9 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
/// </summary>
|
||||
sealed internal class DynamicEventParser : BaseEventParser
|
||||
{
|
||||
public DynamicEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, ApplicationConfiguration appConfig) : base(parserRegexFactory, logger, appConfig)
|
||||
public DynamicEventParser(IParserRegexFactory parserRegexFactory, ILogger logger,
|
||||
ApplicationConfiguration appConfig, IGameScriptEventFactory gameScriptEventFactory) : base(
|
||||
parserRegexFactory, logger, appConfig, gameScriptEventFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Data.Models.Client;
|
||||
using Data.Models.Client.Stats;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Extensions;
|
||||
|
||||
@ -25,4 +27,16 @@ public static class ScriptPluginExtensions
|
||||
{
|
||||
return set.Where(stat => clientIds.Contains(stat.ClientId) && stat.ServerId == (long)serverId).ToList();
|
||||
}
|
||||
|
||||
public static EFClient GetClientByNumber(this IGameServer server, int clientNumber) =>
|
||||
server.ConnectedClients.FirstOrDefault(client => client.ClientNumber == clientNumber);
|
||||
|
||||
public static EFClient GetClientByGuid(this IGameServer server, string clientGuid) =>
|
||||
server.ConnectedClients.FirstOrDefault(client => client?.GuidString == clientGuid?.Trim().ToLower());
|
||||
|
||||
public static EFClient GetClientByXuid(this IGameServer server, string clientGuid) =>
|
||||
server.ConnectedClients.FirstOrDefault(client => client?.XuidString == clientGuid?.Trim().ToLower());
|
||||
|
||||
public static EFClient GetClientByDecimalGuid(this IGameServer server, string clientGuid) =>
|
||||
server.ConnectedClients.FirstOrDefault(client => client.NetworkId.ToString() == clientGuid?.Trim().ToLower());
|
||||
}
|
||||
|
39
Application/Factories/GameScriptEventFactory.cs
Normal file
39
Application/Factories/GameScriptEventFactory.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SharedLibraryCore.Interfaces.Events;
|
||||
|
||||
namespace IW4MAdmin.Application.Factories;
|
||||
|
||||
public class GameScriptEventFactory : IGameScriptEventFactory
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly Dictionary<string, Type> _gameScriptEventMap;
|
||||
|
||||
public GameScriptEventFactory(IServiceProvider serviceProvider, IEnumerable<IGameScriptEvent> gameScriptEventTypes)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
_gameScriptEventMap = gameScriptEventTypes
|
||||
.ToLookup(kvp => kvp.EventName ?? kvp.GetType().Name.Replace("ScriptEvent", ""))
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.First().GetType());
|
||||
}
|
||||
|
||||
public IGameScriptEvent Create(string eventType, string logData)
|
||||
{
|
||||
if (string.IsNullOrEmpty(eventType) || !_gameScriptEventMap.TryGetValue(eventType, out var matchedType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var newEvent = _serviceProvider.GetRequiredService(matchedType) as IGameScriptEvent;
|
||||
|
||||
if (newEvent is not null)
|
||||
{
|
||||
newEvent.ScriptData = logData;
|
||||
}
|
||||
|
||||
return newEvent;
|
||||
}
|
||||
}
|
@ -71,7 +71,6 @@ public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHand
|
||||
await using var fileStream = File.OpenRead(_path);
|
||||
readConfiguration =
|
||||
await JsonSerializer.DeserializeAsync<TConfigurationType>(fileStream, _serializerOptions);
|
||||
await fileStream.DisposeAsync();
|
||||
_watcher.Register(_path, FileUpdated);
|
||||
|
||||
if (readConfiguration is null)
|
||||
@ -131,7 +130,6 @@ public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHand
|
||||
|
||||
await using var fileStream = File.Create(_path);
|
||||
await JsonSerializer.SerializeAsync(fileStream, configuration, _serializerOptions);
|
||||
await fileStream.DisposeAsync();
|
||||
_configurationInstance = configuration;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -155,7 +153,6 @@ public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHand
|
||||
await using var fileStream = File.OpenRead(_path);
|
||||
var readConfiguration =
|
||||
await JsonSerializer.DeserializeAsync<TConfigurationType>(fileStream, _serializerOptions);
|
||||
await fileStream.DisposeAsync();
|
||||
|
||||
if (readConfiguration is null)
|
||||
{
|
||||
|
@ -20,7 +20,6 @@ public sealed class ConfigurationWatcher : IDisposable
|
||||
};
|
||||
|
||||
_watcher.Changed += WatcherOnChanged;
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -31,30 +30,28 @@ public sealed class ConfigurationWatcher : IDisposable
|
||||
|
||||
public void Register(string fileName, Action<string> fileUpdated)
|
||||
{
|
||||
if (_registeredActions.ContainsKey(fileName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_registeredActions.Add(fileName, fileUpdated);
|
||||
_registeredActions.TryAdd(fileName, fileUpdated);
|
||||
}
|
||||
|
||||
public void Unregister(string fileName)
|
||||
{
|
||||
if (_registeredActions.ContainsKey(fileName))
|
||||
{
|
||||
_registeredActions.Remove(fileName);
|
||||
}
|
||||
_registeredActions.Remove(fileName);
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
private void WatcherOnChanged(object sender, FileSystemEventArgs eventArgs)
|
||||
{
|
||||
if (!_registeredActions.ContainsKey(eventArgs.FullPath) || eventArgs.ChangeType != WatcherChangeTypes.Changed ||
|
||||
if (!_registeredActions.TryGetValue(eventArgs.FullPath, out var value) ||
|
||||
eventArgs.ChangeType != WatcherChangeTypes.Changed ||
|
||||
new FileInfo(eventArgs.FullPath).Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_registeredActions[eventArgs.FullPath].Invoke(eventArgs.FullPath);
|
||||
value.Invoke(eventArgs.FullPath);
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != Utilities.WORLD_ID)
|
||||
{
|
||||
gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId);;
|
||||
gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId);
|
||||
}
|
||||
|
||||
if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target)
|
||||
|
@ -1,5 +1,4 @@
|
||||
using IW4MAdmin.Application.API.GameLogServer;
|
||||
using RestEase;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
@ -7,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Refit;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
@ -25,7 +25,7 @@ namespace IW4MAdmin.Application.IO
|
||||
public GameLogReaderHttp(Uri[] gameLogServerUris, IEventParser parser, ILogger<GameLogReaderHttp> logger)
|
||||
{
|
||||
_eventParser = parser;
|
||||
_logServerApi = RestClient.For<IGameLogServer>(gameLogServerUris[0].ToString());
|
||||
_logServerApi = RestService.For<IGameLogServer>(gameLogServerUris[0].ToString());
|
||||
_safeLogPath = gameLogServerUris[1].LocalPath.ToBase64UrlSafeString();
|
||||
_logger = logger;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ using Serilog.Context;
|
||||
using static SharedLibraryCore.Database.Models.EFClient;
|
||||
using Data.Models;
|
||||
using Data.Models.Server;
|
||||
using Humanizer;
|
||||
using IW4MAdmin.Application.Alerts;
|
||||
using IW4MAdmin.Application.Commands;
|
||||
using IW4MAdmin.Application.Plugin.Script;
|
||||
@ -193,18 +194,54 @@ namespace IW4MAdmin
|
||||
Command command = null;
|
||||
if (E.Type == GameEvent.EventType.Command)
|
||||
{
|
||||
if (E.Origin is not null)
|
||||
{
|
||||
var canExecute = true;
|
||||
|
||||
if (E.Origin.CommandExecutionAttempts > 0 && E.Origin.Level < Permission.Trusted)
|
||||
{
|
||||
var remainingTimeout =
|
||||
E.Origin.LastCommandExecutionAttempt +
|
||||
Utilities.GetExponentialBackoffDelay(E.Origin.CommandExecutionAttempts) -
|
||||
DateTimeOffset.UtcNow;
|
||||
|
||||
if (remainingTimeout.TotalSeconds > 0)
|
||||
{
|
||||
if (E.Origin.CommandExecutionAttempts < 2 ||
|
||||
E.Origin.CommandExecutionAttempts % 5 == 0)
|
||||
{
|
||||
E.Origin.Tell(_translationLookup["COMMANDS_BACKOFF_MESSAGE"]
|
||||
.FormatExt(remainingTimeout.Humanize()));
|
||||
}
|
||||
|
||||
canExecute = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
E.Origin.CommandExecutionAttempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
E.Origin.LastCommandExecutionAttempt = DateTimeOffset.UtcNow;
|
||||
E.Origin.CommandExecutionAttempts++;
|
||||
|
||||
if (!canExecute)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
command = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E, Manager.GetApplicationSettings().Configuration(), _commandConfiguration);
|
||||
}
|
||||
|
||||
catch (CommandException e)
|
||||
{
|
||||
ServerLogger.LogWarning(e, "Error validating command from event {@Event}",
|
||||
new { E.Type, E.Data, E.Message, E.Subtype, E.IsRemote, E.CorrelationId });
|
||||
E.FailReason = GameEvent.EventFailReason.Invalid;
|
||||
}
|
||||
|
||||
|
||||
if (command != null)
|
||||
{
|
||||
E.Extra = command;
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
@ -59,11 +60,11 @@ namespace IW4MAdmin.Application.Localization
|
||||
|
||||
var localizationDict = new Dictionary<string, string>();
|
||||
|
||||
foreach (string filePath in localizationFiles)
|
||||
foreach (var filePath in localizationFiles)
|
||||
{
|
||||
var localizationContents = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
var eachLocalizationFile = Newtonsoft.Json.JsonConvert.DeserializeObject<SharedLibraryCore.Localization.Layout>(localizationContents);
|
||||
if (eachLocalizationFile == null)
|
||||
var eachLocalizationFile = JsonSerializer.Deserialize<SharedLibraryCore.Localization.Layout>(localizationContents);
|
||||
if (eachLocalizationFile is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -72,7 +73,7 @@ namespace IW4MAdmin.Application.Localization
|
||||
{
|
||||
if (!localizationDict.TryAdd(item.Key, item.Value))
|
||||
{
|
||||
logger.LogError("Could not add locale string {key} to localization", item.Key);
|
||||
logger.LogError("Could not add locale string {Key} to localization", item.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ using IW4MAdmin.Application.Meta;
|
||||
using IW4MAdmin.Application.Migration;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using RestEase;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
@ -39,6 +38,8 @@ using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using IW4MAdmin.Plugins.Stats.Client.Abstractions;
|
||||
using IW4MAdmin.Plugins.Stats.Client;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Refit;
|
||||
using SharedLibraryCore.Interfaces.Events;
|
||||
using Stats.Client.Abstractions;
|
||||
using Stats.Client;
|
||||
using Stats.Config;
|
||||
@ -94,15 +95,6 @@ namespace IW4MAdmin.Application
|
||||
Console.WriteLine($" Version {Utilities.GetVersionAsString()}");
|
||||
Console.WriteLine("=====================================================");
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("!!!! IMPORTANT !!!!");
|
||||
Console.WriteLine("The next update of IW4MAdmin will require .NET 8.");
|
||||
Console.WriteLine("This is a breaking change!");
|
||||
Console.WriteLine(
|
||||
"Please update the ASP.NET Core Runtime: https://dotnet.microsoft.com/en-us/download/dotnet/8.0");
|
||||
Console.WriteLine("!!!!!!!!!!!!!!!!!!!");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
await LaunchAsync();
|
||||
}
|
||||
|
||||
@ -451,12 +443,12 @@ namespace IW4MAdmin.Application
|
||||
var masterUri = Utilities.IsDevelopment
|
||||
? new Uri("http://127.0.0.1:8080")
|
||||
: appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl;
|
||||
var httpClient = new HttpClient
|
||||
var httpClient = new HttpClient(new HttpClientHandler {AllowAutoRedirect = true})
|
||||
{
|
||||
BaseAddress = masterUri,
|
||||
Timeout = TimeSpan.FromSeconds(15)
|
||||
};
|
||||
var masterRestClient = RestClient.For<IMasterApi>(httpClient);
|
||||
var masterRestClient = RestService.For<IMasterApi>(httpClient);
|
||||
var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig);
|
||||
|
||||
if (appConfig == null)
|
||||
@ -469,10 +461,7 @@ namespace IW4MAdmin.Application
|
||||
// register override level names
|
||||
foreach (var (key, value) in appConfig.OverridePermissionLevelNames)
|
||||
{
|
||||
if (!Utilities.PermissionLevelOverrides.ContainsKey(key))
|
||||
{
|
||||
Utilities.PermissionLevelOverrides.Add(key, value);
|
||||
}
|
||||
Utilities.PermissionLevelOverrides.TryAdd(key, value);
|
||||
}
|
||||
|
||||
// build the dependency list
|
||||
@ -539,6 +528,7 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton(new ConfigurationWatcher())
|
||||
.AddSingleton(typeof(IConfigurationHandlerV2<>), typeof(BaseConfigurationHandlerV2<>))
|
||||
.AddSingleton<IScriptPluginFactory, ScriptPluginFactory>()
|
||||
.AddSingleton<IGameScriptEventFactory, GameScriptEventFactory>()
|
||||
.AddSingleton(translationLookup)
|
||||
.AddDatabaseContextOptions(appConfig);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using RestEase;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Helpers;
|
||||
@ -9,6 +8,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Refit;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
@ -28,6 +28,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
private readonly int _apiVersion = 1;
|
||||
private bool _firstHeartBeat = true;
|
||||
private static readonly TimeSpan Interval = TimeSpan.FromSeconds(30);
|
||||
private string _authorizationToken;
|
||||
|
||||
public MasterCommunication(ILogger<MasterCommunication> logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager)
|
||||
{
|
||||
@ -128,7 +129,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
Id = _appConfig.Id
|
||||
});
|
||||
|
||||
_apiInstance.AuthorizationToken = $"Bearer {token.AccessToken}";
|
||||
_authorizationToken = $"Bearer {token.AccessToken}";
|
||||
}
|
||||
|
||||
var instance = new ApiInstance
|
||||
@ -153,22 +154,22 @@ namespace IW4MAdmin.Application.Misc
|
||||
WebfrontUrl = _appConfig.WebfrontUrl
|
||||
};
|
||||
|
||||
Response<ResultMessage> response;
|
||||
IApiResponse<ResultMessage> response;
|
||||
|
||||
if (_firstHeartBeat)
|
||||
{
|
||||
response = await _apiInstance.AddInstance(instance);
|
||||
response = await _apiInstance.AddInstance(instance, _authorizationToken);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
response = await _apiInstance.UpdateInstance(instance.Id, instance);
|
||||
response = await _apiInstance.UpdateInstance(instance.Id, instance, _authorizationToken);
|
||||
_firstHeartBeat = false;
|
||||
}
|
||||
|
||||
if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK)
|
||||
if (response.StatusCode != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
_logger.LogWarning("Non success response code from master is {StatusCode}, message is {Message}", response.ResponseMessage.StatusCode, response.StringContent);
|
||||
_logger.LogWarning("Non success response code from master is {StatusCode}, message is {Message}", response.StatusCode, response.Error?.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
var decryptedContent = new byte[encryptedContent.Length];
|
||||
|
||||
var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id), IterationCount, HashAlgorithmName.SHA512);
|
||||
var encryption = new AesGcm(keyGen.GetBytes(KeyLength));
|
||||
var encryption = new AesGcm(keyGen.GetBytes(KeyLength),TagLength);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -1,150 +1,255 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Data.Models;
|
||||
using static SharedLibraryCore.Database.Models.EFClient;
|
||||
using static SharedLibraryCore.GameEvent;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
namespace IW4MAdmin.Application.Misc;
|
||||
|
||||
public class IPAddressConverter : JsonConverter<IPAddress>
|
||||
{
|
||||
class IPAddressConverter : JsonConverter
|
||||
public override IPAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return (objectType == typeof(IPAddress));
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
return IPAddress.Parse((string)reader.Value);
|
||||
}
|
||||
var ipAddressString = reader.GetString();
|
||||
return IPAddress.Parse(ipAddressString);
|
||||
}
|
||||
|
||||
class IPEndPointConverter : JsonConverter
|
||||
public override void Write(Utf8JsonWriter writer, IPAddress value, JsonSerializerOptions options)
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return (objectType == typeof(IPEndPoint));
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
IPEndPoint ep = (IPEndPoint)value;
|
||||
JObject jo = new JObject();
|
||||
jo.Add("Address", JToken.FromObject(ep.Address, serializer));
|
||||
jo.Add("Port", ep.Port);
|
||||
jo.WriteTo(writer);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
JObject jo = JObject.Load(reader);
|
||||
IPAddress address = jo["Address"].ToObject<IPAddress>(serializer);
|
||||
int port = (int)jo["Port"];
|
||||
return new IPEndPoint(address, port);
|
||||
}
|
||||
}
|
||||
|
||||
class ClientEntityConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType) => objectType == typeof(EFClient);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType,object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var jsonObject = JObject.Load(reader);
|
||||
|
||||
return new EFClient
|
||||
{
|
||||
NetworkId = (long)jsonObject["NetworkId"],
|
||||
ClientNumber = (int)jsonObject["ClientNumber"],
|
||||
State = Enum.Parse<ClientState>(jsonObject["state"].ToString()),
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
IPAddress = (int?)jsonObject["IPAddress"],
|
||||
Name = jsonObject["Name"].ToString()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var client = value as EFClient;
|
||||
var jsonObject = new JObject
|
||||
{
|
||||
{ "NetworkId", client.NetworkId },
|
||||
{ "ClientNumber", client.ClientNumber },
|
||||
{ "IPAddress", client.CurrentAlias?.IPAddress },
|
||||
{ "Name", client.CurrentAlias?.Name },
|
||||
{ "State", (int)client.State }
|
||||
};
|
||||
|
||||
jsonObject.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
class GameEventConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType) =>objectType == typeof(GameEvent);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var jsonObject = JObject.Load(reader);
|
||||
|
||||
return new GameEvent
|
||||
{
|
||||
Type = Enum.Parse<EventType>(jsonObject["Type"].ToString()),
|
||||
Subtype = jsonObject["Subtype"]?.ToString(),
|
||||
Source = Enum.Parse<EventSource>(jsonObject["Source"].ToString()),
|
||||
RequiredEntity = Enum.Parse<EventRequiredEntity>(jsonObject["RequiredEntity"].ToString()),
|
||||
Data = jsonObject["Data"].ToString(),
|
||||
Message = jsonObject["Message"].ToString(),
|
||||
GameTime = (int?)jsonObject["GameTime"],
|
||||
Origin = jsonObject["Origin"]?.ToObject<EFClient>(serializer),
|
||||
Target = jsonObject["Target"]?.ToObject<EFClient>(serializer),
|
||||
ImpersonationOrigin = jsonObject["ImpersonationOrigin"]?.ToObject<EFClient>(serializer),
|
||||
IsRemote = (bool)jsonObject["IsRemote"],
|
||||
Extra = null, // fix
|
||||
Time = (DateTime)jsonObject["Time"],
|
||||
IsBlocking = (bool)jsonObject["IsBlocking"]
|
||||
};
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var gameEvent = value as GameEvent;
|
||||
|
||||
var jsonObject = new JObject
|
||||
{
|
||||
{ "Type", (int)gameEvent.Type },
|
||||
{ "Subtype", gameEvent.Subtype },
|
||||
{ "Source", (int)gameEvent.Source },
|
||||
{ "RequiredEntity", (int)gameEvent.RequiredEntity },
|
||||
{ "Data", gameEvent.Data },
|
||||
{ "Message", gameEvent.Message },
|
||||
{ "GameTime", gameEvent.GameTime },
|
||||
{ "Origin", gameEvent.Origin != null ? JToken.FromObject(gameEvent.Origin, serializer) : null },
|
||||
{ "Target", gameEvent.Target != null ? JToken.FromObject(gameEvent.Target, serializer) : null },
|
||||
{ "ImpersonationOrigin", gameEvent.ImpersonationOrigin != null ? JToken.FromObject(gameEvent.ImpersonationOrigin, serializer) : null},
|
||||
{ "IsRemote", gameEvent.IsRemote },
|
||||
{ "Extra", gameEvent.Extra?.ToString() },
|
||||
{ "Time", gameEvent.Time },
|
||||
{ "IsBlocking", gameEvent.IsBlocking }
|
||||
};
|
||||
|
||||
jsonObject.WriteTo(writer);
|
||||
}
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public class IPEndPointConverter : JsonConverter<IPEndPoint>
|
||||
{
|
||||
public override IPEndPoint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
IPAddress address = null;
|
||||
var port = 0;
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
var propertyName = reader.GetString();
|
||||
reader.Read();
|
||||
switch (propertyName)
|
||||
{
|
||||
case "Address":
|
||||
var addressString = reader.GetString();
|
||||
address = IPAddress.Parse(addressString);
|
||||
break;
|
||||
case "Port":
|
||||
port = reader.GetInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonTokenType.EndObject)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new IPEndPoint(address, port);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, IPEndPoint value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("Address", value.Address.ToString());
|
||||
writer.WriteNumber("Port", value.Port);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public class ClientEntityConverter : JsonConverter<EFClient>
|
||||
{
|
||||
public override EFClient Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
long networkId = default;
|
||||
int clientNumber = default;
|
||||
ClientState state = default;
|
||||
var currentAlias = new EFAlias();
|
||||
int? ipAddress = null;
|
||||
string name = null;
|
||||
|
||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
var propertyName = reader.GetString();
|
||||
reader.Read(); // Advance to the value.
|
||||
switch (propertyName)
|
||||
{
|
||||
case "NetworkId":
|
||||
networkId = reader.GetInt64();
|
||||
break;
|
||||
case "ClientNumber":
|
||||
clientNumber = reader.GetInt32();
|
||||
break;
|
||||
case "State":
|
||||
state = (ClientState)reader.GetInt32();
|
||||
break;
|
||||
case "IPAddress":
|
||||
ipAddress = reader.TokenType != JsonTokenType.Null ? reader.GetInt32() : null;
|
||||
break;
|
||||
case "Name":
|
||||
name = reader.GetString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentAlias.IPAddress = ipAddress;
|
||||
currentAlias.Name = name;
|
||||
|
||||
return new EFClient
|
||||
{
|
||||
NetworkId = networkId,
|
||||
ClientNumber = clientNumber,
|
||||
State = state,
|
||||
CurrentAlias = currentAlias
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, EFClient value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WriteNumber("NetworkId", value.NetworkId);
|
||||
writer.WriteNumber("ClientNumber", value.ClientNumber);
|
||||
writer.WriteString("State", value.State.ToString());
|
||||
|
||||
if (value.CurrentAlias != null)
|
||||
{
|
||||
writer.WriteNumber("IPAddress", value.CurrentAlias.IPAddress ?? 0);
|
||||
writer.WriteString("Name", value.CurrentAlias.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNull("IPAddress");
|
||||
writer.WriteNull("Name");
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public class GameEventConverter : JsonConverter<GameEvent>
|
||||
{
|
||||
public override GameEvent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var gameEvent = new GameEvent();
|
||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
var propertyName = reader.GetString();
|
||||
reader.Read();
|
||||
switch (propertyName)
|
||||
{
|
||||
case "Type":
|
||||
gameEvent.Type = (EventType)reader.GetInt32();
|
||||
break;
|
||||
case "Subtype":
|
||||
gameEvent.Subtype = reader.GetString();
|
||||
break;
|
||||
case "Source":
|
||||
gameEvent.Source = (EventSource)reader.GetInt32();
|
||||
break;
|
||||
case "RequiredEntity":
|
||||
gameEvent.RequiredEntity = (EventRequiredEntity)reader.GetInt32();
|
||||
break;
|
||||
case "Data":
|
||||
gameEvent.Data = reader.GetString();
|
||||
break;
|
||||
case "Message":
|
||||
gameEvent.Message = reader.GetString();
|
||||
break;
|
||||
case "GameTime":
|
||||
gameEvent.GameTime = reader.TokenType != JsonTokenType.Null ? reader.GetInt32() : null;
|
||||
break;
|
||||
case "Origin":
|
||||
gameEvent.Origin = JsonSerializer.Deserialize<EFClient>(ref reader, options);
|
||||
break;
|
||||
case "Target":
|
||||
gameEvent.Target = JsonSerializer.Deserialize<EFClient>(ref reader, options);
|
||||
break;
|
||||
case "ImpersonationOrigin":
|
||||
gameEvent.ImpersonationOrigin = JsonSerializer.Deserialize<EFClient>(ref reader, options);
|
||||
break;
|
||||
case "IsRemote":
|
||||
gameEvent.IsRemote = reader.GetBoolean();
|
||||
break;
|
||||
case "Time":
|
||||
gameEvent.Time = reader.GetDateTime();
|
||||
break;
|
||||
case "IsBlocking":
|
||||
gameEvent.IsBlocking = reader.GetBoolean();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gameEvent;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, GameEvent value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WriteNumber("Type", (int)value.Type);
|
||||
writer.WriteString("Subtype", value.Subtype);
|
||||
writer.WriteNumber("Source", (int)value.Source);
|
||||
writer.WriteNumber("RequiredEntity", (int)value.RequiredEntity);
|
||||
writer.WriteString("Data", value.Data);
|
||||
writer.WriteString("Message", value.Message);
|
||||
if (value.GameTime.HasValue)
|
||||
{
|
||||
writer.WriteNumber("GameTime", value.GameTime.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNull("GameTime");
|
||||
}
|
||||
|
||||
if (value.Origin != null)
|
||||
{
|
||||
writer.WritePropertyName("Origin");
|
||||
JsonSerializer.Serialize(writer, value.Origin, options);
|
||||
}
|
||||
|
||||
if (value.Target != null)
|
||||
{
|
||||
writer.WritePropertyName("Target");
|
||||
JsonSerializer.Serialize(writer, value.Target, options);
|
||||
}
|
||||
|
||||
if (value.ImpersonationOrigin != null)
|
||||
{
|
||||
writer.WritePropertyName("ImpersonationOrigin");
|
||||
JsonSerializer.Serialize(writer, value.ImpersonationOrigin, options);
|
||||
}
|
||||
|
||||
writer.WriteBoolean("IsRemote", value.IsRemote);
|
||||
writer.WriteString("Time", value.Time.ToString("o"));
|
||||
writer.WriteBoolean("IsBlocking", value.IsBlocking);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ namespace IW4MAdmin.Application.Plugin
|
||||
try
|
||||
{
|
||||
_pluginSubscription ??= _masterApi
|
||||
.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||
.GetPluginSubscription(_appConfig.Id, _appConfig.SubscriptionId).Result;
|
||||
|
||||
return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription
|
||||
.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray());
|
||||
@ -185,7 +185,7 @@ namespace IW4MAdmin.Application.Plugin
|
||||
try
|
||||
{
|
||||
_pluginSubscription ??= _masterApi
|
||||
.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||
.GetPluginSubscription(_appConfig.Id, _appConfig.SubscriptionId).Result;
|
||||
|
||||
return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription
|
||||
.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray());
|
||||
|
@ -23,6 +23,7 @@ using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using Reference = Data.Models.Reference;
|
||||
|
||||
namespace IW4MAdmin.Application.Plugin.Script
|
||||
{
|
||||
|
@ -26,6 +26,7 @@ using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Interfaces.Events;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using JavascriptEngine = Jint.Engine;
|
||||
using Reference = Data.Models.Reference;
|
||||
|
||||
namespace IW4MAdmin.Application.Plugin.Script;
|
||||
|
||||
|
@ -91,9 +91,9 @@ public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequ
|
||||
? iqGroupedClientAliases.OrderByDescending(clientAlias => clientAlias.Key.LastConnection)
|
||||
: iqGroupedClientAliases.OrderBy(clientAlias => clientAlias.Key.LastConnection);
|
||||
|
||||
var clientIds = iqGroupedClientAliases.Select(g => g.Key.ClientId)
|
||||
var clientIds = await iqGroupedClientAliases.Select(g => g.Key.ClientId)
|
||||
.Skip(query.Offset)
|
||||
.Take(query.Count);
|
||||
.Take(query.Count).ToListAsync(); // todo: this change was for a pomelo limitation and may be addressed in future version
|
||||
|
||||
// this pulls in more records than we need, but it's more efficient than ordering grouped entities
|
||||
var clientLookups = await clientAliases
|
||||
|
@ -175,6 +175,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
|
||||
return new StatusResponse
|
||||
{
|
||||
RawResponse = response,
|
||||
Clients = ClientsFromStatus(response).ToArray(),
|
||||
Map = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusMap, Configuration.MapStatus),
|
||||
GameType = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusGametype, Configuration.GametypeStatus),
|
||||
|
@ -6,10 +6,11 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
/// <inheritdoc cref="IStatusResponse"/>
|
||||
public class StatusResponse : IStatusResponse
|
||||
{
|
||||
public string Map { get; set; }
|
||||
public string GameType { get; set; }
|
||||
public string Hostname { get; set; }
|
||||
public int? MaxClients { get; set; }
|
||||
public EFClient[] Clients { get; set; }
|
||||
public string Map { get; init; }
|
||||
public string GameType { get; init; }
|
||||
public string Hostname { get; init; }
|
||||
public int? MaxClients { get; init; }
|
||||
public EFClient[] Clients { get; init; }
|
||||
public string[] RawResponse { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
<Title>RaidMax.IW4MAdmin.Data</Title>
|
||||
@ -9,16 +9,20 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql" Version="6.0.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.2" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
|
||||
<PackageReference Include="Npgsql" Version="8.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
#pragma warning disable CS0659
|
||||
|
||||
namespace Data.Models
|
||||
{
|
||||
public class Vector3
|
||||
public class Vector3 : IParsable<Vector3>
|
||||
{
|
||||
[Key] public int Vector3Id { get; set; }
|
||||
public float X { get; protected set; }
|
||||
@ -78,7 +79,7 @@ namespace Data.Models
|
||||
|
||||
return Math.Sqrt((dx * dx) + (dy * dy));
|
||||
}
|
||||
|
||||
|
||||
public static double ViewAngleDistance(Vector3 a, Vector3 b, Vector3 c)
|
||||
{
|
||||
double dabX = Math.Abs(a.X - b.X);
|
||||
@ -111,5 +112,30 @@ namespace Data.Models
|
||||
public double Magnitude() => Math.Sqrt((X * X) + (Y * Y) + (Z * Z));
|
||||
|
||||
public double AngleBetween(Vector3 a) => Math.Acos(this.DotProduct(a) / (a.Magnitude() * this.Magnitude()));
|
||||
|
||||
public static Vector3 Parse(string s, IFormatProvider provider)
|
||||
{
|
||||
return Parse(s);
|
||||
}
|
||||
|
||||
public static bool TryParse(string s, IFormatProvider provider, out Vector3 result)
|
||||
{
|
||||
result = new Vector3();
|
||||
|
||||
try
|
||||
{
|
||||
var parsed = Parse(s);
|
||||
result.X = parsed.X;
|
||||
result.Y = parsed.Y;
|
||||
result.Z = parsed.Z;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
param (
|
||||
[string]$PublishDir = $(throw "-PublishDir is required.")
|
||||
)
|
||||
|
||||
md -Force ("{0}\Localization" -f $PublishDir)
|
||||
|
||||
$localizations = @("en-US", "ru-RU", "es-EC", "pt-BR", "de-DE")
|
||||
foreach($localization in $localizations)
|
||||
{
|
||||
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
||||
$filePath = "{0}\Localization\IW4MAdmin.{1}.json" -f $PublishDir, $localization
|
||||
$response = Invoke-WebRequest $url -UseBasicParsing
|
||||
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
||||
}
|
||||
|
||||
$versionInfo = (Get-Command ("{0}\IW4MAdmin.exe" -f $PublishDir)).FileVersionInfo
|
||||
$json = @{
|
||||
Major = $versionInfo.ProductMajorPart
|
||||
Minor = $versionInfo.ProductMinorPart
|
||||
Build = $versionInfo.ProductBuildPart
|
||||
Revision = $versionInfo.ProductPrivatePart
|
||||
}
|
||||
$json | ConvertTo-Json | Out-File -FilePath ("{0}\VersionInformation.json" -f $PublishDir) -Encoding ASCII
|
@ -1,264 +0,0 @@
|
||||
name: '$(Date:yyyy.M.d)$(Rev:.r)'
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- release/pre
|
||||
- master
|
||||
- develop
|
||||
paths:
|
||||
exclude:
|
||||
- '**/*.yml'
|
||||
- '*.yml'
|
||||
|
||||
pr: none
|
||||
|
||||
pool:
|
||||
vmImage: 'windows-2022'
|
||||
|
||||
variables:
|
||||
solution: 'IW4MAdmin.sln'
|
||||
buildPlatform: 'Any CPU'
|
||||
outputFolder: '$(Build.ArtifactStagingDirectory)\Publish\$(buildConfiguration)'
|
||||
releaseType: verified
|
||||
buildConfiguration: Stable
|
||||
isPreRelease: false
|
||||
|
||||
jobs:
|
||||
- job: Build_Deploy
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .NET Core 6 SDK'
|
||||
inputs:
|
||||
packageType: 'sdk'
|
||||
version: '6.0.x'
|
||||
includePreviewVersions: true
|
||||
|
||||
- task: NuGetToolInstaller@1
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Setup Pre-Release configuration'
|
||||
condition: or(eq(variables['Build.SourceBranch'], 'refs/heads/release/pre'), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
echo '##vso[task.setvariable variable=releaseType]prerelease'
|
||||
echo '##vso[task.setvariable variable=buildConfiguration]Prerelease'
|
||||
echo '##vso[task.setvariable variable=isPreRelease]true'
|
||||
failOnStderr: true
|
||||
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'Restore nuget packages'
|
||||
inputs:
|
||||
restoreSolution: '$(solution)'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Preload external resources'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
||||
md -Force lib\open-iconic\font\css
|
||||
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
|
||||
cd lib\open-iconic\font\css
|
||||
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build projects'
|
||||
inputs:
|
||||
solution: '$(solution)'
|
||||
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Build.BuildNumber) /p:PackageVersion=$(Build.BuildNumber)'
|
||||
platform: '$(buildPlatform)'
|
||||
configuration: '$(buildConfiguration)'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Bundle JS Files'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Write-Host 'Getting dotnet bundle'
|
||||
wget http://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip -o $(Build.Repository.LocalPath)\dotnet-bundle.zip
|
||||
Write-Host 'Unzipping download'
|
||||
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
|
||||
Write-Host 'Executing dotnet-bundle'
|
||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe clean $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Publish projects'
|
||||
inputs:
|
||||
command: 'publish'
|
||||
publishWebProjects: false
|
||||
projects: |
|
||||
**/WebfrontCore.csproj
|
||||
**/Application.csproj
|
||||
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Build.BuildNumber)'
|
||||
zipAfterPublish: false
|
||||
modifyOutputPath: false
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Run publish script 1'
|
||||
inputs:
|
||||
filePath: 'DeploymentFiles/PostPublish.ps1'
|
||||
arguments: '$(outputFolder)'
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||
|
||||
- task: BatchScript@1
|
||||
displayName: 'Run publish script 2'
|
||||
inputs:
|
||||
filename: 'Application\BuildScripts\PostPublish.bat'
|
||||
workingFolder: '$(Build.Repository.LocalPath)'
|
||||
arguments: '$(outputFolder) $(Build.Repository.LocalPath)'
|
||||
failOnStandardError: true
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Download dos2unix for line endings'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Convert Linux start script line endings'
|
||||
inputs:
|
||||
script: |
|
||||
echo changing to encoding for linux start script
|
||||
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
||||
dos2unix $(outputFolder)\UpdateIW4MAdmin.sh
|
||||
echo creating website version filename
|
||||
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Move script plugins into publish directory'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
||||
Contents: '*.js'
|
||||
TargetFolder: '$(outputFolder)\Plugins'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Move binary plugins into publish directory'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
||||
Contents: '*.dll'
|
||||
TargetFolder: '$(outputFolder)\Plugins'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Move webfront resources into publish directory'
|
||||
inputs:
|
||||
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
||||
failOnStderr: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Move gamescript files into publish directory'
|
||||
inputs:
|
||||
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||
failOnStderr: true
|
||||
|
||||
- task: ArchiveFiles@2
|
||||
displayName: 'Generate final zip file'
|
||||
inputs:
|
||||
rootFolderOrFile: '$(outputFolder)'
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
replaceExistingArchive: true
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
artifact: 'IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish artifact for analysis'
|
||||
inputs:
|
||||
targetPath: '$(outputFolder)'
|
||||
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
||||
publishLocation: 'pipeline'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish nuget package artifact'
|
||||
inputs:
|
||||
targetPath: '$(Build.Repository.LocalPath)/SharedLibraryCore/bin/$(buildConfiguration)/RaidMax.IW4MAdmin.SharedLibraryCore.$(Build.BuildNumber).nupkg'
|
||||
artifact: 'SharedLibraryCore.$(Build.BuildNumber).nupkg'
|
||||
publishLocation: 'pipeline'
|
||||
|
||||
- task: FtpUpload@2
|
||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
displayName: 'Upload zip file to website'
|
||||
inputs:
|
||||
credentialsOption: 'inputs'
|
||||
serverUrl: '$(FTPUrl)'
|
||||
username: '$(FTPUsername)'
|
||||
password: '$(FTPPassword)'
|
||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||
filePatterns: '*.zip'
|
||||
remoteDirectory: 'IW4MAdmin/Download'
|
||||
clean: false
|
||||
cleanContents: false
|
||||
preservePaths: false
|
||||
trustSSL: false
|
||||
|
||||
- task: FtpUpload@2
|
||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
displayName: 'Upload version info to website'
|
||||
inputs:
|
||||
credentialsOption: 'inputs'
|
||||
serverUrl: '$(FTPUrl)'
|
||||
username: '$(FTPUsername)'
|
||||
password: '$(FTPPassword)'
|
||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||
filePatterns: 'version_$(releaseType).txt'
|
||||
remoteDirectory: 'IW4MAdmin'
|
||||
clean: false
|
||||
cleanContents: false
|
||||
preservePaths: false
|
||||
trustSSL: false
|
||||
|
||||
- task: GitHubRelease@1
|
||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
displayName: 'Make GitHub release'
|
||||
inputs:
|
||||
gitHubConnection: 'github.com_RaidMax'
|
||||
repositoryName: 'RaidMax/IW4M-Admin'
|
||||
action: 'create'
|
||||
target: '$(Build.SourceVersion)'
|
||||
tagSource: 'userSpecifiedTag'
|
||||
tag: '$(Build.BuildNumber)-$(releaseType)'
|
||||
title: 'IW4MAdmin $(Build.BuildNumber) ($(releaseType))'
|
||||
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
||||
isPreRelease: $(isPreRelease)
|
||||
releaseNotesSource: 'inline'
|
||||
releaseNotesInline: 'Automated rolling release - changelog below. [Updating Instructions](https://github.com/RaidMax/IW4M-Admin/wiki/Getting-Started#updating)'
|
||||
changeLogCompareToRelease: 'lastNonDraftRelease'
|
||||
changeLogType: 'commitBased'
|
||||
|
||||
- task: PowerShell@2
|
||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
displayName: 'Update master version'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
$payload = @{
|
||||
'current-version-$(releaseType)' = '$(Build.BuildNumber)'
|
||||
'jwt-secret' = '$(JWTSecret)'
|
||||
} | ConvertTo-Json
|
||||
|
||||
|
||||
$params = @{
|
||||
Uri = 'http://api.raidmax.org:5000/version'
|
||||
Method = 'POST'
|
||||
Body = $payload
|
||||
ContentType = 'application/json'
|
||||
}
|
||||
|
||||
Invoke-RestMethod @params
|
@ -1,55 +0,0 @@
|
||||
name: '$(Date:yyyy.M.d)$(Rev:.r)'
|
||||
|
||||
pr: none
|
||||
|
||||
pool:
|
||||
vmImage: 'windows-2022'
|
||||
|
||||
variables:
|
||||
buildPlatform: 'Any CPU'
|
||||
outputFolder: '$(Build.ArtifactStagingDirectory)\Publish\$(buildConfiguration)'
|
||||
releaseType: verified
|
||||
buildConfiguration: Stable
|
||||
isPreRelease: false
|
||||
|
||||
jobs:
|
||||
- job: Build_Pack
|
||||
steps:
|
||||
- task: PowerShell@2
|
||||
displayName: 'Setup Build configuration'
|
||||
condition: or(eq(variables['Build.SourceBranch'], 'refs/heads/release/pre'), eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/chore/nuget-pipeline'))
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
echo '##vso[task.setvariable variable=releaseType]prerelease'
|
||||
echo '##vso[task.setvariable variable=buildConfiguration]Prerelease'
|
||||
echo '##vso[task.setvariable variable=isPreRelease]true'
|
||||
failOnStderr: true
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build Data'
|
||||
inputs:
|
||||
command: 'build'
|
||||
projects: '**/Data.csproj'
|
||||
arguments: '-c $(buildConfiguration)'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build SLC'
|
||||
inputs:
|
||||
command: 'build'
|
||||
projects: '**/SharedLibraryCore.csproj'
|
||||
arguments: '-c $(buildConfiguration) /p:Version=$(Build.BuildNumber)'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Pack SLC'
|
||||
inputs:
|
||||
command: 'pack'
|
||||
packagesToPack: '**/SharedLibraryCore.csproj'
|
||||
versioningScheme: 'byBuildNumber'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish nuget package artifact'
|
||||
inputs:
|
||||
targetPath: 'D:\a\1\a\RaidMax.IW4MAdmin.SharedLibraryCore.$(Build.BuildNumber).nupkg'
|
||||
artifact: 'SharedLibraryCore.$(Build.BuildNumber).nupkg'
|
||||
publishLocation: 'pipeline'
|
58
GameFiles/GameInterface/_integration_iw6.gsc
Normal file
58
GameFiles/GameInterface/_integration_iw6.gsc
Normal file
@ -0,0 +1,58 @@
|
||||
Init()
|
||||
{
|
||||
thread Setup();
|
||||
}
|
||||
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
waittillframeend;
|
||||
|
||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
||||
|
||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
||||
|
||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
||||
}
|
||||
|
||||
GetTotalShotsFired()
|
||||
{
|
||||
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
||||
}
|
||||
|
||||
SetDvarIfUninitializedWrapper( dvar, value )
|
||||
{
|
||||
SetDvarIfUninitialized( dvar, value );
|
||||
}
|
||||
|
||||
WaitillNotifyOrTimeoutWrapper( _notify, timeout )
|
||||
{
|
||||
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
||||
}
|
||||
|
||||
Log2Console( logLevel, message )
|
||||
{
|
||||
Print( "[" + logLevel + "] " + message + "\n" );
|
||||
}
|
||||
|
||||
IsBotWrapper( client )
|
||||
{
|
||||
return IsBot( client );
|
||||
}
|
||||
|
||||
GetXuidWrapper()
|
||||
{
|
||||
return self GetXUID();
|
||||
}
|
||||
|
||||
WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 )
|
||||
{
|
||||
return common_scripts\utility::waittill_any_timeout( timeOut, string1, string2, string3, string4, string5 );
|
||||
}
|
@ -6,13 +6,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
DeploymentFiles\deployment-pipeline.yml = DeploymentFiles\deployment-pipeline.yml
|
||||
DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1
|
||||
README.md = README.md
|
||||
version.txt = version.txt
|
||||
DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1
|
||||
DeploymentFiles\UpdateIW4MAdmin.sh = DeploymentFiles\UpdateIW4MAdmin.sh
|
||||
DeploymentFiles\nuget-pipeline.yml = DeploymentFiles\nuget-pipeline.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"
|
||||
@ -112,6 +109,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pluto IW5", "Pluto IW5", "{
|
||||
GameFiles\AntiCheat\IW5\README.MD = GameFiles\AntiCheat\IW5\README.MD
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GithubActions", "GithubActions", "{DCCEED9F-816E-4595-8B74-D76A77FBE0BE}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\build_application.yml = .github\workflows\build_application.yml
|
||||
.github\workflows\shared_library_nuget.yml = .github\workflows\shared_library_nuget.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -461,6 +464,7 @@ Global
|
||||
{3EA564BD-3AC6-479B-96B6-CB059DCD0C77} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||
{866F453D-BC89-457F-8B55-485494759B31} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||
{603725A4-BC0B-423B-955B-762C89E1C4C2} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||
{DCCEED9F-816E-4595-8B74-D76A77FBE0BE} = {8C8F3945-0AEF-4949-A1F7-B18E952E50BC}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>Integrations.Cod</AssemblyName>
|
||||
<RootNamespace>Integrations.Cod</RootNamespace>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -17,7 +17,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="protobuf-net" Version="3.2.26" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.30" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>Integrations.Source</AssemblyName>
|
||||
<RootNamespace>Integrations.Source</RootNamespace>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<LangVersion>Latest</LangVersion>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -10,11 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.3" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.6.22.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@ -3,7 +3,6 @@ using SharedLibraryCore;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using IW4MAdmin.Plugins.LiveRadar.Configuration;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
@ -82,7 +81,7 @@ namespace IW4MAdmin.Plugins.LiveRadar.Web.Controllers
|
||||
}
|
||||
|
||||
var radarInfo = server.GetClientsAsList()
|
||||
.Select(client => client.GetAdditionalProperty<RadarEvent>("LiveRadar")).ToList();
|
||||
.Select(client => client.GetAdditionalProperty<RadarDto>("LiveRadar")).ToList();
|
||||
|
||||
return Json(radarInfo);
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Events.Game;
|
||||
using EventGeneratorCallback = System.ValueTuple<string, string,
|
||||
System.Func<string, SharedLibraryCore.Interfaces.IEventParserConfiguration,
|
||||
SharedLibraryCore.GameEvent,
|
||||
SharedLibraryCore.GameEvent>>;
|
||||
|
||||
namespace IW4MAdmin.Plugins.LiveRadar.Events;
|
||||
|
||||
public class Script : IRegisterEvent
|
||||
{
|
||||
private const string EventLiveRadar = "LiveRadar";
|
||||
|
||||
private EventGeneratorCallback LiveRadar()
|
||||
{
|
||||
return (EventLiveRadar, EventLiveRadar, (eventLine, _, _) =>
|
||||
{
|
||||
var radarEvent = new LiveRadarEvent
|
||||
{
|
||||
Type = GameEvent.EventType.Other,
|
||||
Subtype = EventLiveRadar,
|
||||
Origin = new EFClient { NetworkId = 0 },
|
||||
ScriptData = eventLine
|
||||
};
|
||||
return radarEvent;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public IEnumerable<EventGeneratorCallback> Events => new[] { LiveRadar() };
|
||||
}
|
||||
|
||||
public class LiveRadarEvent : GameScriptEvent
|
||||
{
|
||||
}
|
19
Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs
Normal file
19
Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Data.Models;
|
||||
using SharedLibraryCore.Events.Game;
|
||||
|
||||
namespace IW4MAdmin.Plugins.LiveRadar.Events;
|
||||
|
||||
public class LiveRadarScriptEvent : GameScriptEvent
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Vector3 Location { get; set; }
|
||||
public Vector3 ViewAngles { get; set; }
|
||||
public string Team { get; set; }
|
||||
public int Kills { get; set; }
|
||||
public int Deaths { get; set; }
|
||||
public int Score { get; set; }
|
||||
public string Weapon { get; set; }
|
||||
public int Health { get; set; }
|
||||
public bool IsAlive { get; set; }
|
||||
public int PlayTime { get; set; }
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RazorCompileOnBuild Condition="'$(CONFIG)'!='Debug'">true</RazorCompileOnBuild>
|
||||
<RazorCompiledOnPublish Condition="'$(CONFIG)'!='Debug'">true</RazorCompiledOnPublish>
|
||||
<PreserveCompilationContext Condition="'$(CONFIG)'!='Debug'">false</PreserveCompilationContext>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
<Version>0.1.0.0</Version>
|
||||
@ -16,11 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.3" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.6.22.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@ -36,6 +36,9 @@ public class Plugin : IPluginV2
|
||||
public static void RegisterDependencies(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddConfiguration<LiveRadarConfiguration>();
|
||||
|
||||
serviceCollection.AddSingleton<IGameScriptEvent, LiveRadarScriptEvent>(); // for identification
|
||||
serviceCollection.AddTransient<LiveRadarScriptEvent>(); // for factory
|
||||
}
|
||||
|
||||
public Plugin(ILogger<Plugin> logger, ApplicationConfiguration appConfig)
|
||||
@ -51,7 +54,7 @@ public class Plugin : IPluginV2
|
||||
|
||||
private Task OnScriptEvent(GameScriptEvent scriptEvent, CancellationToken token)
|
||||
{
|
||||
if (scriptEvent is not LiveRadarEvent radarEvent)
|
||||
if (scriptEvent is not LiveRadarScriptEvent radarEvent)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -83,14 +86,15 @@ public class Plugin : IPluginV2
|
||||
: (originalBotGuid ?? "0").ConvertGuidToLong(NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
var radarUpdate = RadarEvent.Parse(scriptEvent.ScriptData, generatedBotGuid);
|
||||
var radarDto = RadarDto.FromScriptEvent(radarEvent, generatedBotGuid);
|
||||
|
||||
var client =
|
||||
radarEvent.Owner.ConnectedClients.FirstOrDefault(client => client.NetworkId == radarUpdate.Guid);
|
||||
radarEvent.Owner.ConnectedClients.FirstOrDefault(client => client.NetworkId == radarDto.Guid);
|
||||
|
||||
if (client != null)
|
||||
{
|
||||
radarUpdate.Name = client.Name.StripColors();
|
||||
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
||||
radarDto.Name = client.Name.StripColors();
|
||||
client.SetAdditionalProperty("LiveRadar", radarDto);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
using Data.Models;
|
||||
using SharedLibraryCore;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using IW4MAdmin.Plugins.LiveRadar.Events;
|
||||
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
#pragma warning disable CS0659
|
||||
|
||||
namespace IW4MAdmin.Plugins.LiveRadar;
|
||||
|
||||
public class RadarEvent
|
||||
public class RadarDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public long Guid { get; set; }
|
||||
@ -26,7 +26,7 @@ public class RadarEvent
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is RadarEvent re)
|
||||
if (obj is RadarDto re)
|
||||
{
|
||||
return re.ViewAngles.X == ViewAngles.X &&
|
||||
re.ViewAngles.Y == ViewAngles.Y &&
|
||||
@ -39,23 +39,21 @@ public class RadarEvent
|
||||
return false;
|
||||
}
|
||||
|
||||
public static RadarEvent Parse(string input, long generatedBotGuid)
|
||||
public static RadarDto FromScriptEvent(LiveRadarScriptEvent scriptEvent, long generatedBotGuid)
|
||||
{
|
||||
var items = input.Split(';').Skip(1).ToList();
|
||||
|
||||
var parsedEvent = new RadarEvent()
|
||||
var parsedEvent = new RadarDto
|
||||
{
|
||||
Guid = generatedBotGuid,
|
||||
Location = Vector3.Parse(items[1]),
|
||||
ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(),
|
||||
Team = items[3],
|
||||
Kills = int.Parse(items[4]),
|
||||
Deaths = int.Parse(items[5]),
|
||||
Score = int.Parse(items[6]),
|
||||
Weapon = items[7],
|
||||
Health = int.Parse(items[8]),
|
||||
IsAlive = items[9] == "1",
|
||||
PlayTime = Convert.ToInt32(items[10])
|
||||
Location = scriptEvent.Location,
|
||||
ViewAngles = scriptEvent.ViewAngles.FixIW4Angles(),
|
||||
Team = scriptEvent.Team,
|
||||
Kills = scriptEvent.Kills,
|
||||
Deaths = scriptEvent.Deaths,
|
||||
Score = scriptEvent.Score,
|
||||
Weapon =scriptEvent.Weapon,
|
||||
Health = scriptEvent.Health,
|
||||
IsAlive = scriptEvent.IsAlive,
|
||||
PlayTime = scriptEvent.PlayTime
|
||||
};
|
||||
|
||||
return parsedEvent;
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
@ -19,11 +19,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.3" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.6.22.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@ -20,8 +20,8 @@ public class MuteCommand : Command
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
SupportedGames = Plugin.SupportedGames;
|
||||
Arguments = new[]
|
||||
{
|
||||
Arguments =
|
||||
[
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
@ -32,7 +32,7 @@ public class MuteCommand : Command
|
||||
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
|
@ -21,14 +21,14 @@ public class MuteInfoCommand : Command
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
SupportedGames = Plugin.SupportedGames;
|
||||
Arguments = new[]
|
||||
{
|
||||
Arguments =
|
||||
[
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
|
@ -7,10 +7,9 @@ using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Mute.Commands;
|
||||
|
||||
public class TempMuteCommand : Command
|
||||
public partial class TempMuteCommand : Command
|
||||
{
|
||||
private readonly MuteManager _muteManager;
|
||||
private const string TempBanRegex = @"([0-9]+\w+)\ (.+)";
|
||||
|
||||
public TempMuteCommand(CommandConfiguration config, ITranslationLookup translationLookup, MuteManager muteManager) : base(config,
|
||||
translationLookup)
|
||||
@ -22,8 +21,8 @@ public class TempMuteCommand : Command
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
SupportedGames = Plugin.SupportedGames;
|
||||
Arguments = new[]
|
||||
{
|
||||
Arguments =
|
||||
[
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
@ -39,7 +38,7 @@ public class TempMuteCommand : Command
|
||||
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
@ -49,8 +48,8 @@ public class TempMuteCommand : Command
|
||||
gameEvent.Origin.Tell(_translationLookup["COMMANDS_DENY_SELF_TARGET"]);
|
||||
return;
|
||||
}
|
||||
|
||||
var match = Regex.Match(gameEvent.Data, TempBanRegex);
|
||||
|
||||
var match = TempBanRegex().Match(gameEvent.Data);
|
||||
if (match.Success)
|
||||
{
|
||||
var expiration = DateTime.UtcNow + match.Groups[1].ToString().ParseTimespan();
|
||||
@ -72,4 +71,7 @@ public class TempMuteCommand : Command
|
||||
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_BAD_FORMAT"]);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"([0-9]+\w+)\ (.+)")]
|
||||
private static partial Regex TempBanRegex();
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ public class UnmuteCommand : Command
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
SupportedGames = Plugin.SupportedGames;
|
||||
Arguments = new[]
|
||||
{
|
||||
Arguments =
|
||||
[
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
@ -32,7 +32,7 @@ public class UnmuteCommand : Command
|
||||
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Authors>MrAmos123</Authors>
|
||||
@ -12,10 +12,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.3" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.6.22.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@ -10,23 +10,15 @@ using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Mute;
|
||||
|
||||
public class MuteManager
|
||||
public class MuteManager(
|
||||
ILogger<MuteManager> logger,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IMetaServiceV2 metaService,
|
||||
ITranslationLookup translationLookup)
|
||||
{
|
||||
private readonly IMetaServiceV2 _metaService;
|
||||
private readonly ITranslationLookup _translationLookup;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDatabaseContextFactory _databaseContextFactory;
|
||||
private readonly ILogger _logger = logger;
|
||||
private readonly SemaphoreSlim _onMuteAction = new(1, 1);
|
||||
|
||||
public MuteManager(ILogger<MuteManager> logger, IDatabaseContextFactory databaseContextFactory,
|
||||
IMetaServiceV2 metaService, ITranslationLookup translationLookup)
|
||||
{
|
||||
_logger = logger;
|
||||
_databaseContextFactory = databaseContextFactory;
|
||||
_metaService = metaService;
|
||||
_translationLookup = translationLookup;
|
||||
}
|
||||
|
||||
public static bool IsExpiredMute(MuteStateMeta muteStateMeta) =>
|
||||
muteStateMeta.Expiration is not null && muteStateMeta.Expiration < DateTime.UtcNow;
|
||||
|
||||
@ -42,7 +34,7 @@ public class MuteManager
|
||||
var muteState = await ReadPersistentDataV1(client);
|
||||
clientMuteMeta = new MuteStateMeta
|
||||
{
|
||||
Reason = muteState is null ? string.Empty : _translationLookup["PLUGINS_MUTE_MIGRATED"],
|
||||
Reason = muteState is null ? string.Empty : translationLookup["PLUGINS_MUTE_MIGRATED"],
|
||||
Expiration = muteState switch
|
||||
{
|
||||
null => DateTime.UtcNow,
|
||||
@ -149,7 +141,7 @@ public class MuteManager
|
||||
|
||||
private async Task ExpireMutePenalties(EFClient client)
|
||||
{
|
||||
await using var context = _databaseContextFactory.CreateContext();
|
||||
await using var context = databaseContextFactory.CreateContext();
|
||||
var mutePenalties = await context.Penalties
|
||||
.Where(penalty => penalty.OffenderId == client.ClientId)
|
||||
.Where(penalty => penalty.Type == EFPenalty.PenaltyType.Mute || penalty.Type == EFPenalty.PenaltyType.TempMute)
|
||||
@ -184,7 +176,7 @@ public class MuteManager
|
||||
}
|
||||
|
||||
private async Task<MuteState?> ReadPersistentDataV1(EFClient client) => TryParse<MuteState>(
|
||||
(await _metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?.Value, out var muteState)
|
||||
(await metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?.Value, out var muteState)
|
||||
? muteState
|
||||
: null;
|
||||
|
||||
@ -195,7 +187,7 @@ public class MuteManager
|
||||
if (clientMuteMeta is not null) return clientMuteMeta;
|
||||
|
||||
// Get meta from database and store in client if exists
|
||||
clientMuteMeta = await _metaService.GetPersistentMetaValue<MuteStateMeta>(Plugin.MuteKey, client.ClientId);
|
||||
clientMuteMeta = await metaService.GetPersistentMetaValue<MuteStateMeta>(Plugin.MuteKey, client.ClientId);
|
||||
if (clientMuteMeta is not null) client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta);
|
||||
|
||||
return clientMuteMeta;
|
||||
@ -204,6 +196,6 @@ public class MuteManager
|
||||
private async Task WritePersistentData(EFClient client, MuteStateMeta clientMuteMeta)
|
||||
{
|
||||
client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta);
|
||||
await _metaService.SetPersistentMetaValue(Plugin.MuteKey, clientMuteMeta, client.ClientId);
|
||||
await metaService.SetPersistentMetaValue(Plugin.MuteKey, clientMuteMeta, client.ClientId);
|
||||
}
|
||||
}
|
||||
|
@ -21,15 +21,14 @@ public class Plugin : IPluginV2
|
||||
|
||||
public const string MuteKey = "IW4MMute";
|
||||
public static IManager Manager { get; private set; } = null!;
|
||||
public static Server.Game[] SupportedGames { get; private set; } = Array.Empty<Server.Game>();
|
||||
private static readonly string[] DisabledCommands = {nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"};
|
||||
public static Server.Game[] SupportedGames { get; private set; } = [];
|
||||
private static readonly string[] DisabledCommands = [nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"];
|
||||
private readonly IInteractionRegistration _interactionRegistration;
|
||||
private readonly IRemoteCommandService _remoteCommandService;
|
||||
private readonly MuteManager _muteManager;
|
||||
private const string MuteInteraction = "Webfront::Profile::Mute";
|
||||
|
||||
public Plugin(IInteractionRegistration interactionRegistration,
|
||||
IRemoteCommandService remoteCommandService, MuteManager muteManager)
|
||||
public Plugin(IInteractionRegistration interactionRegistration, IRemoteCommandService remoteCommandService, MuteManager muteManager)
|
||||
{
|
||||
_interactionRegistration = interactionRegistration;
|
||||
_remoteCommandService = remoteCommandService;
|
||||
@ -72,7 +71,7 @@ public class Plugin : IPluginV2
|
||||
return !DisabledCommands.Contains(command.GetType().Name) && !command.IsBroadcast;
|
||||
});
|
||||
|
||||
_interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, token) =>
|
||||
_interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, _) =>
|
||||
{
|
||||
if (!targetClientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game)game.Value))
|
||||
{
|
||||
@ -80,16 +79,16 @@ public class Plugin : IPluginV2
|
||||
}
|
||||
|
||||
var clientMuteMetaState =
|
||||
(await _muteManager.GetCurrentMuteState(new EFClient {ClientId = targetClientId.Value}))
|
||||
(await _muteManager.GetCurrentMuteState(new EFClient { ClientId = targetClientId.Value }))
|
||||
.MuteState;
|
||||
var server = manager.GetServers().First();
|
||||
|
||||
string GetCommandName(Type commandType) =>
|
||||
manager.Commands.FirstOrDefault(command => command.GetType() == commandType)?.Name ?? "";
|
||||
|
||||
return clientMuteMetaState is MuteState.Unmuted or MuteState.Unmuting
|
||||
? CreateMuteInteraction(targetClientId.Value, server, GetCommandName)
|
||||
: CreateUnmuteInteraction(targetClientId.Value, server, GetCommandName);
|
||||
|
||||
string GetCommandName(Type commandType) =>
|
||||
manager.Commands.FirstOrDefault(command => command.GetType() == commandType)?.Name ?? string.Empty;
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -108,9 +107,9 @@ public class Plugin : IPluginV2
|
||||
}
|
||||
|
||||
var networkIds = updateEvent.Clients.Select(client => client.NetworkId).ToList();
|
||||
var ingameClients = updateEvent.Server.ConnectedClients.Where(client => networkIds.Contains(client.NetworkId));
|
||||
var inGameClients = updateEvent.Server.ConnectedClients.Where(client => networkIds.Contains(client.NetworkId));
|
||||
|
||||
await Task.WhenAll(ingameClients.Select(async client =>
|
||||
await Task.WhenAll(inGameClients.Select(async client =>
|
||||
{
|
||||
var muteMetaUpdate = await _muteManager.GetCurrentMuteState(client);
|
||||
if (!muteMetaUpdate.CommandExecuted)
|
||||
@ -136,7 +135,7 @@ public class Plugin : IPluginV2
|
||||
{
|
||||
var muteMetaSay = await _muteManager.GetCurrentMuteState(messageEvent.Origin);
|
||||
|
||||
if (muteMetaSay.MuteState == MuteState.Muted)
|
||||
if (muteMetaSay.MuteState is MuteState.Muted)
|
||||
{
|
||||
// Let the client know when their mute expires.
|
||||
messageEvent.Origin.Tell(Utilities.CurrentLocalization
|
||||
@ -159,16 +158,16 @@ public class Plugin : IPluginV2
|
||||
|
||||
switch (muteMetaJoin)
|
||||
{
|
||||
case {MuteState: MuteState.Muted}:
|
||||
case { MuteState: MuteState.Muted }:
|
||||
// Let the client know when their mute expires.
|
||||
state.Client.Tell(Utilities.CurrentLocalization
|
||||
.LocalizationIndex["PLUGINS_MUTE_REMAINING_TIME"].FormatExt(
|
||||
muteMetaJoin is {Expiration: not null}
|
||||
muteMetaJoin is { Expiration: not null }
|
||||
? muteMetaJoin.Expiration.Value.HumanizeForCurrentCulture()
|
||||
: Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_NEVER"],
|
||||
muteMetaJoin.Reason));
|
||||
break;
|
||||
case {MuteState: MuteState.Unmuting}:
|
||||
case { MuteState: MuteState.Unmuting }:
|
||||
// Handle unmute of unmuted players.
|
||||
await _muteManager.Unmute(state.Client.CurrentServer, Utilities.IW4MAdminClient(), state.Client,
|
||||
muteMetaJoin.Reason ?? string.Empty);
|
||||
@ -190,6 +189,29 @@ public class Plugin : IPluginV2
|
||||
Values = (Dictionary<string, string>?)null
|
||||
};
|
||||
|
||||
var presetReasonInput = new
|
||||
{
|
||||
Name = "PresetReason",
|
||||
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_PRESET_REASON"],
|
||||
Type = "select",
|
||||
Values = (Dictionary<string, string>?)new Dictionary<string, string>
|
||||
{
|
||||
{ string.Empty, string.Empty },
|
||||
{
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_ABUSIVE"],
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_ABUSIVE"]
|
||||
},
|
||||
{
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_SPAMMING"],
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_SPAMMING"]
|
||||
},
|
||||
{
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_OTHER"],
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_OTHER"]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var durationInput = new
|
||||
{
|
||||
Name = "Duration",
|
||||
@ -197,16 +219,16 @@ public class Plugin : IPluginV2
|
||||
Type = "select",
|
||||
Values = (Dictionary<string, string>?)new Dictionary<string, string>
|
||||
{
|
||||
{"5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture()},
|
||||
{"30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture()},
|
||||
{"1h", TimeSpan.FromHours(1).HumanizeForCurrentCulture()},
|
||||
{"6h", TimeSpan.FromHours(6).HumanizeForCurrentCulture()},
|
||||
{"1d", TimeSpan.FromDays(1).HumanizeForCurrentCulture()},
|
||||
{"p", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_SELECTION_PERMANENT"]}
|
||||
{ "5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture() },
|
||||
{ "30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture() },
|
||||
{ "1h", TimeSpan.FromHours(1).HumanizeForCurrentCulture() },
|
||||
{ "6h", TimeSpan.FromHours(6).HumanizeForCurrentCulture() },
|
||||
{ "1d", TimeSpan.FromDays(1).HumanizeForCurrentCulture() },
|
||||
{ "p", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_SELECTION_PERMANENT"] }
|
||||
}
|
||||
};
|
||||
|
||||
var inputs = new[] {reasonInput, durationInput};
|
||||
var inputs = new[] { reasonInput, presetReasonInput, durationInput };
|
||||
var inputsJson = JsonSerializer.Serialize(inputs);
|
||||
|
||||
return new InteractionData
|
||||
@ -215,10 +237,10 @@ public class Plugin : IPluginV2
|
||||
Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"],
|
||||
DisplayMeta = "oi-volume-off",
|
||||
ActionPath = "DynamicAction",
|
||||
ActionMeta = new()
|
||||
ActionMeta = new Dictionary<string, string>
|
||||
{
|
||||
{"InteractionId", MuteInteraction},
|
||||
{"Inputs", inputsJson},
|
||||
{ "InteractionId", MuteInteraction },
|
||||
{ "Inputs", inputsJson },
|
||||
{
|
||||
"ActionButtonLabel",
|
||||
Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"]
|
||||
@ -227,7 +249,7 @@ public class Plugin : IPluginV2
|
||||
"Name",
|
||||
Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"]
|
||||
},
|
||||
{"ShouldRefresh", true.ToString()}
|
||||
{ "ShouldRefresh", true.ToString() }
|
||||
},
|
||||
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
||||
Source = Name,
|
||||
@ -249,11 +271,14 @@ public class Plugin : IPluginV2
|
||||
args.Add(duration);
|
||||
}
|
||||
|
||||
if (meta.TryGetValue(reasonInput.Name, out var reason))
|
||||
var definedReason = meta.TryGetValue(reasonInput.Name, out var reason) ? reason : string.Empty;
|
||||
if (meta.TryGetValue(presetReasonInput.Name, out var presetReason) && string.IsNullOrWhiteSpace(definedReason))
|
||||
{
|
||||
args.Add(reason);
|
||||
definedReason = presetReason;
|
||||
}
|
||||
|
||||
args.Add(definedReason);
|
||||
|
||||
var commandResponse =
|
||||
await _remoteCommandService.Execute(originId, targetId, muteCommand, args, server);
|
||||
return string.Join(".", commandResponse.Select(result => result.Response));
|
||||
@ -271,21 +296,20 @@ public class Plugin : IPluginV2
|
||||
Type = "text",
|
||||
};
|
||||
|
||||
var inputs = new[] {reasonInput};
|
||||
var inputs = new[] { reasonInput };
|
||||
var inputsJson = JsonSerializer.Serialize(inputs);
|
||||
|
||||
return new InteractionData
|
||||
{
|
||||
EntityId = targetClientId,
|
||||
Name = Utilities.CurrentLocalization.LocalizationIndex[
|
||||
"WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"],
|
||||
Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"],
|
||||
DisplayMeta = "oi-volume-high",
|
||||
ActionPath = "DynamicAction",
|
||||
ActionMeta = new()
|
||||
ActionMeta = new Dictionary<string, string>
|
||||
{
|
||||
{"InteractionId", MuteInteraction},
|
||||
{"Outputs", reasonInput.Name},
|
||||
{"Inputs", inputsJson},
|
||||
{ "InteractionId", MuteInteraction },
|
||||
{ "Outputs", reasonInput.Name },
|
||||
{ "Inputs", inputsJson },
|
||||
{
|
||||
"ActionButtonLabel",
|
||||
Utilities.CurrentLocalization.LocalizationIndex[
|
||||
@ -296,7 +320,7 @@ public class Plugin : IPluginV2
|
||||
Utilities.CurrentLocalization.LocalizationIndex[
|
||||
"WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"]
|
||||
},
|
||||
{"ShouldRefresh", true.ToString()}
|
||||
{ "ShouldRefresh", true.ToString() }
|
||||
},
|
||||
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
||||
Source = Name,
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
|
||||
@ -16,11 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.3" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.6.22.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@ -94,7 +94,7 @@ const plugin = {
|
||||
onServerValueSetCompleted: async function (serverValueEvent) {
|
||||
this.logger.logDebug('Set {dvarName}={dvarValue} success={success} from {server}', serverValueEvent.valueName,
|
||||
serverValueEvent.value, serverValueEvent.success, serverValueEvent.server.id);
|
||||
|
||||
|
||||
if (serverValueEvent.valueName !== inDvar && serverValueEvent.valueName !== outDvar) {
|
||||
this.logger.logDebug('Ignoring set complete of {name}', serverValueEvent.valueName);
|
||||
return;
|
||||
@ -124,7 +124,7 @@ const plugin = {
|
||||
// loop restarts
|
||||
this.requestGetDvar(inDvar, serverValueEvent.server);
|
||||
},
|
||||
|
||||
|
||||
onServerMonitoringStart: function (monitorStartEvent) {
|
||||
this.initializeServer(monitorStartEvent.server);
|
||||
},
|
||||
@ -162,7 +162,7 @@ const plugin = {
|
||||
serverState.enabled = true;
|
||||
serverState.running = true;
|
||||
serverState.initializationInProgress = false;
|
||||
|
||||
|
||||
// todo: this might not work for all games
|
||||
responseEvent.server.rconParser.configuration.floodProtectInterval = 150;
|
||||
|
||||
@ -233,7 +233,7 @@ const plugin = {
|
||||
|
||||
// todo: refactor to mapping if possible
|
||||
if (event.eventType === 'ClientDataRequested') {
|
||||
const client = server.getClientByNumber(event.clientNumber);
|
||||
const client = server.connectedClients[event.clientNumber];
|
||||
|
||||
if (client != null) {
|
||||
this.logger.logDebug('Found client {name}', client.name);
|
||||
@ -269,8 +269,9 @@ const plugin = {
|
||||
}
|
||||
}
|
||||
|
||||
let _;
|
||||
if (event.eventType === 'SetClientDataRequested') {
|
||||
let client = server.getClientByNumber(event.clientNumber);
|
||||
let client = server.connectedClients[event.clientNumber];
|
||||
let clientId;
|
||||
|
||||
if (client != null) {
|
||||
@ -298,12 +299,12 @@ const plugin = {
|
||||
const parsedValue = parseInt(event.data['value']);
|
||||
const key = event.data['key'].toString();
|
||||
if (!isNaN(parsedValue)) {
|
||||
event.data['direction'] = 'up' ?
|
||||
_ = event.data['direction'] === 'increment' ?
|
||||
(await metaService.incrementPersistentMeta(key, parsedValue, clientId, token)).result :
|
||||
(await metaService.decrementPersistentMeta(key, parsedValue, clientId, token)).result;
|
||||
}
|
||||
} else {
|
||||
const _ = (await metaService.setPersistentMeta(event.data['key'], event.data['value'], clientId, token)).result;
|
||||
_ = (await metaService.setPersistentMeta(event.data['key'], event.data['value'], clientId, token)).result;
|
||||
}
|
||||
|
||||
if (event.data['key'] === 'PersistentClientGuid') {
|
||||
@ -342,34 +343,34 @@ const plugin = {
|
||||
if (typeof response !== 'string' && !(response instanceof String)) {
|
||||
response = JSON.stringify(response);
|
||||
}
|
||||
|
||||
|
||||
const max = 10;
|
||||
this.logger.logDebug(`response length ${response.length}`);
|
||||
|
||||
|
||||
let quoteReplace = '\\"';
|
||||
// todo: may be more than just T6
|
||||
if (server.gameCode === 'T6') {
|
||||
quoteReplace = '\\\\"';
|
||||
}
|
||||
|
||||
|
||||
let chunks = chunkString(response.replace(/"/gm, quoteReplace).replace(/[\n|\t]/gm, ''), 800);
|
||||
if (chunks.length > max) {
|
||||
this.logger.logWarning(`Response chunks greater than max (${max}). Data truncated!`);
|
||||
chunks = chunks.slice(0, max);
|
||||
}
|
||||
this.logger.logDebug(`chunk size ${chunks.length}`);
|
||||
|
||||
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
this.sendEventMessage(server, false, 'UrlRequestCompleted', null, null,
|
||||
null, { entity: event.data.entity, remaining: chunks.length - (i + 1), response: chunks[i]});
|
||||
null, {entity: event.data.entity, remaining: chunks.length - (i + 1), response: chunks[i]});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (event.eventType === 'RegisterCommandRequested') {
|
||||
this.registerDynamicCommand(event);
|
||||
}
|
||||
|
||||
|
||||
if (event.eventType === 'GetBusModeRequested') {
|
||||
if (event.data?.directory && event.data?.mode) {
|
||||
busMode = event.data.mode;
|
||||
@ -433,10 +434,10 @@ const plugin = {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
|
||||
const requestEvent = new serverEvents.ServerValueRequestEvent(dvarName, server);
|
||||
requestEvent.delayMs = this.config.pollingRate;
|
||||
@ -467,8 +468,8 @@ const plugin = {
|
||||
|
||||
requestSetDvar: function (dvarName, dvarValue, server) {
|
||||
const serverState = servers[server.id];
|
||||
|
||||
if ( busMode === 'file' ) {
|
||||
|
||||
if (busMode === 'file') {
|
||||
this.scriptHelper.requestNotifyAfterDelay(250, async () => {
|
||||
const io = importNamespace('System.IO');
|
||||
try {
|
||||
@ -493,7 +494,7 @@ const plugin = {
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -526,7 +527,7 @@ const plugin = {
|
||||
}
|
||||
},
|
||||
|
||||
parseUrlRequest: function(event) {
|
||||
parseUrlRequest: function (event) {
|
||||
const url = event.data?.url;
|
||||
|
||||
if (url === undefined) {
|
||||
@ -556,8 +557,8 @@ const plugin = {
|
||||
const script = importNamespace('IW4MAdmin.Application.Plugin.Script');
|
||||
return new script.ScriptPluginWebRequest(url, body, method, contentType, headerDict);
|
||||
},
|
||||
|
||||
registerDynamicCommand: function(event) {
|
||||
|
||||
registerDynamicCommand: function (event) {
|
||||
const commandWrapper = {
|
||||
commands: [{
|
||||
name: event.data['name'] || 'DEFAULT',
|
||||
@ -571,9 +572,9 @@ const plugin = {
|
||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (gameEvent.data === '--reload' && gameEvent.origin.level === 'Owner') {
|
||||
this.sendEventMessage(gameEvent.owner, true, 'GetCommandsRequested', null, null, null, { name: gameEvent.extra.name });
|
||||
this.sendEventMessage(gameEvent.owner, true, 'GetCommandsRequested', null, null, null, {name: gameEvent.extra.name});
|
||||
} else {
|
||||
sendScriptCommand(gameEvent.owner, `${event.data['eventKey']}Execute`, gameEvent.origin, gameEvent.target, {
|
||||
args: gameEvent.data
|
||||
@ -582,7 +583,7 @@ const plugin = {
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
this.scriptHelper.registerDynamicCommand(commandWrapper);
|
||||
}
|
||||
};
|
||||
@ -920,6 +921,6 @@ const fileForDvar = (dvar) => {
|
||||
if (dvar === inDvar) {
|
||||
return busFileIn;
|
||||
}
|
||||
|
||||
|
||||
return busFileOut;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ var plugin = {
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'kickClient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'kickClient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'kickClient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint';
|
||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||
rconParser.Configuration.Dvar.Pattern = '^ *\\"(.+)\\" is: \\"(.+)?\\" default: \\"(.+)?\\"\\n?(?:latched: \\"(.+)?\\"\\n?)?(.*)$';
|
||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(Yes|No) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) *$';
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +address +qport *';
|
||||
|
@ -9,7 +9,7 @@ const serverOrderCache = [];
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: '1.0',
|
||||
version: '1.1',
|
||||
name: 'Server Banner',
|
||||
serviceResolver: null,
|
||||
scriptHelper: null,
|
||||
@ -26,6 +26,9 @@ const plugin = {
|
||||
this.manager = serviceResolver.resolveService('IManager');
|
||||
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
||||
this.webfrontUrl = serviceResolver.resolveService('ApplicationConfiguration').webfrontUrl;
|
||||
|
||||
this.logger.logInformation('{Name} {Version} by {Author} loaded,', this.name, this.version,
|
||||
this.author);
|
||||
},
|
||||
|
||||
onServerMonitoringStart: function (startEvent) {
|
||||
@ -123,11 +126,13 @@ const plugin = {
|
||||
},
|
||||
};
|
||||
|
||||
plugin.manager.getServers().forEach(eachServer => {
|
||||
if (eachServer.id === serverId) {
|
||||
server = eachServer;
|
||||
const servers = plugin.manager.servers;
|
||||
for (let i = 0; i < servers.length; i++) {
|
||||
if (servers[i].id === serverId) {
|
||||
server = servers[i];
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (serverLocationCache[server.listenAddress] === undefined) {
|
||||
plugin.onServerMonitoringStart({
|
||||
@ -280,7 +285,7 @@ const plugin = {
|
||||
<div class="server-container small" id="server">
|
||||
<div class="first-line small">
|
||||
<div class="game-icon small"></div>
|
||||
<div class="header" style="${colorLeft}">${server.serverName.stripColors()}</div>
|
||||
<div class="header" id="serverName" style="${colorLeft}"></div>
|
||||
</div>
|
||||
<div class="third-line game-info small">
|
||||
${status}
|
||||
@ -298,6 +303,10 @@ const plugin = {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const serverNameElem = document.getElementById('serverName');
|
||||
serverNameElem.textContent = '${server.serverName.stripColors()}';
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
@ -310,7 +319,7 @@ const plugin = {
|
||||
style="background: url('https://raidmax.org/resources/images/icons/games/${gameCode}.jpg');">
|
||||
</div>
|
||||
<div style="flex: 1; ${colorLeft}" class="game-info large">
|
||||
<div class="header">${server.serverName.stripColors()}</div>
|
||||
<div class="header" id="serverName"></div>
|
||||
<div class="text-weight-lighter subtitle">${displayIp}:${server.listenPort}</div>
|
||||
<div class="players-flag-section">
|
||||
<div class="subtitle">${server.throttled ? '-' : server.clientNum}/${server.maxClients} Players</div>
|
||||
@ -324,6 +333,10 @@ const plugin = {
|
||||
${status}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const serverNameElem = document.getElementById('serverName');
|
||||
serverNameElem.textContent = '${server.serverName.stripColors()}';
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
@ -346,22 +359,24 @@ const plugin = {
|
||||
|
||||
interactionData.scriptAction = (_, __, ___, ____, _____) => {
|
||||
if (Object.keys(serverOrderCache).length === 0) {
|
||||
plugin.manager.getServers().forEach(server => {
|
||||
for (let i = 0; i < plugin.manager.servers.length; i++) {
|
||||
const server = plugin.manager.servers[i];
|
||||
plugin.onServerMonitoringStart({
|
||||
server: server
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let response = '<div class="d-flex flex-row flex-wrap" style="margin-left: -1rem; margin-top: -1rem;">';
|
||||
Object.keys(serverOrderCache).forEach(key => {
|
||||
const servers = serverOrderCache[key];
|
||||
servers.forEach(eachServer => {
|
||||
for (let i = 0; i < servers.length; i++) {
|
||||
const eachServer = servers[i];
|
||||
response += `<div class="w-full w-xl-half">
|
||||
<div class="card m-10 p-20">
|
||||
<div class="font-size-16 mb-10">
|
||||
<div class="badge ml-10 float-right font-size-16">${eachServer.gameCode}</div>
|
||||
${eachServer.serverName.stripColors()}
|
||||
<div id="serverName"></div>
|
||||
</div>
|
||||
|
||||
<div style="overflow: hidden">
|
||||
@ -387,8 +402,12 @@ const plugin = {
|
||||
<br/> width="400" height="70" style="border-width: 0; overflow: hidden;"><br/>
|
||||
</iframe></div>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
</div>
|
||||
<script>
|
||||
const serverNameElem = document.getElementById('serverName');
|
||||
serverNameElem.textContent = '${eachServer.serverName.stripColors()}';
|
||||
</script>`;
|
||||
}
|
||||
});
|
||||
|
||||
response += '</div>';
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId>
|
||||
@ -17,11 +17,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.3" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.6.22.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@ -6,8 +6,8 @@ using SharedLibraryCore.Database.Models;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Humanizer;
|
||||
using Data.Abstractions;
|
||||
using Data.Models;
|
||||
@ -108,8 +108,9 @@ public class Plugin : IPluginV2
|
||||
var response =
|
||||
await wc.GetStringAsync(new Uri(
|
||||
$"http://ip-api.com/json/{ip}?lang={Utilities.CurrentLocalization.LocalizationName.Split("-").First().ToLower()}"));
|
||||
var responseObj = JObject.Parse(response);
|
||||
response = responseObj["country"]?.ToString();
|
||||
|
||||
var json = JsonDocument.Parse(response);
|
||||
response = json.RootElement.TryGetProperty("country", out var countryElement) ? countryElement.GetString() : null;
|
||||
|
||||
return string.IsNullOrEmpty(response)
|
||||
? Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_UNKNOWN_COUNTRY"]
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Welcome</PackageId>
|
||||
@ -15,12 +15,8 @@
|
||||
<LangVersion>Latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.2.5.3" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2024.6.22.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -2,8 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Data.Models.Misc;
|
||||
using Newtonsoft.Json;
|
||||
using SharedLibraryCore.Configuration.Attributes;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using static Data.Models.Client.EFClient;
|
||||
@ -22,7 +22,7 @@ namespace SharedLibraryCore.Configuration
|
||||
public bool EnableWebFront { get; set; }
|
||||
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_BIND_URL")]
|
||||
public string WebfrontBindUrl { get; set; }
|
||||
public string WebfrontBindUrl { get; set; } = "http://0.0.0.0:1624";
|
||||
|
||||
[ConfigurationOptional]
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_MANUAL_URL")]
|
||||
|
@ -1,41 +1,40 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System.Text.Json.Serialization;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using static Data.Models.Client.EFClient;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace SharedLibraryCore.Configuration
|
||||
namespace SharedLibraryCore.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Config driven command properties
|
||||
/// </summary>
|
||||
public class CommandProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Config driven command properties
|
||||
/// Specifies the command name
|
||||
/// </summary>
|
||||
public class CommandProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the command name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Alias of this command
|
||||
/// </summary>
|
||||
public string Alias { get; set; }
|
||||
/// <summary>
|
||||
/// Alias of this command
|
||||
/// </summary>
|
||||
public string Alias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the minimum permission level needed to execute the
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public Permission MinimumPermission { get; set; }
|
||||
/// <summary>
|
||||
/// Specifies the minimum permission level needed to execute the
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public Permission MinimumPermission { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the command can be run by another user (impersonation)
|
||||
/// </summary>
|
||||
public bool AllowImpersonation { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates if the command can be run by another user (impersonation)
|
||||
/// </summary>
|
||||
public bool AllowImpersonation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the games supporting the functionality of the command
|
||||
/// </summary>
|
||||
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
|
||||
public Game[] SupportedGames { get; set; } = Array.Empty<Game>();
|
||||
}
|
||||
/// <summary>
|
||||
/// Specifies the games supporting the functionality of the command
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(GameArrayJsonConverter))]
|
||||
public Game[] SupportedGames { get; set; } = Array.Empty<Game>();
|
||||
}
|
||||
|
@ -9,16 +9,9 @@ namespace SharedLibraryCore.Configuration
|
||||
{
|
||||
public class ServerConfiguration : IBaseConfiguration
|
||||
{
|
||||
private readonly IList<IRConParser> _rconParsers;
|
||||
private readonly IList<IRConParser> _rconParsers = new List<IRConParser>();
|
||||
private IRConParser _selectedParser;
|
||||
|
||||
public ServerConfiguration()
|
||||
{
|
||||
_rconParsers = new List<IRConParser>();
|
||||
Rules = new string[0];
|
||||
AutoMessages = new string[0];
|
||||
}
|
||||
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_IP")]
|
||||
public string IPAddress { get; set; }
|
||||
|
||||
@ -29,10 +22,10 @@ namespace SharedLibraryCore.Configuration
|
||||
public string Password { get; set; }
|
||||
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RULES")]
|
||||
public string[] Rules { get; set; } = new string[0];
|
||||
public string[] Rules { get; set; } = [];
|
||||
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_AUTO_MESSAGES")]
|
||||
public string[] AutoMessages { get; set; } = new string[0];
|
||||
public string[] AutoMessages { get; set; } = [];
|
||||
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PATH")]
|
||||
[ConfigurationOptional]
|
||||
@ -88,7 +81,7 @@ namespace SharedLibraryCore.Configuration
|
||||
var passwords = _selectedParser.TryGetRConPasswords();
|
||||
if (passwords.Length > 1)
|
||||
{
|
||||
var (index, value) =
|
||||
var (index, _) =
|
||||
loc["SETUP_RCON_PASSWORD_PROMPT"].PromptSelection(loc["SETUP_RCON_PASSWORD_MANUAL"], null,
|
||||
passwords.Select(pw =>
|
||||
$"{pw.Item1}{(string.IsNullOrEmpty(pw.Item2) ? "" : " " + pw.Item2)}")
|
||||
@ -113,9 +106,8 @@ namespace SharedLibraryCore.Configuration
|
||||
Password = loc["SETUP_SERVER_RCON"].PromptString();
|
||||
}
|
||||
|
||||
AutoMessages = new string[0];
|
||||
Rules = new string[0];
|
||||
ManualLogPath = null;
|
||||
AutoMessages = [];
|
||||
Rules = [];
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ public abstract class CoreEvent
|
||||
{
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
public Guid? CorrelationId { get; init; }
|
||||
public object Source { get; init; }
|
||||
public object Source { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset? ProcessedAt { get; set; }
|
||||
}
|
||||
|
6
SharedLibraryCore/Events/Game/GameLogEvent.cs
Normal file
6
SharedLibraryCore/Events/Game/GameLogEvent.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace SharedLibraryCore.Events.Game;
|
||||
|
||||
public class GameLogEvent : GameEventV2
|
||||
{
|
||||
public string LogLine { get; set; }
|
||||
}
|
@ -1,6 +1,77 @@
|
||||
namespace SharedLibraryCore.Events.Game;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using SharedLibraryCore.Interfaces.Events;
|
||||
|
||||
public class GameScriptEvent : GameEventV2
|
||||
namespace SharedLibraryCore.Events.Game;
|
||||
|
||||
public class GameScriptEvent : GameEventV2, IGameScriptEvent
|
||||
{
|
||||
public string ScriptData { get; init; }
|
||||
public string ScriptData { get; set; }
|
||||
public string EventName { get; } = null;
|
||||
|
||||
public virtual void ParseArguments()
|
||||
{
|
||||
var arguments = ScriptData.Split(';', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var propIndex = 0;
|
||||
foreach (var argument in arguments)
|
||||
{
|
||||
var parts = argument.Split(['='], 2);
|
||||
PropertyInfo propertyInfo = null;
|
||||
string rawValue;
|
||||
|
||||
if (parts.Length == 2) // handle as key/value pairs
|
||||
{
|
||||
var propertyName = parts[0].Trim();
|
||||
rawValue = parts[1].Trim();
|
||||
propertyInfo = GetType().GetProperty(propertyName,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly);
|
||||
}
|
||||
else
|
||||
{
|
||||
rawValue = argument;
|
||||
|
||||
try
|
||||
{
|
||||
propertyInfo =
|
||||
GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase |
|
||||
BindingFlags.DeclaredOnly)[
|
||||
propIndex];
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
if (propertyInfo is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var method = propertyInfo.PropertyType.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public,
|
||||
[typeof(string)]);
|
||||
|
||||
var convertedValue = method is not null
|
||||
? method!.Invoke(null, [rawValue])!
|
||||
: Convert.ChangeType(rawValue, propertyInfo.PropertyType);
|
||||
|
||||
propertyInfo.SetValue(this, convertedValue);
|
||||
}
|
||||
catch (TargetInvocationException ex) when (ex.InnerException is FormatException &&
|
||||
propertyInfo.PropertyType == typeof(bool))
|
||||
{
|
||||
propertyInfo.SetValue(this, rawValue != "0");
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
propIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
using Data.Models;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace SharedLibraryCore.Events.Management;
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace SharedLibraryCore.Events.Server;
|
||||
|
||||
public class ServerStatusReceiveEvent : GameServerEvent
|
||||
{
|
||||
public IStatusResponse Response { get; set; }
|
||||
public string RawData { get; set; }
|
||||
}
|
@ -1,27 +1,58 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SharedLibraryCore.Helpers
|
||||
namespace SharedLibraryCore.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// JSON converter for the build number
|
||||
/// </summary>
|
||||
public class BuildNumberJsonConverter : JsonConverter<BuildNumber>
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON converter for the build number
|
||||
/// </summary>
|
||||
public class BuildNumberJsonConverter : JsonConverter
|
||||
public override BuildNumber Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(string);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
return BuildNumber.Parse(reader.Value.ToString());
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
var stringValue = reader.GetString();
|
||||
return BuildNumber.Parse(stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, BuildNumber value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public class GameArrayJsonConverter : JsonConverter<Server.Game[]>
|
||||
{
|
||||
public override Server.Game[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
List<Server.Game> games = [];
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.EndArray)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var gameString = reader.GetString();
|
||||
var game = Enum.Parse<Server.Game>(gameString);
|
||||
games.Add(game);
|
||||
}
|
||||
|
||||
return games.ToArray();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Server.Game[] value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
|
||||
foreach (var game in value)
|
||||
{
|
||||
writer.WriteStringValue(game.ToString());
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,6 +86,11 @@ public interface IGameEventSubscriptions
|
||||
/// </summary>
|
||||
static event Func<GameScriptEvent, CancellationToken, Task> ScriptEventTriggered;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints a line that is not handled by any other cases
|
||||
/// </summary>
|
||||
static event Func<GameLogEvent, CancellationToken, Task> GameLogEventTriggered;
|
||||
|
||||
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||
{
|
||||
return coreEvent switch
|
||||
@ -100,6 +105,7 @@ public interface IGameEventSubscriptions
|
||||
ClientCommandEvent clientCommandEvent => ClientEnteredCommand?.InvokeAsync(clientCommandEvent, token) ?? Task.CompletedTask,
|
||||
ClientMessageEvent clientMessageEvent => ClientMessaged?.InvokeAsync(clientMessageEvent, token) ?? Task.CompletedTask,
|
||||
GameScriptEvent gameScriptEvent => ScriptEventTriggered?.InvokeAsync(gameScriptEvent, token) ?? Task.CompletedTask,
|
||||
GameLogEvent gameLogEvent => GameLogEventTriggered?.InvokeAsync(gameLogEvent, token) ?? Task.CompletedTask,
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
@ -116,5 +122,6 @@ public interface IGameEventSubscriptions
|
||||
ClientMessaged = null;
|
||||
ClientEnteredCommand = null;
|
||||
ScriptEventTriggered = null;
|
||||
GameLogEventTriggered = null;
|
||||
}
|
||||
}
|
||||
|
8
SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs
Normal file
8
SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace SharedLibraryCore.Interfaces.Events;
|
||||
|
||||
public interface IGameScriptEvent
|
||||
{
|
||||
string ScriptData { get; set; }
|
||||
string EventName { get; }
|
||||
void ParseArguments();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace SharedLibraryCore.Interfaces.Events;
|
||||
|
||||
public interface IGameScriptEventFactory
|
||||
{
|
||||
IGameScriptEvent Create(string eventType, string logData);
|
||||
}
|
@ -73,6 +73,11 @@ public interface IGameServerEventSubscriptions
|
||||
/// </summary>
|
||||
static event Func<ServerValueSetCompleteEvent, CancellationToken, Task> ServerValueSetCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a server's status response is received
|
||||
/// </summary>
|
||||
static event Func<ServerStatusReceiveEvent, CancellationToken, Task> ServerStatusReceived;
|
||||
|
||||
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||
{
|
||||
return coreEvent switch
|
||||
@ -88,6 +93,7 @@ public interface IGameServerEventSubscriptions
|
||||
ServerValueReceiveEvent serverValueReceiveEvent => ServerValueReceived?.InvokeAsync(serverValueReceiveEvent, token) ?? Task.CompletedTask,
|
||||
ServerValueSetRequestEvent serverValueSetRequestEvent => ServerValueSetRequested?.InvokeAsync(serverValueSetRequestEvent, token) ?? Task.CompletedTask,
|
||||
ServerValueSetCompleteEvent serverValueSetCompleteEvent => ServerValueSetCompleted?.InvokeAsync(serverValueSetCompleteEvent, token) ?? Task.CompletedTask,
|
||||
ServerStatusReceiveEvent serverStatusReceiveEvent => ServerStatusReceived?.InvokeAsync(serverStatusReceiveEvent, token) ?? Task.CompletedTask,
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
@ -19,6 +20,9 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <returns></returns>
|
||||
Task Kick(string reason, EFClient target, EFClient origin, EFPenalty previousPenalty = null);
|
||||
|
||||
IPEndPoint ResolvedIpEndPoint { get; }
|
||||
IRConParser RconParser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Execute a server command
|
||||
/// </summary>
|
||||
@ -35,72 +39,72 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="token"><see cref="CancellationToken"/></param>
|
||||
/// <returns></returns>
|
||||
Task SetDvarAsync(string name, object value, CancellationToken token = default);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Time the most recent match ended
|
||||
/// </summary>
|
||||
DateTime? MatchEndTime { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Time the current match started
|
||||
/// </summary>
|
||||
DateTime? MatchStartTime { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// List of connected clients
|
||||
/// </summary>
|
||||
IReadOnlyList<EFClient> ConnectedClients { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Game code corresponding to the development studio project
|
||||
/// </summary>
|
||||
Reference.Game GameCode { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the anticheat/custom callbacks/live radar integration is enabled
|
||||
/// </summary>
|
||||
bool IsLegacyGameIntegrationEnabled { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for the server (typically ip:port)
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Network address the server is listening on
|
||||
/// </summary>
|
||||
string ListenAddress { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Network port the server is listening on
|
||||
/// </summary>
|
||||
int ListenPort { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Name of the server (hostname)
|
||||
/// </summary>
|
||||
string ServerName { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Current gametype
|
||||
/// </summary>
|
||||
string Gametype { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Game password (required to join)
|
||||
/// </summary>
|
||||
string GamePassword { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Number of private client slots
|
||||
/// </summary>
|
||||
int PrivateClientSlots { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Current map the game server is running
|
||||
/// </summary>
|
||||
Map Map { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Database id for EFServer table and references
|
||||
/// </summary>
|
||||
|
@ -41,6 +41,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
ILogger GetLogger(long serverId);
|
||||
|
||||
IList<Server> GetServers();
|
||||
List<Server> Servers { get; }
|
||||
IList<IManagerCommand> GetCommands();
|
||||
IList<MessageToken> GetMessageTokens();
|
||||
IList<EFClient> GetActiveClients();
|
||||
|
@ -15,35 +15,35 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// stores the game/client specific version (usually the value of the "version" DVAR)
|
||||
/// </summary>
|
||||
string Version { get; }
|
||||
string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the game name (usually the internal studio iteration ie: IW4, T5 etc...)
|
||||
/// </summary>
|
||||
Game GameName { get; }
|
||||
Game GameName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the game supports generating a log path from DVAR retrieval
|
||||
/// of fs_game, fs_basepath, g_log
|
||||
/// </summary>
|
||||
bool CanGenerateLogPath { get; }
|
||||
bool CanGenerateLogPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the name of the parser
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the type of rcon engine
|
||||
/// eg: COD, Source
|
||||
/// </summary>
|
||||
string RConEngine { get; }
|
||||
string RConEngine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates that the game does not log to the mods folder (when mod is loaded),
|
||||
/// but rather always to the fs_basegame directory
|
||||
/// </summary>
|
||||
bool IsOneLog { get; }
|
||||
bool IsOneLog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// retrieves the value of a given DVAR
|
||||
@ -54,7 +54,8 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="fallbackValue">default value to return if dvar retrieval fails</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
|
||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default,
|
||||
CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// set value of DVAR by name
|
||||
@ -65,7 +66,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// executes a console command on the server
|
||||
/// </summary>
|
||||
|
@ -10,74 +10,74 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// stores the command format for console commands
|
||||
/// </summary>
|
||||
CommandPrefix CommandPrefixes { get; }
|
||||
CommandPrefix CommandPrefixes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing get status response
|
||||
/// </summary>
|
||||
ParserRegex Status { get; }
|
||||
ParserRegex Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing the map line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex MapStatus { get; }
|
||||
ParserRegex MapStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing the gametype line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex GametypeStatus { get; }
|
||||
ParserRegex GametypeStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing hostname line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex HostnameStatus { get; }
|
||||
ParserRegex HostnameStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing max players line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex MaxPlayersStatus { get; }
|
||||
ParserRegex MaxPlayersStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing get DVAR responses
|
||||
/// </summary>
|
||||
ParserRegex Dvar { get; }
|
||||
ParserRegex Dvar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing the header of a status response
|
||||
/// </summary>
|
||||
ParserRegex StatusHeader { get; }
|
||||
ParserRegex StatusHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the expected response message from rcon when the server is not running
|
||||
/// </summary>
|
||||
string ServerNotRunningResponse { get; }
|
||||
string ServerNotRunningResponse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the application should wait for response from server
|
||||
/// when executing a command
|
||||
/// </summary>
|
||||
bool WaitForResponse { get; }
|
||||
bool WaitForResponse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates the format expected for parsed guids
|
||||
/// </summary>
|
||||
NumberStyles GuidNumberStyle { get; }
|
||||
NumberStyles GuidNumberStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies simple mappings for dvar names in scenarios where the needed
|
||||
/// information is not stored in a traditional dvar name
|
||||
/// </summary>
|
||||
IDictionary<string, string> OverrideDvarNameMapping { get; }
|
||||
IDictionary<string, string> OverrideDvarNameMapping { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the default dvar values for games that don't support certain dvars
|
||||
/// </summary>
|
||||
IDictionary<string, string> DefaultDvarValues { get; }
|
||||
IDictionary<string, string> DefaultDvarValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// contains a setup of commands that have override timeouts
|
||||
/// </summary>
|
||||
IDictionary<string, int?> OverrideCommandTimeouts { get; }
|
||||
IDictionary<string, int?> OverrideCommandTimeouts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies how many lines can be used for ingame notice
|
||||
@ -87,29 +87,30 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// specifies how many characters can be displayed per notice line
|
||||
/// </summary>
|
||||
int NoticeMaxCharactersPerLine { get; }
|
||||
int NoticeMaxCharactersPerLine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the characters used to split a line
|
||||
/// </summary>
|
||||
string NoticeLineSeparator { get; }
|
||||
string NoticeLineSeparator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default port the game listens to RCon requests on
|
||||
/// </summary>
|
||||
int? DefaultRConPort { get; }
|
||||
int? DefaultRConPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default Indicator of where the game is installed (ex file path or registry entry)
|
||||
/// </summary>
|
||||
string DefaultInstallationDirectoryHint { get; }
|
||||
string DefaultInstallationDirectoryHint { get; set; }
|
||||
|
||||
ColorCodeMapping ColorCodeMapping { get; }
|
||||
ColorCodeMapping ColorCodeMapping { get; set; }
|
||||
|
||||
short FloodProtectInterval { get; set; }
|
||||
|
||||
short FloodProtectInterval { get; }
|
||||
/// <summary>
|
||||
/// indicates if diacritics (accented characters) should be normalized
|
||||
/// </summary>
|
||||
bool ShouldRemoveDiacritics { get; }
|
||||
bool ShouldRemoveDiacritics { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -31,5 +31,10 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// active clients
|
||||
/// </summary>
|
||||
EFClient[] Clients { get; }
|
||||
|
||||
/// <summary>
|
||||
/// raw text data from the game server
|
||||
/// </summary>
|
||||
string[] RawResponse { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace SharedLibraryCore.Localization
|
||||
@ -8,6 +9,8 @@ namespace SharedLibraryCore.Localization
|
||||
{
|
||||
private string localizationName;
|
||||
|
||||
public Layout() { }
|
||||
|
||||
public Layout(Dictionary<string, string> set)
|
||||
{
|
||||
LocalizationIndex = new TranslationLookup
|
||||
@ -27,7 +30,7 @@ namespace SharedLibraryCore.Localization
|
||||
}
|
||||
|
||||
public TranslationLookup LocalizationIndex { get; set; }
|
||||
public CultureInfo Culture { get; private set; }
|
||||
[JsonIgnore] public CultureInfo Culture { get; private set; }
|
||||
}
|
||||
|
||||
public class TranslationLookup : ITranslationLookup
|
||||
@ -47,4 +50,4 @@ namespace SharedLibraryCore.Localization
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,11 @@ namespace SharedLibraryCore.Database.Models
|
||||
[NotMapped]
|
||||
public string TimeSinceLastConnectionString => (DateTime.UtcNow - LastConnection).HumanizeForCurrentCulture();
|
||||
|
||||
public DateTimeOffset LastCommandExecutionAttempt { get; set; } = DateTimeOffset.MinValue;
|
||||
|
||||
[NotMapped]
|
||||
public int CommandExecutionAttempts { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
// this is kinda dirty, but I need localizable level names
|
||||
public ClientPermission ClientPermission => new ClientPermission
|
||||
|
@ -434,6 +434,7 @@ namespace SharedLibraryCore
|
||||
|
||||
public abstract Task<long> GetIdForServer(Server server = null);
|
||||
|
||||
[Obsolete("Use the ScriptPluginExtension helper")]
|
||||
public EFClient GetClientByNumber(int clientNumber) =>
|
||||
GetClientsAsList().FirstOrDefault(client => client.ClientNumber == clientNumber);
|
||||
}
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2023.4.5.1</Version>
|
||||
<Version>2024.01.01.1</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -19,7 +19,7 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<PackageVersion>2023.4.5.1</PackageVersion>
|
||||
<PackageVersion>2024.06.22.1</PackageVersion>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
@ -34,37 +34,31 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="11.2.1" />
|
||||
<PackageReference Include="FluentValidation" Version="11.9.2" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="Humanizer.Core.ru" Version="2.14.1" />
|
||||
<PackageReference Include="Humanizer.Core.de" Version="2.14.1" />
|
||||
<PackageReference Include="Humanizer.Core.es" Version="2.14.1" />
|
||||
<PackageReference Include="Humanizer.Core.pt" Version="2.14.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Data\Data.csproj">
|
||||
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
|
||||
<IncludeAssets>Data.dll</IncludeAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Data\Data.csproj">
|
||||
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
|
||||
<IncludeAssets>Data.dll</IncludeAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="if not exist "$(ProjectDir)..\BUILD" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD"
)
)
if not exist "$(ProjectDir)..\BUILD\Plugins" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD\Plugins"
)
)" />
|
||||
</Target>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
@ -868,7 +868,14 @@ namespace SharedLibraryCore
|
||||
{
|
||||
try
|
||||
{
|
||||
return await server.RconParser.GetStatusAsync(server.RemoteConnection, token);
|
||||
var response = await server.RconParser.GetStatusAsync(server.RemoteConnection, token);
|
||||
|
||||
server.Manager.QueueEvent(new ServerStatusReceiveEvent
|
||||
{
|
||||
Response = response
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
catch (TaskCanceledException)
|
||||
@ -1332,6 +1339,14 @@ namespace SharedLibraryCore
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
public static TimeSpan GetExponentialBackoffDelay(int retryCount, int staticDelay = 5)
|
||||
{
|
||||
var maxTimeout = TimeSpan.FromMinutes(2.1);
|
||||
const double factor = 2.0;
|
||||
var delay = Math.Min(staticDelay + Math.Pow(factor, retryCount - 1), maxTimeout.TotalSeconds);
|
||||
return TimeSpan.FromSeconds(delay);
|
||||
}
|
||||
|
||||
public static void ExecuteAfterDelay(TimeSpan duration, Func<CancellationToken, Task> action, CancellationToken token = default) =>
|
||||
ExecuteAfterDelay((int)duration.TotalMilliseconds, action, token);
|
||||
|
@ -10,10 +10,9 @@ using SharedLibraryCore.Interfaces;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WebfrontCore.ViewModels;
|
||||
|
||||
namespace WebfrontCore.Controllers
|
||||
@ -92,15 +91,15 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
var file = JObject.Parse(content);
|
||||
var jsonDocument = JsonDocument.Parse(content);
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
catch (JsonException ex)
|
||||
{
|
||||
return BadRequest($"{fileName}: {ex.Message}");
|
||||
}
|
||||
|
||||
var path = System.IO.Path.Join(Utilities.OperatingDirectory, "Configuration",
|
||||
fileName.Replace($"{System.IO.Path.DirectorySeparatorChar}", ""));
|
||||
var path = Path.Join(Utilities.OperatingDirectory, "Configuration",
|
||||
fileName.Replace($"{Path.DirectorySeparatorChar}", ""));
|
||||
|
||||
// todo: move into a service at some point
|
||||
if (!System.IO.File.Exists(path))
|
||||
|
@ -26,6 +26,11 @@ public class InteractionController : BaseController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (Client.Level < interactionData.MinimumPermission)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
ViewBag.Title = interactionData.Description;
|
||||
var meta = HttpContext.Request.Query.ToDictionary(key => key.Key, value => value.Value.ToString());
|
||||
var result = await _interactionRegistration.ProcessInteraction(interactionName, Client.ClientId, meta: meta, token: token);
|
||||
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:61369/",
|
||||
"sslPort": 0
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
<environment include="Development">
|
||||
<link rel="stylesheet" href="~/lib/halfmoon/css/halfmoon-variables.css"/>
|
||||
<link rel="stylesheet" href="/css/src/main.css"/>
|
||||
<link rel="stylesheet" href="/css/open-iconic.css"/>
|
||||
<link rel="stylesheet" href="/css/open-iconic-bootstrap-override.css"/>
|
||||
</environment>
|
||||
<environment include="Production">
|
||||
<link rel="stylesheet" href="~/css/global.min.css?version=@ViewBag.Version"/>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RazorCompileOnBuild Condition="'$(CONFIG)'!='Debug'">true</RazorCompileOnBuild>
|
||||
<RazorCompiledOnPublish Condition="'$(CONFIG)'!='Debug'">true</RazorCompiledOnPublish>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
@ -33,22 +33,18 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\**\*.*" CopyToPublishDirectory="Never" />
|
||||
<Content Update="wwwroot\css\src\global.css" CopyToPublishDirectory="Never" />
|
||||
<Content Update="wwwroot\css\src\global.min.css" CopyToPublishDirectory="Never" />
|
||||
<Content Update="Views\*.*" CopyToPublishDirectory="Never" />
|
||||
<None Include="wwwroot\css\global.min.css" CopyToPublishDirectory="PreserveNewest" />
|
||||
<None Include="wwwroot\js\global.min.js" CopyToPublishDirectory="PreserveNewest" />
|
||||
<None Include="wwwroot\images\**\*.*" CopyToPublishDirectory="PreserveNewest" />
|
||||
<Content Remove="wwwroot\css\src\main.css.map" />
|
||||
<Content Remove="dotnet-bundle.runtimeconfig.json" />
|
||||
<Content Remove="dotnet-bundle.deps.json" />
|
||||
<None Remove="Properties\launchSettings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BuildWebCompiler2022" Version="1.14.10" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ConcurrencyLimiter" Version="6.0.16" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.8" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ConcurrencyLimiter" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -78,7 +74,4 @@
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="if $(ConfigurationName) == Debug ( 
powershell -Command wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
echo d | xcopy /f /y $(ProjectDir)wwwroot\lib\open-iconic\font\fonts $(ProjectDir)wwwroot\font\
powershell -Command "((Get-Content -path $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss -Raw) -replace '../fonts/','/font/') | Set-Content -Path $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss"
)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
@ -3,9 +3,9 @@
|
||||
"outputFileName": "wwwroot/css/global.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/lib/halfmoon/css/halfmoon-variables.min.css",
|
||||
"wwwroot/css/global.css",
|
||||
"wwwroot/lib/chart.js/dist/Chart.min.css",
|
||||
"wwwroot/css/open-iconic.css"
|
||||
"wwwroot/css/open-iconic-bootstrap-override.css",
|
||||
"wwwroot/css/main.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -1,14 +0,0 @@
|
||||
[
|
||||
{
|
||||
"outputFile": "wwwroot/css/global.css",
|
||||
"inputFile": "wwwroot/css/src/main.scss"
|
||||
},
|
||||
{
|
||||
"outputFile": "wwwroot/css/src/profile.css",
|
||||
"inputFile": "wwwroot/css/src/profile.scss"
|
||||
},
|
||||
{
|
||||
"outputFile": "wwwroot/css/open-iconic.css",
|
||||
"inputFile": "wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss"
|
||||
}
|
||||
]
|
@ -1,49 +0,0 @@
|
||||
{
|
||||
"compilers": {
|
||||
"less": {
|
||||
"autoPrefix": "",
|
||||
"cssComb": "none",
|
||||
"ieCompat": true,
|
||||
"strictMath": false,
|
||||
"strictUnits": false,
|
||||
"relativeUrls": true,
|
||||
"rootPath": "",
|
||||
"sourceMapRoot": "",
|
||||
"sourceMapBasePath": "",
|
||||
"sourceMap": false
|
||||
},
|
||||
"sass": {
|
||||
"includePath": "",
|
||||
"indentType": "space",
|
||||
"indentWidth": 2,
|
||||
"outputStyle": "nested",
|
||||
"Precision": 5,
|
||||
"relativeUrls": true,
|
||||
"sourceMapRoot": "",
|
||||
"sourceMap": false
|
||||
},
|
||||
"stylus": {
|
||||
"sourceMap": false
|
||||
},
|
||||
"babel": {
|
||||
"sourceMap": false
|
||||
},
|
||||
"coffeescript": {
|
||||
"bare": false,
|
||||
"runtimeMode": "node",
|
||||
"sourceMap": false
|
||||
}
|
||||
},
|
||||
"minifiers": {
|
||||
"css": {
|
||||
"enabled": true,
|
||||
"termSemicolons": true,
|
||||
"gzip": false
|
||||
},
|
||||
"javascript": {
|
||||
"enabled": true,
|
||||
"termSemicolons": true,
|
||||
"gzip": false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user