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.
@@ -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
- await self._invoke(
870
- "cardAction",
871
- CardActionEvent(
872
- message_id=message_id or "",
873
- chat_id=chat_id or "",
874
- operator=EventOperator(open_id=operator_open_id or ""),
875
- action=CardActionPayload(
876
- tag=tag or "",
877
- value=raw_value,
878
- name=getattr(raw_value, "name", None)
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
- await self._invoke(
909
- "reaction",
910
- ReactionEvent(
911
- message_id=message_id,
912
- operator=EventOperator(open_id=operator_open_id or ""),
913
- emoji_type=emoji_type or "",
914
- action="added" if action == "create" else "removed",
915
- action_time=action_time,
916
- raw=_coerce.obj_to_dict(data) or {},
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
@@ -1,6 +1,6 @@
1
1
  # Info
2
2
  PROJECT = "oapi-sdk-python"
3
- VERSION = "2.0.0.dev2"
3
+ VERSION = "2.0.0.dev3"
4
4
 
5
5
  # Domain
6
6
  FEISHU_DOMAIN = "https://open.feishu.cn"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lark-oapi
3
- Version: 2.0.0.dev2
3
+ Version: 2.0.0.dev3
4
4
  Summary: Lark OpenAPI SDK for Python
5
5
  Home-page: https://github.com/larksuite/oapi-sdk-python
6
6
  Author: Wenbo Mao
@@ -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=KNvvf6-y3eTUr1Hn08lNbPnG_8dMx_2UFP6ofi_BHnI,54225
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=Nzn_gukWAtdLCrjRSojYlIlYsdALTOBHU102XRWqUuA,608
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.dev2.dist-info/licenses/LICENSE,sha256=N2ZgITrKZ1yufqVdfmr4ixMPp4qJ_ly-1_YeCs3w-Uk,1083
10178
- lark_oapi-2.0.0.dev2.dist-info/METADATA,sha256=Rslg-_LRvQwuubEyhu17sS60bTx-pSEfDiu_tlVslxo,4743
10179
- lark_oapi-2.0.0.dev2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10180
- lark_oapi-2.0.0.dev2.dist-info/top_level.txt,sha256=sDloiIa3R4quQp19_e1mf206wAxH0Wg1bvNnKzV8QJg,10
10181
- lark_oapi-2.0.0.dev2.dist-info/RECORD,,
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,,