"}},
77 | sort=[{"field": "target_id", "direction": SortOrder.DESC}],
78 | options={
79 | "limit": 3,
80 | "next": query_targets_1["next"],
81 | },
82 | )
83 | assert query_targets_2.is_ok()
84 | assert "targets" in query_targets_2
85 | assert "next" in query_targets_2
86 | assert len(query_targets_2["targets"]) == 3
87 |
88 | target_deleted = await segment.remove_targets(target_ids=target_ids)
89 | assert target_deleted.is_ok()
90 |
91 | deleted = await segment.delete()
92 | assert deleted.is_ok()
93 |
94 | async def test_query_segments(self, client: StreamChatAsync):
95 | created = await client.create_segment(segment_type=SegmentType.USER)
96 | assert created.is_ok()
97 | assert "segment" in created
98 | assert "id" in created["segment"]
99 | assert "name" in created["segment"]
100 | segment_id = created["segment"]["id"]
101 |
102 | target_ids = [str(uuid.uuid4()) for _ in range(10)]
103 | target_added = await client.add_segment_targets(
104 | segment_id=segment_id, target_ids=target_ids
105 | )
106 | assert target_added.is_ok()
107 |
108 | query_segments = await client.query_segments(
109 | filter_conditions={"id": {"$eq": segment_id}},
110 | sort=[{"field": "created_at", "direction": SortOrder.DESC}],
111 | )
112 | assert query_segments.is_ok()
113 | assert "segments" in query_segments
114 | assert len(query_segments["segments"]) == 1
115 |
116 | target_deleted = await client.remove_segment_targets(
117 | segment_id=segment_id, target_ids=target_ids
118 | )
119 | assert target_deleted.is_ok()
120 |
121 | deleted = await client.delete_segment(segment_id=segment_id)
122 | assert deleted.is_ok()
123 |
--------------------------------------------------------------------------------
/stream_chat/tests/async_chat/conftest.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 | import uuid
4 | from typing import Dict, List
5 |
6 | import pytest
7 |
8 | from stream_chat.async_chat import StreamChatAsync
9 |
10 |
11 | def pytest_runtest_makereport(item, call):
12 | if "incremental" in item.keywords:
13 | if call.excinfo is not None:
14 | parent = item.parent
15 | parent._previousfailed = item
16 |
17 |
18 | def pytest_runtest_setup(item):
19 | if "incremental" in item.keywords:
20 | previousfailed = getattr(item.parent, "_previousfailed", None)
21 | if previousfailed is not None:
22 | pytest.xfail(f"previous test failed ({previousfailed.name})")
23 |
24 |
25 | def pytest_configure(config):
26 | config.addinivalue_line("markers", "incremental: mark test incremental")
27 |
28 |
29 | @pytest.fixture(scope="module")
30 | def event_loop():
31 | loop = asyncio.get_event_loop_policy().new_event_loop()
32 | yield loop
33 | loop.close()
34 |
35 |
36 | @pytest.fixture(scope="function", autouse=True)
37 | @pytest.mark.asyncio
38 | async def client():
39 | base_url = os.environ.get("STREAM_HOST")
40 | options = {"base_url": base_url} if base_url else {}
41 | async with StreamChatAsync(
42 | api_key=os.environ["STREAM_KEY"],
43 | api_secret=os.environ["STREAM_SECRET"],
44 | timeout=10,
45 | **options,
46 | ) as stream_client:
47 | yield stream_client
48 |
49 |
50 | @pytest.fixture(scope="function")
51 | async def random_user(client: StreamChatAsync):
52 | user = {"id": str(uuid.uuid4())}
53 | response = await client.upsert_user(user)
54 | assert "users" in response
55 | assert user["id"] in response["users"]
56 | yield user
57 | await hard_delete_users(client, [user["id"]])
58 |
59 |
60 | @pytest.fixture(scope="function")
61 | async def server_user(client: StreamChatAsync):
62 | user = {"id": str(uuid.uuid4())}
63 | response = await client.upsert_user(user)
64 | assert "users" in response
65 | assert user["id"] in response["users"]
66 | yield user
67 | await hard_delete_users(client, [user["id"]])
68 |
69 |
70 | @pytest.fixture(scope="function")
71 | async def random_users(client: StreamChatAsync):
72 | user1 = {"id": str(uuid.uuid4())}
73 | user2 = {"id": str(uuid.uuid4())}
74 | user3 = {"id": str(uuid.uuid4())}
75 | await client.upsert_users([user1, user2, user3])
76 | yield [user1, user2, user3]
77 | await hard_delete_users(client, [user1["id"], user2["id"], user3["id"]])
78 |
79 |
80 | @pytest.fixture(scope="function")
81 | async def channel(client: StreamChatAsync, random_user: Dict):
82 | channel = client.channel(
83 | "messaging", str(uuid.uuid4()), {"test": True, "language": "python"}
84 | )
85 | await channel.create(random_user["id"])
86 | yield channel
87 |
88 | try:
89 | await client.delete_channels([channel.cid], hard_delete=True)
90 | except Exception:
91 | pass
92 |
93 |
94 | @pytest.fixture(scope="function")
95 | async def command(client: StreamChatAsync):
96 | response = await client.create_command(
97 | dict(name=str(uuid.uuid4()), description="My command")
98 | )
99 |
100 | yield response["command"]
101 |
102 | await client.delete_command(response["command"]["name"])
103 |
104 |
105 | @pytest.fixture(scope="function")
106 | @pytest.mark.asyncio
107 | async def fellowship_of_the_ring(client: StreamChatAsync):
108 | members: List[Dict] = [
109 | {"id": "frodo-baggins", "name": "Frodo Baggins", "race": "Hobbit", "age": 50},
110 | {"id": "sam-gamgee", "name": "Samwise Gamgee", "race": "Hobbit", "age": 38},
111 | {"id": "gandalf", "name": "Gandalf the Grey", "race": "Istari"},
112 | {"id": "legolas", "name": "Legolas", "race": "Elf", "age": 500},
113 | {"id": "gimli", "name": "Gimli", "race": "Dwarf", "age": 139},
114 | {"id": "aragorn", "name": "Aragorn", "race": "Man", "age": 87},
115 | {"id": "boromir", "name": "Boromir", "race": "Man", "age": 40},
116 | {
117 | "id": "meriadoc-brandybuck",
118 | "name": "Meriadoc Brandybuck",
119 | "race": "Hobbit",
120 | "age": 36,
121 | },
122 | {"id": "peregrin-took", "name": "Peregrin Took", "race": "Hobbit", "age": 28},
123 | ]
124 | await client.upsert_users(members)
125 | channel = client.channel(
126 | "team", "fellowship-of-the-ring", {"members": [m["id"] for m in members]}
127 | )
128 | await channel.create("gandalf")
129 | yield
130 | try:
131 | await channel.delete(hard=True)
132 | await hard_delete_users(client, [m["id"] for m in members])
133 | except Exception:
134 | pass
135 |
136 |
137 | async def hard_delete_users(client: StreamChatAsync, user_ids: List[str]):
138 | try:
139 | await client.delete_users(
140 | user_ids, "hard", conversations="hard", messages="hard"
141 | )
142 | except Exception:
143 | pass
144 |
--------------------------------------------------------------------------------
/stream_chat/tests/test_draft.py:
--------------------------------------------------------------------------------
1 | import uuid
2 | from typing import Dict
3 |
4 | import pytest
5 |
6 | from stream_chat import StreamChat
7 | from stream_chat.channel import Channel
8 | from stream_chat.types.base import SortOrder
9 |
10 |
11 | @pytest.mark.incremental
12 | class TestDraft:
13 | def test_create_draft(self, channel: Channel, random_user: Dict):
14 | draft_message = {"text": "This is a draft message"}
15 | response = channel.create_draft(draft_message, random_user["id"])
16 |
17 | assert "draft" in response
18 | assert response["draft"]["message"]["text"] == "This is a draft message"
19 | assert response["draft"]["channel_cid"] == channel.cid
20 |
21 | def test_get_draft(self, channel: Channel, random_user: Dict):
22 | # First create a draft
23 | draft_message = {"text": "This is a draft to retrieve"}
24 | channel.create_draft(draft_message, random_user["id"])
25 |
26 | # Then get the draft
27 | response = channel.get_draft(random_user["id"])
28 |
29 | assert "draft" in response
30 | assert response["draft"]["message"]["text"] == "This is a draft to retrieve"
31 | assert response["draft"]["channel_cid"] == channel.cid
32 |
33 | def test_delete_draft(self, channel: Channel, random_user: Dict):
34 | # First create a draft
35 | draft_message = {"text": "This is a draft to delete"}
36 | channel.create_draft(draft_message, random_user["id"])
37 |
38 | # Then delete the draft
39 | channel.delete_draft(random_user["id"])
40 |
41 | # Verify it's deleted by trying to get it
42 | try:
43 | channel.get_draft(random_user["id"])
44 | raise AssertionError("Draft should be deleted")
45 | except Exception:
46 | # Expected behavior, draft should not be found
47 | pass
48 |
49 | def test_thread_draft(self, channel: Channel, random_user: Dict):
50 | # First create a parent message
51 | msg = channel.send_message({"text": "Parent message"}, random_user["id"])
52 | parent_id = msg["message"]["id"]
53 |
54 | # Create a draft reply
55 | draft_reply = {"text": "This is a draft reply", "parent_id": parent_id}
56 | response = channel.create_draft(draft_reply, random_user["id"])
57 |
58 | assert "draft" in response
59 | assert response["draft"]["message"]["text"] == "This is a draft reply"
60 | assert response["draft"]["parent_id"] == parent_id
61 |
62 | # Get the draft reply
63 | response = channel.get_draft(random_user["id"], parent_id=parent_id)
64 |
65 | assert "draft" in response
66 | assert response["draft"]["message"]["text"] == "This is a draft reply"
67 | assert response["draft"]["parent_id"] == parent_id
68 |
69 | # Delete the draft reply
70 | channel.delete_draft(random_user["id"], parent_id=parent_id)
71 |
72 | # Verify it's deleted
73 | try:
74 | channel.get_draft(random_user["id"], parent_id=parent_id)
75 | raise AssertionError("Thread draft should be deleted")
76 | except Exception:
77 | # Expected behavior
78 | pass
79 |
80 | def test_query_drafts(
81 | self, client: StreamChat, channel: Channel, random_user: Dict
82 | ):
83 | # Create multiple drafts in different channels
84 | draft1 = {"text": "Draft in channel 1"}
85 | channel.create_draft(draft1, random_user["id"])
86 |
87 | # Create another channel with a draft
88 | channel2 = client.channel("messaging", str(uuid.uuid4()))
89 | channel2.create(random_user["id"])
90 |
91 | draft2 = {"text": "Draft in channel 2"}
92 | channel2.create_draft(draft2, random_user["id"])
93 |
94 | # Query all drafts for the user
95 | response = client.query_drafts(random_user["id"])
96 |
97 | assert "drafts" in response
98 | assert len(response["drafts"]) == 2
99 |
100 | # Query drafts for a specific channel
101 | response = client.query_drafts(
102 | random_user["id"], filter={"channel_cid": channel2.cid}
103 | )
104 |
105 | assert "drafts" in response
106 | assert len(response["drafts"]) == 1
107 | draft = response["drafts"][0]
108 | assert draft["channel_cid"] == channel2.cid
109 | assert draft["message"]["text"] == "Draft in channel 2"
110 |
111 | # Query drafts with sort
112 | response = client.query_drafts(
113 | random_user["id"],
114 | sort=[{"field": "created_at", "direction": SortOrder.ASC}],
115 | )
116 |
117 | assert "drafts" in response
118 | assert len(response["drafts"]) == 2
119 | assert response["drafts"][0]["channel_cid"] == channel.cid
120 | assert response["drafts"][1]["channel_cid"] == channel2.cid
121 |
122 | # Query drafts with pagination
123 | response = client.query_drafts(
124 | random_user["id"],
125 | options={"limit": 1},
126 | )
127 |
128 | assert "drafts" in response
129 | assert len(response["drafts"]) == 1
130 | assert response["drafts"][0]["channel_cid"] == channel2.cid
131 |
132 | assert response["next"] is not None
133 |
134 | # Query drafts with pagination
135 | response = client.query_drafts(
136 | random_user["id"],
137 | options={"limit": 1, "next": response["next"]},
138 | )
139 |
140 | assert "drafts" in response
141 | assert len(response["drafts"]) == 1
142 | assert response["drafts"][0]["channel_cid"] == channel.cid
143 |
144 | # cleanup
145 | try:
146 | channel2.delete()
147 | except Exception:
148 | pass
149 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Official Python SDK for [Stream Chat](https://getstream.io/chat/)
2 |
3 | [](https://github.com/GetStream/stream-chat-python/actions) [](http://badge.fury.io/py/stream-chat)  [](http://mypy-lang.org/)
4 |
5 |
6 |
7 |
8 |
9 | Official Python API client for Stream Chat, a service for building chat applications.
10 |
11 | Explore the docs »
12 |
13 |
14 | Code Samples
15 | ·
16 | Report Bug
17 | ·
18 | Request Feature
19 |
20 |
21 | ---
22 | > ### :bulb: Major update in v4.0 <
23 | > The returned response objects are instances of [`StreamResponse`](https://github.com/GetStream/stream-chat-python/blob/master/stream_chat/types/stream_response.py) class. It inherits from `dict`, so it's fully backward compatible. Additionally, it provides other benefits such as rate limit information (`resp.rate_limit()`), response headers (`resp.headers()`) or status code (`resp.status_code()`).
24 | ---
25 |
26 | ## 📝 About Stream
27 |
28 | You can sign up for a Stream account at our [Get Started](https://getstream.io/chat/get_started/) page.
29 |
30 | You can use this library to access chat API endpoints server-side.
31 |
32 | For the client-side integrations (web and mobile) have a look at the JavaScript, iOS and Android SDK libraries ([docs](https://getstream.io/chat/)).
33 |
34 | ## ⚙️ Installation
35 |
36 | ```shell
37 | $ pip install stream-chat
38 | ```
39 |
40 | ## ✨ Getting started
41 |
42 | > :bulb: The library is almost 100% typed. Feel free to enable [mypy](https://github.com/python/mypy) for our library. We will introduce more improvements in the future in this area.
43 |
44 | ```python
45 | from stream_chat import StreamChat
46 |
47 | chat = StreamChat(api_key="STREAM_KEY", api_secret="STREAM_SECRET")
48 |
49 | # add a user
50 | chat.upsert_user({"id": "chuck", "name": "Chuck"})
51 |
52 | # create a channel about kung-fu
53 | channel = chat.channel("messaging", "kung-fu")
54 | channel.create("chuck")
55 |
56 | # add a first message to the channel
57 | channel.send_message({"text": "AMA about kung-fu"}, "chuck")
58 |
59 | # we also expose some response metadata through a custom dictionary
60 | resp = chat.deactivate_user("bruce_lee")
61 |
62 | print(type(resp)) #
63 | print(resp["user"]["id"]) # bruce_lee
64 |
65 | rate_limit = resp.rate_limit()
66 | print(f"{rate_limit.limit} / {rate_limit.remaining} / {rate_limit.reset}") # 60 / 59 /2022-01-06 12:35:00+00:00
67 |
68 | headers = resp.headers()
69 | print(headers) # { 'Content-Encoding': 'gzip', 'Content-Length': '33', ... }
70 |
71 | status_code = resp.status_code()
72 | print(status_code) # 200
73 |
74 | ```
75 |
76 | ### Async
77 |
78 | ```python
79 | import asyncio
80 | from stream_chat import StreamChatAsync
81 |
82 |
83 | async def main():
84 | async with StreamChatAsync(api_key="STREAM_KEY", api_secret="STREAM_SECRET") as chat:
85 | # add a user
86 | await chat.upsert_user({"id": "chuck", "name": "Chuck"})
87 |
88 | # create a channel about kung-fu
89 | channel = chat.channel("messaging", "kung-fu")
90 | await channel.create("chuck")
91 |
92 | # add a first message to the channel
93 | await channel.send_message({"text": "AMA about kung-fu"}, "chuck")
94 |
95 | # we also expose some response metadata through a custom dictionary
96 | resp = await chat.deactivate_user("bruce_lee")
97 | print(type(resp)) #
98 | print(resp["user"]["id"]) # bruce_lee
99 |
100 | rate_limit = resp.rate_limit()
101 | print(f"{rate_limit.limit} / {rate_limit.remaining} / {rate_limit.reset}") # 60 / 59 / 2022-01-06 12:35:00+00:00
102 |
103 | headers = resp.headers()
104 | print(headers) # { 'Content-Encoding': 'gzip', 'Content-Length': '33', ... }
105 |
106 | status_code = resp.status_code()
107 | print(status_code) # 200
108 |
109 |
110 | if __name__ == '__main__':
111 | loop = asyncio.get_event_loop()
112 | try:
113 | loop.run_until_complete(main())
114 | finally:
115 | loop.run_until_complete(loop.shutdown_asyncgens())
116 | loop.close()
117 |
118 | ```
119 |
120 | ## ✍️ Contributing
121 |
122 | We welcome code changes that improve this library or fix a problem, please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github. We are very happy to merge your code in the official repository. Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first. See our [license file](./LICENSE) for more details.
123 |
124 | Head over to [CONTRIBUTING.md](./CONTRIBUTING.md) for some development tips.
125 |
126 | ## 🧑💻 We are hiring!
127 |
128 | We've recently closed a [$38 million Series B funding round](https://techcrunch.com/2021/03/04/stream-raises-38m-as-its-chat-and-activity-feed-apis-power-communications-for-1b-users/) and we keep actively growing.
129 | Our APIs are used by more than a billion end-users, and you'll have a chance to make a huge impact on the product within a team of the strongest engineers all over the world.
130 |
131 | Check out our current openings and apply via [Stream's website](https://getstream.io/team/#jobs).
132 |
--------------------------------------------------------------------------------
/stream_chat/tests/async_chat/test_draft.py:
--------------------------------------------------------------------------------
1 | import uuid
2 | from typing import Dict
3 |
4 | import pytest
5 |
6 | from stream_chat.async_chat.channel import Channel
7 | from stream_chat.async_chat.client import StreamChatAsync
8 | from stream_chat.types.base import SortOrder
9 |
10 |
11 | @pytest.mark.incremental
12 | class TestDraft:
13 | async def test_create_draft(self, channel: Channel, random_user: Dict):
14 | draft_message = {"text": "This is a draft message"}
15 | response = await channel.create_draft(draft_message, random_user["id"])
16 |
17 | assert "draft" in response
18 | assert response["draft"]["message"]["text"] == "This is a draft message"
19 | assert response["draft"]["channel_cid"] == channel.cid
20 |
21 | async def test_get_draft(self, channel: Channel, random_user: Dict):
22 | # First create a draft
23 | draft_message = {"text": "This is a draft to retrieve"}
24 | await channel.create_draft(draft_message, random_user["id"])
25 |
26 | # Then get the draft
27 | response = await channel.get_draft(random_user["id"])
28 |
29 | assert "draft" in response
30 | assert response["draft"]["message"]["text"] == "This is a draft to retrieve"
31 | assert response["draft"]["channel_cid"] == channel.cid
32 |
33 | async def test_delete_draft(self, channel: Channel, random_user: Dict):
34 | # First create a draft
35 | draft_message = {"text": "This is a draft to delete"}
36 | await channel.create_draft(draft_message, random_user["id"])
37 |
38 | # Then delete the draft
39 | await channel.delete_draft(random_user["id"])
40 |
41 | # Verify it's deleted by trying to get it
42 | try:
43 | await channel.get_draft(random_user["id"])
44 | raise AssertionError("Draft should be deleted")
45 | except Exception:
46 | # Expected behavior, draft should not be found
47 | pass
48 |
49 | async def test_thread_draft(self, channel: Channel, random_user: Dict):
50 | # First create a parent message
51 | msg = await channel.send_message({"text": "Parent message"}, random_user["id"])
52 | parent_id = msg["message"]["id"]
53 |
54 | # Create a draft reply
55 | draft_reply = {"text": "This is a draft reply", "parent_id": parent_id}
56 | response = await channel.create_draft(draft_reply, random_user["id"])
57 |
58 | assert "draft" in response
59 | assert response["draft"]["message"]["text"] == "This is a draft reply"
60 | assert response["draft"]["parent_id"] == parent_id
61 |
62 | # Get the draft reply
63 | response = await channel.get_draft(random_user["id"], parent_id=parent_id)
64 |
65 | assert "draft" in response
66 | assert response["draft"]["message"]["text"] == "This is a draft reply"
67 | assert response["draft"]["parent_id"] == parent_id
68 |
69 | # Delete the draft reply
70 | await channel.delete_draft(random_user["id"], parent_id=parent_id)
71 |
72 | # Verify it's deleted
73 | try:
74 | await channel.get_draft(random_user["id"], parent_id=parent_id)
75 | raise AssertionError("Thread draft should be deleted")
76 | except Exception:
77 | # Expected behavior
78 | pass
79 |
80 | async def test_query_drafts(
81 | self, client: StreamChatAsync, channel: Channel, random_user: Dict
82 | ):
83 | # Create multiple drafts in different channels
84 | draft1 = {"text": "Draft in channel 1"}
85 | await channel.create_draft(draft1, random_user["id"])
86 |
87 | # Create another channel with a draft
88 | channel2 = client.channel("messaging", str(uuid.uuid4()))
89 | await channel2.create(random_user["id"])
90 |
91 | draft2 = {"text": "Draft in channel 2"}
92 | await channel2.create_draft(draft2, random_user["id"])
93 |
94 | # Query all drafts for the user
95 | response = await client.query_drafts(random_user["id"])
96 |
97 | assert "drafts" in response
98 | assert len(response["drafts"]) == 2
99 |
100 | # Query drafts for a specific channel
101 | response = await client.query_drafts(
102 | random_user["id"], filter={"channel_cid": channel2.cid}
103 | )
104 |
105 | assert "drafts" in response
106 | assert len(response["drafts"]) == 1
107 | draft = response["drafts"][0]
108 | assert draft["channel_cid"] == channel2.cid
109 | assert draft["message"]["text"] == "Draft in channel 2"
110 |
111 | # Query drafts with sort
112 | response = await client.query_drafts(
113 | random_user["id"],
114 | sort=[{"field": "created_at", "direction": SortOrder.ASC}],
115 | )
116 |
117 | assert "drafts" in response
118 | assert len(response["drafts"]) == 2
119 | assert response["drafts"][0]["channel_cid"] == channel.cid
120 | assert response["drafts"][1]["channel_cid"] == channel2.cid
121 |
122 | # Query drafts with pagination
123 | response = await client.query_drafts(
124 | random_user["id"],
125 | options={"limit": 1},
126 | )
127 |
128 | assert "drafts" in response
129 | assert len(response["drafts"]) == 1
130 | assert response["drafts"][0]["channel_cid"] == channel2.cid
131 |
132 | assert response["next"] is not None
133 |
134 | # Query drafts with pagination
135 | response = await client.query_drafts(
136 | random_user["id"],
137 | options={"limit": 1, "next": response["next"]},
138 | )
139 |
140 | assert "drafts" in response
141 | assert len(response["drafts"]) == 1
142 | assert response["drafts"][0]["channel_cid"] == channel.cid
143 |
144 | # Cleanup
145 | try:
146 | await channel2.delete()
147 | except Exception:
148 | pass
149 |
--------------------------------------------------------------------------------
/stream_chat/tests/async_chat/test_campaign.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from typing import Dict
3 |
4 | import pytest
5 |
6 | from stream_chat import StreamChatAsync
7 | from stream_chat.types.base import SortOrder
8 | from stream_chat.types.segment import SegmentType
9 |
10 |
11 | @pytest.mark.incremental
12 | @pytest.mark.skip(reason="endpoints are not available in the API yet.")
13 | class TestCampaign:
14 | async def test_campaign_crud(self, client: StreamChatAsync, random_user: Dict):
15 | segment = await client.create_segment(segment_type=SegmentType.USER)
16 | segment_id = segment["segment"]["id"]
17 |
18 | sender_id = random_user["id"]
19 |
20 | campaign = client.campaign(
21 | data={
22 | "message_template": {
23 | "text": "{Hello}",
24 | },
25 | "segment_ids": [segment_id],
26 | "sender_id": sender_id,
27 | "name": "some name",
28 | }
29 | )
30 | created = await campaign.create()
31 | assert created.is_ok()
32 | assert "campaign" in created
33 | assert "id" in created["campaign"]
34 | assert "name" in created["campaign"]
35 |
36 | received = await campaign.get()
37 | assert received.is_ok()
38 | assert "campaign" in received
39 | assert "id" in received["campaign"]
40 | assert "name" in received["campaign"]
41 | assert received["campaign"]["name"] == created["campaign"]["name"]
42 |
43 | updated = await campaign.update(
44 | {
45 | "message_template": {
46 | "text": "{Hello}",
47 | },
48 | "segment_ids": [segment_id],
49 | "sender_id": sender_id,
50 | "name": "updated_name",
51 | }
52 | )
53 | assert updated.is_ok()
54 | assert "campaign" in updated
55 | assert "id" in updated["campaign"]
56 | assert "name" in updated["campaign"]
57 | assert updated["campaign"]["name"] == "updated_name"
58 |
59 | deleted = await campaign.delete()
60 | assert deleted.is_ok()
61 |
62 | await client.delete_segment(segment_id=segment_id)
63 |
64 | async def test_campaign_start_stop(
65 | self, client: StreamChatAsync, random_user: Dict
66 | ):
67 | segment = await client.create_segment(segment_type=SegmentType.USER)
68 | segment_id = segment["segment"]["id"]
69 |
70 | sender_id = random_user["id"]
71 |
72 | target_added = await client.add_segment_targets(
73 | segment_id=segment_id, target_ids=[sender_id]
74 | )
75 | assert target_added.is_ok()
76 |
77 | campaign = client.campaign(
78 | data={
79 | "message_template": {
80 | "text": "{Hello}",
81 | },
82 | "segment_ids": [segment_id],
83 | "sender_id": sender_id,
84 | "name": "some name",
85 | }
86 | )
87 | created = await campaign.create()
88 | assert created.is_ok()
89 | assert "campaign" in created
90 | assert "id" in created["campaign"]
91 | assert "name" in created["campaign"]
92 |
93 | now = datetime.datetime.now(datetime.timezone.utc)
94 | one_hour_later = now + datetime.timedelta(hours=1)
95 | two_hours_later = now + datetime.timedelta(hours=2)
96 |
97 | started = await campaign.start(
98 | scheduled_for=one_hour_later, stop_at=two_hours_later
99 | )
100 | assert started.is_ok()
101 | assert "campaign" in started
102 | assert "id" in started["campaign"]
103 | assert "name" in started["campaign"]
104 |
105 | stopped = await campaign.stop()
106 | assert stopped.is_ok()
107 | assert "campaign" in stopped
108 | assert "id" in stopped["campaign"]
109 | assert "name" in stopped["campaign"]
110 |
111 | deleted = await campaign.delete()
112 | assert deleted.is_ok()
113 |
114 | await client.delete_segment(segment_id=segment_id)
115 |
116 | async def test_query_campaigns(self, client: StreamChatAsync, random_user: Dict):
117 | segment_created = await client.create_segment(segment_type=SegmentType.USER)
118 | segment_id = segment_created["segment"]["id"]
119 |
120 | sender_id = random_user["id"]
121 |
122 | target_added = await client.add_segment_targets(
123 | segment_id=segment_id, target_ids=[sender_id]
124 | )
125 | assert target_added.is_ok()
126 |
127 | created = await client.create_campaign(
128 | data={
129 | "message_template": {
130 | "text": "{Hello}",
131 | },
132 | "segment_ids": [segment_id],
133 | "sender_id": sender_id,
134 | "name": "some name",
135 | }
136 | )
137 | assert created.is_ok()
138 | assert "campaign" in created
139 | assert "id" in created["campaign"]
140 | assert "name" in created["campaign"]
141 | campaign_id = created["campaign"]["id"]
142 |
143 | query_campaigns = await client.query_campaigns(
144 | filter_conditions={
145 | "id": {
146 | "$eq": campaign_id,
147 | }
148 | },
149 | sort=[{"field": "created_at", "direction": SortOrder.DESC}],
150 | options={
151 | "limit": 10,
152 | },
153 | )
154 | assert query_campaigns.is_ok()
155 | assert "campaigns" in query_campaigns
156 | assert len(query_campaigns["campaigns"]) == 1
157 | assert query_campaigns["campaigns"][0]["id"] == campaign_id
158 |
159 | deleted = await client.delete_campaign(campaign_id=campaign_id)
160 | assert deleted.is_ok()
161 |
162 | segment_deleted = await client.delete_segment(segment_id=segment_id)
163 | assert segment_deleted.is_ok()
164 |
--------------------------------------------------------------------------------
/stream_chat/tests/test_campaign.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from typing import Dict
3 |
4 | import pytest
5 |
6 | from stream_chat import StreamChat
7 | from stream_chat.types.base import SortOrder
8 | from stream_chat.types.segment import SegmentType
9 |
10 |
11 | @pytest.mark.incremental
12 | @pytest.mark.skip(reason="endpoints are not available in the API yet.")
13 | class TestCampaign:
14 | def test_campaign_crud(self, client: StreamChat, random_user: Dict):
15 | segment = client.create_segment(segment_type=SegmentType.USER)
16 | segment_id = segment["segment"]["id"]
17 |
18 | sender_id = random_user["id"]
19 |
20 | campaign = client.campaign(
21 | data={
22 | "message_template": {
23 | "text": "{Hello}",
24 | },
25 | "segment_ids": [segment_id],
26 | "sender_id": sender_id,
27 | "name": "some name",
28 | }
29 | )
30 | created = campaign.create(
31 | data={
32 | "name": "created name",
33 | }
34 | )
35 | assert created.is_ok()
36 | assert "campaign" in created
37 | assert "id" in created["campaign"]
38 | assert "name" in created["campaign"]
39 | assert created["campaign"]["name"] == "created name"
40 |
41 | received = campaign.get()
42 | assert received.is_ok()
43 | assert "campaign" in received
44 | assert "id" in received["campaign"]
45 | assert "name" in received["campaign"]
46 | assert received["campaign"]["name"] == created["campaign"]["name"]
47 |
48 | updated = campaign.update(
49 | {
50 | "message_template": {
51 | "text": "{Hello}",
52 | },
53 | "segment_ids": [segment_id],
54 | "sender_id": sender_id,
55 | "name": "updated_name",
56 | }
57 | )
58 | assert updated.is_ok()
59 | assert "campaign" in updated
60 | assert "id" in updated["campaign"]
61 | assert "name" in updated["campaign"]
62 | assert updated["campaign"]["name"] == "updated_name"
63 |
64 | deleted = campaign.delete()
65 | assert deleted.is_ok()
66 |
67 | segment_deleted = client.delete_segment(segment_id=segment_id)
68 | assert segment_deleted.is_ok()
69 |
70 | def test_campaign_start_stop(self, client: StreamChat, random_user: Dict):
71 | segment = client.create_segment(segment_type=SegmentType.USER)
72 | segment_id = segment["segment"]["id"]
73 |
74 | sender_id = random_user["id"]
75 |
76 | target_added = client.add_segment_targets(
77 | segment_id=segment_id, target_ids=[sender_id]
78 | )
79 | assert target_added.is_ok()
80 |
81 | campaign = client.campaign(
82 | data={
83 | "message_template": {
84 | "text": "{Hello}",
85 | },
86 | "segment_ids": [segment_id],
87 | "sender_id": sender_id,
88 | "name": "some name",
89 | }
90 | )
91 | created = campaign.create()
92 | assert created.is_ok()
93 | assert "campaign" in created
94 | assert "id" in created["campaign"]
95 | assert "name" in created["campaign"]
96 |
97 | now = datetime.datetime.now(datetime.timezone.utc)
98 | one_hour_later = now + datetime.timedelta(hours=1)
99 | two_hours_later = now + datetime.timedelta(hours=2)
100 |
101 | started = campaign.start(scheduled_for=one_hour_later, stop_at=two_hours_later)
102 | assert started.is_ok()
103 | assert "campaign" in started
104 | assert "id" in started["campaign"]
105 | assert "name" in started["campaign"]
106 |
107 | stopped = campaign.stop()
108 | assert stopped.is_ok()
109 | assert "campaign" in stopped
110 | assert "id" in stopped["campaign"]
111 | assert "name" in stopped["campaign"]
112 |
113 | deleted = campaign.delete()
114 | assert deleted.is_ok()
115 |
116 | client.delete_segment(segment_id=segment_id)
117 |
118 | def test_query_campaigns(self, client: StreamChat, random_user: Dict):
119 | segment_created = client.create_segment(segment_type=SegmentType.USER)
120 | segment_id = segment_created["segment"]["id"]
121 |
122 | sender_id = random_user["id"]
123 |
124 | target_added = client.add_segment_targets(
125 | segment_id=segment_id, target_ids=[sender_id]
126 | )
127 | assert target_added.is_ok()
128 |
129 | created = client.create_campaign(
130 | data={
131 | "message_template": {
132 | "text": "{Hello}",
133 | },
134 | "segment_ids": [segment_id],
135 | "sender_id": sender_id,
136 | "name": "some name",
137 | }
138 | )
139 | assert created.is_ok()
140 | assert "campaign" in created
141 | assert "id" in created["campaign"]
142 | assert "name" in created["campaign"]
143 | campaign_id = created["campaign"]["id"]
144 |
145 | query_campaigns = client.query_campaigns(
146 | filter_conditions={
147 | "id": {
148 | "$eq": campaign_id,
149 | }
150 | },
151 | sort=[{"field": "created_at", "direction": SortOrder.DESC}],
152 | options={
153 | "limit": 10,
154 | },
155 | )
156 | assert query_campaigns.is_ok()
157 | assert "campaigns" in query_campaigns
158 | assert len(query_campaigns["campaigns"]) == 1
159 | assert query_campaigns["campaigns"][0]["id"] == campaign_id
160 |
161 | deleted = client.delete_campaign(campaign_id=campaign_id)
162 | assert deleted.is_ok()
163 |
164 | segment_deleted = client.delete_segment(segment_id=segment_id)
165 | assert segment_deleted.is_ok()
166 |
--------------------------------------------------------------------------------
/stream_chat/tests/test_reminders.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime, timedelta, timezone
2 |
3 | import pytest
4 |
5 | from stream_chat import StreamChat
6 | from stream_chat.base.exceptions import StreamAPIException
7 |
8 |
9 | class TestReminders:
10 | @pytest.fixture(autouse=True)
11 | def setup_channel_for_reminders(self, channel):
12 | channel.update_partial(
13 | {"config_overrides": {"user_message_reminders": True}},
14 | )
15 | yield
16 | channel.update_partial(
17 | {"config_overrides": {"user_message_reminders": False}},
18 | )
19 |
20 | def test_create_reminder(self, client: StreamChat, channel, random_user):
21 | # First, send a message to create a reminder for
22 | message_data = {
23 | "text": "This is a test message for reminder",
24 | }
25 | response = channel.send_message(message_data, random_user["id"])
26 | message_id = response["message"]["id"]
27 |
28 | # Create a reminder without remind_at
29 | response = client.create_reminder(message_id, random_user["id"])
30 | # Verify the response contains the expected data
31 | assert response is not None
32 | assert "reminder" in response
33 | assert response["reminder"]["message_id"] == message_id
34 | assert "user_id" in response["reminder"]
35 |
36 | # Clean up - try to delete the reminder
37 | try:
38 | client.delete_reminder(message_id, random_user["id"])
39 | except StreamAPIException:
40 | pass # It's okay if deletion fails
41 |
42 | def test_create_reminder_with_remind_at(
43 | self, client: StreamChat, channel, random_user
44 | ):
45 | # First, send a message to create a reminder for
46 | message_data = {
47 | "text": "This is a test message for reminder with time",
48 | }
49 | response = channel.send_message(message_data, random_user["id"])
50 | message_id = response["message"]["id"]
51 |
52 | # Create a reminder with remind_at
53 | remind_at = datetime.now(timezone.utc) + timedelta(days=1)
54 | response = client.create_reminder(message_id, random_user["id"], remind_at)
55 | # Verify the response contains the expected data
56 | assert response is not None
57 | assert "reminder" in response
58 | assert response["reminder"]["message_id"] == message_id
59 | assert "user_id" in response["reminder"]
60 | assert "remind_at" in response["reminder"]
61 |
62 | # Clean up - try to delete the reminder
63 | try:
64 | client.delete_reminder(message_id, random_user["id"])
65 | except StreamAPIException:
66 | pass # It's okay if deletion fails
67 |
68 | def test_update_reminder(self, client: StreamChat, channel, random_user):
69 | # First, send a message to create a reminder for
70 | message_data = {
71 | "text": "This is a test message for updating reminder",
72 | }
73 | response = channel.send_message(message_data, random_user["id"])
74 | message_id = response["message"]["id"]
75 |
76 | # Create a reminder
77 | client.create_reminder(message_id, random_user["id"])
78 |
79 | # Update the reminder with a remind_at time
80 | remind_at = datetime.now(timezone.utc) + timedelta(days=2)
81 | response = client.update_reminder(message_id, random_user["id"], remind_at)
82 | # Verify the response contains the expected data
83 | assert response is not None
84 | assert "reminder" in response
85 | assert response["reminder"]["message_id"] == message_id
86 | assert "user_id" in response["reminder"]
87 | assert "remind_at" in response["reminder"]
88 |
89 | # Clean up - try to delete the reminder
90 | try:
91 | client.delete_reminder(message_id, random_user["id"])
92 | except StreamAPIException:
93 | pass # It's okay if deletion fails
94 |
95 | def test_delete_reminder(self, client: StreamChat, channel, random_user):
96 | # First, send a message to create a reminder for
97 | message_data = {
98 | "text": "This is a test message for deleting reminder",
99 | }
100 | response = channel.send_message(message_data, random_user["id"])
101 | message_id = response["message"]["id"]
102 |
103 | # Create a reminder
104 | client.create_reminder(message_id, random_user["id"])
105 |
106 | # Delete the reminder
107 | response = client.delete_reminder(message_id, random_user["id"])
108 | # Verify the response contains the expected data
109 | assert response is not None
110 | # The delete response may not include the reminder object
111 |
112 | def test_query_reminders(self, client: StreamChat, channel, random_user):
113 | # First, send messages to create reminders for
114 | message_ids = []
115 | channel_cid = channel.cid
116 |
117 | for i in range(3):
118 | message_data = {
119 | "text": f"This is test message {i} for querying reminders",
120 | }
121 | response = channel.send_message(message_data, random_user["id"])
122 | message_id = response["message"]["id"]
123 | message_ids.append(message_id)
124 |
125 | # Create a reminder with different remind_at times
126 | remind_at = datetime.now(timezone.utc) + timedelta(days=i + 1)
127 | client.create_reminder(message_id, random_user["id"], remind_at)
128 |
129 | # Test case 1: Query reminders without filters
130 | response = client.query_reminders(random_user["id"])
131 | assert response is not None
132 | assert "reminders" in response
133 | # Check that we have at least our 3 reminders
134 | assert len(response["reminders"]) >= 3
135 |
136 | # Check that at least some of our message IDs are in the results
137 | found_ids = [
138 | r["message_id"]
139 | for r in response["reminders"]
140 | if r["message_id"] in message_ids
141 | ]
142 | assert len(found_ids) > 0
143 |
144 | # Test case 2: Query reminders by message ID
145 | if len(message_ids) > 0:
146 | filter_conditions = {"message_id": {"$in": [message_ids[0]]}}
147 | response = client.query_reminders(random_user["id"], filter_conditions)
148 | assert response is not None
149 | assert "reminders" in response
150 | # Verify all returned reminders match the filter
151 | for reminder in response["reminders"]:
152 | assert reminder["message_id"] in [message_ids[0]]
153 |
154 | # Test case 3: Query reminders by single message ID
155 | filter_conditions = {"message_id": message_ids[0]}
156 | response = client.query_reminders(random_user["id"], filter_conditions)
157 | assert response is not None
158 | assert "reminders" in response
159 | assert len(response["reminders"]) >= 1
160 | # Verify all returned reminders have the exact message_id
161 | for reminder in response["reminders"]:
162 | assert reminder["message_id"] == message_ids[0]
163 |
164 | # Test case 4: Query reminders by channel CID
165 | filter_conditions = {"channel_cid": channel_cid}
166 | response = client.query_reminders(random_user["id"], filter_conditions)
167 | assert response is not None
168 | assert "reminders" in response
169 | assert len(response["reminders"]) >= 3
170 | # Verify all returned reminders belong to the channel
171 | for reminder in response["reminders"]:
172 | assert reminder["channel_cid"] == channel_cid
173 |
174 | # Clean up - try to delete the reminders
175 | for message_id in message_ids:
176 | try:
177 | client.delete_reminder(message_id, random_user["id"])
178 | except StreamAPIException:
179 | pass # It's okay if deletion fails
180 |
--------------------------------------------------------------------------------
/stream_chat/tests/async_chat/test_reminders.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime, timedelta, timezone
2 |
3 | import pytest
4 |
5 | from stream_chat.async_chat import StreamChatAsync
6 | from stream_chat.base.exceptions import StreamAPIException
7 |
8 |
9 | class TestReminders:
10 | @pytest.fixture(autouse=True)
11 | @pytest.mark.asyncio
12 | async def setup_channel_for_reminders(self, channel):
13 | await channel.update_partial(
14 | {"config_overrides": {"user_message_reminders": True}},
15 | )
16 | yield
17 | await channel.update_partial(
18 | {"config_overrides": {"user_message_reminders": False}},
19 | )
20 |
21 | @pytest.mark.asyncio
22 | async def test_create_reminder(self, client: StreamChatAsync, channel, random_user):
23 | # First, send a message to create a reminder for
24 | message_data = {
25 | "text": "This is a test message for reminder",
26 | }
27 | response = await channel.send_message(message_data, random_user["id"])
28 | message_id = response["message"]["id"]
29 |
30 | # Create a reminder without remind_at
31 | response = await client.create_reminder(message_id, random_user["id"])
32 | # Verify the response contains the expected data
33 | assert response is not None
34 | assert "reminder" in response
35 | assert response["reminder"]["message_id"] == message_id
36 | assert "user_id" in response["reminder"]
37 |
38 | # Clean up - try to delete the reminder
39 | try:
40 | await client.delete_reminder(message_id, random_user["id"])
41 | except StreamAPIException:
42 | pass # It's okay if deletion fails
43 |
44 | @pytest.mark.asyncio
45 | async def test_create_reminder_with_remind_at(
46 | self, client: StreamChatAsync, channel, random_user
47 | ):
48 | # First, send a message to create a reminder for
49 | message_data = {
50 | "text": "This is a test message for reminder with time",
51 | }
52 | response = await channel.send_message(message_data, random_user["id"])
53 | message_id = response["message"]["id"]
54 |
55 | # Create a reminder with remind_at
56 | remind_at = datetime.now(timezone.utc) + timedelta(days=1)
57 | response = await client.create_reminder(
58 | message_id, random_user["id"], remind_at
59 | )
60 | # Verify the response contains the expected data
61 | assert response is not None
62 | assert "reminder" in response
63 | assert response["reminder"]["message_id"] == message_id
64 | assert "user_id" in response["reminder"]
65 | assert "remind_at" in response["reminder"]
66 |
67 | # Clean up - try to delete the reminder
68 | try:
69 | await client.delete_reminder(message_id, random_user["id"])
70 | except StreamAPIException:
71 | pass # It's okay if deletion fails
72 |
73 | @pytest.mark.asyncio
74 | async def test_update_reminder(self, client: StreamChatAsync, channel, random_user):
75 | # First, send a message to create a reminder for
76 | message_data = {
77 | "text": "This is a test message for updating reminder",
78 | }
79 | response = await channel.send_message(message_data, random_user["id"])
80 | message_id = response["message"]["id"]
81 |
82 | # Create a reminder
83 | await client.create_reminder(message_id, random_user["id"])
84 |
85 | # Update the reminder with a remind_at time
86 | remind_at = datetime.now(timezone.utc) + timedelta(days=2)
87 | response = await client.update_reminder(
88 | message_id, random_user["id"], remind_at
89 | )
90 | # Verify the response contains the expected data
91 | assert response is not None
92 | assert "reminder" in response
93 | assert response["reminder"]["message_id"] == message_id
94 | assert "user_id" in response["reminder"]
95 | assert "remind_at" in response["reminder"]
96 |
97 | # Clean up - try to delete the reminder
98 | try:
99 | await client.delete_reminder(message_id, random_user["id"])
100 | except StreamAPIException:
101 | pass # It's okay if deletion fails
102 |
103 | @pytest.mark.asyncio
104 | async def test_delete_reminder(self, client: StreamChatAsync, channel, random_user):
105 | # First, send a message to create a reminder for
106 | message_data = {
107 | "text": "This is a test message for deleting reminder",
108 | }
109 | response = await channel.send_message(message_data, random_user["id"])
110 | message_id = response["message"]["id"]
111 |
112 | # Create a reminder
113 | await client.create_reminder(message_id, random_user["id"])
114 |
115 | # Delete the reminder
116 | response = await client.delete_reminder(message_id, random_user["id"])
117 | # Verify the response contains the expected data
118 | assert response is not None
119 | # The delete response may not include the reminder object
120 |
121 | @pytest.mark.asyncio
122 | async def test_query_reminders(self, client: StreamChatAsync, channel, random_user):
123 | # First, send messages to create reminders for
124 | message_ids = []
125 | channel_cid = channel.cid
126 |
127 | for i in range(3):
128 | message_data = {
129 | "text": f"This is test message {i} for querying reminders",
130 | }
131 | response = await channel.send_message(message_data, random_user["id"])
132 | message_id = response["message"]["id"]
133 | message_ids.append(message_id)
134 |
135 | # Create a reminder with different remind_at times
136 | remind_at = datetime.now(timezone.utc) + timedelta(hours=i + 1)
137 | await client.create_reminder(message_id, random_user["id"], remind_at)
138 |
139 | # Test case 1: Query reminders without filters
140 | response = await client.query_reminders(random_user["id"])
141 | assert response is not None
142 | assert "reminders" in response
143 | # Check that we have at least our 3 reminders
144 | assert len(response["reminders"]) >= 3
145 |
146 | # Check that at least some of our message IDs are in the results
147 | found_ids = [
148 | r["message_id"]
149 | for r in response["reminders"]
150 | if r["message_id"] in message_ids
151 | ]
152 | assert len(found_ids) > 0
153 |
154 | # Test case 2: Query reminders by message ID
155 | if len(message_ids) > 0:
156 | filter_conditions = {"message_id": {"$in": [message_ids[0]]}}
157 | response = await client.query_reminders(
158 | random_user["id"], filter_conditions
159 | )
160 | assert response is not None
161 | assert "reminders" in response
162 | # Verify all returned reminders match the filter
163 | for reminder in response["reminders"]:
164 | assert reminder["message_id"] in [message_ids[0]]
165 |
166 | # Test case 3: Query reminders by single message ID
167 | filter_conditions = {"message_id": message_ids[0]}
168 | response = await client.query_reminders(random_user["id"], filter_conditions)
169 | assert response is not None
170 | assert "reminders" in response
171 | assert len(response["reminders"]) >= 1
172 | # Verify all returned reminders have the exact message_id
173 | for reminder in response["reminders"]:
174 | assert reminder["message_id"] == message_ids[0]
175 |
176 | # Test case 4: Query reminders by channel CID
177 | filter_conditions = {"channel_cid": channel_cid}
178 | response = await client.query_reminders(random_user["id"], filter_conditions)
179 | assert response is not None
180 | assert "reminders" in response
181 | assert len(response["reminders"]) >= 3
182 | # Verify all returned reminders belong to the channel
183 | for reminder in response["reminders"]:
184 | assert reminder["channel_cid"] == channel_cid
185 |
186 | # Clean up - try to delete the reminders
187 | for message_id in message_ids:
188 | try:
189 | await client.delete_reminder(message_id, random_user["id"])
190 | except StreamAPIException:
191 | pass # It's okay if deletion fails
192 |
--------------------------------------------------------------------------------
/stream_chat/channel.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import json
3 | from typing import Any, Dict, Iterable, List, Optional, Union
4 |
5 | from stream_chat.base.channel import ChannelInterface, add_user_id
6 | from stream_chat.base.exceptions import StreamChannelException
7 | from stream_chat.types.stream_response import StreamResponse
8 |
9 |
10 | class Channel(ChannelInterface):
11 | def send_message(
12 | self, message: Dict, user_id: str, **options: Any
13 | ) -> StreamResponse:
14 | payload = {"message": add_user_id(message, user_id), **options}
15 | return self.client.post(f"{self.url}/message", data=payload)
16 |
17 | def send_event(self, event: Dict, user_id: str) -> StreamResponse:
18 | payload = {"event": add_user_id(event, user_id)}
19 | return self.client.post(f"{self.url}/event", data=payload)
20 |
21 | def send_reaction(
22 | self, message_id: str, reaction: Dict, user_id: str
23 | ) -> StreamResponse:
24 | payload = {"reaction": add_user_id(reaction, user_id)}
25 | return self.client.post(f"messages/{message_id}/reaction", data=payload)
26 |
27 | def delete_reaction(
28 | self, message_id: str, reaction_type: str, user_id: str
29 | ) -> StreamResponse:
30 | return self.client.delete(
31 | f"messages/{message_id}/reaction/{reaction_type}",
32 | params={"user_id": user_id},
33 | )
34 |
35 | def create(self, user_id: str, **options: Any) -> StreamResponse:
36 | self.custom_data["created_by"] = {"id": user_id}
37 | options["watch"] = False
38 | options["state"] = False
39 | options["presence"] = False
40 | return self.query(**options)
41 |
42 | def get_messages(self, message_ids: List[str]) -> StreamResponse:
43 | return self.client.get(
44 | f"{self.url}/messages", params={"ids": ",".join(message_ids)}
45 | )
46 |
47 | def query(self, **options: Any) -> StreamResponse:
48 | payload = {"state": True, "data": self.custom_data, **options}
49 |
50 | url = f"channels/{self.channel_type}"
51 | if self.id is not None:
52 | url = f"{url}/{self.id}"
53 |
54 | state = self.client.post(f"{url}/query", data=payload)
55 |
56 | if self.id is None:
57 | self.id: str = state["channel"]["id"]
58 |
59 | return state
60 |
61 | def query_members(
62 | self, filter_conditions: Dict, sort: List[Dict] = None, **options: Any
63 | ) -> List[Dict]:
64 | payload = {
65 | "id": self.id,
66 | "type": self.channel_type,
67 | "filter_conditions": filter_conditions,
68 | "sort": self.client.normalize_sort(sort),
69 | **options,
70 | }
71 | response: StreamResponse = self.client.get(
72 | "members", params={"payload": json.dumps(payload)}
73 | )
74 | return response["members"]
75 |
76 | def update(self, channel_data: Dict, update_message: Dict = None) -> StreamResponse:
77 | payload = {"data": channel_data, "message": update_message}
78 | return self.client.post(self.url, data=payload)
79 |
80 | def update_partial(
81 | self, to_set: Dict = None, to_unset: Iterable[str] = None
82 | ) -> StreamResponse:
83 | payload = {"set": to_set or {}, "unset": to_unset or []}
84 | return self.client.patch(self.url, data=payload)
85 |
86 | def delete(self, hard: bool = False) -> StreamResponse:
87 | return self.client.delete(self.url, params={"hard_delete": hard})
88 |
89 | def truncate(self, **options: Any) -> StreamResponse:
90 | return self.client.post(f"{self.url}/truncate", data=options)
91 |
92 | def add_members(
93 | self,
94 | members: Union[Iterable[Dict], Iterable[str]],
95 | message: Dict = None,
96 | **options: Any,
97 | ) -> StreamResponse:
98 | payload = {"add_members": members, "message": message, **options}
99 | if "hide_history_before" in payload and isinstance(
100 | payload["hide_history_before"], datetime.datetime
101 | ):
102 | payload["hide_history_before"] = payload["hide_history_before"].isoformat()
103 | return self.client.post(self.url, data=payload)
104 |
105 | def assign_roles(
106 | self, members: Iterable[Dict], message: Dict = None
107 | ) -> StreamResponse:
108 | return self.client.post(
109 | self.url, data={"assign_roles": members, "message": message}
110 | )
111 |
112 | def invite_members(
113 | self, user_ids: Iterable[str], message: Dict = None
114 | ) -> StreamResponse:
115 | return self.client.post(
116 | self.url, data={"invites": user_ids, "message": message}
117 | )
118 |
119 | def add_moderators(
120 | self, user_ids: Iterable[str], message: Dict = None
121 | ) -> StreamResponse:
122 | return self.client.post(
123 | self.url, data={"add_moderators": user_ids, "message": message}
124 | )
125 |
126 | def remove_members(
127 | self, user_ids: Iterable[str], message: Dict = None
128 | ) -> StreamResponse:
129 | return self.client.post(
130 | self.url, data={"remove_members": user_ids, "message": message}
131 | )
132 |
133 | def demote_moderators(
134 | self, user_ids: Iterable[str], message: Dict = None
135 | ) -> StreamResponse:
136 | return self.client.post(
137 | self.url, data={"demote_moderators": user_ids, "message": message}
138 | )
139 |
140 | def mark_read(self, user_id: str, **data: Any) -> StreamResponse:
141 | payload = add_user_id(data, user_id)
142 | return self.client.post(f"{self.url}/read", data=payload)
143 |
144 | def mark_unread(self, user_id: str, **data: Any) -> StreamResponse:
145 | payload = add_user_id(data, user_id)
146 | return self.client.post(f"{self.url}/unread", data=payload)
147 |
148 | def get_replies(self, parent_id: str, **options: Any) -> StreamResponse:
149 | return self.client.get(f"messages/{parent_id}/replies", params=options)
150 |
151 | def get_reactions(self, message_id: str, **options: Any) -> StreamResponse:
152 | return self.client.get(f"messages/{message_id}/reactions", params=options)
153 |
154 | def ban_user(self, target_id: str, **options: Any) -> StreamResponse:
155 | return self.client.ban_user( # type: ignore
156 | target_id, type=self.channel_type, id=self.id, **options
157 | )
158 |
159 | def unban_user(self, target_id: str, **options: Any) -> StreamResponse:
160 | return self.client.unban_user( # type: ignore
161 | target_id, type=self.channel_type, id=self.id, **options
162 | )
163 |
164 | def accept_invite(self, user_id: str, **data: Any) -> StreamResponse:
165 | payload = add_user_id(data, user_id)
166 | payload["accept_invite"] = True
167 | response = self.client.post(self.url, data=payload)
168 | self.custom_data = response["channel"]
169 | return response
170 |
171 | def reject_invite(self, user_id: str, **data: Any) -> StreamResponse:
172 | payload = add_user_id(data, user_id)
173 | payload["reject_invite"] = True
174 | response = self.client.post(self.url, data=payload)
175 | self.custom_data = response["channel"]
176 | return response
177 |
178 | def send_file(
179 | self, url: str, name: str, user: Dict, content_type: str = None
180 | ) -> StreamResponse:
181 | return self.client.send_file( # type: ignore
182 | f"{self.url}/file", url, name, user, content_type=content_type
183 | )
184 |
185 | def send_image(
186 | self, url: str, name: str, user: Dict, content_type: str = None
187 | ) -> StreamResponse:
188 | return self.client.send_file( # type: ignore
189 | f"{self.url}/image", url, name, user, content_type=content_type
190 | )
191 |
192 | def delete_file(self, url: str) -> StreamResponse:
193 | return self.client.delete(f"{self.url}/file", {"url": url})
194 |
195 | def delete_image(self, url: str) -> StreamResponse:
196 | return self.client.delete(f"{self.url}/image", {"url": url})
197 |
198 | def hide(self, user_id: str) -> StreamResponse:
199 | return self.client.post(f"{self.url}/hide", data={"user_id": user_id})
200 |
201 | def show(self, user_id: str) -> StreamResponse:
202 | return self.client.post(f"{self.url}/show", data={"user_id": user_id})
203 |
204 | def mute(self, user_id: str, expiration: int = None) -> StreamResponse:
205 | params: Dict[str, Union[str, int]] = {
206 | "user_id": user_id,
207 | "channel_cid": self.cid,
208 | }
209 | if expiration:
210 | params["expiration"] = expiration
211 | return self.client.post("moderation/mute/channel", data=params)
212 |
213 | def unmute(self, user_id: str) -> StreamResponse:
214 | params = {
215 | "user_id": user_id,
216 | "channel_cid": self.cid,
217 | }
218 | return self.client.post("moderation/unmute/channel", data=params)
219 |
220 | def pin(self, user_id: str) -> StreamResponse:
221 | if not user_id:
222 | raise StreamChannelException("user_id must not be empty")
223 |
224 | payload = {"set": {"pinned": True}}
225 | return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
226 |
227 | def unpin(self, user_id: str) -> StreamResponse:
228 | if not user_id:
229 | raise StreamChannelException("user_id must not be empty")
230 |
231 | payload = {"set": {"pinned": False}}
232 | return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
233 |
234 | def archive(self, user_id: str) -> StreamResponse:
235 | if not user_id:
236 | raise StreamChannelException("user_id must not be empty")
237 |
238 | payload = {"set": {"archived": True}}
239 | return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
240 |
241 | def unarchive(self, user_id: str) -> StreamResponse:
242 | if not user_id:
243 | raise StreamChannelException("user_id must not be empty")
244 |
245 | payload = {"set": {"archived": False}}
246 | return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
247 |
248 | def update_member_partial(
249 | self, user_id: str, to_set: Dict = None, to_unset: Iterable[str] = None
250 | ) -> StreamResponse:
251 | if not user_id:
252 | raise StreamChannelException("user_id must not be empty")
253 |
254 | payload = {"set": to_set or {}, "unset": to_unset or []}
255 | return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
256 |
257 | def create_draft(self, message: Dict, user_id: str) -> StreamResponse:
258 | message["user_id"] = user_id
259 | payload = {"message": message}
260 | return self.client.post(f"{self.url}/draft", data=payload)
261 |
262 | def delete_draft(
263 | self, user_id: str, parent_id: Optional[str] = None
264 | ) -> StreamResponse:
265 | params = {"user_id": user_id}
266 | if parent_id:
267 | params["parent_id"] = parent_id
268 |
269 | return self.client.delete(f"{self.url}/draft", params=params)
270 |
271 | def get_draft(
272 | self, user_id: str, parent_id: Optional[str] = None
273 | ) -> StreamResponse:
274 | params = {"user_id": user_id}
275 | if parent_id:
276 | params["parent_id"] = parent_id
277 |
278 | return self.client.get(f"{self.url}/draft", params=params)
279 |
280 | def add_filter_tags(
281 | self,
282 | tags: Iterable[str],
283 | message: Dict = None,
284 | ) -> StreamResponse:
285 | payload = {"add_filter_tags": tags, "message": message}
286 | return self.client.post(self.url, data=payload)
287 |
288 | def remove_filter_tags(
289 | self,
290 | tags: Iterable[str],
291 | message: Dict = None,
292 | ) -> StreamResponse:
293 | payload = {"remove_filter_tags": tags, "message": message}
294 | return self.client.post(self.url, data=payload)
295 |
--------------------------------------------------------------------------------
/stream_chat/async_chat/channel.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import json
3 | from typing import Any, Dict, Iterable, List, Optional, Union
4 |
5 | from stream_chat.base.channel import ChannelInterface, add_user_id
6 | from stream_chat.base.exceptions import StreamChannelException
7 | from stream_chat.types.stream_response import StreamResponse
8 |
9 |
10 | class Channel(ChannelInterface):
11 | async def send_message(
12 | self, message: Dict, user_id: str, **options: Any
13 | ) -> StreamResponse:
14 | payload = {"message": add_user_id(message, user_id), **options}
15 | return await self.client.post(f"{self.url}/message", data=payload)
16 |
17 | async def get_messages(self, message_ids: List[str]) -> StreamResponse:
18 | return await self.client.get(
19 | f"{self.url}/messages", params={"ids": ",".join(message_ids)}
20 | )
21 |
22 | async def send_event(self, event: Dict, user_id: str) -> StreamResponse:
23 | payload = {"event": add_user_id(event, user_id)}
24 | return await self.client.post(f"{self.url}/event", data=payload)
25 |
26 | async def send_reaction(
27 | self, message_id: str, reaction: Dict, user_id: str
28 | ) -> StreamResponse:
29 | payload = {"reaction": add_user_id(reaction, user_id)}
30 | return await self.client.post(f"messages/{message_id}/reaction", data=payload)
31 |
32 | async def delete_reaction(
33 | self, message_id: str, reaction_type: str, user_id: str
34 | ) -> StreamResponse:
35 | return await self.client.delete(
36 | f"messages/{message_id}/reaction/{reaction_type}",
37 | params={"user_id": user_id},
38 | )
39 |
40 | async def create(self, user_id: str, **options: Any) -> StreamResponse:
41 | self.custom_data["created_by"] = {"id": user_id}
42 | options["watch"] = False
43 | options["state"] = False
44 | options["presence"] = False
45 | return await self.query(**options)
46 |
47 | async def query(self, **options: Any) -> StreamResponse:
48 | payload = {"state": True, "data": self.custom_data, **options}
49 |
50 | url = f"channels/{self.channel_type}"
51 | if self.id is not None:
52 | url = f"{url}/{self.id}"
53 |
54 | state = await self.client.post(f"{url}/query", data=payload)
55 |
56 | if self.id is None:
57 | self.id = state["channel"]["id"]
58 |
59 | return state
60 |
61 | async def query_members(
62 | self, filter_conditions: Dict, sort: List[Dict] = None, **options: Any
63 | ) -> List[Dict]:
64 | payload = {
65 | "id": self.id,
66 | "type": self.channel_type,
67 | "filter_conditions": filter_conditions,
68 | "sort": self.client.normalize_sort(sort),
69 | **options,
70 | }
71 | response: StreamResponse = await self.client.get(
72 | "members", params={"payload": json.dumps(payload)}
73 | )
74 | return response["members"]
75 |
76 | async def update(
77 | self, channel_data: Dict, update_message: Dict = None
78 | ) -> StreamResponse:
79 | payload = {"data": channel_data, "message": update_message}
80 | return await self.client.post(self.url, data=payload)
81 |
82 | async def update_partial(
83 | self, to_set: Dict = None, to_unset: Iterable[str] = None
84 | ) -> StreamResponse:
85 | payload = {"set": to_set or {}, "unset": to_unset or []}
86 | return await self.client.patch(self.url, data=payload)
87 |
88 | async def delete(self, hard: bool = False) -> StreamResponse:
89 | return await self.client.delete(self.url, {"hard_delete": hard})
90 |
91 | async def truncate(self, **options: Any) -> StreamResponse:
92 | return await self.client.post(f"{self.url}/truncate", data=options)
93 |
94 | async def add_members(
95 | self, members: Iterable[Dict], message: Dict = None, **options: Any
96 | ) -> StreamResponse:
97 | payload = {"add_members": members, "message": message, **options}
98 | if "hide_history_before" in payload and isinstance(
99 | payload["hide_history_before"], datetime.datetime
100 | ):
101 | payload["hide_history_before"] = payload["hide_history_before"].isoformat()
102 | return await self.client.post(self.url, data=payload)
103 |
104 | async def assign_roles(
105 | self, members: Iterable[Dict], message: Dict = None
106 | ) -> StreamResponse:
107 | return await self.client.post(
108 | self.url, data={"assign_roles": members, "message": message}
109 | )
110 |
111 | async def invite_members(
112 | self, user_ids: Iterable[str], message: Dict = None
113 | ) -> StreamResponse:
114 | return await self.client.post(
115 | self.url, data={"invites": user_ids, "message": message}
116 | )
117 |
118 | async def add_moderators(
119 | self, user_ids: Iterable[str], message: Dict = None
120 | ) -> StreamResponse:
121 | return await self.client.post(
122 | self.url, data={"add_moderators": user_ids, "message": message}
123 | )
124 |
125 | async def remove_members(
126 | self, user_ids: Iterable[str], message: Dict = None
127 | ) -> StreamResponse:
128 | return await self.client.post(
129 | self.url, data={"remove_members": user_ids, "message": message}
130 | )
131 |
132 | async def demote_moderators(
133 | self, user_ids: Iterable[str], message: Dict = None
134 | ) -> StreamResponse:
135 | return await self.client.post(
136 | self.url, data={"demote_moderators": user_ids, "message": message}
137 | )
138 |
139 | async def mark_read(self, user_id: str, **data: Any) -> StreamResponse:
140 | payload = add_user_id(data, user_id)
141 | return await self.client.post(f"{self.url}/read", data=payload)
142 |
143 | async def mark_unread(self, user_id: str, **data: Any) -> StreamResponse:
144 | payload = add_user_id(data, user_id)
145 | return await self.client.post(f"{self.url}/unread", data=payload)
146 |
147 | async def get_replies(self, parent_id: str, **options: Any) -> StreamResponse:
148 | return await self.client.get(f"messages/{parent_id}/replies", params=options)
149 |
150 | async def get_reactions(self, message_id: str, **options: Any) -> StreamResponse:
151 | return await self.client.get(f"messages/{message_id}/reactions", params=options)
152 |
153 | async def ban_user(self, target_id: str, **options: Any) -> StreamResponse:
154 | return await self.client.ban_user( # type: ignore
155 | target_id, type=self.channel_type, id=self.id, **options
156 | )
157 |
158 | async def unban_user(self, target_id: str, **options: Any) -> StreamResponse:
159 | return await self.client.unban_user( # type: ignore
160 | target_id, type=self.channel_type, id=self.id, **options
161 | )
162 |
163 | async def accept_invite(self, user_id: str, **data: Any) -> StreamResponse:
164 | payload = add_user_id(data, user_id)
165 | payload["accept_invite"] = True
166 | response = await self.client.post(self.url, data=payload)
167 | self.custom_data = response["channel"]
168 | return response
169 |
170 | async def reject_invite(self, user_id: str, **data: Any) -> StreamResponse:
171 | payload = add_user_id(data, user_id)
172 | payload["reject_invite"] = True
173 | response = await self.client.post(self.url, data=payload)
174 | self.custom_data = response["channel"]
175 | return response
176 |
177 | async def send_file(
178 | self, url: str, name: str, user: Dict, content_type: str = None
179 | ) -> StreamResponse:
180 | return await self.client.send_file( # type: ignore
181 | f"{self.url}/file", url, name, user, content_type=content_type
182 | )
183 |
184 | async def send_image(
185 | self, url: str, name: str, user: Dict, content_type: str = None
186 | ) -> StreamResponse:
187 | return await self.client.send_file( # type: ignore
188 | f"{self.url}/image", url, name, user, content_type=content_type
189 | )
190 |
191 | async def delete_file(self, url: str) -> StreamResponse:
192 | return await self.client.delete(f"{self.url}/file", {"url": url})
193 |
194 | async def delete_image(self, url: str) -> StreamResponse:
195 | return await self.client.delete(f"{self.url}/image", {"url": url})
196 |
197 | async def hide(self, user_id: str) -> StreamResponse:
198 | return await self.client.post(f"{self.url}/hide", data={"user_id": user_id})
199 |
200 | async def show(self, user_id: str) -> StreamResponse:
201 | return await self.client.post(f"{self.url}/show", data={"user_id": user_id})
202 |
203 | async def mute(self, user_id: str, expiration: int = None) -> StreamResponse:
204 | params: Dict[str, Union[str, int]] = {
205 | "user_id": user_id,
206 | "channel_cid": self.cid,
207 | }
208 | if expiration:
209 | params["expiration"] = expiration
210 | return await self.client.post("moderation/mute/channel", data=params)
211 |
212 | async def unmute(self, user_id: str) -> StreamResponse:
213 | params = {
214 | "user_id": user_id,
215 | "channel_cid": self.cid,
216 | }
217 | return await self.client.post("moderation/unmute/channel", data=params)
218 |
219 | async def pin(self, user_id: str) -> StreamResponse:
220 | if not user_id:
221 | raise StreamChannelException("user_id must not be empty")
222 |
223 | payload = {"set": {"pinned": True}}
224 | return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
225 |
226 | async def unpin(self, user_id: str) -> StreamResponse:
227 | if not user_id:
228 | raise StreamChannelException("user_id must not be empty")
229 |
230 | payload = {"set": {"pinned": False}}
231 | return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
232 |
233 | async def archive(self, user_id: str) -> StreamResponse:
234 | if not user_id:
235 | raise StreamChannelException("user_id must not be empty")
236 |
237 | payload = {"set": {"archived": True}}
238 | return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
239 |
240 | async def unarchive(self, user_id: str) -> StreamResponse:
241 | if not user_id:
242 | raise StreamChannelException("user_id must not be empty")
243 |
244 | payload = {"set": {"archived": False}}
245 | return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
246 |
247 | async def update_member_partial(
248 | self, user_id: str, to_set: Dict = None, to_unset: Iterable[str] = None
249 | ) -> StreamResponse:
250 | if not user_id:
251 | raise StreamChannelException("user_id must not be empty")
252 |
253 | payload = {"set": to_set or {}, "unset": to_unset or []}
254 | return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
255 |
256 | async def create_draft(self, message: Dict, user_id: str) -> StreamResponse:
257 | payload = {"message": add_user_id(message, user_id)}
258 | return await self.client.post(f"{self.url}/draft", data=payload)
259 |
260 | async def delete_draft(
261 | self, user_id: str, parent_id: Optional[str] = None
262 | ) -> StreamResponse:
263 | params = {"user_id": user_id}
264 | if parent_id:
265 | params["parent_id"] = parent_id
266 | return await self.client.delete(f"{self.url}/draft", params=params)
267 |
268 | async def get_draft(
269 | self, user_id: str, parent_id: Optional[str] = None
270 | ) -> StreamResponse:
271 | params = {"user_id": user_id}
272 | if parent_id:
273 | params["parent_id"] = parent_id
274 | return await self.client.get(f"{self.url}/draft", params=params)
275 |
276 | async def add_filter_tags(
277 | self,
278 | tags: Iterable[str],
279 | message: Dict = None,
280 | ) -> StreamResponse:
281 | payload = {"add_filter_tags": tags, "message": message}
282 | return await self.client.post(self.url, data=payload)
283 |
284 | async def remove_filter_tags(
285 | self,
286 | tags: Iterable[str],
287 | message: Dict = None,
288 | ) -> StreamResponse:
289 | payload = {"remove_filter_tags": tags, "message": message}
290 | return await self.client.post(self.url, data=payload)
291 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | SOURCE CODE LICENSE AGREEMENT
2 |
3 | IMPORTANT - READ THIS CAREFULLY BEFORE DOWNLOADING, INSTALLING, USING OR
4 | ELECTRONICALLY ACCESSING THIS PROPRIETARY PRODUCT.
5 |
6 | THIS IS A LEGAL AGREEMENT BETWEEN STREAM.IO, INC. (“STREAM.IO”) AND THE
7 | BUSINESS ENTITY OR PERSON FOR WHOM YOU (“YOU”) ARE ACTING (“CUSTOMER”) AS THE
8 | LICENSEE OF THE PROPRIETARY SOFTWARE INTO WHICH THIS AGREEMENT HAS BEEN
9 | INCLUDED (THE “AGREEMENT”). YOU AGREE THAT YOU ARE THE CUSTOMER, OR YOU ARE AN
10 | EMPLOYEE OR AGENT OF CUSTOMER AND ARE ENTERING INTO THIS AGREEMENT FOR LICENSE
11 | OF THE SOFTWARE BY CUSTOMER FOR CUSTOMER’S BUSINESS PURPOSES AS DESCRIBED IN
12 | AND IN ACCORDANCE WITH THIS AGREEMENT. YOU HEREBY AGREE THAT YOU ENTER INTO
13 | THIS AGREEMENT ON BEHALF OF CUSTOMER AND THAT YOU HAVE THE AUTHORITY TO BIND
14 | CUSTOMER TO THIS AGREEMENT.
15 |
16 | STREAM.IO IS WILLING TO LICENSE THE SOFTWARE TO CUSTOMER ONLY ON THE FOLLOWING
17 | CONDITIONS: (1) YOU ARE A CURRENT CUSTOMER OF STREAM.IO; (2) YOU ARE NOT A
18 | COMPETITOR OF STREAM.IO; AND (3) THAT YOU ACCEPT ALL THE TERMS IN THIS
19 | AGREEMENT. BY DOWNLOADING, INSTALLING, CONFIGURING, ACCESSING OR OTHERWISE
20 | USING THE SOFTWARE, INCLUDING ANY UPDATES, UPGRADES, OR NEWER VERSIONS, YOU
21 | REPRESENT, WARRANT AND ACKNOWLEDGE THAT (A) CUSTOMER IS A CURRENT CUSTOMER OF
22 | STREAM.IO; (B) CUSTOMER IS NOT A COMPETITOR OF STREAM.IO; AND THAT (C) YOU HAVE
23 | READ THIS AGREEMENT, UNDERSTAND THIS AGREEMENT, AND THAT CUSTOMER AGREES TO BE
24 | BOUND BY ALL THE TERMS OF THIS AGREEMENT.
25 |
26 | IF YOU DO NOT AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT,
27 | STREAM.IO IS UNWILLING TO LICENSE THE SOFTWARE TO CUSTOMER, AND THEREFORE, DO
28 | NOT COMPLETE THE DOWNLOAD PROCESS, ACCESS OR OTHERWISE USE THE SOFTWARE, AND
29 | CUSTOMER SHOULD IMMEDIATELY RETURN THE SOFTWARE AND CEASE ANY USE OF THE
30 | SOFTWARE.
31 |
32 | 1. SOFTWARE. The Stream.io software accompanying this Agreement, may include
33 | Source Code, Executable Object Code, associated media, printed materials and
34 | documentation (collectively, the “Software”). The Software also includes any
35 | updates or upgrades to or new versions of the original Software, if and when
36 | made available to you by Stream.io. “Source Code” means computer programming
37 | code in human readable form that is not suitable for machine execution without
38 | the intervening steps of interpretation or compilation. “Executable Object
39 | Code" means the computer programming code in any other form than Source Code
40 | that is not readily perceivable by humans and suitable for machine execution
41 | without the intervening steps of interpretation or compilation. “Site” means a
42 | Customer location controlled by Customer. “Authorized User” means any employee
43 | or contractor of Customer working at the Site, who has signed a written
44 | confidentiality agreement with Customer or is otherwise bound in writing by
45 | confidentiality and use obligations at least as restrictive as those imposed
46 | under this Agreement.
47 |
48 | 2. LICENSE GRANT. Subject to the terms and conditions of this Agreement, in
49 | consideration for the representations, warranties, and covenants made by
50 | Customer in this Agreement, Stream.io grants to Customer, during the term of
51 | this Agreement, a personal, non-exclusive, non-transferable, non-sublicensable
52 | license to:
53 |
54 | a. install and use Software Source Code on password protected computers at a Site,
55 | restricted to Authorized Users;
56 |
57 | b. create derivative works, improvements (whether or not patentable), extensions
58 | and other modifications to the Software Source Code (“Modifications”) to build
59 | unique scalable newsfeeds, activity streams, and in-app messaging via Stream’s
60 | application program interface (“API”);
61 |
62 | c. compile the Software Source Code to create Executable Object Code versions of
63 | the Software Source Code and Modifications to build such newsfeeds, activity
64 | streams, and in-app messaging via the API;
65 |
66 | d. install, execute and use such Executable Object Code versions solely for
67 | Customer’s internal business use (including development of websites through
68 | which data generated by Stream services will be streamed (“Apps”));
69 |
70 | e. use and distribute such Executable Object Code as part of Customer’s Apps; and
71 |
72 | f. make electronic copies of the Software and Modifications as required for backup
73 | or archival purposes.
74 |
75 | 3. RESTRICTIONS. Customer is responsible for all activities that occur in
76 | connection with the Software. Customer will not, and will not attempt to: (a)
77 | sublicense or transfer the Software or any Source Code related to the Software
78 | or any of Customer’s rights under this Agreement, except as otherwise provided
79 | in this Agreement, (b) use the Software Source Code for the benefit of a third
80 | party or to operate a service; (c) allow any third party to access or use the
81 | Software Source Code; (d) sublicense or distribute the Software Source Code or
82 | any Modifications in Source Code or other derivative works based on any part of
83 | the Software Source Code; (e) use the Software in any manner that competes with
84 | Stream.io or its business; or (e) otherwise use the Software in any manner that
85 | exceeds the scope of use permitted in this Agreement. Customer shall use the
86 | Software in compliance with any accompanying documentation any laws applicable
87 | to Customer.
88 |
89 | 4. OPEN SOURCE. Customer and its Authorized Users shall not use any software or
90 | software components that are open source in conjunction with the Software
91 | Source Code or any Modifications in Source Code or in any way that could
92 | subject the Software to any open source licenses.
93 |
94 | 5. CONTRACTORS. Under the rights granted to Customer under this Agreement,
95 | Customer may permit its employees, contractors, and agencies of Customer to
96 | become Authorized Users to exercise the rights to the Software granted to
97 | Customer in accordance with this Agreement solely on behalf of Customer to
98 | provide services to Customer; provided that Customer shall be liable for the
99 | acts and omissions of all Authorized Users to the extent any of such acts or
100 | omissions, if performed by Customer, would constitute a breach of, or otherwise
101 | give rise to liability to Customer under, this Agreement. Customer shall not
102 | and shall not permit any Authorized User to use the Software except as
103 | expressly permitted in this Agreement.
104 |
105 | 6. COMPETITIVE PRODUCT DEVELOPMENT. Customer shall not use the Software in any way
106 | to engage in the development of products or services which could be reasonably
107 | construed to provide a complete or partial functional or commercial alternative
108 | to Stream.io’s products or services (a “Competitive Product”). Customer shall
109 | ensure that there is no direct or indirect use of, or sharing of, Software
110 | source code, or other information based upon or derived from the Software to
111 | develop such products or services. Without derogating from the generality of
112 | the foregoing, development of Competitive Products shall include having direct
113 | or indirect access to, supervising, consulting or assisting in the development
114 | of, or producing any specifications, documentation, object code or source code
115 | for, all or part of a Competitive Product.
116 |
117 | 7. LIMITATION ON MODIFICATIONS. Notwithstanding any provision in this Agreement,
118 | Modifications may only be created and used by Customer as permitted by this
119 | Agreement and Modification Source Code may not be distributed to third parties.
120 | Customer will not assert against Stream.io, its affiliates, or their customers,
121 | direct or indirect, agents and contractors, in any way, any patent rights that
122 | Customer may obtain relating to any Modifications for Stream.io, its
123 | affiliates’, or their customers’, direct or indirect, agents’ and contractors’
124 | manufacture, use, import, offer for sale or sale of any Stream.io products or
125 | services.
126 |
127 | 8. DELIVERY AND ACCEPTANCE. The Software will be delivered electronically pursuant
128 | to Stream.io standard download procedures. The Software is deemed accepted upon
129 | delivery.
130 |
131 | 9. IMPLEMENTATION AND SUPPORT. Stream.io has no obligation under this Agreement to
132 | provide any support or consultation concerning the Software.
133 |
134 | 10. TERM AND TERMINATION. The term of this Agreement begins when the Software is
135 | downloaded or accessed and shall continue until terminated. Either party may
136 | terminate this Agreement upon written notice. This Agreement shall
137 | automatically terminate if Customer is or becomes a competitor of Stream.io or
138 | makes or sells any Competitive Products. Upon termination of this Agreement for
139 | any reason, (a) all rights granted to Customer in this Agreement immediately
140 | cease to exist, (b) Customer must promptly discontinue all use of the Software
141 | and return to Stream.io or destroy all copies of the Software in Customer’s
142 | possession or control. Any continued use of the Software by Customer or attempt
143 | by Customer to exercise any rights under this Agreement after this Agreement
144 | has terminated shall be considered copyright infringement and subject Customer
145 | to applicable remedies for copyright infringement. Sections 2, 5, 6, 8 and 9
146 | shall survive expiration or termination of this Agreement for any reason.
147 |
148 | 11. OWNERSHIP. As between the parties, the Software and all worldwide intellectual
149 | property rights and proprietary rights relating thereto or embodied therein,
150 | are the exclusive property of Stream.io and its suppliers. Stream.io and its
151 | suppliers reserve all rights in and to the Software not expressly granted to
152 | Customer in this Agreement, and no other licenses or rights are granted by
153 | implication, estoppel or otherwise.
154 |
155 | 12. WARRANTY DISCLAIMER. USE OF THIS SOFTWARE IS ENTIRELY AT YOURS AND CUSTOMER’S
156 | OWN RISK. THE SOFTWARE IS PROVIDED “AS IS” WITHOUT ANY WARRANTY OF ANY KIND
157 | WHATSOEVER. STREAM.IO DOES NOT MAKE, AND HEREBY DISCLAIMS, ANY WARRANTY OF ANY
158 | KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING WITHOUT
159 | LIMITATION, THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
160 | PURPOSE, TITLE, NON-INFRINGEMENT OF THIRD-PARTY RIGHTS, RESULTS, EFFORTS,
161 | QUALITY OR QUIET ENJOYMENT. STREAM.IO DOES NOT WARRANT THAT THE SOFTWARE IS
162 | ERROR-FREE, WILL FUNCTION WITHOUT INTERRUPTION, WILL MEET ANY SPECIFIC NEED
163 | THAT CUSTOMER HAS, THAT ALL DEFECTS WILL BE CORRECTED OR THAT IT IS
164 | SUFFICIENTLY DOCUMENTED TO BE USABLE BY CUSTOMER. TO THE EXTENT THAT STREAM.IO
165 | MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, THE SCOPE AND
166 | DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW.
167 | CUSTOMER ACKNOWLEDGES THAT IT HAS RELIED ON NO WARRANTIES OTHER THAN THE
168 | EXPRESS WARRANTIES IN THIS AGREEMENT.
169 |
170 | 13. LIMITATION OF LIABILITY. TO THE FULLEST EXTENT PERMISSIBLE BY LAW, STREAM.IO’S
171 | TOTAL LIABILITY FOR ALL DAMAGES ARISING OUT OF OR RELATED TO THE SOFTWARE OR
172 | THIS AGREEMENT, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE,
173 | SHALL NOT EXCEED $100. IN NO EVENT WILL STREAM.IO BE LIABLE FOR ANY INDIRECT,
174 | CONSEQUENTIAL, EXEMPLARY, PUNITIVE, SPECIAL OR INCIDENTAL DAMAGES OF ANY KIND
175 | WHATSOEVER, INCLUDING ANY LOST DATA AND LOST PROFITS, ARISING FROM OR RELATING
176 | TO THE SOFTWARE EVEN IF STREAM.IO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
177 | DAMAGES. CUSTOMER ACKNOWLEDGES THAT THIS PROVISION REFLECTS THE AGREED UPON
178 | ALLOCATION OF RISK FOR THIS AGREEMENT AND THAT STREAM.IO WOULD NOT ENTER INTO
179 | THIS AGREEMENT WITHOUT THESE LIMITATIONS ON ITS LIABILITY.
180 |
181 | 14. General. Customer may not assign or transfer this Agreement, by operation of
182 | law or otherwise, or any of its rights under this Agreement (including the
183 | license rights granted to Customer) to any third party without Stream.io’s
184 | prior written consent, which consent will not be unreasonably withheld or
185 | delayed. Stream.io may assign this Agreement, without consent, including, but
186 | limited to, affiliate or any successor to all or substantially all its business
187 | or assets to which this Agreement relates, whether by merger, sale of assets,
188 | sale of stock, reorganization or otherwise. Any attempted assignment or
189 | transfer in violation of the foregoing will be null and void. Stream.io shall
190 | not be liable hereunder by reason of any failure or delay in the performance of
191 | its obligations hereunder for any cause which is beyond the reasonable control.
192 | All notices, consents, and approvals under this Agreement must be delivered in
193 | writing by courier, by electronic mail, or by certified or registered mail,
194 | (postage prepaid and return receipt requested) to the other party at the
195 | address set forth in the customer agreement between Stream.io and Customer and
196 | will be effective upon receipt or when delivery is refused. This Agreement will
197 | be governed by and interpreted in accordance with the laws of the State of
198 | Colorado, without reference to its choice of laws rules. The United Nations
199 | Convention on Contracts for the International Sale of Goods does not apply to
200 | this Agreement. Any action or proceeding arising from or relating to this
201 | Agreement shall be brought in a federal or state court in Denver, Colorado, and
202 | each party irrevocably submits to the jurisdiction and venue of any such court
203 | in any such action or proceeding. All waivers must be in writing. Any waiver or
204 | failure to enforce any provision of this Agreement on one occasion will not be
205 | deemed a waiver of any other provision or of such provision on any other
206 | occasion. If any provision of this Agreement is unenforceable, such provision
207 | will be changed and interpreted to accomplish the objectives of such provision
208 | to the greatest extent possible under applicable law and the remaining
209 | provisions will continue in full force and effect. Customer shall not violate
210 | any applicable law, rule or regulation, including those regarding the export of
211 | technical data. The headings of Sections of this Agreement are for convenience
212 | and are not to be used in interpreting this Agreement. As used in this
213 | Agreement, the word “including” means “including but not limited to.” This
214 | Agreement (including all exhibits and attachments) constitutes the entire
215 | agreement between the parties regarding the subject hereof and supersedes all
216 | prior or contemporaneous agreements, understandings and communication, whether
217 | written or oral. This Agreement may be amended only by a written document
218 | signed by both parties. The terms of any purchase order or similar document
219 | submitted by Customer to Stream.io will have no effect.
220 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ## [4.28.0](https://github.com/GetStream/stream-chat-python/compare/v4.27.0...v4.28.0) (2025-11-13)
6 |
7 |
8 | ### Features
9 |
10 | * add message_timestamp option to MarkUnread ([#211](https://github.com/GetStream/stream-chat-python/issues/211)) ([1e239cd](https://github.com/GetStream/stream-chat-python/commit/1e239cd0688027442e169c968e63ef21e1a3c018))
11 |
12 | ## [4.27.0](https://github.com/GetStream/stream-chat-python/compare/v4.26.0...v4.27.0) (2025-11-12)
13 |
14 |
15 | ### Features
16 |
17 | * add filter tags to channels ([#210](https://github.com/GetStream/stream-chat-python/issues/210)) ([85d7a38](https://github.com/GetStream/stream-chat-python/commit/85d7a38fb99067259a1944956aa0c5eed285f909))
18 | * add hide_history_before option for adding members ([#208](https://github.com/GetStream/stream-chat-python/issues/208)) ([0c96e70](https://github.com/GetStream/stream-chat-python/commit/0c96e703c7365b6abad4710a684e0e9aaada8790))
19 | * Added Delete for me support on behalf of a user ([#204](https://github.com/GetStream/stream-chat-python/issues/204)) ([c911a8b](https://github.com/GetStream/stream-chat-python/commit/c911a8b3126b74ff2104edb4be373a9c8465d179))
20 | * support members_template property for campaigns ([#209](https://github.com/GetStream/stream-chat-python/issues/209)) ([21997ee](https://github.com/GetStream/stream-chat-python/commit/21997ee97c64f496e71f28de5c9aeb225856254f))
21 |
22 |
23 | ### Bug Fixes
24 |
25 | * failing webhook and blocklist tests ([#207](https://github.com/GetStream/stream-chat-python/issues/207)) ([bd46569](https://github.com/GetStream/stream-chat-python/commit/bd46569c2d59572b2ca3198a876b5f8b7debd8cb))
26 |
27 | ## [4.26.0](https://github.com/GetStream/stream-chat-python/compare/v4.25.0...v4.26.0) (2025-07-08)
28 |
29 | ## [4.25.0](https://github.com/GetStream/stream-chat-python/compare/v4.24.0...v4.25.0) (2025-06-18)
30 |
31 |
32 | ### Bug Fixes
33 |
34 | * add block user methods ([#199](https://github.com/GetStream/stream-chat-python/issues/199)) ([6364604](https://github.com/GetStream/stream-chat-python/commit/6364604e6ce17491e66c7b2e68b197e5de0ec25c))
35 | * make sure we don't have left over users and channels ([#197](https://github.com/GetStream/stream-chat-python/issues/197)) ([564e947](https://github.com/GetStream/stream-chat-python/commit/564e94741a7069e26670c57bb3183e41db1c955e))
36 |
37 | ## [4.24.0](https://github.com/GetStream/stream-chat-python/compare/v4.23.0...v4.24.0) (2025-04-07)
38 |
39 | ## [4.23.0](https://github.com/GetStream/stream-chat-python/compare/v4.22.0...v4.23.0) (2025-03-11)
40 |
41 | ## [4.22.0](https://github.com/GetStream/stream-chat-python/compare/v4.21.0...v4.22.0) (2025-02-18)
42 |
43 | ## [4.21.0](https://github.com/GetStream/stream-chat-python/compare/v4.20.0...v4.21.0) (2025-02-11)
44 |
45 | ## [4.20.0](https://github.com/GetStream/stream-chat-python/compare/v4.19.0...v4.20.0) (2024-12-07)
46 |
47 | ## [4.19.0](https://github.com/GetStream/stream-chat-python/compare/v4.18.0...v4.19.0) (2024-09-05)
48 |
49 | ## [4.18.0](https://github.com/GetStream/stream-chat-python/compare/v4.17.0...v4.18.0) (2024-07-03)
50 |
51 |
52 | ### Features
53 |
54 | * Add support for mark_unread msg or thread ([#172](https://github.com/GetStream/stream-chat-python/issues/172)) ([1981ab7](https://github.com/GetStream/stream-chat-python/commit/1981ab7e75dcbf5d13ef105dfae50d46a8e39d70))
55 |
56 | ## [4.17.0](https://github.com/GetStream/stream-chat-python/compare/v4.16.0...v4.17.0) (2024-05-29)
57 |
58 |
59 | ### Features
60 |
61 | * add support for undeleting messages ([#162](https://github.com/GetStream/stream-chat-python/issues/162)) ([c1da19d](https://github.com/GetStream/stream-chat-python/commit/c1da19d3915c455f9c0cf37434ffbeac4e9a09b0))
62 |
63 | ## [4.16.0](https://github.com/GetStream/stream-chat-python/compare/v4.15.0...v4.16.0) (2024-05-17)
64 |
65 |
66 | ### Features
67 |
68 | * new API endpoint query_message_history ([#168](https://github.com/GetStream/stream-chat-python/issues/168)) ([f3f4c9f](https://github.com/GetStream/stream-chat-python/commit/f3f4c9fe1ad94fd11065bf742664fb69339df757))
69 |
70 | ## [4.15.0](https://github.com/GetStream/stream-chat-python/compare/v4.14.0...v4.15.0) (2024-03-19)
71 |
72 |
73 | ### Features
74 |
75 | * support all_users & all_sender_channels for segment ([#164](https://github.com/GetStream/stream-chat-python/issues/164)) ([7d57dcc](https://github.com/GetStream/stream-chat-python/commit/7d57dcc6e603863140588f5e02927a13e4c0f595))
76 |
77 | ## [4.14.0](https://github.com/GetStream/stream-chat-python/compare/v4.13.0...v4.14.0) (2024-03-08)
78 |
79 |
80 | ### Features
81 |
82 | * add support for show_deleted_message in getMessage ([#161](https://github.com/GetStream/stream-chat-python/issues/161)) ([e42aa94](https://github.com/GetStream/stream-chat-python/commit/e42aa94fe8aac489a33f288479c08e5f31d7e6d8))
83 |
84 | ## [4.13.0](https://github.com/GetStream/stream-chat-python/compare/v4.12.1...v4.13.0) (2024-03-01)
85 |
86 | ### [4.12.1](https://github.com/GetStream/stream-chat-python/compare/v4.12.0...v4.12.1) (2024-02-27)
87 |
88 | ## [4.12.0](https://github.com/GetStream/stream-chat-python/compare/v4.11.0...v4.12.0) (2024-02-23)
89 |
90 | ## [4.11.0](https://github.com/GetStream/stream-chat-python/compare/v4.10.0...v4.11.0) (2023-11-23)
91 |
92 |
93 | ### Features
94 |
95 | * Add type field support on create_blocklist() ([#149](https://github.com/GetStream/stream-chat-python/issues/149)) ([786a50c](https://github.com/GetStream/stream-chat-python/commit/786a50cf1b4d56071a11a5d3bf91fe5e0b8fba75))
96 |
97 | ## [4.10.0](https://github.com/GetStream/stream-chat-python/compare/v4.9.0...v4.10.0) (2023-10-26)
98 |
99 |
100 | ### Features
101 |
102 | * sns ([#147](https://github.com/GetStream/stream-chat-python/issues/147)) ([616206e](https://github.com/GetStream/stream-chat-python/commit/616206ec62ab96a3d2e4f6468ce17888ccd3fbb9))
103 |
104 | ## [4.9.0](https://github.com/GetStream/stream-chat-python/compare/v4.8.0...v4.9.0) (2023-07-20)
105 |
106 |
107 | ### Features
108 |
109 | * pending messages ([#143](https://github.com/GetStream/stream-chat-python/issues/143)) ([c9445d3](https://github.com/GetStream/stream-chat-python/commit/c9445d32c7c6759b49c02632078783052e1cc7b6))
110 |
111 | ## [4.8.0](https://github.com/GetStream/stream-chat-python/compare/v4.7.0...v4.8.0) (2023-05-03)
112 |
113 |
114 | ### Features
115 |
116 | * add multi user mute/unmute ([#140](https://github.com/GetStream/stream-chat-python/issues/140)) ([2a9ebd7](https://github.com/GetStream/stream-chat-python/commit/2a9ebd7d9d7b20445e1040467737b37b67900e2b))
117 |
118 | ## [4.7.0](https://github.com/GetStream/stream-chat-python/compare/v4.6.0...v4.7.0) (2023-03-29)
119 |
120 |
121 | ### Features
122 |
123 | * enable creation of channels with query options ([#137](https://github.com/GetStream/stream-chat-python/issues/137)) ([e33ae40](https://github.com/GetStream/stream-chat-python/commit/e33ae40d2680ad0a16ed84cdc582031d18e23c6f))
124 |
125 | ## [4.6.0](https://github.com/GetStream/stream-chat-python/compare/v4.5.0...v4.6.0) (2022-11-08)
126 |
127 |
128 | ### Features
129 |
130 | * add restore_users ([#134](https://github.com/GetStream/stream-chat-python/issues/134)) ([0d57324](https://github.com/GetStream/stream-chat-python/commit/0d573241b6d4942c60def4fb076f33a662ad4ae5))
131 |
132 | ## [4.5.0](https://github.com/GetStream/stream-chat-python/compare/v4.4.2...v4.5.0) (2022-09-07)
133 |
134 |
135 | ### Features
136 |
137 | * add gdpr flag for campaign deletion ([#132](https://github.com/GetStream/stream-chat-python/issues/132)) ([5dc0c2e](https://github.com/GetStream/stream-chat-python/commit/5dc0c2e4bda97a5ee8f67c28eca89244bfeb5333))
138 |
139 | ### [4.4.2](https://github.com/GetStream/stream-chat-python/compare/v4.4.1...v4.4.2) (2022-08-22)
140 |
141 | ### [4.4.1](https://github.com/GetStream/stream-chat-python/compare/v4.4.0...v4.4.1) (2022-08-17)
142 |
143 | ## [4.4.0](https://github.com/GetStream/stream-chat-python/compare/v4.3.0...v4.4.0) (2022-08-17)
144 |
145 |
146 | ### Features
147 |
148 | * add new campaign endpoints ([#123](https://github.com/GetStream/stream-chat-python/issues/123)) ([a63992f](https://github.com/GetStream/stream-chat-python/commit/a63992faf1cd1ca530cc1b290e2afd28f451a06e))
149 |
150 |
151 | ### Bug Fixes
152 |
153 | * handle broken rate limit as best effort ([#126](https://github.com/GetStream/stream-chat-python/issues/126)) ([683529d](https://github.com/GetStream/stream-chat-python/commit/683529d6684454434580c8d6768e7dd541bfa86c))
154 |
155 | ## [4.3.0](https://github.com/GetStream/stream-chat-python/compare/v4.2.2...v4.3.0) (2022-05-30)
156 |
157 |
158 | ### Features
159 |
160 | * **import:** add import endpoints ([#121](https://github.com/GetStream/stream-chat-python/issues/121)) ([44d1a6a](https://github.com/GetStream/stream-chat-python/commit/44d1a6a9b3bd9c29cbbcc8ac3500598de01f1897))
161 |
162 | ### [4.2.2](https://github.com/GetStream/stream-chat-python/compare/v4.2.1...v4.2.2) (2022-05-10)
163 |
164 |
165 | ### Features
166 |
167 | * **verify_webhook:** bytes as signature header ([#119](https://github.com/GetStream/stream-chat-python/issues/119)) ([1658d0b](https://github.com/GetStream/stream-chat-python/commit/1658d0b2374a7edc2b9c006f4af3171e3ee85965))
168 |
169 | ### [4.2.1](https://github.com/GetStream/stream-chat-python/compare/v4.2.0...v4.2.1) (2022-04-21)
170 |
171 | ### Features
172 |
173 | * add documentation to public methods ([#111](https://github.com/GetStream/stream-chat-python/issues/111)) ([758faa4](https://github.com/GetStream/stream-chat-python/commit/758faa475cd801c3fdecc1759b30b72629fc7786))
174 |
175 | ## [4.2.0](https://github.com/GetStream/stream-chat-python/compare/v4.1.0...v4.2.0) (2022-04-08)
176 |
177 |
178 | ### Features
179 |
180 | * add custom event endpoint ([#103](https://github.com/GetStream/stream-chat-python/issues/103)) ([b619130](https://github.com/GetStream/stream-chat-python/commit/b61913091409f4aba2f8fa2b0f2fe97ed6da9ab0))
181 | * add device fields ([#107](https://github.com/GetStream/stream-chat-python/issues/107)) ([3b582f5](https://github.com/GetStream/stream-chat-python/commit/3b582f51e5b9f81e618f0a6a5e81356bebc9bb3f))
182 | * add options to export channel ([#100](https://github.com/GetStream/stream-chat-python/issues/100)) ([82f07ff](https://github.com/GetStream/stream-chat-python/commit/82f07ff5364dd1a5ee8be1b5f983cf156dcc84f3))
183 | * add provider management ([#106](https://github.com/GetStream/stream-chat-python/issues/106)) ([9710a90](https://github.com/GetStream/stream-chat-python/commit/9710a9099d6c3d2e73a347aada07aabf4f4515d4))
184 | * set keepalive timeout to 59s ([#101](https://github.com/GetStream/stream-chat-python/issues/101)) ([e7707ea](https://github.com/GetStream/stream-chat-python/commit/e7707ea9d500e3eb48dac38f08b2c8c33db456d3))
185 | * swappable http client ([#102](https://github.com/GetStream/stream-chat-python/issues/102)) ([1343f43](https://github.com/GetStream/stream-chat-python/commit/1343f43caf82e415fccc6f82e8db1f42a0ca969b))
186 | * **upser_user:** deprecated update_user in favor of upsert_user ([#109](https://github.com/GetStream/stream-chat-python/issues/109)) ([0b2a8aa](https://github.com/GetStream/stream-chat-python/commit/0b2a8aa627f386da8bcaa6a2b11e8ebe91f36f22))
187 |
188 |
189 | ### Bug Fixes
190 |
191 | * report_id is is a uuid ([#105](https://github.com/GetStream/stream-chat-python/issues/105)) ([c4a5e24](https://github.com/GetStream/stream-chat-python/commit/c4a5e24fc658a9cebcf17ed9e3200b10f6caf94b))
192 |
193 | ## [4.1.0](https://github.com/GetStream/stream-chat-python/compare/v4.0.0...v4.1.0) (2022-01-20)
194 |
195 |
196 | ### Features
197 |
198 | * add full feature parity ([#98](https://github.com/GetStream/stream-chat-python/issues/98)) ([02ea360](https://github.com/GetStream/stream-chat-python/commit/02ea3602df45ec6ff0a34f27cc3b2ecdb8c33faa))
199 |
200 | ## [4.0.0](https://github.com/GetStream/stream-chat-python/compare/v3.17.0...v4.0.0) (2022-01-18)
201 |
202 |
203 | ### Features
204 |
205 | * Add ratelimit info to response object, Plus remove Python 3.6 support ([c472c0b](https://github.com/GetStream/stream-chat-python/commit/c472c0b7c43d7741a092d29920a1d31a413e9dc0))
206 | > The returned response objects are instances of `StreamResponse` class. It inherits from `dict`, so it's fully backward compatible. Additionally, it provides other benefits such as rate limit information (`resp.rate_limit()`), response headers (`resp.headers()`) or status code (`resp.status_code()`).
207 |
208 | ## [3.17.0](https://github.com/GetStream/stream-chat-python/compare/v3.16.0...v3.17.0) (2022-01-06)
209 |
210 | - Add options support into channel truncate
211 | - Add options support into add members
212 | - Add type hints
213 | - Add internal flag report query and review endpoint support
214 | - Improve tests and docs
215 |
216 | ## Nov 15, 2021 - 3.16.0
217 |
218 | - Add support for assign_roles feature
219 |
220 | ## Nov 12, 2021 - 3.15.0
221 |
222 | - Add update message partial support
223 | - Add pin message and unpin message helpers
224 |
225 | ## Nov 1, 2021 - 3.14.0
226 |
227 | - Add support for async endpoints
228 | - get_task
229 | - delete_users
230 | - delete_channels
231 | - Add support for permissions v2
232 | - Add convenience helpers for shadow ban
233 | - Use json helper for unmarshal response in async
234 | - Add support for Python 3.10
235 |
236 | ## Sep 14, 2021 - 3.13.1
237 |
238 | - Tweak connection pool configuration for idle timeouts
239 |
240 | ## Sep 7, 2021 - 3.13.0
241 |
242 | - Add optional message into member updates
243 |
244 | ## Aug 31, 2021 - 3.12.2
245 |
246 | - Use post instead of get requests in query channels
247 |
248 | ## Aug 24, 2021 - 3.12.1
249 |
250 | - Add namespace for ease of use and consistency in campaign update endpoints.
251 |
252 | ## Aug 23, 2021 - 3.12.0
253 |
254 | - Add support for channel exports.
255 |
256 | ## Aug 20, 2021 - 3.11.2
257 |
258 | - Set base url to edge, there is no need to set a region anymore.
259 | - Fix file uploads from a local file.
260 |
261 | ## Aug 19, 2021 - 3.11.1
262 |
263 | - Fix base path for listing campaigns
264 |
265 | ## Jul 5, 2021 - 3.11.0
266 |
267 | - Update existing permission related API (/roles and /permissions endpoints)
268 |
269 | ## Jul 2, 2021 - 3.10.0
270 |
271 | - Add support for campaign API (early alpha, can change)
272 |
273 | ## Jul 1, 2021 - 3.9.0
274 |
275 | - Add support for search improvements (i.e. next, prev, sorting, more filters)
276 |
277 | ## May 26, 2021 - 3.8.0
278 |
279 | - Add query_message_flags endpoint support
280 | - Add token revoke support
281 | - Run CI sequentially for different Python versions
282 | - Drop codecov
283 |
284 | ## March 10, 2021 - 3.7.0
285 |
286 | - Add get_rate_limits endpoint support
287 |
288 | ## March 10, 2021 - 3.6.0
289 |
290 | - Add custom permission/role lifecycle endpoints
291 |
292 | ## February 26, 2021 - 3.5.0
293 |
294 | - Support additional claims for jwt token generation
295 |
296 | ## February 22, 2021 - 3.4.0
297 |
298 | - Add channel mute/unmute for a user
299 |
300 | ## February 22, 2021 - 3.3.0
301 |
302 | - Add options to send message
303 | - for example to silence push notification on this message: `channel.send_message({"text": "hi"}, user_id, skip_push=True)`
304 |
305 | ## February 9, 2021 - 3.2.1
306 |
307 | - Drop brotli dependency in async, causes install issues in Darwin
308 | - upstream needs updates
309 |
310 | ## February 8, 2021 - 3.2.0
311 |
312 | - Add channel partial update
313 |
314 | ## January 22, 2021 - 3.1.1
315 |
316 | - Bump pyjwt to 2.x
317 |
318 | ## January 5, 2021 - 3.1.0
319 |
320 | - Add check SQS helper
321 |
322 | ## December 17, 2020 - 3.0.1
323 |
324 | - Use f strings internally
325 | - Use github actions and CI requirements to setup
326 |
327 | ## December 9, 2020 - 3.0.0
328 |
329 | - Add async version of the client
330 | - Make double invite accept/reject noop
331 |
332 | ## November 12, 2020 - 2.0.0
333 |
334 | - Drop Python 3.5 and add 3.9
335 |
336 | ## November 12, 2020 - 1.7.1
337 |
338 | - Normalize sort parameter in query endpoints
339 |
340 | ## October 20, 2020 - 1.7.0
341 |
342 | - Added support for blocklists
343 |
344 | ## September 24, 2020 - 1.6.0
345 |
346 | - Support for creating custom commands
347 |
348 | ## September 10, 2020 - 1.5.0
349 |
350 | - Support for query members
351 | - Prefer literals over constructors to simplify code
352 |
353 | ## July 23, 2020 - 1.4.0
354 |
355 | - Support timeout while muting a user
356 |
357 | ## June 23, 2020 - 1.3.1
358 |
359 | - Set a generic user agent for file/image get to prevent bot detection
360 |
361 | ## Apr 28, 2020 - 1.3.0
362 |
363 | - Drop six dependency
364 | - `verify_webhook` is affected and expects bytes for body parameter
365 | - Add 3.8 support
366 |
367 | ## Apr 17, 2020 - 1.2.2
368 |
369 | - Fix version number
370 |
371 | ## Apr 17, 2020 - 1.2.1
372 |
373 | - Allow to override client.base_url
374 |
375 | ## Mar 29, 2020 - 1.2.0
376 |
377 | - Add support for invites
378 |
379 | ## Mar 29, 2020 - 1.1.1
380 |
381 | - Fix client.create_token: returns a string now
382 |
383 | ## Mar 3, 2020 - 1.1.
384 |
385 | - Add support for client.get_message
386 |
387 | ## Nov 7, 2019 - 1.0.2
388 |
389 | - Bump crypto requirements
390 |
391 | ## Oct 21th, 2019 - 1.0.1
392 |
393 | - Fixed app update method parameter passing
394 |
395 | ## Oct 19th, 2019 - 1.0.0
396 |
397 | - Added support for user partial update endpoint
398 |
--------------------------------------------------------------------------------
/stream_chat/base/channel.py:
--------------------------------------------------------------------------------
1 | import abc
2 | from typing import Any, Awaitable, Dict, Iterable, List, Optional, Union
3 |
4 | from stream_chat.base.client import StreamChatInterface
5 | from stream_chat.base.exceptions import StreamChannelException
6 | from stream_chat.types.stream_response import StreamResponse
7 |
8 |
9 | class ChannelInterface(abc.ABC):
10 | def __init__(
11 | self,
12 | client: StreamChatInterface,
13 | channel_type: str,
14 | channel_id: str = None,
15 | custom_data: Dict = None,
16 | ):
17 | self.channel_type = channel_type
18 | self.id = channel_id
19 | self.client = client
20 | self.custom_data = custom_data or {}
21 |
22 | @property
23 | def url(self) -> str:
24 | if self.id is None:
25 | raise StreamChannelException("channel does not have an id")
26 | return f"channels/{self.channel_type}/{self.id}"
27 |
28 | @property
29 | def cid(self) -> str:
30 | if self.id is None:
31 | raise StreamChannelException("channel does not have an id")
32 | return f"{self.channel_type}:{self.id}"
33 |
34 | @abc.abstractmethod
35 | def send_message(
36 | self, message: Dict, user_id: str, **options: Any
37 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
38 | """
39 | Send a message to this channel
40 |
41 | :param message: the Message object
42 | :param user_id: the ID of the user that created the message
43 | :return: the Server Response
44 | """
45 | pass
46 |
47 | @abc.abstractmethod
48 | def send_event(
49 | self, event: Dict, user_id: str
50 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
51 | """
52 | Send an event on this channel
53 |
54 | :param event: event data, ie {type: 'message.read'}
55 | :param user_id: the ID of the user sending the event
56 | :return: the Server Response
57 | """
58 | pass
59 |
60 | @abc.abstractmethod
61 | def send_reaction(
62 | self, message_id: str, reaction: Dict, user_id: str
63 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
64 | """
65 | Send a reaction about a message
66 |
67 | :param message_id: the message id
68 | :param reaction: the reaction object, ie {type: 'love'}
69 | :param user_id: the ID of the user that created the reaction
70 | :return: the Server Response
71 | """
72 | pass
73 |
74 | @abc.abstractmethod
75 | def delete_reaction(
76 | self, message_id: str, reaction_type: str, user_id: str
77 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
78 | """
79 | Delete a reaction by user and type
80 |
81 | :param message_id: the id of the message from which te remove the reaction
82 | :param reaction_type: the type of reaction that should be removed
83 | :param user_id: the id of the user
84 | :return: the Server Response
85 | """
86 | pass
87 |
88 | @abc.abstractmethod
89 | def create(
90 | self, user_id: str, **options: Any
91 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
92 | """
93 | Create the channel
94 |
95 | :param user_id: the ID of the user creating this channel
96 | :return:
97 | """
98 | pass
99 |
100 | @abc.abstractmethod
101 | def get_messages(
102 | self, message_ids: List[str]
103 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
104 | """
105 | Gets many messages
106 |
107 | :param message_ids: list of message ids to returns
108 | :return:
109 | """
110 | pass
111 |
112 | @abc.abstractmethod
113 | def query(self, **options: Any) -> Union[StreamResponse, Awaitable[StreamResponse]]:
114 | """
115 | Query the API for this channel, get messages, members or other channel fields
116 |
117 | :param options: the query options, check docs on https://getstream.io/chat/docs/
118 | :return: Returns a query response
119 | """
120 | pass
121 |
122 | @abc.abstractmethod
123 | def query_members(
124 | self, filter_conditions: Dict, sort: List[Dict] = None, **options: Any
125 | ) -> Union[List[Dict], Awaitable[List[Dict]]]:
126 | """
127 | Query the API for this channel to filter, sort and paginate its members efficiently.
128 |
129 | :param filter_conditions: filters, checks docs on https://getstream.io/chat/docs/
130 | :param sort: sorting field and direction slice, check docs on https://getstream.io/chat/docs/
131 | :param options: pagination or members based channel searching details
132 | :return: Returns members response
133 |
134 | eg.
135 | channel.query_members(filter_conditions={"name": "tommaso"},
136 | sort=[{"field": "created_at", "direction": -1}],
137 | offset=0,
138 | limit=10)
139 | """
140 | pass
141 |
142 | @abc.abstractmethod
143 | def update(
144 | self, channel_data: Dict, update_message: Dict = None
145 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
146 | """
147 | Edit the channel's custom properties
148 |
149 | :param channel_data: the object to update the custom properties of this channel with
150 | :param update_message: optional update message
151 | :return: The server response
152 | """
153 | pass
154 |
155 | @abc.abstractmethod
156 | def update_partial(
157 | self, to_set: Dict = None, to_unset: Iterable[str] = None
158 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
159 | """
160 | Update channel partially
161 |
162 | :param to_set: a dictionary of key/value pairs to set or to override
163 | :param to_unset: a list of keys to clear
164 | """
165 | pass
166 |
167 | @abc.abstractmethod
168 | def delete(
169 | self, hard: bool = False
170 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
171 | """
172 | Delete the channel. Messages are permanently removed.
173 |
174 | :return: The server response
175 | """
176 | pass
177 |
178 | @abc.abstractmethod
179 | def truncate(
180 | self, **options: Any
181 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
182 | """
183 | Removes all messages from the channel
184 |
185 | :param options: the query options, check docs on https://getstream.io/chat/docs/python/channel_delete/?language=python#truncating-a-channel
186 | :return: The server response
187 | """
188 | pass
189 |
190 | @abc.abstractmethod
191 | def add_members(
192 | self, members: Iterable[Dict], message: Dict = None, **options: Any
193 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
194 | """
195 | Adds members to the channel
196 |
197 | :param members: member objects to add
198 | :param message: An optional to show
199 | :param options: additional options such as hide_history or hide_history_before
200 | :return:
201 | """
202 | pass
203 |
204 | @abc.abstractmethod
205 | def assign_roles(
206 | self, members: Iterable[Dict], message: Dict = None
207 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
208 | """
209 | Assigns new roles to specified channel members
210 |
211 | :param members: member objects with role information
212 | :param message: An optional to show
213 | :return:
214 | """
215 | pass
216 |
217 | @abc.abstractmethod
218 | def invite_members(
219 | self, user_ids: Iterable[str], message: Dict = None
220 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
221 | """
222 | invite members to the channel
223 |
224 | :param user_ids: user IDs to invite
225 | :param message: An optional to show
226 | :return:
227 | """
228 | pass
229 |
230 | @abc.abstractmethod
231 | def add_moderators(
232 | self, user_ids: Iterable[str], message: Dict = None
233 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
234 | """
235 | Adds moderators to the channel
236 |
237 | :param user_ids: user IDs to add as moderators
238 | :param message: An optional to show
239 | :return:
240 | """
241 | pass
242 |
243 | @abc.abstractmethod
244 | def remove_members(
245 | self, user_ids: Iterable[str], message: Dict = None
246 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
247 | """
248 | Remove members from the channel
249 |
250 | :param user_ids: user IDs to remove from the member list
251 | :param message: An optional to show
252 | :return:
253 | """
254 | pass
255 |
256 | @abc.abstractmethod
257 | def demote_moderators(
258 | self, user_ids: Iterable[str], message: Dict = None
259 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
260 | """
261 | Demotes moderators from the channel
262 |
263 | :param user_ids: user IDs to demote
264 | :param message: An optional to show
265 | :return:
266 | """
267 | pass
268 |
269 | @abc.abstractmethod
270 | def mark_read(
271 | self, user_id: str, **data: Any
272 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
273 | """
274 | Send the mark read event for this user, only works if the `read_events` setting is enabled
275 |
276 | :param user_id: the user ID for the event
277 | :param data: additional data, ie {"message_id": last_message_id}
278 | :return: The server response
279 | """
280 | pass
281 |
282 | @abc.abstractmethod
283 | def mark_unread(
284 | self, user_id: str, **data: Any
285 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
286 | """
287 | Marks channel as unread from a specific message, thread, or timestamp, if thread_id is provided in data
288 | a thread will be searched, otherwise a message.
289 |
290 | :param user_id: the user ID for the event
291 | :param data: additional data, ie {"message_id": last_message_id}, {"thread_id": thread_id}, or {"message_timestamp": timestamp}
292 | :return: The server response
293 | """
294 | pass
295 |
296 | @abc.abstractmethod
297 | def get_replies(
298 | self, parent_id: str, **options: Any
299 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
300 | """
301 | List the message replies for a parent message
302 |
303 | :param parent_id: The message parent id, ie the top of the thread
304 | :param options: Pagination params, ie {limit:10, id_lte: 10}
305 | :return: A response with a list of messages
306 | """
307 | pass
308 |
309 | @abc.abstractmethod
310 | def get_reactions(
311 | self, message_id: str, **options: Any
312 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
313 | """
314 | List the reactions, supports pagination
315 |
316 | :param message_id: The message id
317 | :param options: Pagination params, ie {"limit":10, "id_lte": 10}
318 | :return: A response with a list of reactions
319 | """
320 | pass
321 |
322 | @abc.abstractmethod
323 | def ban_user(
324 | self, target_id: str, **options: Any
325 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
326 | """
327 | Bans a user from this channel
328 |
329 | :param target_id: the ID of the user to ban
330 | :param options: additional ban options, ie {"timeout": 3600, "reason": "offensive language is not allowed here"}
331 | :return: The server response
332 | """
333 | pass
334 |
335 | @abc.abstractmethod
336 | def unban_user(
337 | self, target_id: str, **options: Any
338 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
339 | """
340 | Removes the ban for a user on this channel
341 |
342 | :param target_id: the ID of the user to unban
343 | :return: The server response
344 | """
345 | pass
346 |
347 | @abc.abstractmethod
348 | def accept_invite(
349 | self, user_id: str, **data: Any
350 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
351 | """
352 | Accepts an invitation to this channel.
353 | """
354 | pass
355 |
356 | @abc.abstractmethod
357 | def reject_invite(
358 | self, user_id: str, **data: Any
359 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
360 | """
361 | Rejects an invitation to this channel.
362 | """
363 | pass
364 |
365 | @abc.abstractmethod
366 | def send_file(
367 | self, url: str, name: str, user: Dict, content_type: str = None
368 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
369 | """
370 | Uploads a file.
371 | This functionality defaults to using the Stream CDN. If you would like, you can
372 | easily change the logic to upload to your own CDN of choice.
373 | """
374 | pass
375 |
376 | @abc.abstractmethod
377 | def send_image(
378 | self, url: str, name: str, user: Dict, content_type: str = None
379 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
380 | """
381 | Uploads an image.
382 | Stream supported image types are: image/bmp, image/gif, image/jpeg, image/png, image/webp,
383 | image/heic, image/heic-sequence, image/heif, image/heif-sequence, image/svg+xml.
384 | You can set a more restrictive list for your application if needed.
385 | The maximum file size is 100MB.
386 | """
387 | pass
388 |
389 | @abc.abstractmethod
390 | def delete_file(self, url: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
391 | """
392 | Deletes a file by file url.
393 | """
394 | pass
395 |
396 | @abc.abstractmethod
397 | def delete_image(
398 | self, url: str
399 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
400 | """
401 | Deletes an image by image url.
402 | """
403 | pass
404 |
405 | @abc.abstractmethod
406 | def hide(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
407 | """
408 | Removes a channel from query channel requests for that user until a new message is added.
409 | Use `show` to cancel this operation.
410 | """
411 | pass
412 |
413 | @abc.abstractmethod
414 | def show(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
415 | """
416 | Shows a previously hidden channel.
417 | Use `hide` to hide a channel.
418 | """
419 | pass
420 |
421 | @abc.abstractmethod
422 | def mute(
423 | self, user_id: str, expiration: int = None
424 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
425 | """
426 | Mutes a channel.
427 | Messages added to a muted channel will not trigger push notifications, nor change the
428 | unread count for the users that muted it. By default, mutes stay in place indefinitely
429 | until the user removes it; however, you can optionally set an expiration time. The list
430 | of muted channels and their expiration time is returned when the user connects.
431 | """
432 | pass
433 |
434 | @abc.abstractmethod
435 | def unmute(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
436 | """
437 | Unmutes a channel.
438 | Messages added to a muted channel will not trigger push notifications, nor change the
439 | unread count for the users that muted it. By default, mutes stay in place indefinitely
440 | until the user removes it; however, you can optionally set an expiration time. The list
441 | of muted channels and their expiration time is returned when the user connects.
442 | """
443 | pass
444 |
445 | @abc.abstractmethod
446 | def pin(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
447 | """
448 | Pins a channel
449 | Allows a user to pin the channel (only for themselves)
450 | """
451 | pass
452 |
453 | @abc.abstractmethod
454 | def unpin(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
455 | """
456 | Unpins a channel
457 | Allows a user to unpin the channel (only for themselves)
458 | """
459 | pass
460 |
461 | @abc.abstractmethod
462 | def archive(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
463 | """
464 | Pins a channel
465 | Allows a user to archive the channel (only for themselves)
466 | """
467 | pass
468 |
469 | @abc.abstractmethod
470 | def unarchive(
471 | self, user_id: str
472 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
473 | """
474 | Unpins a channel
475 | Allows a user to unpin the channel (only for themselves)
476 | """
477 | pass
478 |
479 | @abc.abstractmethod
480 | def update_member_partial(
481 | self, user_id: str, to_set: Dict = None, to_unset: Iterable[str] = None
482 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
483 | """
484 | Update channel member partially
485 |
486 | :param to_set: a dictionary of key/value pairs to set or to override
487 | :param to_unset: a list of keys to clear
488 | """
489 | pass
490 |
491 | @abc.abstractmethod
492 | def create_draft(
493 | self, message: Dict, user_id: str
494 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
495 | """
496 | Creates or updates a draft message in a channel.
497 |
498 | :param message: The message object
499 | :param user_id: The ID of the user creating the draft
500 | :return: The Server Response
501 | """
502 | pass
503 |
504 | @abc.abstractmethod
505 | def delete_draft(
506 | self, user_id: str, parent_id: Optional[str] = None
507 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
508 | """
509 | Deletes a draft message from a channel.
510 |
511 | :param user_id: The ID of the user who owns the draft
512 | :param parent_id: Optional ID of the parent message if this is a thread draft
513 | :return: The Server Response
514 | """
515 | pass
516 |
517 | @abc.abstractmethod
518 | def get_draft(
519 | self, user_id: str, parent_id: Optional[str] = None
520 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
521 | """
522 | Retrieves a draft message from a channel.
523 |
524 | :param user_id: The ID of the user who owns the draft
525 | :param parent_id: Optional ID of the parent message if this is a thread draft
526 | :return: The Server Response
527 | """
528 | pass
529 |
530 | @abc.abstractmethod
531 | def add_filter_tags(
532 | self, tags: Iterable[str], message: Dict = None
533 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
534 | """
535 | Adds filter tags to the channel
536 |
537 | :param tags: list of tags to add
538 | :param message: optional system message
539 | :return: The server response
540 | """
541 | pass
542 |
543 | @abc.abstractmethod
544 | def remove_filter_tags(
545 | self, tags: Iterable[str], message: Dict = None
546 | ) -> Union[StreamResponse, Awaitable[StreamResponse]]:
547 | """
548 | Removes filter tags from the channel
549 |
550 | :param tags: list of tags to remove
551 | :param message: optional system message
552 | :return: The server response
553 | """
554 | pass
555 |
556 |
557 | def add_user_id(payload: Dict, user_id: str) -> Dict:
558 | return {**payload, "user": {"id": user_id}}
559 |
--------------------------------------------------------------------------------