lark-oapi 2.0.0.dev2__py3-none-any.whl → 2.0.0.dev3__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.
- lark_oapi/channel/channel.py +137 -23
- lark_oapi/core/const.py +1 -1
- {lark_oapi-2.0.0.dev2.dist-info → lark_oapi-2.0.0.dev3.dist-info}/METADATA +1 -1
- {lark_oapi-2.0.0.dev2.dist-info → lark_oapi-2.0.0.dev3.dist-info}/RECORD +7 -7
- {lark_oapi-2.0.0.dev2.dist-info → lark_oapi-2.0.0.dev3.dist-info}/WHEEL +0 -0
- {lark_oapi-2.0.0.dev2.dist-info → lark_oapi-2.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {lark_oapi-2.0.0.dev2.dist-info → lark_oapi-2.0.0.dev3.dist-info}/top_level.txt +0 -0
lark_oapi/channel/channel.py
CHANGED
|
@@ -70,6 +70,7 @@ from .config import (
|
|
|
70
70
|
from .driver import LarkClientDriver
|
|
71
71
|
from .errors import FeishuChannelError, FeishuChannelErrorCode
|
|
72
72
|
from .identity import IdentityResolver, NameCache
|
|
73
|
+
from .normalize.comment import normalize_comment
|
|
73
74
|
from .normalize.dedup import Deduper, InMemoryDedupStore
|
|
74
75
|
from .normalize.pipeline import InboundPipeline, PipelineConfig, PipelineDeps
|
|
75
76
|
from .outbound.routing import infer_receive_id_type
|
|
@@ -95,6 +96,22 @@ EventHandler = Callable[..., Any]
|
|
|
95
96
|
Unsubscribe = Callable[[], None]
|
|
96
97
|
|
|
97
98
|
|
|
99
|
+
def _card_action_identity(action: Any) -> str:
|
|
100
|
+
"""Stable dedup fragment for a card action.
|
|
101
|
+
|
|
102
|
+
Node-SDK aligned: different buttons on the same card click to different
|
|
103
|
+
``(tag, value)`` pairs, and those must dedup-distinctly; a genuine WS
|
|
104
|
+
redelivery of the *same* click hashes identically and is suppressed.
|
|
105
|
+
"""
|
|
106
|
+
tag = getattr(action, "tag", "") or ""
|
|
107
|
+
value = getattr(action, "value", None)
|
|
108
|
+
try:
|
|
109
|
+
value_repr = json.dumps(value, sort_keys=True, ensure_ascii=False)
|
|
110
|
+
except (TypeError, ValueError):
|
|
111
|
+
value_repr = repr(value)
|
|
112
|
+
return f"{tag}:{value_repr}"
|
|
113
|
+
|
|
114
|
+
|
|
98
115
|
# ---------------------------------------------------------------------------
|
|
99
116
|
# FeishuChannel
|
|
100
117
|
# ---------------------------------------------------------------------------
|
|
@@ -771,6 +788,14 @@ class FeishuChannel:
|
|
|
771
788
|
b = getattr(b, register)(handler)
|
|
772
789
|
except Exception: # pragma: no cover
|
|
773
790
|
pass
|
|
791
|
+
# drive.notice.comment_add_v1 is a legacy p1-schema event; there is no
|
|
792
|
+
# typed processor for it in the generated SDK, so register a
|
|
793
|
+
# customized-event callback so the WS dispatcher has an entry and
|
|
794
|
+
# we can fan it out to ``channel.on("comment", ...)`` handlers
|
|
795
|
+
# instead of the SDK logging "processor not found".
|
|
796
|
+
b = b.register_p1_customized_event(
|
|
797
|
+
"drive.notice.comment_add_v1", self._on_p1_comment_add
|
|
798
|
+
)
|
|
774
799
|
return b.build()
|
|
775
800
|
|
|
776
801
|
# ------------------------------------------------------------------
|
|
@@ -804,6 +829,9 @@ class FeishuChannel:
|
|
|
804
829
|
def _on_p2_message_read(self, data: Any) -> None:
|
|
805
830
|
self.schedule(self._handle_message_read_event(data))
|
|
806
831
|
|
|
832
|
+
def _on_p1_comment_add(self, data: Any) -> None:
|
|
833
|
+
self.schedule(self._handle_comment_event(data))
|
|
834
|
+
|
|
807
835
|
# ------------------------------------------------------------------
|
|
808
836
|
# Async event handlers
|
|
809
837
|
# ------------------------------------------------------------------
|
|
@@ -866,25 +894,80 @@ class FeishuChannel:
|
|
|
866
894
|
operator_open_id = getattr(
|
|
867
895
|
getattr(event, "operator", None), "open_id", None
|
|
868
896
|
)
|
|
869
|
-
|
|
870
|
-
"
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
if hasattr(raw_value, "name")
|
|
880
|
-
else None,
|
|
881
|
-
),
|
|
882
|
-
raw=_coerce.obj_to_dict(data) or {},
|
|
897
|
+
payload = CardActionEvent(
|
|
898
|
+
message_id=message_id or "",
|
|
899
|
+
chat_id=chat_id or "",
|
|
900
|
+
operator=EventOperator(open_id=operator_open_id or ""),
|
|
901
|
+
action=CardActionPayload(
|
|
902
|
+
tag=tag or "",
|
|
903
|
+
value=raw_value,
|
|
904
|
+
name=getattr(raw_value, "name", None)
|
|
905
|
+
if hasattr(raw_value, "name")
|
|
906
|
+
else None,
|
|
883
907
|
),
|
|
908
|
+
raw=_coerce.obj_to_dict(data) or {},
|
|
909
|
+
)
|
|
910
|
+
# Route through safety.push_action (tier 2): dedup on a stable
|
|
911
|
+
# action identity (tag + value payload) so Feishu's at-least-once
|
|
912
|
+
# WS redelivery can't double-invoke the handler, and serialize by
|
|
913
|
+
# chat_id so two fast clicks in the same chat are processed in
|
|
914
|
+
# order. Node-SDK aligned.
|
|
915
|
+
await self._through_action_safety(
|
|
916
|
+
event_id=f"card:{payload.message_id}:{payload.operator.open_id}:"
|
|
917
|
+
f"{_card_action_identity(payload.action)}",
|
|
918
|
+
queue_scope=payload.chat_id or payload.message_id or "",
|
|
919
|
+
handler=lambda: self._invoke("cardAction", payload),
|
|
884
920
|
)
|
|
885
921
|
except Exception as e:
|
|
886
922
|
logger.exception("FeishuChannel cardAction dispatch failed: %s", e)
|
|
887
923
|
|
|
924
|
+
async def _through_action_safety(
|
|
925
|
+
self,
|
|
926
|
+
*,
|
|
927
|
+
event_id: str,
|
|
928
|
+
queue_scope: str,
|
|
929
|
+
handler: Callable[[], Any],
|
|
930
|
+
) -> None:
|
|
931
|
+
"""Run ``handler`` through the safety tier-2 gate (dedup + lock +
|
|
932
|
+
per-scope serial queue) when the pipeline exists; fall back to a
|
|
933
|
+
direct invocation when it hasn't been built yet (early events during
|
|
934
|
+
startup, unit tests that bypass ``connect``)."""
|
|
935
|
+
safety = self._safety
|
|
936
|
+
if safety is None:
|
|
937
|
+
result = handler()
|
|
938
|
+
if inspect.isawaitable(result):
|
|
939
|
+
await result
|
|
940
|
+
return
|
|
941
|
+
|
|
942
|
+
async def _run() -> None:
|
|
943
|
+
result = handler()
|
|
944
|
+
if inspect.isawaitable(result):
|
|
945
|
+
await result
|
|
946
|
+
|
|
947
|
+
await safety.push_action(event_id, queue_scope or event_id, _run)
|
|
948
|
+
|
|
949
|
+
async def _through_light_safety(
|
|
950
|
+
self,
|
|
951
|
+
*,
|
|
952
|
+
event_id: str,
|
|
953
|
+
handler: Callable[[], Any],
|
|
954
|
+
) -> None:
|
|
955
|
+
"""Tier-3 variant: dedup only (reaction add/remove). Same fallback
|
|
956
|
+
semantics as :meth:`_through_action_safety`."""
|
|
957
|
+
safety = self._safety
|
|
958
|
+
if safety is None:
|
|
959
|
+
result = handler()
|
|
960
|
+
if inspect.isawaitable(result):
|
|
961
|
+
await result
|
|
962
|
+
return
|
|
963
|
+
|
|
964
|
+
async def _run() -> None:
|
|
965
|
+
result = handler()
|
|
966
|
+
if inspect.isawaitable(result):
|
|
967
|
+
await result
|
|
968
|
+
|
|
969
|
+
await safety.push_light(event_id, _run)
|
|
970
|
+
|
|
888
971
|
async def _handle_reaction_event(self, data: Any, *, action: str) -> None:
|
|
889
972
|
cfg = self._config.inbound.reaction_notifications
|
|
890
973
|
if cfg == "off":
|
|
@@ -905,16 +988,25 @@ class FeishuChannel:
|
|
|
905
988
|
if message_id and message_id not in self._sent_messages:
|
|
906
989
|
return
|
|
907
990
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
991
|
+
direction = "added" if action == "create" else "removed"
|
|
992
|
+
payload = ReactionEvent(
|
|
993
|
+
message_id=message_id,
|
|
994
|
+
operator=EventOperator(open_id=operator_open_id or ""),
|
|
995
|
+
emoji_type=emoji_type or "",
|
|
996
|
+
action=direction,
|
|
997
|
+
action_time=action_time,
|
|
998
|
+
raw=_coerce.obj_to_dict(data) or {},
|
|
999
|
+
)
|
|
1000
|
+
# Tier 3: dedup only. Reactions are idempotent state changes so
|
|
1001
|
+
# lock / serial queue would add latency for no benefit, but
|
|
1002
|
+
# WS redelivery would double-invoke without this guard.
|
|
1003
|
+
# Node-SDK aligned (pushLight).
|
|
1004
|
+
await self._through_light_safety(
|
|
1005
|
+
event_id=(
|
|
1006
|
+
f"reaction:{message_id}:{operator_open_id or ''}:"
|
|
1007
|
+
f"{emoji_type or ''}:{direction}"
|
|
917
1008
|
),
|
|
1009
|
+
handler=lambda: self._invoke("reaction", payload),
|
|
918
1010
|
)
|
|
919
1011
|
except Exception as e:
|
|
920
1012
|
logger.exception("FeishuChannel reaction dispatch failed: %s", e)
|
|
@@ -961,6 +1053,28 @@ class FeishuChannel:
|
|
|
961
1053
|
except Exception as e:
|
|
962
1054
|
logger.exception("FeishuChannel messageRead dispatch failed: %s", e)
|
|
963
1055
|
|
|
1056
|
+
async def _handle_comment_event(self, data: Any) -> None:
|
|
1057
|
+
try:
|
|
1058
|
+
# ``CustomizedEvent.event`` is the raw inner event payload as a
|
|
1059
|
+
# plain dict, which is exactly what normalize_comment expects.
|
|
1060
|
+
raw_event = getattr(data, "event", None)
|
|
1061
|
+
normalized = normalize_comment(
|
|
1062
|
+
raw_event if raw_event is not None else data,
|
|
1063
|
+
bot_open_id=self._bot_open_id,
|
|
1064
|
+
)
|
|
1065
|
+
if normalized is None:
|
|
1066
|
+
return
|
|
1067
|
+
# Tier 2: dedup + lock + per-file_token serial queue. Multiple
|
|
1068
|
+
# comments on the same document are ordered; redeliveries of the
|
|
1069
|
+
# same comment event are dropped. Node-SDK aligned.
|
|
1070
|
+
await self._through_action_safety(
|
|
1071
|
+
event_id=f"comment:{normalized.file_token}:{normalized.comment_id}",
|
|
1072
|
+
queue_scope=normalized.file_token,
|
|
1073
|
+
handler=lambda: self._invoke("comment", normalized),
|
|
1074
|
+
)
|
|
1075
|
+
except Exception as e:
|
|
1076
|
+
logger.exception("FeishuChannel comment dispatch failed: %s", e)
|
|
1077
|
+
|
|
964
1078
|
def _notify_reconnecting(self) -> None:
|
|
965
1079
|
for h in list(self._handlers.get("reconnecting", [])):
|
|
966
1080
|
try:
|
lark_oapi/core/const.py
CHANGED
|
@@ -10003,7 +10003,7 @@ lark_oapi/channel/__init__.py,sha256=yZeVV8Zeyv53YufjfX6lHrDblyeMmhMS2I40sHRv0kM
|
|
|
10003
10003
|
lark_oapi/channel/_api_helpers.py,sha256=mv0pNmf1X2gG0gSCNfFXOFf_Y2Bl08FhOQCATc_vzqM,8874
|
|
10004
10004
|
lark_oapi/channel/_coerce.py,sha256=nHdt-y6iHuo_Fp7ZdLFkydG1pTMM-_wQJGJbOzcUiEI,7490
|
|
10005
10005
|
lark_oapi/channel/bot_identity.py,sha256=E2zMl1AYFCENi-HXtXH47WgZrWRpPMXiXyM38cvMdrw,5488
|
|
10006
|
-
lark_oapi/channel/channel.py,sha256=
|
|
10006
|
+
lark_oapi/channel/channel.py,sha256=O4EalY-txsf0g6PQ4-nSGhsVyR9X9NEE2ejfPxWWTsA,59104
|
|
10007
10007
|
lark_oapi/channel/config.py,sha256=PEYUDqG_r3sOqAF7acrA3pDgVPmk16ChWVB71l1xd_U,9877
|
|
10008
10008
|
lark_oapi/channel/driver.py,sha256=5Ij0WaQP3kEhUqZqUoREgfwv052118lyj2A6bCiIg7Y,11863
|
|
10009
10009
|
lark_oapi/channel/errors.py,sha256=MbQIH95dxt3m99ofaMp_3oM1RJVvAMxW1A3SKiavAfk,7179
|
|
@@ -10073,7 +10073,7 @@ lark_oapi/channel/safety/processing_lock.py,sha256=G4Kcm8-aRCfrIKD_6aXu-inWG28tB
|
|
|
10073
10073
|
lark_oapi/channel/safety/stale_detector.py,sha256=FKVBdYwRddpFynpRZxTyznxL3gkJVaoTXnn_iQ9uY04,691
|
|
10074
10074
|
lark_oapi/channel/safety/types.py,sha256=WaWhUleLe21-57HTFH4cOwJpZb2OiwKuDxWxPAE0OYA,2124
|
|
10075
10075
|
lark_oapi/core/__init__.py,sha256=z286YdAEQ4kRDGuKsZb0Osua5KMcg4yqBsLmKsdW1Hw,263
|
|
10076
|
-
lark_oapi/core/const.py,sha256=
|
|
10076
|
+
lark_oapi/core/const.py,sha256=1evFaIZKXM40ynmes6XUT6_G89uhiMP-wGcNH2Xjp5U,608
|
|
10077
10077
|
lark_oapi/core/construct.py,sha256=CKsC-5bza7R01PDE2pWbyThlXi1oLAtjR0YD3Lex_sM,1980
|
|
10078
10078
|
lark_oapi/core/enum.py,sha256=ehxuylOjD3btFHTRtpA--QvEcvUlWimuqXIqglcg4Kw,471
|
|
10079
10079
|
lark_oapi/core/env_var.py,sha256=6xonyZzC_6Jyaotazj-ip0_yzB4wpZ4xArXIdPdPTck,169
|
|
@@ -10174,8 +10174,8 @@ lark_oapi/ws/pb/google/protobuf/pyext/cpp_message.py,sha256=GCWPkLCXd0QxY3yzNN-G
|
|
|
10174
10174
|
lark_oapi/ws/pb/google/protobuf/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10175
10175
|
lark_oapi/ws/pb/google/protobuf/util/json_format_pb2.py,sha256=4QRyr6gfGO_e2MNpt3z3MT1ZHfoR5SXRpmIs4aWrv6g,6375
|
|
10176
10176
|
lark_oapi/ws/pb/google/protobuf/util/json_format_proto3_pb2.py,sha256=uA8FCLxKYYUrFOlpjSlyWQWbtpodzBMZrIxM25dyJhM,14610
|
|
10177
|
-
lark_oapi-2.0.0.
|
|
10178
|
-
lark_oapi-2.0.0.
|
|
10179
|
-
lark_oapi-2.0.0.
|
|
10180
|
-
lark_oapi-2.0.0.
|
|
10181
|
-
lark_oapi-2.0.0.
|
|
10177
|
+
lark_oapi-2.0.0.dev3.dist-info/licenses/LICENSE,sha256=N2ZgITrKZ1yufqVdfmr4ixMPp4qJ_ly-1_YeCs3w-Uk,1083
|
|
10178
|
+
lark_oapi-2.0.0.dev3.dist-info/METADATA,sha256=pZ6mifvUtzUBIGJ3dTggW4zawsx5MCHUTbKNyYU8IZ0,4743
|
|
10179
|
+
lark_oapi-2.0.0.dev3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10180
|
+
lark_oapi-2.0.0.dev3.dist-info/top_level.txt,sha256=sDloiIa3R4quQp19_e1mf206wAxH0Wg1bvNnKzV8QJg,10
|
|
10181
|
+
lark_oapi-2.0.0.dev3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|