Introduction¶
Prerequisites¶
pyryver
requires Python 3.6 or later, and is regularly tested against Python 3.6 & Python 3.8.
Our only dependency is on aiohttp.
You may also wish to read aiohttp’s information about optional prerequisites for high-performance workloads.
Installation¶
Installing pyryver
can either be accomplished by cloning our git repository and doing the normal setup.py install
, or using PyPI:
# normal
pip install -U pyryver
# if you have multiple versions of python
python3 -m pip install -U pyryver
# if you use windows
py -3 -m pip install -U pyryver
Key Information¶
In Ryver’s API, the base class is a Chat. This, although somewhat unintuitive, does make sense: all of Ryver’s functionality can be accessed through one of many interfaces, all
of which support chatting. As such, pyryver
’s API and this documentation often uses the word “chat” to refer to “users, teams and forums”. We also use the term “group chat” to
refer to both teams and forums, and you might see them referred to as “conferences” within the code since that’s what Ryver appears to call them (especially within the WebSocket API).
We also use the term “logged-in” user to refer to whichever user who’s credentials were passed when creating the Ryver session.
Quickstart¶
The core of the pyryver
API is the pyryver.ryver.Ryver
object, which represents a session with the Ryver OData HTTP API.
# Log in as a normal user
async with pyryver.Ryver("organization_name", "username", "password") as ryver:
pass
# Log in with a token (for custom integrations)
async with pyryver.Ryver("organization_name", token="token") as ryver:
pass
As the snippet above demonstrates, you can log in as a normal user, or using a token for a custom integration.
Warning
While both normal users and custom integrations can perform most actions, the Realtime API currently does not function when logging in with a token.
The Ryver
object also stores (and can cache) some information about the Ryver organization, specifically lists of all chats.
These can be loaded either with the type-specific pyryver.ryver.Ryver.load_users
, pyryver.ryver.Ryver.load_teams
and pyryver.ryver.Ryver.load_forums
or with pyryver.ryver.Ryver.load_chats
.
There’s also pyryer.ryver.Ryver.load_missing_chats
which won’t update already loaded chats, which can be useful.
async with pyryver.Ryver("organization_name", "username", "password") as ryver:
await ryver.load_chats()
a_user = ryver.get_user(username="tylertian123")
a_forum = ryver.get_groupchat(display_name="Off-Topic")
Notice that since we grab all the chats once at the beginning, the specific chat lookup methods do not need to be awaited, since they just search within pre-fetched data. Also notice that searching for users
and group chats are in separate methods; either a pyryver.objects.Forum
or pyryver.objects.Team
is returned depending on what gets found.
Most of the functionality of pyryver
exists within these chats, such as sending/checking messages and managing topics. Additional, more specific methods (such as user and chat membership management) can also
be found within the different pyryver.objects.Chat
subclasses. For example, the following code will scan the most recent 50 messages the
logged-in user sent to tylertian123
and inform them of how many times an ellipsis occurred
within them.
async with pyryver.Ryver("organization_name", "username", "password") as ryver:
await ryver.load_chats()
a_user = ryver.get_user(username="tylertian123")
# a_forum = ryver.get_groupchat(display_name="Off-Topic")
tally = 0
for message in await a_user.get_messages(50):
if "..." in message.get_body():
tally += 1
await a_user.send_message("There's been an ellipsis in here {} times".format(tally))
For more information on how to use Chats
and other Ryver data types, use the Ryver entities reference.
Realtime Quickstart¶
Building on the previous example, what if we want our terrible ellipsis counting bot to give live updates? We can use the realtime API! The realtime interface is centred around the pyryver.ryver_ws.RyverWS
object, which
can be obtained with Ryver.get_live_session()
. Unlike the rest of the API, the realtime API is largely event driven. For example:
Warning
The Realtime API currently does not work when logging in with a token.
async with pyryver.Ryver("organization_name", "username", "password") as ryver:
await ryver.load_chats()
a_user = ryver.get_user(username="tylertian123")
async with ryver.get_live_session() as session:
@session.on_chat
async def on_chat(msg: pyryver.WSChatMessageData):
pass
await session.run_forever()
There are a few things to notice here: firstly, that we can set event handlers with the various on_
decorators of the pyryver.ryver_ws.RyverWS
instance (you could also call these directly like any other decorator if
you want to declare these callbacks without having obtained the pyryver.ryver_ws.RyverWS
instance yet), and secondly that the realtime API starts as soon as it is created. pyryver.ryver_ws.RyverWS.run_forever()
is
a helper that will run until something calls pyryver.ryver_ws.RyverWS.close()
, which can be called from within event callbacks safely.
The contents of the msg
parameter passed to our callback is an object of type pyryver.ws_data.WSChatMessageData
that contains information about the message. In the chat
message,
there are two fields our “bot” needs to care about: to_jid
, which specifies which chat the message was posted in, and text
, which is the content of the message. from_jid
refers to the message’s creator.
Perhaps unintuitively, the to_jid
field should be referring to our user’s chat, since we’re looking at a private DM. For group chats, you’d expect the chat’s JID here.
Note
Note that the callback will be called even if the message was sent by the current logged in user!
Therefore, even if you want to respond to messages from everyone, you should still make sure to check that from_jid
is not the bot user’s JID to avoid replying to your own messages.
Notice how we’re working with the chat’s JID here, which is a string, as opposed to the regular ID, which is an integer. This is because the websocket system uses JIDs to refer to chats. Using this information, we can complete our terrible little bot:
Note
The reason for the separate IDs is because the “ratatoskr” chat system appears to be built on XMPP, which uses these “JabberID”s to refer to users and groups.
async with pyryver.Ryver("organization_name", "username", "password") as ryver:
await ryver.load_chats()
a_user = ryver.get_user(username="tylertian123")
me = ryver.get_user(username="username")
async with ryver.get_live_session() as session:
@session.on_chat
async def on_chat(msg: pyryver.WSChatMessageData):
# did the message come from a_user and was sent via DM to us?
if msg.to_jid == me.get_jid() and msg.from_jid == a_user.get_jid():
# did the message contain "..."?
if "..." in msg.text:
# send a reply via the non-realtime system (slow)
# await a_user.send_message("Hey, that ellipsis is _mean_!")
# send a reply via the realtime system
await session.send_chat(a_user, "Hey, that ellipsis is _mean_!")
@session.on_connection_loss
async def on_connection_loss():
# Make sure that the session is closed and run_forever() returns on connection loss
await session.close()
await session.run_forever()
Note
Prior to v0.3.0, the msg
parameter would have been a dict containing the raw JSON data of the message, and you would access the fields directly by name through dict lookups.
If you still wish to access the raw data of the message, all message objects passed to callbacks have a raw_data
attribute that contains the dict. In v0.3.2, __getitem__()
was implemented for message objects
to directly access the raw_data
dict, providing (partial) backwards compatibility.
Here we also added a connection loss handler with the pyryver.ryver_ws.RyverWS.on_connection_loss()
decorator. The connection loss handler closes the session, which causes run_forever()
to terminate, allowing the program to
exit on connection loss instead of waiting forever. It is recommended to always have a connection loss handler if you’re using run_forever()
.
It’s important to note here that although the non-realtime API is perfectly accessible (and sometimes necessary) to use in event callbacks, it’s often faster to use corresponding methods in the pyryver.ryver_ws.RyverWS
instance
whenever possible. For some ephemeral actions like typing indicators and presence statuses, the realtime API is the only way to accomplish certain tasks.
For more information on how to use the realtime interface, use the live session reference.