Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions examples/native_sync/message_reactions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import os
from typing import Dict, Any
from pubnub.models.consumer.message_actions import PNMessageAction
from pubnub.pnconfiguration import PNConfiguration
from pubnub.pubnub import PubNub


# snippet.init_pubnub
def initialize_pubnub(
publish_key: str,
subscribe_key: str,
user_id: str
) -> PubNub:
"""
Initialize a PubNub instance with the provided configuration.

Args:
publish_key (str): PubNub publish key
subscribe_key (str): PubNub subscribe key
user_id (str): User identifier for PubNub

Returns:
PubNub: Configured PubNub instance ready for publishing and subscribing
"""
pnconfig = PNConfiguration()

# Configure keys with provided values
pnconfig.publish_key = publish_key
pnconfig.subscribe_key = subscribe_key
pnconfig.user_id = user_id

return PubNub(pnconfig)
# snippet.end


# snippet.publish_message
def publish_message(pubnub: PubNub, channel: str, message: Any) -> Dict:
"""
Publish a message to a specific channel.

Args:
pubnub (PubNub): PubNub instance
channel (str): Channel to publish to
message (Any): Message content to publish

Returns:
Dict: Publish operation result containing timetoken
"""
envelope = pubnub.publish().channel(channel).message(message).sync()
return envelope.result
# snippet.end


# snippet.publish_reaction
def publish_reaction(
pubnub: PubNub,
channel: str,
message_timetoken: str,
reaction_type: str,
reaction_value: str,
user_id: str

) -> Dict:
"""
Publish a reaction to a specific message.

Args:
pubnub (PubNub): PubNub instance
channel (str): Channel where the original message was published
message_timetoken (str): Timetoken of the message to react to
reaction_type (str): Type of reaction (e.g. "smile", "thumbs_up")

Returns:
Dict: Reaction publish operation result
"""
message_action = PNMessageAction().create(
type=reaction_type,
value=reaction_value,
message_timetoken=message_timetoken,
user_id=user_id
)
envelope = pubnub.add_message_action().channel(channel).message_action(message_action).sync()

return envelope.result
# snippet.end


# snippet.get_reactions
def get_reactions(pubnub: PubNub, channel: str, start_timetoken: str, end_timetoken: str, limit: str) -> Dict:
"""
Get reactions for a specific message.

Args:
pubnub (PubNub): PubNub instance
channel (str): Channel where the original message was published
start_timetoken (str): Start timetoken of the message to get reactions for
end_timetoken (str): End timetoken of the message to get reactions for
limit (str): Limit the number of reactions to return
Returns:
Dict: Reactions for the message
"""
envelope = pubnub.get_message_actions() \
.channel(channel) \
.start(start_timetoken) \
.end(end_timetoken) \
.limit(limit) \
.sync()
return envelope.result
# snippet.end


# snippet.remove_reaction
def remove_reaction(pubnub: PubNub, channel: str, message_timetoken: str, action_timetoken: str) -> Dict:
"""
Remove a reaction from a specific message.

Args:
pubnub (PubNub): PubNub instance
channel (str): Channel where the original message was published
message_timetoken (str): Timetoken of the message to react to
action_timetoken (str): Timetoken of the reaction to remove
"""
envelope = pubnub.remove_message_action() \
.channel(channel) \
.message_timetoken(message_timetoken) \
.action_timetoken(action_timetoken) \
.sync()
return envelope.result
# snippet.end


def main() -> None:
"""
Main execution function.
"""
# Get configuration from environment variables or use defaults
publish_key = os.getenv('PUBLISH_KEY', 'demo')
subscribe_key = os.getenv('SUBSCRIBE_KEY', 'demo')
user_id = os.getenv('USER_ID', 'example-user')

# snippet.usage_example
# Initialize PubNub instance with configuration
# If environment variables are not set, demo keys will be used
pubnub = initialize_pubnub(
publish_key=publish_key,
subscribe_key=subscribe_key,
user_id=user_id
)

# Channel where all the communication will happen
channel = "my_channel"

# Message that will receive reactions
message = "Hello, PubNub!"

