Merge pull request #603 from datamachine/master

Python version support fixes
This commit is contained in:
vysheng 2015-06-12 11:51:31 +03:00
commit 706aed07cb
6 changed files with 279 additions and 102 deletions

View File

@ -1,95 +0,0 @@
To use python with client you should write python script. You can specify it from config ("python_script" option) or from command_line [-Z].
You should set the following callbacks in your script:
tgl.on_binlog_replay_end() - it is called when replay of old events end. Any updates prior this call were already received by this client
some time ago.
tgl.on_get_difference_end() - it is called after first get_difference call. So we received all updates after last client execute.
tgl.on_our_id(our_id) - Informs about id of currently logged in user.
tgl.on_msg_receive(msg) - it is called when we receive new msg (!! may be called before on_binlog_replay_end, than it is old msg).
tgl.on_user_update(peer, what_changed) - updated info about user. what_changed is array of strings.
tgl.on_chat_update(peer, what_changed) - updated info about user. what_changed is array of strings.
tgl.on_secret_chat_update(peer, what_changed) - updated info about user. what_changed is array of strings.
Also, you can call several functions. Each this function last two arguments, are callback, which is a python function implementing the callback from the function.
These functions may return false immidiately if something is bad with args, or return true and call cb_function in future.
The callback function should have one arguments: first success (True or False), and the rest depends on the call.
Functions that require a peer type other than what is passed will raise tgl.PeerError.
Function_list (arguments are listed aside from callback, import tgl for access) :
tgl.get_contact_list ()
tgl.get_dialog_list ()
tgl.rename_chat (peer, new_name)
tgl.chat_set_photo (peer, file)
tgl.send_typing (peer)
tgl.send_typing_abort (peer)
tgl.send_msg (peer, text)
tgl.fwd_msg (peer, msg)
tgl.send_photo (peer, file)
tgl.send_video (peer, file)
tgl.send_audio (peer, file)
tgl.send_document (peer, file)
tgl.send_text (peer, file)
tgl.load_photo(msg)
tgl.load_video(msg)
tgl.load_video_thumb(msg)
tgl.load_audio(msg)
tgl.load_document(msg)
tgl.load_document_thumb(msg)
tgl.info (peer)
tgl.get_history (peer, limit)
tgl.chat_add_user (peer, user)
tgl.chat_del_user (peer, user)
tgl.add_contact (phone, first_name, last_name)
tgl.rename_contact (phone, first_name, last_name)
tgl.msg_search (peer, text)
tgl.msg_global_search (text)
tgl.mark_read (peer)
tgl.set_profile_photo (file)
tgl.create_secret_chat (user)
tgl.create_group_chat (peer, name)
tgl.delete_msg (msg)
tgl.restore_msg (msg_id)
tgl.status_online ()
tgl.status_offline ()
tgl.send_location (peer, latitude, longitude)
Additionally, the tgl.Peer object has the following direct methods:
peer.rename_chat(new_name)
peer.chat_set_photo(file)
peer.send_typing()
peer.send_typing_abort()
peer.send_msg(text)
peer.send_photo(file)
peer.send_video(file)
peer.send_document(file)
peer.send_text(file)
peer.info()
peer.history(limit, offset)
peer.add_user(peer)
peer.del_user(peer)
peer.search(text)
peer.mark_read()
peer.send_location(latitude, longitude)

175
README-PY.md Normal file
View File

