Skip to content

Commit

Permalink
feat: initial version (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rushaway authored Jan 29, 2024
1 parent 4c9ab54 commit a8f44a4
Show file tree
Hide file tree
Showing 6 changed files with 384 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
98 changes: 98 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: CI

on:
push:
branches:
- main
- master
tags:
- '*'
pull_request:
branches:
- main
- master

jobs:
build:
name: "Build"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04]
include:
- os: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Build sourcemod plugin
uses: maxime1907/action-sourceknight@v1
with:
cmd: build

- name: Create package
run: |
mkdir -p /tmp/package
cp -R .sourceknight/package/* /tmp/package
- name: Upload build archive for test runners
uses: actions/upload-artifact@v3
with:
name: package
path: /tmp/package

tag:
name: Tag
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'

- uses: dev-drprasad/[email protected]
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
with:
delete_release: true
tag_name: latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- uses: rickstaa/action-create-tag@v1
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
with:
tag: "latest"
github_token: ${{ secrets.GITHUB_TOKEN }}

release:
name: Release
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
needs: [build, tag]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v3

- name: Versioning
run: |
version="latest"
if [[ "${{ github.ref_type }}" == 'tag' ]]; then
version=`echo $GITHUB_REF | sed "s/refs\/tags\///"`;
fi
echo "RELEASE_VERSION=$version" >> $GITHUB_ENV
- name: Package
run: |
ls -Rall
if [ -d "./package/" ]; then
cd ./package/
tar -czf ../${{ github.event.repository.name }}-${{ env.RELEASE_VERSION }}.tar.gz -T <(\ls -1)
cd -
fi
- name: Release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: '*.tar.gz'
tag: ${{ env.RELEASE_VERSION }}
file_glob: true
overwrite: true
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
build/
release/

.DS_Store
.vscode

*.smx
plugins/
.sourceknight
.venv
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# sm-plugin-AntiBhopCheat-discord
sm-plugin-AntiBhopCheat-discord
> [!IMPORTANT]
> Requirement: [sm-plugin-AntiBhopCheat](https://github.com/srcdslab/sm-plugin-AntiBhopCheat)
AntiBhopCheat detections notifications to discord
![Preview](https://i.imgur.com/T2NYkTc.png)
215 changes: 215 additions & 0 deletions addons/sourcemod/scripting/AntiBhopCheat_Discord.sp
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#include <utilshelper>
#include <discordWebhookAPI>

#undef REQUIRE_PLUGIN
#tryinclude <SelectiveBhop>
#tryinclude <ExtendedDiscord>
#tryinclude <sourcebanschecker>
#define REQUIRE_PLUGIN

#define WEBHOOK_MSG_MAX_SIZE 2000
#define WEBHOOK_URL_MAX_SIZE 1000
#define WEBHOOK_THREAD_NAME_MAX_SIZE 100

ConVar g_cvCountBots, g_cvWebhook, g_cvWebhookRetry, g_cvChannelType;
ConVar g_cvThreadName, g_cvThreadID, g_cvAvatar;

char g_sMap[PLATFORM_MAX_PATH];
char g_sPluginName[256];
bool g_Plugin_SourceBans = false;
bool g_Plugin_ExtDiscord = false;

public Plugin myinfo =
{
name = "AntiBhopCheat Discord",
author = ".Rushaway",
description = "Send webhook when a bhop cheat is detected",
version = "1.0.0",
url = "https://github.com/srcdslab/sm-plugin-AntiBhopCheat-discord"
};

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
RegPluginLibrary("AntiBhopCheat_Discord");
return APLRes_Success;
}

public void OnPluginStart()
{
g_cvCountBots = CreateConVar("sm_antibhopcheat_count_bots", "1", "Should we count bots as players ?[0 = No, 1 = Yes]", FCVAR_NOTIFY, true, 0.0, true, 1.0);
g_cvWebhook = CreateConVar("sm_antibhopcheat_discord_webhook", "", "The webhook URL of your Discord channel.", FCVAR_PROTECTED);
g_cvWebhookRetry = CreateConVar("sm_antibhopcheat_discord_webhook_retry", "3", "Number of retries if webhook fails.", FCVAR_PROTECTED);
g_cvAvatar = CreateConVar("sm_antibhopcheat_discord_avatar", "https://avatars.githubusercontent.com/u/110772618?s=200&v=4", "URL to Avatar image.");
g_cvChannelType = CreateConVar("sm_antibhopcheat_discord_channel_type", "0", "Type of your channel: (1 = Thread, 0 = Classic Text channel");

/* Thread config */
g_cvThreadName = CreateConVar("sm_antibhopcheat_discord_threadname", "AntiBhopCheat - New detection", "The Thread Name of your Discord forums. (If not empty, will create a new thread)", FCVAR_PROTECTED);
g_cvThreadID = CreateConVar("sm_antibhopcheat_discord_threadid", "0", "If thread_id is provided, the message will send in that thread.", FCVAR_PROTECTED);
AutoExecConfig(true);