# Step 1: Publish initial message
# The timetoken is needed to add reactions to this specific message
result = publish_message(pubnub, channel, message)
message_timetoken = result.timetoken
assert result.timetoken is not None, "Message publish failed - no timetoken returned"
assert isinstance(result.timetoken, (int, str)) and str(result.timetoken).isnumeric(), "Invalid timetoken format"
print(f"Published message with timetoken: {result.timetoken}")

# Step 2: Add different types of reactions from different users
# First reaction: text-based reaction from guest_1
reaction_type = "text"
reaction_value = "Hello"
first_reaction = publish_reaction(pubnub, channel, message_timetoken, reaction_type, reaction_value, "guest_1")
print(f"Added first reaction {first_reaction.__dict__}")
assert first_reaction is not None, "Reaction publish failed - no result returned"
assert isinstance(first_reaction, PNMessageAction), "Invalid reaction result type"

# Second reaction: emoji-based reaction from guest_2
reaction_type = "emoji"
reaction_value = "👋"
second_reaction = publish_reaction(pubnub, channel, message_timetoken, reaction_type, reaction_value, "guest_2")
print(f"Added second reaction {second_reaction.__dict__}")
assert second_reaction is not None, "Reaction publish failed - no result returned"
assert isinstance(second_reaction, PNMessageAction), "Invalid reaction result type"

# Step 3: Fetch the message with its reactions from history
fetch_result = pubnub.fetch_messages()\
.channels(channel)\
.include_message_actions(True)\
.count(1)\
.sync()

messages = fetch_result.result.channels[channel]
print(f"Fetched message with reactions: {messages[0].__dict__}")
assert len(messages) == 1, "Message not found in history"
assert hasattr(messages[0], 'actions'), "Message actions not included in response"
assert len(messages[0].actions) == 2, "Unexpected number of actions in history"

# Step 4: Retrieve all reactions for the message
# We use a time window around the message timetoken to fetch reactions
# The window is 1000 time units before and after the message
start_timetoken = str(int(message_timetoken) - 1000)
end_timetoken = str(int(message_timetoken) + 1000)
reactions = get_reactions(pubnub, channel, start_timetoken, end_timetoken, "100")
print(f"Reactions found: {len(reactions.actions)}")
assert len(reactions.actions) == 2, "Unexpected number of reactions"

# Step 5: Display and remove each reaction
for reaction in reactions.actions:
print(f" Reaction: {reaction.__dict__}")
# Remove the reaction and confirm removal
remove_reaction(pubnub, channel, reaction.message_timetoken, reaction.action_timetoken)
print(f"Removed reaction {reaction.__dict__}")

# Step 6: Verify reactions were removed
# Fetch reactions again - should be empty now
reactions = get_reactions(pubnub, channel, start_timetoken, end_timetoken, "100")
print(f"Reactions found: {len(reactions.actions)}")
assert len(reactions.actions) == 0, "Unexpected number of reactions"
# snippet.end


if __name__ == '__main__':
main()
56 changes: 56 additions & 0 deletions examples/native_sync/using_tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import time
from pubnub.exceptions import PubNubException
from pubnub.models.consumer.v3.channel import Channel
from pubnub.pubnub import PubNub
from pubnub.pnconfiguration import PNConfiguration

# We are using keyset with Access Manager enabled.
# Admin has superpowers and can grant tokens, access to all channels, etc. Notice admin has secret key.
admin_config = PNConfiguration()
admin_config.publish_key = os.environ.get('PUBLISH_PAM_KEY', 'demo')
admin_config.subscribe_key = os.environ.get('SUBSCRIBE_PAM_KEY', 'demo')
admin_config.secret_key = os.environ.get('SECRET_PAM_KEY', 'demo')
admin_config.uuid = "example_admin"

# User also has the same keyset as admin.
# User has limited access to the channels they are granted access to. Notice user has no secret key.
user_config = PNConfiguration()
user_config.publish_key = os.environ.get('PUBLISH_PAM_KEY', 'demo')
user_config.subscribe_key = os.environ.get('SUBSCRIBE_PAM_KEY', 'demo')
user_config.uuid = "example_user"

admin = PubNub(admin_config)
user = PubNub(user_config)

try:
user.publish().channel("test_channel").message("test message").sync()
except PubNubException as e:
print(f"User cannot publish to test_channel as expected.\nError: {e}")

