Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python 3 support #9

Merged
merged 21 commits into from
Mar 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
13 changes: 9 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Matrix Client SDK for Python
:target: https://pypi.python.org/pypi/matrix-client/
:alt: Latest Version

This is a Matrix client-server SDK for Python 2.x.
This is a Matrix client-server SDK for Python 2.x and 3.x.

Usage
=====
Expand Down Expand Up @@ -42,10 +42,15 @@ The SDK is split into two modules: ``api`` and ``client``.

API
---
This contains the raw HTTP API calls and has minimal business logic. You can
set the access token (``token``) to use for requests as well as set a custom
This contains the raw HTTP API calls and has minimal business logic. You can
set the access token (``token``) to use for requests as well as set a custom
transaction ID (``txn_id``) which will be incremented for each request.

Client
------
This encapsulates the API module and provides object models such as ``Room``.
This encapsulates the API module and provides object models such as ``Room``.

Samples
=======
A collection of samples are included, written in Python 3.
You do not need to install matrix_client to run the samples, they will automatically include the files.
54 changes: 40 additions & 14 deletions matrix_client/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,24 @@
import json
import re
import requests
import urllib

try:
import urlparse
from urllib import quote
except ImportError:
from urllib.parse import quote
import urllib.parse as urlparse # For python 3

class MatrixError(Exception):
"""A generic Matrix error. Specific errors will subclass this."""
pass

class MatrixUnexpectedResponse(MatrixError):
"""The home server gave an unexpected response. """
def __init__(self,content=""):
super(MatrixRequestError, self).__init__(content)
self.content = content


class MatrixRequestError(MatrixError):
""" The home server returned an error response. """
Expand Down Expand Up @@ -54,10 +62,7 @@ def __init__(self, base_url, token=None):
base_url(str): The home server URL e.g. 'http://localhost:8008'
token(str): Optional. The client's access token.
"""
if not base_url.endswith("/_matrix/client/api/v1"):
self.url = urlparse.urljoin(base_url, "/_matrix/client/api/v1")
else:
self.url = base_url
self.base_url = base_url
self.token = token
self.txn_id = 0
self.validate_cert = True
Expand Down Expand Up @@ -130,7 +135,7 @@ def join_room(self, room_id_or_alias):
if not room_id_or_alias:
raise MatrixError("No alias or room ID to join.")

path = "/join/%s" % urllib.quote(room_id_or_alias)
path = "/join/%s" % quote(room_id_or_alias)

return self._send("POST", path)

Expand Down Expand Up @@ -159,10 +164,10 @@ def send_state_event(self, room_id, event_type, content, state_key=""):
state_key(str): Optional. The state key for the event.
"""
path = ("/rooms/%s/state/%s" %
(urllib.quote(room_id), urllib.quote(event_type))
(urlparse.quote(room_id), urlparse.quote(event_type))
)
if state_key:
path += "/%s" % (urllib.quote(state_key))
path += "/%s" % (quote(state_key))
return self._send("PUT", path, content)

def send_message_event(self, room_id, event_type, content, txn_id=None):
Expand All @@ -180,11 +185,24 @@ def send_message_event(self, room_id, event_type, content, txn_id=None):
self.txn_id = self.txn_id + 1

path = ("/rooms/%s/send/%s/%s" %
(urllib.quote(room_id), urllib.quote(event_type),
urllib.quote(unicode(txn_id)))
(quote(room_id), quote(event_type), quote(str(txn_id)))
)
return self._send("PUT", path, content)

# content_type can be a image,audio or video
# extra information should be supplied, see https://matrix.org/docs/spec/r0.0.1/client_server.html
def send_content(self, room_id, item_url, item_name, msg_type, extra_information=None):
if extra_information == None:
extra_information = {}