GetPluginInfo(INVALID_HANDLE, PlInfo_Name, g_sPluginName, sizeof(g_sPluginName));
}

public void OnAllPluginsLoaded()
{
g_Plugin_SourceBans = LibraryExists("sourcebans++");
g_Plugin_ExtDiscord = LibraryExists("ExtendedDiscord");
}

public void OnLibraryAdded(const char[] sName)
{
if (strcmp(sName, "sourcebans++", false) == 0)
g_Plugin_SourceBans = true;
if (strcmp(sName, "ExtendedDiscord", false) == 0)
g_Plugin_ExtDiscord = true;
}

public void OnLibraryRemoved(const char[] sName)
{
if (strcmp(sName, "sourcebans++", false) == 0)
g_Plugin_SourceBans = false;
if (strcmp(sName, "ExtendedDiscord", false) == 0)
g_Plugin_ExtDiscord = false;
}

public void OnMapInit(const char[] mapName)
{
FormatEx(g_sMap, sizeof(g_sMap), mapName);
}

public void AntiBhopCheat_OnClientDetected(int client, char[] sReason, char[] sStats)
{
char sWebhookURL[WEBHOOK_URL_MAX_SIZE];
g_cvWebhook.GetString(sWebhookURL, sizeof sWebhookURL);
if (!sWebhookURL[0]) {
LogError("[%s] No webhook found or specified.", g_sPluginName);
return;
}

char sAuth[32];
GetClientAuthId(client, AuthId_Steam2, sAuth, sizeof(sAuth), false);

char sPlayer[256];
if (g_Plugin_SourceBans) {
int iClientBans = 0;
int iClientComms = 0;

#if defined _sourcebanschecker_included
iClientBans = SBPP_CheckerGetClientsBans(client);
iClientComms = SBPP_CheckerGetClientsComms(client);
#endif

FormatEx(sPlayer, sizeof(sPlayer), "%N (%d bans - %d comms) [%s] %s.", client, iClientBans, iClientComms, sAuth, sReason);
} else {
FormatEx(sPlayer, sizeof(sPlayer), "%N [%s] %s.", client, sAuth, sReason);
}

char sTime[64];
int iTime = GetTime();
FormatTime(sTime, sizeof(sTime), "Date : %d/%m/%Y @ %H:%M:%S", iTime);

char sCount[32];
int iMaxPlayers = MaxClients;
int iConnected = GetPlayerCount(g_cvCountBots.BoolValue);
FormatEx(sCount, sizeof(sCount), "Players : %d/%d", iConnected, iMaxPlayers);

char sHeader[512], sMessage[WEBHOOK_MSG_MAX_SIZE];
FormatEx(sHeader, sizeof(sHeader), "%s \nCurrent map : %s \n%s \n%s", sPlayer, g_sMap, sTime, sCount);

// Discord character limit is 2000 (discord msg + stats)
if (strlen(sHeader) + strlen(sStats) < WEBHOOK_MSG_MAX_SIZE)
{
FormatEx(sMessage, sizeof(sMessage), "```%s \n\n%s```", sHeader, sStats);
ReplaceString(sMessage, sizeof(sMessage), "\\n", "\n");
SendWebHook(sMessage, sWebhookURL);
}
else
{
// First part: Header infos
FormatEx(sMessage, sizeof(sMessage), "```%s```", sHeader);
ReplaceString(sMessage, sizeof(sMessage), "\\n", "\n");
SendWebHook(sMessage, sWebhookURL);

// Second part: Stats
FormatEx(sMessage, sizeof(sMessage), "```%s```", sStats);
ReplaceString(sMessage, sizeof(sMessage), "\\n", "\n");
SendWebHook(sMessage, sWebhookURL);
}
}

