diff --git a/event_rewards/php/bootstrap_user_upgrade.php b/event_rewards/php/bootstrap_user_upgrade.php
new file mode 100644
index 00000000..474ec545
--- /dev/null
+++ b/event_rewards/php/bootstrap_user_upgrade.php
@@ -0,0 +1,38 @@
+repository('XF:UserUpgrade');
+
diff --git a/event_rewards/php/private_user_upgrade_api.php b/event_rewards/php/private_user_upgrade_api.php
new file mode 100644
index 00000000..78609f55
--- /dev/null
+++ b/event_rewards/php/private_user_upgrade_api.php
@@ -0,0 +1,156 @@
+ - Required: A respective ID query string should be set. E.g. the Steam provider requires a "steam_id" query string.
+ days_amount - Required: The amount of days to give as userUpgrade. this is related to event rewards.
+*/
+
+// --- CONFIG START ---
+// API key for authorization
+$api_key = "k9Mk2lA3mF9Sk0FoaD";
+
+// Provider name with given query string
+$provider_config = array(
+ "th_cap_steam" => "steam_id",
+ "minecraft" => "uuid"
+);
+
+// For backwards compatibility
+$default_provider = "th_cap_steam";
+
+// Responses
+$response_error_apikey = 'API KEY INVALID';
+$response_no_account_associated = 'NOACCOUNT';
+$response_group_none = 'NOGROUP';
+$response_error_id = 'MISSING ID';
+
+
+
+// --- CONFIG END ---
+
+// TODO: Use single query string for ID?
+
+// Prints the reason and exits.
+function quitWithReason($reason, $status_code = 200){
+
+ http_response_code($status_code);
+ echo $reason;
+ die;
+}
+
+function debug_to_console($data) {
+ $output = $data;
+ if (is_array($output))
+ $output = implode(',', $output);
+ echo "";
+}
+
+
+// Check the API key
+if(!isset($_GET['api_key']) || $_GET['api_key'] !== $api_key){
+ quitWithReason($response_error_apikey, 401);
+}
+
+
+// Load XenForo stuffs
+require ('bootstrap_user_upgrade.php');
+
+// Fetch provider from query string. If none is found, use the default provider.
+$provider = isset($_GET['provider']) ? $_GET['provider'] : $default_provider;
+
+
+$days_reward = $_GET['days_reward'];
+
+//debug_to_console("$days_reward"); //will always be set.
+
+// Check if the given provider has been configured
+if(!array_key_exists($provider, $provider_config)){
+ quitWithReason($response_error_provider, 404);
+}
+
+// Check if the ID is set.
+if(!isset($_GET[$provider_config[$provider]])){
+ quitWithReason($response_error_id, 404);
+}
+
+
+// Fetch the respective ID query string.
+$provider_key = $_GET[$provider_config[$provider]];
+
+// Use finder for external accounts
+$connectedAccountFinder = \XF::finder('XF:UserConnectedAccount');
+
+// Search for external accounts with the given provider and ID
+$stAssoc = $connectedAccountFinder->where('provider', $provider)->where('provider_key', $provider_key)->fetchOne();
+
+// If no account is associated to the steam account return NOACCOUNT
+if(empty($stAssoc)){
+ quitWithReason($response_no_account_associated, 500);
+}
+
+// Load XenForo model for users
+$userFinder = \XF::finder('XF:User');
+
+// Fetch the user from the external account entry
+$user = $userFinder->where('user_id', $stAssoc->user_id)->fetchOne();
+
+// (This should not happen) If no user is found, assign no group.
+if(empty($user)){
+ // Push error to XenForo error log.
+ trigger_error("External account association exists for non-existing user ID (" . $stAssoc->user_id . ").");
+ quitWithReason($response_group_none, 500);
+}
+
+//debug_to_console("$stAssoc->user_id");
+
+// Get UserUpgrade Information
+$upgradeFinder = \XF::finder('XF:UserUpgrade');
+$userUpgrade = $upgradeFinder->where('user_upgrade_id', 1)->fetchOne(); //its 1 because it should not matter which userupgrade we pick. its actually 1 month vip.
+
+//debug_to_console("$user");
+
+//before upgrading the user lets confirm if they already have a running vip duration. if they do we update it instead of creating a new one.
+// Get Active Upgrade Information
+$userUpgradeActiveFinder = \XF::Finder('XF:UserUpgradeActive');
+$activeUserUpgrades = $userUpgradeActiveFinder->where('user_id', $stAssoc->user_id)->fetchOne();
+//debug_to_console("$activeUserUpgrades");
+
+if (isset($activeUserUpgrades))
+{
+ debug_to_console("has user upgrade");
+ $userUpgrade_new = $activeUserUpgrades->Upgrade;
+ $expireDate = $activeUserUpgrades->end_date;
+ //debug_to_console("$userUpgrade_new");
+ //debug_to_console("$expireDate");
+
+ $dateObject = date_create_from_format('U', $expireDate);
+ $intervalString = "P{$days_reward}D";
+ $interval = new DateInterval($intervalString);
+ $dateObject->add($interval);
+ $newTimestamp = $dateObject->getTimestamp();
+
+ //debug_to_console("$newTimestamp");
+ $upgradeService = $app->service('XF:User\Upgrade', $userUpgrade_new, $user);
+ $upgradeService->setEndDate($newTimestamp);
+ $upgradeService->ignoreUnpurchasable(true);
+ $upgradeService->upgrade();
+}
+else
+{
+ debug_to_console("no user upgrade so far");
+ // add days to current date
+ $date = strtotime("+$days_reward day");
+
+ // Upgrade User
+ $upgradeService = $app->service('XF:User\Upgrade', $userUpgrade, $user);
+ $upgradeService->setEndDate($date);
+ $upgradeService->ignoreUnpurchasable(true);
+ $upgradeService->upgrade();
+}
+
+quitWithReason(" end ", 200);
+
diff --git a/event_rewards/python/README.md b/event_rewards/python/README.md
new file mode 100644
index 00000000..0aa03146
--- /dev/null
+++ b/event_rewards/python/README.md
@@ -0,0 +1,7 @@
+this is meant to automatically give event rewards through some xenforo API call
+
+mysql-connector-python
+requests
+
+
+
diff --git a/event_rewards/python/main.py b/event_rewards/python/main.py
new file mode 100644
index 00000000..8532092e
--- /dev/null
+++ b/event_rewards/python/main.py
@@ -0,0 +1,71 @@
+#!/home/nonroot/handle_event_rewards/venv/bin/python3
+from settings import (get_connection_events, api_key)
+import requests
+
+unloze_user_upgrade_api = f"https://unloze.com/api/private_user_upgrade_api.php?api_key={api_key}&steam_id="
+
+def get_all_pending_event_rewards():
+ with get_connection_events() as conn:
+ with conn.cursor(buffered=True) as cur:
+ sql_statement = """
+ select steamid, event_reward_days
+ from unloze_event.rewards
+ where is_processed is false
+ """
+ cur.execute(sql_statement)
+ res = cur.fetchall()
+ conn.close()
+ return res
+
+def update_is_procssed():
+ with get_connection_events() as conn:
+ with conn.cursor(buffered=True) as cur:
+ sql_statement = """
+ update unloze_event.rewards set is_processed = true where is_processed is false
+ """
+ cur.execute(sql_statement)
+ conn.commit() #pretty gay how contextmanager cant commit and close itself like in psycopg2
+ conn.close()
+
+def steamid_to_commid(steamid):
+ steamid64ident = 76561197960265728
+ sid_split = steamid.split(':')
+ commid = int(sid_split[2]) * 2
+ if sid_split[1] == '1':
+ commid += 1
+ commid += steamid64ident
+ return commid
+
+def update_steam_id_has_no_forum_account_with_steam(steamid):
+ with get_connection_events() as conn:
+ with conn.cursor(buffered=True) as cur:
+ sql_statement = """
+ update unloze_event.rewards
+ set has_no_forum_account_with_associated_steam = true
+ where has_no_forum_account_with_associated_steam is not true
+ and steamid = %s
+ """
+ cur.execute(sql_statement, [steamid])
+ conn.commit() #pretty gay how contextmanager cant commit and close itself like in psycopg2
+ conn.close()
+
+def reward_vip_to_event_winners(res):
+ for result in res:
+ steamid = result[0]
+ days_rewarded = result[1]
+ steam_community_id = steamid_to_commid(steamid)
+
+ r = requests.get(f"{unloze_user_upgrade_api}{steam_community_id}&days_reward={days_rewarded}")
+ #print("r.url: ", r.url)
+ if r.content.decode('utf8').startswith("NOACCOUNT"): #not registed with associated steam account
+ update_steam_id_has_no_forum_account_with_steam(steamid)
+
+def main():
+ res = get_all_pending_event_rewards()
+ #send request to xenforo for giving vip time
+ reward_vip_to_event_winners(res)
+ update_is_procssed() #we took them all out so we just update all rows where is_processed is false.
+
+if __name__ == '__main__':
+ main()
+
diff --git a/event_rewards/scripting/trackwinners.sp b/event_rewards/scripting/trackwinners.sp
new file mode 100644
index 00000000..55c95f9f
--- /dev/null
+++ b/event_rewards/scripting/trackwinners.sp
@@ -0,0 +1,118 @@
+#pragma semicolon 1
+#define PLUGIN_AUTHOR "jenz"
+#define PLUGIN_VERSION "1.0"
+#include
+#include
+
+Database g_hDatabase;
+int g_ireward_days = 0;
+char g_cadmin_sid[64];
+char g_cadmin_escaped_name[256];
+
+public Plugin myinfo =
+{
+ name = "unloze track event winners",
+ author = PLUGIN_AUTHOR,
+ description = "during events it tracks through the admin command if people won the event",
+ version = PLUGIN_VERSION,
+ url = "www.unloze.com"
+};
+
+
+public void OnPluginStart()
+{
+ if (!g_hDatabase)
+ {
+ Database.Connect(SQL_OnDatabaseConnect, "Event_notifier");
+ }
+ RegAdminCmd("sm_trackwinners", Cmd_trackwinners, ADMFLAG_GENERIC, "Use this each round where humans can win VIP. the alive humans on that round will have their steam IDs stored. Also specify a day as the number.");
+ HookEvent("round_start", Event_RoundStart);
+ HookEvent("round_end", Event_RoundEnd);
+}
+
+public void Event_RoundStart(Handle event, const char[] name, bool dontBroadcast)
+{
+ g_ireward_days = 0;
+ Format(g_cadmin_sid, sizeof(g_cadmin_sid), "");
+ Format(g_cadmin_escaped_name, sizeof(g_cadmin_escaped_name), "");
+}
+
+public void Event_RoundEnd(Handle event, const char[] name, bool dontBroadcast)
+{
+ int winner_team = GetEventInt(event, "winner");
+ if (winner_team == 3 && g_ireward_days > 0) //ct won the round and are supposed to get vip for it.
+ {
+ char current_map[128];
+ GetCurrentMap(current_map, sizeof(current_map));
+ for (int i = 1; i <= MaxClients; i++)
+ {
+ if (IsValidClient(i) && !IsFakeClient(i) && IsPlayerAlive(i) && GetClientTeam(i) == CS_TEAM_CT)
+ {
+ write_reward_entry(i, current_map);
+ }
+ }
+ }
+}
+
+public void write_reward_entry(int client, char[] event_map)
+{
+ char sQuery[512];
+ char sName[MAX_NAME_LENGTH];
+ GetClientName(client, sName, sizeof(sName));
+ int size2 = 2 * strlen(sName) + 1;
+ char[] sEscapedName = new char[size2 + 1];
+ g_hDatabase.Escape(sName, sEscapedName, size2 + 1);
+
+ char SID[64];
+ GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID));
+ Format(sQuery, sizeof(sQuery), "INSERT INTO `rewards` (`steamid`, `username`, `event_map`, `event_reward_days`, `admin_created_reward_steamid`, `admin_created_reward_name`) VALUES ('%s', '%s', '%s', '%i', '%s', '%s')", SID, sEscapedName, event_map, g_ireward_days, g_cadmin_sid, g_cadmin_escaped_name);
+ g_hDatabase.Query(DummyCallback, sQuery, DBPrio_Normal);
+ PrintToChat(client, "Your %i days VIP were stored for winning this round. They will be awarded in the next 2-3 hours if you have a forum account.", g_ireward_days);
+}
+
+public void DummyCallback(Handle hOwner, Handle hChild, const char[] err, any data)
+{
+ if (hOwner == null || hChild == null)
+ LogError("Query error. (%s)", err);
+}
+
+public Action Cmd_trackwinners(int client, int args)
+{
+ if (!IsValidClient(client))
+ return Plugin_Handled;
+ if (args != 1)
+ {
+ ReplyToCommand(client, "[SM] sm_trackwinners ");
+ return Plugin_Handled;
+ }
+ char sDays[65];
+ GetCmdArg(1, sDays, sizeof(sDays));
+ g_ireward_days = StringToInt(sDays);
+
+ GetClientAuthId(client, AuthId_Steam2, g_cadmin_sid, sizeof(g_cadmin_sid));
+
+ char sName[MAX_NAME_LENGTH];
+ GetClientName(client, sName, sizeof(sName));
+ int size2 = 2 * strlen(sName) + 1;
+ g_hDatabase.Escape(sName, g_cadmin_escaped_name, size2 + 1);
+
+ PrintToChatAll("Admin %N set a VIP reward of %i days for winning this round", client, g_ireward_days);
+ return Plugin_Handled;
+}
+
+public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
+{
+ if(!db || strlen(error))
+ {
+ LogError("Database error: %s", error);
+ return;
+ }
+ g_hDatabase = db;
+}
+
+stock bool IsValidClient(int client)
+{
+ if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
+ return true;
+ return false;
+}
diff --git a/event_rewards/sql/rewards_table.sql b/event_rewards/sql/rewards_table.sql
new file mode 100644
index 00000000..2ec5dd67
--- /dev/null
+++ b/event_rewards/sql/rewards_table.sql
@@ -0,0 +1,13 @@
+CREATE TABLE unloze_event.rewards (
+ `rowid` int auto_increment NOT NULL,
+ `steamid` varchar(64) not NULL,
+ `username` varchar(256) DEFAULT NULL,
+ `event_map` varchar(512) DEFAULT NULL,
+ `event_reward_days` int not NULL,
+ `created_on` datetime DEFAULT current_timestamp(),
+ `admin_created_reward_steamid` varchar(64) not NULL,
+ `admin_created_reward_name` varchar(64) not NULL,
+ `is_processed` bool default false,
+ `has_no_forum_account_with_associated_steam` bool default null,
+ PRIMARY KEY (`rowid`)
+)
diff --git a/event_rewards/systemctl/handle_event_rewards.service b/event_rewards/systemctl/handle_event_rewards.service
new file mode 100644
index 00000000..363033fc
--- /dev/null
+++ b/event_rewards/systemctl/handle_event_rewards.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=runs python script to check if there are missing event rewards to hand out.
+
+[Service]
+Type=simple
+User=nonroot
+Environment=PYTHONUNBUFFERED=1
+Environment=PATH=/home/nonroot/handle_event_rewards/venv/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin
+WorkingDirectory=/home/nonroot/handle_event_rewards
+ExecStart=/home/nonroot/handle_event_rewards/main.py
diff --git a/event_rewards/systemctl/handle_event_rewards.timer b/event_rewards/systemctl/handle_event_rewards.timer
new file mode 100644
index 00000000..52daded8
--- /dev/null
+++ b/event_rewards/systemctl/handle_event_rewards.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Checks every 3 hours if there are event rewards to hand out.
+
+[Timer]
+OnCalendar=0/3:00:00
+
+[Install]
+WantedBy=multi-user.target
+