content_pack = {
"url":item_url,
"msgtype":msg_type,
"body":item_name,
"info":extra_information
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put spaces after :

return self.send_message_event(room_id, "m.room.message", content_pack)

def send_message(self, room_id, text_content, msgtype="m.text"):
"""Perform /rooms/$room_id/send/m.room.message

Expand Down Expand Up @@ -302,18 +320,23 @@ def get_emote_body(self, text):
"body": text
}

def _send(self, method, path, content=None, query_params={}, headers={}):
def _send(self, method, path, content=None, query_params={}, headers={}, api_path="/_matrix/client/api/v1"):
method = method.upper()
if method not in ["GET", "PUT", "DELETE", "POST"]:
raise MatrixError("Unsupported HTTP method: %s" % method)

headers["Content-Type"] = "application/json"
if "Content-Type" not in headers:
headers["Content-Type"] = "application/json"

query_params["access_token"] = self.token
endpoint = self.url + path
endpoint = self.base_url + api_path + path

if headers["Content-Type"] == "application/json":
content = json.dumps(content)

response = requests.request(
method, endpoint, params=query_params,
data=json.dumps(content), headers=headers
data=content, headers=headers
, verify=self.validate_cert #if you want to use SSL without verifying the Cert
)

Expand All @@ -323,3 +346,6 @@ def _send(self, method, path, content=None, query_params={}, headers={}):
)

return response.json()

def media_upload(self, content, content_type):
return _send("PUT","",content=content,headers={"Content-Type":content_type},apipath="/_matrix/media/r0/upload")
17 changes: 15 additions & 2 deletions matrix_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .api import MatrixHttpApi, MatrixRequestError
from .api import MatrixHttpApi, MatrixRequestError, MatrixUnexpectedResponse
from threading import Thread
import sys
# TODO: Finish implementing this.
Expand Down Expand Up @@ -127,6 +127,16 @@ def start_listener_thread(self, timeout=30000):
e = sys.exc_info()[0]
print("Error: unable to start thread. " + str(e))

def upload(self,content,content_type):
try:
response = self.api.media_upload(content,content_type)
return response["content_uri"]
except MatrixRequestError as e:
raise MatrixRequestError(code=e.code, content="Upload failed: %s" % e)
except KeyError:
raise MatrixUnexpectedResponse(content="The upload was successful, but content_uri was found in response.")


def _mkroom(self, room_id):
self.rooms[room_id] = Room(self, room_id)
return self.rooms[room_id]
Expand Down Expand Up @@ -171,6 +181,10 @@ def send_text(self, text):
def send_emote(self, text):
return self.client.api.send_emote(self.room_id, text)

#See http://matrix.org/docs/spec/r0.0.1/client_server.html#m-image for the imageinfo args.
def send_image(self, url, name, **imageinfo):
return self.client.api.send_content(self.room_id, url, name, "image", imageinfo)

def add_listener(self, callback):
self.listeners.append(callback)

Expand Down Expand Up @@ -256,4 +270,3 @@ def update_aliases(self):
return False
except MatrixRequestError:
return False

95 changes: 95 additions & 0 deletions samples/SimpleChatClient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env python3

# A simple chat client for matrix.
# This sample will allow you to connect to a room, and send/recieve messages.
# Args: host:port username room
# Error Codes:
# 1 - Unknown problem has occured
# 2 - Could not find the server.
# 3 - Bad URL Format.
# 4 - Bad username/password.
# 11 - Wrong room format.
# 12 - Couldn't find room.

import sys
sys.path.insert(0, "../") # add ../ to PYTHONPATH

from matrix_client.client import MatrixClient
from matrix_client.api import MatrixRequestError
from requests.exceptions import MissingSchema

from getpass import getpass

# Called when a message is recieved.
def on_message(event):
if event['type'] == "m.room.member":
if event['membership'] == "join":
print("{0} joined".format(event['content']['displayname']))
elif event['type'] == "m.room.message":
if event['content']['msgtype'] == "m.text":
print("{0}: {1}".format(event['sender'],event['content']['body']))
else:
print(event['type'])

host = ""
username = ""
room = ""

if len(sys.argv) > 1:
host = sys.argv[1]
else:
host = input("Host (ex: http://localhost:8008 ): ")

client = MatrixClient(host)

if len(sys.argv) > 2:
username = sys.argv[2]
else:
username = input("Username: ")

password = getpass() #Hide user input

try:
client.login_with_password(username,password)
except MatrixRequestError as e:
print(e)
if e.code == 403:
print("Bad username or password.")
sys.exit(4)
else:
print("Check your sever details are correct.")
sys.exit(3)

except MissingSchema as e:
print("Bad URL format.")
print(e)
sys.exit(2)


room = None

if len(sys.argv) > 3:
room = sys.argv[3]
else:
room = input("Room ID/Alias: ")

try:
room = client.join_room(room)
except MatrixRequestError as e:
print(e)
if e.code == 400:
print("Room ID/Alias in the wrong format")
sys.exit(11)
else:
print("Couldn't find room.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth splatting out the entire error here (at least the M_ code and the HTTP status) to aid debugging.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And throughout (e.g. :44)

sys.exit(12)

room.add_listener(on_message)
client.start_listener_thread()

while True:
msg = input()
if msg == "/quit":
break
else:
room.send_text(msg)