@ -0,0 +1,175 @@
Python Bindings
====================
All of the functions and methods are executed in the network loop of tg, ***NOT IMMEDIATELY***. What this means is all calls should be considered async, and so there is an optional callback parameter for every function/method as the last parameter. For many uses, you won't care about the return value, so you can leave out the callback. Note there are a few cases where the callback is considered mandatory when the function is considered an information query and has no functionality without returned data. These will explicitly have the callback in the parameter list and will be noted in the description.
You can specify the python script from config ("python_script" option) or from command_line [-Z].
Version Notes
=====================
The python integration is written with Python 2/3 in mind, however, there is a bias to Python 3. Because of this, there are a few caveats:
- I am only testing against Python 2.7, and have no intention to support/test < 2.7 but am more than happy to accept PRs for fixes as long as it does not break 2.7/3
- repr/print of native types is dumbed down for < 2.7.9, I highly recommend using this version or newer. (This is due to a [bug](http://bugs.python.org/issue22023) in python)
TGL Callbacks
=============
Assign python fuctions to the following tgl attributes to set callbacks from TG.
| Callback | Description |
|--------- | ---------------------|
|`tgl.on_binlog_replay_end()` | This is called when replay of old events end. Any updates prior this call were already received by this client some time ago.|
|`tgl.on_get_difference_end()`| This is called after first get_difference call. So we received all updates after last client execute.|
|`tgl.on_our_id(our_id)`|Informs about id of currently logged in user.|
|`tgl.on_msg_receive(msg)`| This is called when we receive new `tgl.Msg` object (*may be called before on_binlog_replay_end, than it is old msg*).|
|`tgl.on_user_update(peer, what_changed)`|updated info about user. peer is a `tgl.Peer` object representing the user, and what_changed is array of strings.|
|`tgl.on_chat_update(peer, what_changed)`|updated info about chat. peer is a `tgl.Peer` object representing the chat, and what_changed is array of strings.|
|`tgl.on_secret_chat_update(peer, what_changed)`|updated info about secret chat. peer is a `tgl.Peer` object representing the secret chat, and what_changed is array of strings.|
Python Callback Signatures
=========================
As mentioned, all methods and functions have callbacks. The following are the different signatures that may be required (*The description of the method will mention which is used*)
| Type | Signature | Description |
|----- | ------------------ | ------------|
|empty_cb|`(success)`|This just indicated the success of the call. All other callback types have this as well.|
|contact_list_cb|`(success, peer_list)`|`peer_list` is a list of `tgl.Peer` objects|
|dialog_list_cb|`(success, dialog_list)`|`dialog_list` is a list of dicts, with keys: 'peer': `tgl.Peer`, 'message': `tgl.Msg`|
|msg_cb|`(success, msg)`|`msg` is a `tgl.Msg`|
|msg_list_cb|`(success, msg_list)`|`msg_list` is a list of `tgl.Msg` objects|
|file_cb|`(success, file_path)`|`file_path` is a string with an absolute path|
|chat_cb|`(success, peer)`|`peer` is a `tgl.Peer` that will have the type of `tgl.PEER_CHAT`|
|peer_cb|`(success, peer)`|`peer` is a `tgl.Peer`|
|secret_chat_cb|`(success, peer)`|`peer` is a `tgl.Peer` that will have the type of `tgl.PEER_ENCR_CHAT`|
|user_cb|`(success, peer)`|`peer` is a `tgl.Peer` that will have the type of `tgl.PEER_USER`|
|str_cb|`(success, string)`|`string` is a simple string|
TGL Module Level Fuctions
=========================
All of these functions are accessed by importing the `tgl` module.
| Function | Description | Callback Type |
|--------- | ---------------------| ------------- |
|`tgl.get_contact_list (callback)`|Retrieve peers stored in the contact list. *requires callback*|`contact_list_cb`|
|`tgl.get_dialog_list (callback)`|Get a list of current conversations with the `tgl.Peer` and the most recent `tgl.Msg`. *requires callback*|`dialog_list_cb`|
|`tgl.add_contact (phone, first_name, last_name)`|Adds contact to contact list, last name is optional|`contact_list_cb`|
|`tgl.del_contact (peer)`|Removes contact from contact list|`empty_cb`|
|`tgl.rename_contact (phone, first_name, last_name) `|Renaming a contact means sending the first/last name with the same phone number of an existing contact|`contact_list_cb`|
|`tgl.msg_global_search (text, callback)`|Get all messages that match the search text. *requires callback*|`msg_list_cb`|
|`tgl.set_profile_photo (file_path)`|Sets avatar to image found at `file_path`, no checking on the file.|`empty_cb`|
|`tgl.create_secret_chat (user)`|Creates secret chat with user, callback recommended to get new peer for the secret chat.|`secret_chat_cb`|
|`tgl.create_group_chat (peer_list, name)`|`peer_list` contains users to create group with, must be more than 1 peer.|`empty_cb`|
|`tgl.restore_msg (msg_id)`|Restore a deleted message by message id.|`empty_cb`|
|`tgl.status_online ()`|Sets status as online|`empty_cb`|
|`tgl.status_offline ()`|Sets status as offline|`empty_cb`|
|`tgl.import_chat_link (link)`|Join channel using the `link`.|`empty_cb`|
|`tgl.safe_exit (exit_status)`|Exception to the rule about callbacks, no callback parameter is accepted. Causes the bot to quit after cleaning up. `exit_status` is an optional parameter with the exit status (On glibc, this must be 0-255)|NONE|
Peer
====
## Attributes
| Attribute | Type | Description |
|---------- | ---- | ----------------------|
|`id`|`int`|Telegram peer id|
|`type`|`int`|Peer type, you can compare this with `tgl.PEER_CHAT`, `tgl.PEER_USER`, or `tgl.PEER_ENCR_CHAT`|
|`type_name`|`string`|Text representation of the type of the peer, `'chat'`, `'user'`, or `'secret_chat'`|
|`name`|`string`|Returns the tg print version of the name. Usually `FirstName_LastName` for user, and the chatname with spaces replaced with `_`|
|`user_id`|`int`|Used in secret chats, since a secret chat has it's own id, this is the id of the user at the endpoint.|
|`user_list`|`peer_list`|Only used in `tgl.PEER_CHAT` peers, contains list of users. This currently does not work, it is not populating properly.|
|`user_status`|`dict`|Only used in `tgl.PEER_USER` peers. Dictionary with the current status, keys: 'online': `bool`, 'when': `datetime`|
|`phone`|`string`|Only used in `tgl.PEER_USER` peers. Phone number, only available if user is on contact list.|
|`username`|`string`|Only used in `tgl.PEER_USER` peers. Will be `None` if username is not set.|
|`first_name`|`string`|Only used in `tgl.PEER_USER` peers.|
|`last_name`|`string`|Only used in `tgl.PEER_USER` peers.|
## Methods
| Method | Description | Callback Type |
|------- | ---------------------| ------------- |
|`peer.rename_chat (new_name)`||`empty_cb`|
|`peer.chat_set_photo (file)`|Sets avatar for the group to image found at `file_path`, no checking on the file. The calling peer must be of type `tgl.PEER_CHAT`.|`msg_cb`|
|`peer.send_typing ()`|Tell peer that you are typing.|`empty_cb`|
|`peer.send_typing_abort ()`|Tell peer you are done typing.|`empty_cb`|
|`peer.send_msg (text, reply=msg_id, preview=bool)`|Sends message to peer. Optional keyword arguments: reply is the message id we are replying to, preview is a boolean that forces URL preview on or off.|`msg_cb`|
|`peer.fwd_msg (msg_id)`|Forwards msg with message id to peer.|`msg_cb`|
|`peer.fwd_media (msg_id)`|Forwards media with message id to peer.|`msg_cb`|
|`peer.send_photo (file)`|Send media to peer using `file`. No checking is done on the contents of the file.|`msg_cb`|
|`peer.send_video (file)`|Send media to peer using `file`. No checking is done on the contents of the file.|`msg_cb`|
|`peer.send_audio (file)`|Send media to peer using `file`. No checking is done on the contents of the file.|`msg_cb`|
|`peer.send_document (file)`|Send media to peer using `file`. No checking is done on the contents of the file.|`msg_cb`|
|`peer.send_text (file)`|Send media to peer using `file`. No checking is done on the contents of the file.|`msg_cb`|
|`peer.send_location (latitude, longitude)`|Sends location media message to peer, `longitude` and `latitude` should be specified as double.|`msg_cb`|
|`peer.chat_add_user (user)`|Adds `user`(`tgl.Peer`) to the group. The calling peer must be of type `tgl.PEER_CHAT`|`empty_cb`|
|`peer.chat_del_user (user)`|Removes `user`(`tgl.Peer`) from the group. The calling peer must be of type `tgl.PEER_CHAT`|`empty_cb`|
|`peer.mark_read ()`|Marks the dialog with the peer as read. This cannot be done on message level.|`empty_cb`|
|`peer.msg_search (text, callback)`|Get all messages that match the search text with the peer. *requires callback*|`msg_list_cb`|
|`peer.get_history (offset, limit, callback)`|Get all messages with the peer. `offset` specifies what message to start at, and `limit` specifies how many messages to retrieve. See example below for one method to get the entire history. *requires callback*|`msg_list_cb`|
|`peer.info ()`|Gets peer info.|`peer_cb`|
Example usage for `peer.get_history`:
```
from functools import partial
history = []
# Get all the history, 100 msgs at a time
peer.get_history(0, 100, partial(history_callback, 100, peer))
def history_callback(msg_count, peer, success, msgs)
history.extend(msgs)
if len(msgs) == msg_count:
peer.get_history(len(history), msg_count, partial(history_callback, msg_count, peer))
```
Msg
====
## Attributes
| Attribute | Type | Description |
|---------- | ---- | ----------------------|
|`id`|`int`|Message id|
|`flags`|`int`|tgl flags, see source code for tgl for various possible flags. This is a bitfield in an int.|
|`mention`|`bool`|`True` if you are @mentioned.|
|`out`|`bool`|`True` if you sent this message.|
|`unread`|`bool`|`True` if you have not marked this as read.|
|`service`|`bool`|`True` if the message is a service messages, see tgl.Msg.action for the type.|
|`src`|`tgl.Peer`|Peer who sent the message|
|`dest`|`tgl.Peer`|Peer who the message was sent too. In a group, this will be the chat peer. Otherwise it will be you.|
|`text`|`string`|Text contents of the message. This may be `None` if it's media without caption.|
|`media`|`dict`|Dictionary that varies based on the media type.|
|`date`|`datetime`|When the message was sent.|
|`fwd_src`|`tgl.Peer`|The user that sent the forwarded message.|
|`fwd_date`|`datetime`|When the forwarded message was originally sent.|
|`reply`|`tgl.Msg`|Message that this message is replying to.|
|`reply_id`|`int`|Message id that this message is replying to.|
|`action`|`int`|Action enum for the message if `msg.service == True`. See all the possible values below|
## Action Type Constants
- tgl.ACTION_NONE
- tgl.ACTION_GEO_CHAT_CREATE
- tgl.ACTION_GEO_CHAT_CHECKIN
- tgl.ACTION_CHAT_CREATE
- tgl.ACTION_CHAT_EDIT_TITLE
- tgl.ACTION_CHAT_EDIT_PHOTO
- tgl.ACTION_CHAT_DELETE_PHOTO
- tgl.ACTION_CHAT_ADD_USER
- tgl.ACTION_CHAT_ADD_USER_BY_LINK
- tgl.ACTION_CHAT_DELETE_USER
- tgl.ACTION_SET_MESSAGE_TTL
- tgl.ACTION_READ_MESSAGES
- tgl.ACTION_DELETE_MESSAGES
- tgl.ACTION_SCREENSHOT_MESSAGES
- tgl.ACTION_FLUSH_HISTORY
- tgl.ACTION_RESEND
- tgl.ACTION_NOTIFY_LAYER
- tgl.ACTION_TYPING
- tgl.ACTION_NOOP
- tgl.ACTION_COMMIT_KEY
- tgl.ACTION_ABORT_KEY
- tgl.ACTION_REQUEST_KEY
- tgl.ACTION_ACCEPT_KEY
## Methods
| Method | Description | Callback Type |
|------- | ---------------------| ------------- |
|`msg.load_photo(callback)`|Saves the media and returns the path to the file in the callback. *requires callback*|`file_cb`|
|`msg.load_video(callback)`|Saves the media and returns the path to the file in the callback. *requires callback*|`file_cb`|
|`msg.load_video_thumb(callback)`|Saves the media and returns the path to the file in the callback. *requires callback*|`file_cb`|
|`msg.load_audio(callback)`|Saves the media and returns the path to the file in the callback. *requires callback*|`file_cb`|
|`msg.load_document(callback)`|Saves the media and returns the path to the file in the callback. *requires callback*|`file_cb`|
|`msg.load_document_thumb(callback)`|Saves the media and returns the path to the file in the callback. *requires callback*|`file_cb`|
|`msg.delete_msg ()`|Deletes the message from the local history|`empty_cb`|

View File

@ -64,6 +64,9 @@ if test x$ax_python_bin != x; then
if test x$ax_python_lib == xno; then
AC_CHECK_LIB(${ax_python_bin}m, main, ax_python_lib=${ax_python_bin}m, ax_python_lib=no)
fi
if test x$ax_python_lib == xno; then
AC_CHECK_LIB(${ax_python_bin}mu, main, ax_python_lib=${ax_python_bin}mu, ax_python_lib=no)
fi
if test x$ax_python_lib != xno; then
ax_python_header=`$ax_python_bin -c "from distutils.sysconfig import *; print(get_config_var('CONFINCLUDEPY'))"`
if test x$ax_python_header != x; then

80
configure vendored
View File

@ -6069,6 +6069,46 @@ else
ax_python_lib=no
fi
fi
if test x$ax_python_lib == xno; then
as_ac_Lib=`$as_echo "ac_cv_lib_${ax_python_bin}mu''_main" | $as_tr_sh`
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -l${ax_python_bin}mu" >&5
$as_echo_n "checking for main in -l${ax_python_bin}mu... " >&6; }
if eval \${$as_ac_Lib+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-l${ax_python_bin}mu $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
return main ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
eval "$as_ac_Lib=yes"
else
eval "$as_ac_Lib=no"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
eval ac_res=\$$as_ac_Lib
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
ax_python_lib=${ax_python_bin}mu
else
ax_python_lib=no
fi
fi
if test x$ax_python_lib != xno; then
ax_python_header=`$ax_python_bin -c "from distutils.sysconfig import *; print(get_config_var('CONFINCLUDEPY'))"`
@ -6262,6 +6302,46 @@ else
ax_python_lib=no
fi
fi
if test x$ax_python_lib == xno; then
as_ac_Lib=`$as_echo "ac_cv_lib_${ax_python_bin}mu''_main" | $as_tr_sh`
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -l${ax_python_bin}mu" >&5
$as_echo_n "checking for main in -l${ax_python_bin}mu... " >&6; }
if eval \${$as_ac_Lib+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-l${ax_python_bin}mu $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
return main ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
eval "$as_ac_Lib=yes"
else
eval "$as_ac_Lib=no"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
eval ac_res=\$$as_ac_Lib
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
ax_python_lib=${ax_python_bin}mu
else
ax_python_lib=no
fi
fi
if test x$ax_python_lib != xno; then
ax_python_header=`$ax_python_bin -c "from distutils.sysconfig import *; print(get_config_var('CONFINCLUDEPY'))"`

View File

@ -467,7 +467,8 @@ enum py_query_type {
pq_status_online,
pq_status_offline,
pq_send_location,
pq_extf
pq_extf,
pq_import_chat_link
};
void py_empty_cb (struct tgl_state *TLSR, void *cb_extra, int success) {
@ -807,7 +808,7 @@ void py_do_all (void) {
break;
case pq_msg:
if(PyArg_ParseTuple(args, "O!s#|OO", &tgl_PeerType, &peer, &str, &len, &cb_extra, &pyObj1)) {
if(PyArg_ParseTuple(pyObj1, "ii", &preview, &reply_id)) {
if(pyObj1 && PyArg_ParseTuple(pyObj1, "ii", &preview, &reply_id)) {
if(preview != -1) {
if(preview)
flags |= TGL_SEND_MSG_FLAG_ENABLE_PREVIEW;
@ -1062,7 +1063,13 @@ void py_do_all (void) {
break;
case pq_extf:
if(PyArg_ParseTuple(args, "s#|O", &str, &len, &cb_extra))
tgl_do_send_extf (TLS, str, len, py_str_cb, &cb_extra);
tgl_do_send_extf (TLS, str, len, py_str_cb, cb_extra);
else
PyErr_Print();
break;
case pq_import_chat_link:
if(PyArg_ParseTuple(args, "s#|O", &str, &len, &cb_extra))
tgl_do_import_chat_link (TLS, str, len, py_empty_cb, cb_extra);
else
PyErr_Print();
break;
@ -1146,6 +1153,7 @@ PyObject* py_status_online(PyObject *self, PyObject *args) { return push_py_func
PyObject* py_status_offline(PyObject *self, PyObject *args) { return push_py_func(pq_status_offline, args); }
PyObject* py_send_location(PyObject *self, PyObject *args) { return push_py_func(pq_send_location, args); }
PyObject* py_extf(PyObject *self, PyObject *args) { return push_py_func(pq_extf, args); }
PyObject* py_import_chat_link(PyObject *self, PyObject *args) { return push_py_func(pq_import_chat_link, args); }
extern int safe_quit;
extern int exit_code;
@ -1232,6 +1240,7 @@ static PyMethodDef py_tgl_methods[] = {
{"status_offline", py_status_offline, METH_VARARGS, ""},
{"send_location", py_send_location, METH_VARARGS, ""},
{"ext_function", py_extf, METH_VARARGS, ""},
{"import_chat_link", py_import_chat_link, METH_VARARGS, ""},
{"set_on_binlog_replay_end", set_py_binlog_end, METH_VARARGS, ""},
{"set_on_get_difference_end", set_py_diff_end, METH_VARARGS, ""},
{"set_on_our_id", set_py_our_id, METH_VARARGS, ""},

View File

@ -19,7 +19,6 @@
#include "python-tg.h"
extern struct tgl_state *TLS;
// TGL Python Exceptions
extern PyObject *TglError;
extern PyObject *PeerError;
@ -944,7 +943,10 @@ tgl_Peer_repr(tgl_Peer *self)
switch(self->peer->id.type) {
case TGL_PEER_USER:
ret = PyUnicode_FromFormat("<tgl.Peer: type=user, id=%ld, username=%R, name=%R, first_name=%R, last_name=%R, phone=%R>",
#if PY_VERSION_HEX < 0x02070900
ret = PyUnicode_FromFormat("<tgl.Peer: id=%ld>", self->peer->id.id);
#else
ret = PyUnicode_FromFormat("<tgl.Peer: type=user, id=%ld, username=%R, name=%R, first_name=%R, last_name=%R, phone=%R>",
self->peer->id.id,
PyObject_GetAttrString((PyObject*)self, "username"),
PyObject_GetAttrString((PyObject*)self, "name"),
@ -952,6 +954,7 @@ tgl_Peer_repr(tgl_Peer *self)
PyObject_GetAttrString((PyObject*)self, "last_name"),
PyObject_GetAttrString((PyObject*)self, "phone")
);
#endif
break;
case TGL_PEER_CHAT:
ret = PyUnicode_FromFormat("<tgl.Peer: type=chat, id=%ld, name=%s>",
@ -1423,7 +1426,9 @@ static PyObject *
tgl_Msg_repr(tgl_Msg *self)
{
PyObject *ret;
#if PY_VERSION_HEX < 0x02070900
ret = PyUnicode_FromFormat("<tgl.Msg id=%ld>", self->msg->id);
#else
ret = PyUnicode_FromFormat("<tgl.Msg id=%ld, flags=%d, mention=%R, out=%R, unread=%R, service=%R, src=%R, "
"dest=%R, text=%R, media=%R, date=%R, fwd_src=%R, fwd_date=%R, reply_id=%R, reply=%R>",
self->msg->id, self->msg->flags,
@ -1441,7 +1446,7 @@ tgl_Msg_repr(tgl_Msg *self)
PyObject_GetAttrString((PyObject*)self, "reply_id"),
PyObject_GetAttrString((PyObject*)self, "reply")
);
#endif
return ret;
}