django-nativemojo 0.1.10__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.
- django_nativemojo-0.1.10.dist-info/LICENSE +19 -0
- django_nativemojo-0.1.10.dist-info/METADATA +96 -0
- django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
- django_nativemojo-0.1.10.dist-info/RECORD +194 -0
- django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
- mojo/__init__.py +3 -0
- mojo/apps/account/__init__.py +1 -0
- mojo/apps/account/admin.py +91 -0
- mojo/apps/account/apps.py +16 -0
- mojo/apps/account/migrations/0001_initial.py +77 -0
- mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
- mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
- mojo/apps/account/migrations/__init__.py +0 -0
- mojo/apps/account/models/__init__.py +3 -0
- mojo/apps/account/models/group.py +98 -0
- mojo/apps/account/models/member.py +95 -0
- mojo/apps/account/models/pkey.py +18 -0
- mojo/apps/account/models/user.py +211 -0
- mojo/apps/account/rest/__init__.py +3 -0
- mojo/apps/account/rest/group.py +25 -0
- mojo/apps/account/rest/user.py +47 -0
- mojo/apps/account/utils/__init__.py +0 -0
- mojo/apps/account/utils/jwtoken.py +72 -0
- mojo/apps/account/utils/passkeys.py +54 -0
- mojo/apps/fileman/README.md +549 -0
- mojo/apps/fileman/__init__.py +0 -0
- mojo/apps/fileman/apps.py +15 -0
- mojo/apps/fileman/backends/__init__.py +117 -0
- mojo/apps/fileman/backends/base.py +319 -0
- mojo/apps/fileman/backends/filesystem.py +397 -0
- mojo/apps/fileman/backends/s3.py +398 -0
- mojo/apps/fileman/examples/configurations.py +378 -0
- mojo/apps/fileman/examples/usage_example.py +665 -0
- mojo/apps/fileman/management/__init__.py +1 -0
- mojo/apps/fileman/management/commands/__init__.py +1 -0
- mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
- mojo/apps/fileman/models/__init__.py +7 -0
- mojo/apps/fileman/models/file.py +292 -0
- mojo/apps/fileman/models/manager.py +227 -0
- mojo/apps/fileman/models/render.py +0 -0
- mojo/apps/fileman/rest/__init__ +0 -0
- mojo/apps/fileman/rest/__init__.py +23 -0
- mojo/apps/fileman/rest/fileman.py +13 -0
- mojo/apps/fileman/rest/upload.py +92 -0
- mojo/apps/fileman/utils/__init__.py +19 -0
- mojo/apps/fileman/utils/upload.py +616 -0
- mojo/apps/incident/__init__.py +1 -0
- mojo/apps/incident/handlers/__init__.py +3 -0
- mojo/apps/incident/handlers/event_handlers.py +142 -0
- mojo/apps/incident/migrations/0001_initial.py +83 -0
- mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
- mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
- mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
- mojo/apps/incident/migrations/__init__.py +0 -0
- mojo/apps/incident/models/__init__.py +3 -0
- mojo/apps/incident/models/event.py +135 -0
- mojo/apps/incident/models/incident.py +33 -0
- mojo/apps/incident/models/rule.py +247 -0
- mojo/apps/incident/parsers/__init__.py +0 -0
- mojo/apps/incident/parsers/ossec/__init__.py +1 -0
- mojo/apps/incident/parsers/ossec/core.py +82 -0
- mojo/apps/incident/parsers/ossec/parsed.py +23 -0
- mojo/apps/incident/parsers/ossec/rules.py +124 -0
- mojo/apps/incident/parsers/ossec/utils.py +169 -0
- mojo/apps/incident/reporter.py +42 -0
- mojo/apps/incident/rest/__init__.py +2 -0
- mojo/apps/incident/rest/event.py +23 -0
- mojo/apps/incident/rest/ossec.py +22 -0
- mojo/apps/logit/__init__.py +0 -0
- mojo/apps/logit/admin.py +37 -0
- mojo/apps/logit/migrations/0001_initial.py +32 -0
- mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
- mojo/apps/logit/migrations/0003_log_level.py +18 -0
- mojo/apps/logit/migrations/__init__.py +0 -0
- mojo/apps/logit/models/__init__.py +1 -0
- mojo/apps/logit/models/log.py +57 -0
- mojo/apps/logit/rest.py +9 -0
- mojo/apps/metrics/README.md +79 -0
- mojo/apps/metrics/__init__.py +12 -0
- mojo/apps/metrics/redis_metrics.py +331 -0
- mojo/apps/metrics/rest/__init__.py +1 -0
- mojo/apps/metrics/rest/base.py +152 -0
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/metrics/utils.py +227 -0
- mojo/apps/notify/README.md +91 -0
- mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
- mojo/apps/notify/__init__.py +0 -0
- mojo/apps/notify/admin.py +52 -0
- mojo/apps/notify/handlers/__init__.py +0 -0
- mojo/apps/notify/handlers/example_handlers.py +516 -0
- mojo/apps/notify/handlers/ses/__init__.py +25 -0
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +25 -0
- mojo/apps/notify/handlers/ses/message.py +86 -0
- mojo/apps/notify/management/__init__.py +0 -0
- mojo/apps/notify/management/commands/__init__.py +1 -0
- mojo/apps/notify/management/commands/process_notifications.py +370 -0
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +12 -0
- mojo/apps/notify/models/account.py +128 -0
- mojo/apps/notify/models/attachment.py +24 -0
- mojo/apps/notify/models/bounce.py +68 -0
- mojo/apps/notify/models/complaint.py +40 -0
- mojo/apps/notify/models/inbox.py +113 -0
- mojo/apps/notify/models/inbox_message.py +173 -0
- mojo/apps/notify/models/outbox.py +129 -0
- mojo/apps/notify/models/outbox_message.py +288 -0
- mojo/apps/notify/models/template.py +30 -0
- mojo/apps/notify/providers/__init__.py +0 -0
- mojo/apps/notify/providers/aws.py +73 -0
- mojo/apps/notify/rest/__init__.py +0 -0
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +2 -0
- mojo/apps/notify/utils/notifications.py +404 -0
- mojo/apps/notify/utils/parsing.py +202 -0
- mojo/apps/notify/utils/render.py +144 -0
- mojo/apps/tasks/README.md +118 -0
- mojo/apps/tasks/__init__.py +11 -0
- mojo/apps/tasks/manager.py +489 -0
- mojo/apps/tasks/rest/__init__.py +2 -0
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +62 -0
- mojo/apps/tasks/runner.py +174 -0
- mojo/apps/tasks/tq_handlers.py +14 -0
- mojo/decorators/__init__.py +3 -0
- mojo/decorators/auth.py +25 -0
- mojo/decorators/cron.py +31 -0
- mojo/decorators/http.py +132 -0
- mojo/decorators/validate.py +14 -0
- mojo/errors.py +88 -0
- mojo/helpers/__init__.py +0 -0
- mojo/helpers/aws/__init__.py +0 -0
- mojo/helpers/aws/client.py +8 -0
- mojo/helpers/aws/s3.py +268 -0
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/cron.py +79 -0
- mojo/helpers/crypto/__init__.py +4 -0
- mojo/helpers/crypto/aes.py +60 -0
- mojo/helpers/crypto/hash.py +59 -0
- mojo/helpers/crypto/privpub/__init__.py +1 -0
- mojo/helpers/crypto/privpub/hybrid.py +97 -0
- mojo/helpers/crypto/privpub/rsa.py +104 -0
- mojo/helpers/crypto/sign.py +36 -0
- mojo/helpers/crypto/too.l.py +25 -0
- mojo/helpers/crypto/utils.py +26 -0
- mojo/helpers/daemon.py +94 -0
- mojo/helpers/dates.py +69 -0
- mojo/helpers/dns/__init__.py +0 -0
- mojo/helpers/dns/godaddy.py +62 -0
- mojo/helpers/filetypes.py +128 -0
- mojo/helpers/logit.py +310 -0
- mojo/helpers/modules.py +95 -0
- mojo/helpers/paths.py +63 -0
- mojo/helpers/redis.py +10 -0
- mojo/helpers/request.py +89 -0
- mojo/helpers/request_parser.py +269 -0
- mojo/helpers/response.py +14 -0
- mojo/helpers/settings.py +146 -0
- mojo/helpers/sysinfo.py +140 -0
- mojo/helpers/ua.py +0 -0
- mojo/middleware/__init__.py +0 -0
- mojo/middleware/auth.py +26 -0
- mojo/middleware/logging.py +55 -0
- mojo/middleware/mojo.py +21 -0
- mojo/migrations/0001_initial.py +32 -0
- mojo/migrations/__init__.py +0 -0
- mojo/models/__init__.py +2 -0
- mojo/models/meta.py +262 -0
- mojo/models/rest.py +538 -0
- mojo/models/secrets.py +59 -0
- mojo/rest/__init__.py +1 -0
- mojo/rest/info.py +26 -0
- mojo/serializers/__init__.py +0 -0
- mojo/serializers/models.py +165 -0
- mojo/serializers/openapi.py +188 -0
- mojo/urls.py +38 -0
- mojo/ws4redis/README.md +174 -0
- mojo/ws4redis/__init__.py +2 -0
- mojo/ws4redis/client.py +283 -0
- mojo/ws4redis/connection.py +327 -0
- mojo/ws4redis/exceptions.py +32 -0
- mojo/ws4redis/redis.py +183 -0
- mojo/ws4redis/servers/__init__.py +0 -0
- mojo/ws4redis/servers/base.py +86 -0
- mojo/ws4redis/servers/django.py +171 -0
- mojo/ws4redis/servers/uwsgi.py +63 -0
- mojo/ws4redis/settings.py +45 -0
- mojo/ws4redis/utf8validator.py +128 -0
- mojo/ws4redis/websocket.py +403 -0
- testit/__init__.py +0 -0
- testit/client.py +147 -0
- testit/faker.py +20 -0
- testit/helpers.py +198 -0
- testit/runner.py +262 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
import hmac
|
2
|
+
import hashlib
|
3
|
+
import json
|
4
|
+
from django.conf import settings
|
5
|
+
|
6
|
+
|
7
|
+
def generate_signature(data, secret_key=settings.SECRET_KEY):
|
8
|
+
"""
|
9
|
+
Generate an HMAC-SHA256 signature for the given data using the secret key.
|
10
|
+
|
11
|
+
:param data: str, bytes, or dict - the data to sign
|
12
|
+
:param secret_key: str or bytes - the shared secret key
|
13
|
+
:return: str - the hex signature
|
14
|
+
"""
|
15
|
+
if isinstance(data, dict):
|
16
|
+
data = json.dumps(data, separators=(',', ':'), sort_keys=True)
|
17
|
+
if isinstance(data, str):
|
18
|
+
data = data.encode('utf-8')
|
19
|
+
if isinstance(secret_key, str):
|
20
|
+
secret_key = secret_key.encode('utf-8')
|
21
|
+
|
22
|
+
signature = hmac.new(secret_key, data, hashlib.sha256).hexdigest()
|
23
|
+
return signature
|
24
|
+
|
25
|
+
|
26
|
+
def verify_signature(data, signature, secret_key=settings.SECRET_KEY):
|
27
|
+
"""
|
28
|
+
Verify an HMAC-SHA256 signature.
|
29
|
+
|
30
|
+
:param data: str, bytes, or dict - the original data
|
31
|
+
:param signature: str - the provided hex signature
|
32
|
+
:param secret_key: str or bytes - the shared secret key
|
33
|
+
:return: bool - True if valid, False otherwise
|
34
|
+
"""
|
35
|
+
expected_signature = generate_signature(data, secret_key)
|
36
|
+
return hmac.compare_digest(expected_signature, signature)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from mojo.helpers import paths
|
2
|
+
import mojo.errors
|
3
|
+
from objict import objict
|
4
|
+
|
5
|
+
cache = objict()
|
6
|
+
|
7
|
+
def get_global_encrypter():
|
8
|
+
return get_privpub("global_encrypter.pub")
|
9
|
+
|
10
|
+
def get_global_decrypter():
|
11
|
+
return get_privpub("global_decrypter.priv")
|
12
|
+
|
13
|
+
|
14
|
+
def get_privpub(key_file_name):
|
15
|
+
name, ext = key_file_name.split(".")
|
16
|
+
if cache.get(name) is None:
|
17
|
+
key_file = paths.VAR_ROOT / "keys" / f"{key_file_name}"
|
18
|
+
if not key_file.exists():
|
19
|
+
raise mojo.errors.ValueException(f"missing var/keys/{key_file_name}")
|
20
|
+
from mojo.helpers.crypto.privpub import PrivatePublicEncryption
|
21
|
+
if ext == "pub":
|
22
|
+
cache[name] = PrivatePublicEncryption(public_key_file=key_file)
|
23
|
+
else:
|
24
|
+
cache[name] = PrivatePublicEncryption(private_key_file=key_file)
|
25
|
+
return cache[name]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from Crypto.Random import get_random_bytes
|
2
|
+
import string
|
3
|
+
|
4
|
+
|
5
|
+
def generate_key(bit_size=128):
|
6
|
+
byte_size = bit_size // 8
|
7
|
+
return random_string(byte_size, allow_special=False)
|
8
|
+
|
9
|
+
|
10
|
+
def random_bytes(length):
|
11
|
+
return get_random_bytes(length)
|
12
|
+
|
13
|
+
|
14
|
+
def random_string(length, allow_digits=True, allow_chars=True, allow_special=True):
|
15
|
+
characters = ''
|
16
|
+
if allow_digits:
|
17
|
+
characters += string.digits
|
18
|
+
if allow_chars:
|
19
|
+
characters += string.ascii_letters
|
20
|
+
if allow_special:
|
21
|
+
characters += string.punctuation
|
22
|
+
|
23
|
+
if characters == '':
|
24
|
+
raise ValueError("At least one character set (digits, chars, special) must be allowed")
|
25
|
+
random_bytes = get_random_bytes(length)
|
26
|
+
return ''.join(characters[b % len(characters)] for b in random_bytes)
|
mojo/helpers/daemon.py
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import signal
|
4
|
+
|
5
|
+
class Daemon:
|
6
|
+
"""
|
7
|
+
A simple base class for creating a Linux daemon process without using third-party daemon libraries.
|
8
|
+
"""
|
9
|
+
def __init__(self, name, var_path=None):
|
10
|
+
self.name = name
|
11
|
+
self.var_path = var_path
|
12
|
+
if var_path is not None:
|
13
|
+
self.pid_file = os.path.join(var_path, name, f"{name}.pid")
|
14
|
+
self.log_file = os.path.join(var_path, name, f"{name}.log")
|
15
|
+
self.error_file = os.path.join(var_path, name, "errors.log")
|
16
|
+
else:
|
17
|
+
self.pid_file = f"{name}.pid"
|
18
|
+
self.log_file = f"{name}.log"
|
19
|
+
self.error_file = "errors.log"
|
20
|
+
self.running = True
|
21
|
+
|
22
|
+
def daemonize(self):
|
23
|
+
"""Daemonizes the process using the double-fork technique."""
|
24
|
+
if os.fork() > 0:
|
25
|
+
sys.exit(0) # Exit parent
|
26
|
+
os.chdir("/")
|
27
|
+
os.setsid() # Become session leader
|
28
|
+
os.umask(0)
|
29
|
+
if os.fork() > 0:
|
30
|
+
sys.exit(0) # Exit second parent
|
31
|
+
# Redirect standard file descriptors
|
32
|
+
sys.stdout.flush()
|
33
|
+
sys.stderr.flush()
|
34
|
+
with open(self.log_file, "a", buffering=1) as log, open(self.error_file, "a", buffering=1) as err:
|
35
|
+
os.dup2(log.fileno(), sys.stdout.fileno())
|
36
|
+
os.dup2(err.fileno(), sys.stderr.fileno())
|
37
|
+
self.write_pid()
|
38
|
+
self.register_signal_handlers()
|
39
|
+
|
40
|
+
def write_pid(self):
|
41
|
+
"""Writes the process ID to a PID file."""
|
42
|
+
with open(self.pid_file, "w") as f:
|
43
|
+
f.write(str(os.getpid()) + "\n")
|
44
|
+
|
45
|
+
def remove_pid(self):
|
46
|
+
"""Removes the PID file upon shutdown."""
|
47
|
+
if os.path.exists(self.pid_file):
|
48
|
+
os.remove(self.pid_file)
|
49
|
+
|
50
|
+
def register_signal_handlers(self):
|
51
|
+
"""Registers signal handlers for graceful shutdown."""
|
52
|
+
signal.signal(signal.SIGTERM, self.handle_signal)
|
53
|
+
signal.signal(signal.SIGINT, self.handle_signal)
|
54
|
+
|
55
|
+
def handle_signal(self, signum, frame):
|
56
|
+
"""Handles termination signals to shut down cleanly."""
|
57
|
+
self.running = False
|
58
|
+
if signum == signal.SIGTERM:
|
59
|
+
self.cleanup()
|
60
|
+
sys.exit(0)
|
61
|
+
|
62
|
+
def cleanup(self):
|
63
|
+
"""Override this method for custom cleanup actions before shutdown."""
|
64
|
+
self.remove_pid()
|
65
|
+
|
66
|
+
def start(self):
|
67
|
+
"""Starts the daemon process."""
|
68
|
+
if os.path.exists(self.pid_file):
|
69
|
+
sys.exit(1)
|
70
|
+
self.daemonize()
|
71
|
+
self.run()
|
72
|
+
|
73
|
+
def get_pid(self):
|
74
|
+
"""Returns the process ID of the running daemon."""
|
75
|
+
try:
|
76
|
+
with open(self.pid_file, "r") as f:
|
77
|
+
return int(f.read().strip())
|
78
|
+
except FileNotFoundError:
|
79
|
+
return None
|
80
|
+
|
81
|
+
def stop(self, force=False):
|
82
|
+
"""Stops the running daemon by sending SIGTERM or SIGINT based on the force option."""
|
83
|
+
try:
|
84
|
+
pid = self.get_pid()
|
85
|
+
os.kill(pid, signal.SIGINT if force else signal.SIGTERM)
|
86
|
+
self.remove_pid()
|
87
|
+
except FileNotFoundError:
|
88
|
+
pass
|
89
|
+
except ProcessLookupError:
|
90
|
+
self.remove_pid()
|
91
|
+
|
92
|
+
def run(self):
|
93
|
+
"""Override this method in subclasses to define the daemon's main loop."""
|
94
|
+
raise NotImplementedError("You must override the run() method!")
|
mojo/helpers/dates.py
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
from datetime import datetime, timedelta
|
2
|
+
import pytz
|
3
|
+
import objict
|
4
|
+
from .settings import settings
|
5
|
+
|
6
|
+
|
7
|
+
def parse_datetime(value, timezone=None):
|
8
|
+
date = objict.parse_date(value)
|
9
|
+
if date.tzinfo is None:
|
10
|
+
date = pytz.UTC.localize(date)
|
11
|
+
if timezone is not None:
|
12
|
+
local_tz = pytz.timezone(timezone)
|
13
|
+
else:
|
14
|
+
local_tz = pytz.UTC
|
15
|
+
return date.astimezone(local_tz)
|
16
|
+
|
17
|
+
|
18
|
+
def get_local_day(timezone, dt_utc=None):
|
19
|
+
if dt_utc is None:
|
20
|
+
dt_utc = datetime.now(tz=pytz.UTC)
|
21
|
+
local_tz = pytz.timezone(timezone)
|
22
|
+
local_dt = dt_utc.astimezone(local_tz)
|
23
|
+
start_of_day = local_tz.localize(datetime(
|
24
|
+
local_dt.year, local_dt.month, local_dt.day, 0, 0, 0))
|
25
|
+
end_of_day = start_of_day + timedelta(days=1)
|
26
|
+
return start_of_day.astimezone(pytz.UTC), end_of_day.astimezone(pytz.UTC)
|
27
|
+
|
28
|
+
def get_local_time(timezone, dt_utc=None):
|
29
|
+
"""Convert a passed in datetime to the group's timezone."""
|
30
|
+
if dt_utc is None:
|
31
|
+
dt_utc = datetime.now(tz=pytz.UTC)
|
32
|
+
if dt_utc.tzinfo is None:
|
33
|
+
dt_utc = pytz.UTC.localize(dt_utc)
|
34
|
+
local_tz = pytz.timezone(timezone)
|
35
|
+
return dt_utc.astimezone(local_tz)
|
36
|
+
|
37
|
+
def utcnow():
|
38
|
+
"""look at django setting to get proper datetime aware or not"""
|
39
|
+
if settings.USE_TZ:
|
40
|
+
return datetime.now(tz=pytz.UTC)
|
41
|
+
return datetime.utcnow()
|
42
|
+
|
43
|
+
|
44
|
+
def add(when=None, seconds=None, minutes=None, hours=None, days=None):
|
45
|
+
if when is None:
|
46
|
+
when = utcnow()
|
47
|
+
elapsed_time = timedelta(
|
48
|
+
seconds=seconds or 0,
|
49
|
+
minutes=minutes or 0,
|
50
|
+
hours=hours or 0,
|
51
|
+
days=days or 0
|
52
|
+
)
|
53
|
+
return when + elapsed_time
|
54
|
+
|
55
|
+
|
56
|
+
def subtract(when=None, seconds=None, minutes=None, hours=None, days=None):
|
57
|
+
if when is None:
|
58
|
+
when = utcnow()
|
59
|
+
elapsed_time = timedelta(
|
60
|
+
seconds=seconds or 0,
|
61
|
+
minutes=minutes or 0,
|
62
|
+
hours=hours or 0,
|
63
|
+
days=days or 0
|
64
|
+
)
|
65
|
+
return when - elapsed_time
|
66
|
+
|
67
|
+
|
68
|
+
def has_time_elsapsed(when, seconds=None, minutes=None, hours=None, days=None):
|
69
|
+
return utcnow() >= add(when, seconds, minutes, hours, days)
|
File without changes
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# aws_tools/godaddy.py
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from objict import objict
|
5
|
+
BASE_URL = "https://api.godaddy.com/v1"
|
6
|
+
|
7
|
+
class DNSManager:
|
8
|
+
def __init__(self, api_key, api_secret):
|
9
|
+
self.api_key = api_key
|
10
|
+
self.api_secret = api_secret
|
11
|
+
|
12
|
+
def _headers(self):
|
13
|
+
return {
|
14
|
+
"Authorization": f"sso-key {self.api_key}:{self.api_secret}",
|
15
|
+
"Content-Type": "application/json",
|
16
|
+
"Accept": "application/json"
|
17
|
+
}
|
18
|
+
|
19
|
+
def get_domains(self, status="ACTIVE"):
|
20
|
+
url = f"{BASE_URL}/domains"
|
21
|
+
params = {"statuses": status} if status else {}
|
22
|
+
resp = requests.get(url, headers=self._headers(), params=params)
|
23
|
+
# resp.raise_for_status()
|
24
|
+
return objict(resp.json())
|
25
|
+
|
26
|
+
def get_domain_info(self, domain):
|
27
|
+
url = f"{BASE_URL}/domains/{domain}"
|
28
|
+
resp = requests.get(url, headers=self._headers())
|
29
|
+
# resp.raise_for_status()
|
30
|
+
return objict(resp.json())
|
31
|
+
|
32
|
+
def is_domain_active(self, domain):
|
33
|
+
info = self.get_domain_info(domain)
|
34
|
+
return info.status == "ACTIVE"
|
35
|
+
|
36
|
+
def get_record(self, domain, record_type, name):
|
37
|
+
url = f"{BASE_URL}/domains/{domain}/records/{record_type}/{name}"
|
38
|
+
resp = requests.get(url, headers=self._headers())
|
39
|
+
# resp.raise_for_status()
|
40
|
+
return objict(resp.json())
|
41
|
+
|
42
|
+
def get_records(self, domain):
|
43
|
+
url = f"{BASE_URL}/domains/{domain}/records"
|
44
|
+
resp = requests.get(url, headers=self._headers())
|
45
|
+
# resp.raise_for_status()
|
46
|
+
return objict(resp.json())
|
47
|
+
|
48
|
+
def edit_record(self, domain, record_type, name, data, ttl):
|
49
|
+
url = f"{BASE_URL}/domains/{domain}/records/{record_type}/{name}"
|
50
|
+
payload = [{"data": data, "ttl": ttl}]
|
51
|
+
resp = requests.put(url, headers=self._headers(), json=payload)
|
52
|
+
# resp.raise_for_status()
|
53
|
+
return objict(resp.json()) if resp.content else {"status": "success"}
|
54
|
+
|
55
|
+
def add_record(self, domain, record_type, name, data, ttl):
|
56
|
+
return self.edit_record(domain, record_type, name, data, ttl)
|
57
|
+
|
58
|
+
def bulk_add_records(self, domain, records):
|
59
|
+
url = f"{BASE_URL}/domains/{domain}/records"
|
60
|
+
resp = requests.patch(url, headers=self._headers(), json=records)
|
61
|
+
# resp.raise_for_status()
|
62
|
+
return objict(resp.json()) if resp.content else {"status": "success"}
|
@@ -0,0 +1,128 @@
|
|
1
|
+
EXTENSION_TO_MIME = {
|
2
|
+
# Documents
|
3
|
+
'.pdf': 'application/pdf',
|
4
|
+
'.doc': 'application/msword',
|
5
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
6
|
+
'.xls': 'application/vnd.ms-excel',
|
7
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
8
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
9
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
10
|
+
'.csv': 'text/csv',
|
11
|
+
'.tsv': 'text/tab-separated-values',
|
12
|
+
'.txt': 'text/plain',
|
13
|
+
'.rtf': 'application/rtf',
|
14
|
+
'.odt': 'application/vnd.oasis.opendocument.text',
|
15
|
+
'.ods': 'application/vnd.oasis.opendocument.spreadsheet',
|
16
|
+
'.md': 'text/markdown',
|
17
|
+
'.epub': 'application/epub+zip',
|
18
|
+
'.ics': 'text/calendar',
|
19
|
+
|
20
|
+
# Images
|
21
|
+
'.jpg': 'image/jpeg',
|
22
|
+
'.jpeg': 'image/jpeg',
|
23
|
+
'.png': 'image/png',
|
24
|
+
'.gif': 'image/gif',
|
25
|
+
'.bmp': 'image/bmp',
|
26
|
+
'.webp': 'image/webp',
|
27
|
+
'.tiff': 'image/tiff',
|
28
|
+
'.svg': 'image/svg+xml',
|
29
|
+
'.heic': 'image/heic',
|
30
|
+
'.ico': 'image/vnd.microsoft.icon',
|
31
|
+
|
32
|
+
# Audio
|
33
|
+
'.mp3': 'audio/mpeg',
|
34
|
+
'.wav': 'audio/wav',
|
35
|
+
'.ogg': 'audio/ogg',
|
36
|
+
'.m4a': 'audio/mp4',
|
37
|
+
'.flac': 'audio/flac',
|
38
|
+
|
39
|
+
# Video
|
40
|
+
'.mp4': 'video/mp4',
|
41
|
+
'.mov': 'video/quicktime',
|
42
|
+
'.avi': 'video/x-msvideo',
|
43
|
+
'.wmv': 'video/x-ms-wmv',
|
44
|
+
'.webm': 'video/webm',
|
45
|
+
'.mkv': 'video/x-matroska',
|
46
|
+
|
47
|
+
# Archives
|
48
|
+
'.zip': 'application/zip',
|
49
|
+
'.tar': 'application/x-tar',
|
50
|
+
'.gz': 'application/gzip',
|
51
|
+
'.rar': 'application/vnd.rar',
|
52
|
+
'.7z': 'application/x-7z-compressed',
|
53
|
+
'.tar.gz': 'application/gzip', # Special case
|
54
|
+
'.tgz': 'application/gzip',
|
55
|
+
'.bz2': 'application/x-bzip2',
|
56
|
+
'.tar.bz2': 'application/x-bzip2',
|
57
|
+
|
58
|
+
# Code / Data
|
59
|
+
'.json': 'application/json',
|
60
|
+
'.xml': 'application/xml',
|
61
|
+
'.html': 'text/html',
|
62
|
+
'.htm': 'text/html',
|
63
|
+
'.css': 'text/css',
|
64
|
+
'.js': 'application/javascript',
|
65
|
+
'.py': 'text/x-python',
|
66
|
+
'.java': 'text/x-java-source',
|
67
|
+
'.c': 'text/x-c',
|
68
|
+
'.cpp': 'text/x-c++',
|
69
|
+
'.ts': 'application/typescript',
|
70
|
+
'.yml': 'application/x-yaml',
|
71
|
+
'.yaml': 'application/x-yaml',
|
72
|
+
'.sql': 'application/sql',
|
73
|
+
|
74
|
+
# Fonts
|
75
|
+
'.woff': 'font/woff',
|
76
|
+
'.woff2': 'font/woff2',
|
77
|
+
'.ttf': 'font/ttf',
|
78
|
+
'.otf': 'font/otf',
|
79
|
+
|
80
|
+
# Misc
|
81
|
+
'.apk': 'application/vnd.android.package-archive',
|
82
|
+
'.exe': 'application/vnd.microsoft.portable-executable',
|
83
|
+
'.dmg': 'application/x-apple-diskimage',
|
84
|
+
'.bat': 'application/x-bat',
|
85
|
+
'.sh': 'application/x-sh',
|
86
|
+
'.pdfa': 'application/pdf',
|
87
|
+
'.webmanifest': 'application/manifest+json',
|
88
|
+
}
|
89
|
+
|
90
|
+
def parse_extension(filename):
|
91
|
+
"""
|
92
|
+
Extract the extension from a filename, handling both single and double extensions,
|
93
|
+
and return the extension along with the filename without the extension.
|
94
|
+
|
95
|
+
Parameters:
|
96
|
+
filename (str): The name of the file for which to extract the extension.
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
tuple: A tuple containing the extracted extension (or an empty string if no valid
|
100
|
+
extension is found) and the filename without the extension.
|
101
|
+
"""
|
102
|
+
filename = filename.lower()
|
103
|
+
parts = filename.rsplit('.', 2) # Max 2 splits: base.name.ext1.ext2
|
104
|
+
if len(parts) == 3:
|
105
|
+
double_ext = f".{parts[-2]}.{parts[-1]}"
|
106
|
+
if double_ext in EXTENSION_TO_MIME:
|
107
|
+
return double_ext, '.'.join(parts[:-1])
|
108
|
+
ext = f".{parts[-1]}" if '.' in filename else ''
|
109
|
+
base_filename = parts[0] if len(parts) == 1 else '.'.join(parts[:-1])
|
110
|
+
return ext, base_filename
|
111
|
+
|
112
|
+
def guess_type(filename):
|
113
|
+
"""
|
114
|
+
Guess the MIME type of a file based on its extension.
|
115
|
+
|
116
|
+
This function attempts to determine the MIME type of a file by examining
|
117
|
+
its extension. It can handle both single and double extensions, such as
|
118
|
+
".tar.gz" or ".tar.bz2".
|
119
|
+
|
120
|
+
Parameters:
|
121
|
+
filename (str): The name of the file whose MIME type needs to be guessed.
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
str: The guessed MIME type if found in EXTENSION_TO_MIME mapping, or
|
125
|
+
'application/octet-stream' as a default fallback.
|
126
|
+
"""
|
127
|
+
ext, _ = parse_extension(filename)
|
128
|
+
return EXTENSION_TO_MIME.get(ext, 'application/octet-stream')
|