crxsnake 1.4.7__py3-none-any.whl → 1.4.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crxsnake/__init__.py +39 -37
- crxsnake/database.py +26 -0
- crxsnake/logger.py +56 -29
- crxsnake/{files.py → readers.py} +70 -50
- crxsnake/utils/dayz/__init__.py +0 -0
- crxsnake/utils/{convert.py → dayz/convert.py} +34 -19
- crxsnake/utils/{hotline.py → dayz/hotline.py} +65 -65
- crxsnake/utils/discord/__init__.py +0 -0
- crxsnake/utils/discord/cogs.py +27 -0
- crxsnake/utils/discord/colors.py +10 -0
- crxsnake/utils/{embed_log.py → discord/embed.py} +78 -77
- crxsnake/utils/discord/parse.py +14 -0
- crxsnake/utils/misc.py +7 -22
- crxsnake/utils/steam.py +33 -0
- crxsnake/web/__init__.py +0 -0
- crxsnake/web/request.py +38 -0
- {crxsnake-1.4.7.dist-info → crxsnake-1.4.8.dist-info}/LICENSE +21 -21
- crxsnake-1.4.8.dist-info/METADATA +31 -0
- crxsnake-1.4.8.dist-info/RECORD +21 -0
- {crxsnake-1.4.7.dist-info → crxsnake-1.4.8.dist-info}/WHEEL +1 -2
- crxsnake/cogs.py +0 -18
- crxsnake/tortoise.py +0 -26
- crxsnake-1.4.7.dist-info/METADATA +0 -37
- crxsnake-1.4.7.dist-info/RECORD +0 -15
- crxsnake-1.4.7.dist-info/top_level.txt +0 -1
crxsnake/__init__.py
CHANGED
@@ -1,37 +1,39 @@
|
|
1
|
-
|
2
|
-
from .
|
3
|
-
from .
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
from .utils.
|
8
|
-
from .utils.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
"
|
24
|
-
"
|
25
|
-
"
|
26
|
-
"
|
27
|
-
|
28
|
-
"
|
29
|
-
|
30
|
-
|
31
|
-
"
|
32
|
-
|
33
|
-
"
|
34
|
-
"
|
35
|
-
|
36
|
-
"
|
37
|
-
|
1
|
+
# Main file for the crxsnake package
|
2
|
+
from .database import Database
|
3
|
+
from .readers import read_json, write_json, read_jsonc
|
4
|
+
from .logger import log
|
5
|
+
|
6
|
+
# Main utils for the crxsnake package
|
7
|
+
from .utils.steam import get_steam_id
|
8
|
+
from .utils.misc import restart
|
9
|
+
|
10
|
+
# Discord utils for the crxsnake package
|
11
|
+
from .utils.discord.cogs import load_cogs
|
12
|
+
from .utils.discord.colors import Colors
|
13
|
+
from .utils.discord.embed import EmbedLog
|
14
|
+
from .utils.discord.parse import get_user_id
|
15
|
+
|
16
|
+
# DayZ utils for the crxsnake package
|
17
|
+
from .utils.dayz.convert import steam_to_be_guid, steam_to_dayz_guid
|
18
|
+
from .utils.dayz.hotline import Hotline
|
19
|
+
|
20
|
+
|
21
|
+
__all__ = [
|
22
|
+
"Database",
|
23
|
+
"read_json",
|
24
|
+
"write_json",
|
25
|
+
"read_jsonc",
|
26
|
+
"log",
|
27
|
+
|
28
|
+
"get_steam_id",
|
29
|
+
"restart",
|
30
|
+
|
31
|
+
"load_cogs",
|
32
|
+
"Colors",
|
33
|
+
"EmbedLog",
|
34
|
+
"get_user_id",
|
35
|
+
|
36
|
+
"steam_to_be_guid",
|
37
|
+
"steam_to_dayz_guid",
|
38
|
+
"Hotline",
|
39
|
+
]
|
crxsnake/database.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
from tortoise import Tortoise
|
2
|
+
from crxsnake.logger import log
|
3
|
+
|
4
|
+
|
5
|
+
class Database:
|
6
|
+
DEFAULT_URL = "sqlite://settings/database/database.db"
|
7
|
+
DEFAULT_MODELS = "src.utils"
|
8
|
+
|
9
|
+
def __init__(self, db_url: str = None, models: str = None):
|
10
|
+
self.db_url = db_url or self.DEFAULT_URL
|
11
|
+
self.models = {"models": [models or self.DEFAULT_MODELS]}
|
12
|
+
|
13
|
+
async def db_connect(self) -> None:
|
14
|
+
"""Connect to the database."""
|
15
|
+
try:
|
16
|
+
await Tortoise.init(db_url=self.db_url, modules=self.models)
|
17
|
+
await Tortoise.generate_schemas()
|
18
|
+
log.info("SqLite connected", "Database")
|
19
|
+
|
20
|
+
except Exception as e:
|
21
|
+
log.error("Failed to connect SqLite", "Database", e)
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
async def disconnect_db() -> None:
|
25
|
+
"""Disconnect from the database."""
|
26
|
+
await Tortoise.close_connections()
|
crxsnake/logger.py
CHANGED
@@ -1,29 +1,56 @@
|
|
1
|
-
import sys
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
)
|
29
|
-
|
1
|
+
import sys
|
2
|
+
import traceback
|
3
|
+
|
4
|
+
from loguru import logger
|
5
|
+
|
6
|
+
|
7
|
+
class CustomLogger:
|
8
|
+
_logger = logger
|
9
|
+
_logger.remove()
|
10
|
+
|
11
|
+
_logger.add(
|
12
|
+
sys.stderr,
|
13
|
+
format="<blue>[{time:DD.MM.YY / HH:mm:ss}]</blue> <green>({level})</green> <green>{message}</green>",
|
14
|
+
colorize=True,
|
15
|
+
level="INFO",
|
16
|
+
filter=lambda record: record["level"].name == "INFO",
|
17
|
+
)
|
18
|
+
_logger.add(
|
19
|
+
sys.stderr,
|
20
|
+
format="<blue>[{time:DD.MM.YY / HH:mm:ss}]</blue> <yellow>({level})</yellow> <yellow>{message}</yellow>",
|
21
|
+
colorize=True,
|
22
|
+
level="WARNING",
|
23
|
+
filter=lambda record: record["level"].name == "WARNING",
|
24
|
+
)
|
25
|
+
_logger.add(
|
26
|
+
sys.stderr,
|
27
|
+
format=(
|
28
|
+
"<blue>[{time:DD.MM.YY / HH:mm:ss}]</blue> <red>({level})</red> <red>{message}</red>\n"
|
29
|
+
"<red>╰─> {extra[traceback]}</red>"
|
30
|
+
),
|
31
|
+
colorize=True,
|
32
|
+
level="ERROR",
|
33
|
+
filter=lambda record: record["level"].name == "ERROR",
|
34
|
+
)
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def info(cls, message, module: str = None):
|
38
|
+
log_message = f"[{module}] {message}" if module else str(message)
|
39
|
+
cls._logger.info(log_message)
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def warning(cls, message, module: str = None):
|
43
|
+
log_message = f"[{module}] {message}" if module else str(message)
|
44
|
+
cls._logger.warning(log_message)
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
def error(cls, message, module: str = None, error: Exception = None):
|
48
|
+
if error:
|
49
|
+
tb = traceback.format_exc()
|
50
|
+
log_message = f"[{module}] {message}" if module else str(message)
|
51
|
+
cls._logger.bind(traceback=tb).error(log_message)
|
52
|
+
else:
|
53
|
+
log_message = f"[{module}] {message}" if module else str(message)
|
54
|
+
cls._logger.error(log_message)
|
55
|
+
|
56
|
+
log = CustomLogger()
|
crxsnake/{files.py → readers.py}
RENAMED
@@ -1,50 +1,70 @@
|
|
1
|
-
import re
|
2
|
-
import aiofiles
|
3
|
-
|
4
|
-
from typing import Optional, Any
|
5
|
-
from json import loads, dumps
|
6
|
-
|
7
|
-
|
8
|
-
async def read_json(path: str, *keys: str) -> Optional[Any]:
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
1
|
+
import re
|
2
|
+
import aiofiles
|
3
|
+
|
4
|
+
from typing import Optional, Any
|
5
|
+
from json import loads, dumps
|
6
|
+
|
7
|
+
|
8
|
+
async def read_json(path: str, *keys: str) -> Optional[Any]:
|
9
|
+
"""Read data from a JSON file.
|
10
|
+
Args:
|
11
|
+
path (str): Json file path
|
12
|
+
*keys (str): Keys to read from the JSON file
|
13
|
+
Returns:
|
14
|
+
Optional[Dict]:
|
15
|
+
"""
|
16
|
+
try:
|
17
|
+
async with aiofiles.open(path, "r", encoding="utf-8") as file:
|
18
|
+
file_content = await file.read()
|
19
|
+
file_data = loads(file_content)
|
20
|
+
|
21
|
+
for key in keys:
|
22
|
+
file_data = file_data.get(key)
|
23
|
+
if file_data is None:
|
24
|
+
return None
|
25
|
+
return file_data
|
26
|
+
except Exception:
|
27
|
+
raise
|
28
|
+
|
29
|
+
|
30
|
+
async def write_json(path: str, data: Any) -> bool:
|
31
|
+
"""Write data to a JSON file.
|
32
|
+
Args:
|
33
|
+
path (str): Json file path
|
34
|
+
data (Any): Data to write to the JSON file
|
35
|
+
Returns:
|
36
|
+
bool: True if the data was written successfully, False otherwise
|
37
|
+
"""
|
38
|
+
try:
|
39
|
+
async with aiofiles.open(path, "w", encoding="utf-8") as file:
|
40
|
+
await file.write(dumps(data, ensure_ascii=False, indent=2))
|
41
|
+
return True
|
42
|
+
except Exception:
|
43
|
+
raise
|
44
|
+
|
45
|
+
|
46
|
+
async def read_jsonc(path: str, *keys: str) -> Optional[Any]:
|
47
|
+
"""Read data from a JSONC file.
|
48
|
+
Args:
|
49
|
+
path (str): Json file path
|
50
|
+
*keys (str): Keys to read from the JSON file
|
51
|
+
Returns:
|
52
|
+
Optional[Dict]: Data read from the JSON file
|
53
|
+
"""
|
54
|
+
try:
|
55
|
+
regex = re.compile(r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*)", re.MULTILINE | re.DOTALL)
|
56
|
+
|
57
|
+
def __re_sub(match):
|
58
|
+
return match.group(1) or ""
|
59
|
+
|
60
|
+
async with aiofiles.open(path, "r", encoding="utf-8") as file:
|
61
|
+
file_content = regex.sub(__re_sub, await file.read())
|
62
|
+
file_data = loads(file_content)
|
63
|
+
|
64
|
+
for key in keys:
|
65
|
+
file_data = file_data.get(key)
|
66
|
+
if file_data is None:
|
67
|
+
return None
|
68
|
+
return file_data
|
69
|
+
except Exception:
|
70
|
+
raise
|
File without changes
|
@@ -1,19 +1,34 @@
|
|
1
|
-
from hashlib import sha256, md5
|
2
|
-
from base64 import b64encode
|
3
|
-
|
4
|
-
|
5
|
-
def steam_to_be_guid(steam_id: int) -> str:
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
from hashlib import sha256, md5
|
2
|
+
from base64 import b64encode
|
3
|
+
|
4
|
+
|
5
|
+
def steam_to_be_guid(steam_id: int) -> str:
|
6
|
+
"""Convert steam id to Battleye guid.
|
7
|
+
|
8
|
+
Args:
|
9
|
+
steam_id (int)
|
10
|
+
Returns:
|
11
|
+
str: Battleye guid
|
12
|
+
"""
|
13
|
+
parts = [0x42, 0x45] + [0] * 8
|
14
|
+
for i in range(2, 10):
|
15
|
+
steam_id, remainder = divmod(steam_id, 256)
|
16
|
+
parts[i] = remainder
|
17
|
+
|
18
|
+
byte_array = bytes(parts)
|
19
|
+
hash_object = md5(byte_array)
|
20
|
+
return hash_object.hexdigest()
|
21
|
+
|
22
|
+
|
23
|
+
def steam_to_dayz_guid(steam_id: int) -> str:
|
24
|
+
"""Convert steam id to Bohemia ID
|
25
|
+
|
26
|
+
Args:
|
27
|
+
steam_id (int)_
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
str: Bohemia ID
|
31
|
+
"""
|
32
|
+
hashed = sha256()
|
33
|
+
hashed.update(str(steam_id).encode("utf-8"))
|
34
|
+
return b64encode(hashed.digest()).decode("utf-8")
|
@@ -1,65 +1,65 @@
|
|
1
|
-
from random import choices
|
2
|
-
from dataclasses import dataclass, field
|
3
|
-
from string import ascii_uppercase, ascii_letters, digits
|
4
|
-
from typing import List, Dict, Any, Generator
|
5
|
-
|
6
|
-
|
7
|
-
@dataclass
|
8
|
-
class CodeArray:
|
9
|
-
m_code: str
|
10
|
-
m_name: str
|
11
|
-
m_type: str
|
12
|
-
m_itemsArray: List[Dict[str, Any]] = field(default_factory=list)
|
13
|
-
m_teleport_position: str = ""
|
14
|
-
m_vehicles: Dict[str, Any] = field(default_factory=lambda: {"m_item": "", "m_attachments": [], "m_cargo": []})
|
15
|
-
m_give_zone_positions: List[Dict[str, Any]] = field(default_factory=list)
|
16
|
-
m_give_zone_positions_forbidden: List[Dict[str, Any]] = field(default_factory=list)
|
17
|
-
|
18
|
-
|
19
|
-
class
|
20
|
-
MAX_STACK = 100
|
21
|
-
|
22
|
-
@staticmethod
|
23
|
-
def __generate_code() -> str:
|
24
|
-
return "-".join([
|
25
|
-
"".join(choices(ascii_uppercase, k=2)),
|
26
|
-
"".join(choices(ascii_letters + digits, k=4)),
|
27
|
-
"".join(choices(ascii_letters + digits, k=4))
|
28
|
-
])
|
29
|
-
|
30
|
-
def __generate_count(self, items_list: List[Dict[str, Any]]) -> Generator[Dict[str, Any], None, None]:
|
31
|
-
for item in items_list:
|
32
|
-
item_count = item.get("m_count", 0)
|
33
|
-
item_name = item.get("m_item", "")
|
34
|
-
full_stacks, remainder = divmod(item_count, self.MAX_STACK)
|
35
|
-
|
36
|
-
for _ in range(full_stacks):
|
37
|
-
yield {"m_item": item_name, "m_count": self.MAX_STACK}
|
38
|
-
|
39
|
-
if remainder > 0:
|
40
|
-
yield {"m_item": item_name, "m_count": remainder}
|
41
|
-
|
42
|
-
async def create_items(self, issue_name: str, items_list: List[Dict[str, Any]]) -> Dict[str, Any]:
|
43
|
-
processed_items = [item for item in self.__generate_count(items_list)]
|
44
|
-
return {"m_CodeArray": [CodeArray(
|
45
|
-
m_code=self.__generate_code(),
|
46
|
-
m_name=issue_name,
|
47
|
-
m_type="item",
|
48
|
-
m_itemsArray=processed_items
|
49
|
-
).__dict__]}
|
50
|
-
|
51
|
-
async def create_vehicle(self, issue_name: str, items_dict: Dict[str, Any]) -> Dict[str, Any]:
|
52
|
-
return {"m_CodeArray": [CodeArray(
|
53
|
-
m_code=self.__generate_code(),
|
54
|
-
m_name=issue_name,
|
55
|
-
m_type="vehicle",
|
56
|
-
m_vehicles=items_dict
|
57
|
-
).__dict__]}
|
58
|
-
|
59
|
-
async def create_teleport(self, issue_name: str, teleport_position: str) -> Dict[str, Any]:
|
60
|
-
return {"m_CodeArray": [CodeArray(
|
61
|
-
m_code=self.__generate_code(),
|
62
|
-
m_name=issue_name,
|
63
|
-
m_type="teleport",
|
64
|
-
m_teleport_position=teleport_position
|
65
|
-
).__dict__]}
|
1
|
+
from random import choices
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from string import ascii_uppercase, ascii_letters, digits
|
4
|
+
from typing import List, Dict, Any, Generator
|
5
|
+
|
6
|
+
|
7
|
+
@dataclass
|
8
|
+
class CodeArray:
|
9
|
+
m_code: str
|
10
|
+
m_name: str
|
11
|
+
m_type: str
|
12
|
+
m_itemsArray: List[Dict[str, Any]] = field(default_factory=list)
|
13
|
+
m_teleport_position: str = ""
|
14
|
+
m_vehicles: Dict[str, Any] = field(default_factory=lambda: {"m_item": "", "m_attachments": [], "m_cargo": []})
|
15
|
+
m_give_zone_positions: List[Dict[str, Any]] = field(default_factory=list)
|
16
|
+
m_give_zone_positions_forbidden: List[Dict[str, Any]] = field(default_factory=list)
|
17
|
+
|
18
|
+
|
19
|
+
class Hotline:
|
20
|
+
MAX_STACK = 100
|
21
|
+
|
22
|
+
@staticmethod
|
23
|
+
def __generate_code() -> str:
|
24
|
+
return "-".join([
|
25
|
+
"".join(choices(ascii_uppercase, k=2)),
|
26
|
+
"".join(choices(ascii_letters + digits, k=4)),
|
27
|
+
"".join(choices(ascii_letters + digits, k=4))
|
28
|
+
])
|
29
|
+
|
30
|
+
def __generate_count(self, items_list: List[Dict[str, Any]]) -> Generator[Dict[str, Any], None, None]:
|
31
|
+
for item in items_list:
|
32
|
+
item_count = item.get("m_count", 0)
|
33
|
+
item_name = item.get("m_item", "")
|
34
|
+
full_stacks, remainder = divmod(item_count, self.MAX_STACK)
|
35
|
+
|
36
|
+
for _ in range(full_stacks):
|
37
|
+
yield {"m_item": item_name, "m_count": self.MAX_STACK}
|
38
|
+
|
39
|
+
if remainder > 0:
|
40
|
+
yield {"m_item": item_name, "m_count": remainder}
|
41
|
+
|
42
|
+
async def create_items(self, issue_name: str, items_list: List[Dict[str, Any]]) -> Dict[str, Any]:
|
43
|
+
processed_items = [item for item in self.__generate_count(items_list)]
|
44
|
+
return {"m_CodeArray": [CodeArray(
|
45
|
+
m_code=self.__generate_code(),
|
46
|
+
m_name=issue_name,
|
47
|
+
m_type="item",
|
48
|
+
m_itemsArray=processed_items
|
49
|
+
).__dict__]}
|
50
|
+
|
51
|
+
async def create_vehicle(self, issue_name: str, items_dict: Dict[str, Any]) -> Dict[str, Any]:
|
52
|
+
return {"m_CodeArray": [CodeArray(
|
53
|
+
m_code=self.__generate_code(),
|
54
|
+
m_name=issue_name,
|
55
|
+
m_type="vehicle",
|
56
|
+
m_vehicles=items_dict
|
57
|
+
).__dict__]}
|
58
|
+
|
59
|
+
async def create_teleport(self, issue_name: str, teleport_position: str) -> Dict[str, Any]:
|
60
|
+
return {"m_CodeArray": [CodeArray(
|
61
|
+
m_code=self.__generate_code(),
|
62
|
+
m_name=issue_name,
|
63
|
+
m_type="teleport",
|
64
|
+
m_teleport_position=teleport_position
|
65
|
+
).__dict__]}
|
File without changes
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from disnake.ext.commands import Bot
|
3
|
+
from crxsnake.logger import log
|
4
|
+
|
5
|
+
|
6
|
+
async def load_cogs(bot: Bot, folder: str = "src", utils: str = "utils"):
|
7
|
+
"""Loads cogs (extensions) for the bot from the specified folder.
|
8
|
+
Args:
|
9
|
+
bot (Bot): The bot instance.
|
10
|
+
folder (str, optional): The main folder containing the cogs. Defaults to "src".
|
11
|
+
utils (str, optional): The name of the utilities folder to exclude. Defaults to "utils".
|
12
|
+
|
13
|
+
Raises:
|
14
|
+
Exception: If an error occurs while loading cogs.
|
15
|
+
"""
|
16
|
+
try:
|
17
|
+
base_path = Path.cwd() / folder
|
18
|
+
|
19
|
+
for entry in base_path.iterdir():
|
20
|
+
if entry.is_dir() and entry.name != utils:
|
21
|
+
for filename in entry.glob("*.py"):
|
22
|
+
bot.load_extension(f"{folder}.{entry.name}.{filename.stem}")
|
23
|
+
|
24
|
+
log.info("Cogs successfully loaded", "Discord")
|
25
|
+
|
26
|
+
except Exception as e:
|
27
|
+
log.error(f"An error occurred while loading modules", "Discord", e)
|
@@ -1,77 +1,78 @@
|
|
1
|
-
from typing import Optional, Tuple, List
|
2
|
-
from disnake import TextChannel, User, Embed, Forbidden, HTTPException
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
if fields:
|
23
|
-
for name, value, inline in fields:
|
24
|
-
embed.add_field(name=name, value=value, inline=inline)
|
25
|
-
|
26
|
-
if thumbnail:
|
27
|
-
embed.set_thumbnail(url=thumbnail)
|
28
|
-
if image:
|
29
|
-
embed.set_image(url=image)
|
30
|
-
if footer:
|
31
|
-
embed.set_footer(text=footer)
|
32
|
-
|
33
|
-
return embed
|
34
|
-
|
35
|
-
async def send_channel(
|
36
|
-
self,
|
37
|
-
channel: TextChannel,
|
38
|
-
title: Optional[str] = None,
|
39
|
-
color: Optional[int] = None,
|
40
|
-
fields: Optional[List[Tuple[str, str, bool]]] = None,
|
41
|
-
desc: Optional[str] = None,
|
42
|
-
url: Optional[str] = None,
|
43
|
-
thumbnail: Optional[str] = None,
|
44
|
-
image: Optional[str] = None,
|
45
|
-
footer: Optional[str] = None,
|
46
|
-
custom_embed: Optional[Embed] = None,
|
47
|
-
) -> None:
|
48
|
-
embed = custom_embed or self.build_embed(
|
49
|
-
title, color, fields, desc,
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
1
|
+
from typing import Optional, Tuple, List
|
2
|
+
from disnake import TextChannel, User, Embed, Forbidden, HTTPException
|
3
|
+
from crxsnake.logger import log
|
4
|
+
|
5
|
+
|
6
|
+
class EmbedLog:
|
7
|
+
|
8
|
+
@classmethod
|
9
|
+
def build_embed(
|
10
|
+
cls,
|
11
|
+
title: Optional[str] = None,
|
12
|
+
color: Optional[int] = None,
|
13
|
+
fields: Optional[List[Tuple[str, str, bool]]] = None,
|
14
|
+
desc: Optional[str] = None,
|
15
|
+
url: Optional[str] = None,
|
16
|
+
thumbnail: Optional[str] = None,
|
17
|
+
image: Optional[str] = None,
|
18
|
+
footer: Optional[str] = None,
|
19
|
+
) -> Embed:
|
20
|
+
|
21
|
+
embed = Embed(title=title, description=desc, color=color, url=url)
|
22
|
+
if fields:
|
23
|
+
for name, value, inline in fields:
|
24
|
+
embed.add_field(name=name, value=value, inline=inline)
|
25
|
+
|
26
|
+
if thumbnail:
|
27
|
+
embed.set_thumbnail(url=thumbnail)
|
28
|
+
if image:
|
29
|
+
embed.set_image(url=image)
|
30
|
+
if footer:
|
31
|
+
embed.set_footer(text=footer)
|
32
|
+
|
33
|
+
return embed
|
34
|
+
|
35
|
+
async def send_channel(
|
36
|
+
self,
|
37
|
+
channel: TextChannel,
|
38
|
+
title: Optional[str] = None,
|
39
|
+
color: Optional[int] = None,
|
40
|
+
fields: Optional[List[Tuple[str, str, bool]]] = None,
|
41
|
+
desc: Optional[str] = None,
|
42
|
+
url: Optional[str] = None,
|
43
|
+
thumbnail: Optional[str] = None,
|
44
|
+
image: Optional[str] = None,
|
45
|
+
footer: Optional[str] = None,
|
46
|
+
custom_embed: Optional[Embed] = None,
|
47
|
+
) -> None:
|
48
|
+
embed = custom_embed or self.build_embed(
|
49
|
+
title, color, fields, desc,
|
50
|
+
url, thumbnail, image, footer
|
51
|
+
)
|
52
|
+
try:
|
53
|
+
await channel.send(embed=embed)
|
54
|
+
except Forbidden:
|
55
|
+
log.error("Not enough permissions to send embed", "LogEmbed")
|
56
|
+
except HTTPException as e:
|
57
|
+
log.error("Error when sending embed", "LogEmbed", e)
|
58
|
+
|
59
|
+
async def send_user(
|
60
|
+
self,
|
61
|
+
user: User,
|
62
|
+
title: Optional[str] = None,
|
63
|
+
color: Optional[int] = None,
|
64
|
+
fields: Optional[List[Tuple[str, str, bool]]] = None,
|
65
|
+
desc: Optional[str] = None,
|
66
|
+
url: Optional[str] = None,
|
67
|
+
thumbnail: Optional[str] = None,
|
68
|
+
image: Optional[str] = None,
|
69
|
+
footer: Optional[str] = None,
|
70
|
+
custom_embed: Optional[Embed] = None,
|
71
|
+
) -> None:
|
72
|
+
embed = custom_embed or self.build_embed(
|
73
|
+
title, color, fields, desc, url, thumbnail, image, footer
|
74
|
+
)
|
75
|
+
try:
|
76
|
+
await user.send(embed=embed)
|
77
|
+
except Exception:
|
78
|
+
pass
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import re
|
2
|
+
from disnake import Embed
|
3
|
+
|
4
|
+
|
5
|
+
def get_user_id(field_index: int, embed: Embed) -> int:
|
6
|
+
"""Gets the user ID from the specified field index in the given embed.
|
7
|
+
Args:
|
8
|
+
field_index (int): Field index.
|
9
|
+
embed (Embed): Embed object.
|
10
|
+
Returns:
|
11
|
+
int: DiscordID
|
12
|
+
"""
|
13
|
+
user_id_str = re.search(r"<@!?(\d+)>", embed.fields[field_index].value).group(1)
|
14
|
+
return int(user_id_str)
|
crxsnake/utils/misc.py
CHANGED
@@ -1,22 +1,7 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
green = 65280
|
9
|
-
blue = 255
|
10
|
-
yellow = 16776960
|
11
|
-
purple = 9109759
|
12
|
-
trans = 2829617
|
13
|
-
|
14
|
-
|
15
|
-
def restart():
|
16
|
-
python = sys.executable
|
17
|
-
os.execl(python, python, '-B', *sys.argv)
|
18
|
-
|
19
|
-
|
20
|
-
def get_user_id(field_index: int, embed: Embed) -> int:
|
21
|
-
user_id_str = re.search(r"<@!?(\d+)>", embed.fields[field_index].value).group(1)
|
22
|
-
return int(user_id_str)
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
|
4
|
+
|
5
|
+
def restart():
|
6
|
+
python = sys.executable
|
7
|
+
os.execl(python, python, '-B', *sys.argv)
|
crxsnake/utils/steam.py
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
import re
|
2
|
+
import json
|
3
|
+
|
4
|
+
from aiohttp import ClientError
|
5
|
+
|
6
|
+
from crxsnake.web.request import Request
|
7
|
+
from crxsnake.logger import log
|
8
|
+
|
9
|
+
|
10
|
+
async def get_steam_id(steam_profile_url: str) -> int | None:
|
11
|
+
"""Get steam id from steam profile url.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
steam_profile_url (str)
|
15
|
+
Returns:
|
16
|
+
int | None
|
17
|
+
"""
|
18
|
+
async with Request() as steam:
|
19
|
+
try:
|
20
|
+
response = await steam.request(steam_profile_url)
|
21
|
+
if not response:
|
22
|
+
return None
|
23
|
+
|
24
|
+
data_match = re.search(r"g_rgProfileData\s*=\s*(?P<json>{.*?});", response)
|
25
|
+
if data_match:
|
26
|
+
return int(json.loads(data_match.group("json"))["steamid"])
|
27
|
+
|
28
|
+
except ClientError as e:
|
29
|
+
log.error("Error during request", "Steam", e)
|
30
|
+
except (KeyError, ValueError, json.JSONDecodeError) as e:
|
31
|
+
log.error("Error during data processing", "Steam", e)
|
32
|
+
|
33
|
+
return None
|
crxsnake/web/__init__.py
ADDED
File without changes
|
crxsnake/web/request.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
from aiohttp import ClientSession, ClientTimeout, ClientError
|
2
|
+
from crxsnake.logger import log
|
3
|
+
|
4
|
+
|
5
|
+
class Request:
|
6
|
+
TIMEOUT = ClientTimeout(total=20)
|
7
|
+
STEAM_USER_AGENT = "python-steam/1.4.4"
|
8
|
+
|
9
|
+
def __init__(self, user_agent: str = None):
|
10
|
+
self.session = None
|
11
|
+
self.user_agent = {"User-Agent": user_agent or self.STEAM_USER_AGENT}
|
12
|
+
|
13
|
+
async def __aenter__(self):
|
14
|
+
self.session = ClientSession(headers=self.user_agent, timeout=self.TIMEOUT)
|
15
|
+
return self
|
16
|
+
|
17
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
18
|
+
if self.session:
|
19
|
+
await self.session.close()
|
20
|
+
|
21
|
+
async def request(self, url: str, method: str = "GET", **kwargs) -> str | None:
|
22
|
+
"""Make a request to a URL.
|
23
|
+
Args:
|
24
|
+
url (str): URL to request
|
25
|
+
method (str, optional): Request method. Defaults to "GET".
|
26
|
+
Returns:
|
27
|
+
str | None: Response text or None if an error occurred
|
28
|
+
"""
|
29
|
+
try:
|
30
|
+
async with self.session.request(method, url, **kwargs) as response:
|
31
|
+
response.raise_for_status()
|
32
|
+
return await response.text()
|
33
|
+
|
34
|
+
except ClientError as e:
|
35
|
+
log.error("Error when requesting", "Request", e)
|
36
|
+
except Exception as e:
|
37
|
+
log.error(f"Unexpected mistake", "Request", e)
|
38
|
+
return None
|
@@ -1,21 +1,21 @@
|
|
1
|
-
MIT License
|
2
|
-
|
3
|
-
Copyright (c) 2024 CRX-DEV
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
13
|
-
copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
SOFTWARE.
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 CRX-DEV
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: crxsnake
|
3
|
+
Version: 1.4.8
|
4
|
+
Summary: A set of my project creation utilities
|
5
|
+
License: LICENSE
|
6
|
+
Author: CYRAX
|
7
|
+
Author-email: cherniq66@gmail.com
|
8
|
+
Requires-Python: >=3.12,<4.0
|
9
|
+
Classifier: License :: Other/Proprietary License
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
13
|
+
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
14
|
+
Requires-Dist: disnake (>=2.10.1,<3.0.0)
|
15
|
+
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
16
|
+
Requires-Dist: tortoise-orm (>=0.24.0,<0.25.0)
|
17
|
+
Description-Content-Type: text/markdown
|
18
|
+
|
19
|
+
# CRX-Snake
|
20
|
+
|
21
|
+
**CRX-Snake** is a set of utilities designed to simplify the development of projects, including the creation of Discord bots. This library contains a number of features and tools to help you quickly develop and customize bots and other components of your projects.
|
22
|
+
|
23
|
+
## Description
|
24
|
+
|
25
|
+
crxsnake was developed specifically for internal use and is now available to other developers. It provides user-friendly features and solutions for common tasks in bot development and project management. It includes:
|
26
|
+
|
27
|
+
- **Features for working with Discord**: Tools for creating and managing Discord bots using the Disnake library.
|
28
|
+
- **File Utilities**: Functions for reading and writing JSON files, making it easy to work with configurations and data.
|
29
|
+
- **Database Tools**: Interfaces with Tortoise ORM for data management and query execution.
|
30
|
+
- **Logging and debugging**: Includes functions for error logging and debugging.
|
31
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
crxsnake/__init__.py,sha256=t_2lmaxgOQXHeu52vqBKZZxp3ly77-opJJi_yZZZqSs,881
|
2
|
+
crxsnake/database.py,sha256=SqwR8dQQhKLYkOGHZy7XJBpbVNU-I1ofCifOIZfYEqY,859
|
3
|
+
crxsnake/logger.py,sha256=FUFF8FEreJSYI8_CqR4ux-a8HKbdZFwh7k0HbLySzDk,1791
|
4
|
+
crxsnake/readers.py,sha256=R6nfcsVyv73NDnj7-rhQxQhnDoU9tovkB9OFnZLhyWA,2045
|
5
|
+
crxsnake/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
crxsnake/utils/dayz/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
crxsnake/utils/dayz/convert.py,sha256=Vk5ZfvjMA-llIMMTKsRGDzs99RuPtq_oDgNPfovNO-M,770
|
8
|
+
crxsnake/utils/dayz/hotline.py,sha256=zhf8u5UzWkG1NdN8EgPdD8jRzfRQsrFrktHTEW9B8w0,2454
|
9
|
+
crxsnake/utils/discord/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
crxsnake/utils/discord/cogs.py,sha256=cGeHN1WPF1waLSkvpKqc0ZkBsf5K6EeRafNHnB-Qkjk,992
|
11
|
+
crxsnake/utils/discord/colors.py,sha256=8AcJTzAQWKbc0Mmg2Kvwe_9Sl5UtvnKBRopNrzVczlE,171
|
12
|
+
crxsnake/utils/discord/embed.py,sha256=iruBTB9O6dsLwJs1KDNVZ7dv4-l2IZ_0WRu2K1kl8H4,2503
|
13
|
+
crxsnake/utils/discord/parse.py,sha256=RTJGHH_N-hg62hhLyXil2iFpw7kX4uTu0ibWtOPG6jQ,413
|
14
|
+
crxsnake/utils/misc.py,sha256=GrvsgGu6s2wrqL0WxPusOH1bXBBn5jnmvldte4gKxLI,112
|
15
|
+
crxsnake/utils/steam.py,sha256=MWrgCJwU2Xm6SNPa5tWY-R9yLWOglOj_uJxH6hC3sLs,920
|
16
|
+
crxsnake/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
crxsnake/web/request.py,sha256=tkeHs8-FI-XGkIniiSmh5NXcIT4IuriLFIwepFGz1w4,1335
|
18
|
+
crxsnake-1.4.8.dist-info/LICENSE,sha256=F3EID4k6URP-CPk1GaylW3iyy0vZpRwGUSSL5b2HX24,1064
|
19
|
+
crxsnake-1.4.8.dist-info/METADATA,sha256=ySBns8jnOQV3WZbb9xyoQLgkdRCiuRMpP0Io2z3x_1w,1525
|
20
|
+
crxsnake-1.4.8.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
21
|
+
crxsnake-1.4.8.dist-info/RECORD,,
|
crxsnake/cogs.py
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
from sys import path
|
2
|
-
from pathlib import Path
|
3
|
-
from .logger import log
|
4
|
-
|
5
|
-
|
6
|
-
async def load_cogs(bot, folder: str = "src", utils_folder: str = "utils") -> None:
|
7
|
-
try:
|
8
|
-
path.append(str(Path(folder)))
|
9
|
-
|
10
|
-
for entry in Path(folder).iterdir():
|
11
|
-
for filename in entry.iterdir():
|
12
|
-
if filename.suffix == ".py" and entry.name != utils_folder:
|
13
|
-
bot.load_extension(f"{entry.name}.{filename.stem}")
|
14
|
-
|
15
|
-
log.info(f"[Discord] Cogs success loaded")
|
16
|
-
|
17
|
-
except Exception as e:
|
18
|
-
log.error(f"[Discord] An error occurred while loading modules\n╰─> Error: {e}")
|
crxsnake/tortoise.py
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
from .logger import log
|
2
|
-
from tortoise import Tortoise
|
3
|
-
|
4
|
-
|
5
|
-
class Database:
|
6
|
-
|
7
|
-
def __init__(self, db_url: str = "sqlite://settings/database/database.db"):
|
8
|
-
self.db_url = db_url
|
9
|
-
|
10
|
-
async def db_connect(self) -> None:
|
11
|
-
try:
|
12
|
-
await Tortoise.init(
|
13
|
-
db_url=self.db_url,
|
14
|
-
modules={
|
15
|
-
"models": ["src.utils"]
|
16
|
-
}
|
17
|
-
)
|
18
|
-
await Tortoise.generate_schemas()
|
19
|
-
log.info("[Database] SqLite connected")
|
20
|
-
|
21
|
-
except Exception as e:
|
22
|
-
log.error(f"[Database] Failed to connect SqLite: {e}")
|
23
|
-
|
24
|
-
@staticmethod
|
25
|
-
async def disconnect_db() -> None:
|
26
|
-
await Tortoise.close_connections()
|
@@ -1,37 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.2
|
2
|
-
Name: crxsnake
|
3
|
-
Version: 1.4.7
|
4
|
-
Home-page: https://discord.gg/EEp67FWQDP
|
5
|
-
Author: CRX-DEV
|
6
|
-
Author-email: cherniq66@gmail.com
|
7
|
-
License: MIT License
|
8
|
-
Project-URL: Repository, https://github.com/cyrax-dev/crxsnake
|
9
|
-
Project-URL: Discord, https://discord.gg/EEp67FWQDP
|
10
|
-
Description-Content-Type: text/markdown
|
11
|
-
License-File: LICENSE
|
12
|
-
Requires-Dist: setuptools
|
13
|
-
Requires-Dist: tortoise-orm
|
14
|
-
Requires-Dist: disnake
|
15
|
-
Requires-Dist: aiofiles
|
16
|
-
Requires-Dist: loguru
|
17
|
-
Dynamic: author
|
18
|
-
Dynamic: author-email
|
19
|
-
Dynamic: description
|
20
|
-
Dynamic: description-content-type
|
21
|
-
Dynamic: home-page
|
22
|
-
Dynamic: license
|
23
|
-
Dynamic: project-url
|
24
|
-
Dynamic: requires-dist
|
25
|
-
|
26
|
-
# CRX-Snake
|
27
|
-
|
28
|
-
**CRX-Snake** is a set of utilities designed to simplify the development of projects, including the creation of Discord bots. This library contains a number of features and tools to help you quickly develop and customize bots and other components of your projects.
|
29
|
-
|
30
|
-
## Description
|
31
|
-
|
32
|
-
CRX-Snake was developed specifically for internal use and is now available to other developers. It provides user-friendly features and solutions for common tasks in bot development and project management. It includes:
|
33
|
-
|
34
|
-
- **Features for working with Discord**: Tools for creating and managing Discord bots using the Disnake library.
|
35
|
-
- **File Utilities**: Functions for reading and writing JSON files, making it easy to work with configurations and data.
|
36
|
-
- **Database Tools**: Interfaces with Tortoise ORM for data management and query execution.
|
37
|
-
- **Logging and debugging**: Includes functions for error logging and debugging.
|
crxsnake-1.4.7.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
crxsnake/__init__.py,sha256=Jt2fTmwyJ0VjIjmUyFipG99CBC7-BBe83rNKdC6vQ1Y,572
|
2
|
-
crxsnake/cogs.py,sha256=E0BF6SBvF4K7KlfpwZ6j6mpIcJYX3H2neaHiLYPK2fI,633
|
3
|
-
crxsnake/files.py,sha256=oV8xNSETRLlE_UR5sd0-zaANQLBR9hZ0GlAXDgPn5Sc,1486
|
4
|
-
crxsnake/logger.py,sha256=yv1298VWx0TwoBE2X9wJq1WOsGTRwDfdJ-BLds0Jnew,778
|
5
|
-
crxsnake/tortoise.py,sha256=2jaeeAXin8WOEA65w8mfj2geAWhYClcY8VUCtjdGCB4,731
|
6
|
-
crxsnake/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
crxsnake/utils/convert.py,sha256=lfS4fhePDdNJ4LS0PqT523bs34auIA5114NfZi4aaeE,542
|
8
|
-
crxsnake/utils/embed_log.py,sha256=A0nEf8-OBM6lDYpWFW5qqCeo0O4fZv_nJPdhiVYPfGQ,2711
|
9
|
-
crxsnake/utils/hotline.py,sha256=l82Pf6slP4NH_rhXzo7iWjhfN4zXihoOrFxDrbqMh8U,2524
|
10
|
-
crxsnake/utils/misc.py,sha256=eV6WBod5yMpWVH4PaCG4vNIWN9xhoeO-b6z3mRNPe0Q,434
|
11
|
-
crxsnake-1.4.7.dist-info/LICENSE,sha256=xt4Ru6tOCU8e2wVlx_lgx7wh-sNLKbiCbmANzr2e3cc,1085
|
12
|
-
crxsnake-1.4.7.dist-info/METADATA,sha256=FLnrfBjYYfX_wpzUBjzWPUrgwLmuXTw2G6T82rmtlXQ,1596
|
13
|
-
crxsnake-1.4.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
14
|
-
crxsnake-1.4.7.dist-info/top_level.txt,sha256=GOgG6tMH05czsfM6y-Tvm9LUSd-0PPuuuYkkkbWHZjQ,9
|
15
|
-
crxsnake-1.4.7.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
crxsnake
|