# admin can grant tokens to users
grant_envelope = admin.grant_token() \
.channels([Channel.id("test_channel").read().write().manage().update().join().delete()]) \
.authorized_uuid("example_user") \
.ttl(1) \
.sync()
assert grant_envelope.status.status_code == 200

token = grant_envelope.result.get_token()
assert token is not None

user.set_token(token)
user.publish().channel("test_channel").message("test message").sync()

# admin can revoke tokens
revoke_envelope = admin.revoke_token(token).sync()
assert revoke_envelope.status.status_code == 200

# We have to wait for the token revoke to propagate.
time.sleep(10)

# user cannot publish to test_channel after token is revoked
try:
user.publish().channel("test_channel").message("test message").sync()
except PubNubException as e:
print(f"User cannot publish to test_channel any more.\nError: {e}")
1 change: 1 addition & 0 deletions pubnub/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class PNOperationType(object):
PNRemoveSpaceUsersOperation = 82
PNFetchUserMembershipsOperation = 85
PNFetchSpaceMembershipsOperation = 86
# NOTE: remember to update PubNub.managers.TelemetryManager.endpoint_name_for_operation() when adding operations


class PNHeartbeatNotificationOptions(object):
Expand Down
24 changes: 24 additions & 0 deletions pubnub/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ def endpoint_name_for_operation(operation_type):
endpoint = {
PNOperationType.PNPublishOperation: 'pub',
PNOperationType.PNFireOperation: 'pub',
PNOperationType.PNSendFileNotification: "pub",

PNOperationType.PNHistoryOperation: 'hist',
PNOperationType.PNHistoryDeleteOperation: 'hist',
Expand Down Expand Up @@ -534,6 +535,29 @@ def endpoint_name_for_operation(operation_type):
PNOperationType.PNDownloadFileAction: 'file',
PNOperationType.PNSendFileAction: 'file',


PNOperationType.PNFetchMessagesOperation: "hist",

PNOperationType.PNCreateSpaceOperation: "obj",
PNOperationType.PNUpdateSpaceOperation: "obj",
PNOperationType.PNFetchSpaceOperation: "obj",
PNOperationType.PNFetchSpacesOperation: "obj",
PNOperationType.PNRemoveSpaceOperation: "obj",

PNOperationType.PNCreateUserOperation: "obj",
PNOperationType.PNUpdateUserOperation: "obj",
PNOperationType.PNFetchUserOperation: "obj",
PNOperationType.PNFetchUsersOperation: "obj",
PNOperationType.PNRemoveUserOperation: "obj",

PNOperationType.PNAddUserSpacesOperation: "obj",
PNOperationType.PNAddSpaceUsersOperation: "obj",
PNOperationType.PNUpdateUserSpacesOperation: "obj",

PNOperationType.PNUpdateSpaceUsersOperation: "obj",
PNOperationType.PNFetchUserMembershipsOperation: "obj",
PNOperationType.PNFetchSpaceMembershipsOperation: "obj",

}[operation_type]

return endpoint
Expand Down
19 changes: 18 additions & 1 deletion pubnub/models/consumer/message_actions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class PNMessageAction(object):
class PNMessageAction:
def __init__(self, message_action=None):
if message_action is not None:
self.type = message_action['type']
Expand All @@ -13,6 +13,23 @@ def __init__(self, message_action=None):
self.uuid = None
self.action_timetoken = None

def create(self, *, type: str = None, value: str = None, message_timetoken: str = None,
user_id: str = None) -> 'PNMessageAction':
"""
Create a new message action convenience method.

:param type: Type of the message action
:param value: Value of the message action
:param message_timetoken: Timetoken of the message
:param user_id: User ID of the message
"""

self.type = type
self.value = value
self.message_timetoken = message_timetoken
self.uuid = user_id
return self

def __str__(self):
return "Message action with tt: %s for uuid %s with value %s " % (self.action_timetoken, self.uuid, self.value)

Expand Down
2 changes: 2 additions & 0 deletions tests/examples/native_sync/test_examples.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# flake8: noqa
from examples.native_sync.file_handling import main as test_file_handling

from examples.native_sync.message_reactions import main as test_message_reactions
Loading
Loading