Release 0.9.0

This commit is contained in:
2025-11-21 07:26:02 +01:00
committed by ecv
commit 472f0812e7
240 changed files with 20033 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
from .discord_webhook import DiscordWebhook
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(*args, **kwargs):
module = args[0]
permission_levels = (
module.dom.data
.get(module.get_module_identifier(), {})
.get("admins", {})
)
for steamid, level in permission_levels.items():
event_data = ['update_player_permission_level', {
'steamid': steamid,
'level': level
}]
module.trigger_action_hook(module.players, event_data=event_data)
trigger_meta = {
"description": (
"Will call the update_player_permission_level action after permissions have been retrieved from the game"
),
"main_function": main_function,
"handlers": {
"module_players/admins": main_function,
}
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,4 @@
__all__ = ["DiscordWebhook",
"DiscordEmbed"]
from .webhook import (DiscordWebhook, DiscordEmbed)

View File

@@ -0,0 +1,284 @@
import requests
import time
import datetime
import logging
import json
logger = logging.getLogger(__name__)
class DiscordWebhook:
"""
Webhook for Discord
"""
def __init__(self, url, **kwargs):
"""
Init Webhook for Discord
:param url: discord_webhook webhook url
:keyword content: the message contents
:keyword username: override the default username of the webhook
:keyword avatar_url: ooverride the default avatar of the webhook
:keyword tts: true if this is a TTS message
:keyword file: file contents
:keyword filename: file name
:keyword embeds: list of embedded rich content
:keyword proxies: dict of proxies
"""
self.url = url
self.content = kwargs.get('content')
self.username = kwargs.get('username')
self.avatar_url = kwargs.get('avatar_url')
self.tts = kwargs.get('tts', False)
self.files = kwargs.get('files', dict())
self.embeds = kwargs.get('embeds', [])
self.proxies = kwargs.get('proxies', None)
def add_file(self, file, filename):
"""
add file to webhook
:param file: file content
:param filename: filename
:return:
"""
self.files['_{}'.format(filename)] = (filename, file)
def add_embed(self, embed):
"""
add embedded rich content
:param embed: embed object or dict
"""
self.embeds.append(embed.__dict__ if isinstance(embed, DiscordEmbed) else embed)
def remove_embed(self, index):
"""
remove embedded rich content from `self.embeds`
:param index: index of embed in `self.embeds`
"""
self.embeds.pop(index)
def get_embeds(self):
"""
get all `self.embeds` as list
:return: `self.embeds`
"""
return self.embeds
def set_proxies(self, proxies):
"""
set proxies
:param proxies: dict of proxies
"""
self.proxies = proxies
@property
def json(self):
"""
convert webhook data to json
:return webhook data as json:
"""
data = dict()
embeds = self.embeds
self.embeds = list()
# convert DiscordEmbed to dict
for embed in embeds:
self.add_embed(embed)
for key, value in self.__dict__.items():
if value and key not in ['url', 'files', 'filename']:
data[key] = value
embeds_empty = all(not embed for embed in data["embeds"]) if 'embeds' in data else True
if embeds_empty and 'content' not in data and bool(self.files) is False:
logger.error('webhook message is empty! set content or embed data')
return data
def execute(self):
"""
execute Webhook
:return:
"""
if bool(self.files) is False:
response = requests.post(self.url, json=self.json, proxies=self.proxies)
else:
self.files['payload_json'] = (None, json.dumps(self.json))
response = requests.post(self.url, files=self.files, proxies=self.proxies)
if response.status_code in [200, 204]:
logger.debug("Webhook executed")
else:
logger.error('status code %s: %s' % (response.status_code, response.content.decode("utf-8")))
class DiscordEmbed:
"""
Discord Embed
"""
def __init__(self, **kwargs):
"""
Init Discord Embed
:keyword title: title of embed
:keyword description: description of embed
:keyword url: url of embed
:keyword timestamp: timestamp of embed content
:keyword color: color code of the embed as int
:keyword footer: footer information
:keyword image: image information
:keyword thumbnail: thumbnail information
:keyword video: video information
:keyword provider: provider information
:keyword author: author information
:keyword fields: fields information
"""
self.title = kwargs.get('title')
self.description = kwargs.get('description')
self.url = kwargs.get('url')
self.timestamp = kwargs.get('timestamp')
self.color = kwargs.get('color')
self.footer = kwargs.get('footer')
self.image = kwargs.get('image')
self.thumbnail = kwargs.get('thumbnail')
self.video = kwargs.get('video')
self.provider = kwargs.get('provider')
self.author = kwargs.get('author')
self.fields = kwargs.get('fields', [])
def set_title(self, title):
"""
set title of embed
:param title: title of embed
"""
self.title = title
def set_description(self, description):
"""
set description of embed
:param description: description of embed
"""
self.description = description
def set_url(self, url):
"""
set url of embed
:param url: url of embed
"""
self.url = url
def set_timestamp(self, timestamp=str(datetime.datetime.utcfromtimestamp(time.time()))):
"""
set timestamp of embed content
:param timestamp: (optional) timestamp of embed content
"""
self.timestamp = timestamp
def set_color(self, color):
"""
set color code of the embed as int
:param color: color code of the embed as int
"""
self.color = color
def set_footer(self, **kwargs):
"""
set footer information of embed
:keyword text: footer text
:keyword icon_url: url of footer icon (only supports http(s) and attachments)
:keyword proxy_icon_url: a proxied url of footer icon
"""
self.footer = {
'text': kwargs.get('text'),
'icon_url': kwargs.get('icon_url'),
'proxy_icon_url': kwargs.get('proxy_icon_url')
}
def set_image(self, **kwargs):
"""
set image of embed
:keyword url: source url of image (only supports http(s) and attachments)
:keyword proxy_url: a proxied url of the image
:keyword height: height of image
:keyword width: width of image
"""
self.image = {
'url': kwargs.get('url'),
'proxy_url': kwargs.get('proxy_url'),
'height': kwargs.get('height'),
'width': kwargs.get('width'),
}
def set_thumbnail(self, **kwargs):
"""
set thumbnail of embed
:keyword url: source url of thumbnail (only supports http(s) and attachments)
:keyword proxy_url: a proxied thumbnail of the image
:keyword height: height of thumbnail
:keyword width: width of thumbnail
"""
self.thumbnail = {
'url': kwargs.get('url'),
'proxy_url': kwargs.get('proxy_url'),
'height': kwargs.get('height'),
'width': kwargs.get('width'),
}
def set_video(self, **kwargs):
"""
set video of embed
:keyword url: source url of video
:keyword height: height of video
:keyword width: width of video
"""
self.video = {
'url': kwargs.get('url'),
'height': kwargs.get('height'),
'width': kwargs.get('width'),
}
def set_provider(self, **kwargs):
"""
set provider of embed
:keyword name: name of provider
:keyword url: url of provider
"""
self.provider = {
'name': kwargs.get('name'),
'url': kwargs.get('url'),
}
def set_author(self, **kwargs):
"""
set author of embed
:keyword name: name of author
:keyword url: url of author
:keyword icon_url: url of author icon (only supports http(s) and attachments)
:keyword proxy_icon_url: a proxied url of author icon
"""
self.author = {
'name': kwargs.get('name'),
'url': kwargs.get('url'),
'icon_url': kwargs.get('icon_url'),
'proxy_icon_url': kwargs.get('proxy_icon_url'),
}
def add_embed_field(self, **kwargs):
"""
set field of embed
:keyword name: name of the field
:keyword value: value of the field
:keyword inline: (optional) whether or not this field should display inline
"""
self.fields.append({
'name': kwargs.get('name'),
'value': kwargs.get('value'),
'inline': kwargs.get('inline', True)
})
def del_embed_field(self, index):
"""
remove field from `self.fields`
:param index: index of field in `self.fields`
"""
self.fields.pop(index)
def get_embed_fields(self):
"""
get all `self.fields` as list
:return: `self.fields`
"""
return self.fields

View File

@@ -0,0 +1,115 @@
from .discord_webhook import DiscordWebhook
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
player_steamid = regex_result.group("player_steamid")
existing_player_dict = (
module.dom.data
.get("module_players", {})
.get("elements", {})
.get(active_dataset, {})
.get(player_steamid, None)
)
last_seen_gametime_string = module.game_environment.get_last_recorded_gametime_string()
executed_trigger = False
player_dict = {}
if command in ["Authenticating", "connected"]:
if existing_player_dict is None:
player_dict = {
"name": regex_result.group("player_name"),
"steamid": player_steamid,
"pos": {
"x": 0,
"y": 0,
"z": 0,
},
"dataset": active_dataset,
"owner": player_steamid,
"last_seen_gametime": last_seen_gametime_string
}
else:
player_dict.update(existing_player_dict)
player_dict.update({
"is_online": True,
"in_limbo": True,
"is_initialized": False,
})
if command == "connected":
player_dict.update({
"id": regex_result.group("entity_id"),
"ip": regex_result.group("player_ip"),
"steamid": player_steamid,
"owner": player_steamid
})
player_name = player_dict.get("name", regex_result.group("player_name"))
payload = '{} is logging into {} at {}'.format(player_name, active_dataset, last_seen_gametime_string)
discord_payload_url = origin_module.options.get("discord_webhook", None)
if discord_payload_url is not None:
webhook = DiscordWebhook(
url=discord_payload_url,
content=payload
)
webhook.execute()
executed_trigger = True
if all([
executed_trigger is True,
active_dataset is not None,
player_steamid is not None,
len(player_dict) >= 1
]):
module.dom.data.upsert({
"module_players": {
"elements": {
active_dataset: {
player_steamid: player_dict
}
}
}
})
trigger_meta = {
"description": "reacts to telnets player discovery messages for real time responses!",
"main_function": main_function,
"triggers": [
{
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
r"\[Steamworks.NET\]\s"
r"(?P<command>.*)\s"
r"player:\s(?P<player_name>.*)\s"
r"SteamId:\s(?P<player_steamid>\d+)\s(.*)"
),
"callback": main_function
}, {
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
r"Player\s"
r"(?P<command>.*), "
r"entityid=(?P<entity_id>.*), "
r"name=(?P<player_name>.*), "
r"steamid=(?P<player_steamid>.*), "
r"steamOwner=(?P<owner_id>.*), "
r"ip=(?P<player_ip>.*)"
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,78 @@
from .discord_webhook import DiscordWebhook
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
executed_trigger = False
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
if command == "disconnected":
player_steamid = regex_result.group("player_steamid")
existing_player_dict = (
module.dom.data
.get("module_players", {})
.get("elements", {})
.get(active_dataset, {})
.get(player_steamid, {})
)
player_dict = {}
player_dict.update(existing_player_dict)
player_dict.update({
"is_online": False,
"is_initialized": False
})
player_name = player_dict.get("name", regex_result.group("player_name"))
last_seen_gametime_string = module.game_environment.get_last_recorded_gametime_string()
payload = '{} left {} at {}'.format(player_name, active_dataset, last_seen_gametime_string)
discord_payload_url = origin_module.options.get("discord_webhook", None)
if discord_payload_url is not None:
webhook = DiscordWebhook(
url=discord_payload_url,
content=payload
)
webhook.execute()
executed_trigger = True
if executed_trigger is True:
module.dom.data.upsert({
"module_players": {
"elements": {
active_dataset: {
player_steamid: player_dict
}
}
}
})
trigger_meta = {
"description": "reacts to telnets player disconnected message for real time responses!",
"main_function": main_function,
"triggers": [
{
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
r"Player\s"
r"(?P<command>.*):\s"
r"EntityID=(?P<entity_id>.*),\s"
r"PlayerID='(?P<player_steamid>\d{17})',\s"
r"OwnerID='(?P<owner_id>\d{17})',\s"
r"PlayerName='(?P<player_name>.*)'"
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,108 @@
from bot import loaded_modules_dict
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def send_player_update_to_map(*args, **kwargs):
"""Send player updates to map view via socket.io"""
module = args[0]
updated_values_dict = kwargs.get("updated_values_dict", {})
if updated_values_dict is None:
return
# Get steamid and dataset
steamid = updated_values_dict.get("steamid")
dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
if not all([dataset, steamid]):
return
# Get full player data from DOM
player_dict = (
module.dom.data
.get("module_players", {})
.get("elements", {})
.get(dataset, {})
.get(steamid, {})
)
if not player_dict:
return
# Check which clients are viewing the map
locations_module = loaded_modules_dict.get("module_locations")
if not locations_module:
return
for clientid in module.webserver.connected_clients.keys():
# Check if client is viewing the map in the locations widget
current_view = (
locations_module.dom.data
.get("module_locations", {})
.get("visibility", {})
.get(clientid, {})
.get("current_view", None)
)
if current_view != "map":
continue
# Prepare player update data
pos = player_dict.get("pos", {})
if not pos:
continue
player_update_data = {
"steamid": steamid,
"name": player_dict.get("name", "Player"),
"level": player_dict.get("level", 0),
"health": player_dict.get("health", 0),
"zombies": player_dict.get("zombies", 0),
"deaths": player_dict.get("deaths", 0),
"players": player_dict.get("players", 0),
"score": player_dict.get("score", 0),
"ping": player_dict.get("ping", 0),
"is_muted": player_dict.get("is_muted", False),
"is_authenticated": player_dict.get("is_authenticated", False),
"in_limbo": player_dict.get("in_limbo", False),
"is_initialized": player_dict.get("is_initialized", False),
"permission_level": player_dict.get("permission_level", None),
"dataset": dataset,
"position": {
"x": float(pos.get("x", 0)),
"y": float(pos.get("y", 0)),
"z": float(pos.get("z", 0))
}
}
module.webserver.send_data_to_client_hook(
module,
payload=player_update_data,
data_type="player_position_update",
clients=[clientid]
)
trigger_meta = {
"description": "sends player updates to webmap clients",
"main_function": send_player_update_to_map,
"handlers": {
# Listen to all player field updates that are relevant for the map
"module_players/elements/%map_identifier%/%steamid%/pos": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/health": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/level": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/zombies": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/deaths": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/players": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/score": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/ping": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/is_muted": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/is_authenticated": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/in_limbo": send_player_update_to_map,
"module_players/elements/%map_identifier%/%steamid%/is_initialized": send_player_update_to_map,
}
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)

View File

@@ -0,0 +1,138 @@
from .discord_webhook import DiscordWebhook
from bot import loaded_modules_dict
from bot import telnet_prefixes
from os import path, pardir
module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir)))
trigger_name = path.basename(path.abspath(__file__))[:-3]
def main_function(origin_module, module, regex_result):
command = regex_result.group("command")
active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None)
update_player_pos = False
last_recorded_gametime_string = module.game_environment.get_last_recorded_gametime_string()
if command == "joined the game":
player_name = regex_result.group("player_name")
payload = '{} joined {} at {}'.format(player_name, active_dataset, last_recorded_gametime_string)
discord_payload_url = origin_module.options.get("discord_webhook", None)
if discord_payload_url is not None:
webhook = DiscordWebhook(
url=discord_payload_url,
content=payload
)
webhook.execute()
elif any([
command == "EnterMultiplayer",
command == "JoinMultiplayer"
]):
steamid = regex_result.group("player_steamid")
player_name = regex_result.group("player_name")
existing_player_dict = (
module.dom.data.get("module_players", {})
.get("elements", {})
.get(active_dataset, {})
.get(steamid, {})
)
player_dict = {
"name": player_name
}
player_dict.update(existing_player_dict)
if command == "EnterMultiplayer":
player_dict["first_seen_gametime"] = last_recorded_gametime_string
module.dom.data.upsert({
"module_players": {
"elements": {
active_dataset: {
steamid: player_dict
}
}
}
})
elif command == "JoinMultiplayer":
default_player_password = module.default_options.get("default_player_password", None)
if player_dict.get("is_authenticated", False) is True or default_player_password is None:
message = "[66FF66]Welcome back[-] [FFFFFF]{}[-]".format(player_name)
else:
message = (
"[66FF66]Welcome to the server[-] [FFFFFF]{player_name}[-]"
).format(
player_name=player_name
)
if default_player_password is not None:
message += ", [FF6666]please authenticate[-] [FFFFFF]and make yourself at home[-]"
event_data = ['say_to_player', {
'steamid': steamid,
'message': message
}]
module.trigger_action_hook(origin_module.players, event_data=event_data)
elif command == "Teleport":
update_player_pos = True
if update_player_pos:
player_to_be_updated = regex_result.group("player_steamid")
pos_after_teleport = {
"pos": {
"x": regex_result.group("pos_x"),
"y": regex_result.group("pos_y"),
"z": regex_result.group("pos_z"),
}
}
# update the players location data with the teleport ones
module.dom.data.upsert({
"module_players": {
"elements": {
active_dataset: {
player_to_be_updated: pos_after_teleport
}
}
}
})
trigger_meta = {
"description": "reacts to telnets playerspawn messages for real time responses!",
"main_function": main_function,
"triggers": [
{
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
r"PlayerSpawnedInWorld\s"
r"\("
r"reason: (?P<command>.+?),\s"
r"position: (?P<pos_x>.*),\s(?P<pos_y>.*),\s(?P<pos_z>.*)"
r"\):\s"
r"EntityID=(?P<entity_id>.*),\s"
r"PlayerID='(?P<player_steamid>.*)',\s"
r"OwnerID='(?P<owner_steamid>.*)',\s"
r"PlayerName='(?P<player_name>.*)'"
),
"callback": main_function
}, {
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
r"Player (?P<command>.*): "
r"EntityID=(?P<entity_id>.*), "
r"PlayerID=\'(?P<player_steamid>.*)\', "
r"OwnerID=\'(?P<owner_id>.*)\', "
r"PlayerName='(?P<player_name>.*)\'$"
),
"callback": main_function
}, {
"regex": (
telnet_prefixes["telnet_log"]["timestamp"] +
telnet_prefixes["GMSG"]["command"]
),
"callback": main_function
}
]
}
loaded_modules_dict["module_" + module_name].register_trigger(trigger_name, trigger_meta)