Api 9 release

This commit is contained in:
brostosjoined 2025-01-26 20:33:06 +03:00
parent 9b4ee194a1
commit ba27a96ede
4 changed files with 867 additions and 533 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,19 @@
# ba_meta require api 8
# ba_meta require api 9
#! Try patching upnpclient to use defusedxml replacement for lxml for more device support
import babase
import bauiv1 as bui
import bascenev1 as bs
import shutil
import platform
import os
import hashlib
import zipfile
import tarfile
import threading
import ast
import time
from urllib.parse import urlparse, unquote
from pathlib import Path
from os import remove, getcwd
from urllib.request import urlretrieve, urlopen
@ -36,63 +41,214 @@ def threaded(func):
@threaded
def get_modules() -> None:
install_path = Path(f"{getcwd()}/ba_data/python") # For the guys like me on windows
upnpy_path = Path(f"{install_path}/upnp.tar.gz")
nat_pmp_path = Path(f"{install_path}/natpmp.tar.gz")
upnpy_file_path = Path(f"{install_path}/upnpy")
nat_pmp_file_path = Path(f"{install_path}/natpmp")
nat_pmp_source_dir = Path(f"{install_path}/NAT-PMP-1.3.2/natpmp")
upnpy_source_dir = Path(f"{install_path}/UPnPy-1.1.8/upnpy")
if (
not Path(f"{nat_pmp_file_path}/__init__.py").exists()
and not Path(f"{upnpy_file_path}/__init__.py").exists()
): # YouKnowDev
nat_pmp_url = "https://files.pythonhosted.org/packages/dc/0c/28263fb4a623e6718a179bca1f360a6ae38f0f716a6cacdf47e15a5fa23e/NAT-PMP-1.3.2.tar.gz"
upnpy_url = "https://files.pythonhosted.org/packages/80/66/d4e721ff8766ea3e78730574669f6feeb71e438a8c2d7a62b2c3456a5c12/UPnPy-1.1.8.tar.gz"
try:
# fix issue where the file delete themselves
if babase.app.classic.platform == "mac":
install_path = bs.app.env.python_directory_app
else:
install_path = Path(
f"{getcwd()}/ba_data/python"
) # For the guys like me on windows
packages = {
"upnp-client": {
"url": "https://files.pythonhosted.org/packages/dd/69/4d38d9fa757c328df93e7037eb8c1da8ca48e62828c23ba3c421e9335e30/upnpclient-1.0.3.tar.gz",
"md5": "f936c8de89705555f6bd736a66d3af5d",
"folder": "upnpclient",
},
"python-dateutil": {
"url": "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz",
"md5": "81cb6aad924ef40ebfd3d62eaebe47c6",
"folder": "dateutil",
},
"six": {
"url": "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz",
"md5": "a0387fe15662c71057b4fb2b7aa9056a",
"folder": "six.py",
},
"requests": {
"url": "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz",
"md5": "fa3ee5ac3f1b3f4368bd74ab530d3f0f",
"folder": "requests",
},
"idna": {
"url": "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz",
"md5": "28448b00665099117b6daa9887812cc4",
"folder": "idna",
},
#! Api 9 already has urllib3 module
# "urllib3": {
# "url": "https://files.pythonhosted.org/packages/7a/50/7fd50a27caa0652cd4caf224aa87741ea41d3265ad13f010886167cfcc79/urllib3-2.2.1.tar.gz",
# "md5": "872f7f43af1b48e7c116c7542ab39fab",
# "folder": "urllib3",
# },
"ifaddr": {
"url": "https://files.pythonhosted.org/packages/e8/ac/fb4c578f4a3256561548cd825646680edcadb9440f3f68add95ade1eb791/ifaddr-0.2.0.tar.gz",
"md5": "b1cac02b5dc354d68dd6d853bc9565a7",
"folder": "ifaddr",
},
"NAT-PMP": {
"url": "https://files.pythonhosted.org/packages/dc/0c/28263fb4a623e6718a179bca1f360a6ae38f0f716a6cacdf47e15a5fa23e/NAT-PMP-1.3.2.tar.gz",
"md5": "7e5faa22acb0935f75664e9c4941fda4",
"folder": "natpmp",
},
}
system = platform.platform()
if "Windows" in system:
packages["lxml"] = {
"url": "https://files.pythonhosted.org/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl",
"md5": "a5579cb068a3fbfb5989fbeb4024c599",
"folder": "lxml",
}
packages["charset_normalizer"] = {
"url": "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl",
"md5": "babec153025b1270d6a2fd76e2c3772f",
"folder": "charset_normalizer",
}
elif "Darwin" in system and "arm64" in system:
packages["lxml"] = {
"url": "https://files.pythonhosted.org/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl",
"md5": "0200ca09c13892c80b47cf4c713786ed",
"folder": "lxml",
}
packages["charset_normalizer"] = {
"url": "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl",
"md5": "572c9f4f64469518d6a6b4c15710201a",
"folder": "charset_normalizer",
}
elif "Darwin" in system and "x86_64" in system:
packages["lxml"] = {
"url": "https://files.pythonhosted.org/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl",
"md5": "96b82c1e6d24472af28c48d9bb21605e",
"folder": "lxml",
}
packages["charset_normalizer"] = {
"url": "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl",
"md5": "572c9f4f64469518d6a6b4c15710201a",
"folder": "charset_normalizer",
}
elif "glibc" in system and "x86_64" in system:
packages["lxml"] = {
"url": "https://files.pythonhosted.org/packages/0a/6e/94537acfb5b8f18235d13186d247bca478fea5e87d224644e0fe907df976/lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl",
"md5": "d63bf3d33e46a3b0262176b1a815b4b0",
"folder": "lxml",
}
packages["charset_normalizer"] = {
"url": "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"md5": "1edb315f82fa657b8ee5d564117e057c",
"folder": "charset_normalizer",
}
elif "glibc" in system and "aarch64" in system:
packages["lxml"] = {
"url": "https://files.pythonhosted.org/packages/88/69/6972bfafa8cd3ddc8562b126dd607011e218e17be313a8b1b9cc5a0ee876/lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"md5": "663ccdccd076b26b5607901799c671be",
"folder": "lxml",
}
packages["charset_normalizer"] = {
"url": "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"md5": "d2e8c76665fb9fb013882d4052f46b95",
"folder": "charset_normalizer",
}
elif not "glibc" in system and "x86_64" in system:
packages["lxml"] = {
"url": "https://files.pythonhosted.org/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl",
"md5": "659bdaee4672e8409b277b570e3e3e39",
"folder": "lxml",
}
packages["charset_normalizer"] = {
"url": "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl",
"md5": "7a60860d64616d5a0af22d034963ab11",
"folder": "charset_normalizer",
}
elif not "glibc" in system and "aarch64" in system:
packages["lxml"] = {
"url": "https://files.pythonhosted.org/packages/8d/e8/4b15df533fe8e8d53363b23a41df9be907330e1fa28c7ca36893fad338ee/lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl",
"md5": "3ec71cd198cc28525f4c1d65d41a7689",
"folder": "lxml",
}
packages["charset_normalizer"] = {
"url": "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl",
"md5": "ed3a63cc79137f316ee386cd7aaea7e6",
"folder": "charset_normalizer",
}
else:
packages["lxml"] = {
"url": "https://files.pythonhosted.org/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
"md5": "ecfccadd587adb67ca54a24977e1a436",
"folder": "lxml",
}
packages["charset_normalizer"] = {
"url": "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
"md5": "9bdbf872c3bdbcb7191d5cdf3176c38a",
"folder": "charset_normalizer",
}
for package, details in packages.items():
parsed_url = urlparse(details["url"])
path = unquote(parsed_url.path)
filename = os.path.basename(path)
if details["url"].endswith(".whl"):
file_format = "whl"
folder_name = "-".join(filename.split("-")[:2])
elif details["url"].endswith(".tar.gz"):
file_format = "tar.gz"
folder_name = filename.rsplit(".", 2)[0]
package_path = os.path.join(install_path, f"{package}.{file_format}")
package_path = Path(f"{install_path}/{package}.{file_format}")
package_source_dir = Path(f"{install_path}/{details['folder']}")
if not Path(f"{package_source_dir}/__init__.py").exists():
try:
shutil.rmtree(nat_pmp_file_path)
shutil.rmtree(upnpy_file_path)
shutil.rmtree(package_source_dir)
except:
pass
nat_pmp_filename, headers = urlretrieve(nat_pmp_url, filename=nat_pmp_path)
upnpy_filename, headers = urlretrieve(upnpy_url, filename=upnpy_path)
with open(nat_pmp_filename, "rb") as f:
content = f.read()
assert (
hashlib.md5(content).hexdigest()
== "7e5faa22acb0935f75664e9c4941fda4"
)
with open(upnpy_filename, "rb") as f:
content = f.read()
assert (
hashlib.md5(content).hexdigest()
== "b33ad0b38e39af258e2c8f38813abf7b"
)
shutil.unpack_archive(nat_pmp_filename, install_path)
shutil.unpack_archive(upnpy_filename, install_path)
remove(upnpy_path)
remove(nat_pmp_path)
shutil.copytree(nat_pmp_source_dir, nat_pmp_file_path)
shutil.copytree(upnpy_source_dir, upnpy_file_path)
shutil.rmtree(Path(f"{install_path}/NAT-PMP-1.3.2"))
shutil.rmtree(Path(f"{install_path}/UPnPy-1.1.8"))
except Exception as e:
if type(e) == shutil.Error:
shutil.rmtree(Path(f"{install_path}/NAT-PMP-1.3.2"))
shutil.rmtree(Path(f"{install_path}/UPnPy-1.1.8"))
else:
pass
# Patch to natpmp to work without netifaces
with open(f"{nat_pmp_file_path}/__init__.py", "r") as f:
lines = f.readlines()
# Define the new function as a string
new_function = '''
package_filename, headers = urlretrieve(
details["url"], filename=package_path
)
with open(package_filename, "rb") as f:
content = f.read()
assert hashlib.md5(content).hexdigest() == details["md5"]
try:
shutil.unpack_archive(package_filename, install_path, format='gztar')
extracted_package_files = Path(f"{install_path}/{folder_name}")
for root, dirs, files in os.walk(extracted_package_files):
for dir in dirs:
subfolder = os.path.join(root, dir)
if subfolder.endswith(details["folder"]):
shutil.copytree(
subfolder, f"{install_path}/{details['folder']}"
)
if details["folder"] == "six.py":
shutil.copy(
Path(f"{install_path}/{folder_name}/six.py"),
f"{install_path}/six.py",
)
try:
shutil.rmtree(Path(f"{install_path}/{folder_name}"))
except FileNotFoundError:
pass
except shutil.ReadError as e:
with zipfile.ZipFile(package_filename, "r") as zip_ref:
zip_ref.extractall(install_path)
try:
shutil.rmtree(Path(f"{install_path}/lxml-5.3.0.dist-info")) #! Remember to update accordingly
except:
shutil.rmtree(Path(f"{install_path}/charset_normalizer-3.4.1.dist-info")) #!
remove(package_path)
else:
return
# Patch to natpmp to work without netifaces
with open(Path(f"{install_path}/natpmp/__init__.py"), "r") as f:
lines = f.readlines()
# Define the new function as a string
new_function = '''
# Plucked from https://github.com/tenable/upnp_info/blob/d20a1fda8ca4877d61b89fe7126077a3a5f0b322/upnp_info.py#L23
def get_gateway_addr():
"""Returns the gateway ip of the router if upnp service is available"""
"""
Returns the gateway ip of the router if upnp service is available
"""
try:
locations = set()
location_regex = re.compile("location:[ ]*(.+)"+ chr(13) + chr(10), re.IGNORECASE)
@ -124,168 +280,149 @@ def get_gateway_addr():
return gateway_ip_address
except:
pass
'''
# Replace the function
lines[224:229] = new_function
lines[21] = "import socket\nimport re\nfrom urllib.parse import urlparse"
# Replace the function
lines[224:229] = new_function
lines[21] = "import socket\nimport re\nfrom urllib.parse import urlparse"
with open(Path(f"{install_path}/natpmp/__init__.py"), "w") as f:
f.writelines(lines)
with open(f"{nat_pmp_file_path}/__init__.py", "w") as f:
f.writelines(lines)
add_port_mapping()
add_port_mapping()
def play_sound(sound):
with bs.get_foreground_host_activity().context:
bs.getsound(sound).play()
accessible_online = None
@threaded
def confirm_port():
global accessible_online
time.sleep(5)
with urlopen("https://legacy.ballistica.net/bsAccessCheck") as resp:
resp = resp.read().decode()
resp = ast.literal_eval(resp)
return resp["accessible"]
accessible_online = resp["accessible"]
# return resp["accessible"]
@threaded
def add_port_mapping():
if accessible_online:
return
# Try to add UDP port using NAT-PMP
import socket
import natpmp
from natpmp import NATPMPUnsupportedError, NATPMPNetworkError
try:
natpmp.map_port(
natpmp.NATPMP_PROTOCOL_UDP,
BS_PORT,
BS_PORT,
14400,
gateway_ip=natpmp.get_gateway_addr(),
)
if confirm_port():
babase.screenmessage(
"You are now joinable from the internet", (0.2, 1, 0.2)
)
except (NATPMPUnsupportedError, NATPMPNetworkError):
import upnpy
from upnpy.exceptions import SOAPError
from urllib.error import HTTPError
upnp = upnpy.UPnP()
devices = upnp.discover()
if devices == []:
babase.screenmessage(
"Please enable upnp service on your router", (1.00, 0.15, 0.15)
)
# bui.getsound('shieldDown').play() -> RuntimeError : Sound creation failed
return
local_ip = (
(
[
ip
for ip in socket.gethostbyname_ex(socket.gethostname())[2]
if not ip.startswith("127.")
]
or [
[
(s.connect(("8.8.8.8", 53)), s.getsockname()[0], s.close())
for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]
][0][1]
]
)
+ ["no IP found"]
)[0]
try:
for upnp_dev in devices:
for service in upnp_dev.services:
if service in WAN_SERVICE_NAMES:
service = upnp_dev[service]
try:
result = service.GetSpecificPortMappingEntry(
NewRemoteHost="", NewExternalPort=BS_PORT, NewProtocol="UDP"
)
if result and not confirm_port():
if babase.do_once():
babase.screenmessage(
"Oops seems like your network doesn't support upnp",
(1.0, 0.15, 0.15),
)
babase.pushcall(
bui.getsound("error").play(), from_other_thread=True
)
return
except SOAPError:
if confirm_port():
return
service.AddPortMapping(
NewRemoteHost="",
NewExternalPort=BS_PORT,
NewProtocol="UDP",
NewInternalPort=BS_PORT,
NewInternalClient=str(local_ip),
NewEnabled="1",
NewPortMappingDescription="Bombsquad",
NewLeaseDuration=14400,
)
if confirm_port():
babase.screenmessage(
"You are now joinable from the internet", (0.2, 1, 0.2)
)
bui.getsound("shieldUp").play()
except (SOAPError, HTTPError, UnicodeDecodeError):
babase.screenmessage('You will need to manualy add the port at the router :(')
@threaded
def delete_port_mapping():
import socket
import natpmp
from natpmp import NATPMPUnsupportedError, NATPMPNetworkError
try:
natpmp.map_port(
natpmp.NATPMP_PROTOCOL_UDP,
BS_PORT,
BS_PORT,
0,
gateway_ip=natpmp.get_gateway_addr(),
)
except (NATPMPUnsupportedError, NATPMPNetworkError):
import upnpy
from upnpy.exceptions import SOAPError
upnp = upnpy.UPnP()
devices = upnp.discover()
if devices == []:
return
import socket
import natpmp
from natpmp import NATPMPUnsupportedError, NATPMPNetworkError
try:
for upnp_dev in devices:
for service in upnp_dev.services:
if service in WAN_SERVICE_NAMES:
service = upnp_dev[service]
service.DeletePortMapping(
NewRemoteHost="", NewExternalPort=BS_PORT, NewProtocol="UDP")
except:
pass
natpmp.map_port(
natpmp.NATPMP_PROTOCOL_UDP,
BS_PORT,
BS_PORT,
14400,
gateway_ip=natpmp.get_gateway_addr(),
)
if accessible_online:
babase.screenmessage(
"You are now joinable from the internet", (0.2, 1, 0.2)
)
babase.pushcall(
babase.Call(play_sound, "shieldUp"), from_other_thread=True
)
except (NATPMPUnsupportedError, NATPMPNetworkError):
import upnpclient
from upnpclient.soap import SOAPError
from urllib.error import HTTPError
devices = upnpclient.discover()
if devices == []:
babase.screenmessage(
"Please enable upnp service on your router", (1.00, 0.15, 0.15)
)
babase.pushcall(
babase.Call(play_sound, "shieldDown"), from_other_thread=True
)
return
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# connect() for UDP doesn't send packets
s.connect(("10.0.0.0", 0))
local_ip = s.getsockname()[0]
s.close()
except:
pass
try:
for upnp_dev in devices:
for service in upnp_dev.services:
if service.name in WAN_SERVICE_NAMES:
try:
result = service.GetSpecificPortMappingEntry(
NewRemoteHost="",
NewExternalPort=BS_PORT,
NewProtocol="UDP",
)
print(result["NewEnabled"])
if result["NewEnabled"] and not accessible_online:
if babase.do_once():
babase.screenmessage(
"Oops seems like your network doesn't support upnp",
(1.0, 0.15, 0.15),
)
babase.pushcall(
babase.Call(play_sound, "shieldDown"),
from_other_thread=True,
)
return
except SOAPError:
if accessible_online:
return
service.AddPortMapping(
NewRemoteHost="0.0.0.0",
NewExternalPort=BS_PORT,
NewProtocol="UDP",
NewInternalPort=BS_PORT,
NewInternalClient=local_ip,
NewEnabled="1",
NewPortMappingDescription="Bombsquad",
NewLeaseDuration=14400,
)
babase.pushcall(
babase.Call(play_sound, "shieldUp"),
from_other_thread=True,
)
except (SOAPError, HTTPError, UnicodeDecodeError):
babase.screenmessage(
"You will need to manualy port forward at the router :("
)
babase.pushcall(babase.Call(play_sound, "error"),from_other_thread=True,)
except ModuleNotFoundError:
pass
# ba_meta export babase.Plugin
class Joinable(babase.Plugin):
def on_app_running(self) -> None:
get_modules()
if confirm_port():
# try:
confirm_port()
if accessible_online:
return
else:
add_port_mapping()
def on_app_shutdown(self) -> None:
delete_port_mapping()
def on_app_pause(self) -> None:
delete_port_mapping()
try:
import upnpclient
add_port_mapping()
except ImportError:
try:
install_path = Path(f"{getcwd()}/ba_data/python")
shutil.rmtree(f"{install_path}/upnpy")
shutil.rmtree(f"{install_path}/natpmp")
except FileNotFoundError:
get_modules()
def on_app_resume(self) -> None:
confirm_port()
add_port_mapping()

View file

@ -0,0 +1,50 @@
# ba_meta require api 9
#! Fix bug when the previous message is "hello" it will not trigger the wave emote on new round or game
import time
import babase
import bascenev1 as bs
last_len_msg = 0 # Initialize the global variable outside the function
def wave_emote():
global last_len_msg # To modify the global variable
# Check if the players are in game first
try:
act_players = bs.get_foreground_host_activity().players
if not act_players:
return
except AttributeError:
# Except the attribute error if the player is in a server
return
# Incase chats are empty or in replay
try:
lastmsg = bs.get_chat_messages()[-1]
except:
return
# Perform a check to see if the player is playing|spectating
for player in act_players:
try:
if player.actor.node:
continue
except:
return
# Check if the message contains "hello"
if len(bs.get_chat_messages()) != last_len_msg:
if act_players and "hello" in lastmsg:
for player in act_players:
if player.getname() in lastmsg:
# Trigger the wave emote
player.actor.node.handlemessage("celebrate_r", 1000)
last_len_msg = len(bs.get_chat_messages())
print(last_len_msg, "last_len_msg")
# ba_meta export plugin
class brostos(babase.Plugin):
timer = bs.AppTimer(0.5, wave_emote, repeat=True)