stock void SendWebHook(char sMessage[WEBHOOK_MSG_MAX_SIZE], char sWebhookURL[WEBHOOK_URL_MAX_SIZE])
{
Webhook webhook = new Webhook(sMessage);

char sThreadID[32], sThreadName[WEBHOOK_THREAD_NAME_MAX_SIZE];
g_cvThreadID.GetString(sThreadID, sizeof sThreadID);
g_cvThreadName.GetString(sThreadName, sizeof sThreadName);

bool IsThread = g_cvChannelType.BoolValue;

if (IsThread) {
if (!sThreadName[0] && !sThreadID[0]) {
LogError("[%s] Thread Name or ThreadID not found or specified.", g_sPluginName);
delete webhook;
return;
} else {
if (strlen(sThreadName) > 0) {
webhook.SetThreadName(sThreadName);
sThreadID[0] = '\0';
}
}
}

char sAvatar[256];
g_cvAvatar.GetString(sAvatar, sizeof(sAvatar));

if (strlen(sAvatar) > 0)
webhook.SetAvatarURL(sAvatar);

DataPack pack = new DataPack();
if (IsThread && strlen(sThreadName) <= 0 && strlen(sThreadID) > 0)
pack.WriteCell(1);
else
pack.WriteCell(0);
pack.WriteString(sMessage);
pack.WriteString(sWebhookURL);

webhook.Execute(sWebhookURL, OnWebHookExecuted, pack, sThreadID);
delete webhook;
}

public void OnWebHookExecuted(HTTPResponse response, DataPack pack)
{
static int retries = 0;
pack.Reset();

bool IsThreadReply = pack.ReadCell();

char sMessage[WEBHOOK_MSG_MAX_SIZE], sWebhookURL[WEBHOOK_URL_MAX_SIZE];
pack.ReadString(sMessage, sizeof(sMessage));
pack.ReadString(sWebhookURL, sizeof(sWebhookURL));

delete pack;

if ((!IsThreadReply && response.Status != HTTPStatus_OK) || (IsThreadReply && response.Status != HTTPStatus_NoContent)) {
if (retries < g_cvWebhookRetry.IntValue) {
PrintToServer("[%s] Failed to send the webhook. Resending it .. (%d/%d)", g_sPluginName, retries, g_cvWebhookRetry.IntValue);
SendWebHook(sMessage, sWebhookURL);
retries++;
return;
}
} else {
if (!g_Plugin_ExtDiscord) {
LogError("[%s] Failed to send the webhook after %d retries, aborting.", g_sPluginName, retries);
LogError("[%s] Failed message : %s", g_sPluginName, sMessage);
}
#if defined _extendeddiscord_included
else {
ExtendedDiscord_LogError("[%s] Failed to send the webhook after %d retries, aborting.", g_sPluginName, retries);
ExtendedDiscord_LogError("[%s] Failed message : %s", g_sPluginName, sMessage);
}
#endif
}

retries = 0;
}
51 changes: 51 additions & 0 deletions sourceknight.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
project:
sourceknight: 0.2
name: AntiBhopCheat_Discord
dependencies:
- name: sourcemod
type: tar
version: 1.11.0-git6934
location: https://sm.alliedmods.net/smdrop/1.11/sourcemod-1.11.0-git6934-linux.tar.gz
unpack:
- source: /addons
dest: /addons

- name: utilshelper
type: git
repo: https://github.com/srcdslab/sm-plugin-UtilsHelper
unpack:
- source: /addons/sourcemod/scripting/include
dest: /addons/sourcemod/scripting/include

- name: discordwebapi
type: git
repo: https://github.com/srcdslab/sm-plugin-DiscordWebhookAPI
unpack:
- source: /include
dest: /addons/sourcemod/scripting/include

- name: sourcebans-pp
type: git
repo: https://github.com/srcdslab/sourcebans-pp
unpack:
- source: /game/addons
dest: /addons

- name: Extended-Discord
type: git
repo: https://github.com/srcdslab/sm-plugin-Extended-Discord
unpack:
- source: /addons/sourcemod/scripting/include
dest: /addons/sourcemod/scripting/include

- name: selectivebhop
type: git
repo: https://github.com/srcdslab/sm-plugin-SelectiveBhop
unpack:
- source: /addons/sourcemod/scripting/include
dest: /addons/sourcemod/scripting/include

root: /
output: /addons/sourcemod/plugins
targets:
- AntiBhopCheat_Discord

0 comments on commit a8f44a4

Please sign in to comment.