kimi-agent-module-api 1.0.0__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.
@@ -0,0 +1,140 @@
1
+ """Normalized core Discord events published on the module event bus.
2
+
3
+ Each dataclass carries what a subscriber cannot re-fetch after the fact:
4
+ deleted or pre-edit content, removed members' roles, role and timeout deltas,
5
+ audit-log changes. Anything still live is fetched through a declared Discord
6
+ action instead. Payloads never contain Discord SDK objects.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import Mapping
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Literal
14
+
15
+ from kimi_agent_module_api.contracts import (
16
+ CORE_TOPIC_PREFIX,
17
+ AttachmentSnapshot,
18
+ InviteSnapshot,
19
+ MemberSnapshot,
20
+ MessageRef,
21
+ MessageSnapshot,
22
+ )
23
+
24
+ TOPIC_MESSAGE = f"{CORE_TOPIC_PREFIX}.message"
25
+ TOPIC_MESSAGE_EDIT = f"{CORE_TOPIC_PREFIX}.message_edit"
26
+ TOPIC_MESSAGE_DELETE = f"{CORE_TOPIC_PREFIX}.message_delete"
27
+ TOPIC_MESSAGE_BULK_DELETE = f"{CORE_TOPIC_PREFIX}.message_bulk_delete"
28
+ TOPIC_INVITE_CREATE = f"{CORE_TOPIC_PREFIX}.invite_create"
29
+ TOPIC_INVITE_DELETE = f"{CORE_TOPIC_PREFIX}.invite_delete"
30
+ TOPIC_MEMBER_JOIN = f"{CORE_TOPIC_PREFIX}.member_join"
31
+ TOPIC_MEMBER_REMOVE = f"{CORE_TOPIC_PREFIX}.member_remove"
32
+ TOPIC_MEMBER_UPDATE = f"{CORE_TOPIC_PREFIX}.member_update"
33
+ TOPIC_AUDIT_LOG_ENTRY = f"{CORE_TOPIC_PREFIX}.audit_log_entry"
34
+
35
+ CORE_TOPICS: frozenset[str] = frozenset(
36
+ {
37
+ TOPIC_MESSAGE,
38
+ TOPIC_MESSAGE_EDIT,
39
+ TOPIC_MESSAGE_DELETE,
40
+ TOPIC_MESSAGE_BULK_DELETE,
41
+ TOPIC_INVITE_CREATE,
42
+ TOPIC_INVITE_DELETE,
43
+ TOPIC_MEMBER_JOIN,
44
+ TOPIC_MEMBER_REMOVE,
45
+ TOPIC_MEMBER_UPDATE,
46
+ TOPIC_AUDIT_LOG_ENTRY,
47
+ }
48
+ )
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class MessageEvent:
53
+ message: MessageSnapshot
54
+ author_is_bot: bool
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class MessageEditEvent:
59
+ ref: MessageRef
60
+ author_id: int | None
61
+ before_content: str | None
62
+ after_content: str | None
63
+ edited_at: float
64
+
65
+
66
+ @dataclass(frozen=True, slots=True)
67
+ class MessageDeleteEvent:
68
+ ref: MessageRef
69
+ author_id: int | None
70
+ cached_content: str | None
71
+ cached_attachments: tuple[AttachmentSnapshot, ...]
72
+
73
+
74
+ @dataclass(frozen=True, slots=True)
75
+ class MessageBulkDeleteEvent:
76
+ refs: tuple[MessageRef, ...]
77
+
78
+
79
+ @dataclass(frozen=True, slots=True)
80
+ class InviteCreateEvent:
81
+ invite: InviteSnapshot
82
+
83
+
84
+ @dataclass(frozen=True, slots=True)
85
+ class InviteDeleteEvent:
86
+ invite: InviteSnapshot
87
+
88
+
89
+ @dataclass(frozen=True, slots=True)
90
+ class MemberJoinEvent:
91
+ member: MemberSnapshot
92
+ account_created_at: float
93
+
94
+
95
+ @dataclass(frozen=True, slots=True)
96
+ class MemberRemoveEvent:
97
+ guild_id: int
98
+ user_id: int
99
+ roles_at_removal: tuple[int, ...]
100
+ display_name: str = ""
101
+ is_bot: bool = False
102
+
103
+
104
+ @dataclass(frozen=True, slots=True)
105
+ class MemberUpdateEvent:
106
+ guild_id: int
107
+ user_id: int
108
+ roles_added: tuple[int, ...]
109
+ roles_removed: tuple[int, ...]
110
+ timed_out_until_before: float | None
111
+ timed_out_until_after: float | None
112
+ nickname_before: str | None
113
+ nickname_after: str | None
114
+ display_name: str = ""
115
+ is_bot: bool = False
116
+ # Names for every role id in roles_added / roles_removed.
117
+ role_names: Mapping[int, str] = field(default_factory=dict)
118
+
119
+
120
+ type AuditAction = Literal[
121
+ "ban", "unban", "kick", "timeout", "timeout_cleared", "member_update", "other"
122
+ ]
123
+
124
+
125
+ @dataclass(frozen=True, slots=True)
126
+ class AuditLogEntryEvent:
127
+ guild_id: int
128
+ entry_id: int
129
+ action: AuditAction
130
+ raw_action: str
131
+ actor_id: int | None
132
+ target_id: int | None
133
+ reason: str | None
134
+ changes: tuple[tuple[str, Any, Any], ...]
135
+ created_at: float
136
+ target_display_name: str | None = None
137
+ # Timeout expiry (unix seconds) for `timeout` actions.
138
+ until: float | None = None
139
+ # True when this bot performed the action (e.g. through a module command).
140
+ actor_is_self: bool = False
@@ -0,0 +1,34 @@
1
+ """Pure image helpers that do not depend on a host runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ SUPPORTED_IMAGE_MEDIA_TYPES = frozenset({"image/png", "image/jpeg", "image/gif", "image/webp"})
6
+ IMAGE_FILENAME_SUFFIXES = (".png", ".jpg", ".jpeg", ".gif", ".webp")
7
+
8
+
9
+ def looks_like_image_attachment(filename: str | None, content_type: str | None) -> bool:
10
+ """Use declared metadata as a cheap candidate filter before byte sniffing."""
11
+ declared = str(content_type or "").strip().lower()
12
+ name = str(filename or "").strip().lower()
13
+ return declared.startswith("image/") or name.endswith(IMAGE_FILENAME_SUFFIXES)
14
+
15
+
16
+ def sniff_image_media_type(payload: bytes) -> str | None:
17
+ """Return a supported image media type from magic bytes, or None."""
18
+ if payload.startswith(b"\x89PNG\r\n\x1a\n"):
19
+ return "image/png"
20
+ if payload.startswith(b"\xff\xd8\xff"):
21
+ return "image/jpeg"
22
+ if payload.startswith((b"GIF87a", b"GIF89a")):
23
+ return "image/gif"
24
+ if len(payload) >= 12 and payload[:4] == b"RIFF" and payload[8:12] == b"WEBP":
25
+ return "image/webp"
26
+ return None
27
+
28
+
29
+ __all__ = [
30
+ "IMAGE_FILENAME_SUFFIXES",
31
+ "SUPPORTED_IMAGE_MEDIA_TYPES",
32
+ "looks_like_image_attachment",
33
+ "sniff_image_media_type",
34
+ ]
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,33 @@
1
+ """Public settings declarations owned by the module API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from pydantic_settings import BaseSettings
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ModuleSetting:
12
+ """Presentation metadata for one explicitly exposed settings field."""
13
+
14
+ field: str
15
+ label: str
16
+ help: str = ""
17
+ choices: tuple[str, ...] = ()
18
+ minimum: int | float | None = None
19
+ multiline: bool = False
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class ModuleSettingsDefinition:
24
+ """A module's settings model and its operator-editable subset."""
25
+
26
+ name: str
27
+ label: str
28
+ model: type[BaseSettings]
29
+ exposed: tuple[ModuleSetting, ...]
30
+ environment_only: frozenset[str] = frozenset()
31
+
32
+
33
+ __all__ = ["ModuleSetting", "ModuleSettingsDefinition"]