Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
T
tg
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
tg
Commits
fe030a69
Commit
fe030a69
authored
May 19, 2015
by
Vincent Castellano
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial python class implementation complete.
parent
7c9129b0
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
42 additions
and
224 deletions
+42
-224
python-tg.c
python-tg.c
+9
-214
python-types.c
python-types.c
+31
-8
tg-test.py
tg-test.py
+2
-2
No files found.
python-tg.c
View file @
fe030a69
...
@@ -78,6 +78,7 @@
...
@@ -78,6 +78,7 @@
extern
PyTypeObject
tgl_PeerType
;
extern
PyTypeObject
tgl_PeerType
;
extern
PyTypeObject
tgl_MsgType
;
//#include "interface.h"
//#include "interface.h"
...
@@ -101,7 +102,6 @@ PyObject *_py_secret_chat_update;
...
@@ -101,7 +102,6 @@ PyObject *_py_secret_chat_update;
PyObject
*
_py_user_update
;
PyObject
*
_py_user_update
;
PyObject
*
_py_chat_update
;
PyObject
*
_py_chat_update
;
PyObject
*
get_user
(
tgl_peer_t
*
P
);
PyObject
*
get_peer
(
tgl_peer_id_t
id
,
tgl_peer_t
*
P
);
PyObject
*
get_peer
(
tgl_peer_id_t
id
,
tgl_peer_t
*
P
);
// Utility functions
// Utility functions
...
@@ -155,67 +155,6 @@ PyObject* get_tgl_peer_type (int x) {
...
@@ -155,67 +155,6 @@ PyObject* get_tgl_peer_type (int x) {
return
type
;
return
type
;
}
}
PyObject
*
get_user
(
tgl_peer_t
*
P
)
{
PyObject
*
user
;
user
=
PyDict_New
();
if
(
user
==
NULL
)
assert
(
0
);
// TODO handle python exception
py_add_string_field
(
user
,
"first_name"
,
P
->
user
.
first_name
);
py_add_string_field
(
user
,
"last_name"
,
P
->
user
.
last_name
);
py_add_string_field
(
user
,
"real_first_name"
,
P
->
user
.
real_first_name
);
py_add_string_field
(
user
,
"real_last_name"
,
P
->
user
.
real_last_name
);
py_add_string_field
(
user
,
"phone"
,
P
->
user
.
phone
);
if
(
P
->
user
.
access_hash
)
{
py_add_num_field
(
user
,
"access_hash"
,
1
);
}
if
(
P
->
user
.
username
)
{
py_add_string_field
(
user
,
"username"
,
P
->
user
.
username
);
}
return
user
;
}
PyObject
*
get_chat
(
tgl_peer_t
*
P
)
{
PyObject
*
chat
,
*
members
;
chat
=
PyDict_New
();
if
(
chat
==
NULL
)
assert
(
0
);
// TODO handle python exception
assert
(
P
->
chat
.
title
);
py_add_string_field
(
chat
,
"title"
,
P
->
chat
.
title
);
py_add_num_field
(
chat
,
"members_num"
,
P
->
chat
.
users_num
);
if
(
P
->
chat
.
user_list
)
{
members
=
PyList_New
(
P
->
chat
.
users_num
);
if
(
members
==
NULL
)
assert
(
0
);
// TODO handle python exception
int
i
;
for
(
i
=
0
;
i
<
P
->
chat
.
users_num
;
i
++
)
{
tgl_peer_id_t
id
=
TGL_MK_USER
(
P
->
chat
.
user_list
[
i
].
user_id
);
PyList_SetItem
(
members
,
i
,
get_peer
(
id
,
tgl_peer_get
(
TLS
,
id
)));
}
PyDict_SetItemString
(
chat
,
"members"
,
members
);
}
return
chat
;
}
PyObject
*
get_encr_chat
(
tgl_peer_t
*
P
)
{
PyObject
*
encr_chat
,
*
user
;
encr_chat
=
PyDict_New
();
if
(
encr_chat
==
NULL
)
assert
(
0
);
// TODO handle python exception
user
=
get_peer
(
TGL_MK_USER
(
P
->
encr_chat
.
user_id
),
tgl_peer_get
(
TLS
,
TGL_MK_USER
(
P
->
encr_chat
.
user_id
)));
PyDict_SetItemString
(
encr_chat
,
"user"
,
user
);
return
encr_chat
;
}
PyObject
*
get_update_types
(
unsigned
flags
)
{
PyObject
*
get_update_types
(
unsigned
flags
)
{
PyObject
*
types
;
PyObject
*
types
;
types
=
PyList_New
(
0
);
types
=
PyList_New
(
0
);
...
@@ -275,166 +214,16 @@ PyObject* get_update_types (unsigned flags) {
...
@@ -275,166 +214,16 @@ PyObject* get_update_types (unsigned flags) {
PyObject
*
get_peer
(
tgl_peer_id_t
id
,
tgl_peer_t
*
P
)
{
PyObject
*
get_peer
(
tgl_peer_id_t
id
,
tgl_peer_t
*
P
)
{
PyObject
*
peer
;
PyObject
*
peer
;
/*
peer = PyDict_New();
if(peer == NULL)
assert(0); // TODO handle python exception;
PyDict_SetItemString (peer, "type_str", get_tgl_peer_type (tgl_get_peer_type(id)));
PyDict_SetItemString (peer, "type", PyInt_FromLong(tgl_get_peer_type(id)));
PyDict_SetItemString (peer, "id", PyInt_FromLong(tgl_get_peer_id(id)));
if (!P || !(P->flags & TGLPF_CREATED)) {
PyObject *name;
static char s[100];
switch (tgl_get_peer_type (id)) {
case TGL_PEER_USER:
sprintf (s, "user#%d", tgl_get_peer_id (id));
break;
case TGL_PEER_CHAT:
sprintf (s, "chat#%d", tgl_get_peer_id (id));
break;
case TGL_PEER_ENCR_CHAT:
sprintf (s, "encr_chat#%d", tgl_get_peer_id (id));
break;
default:
assert (0);
}
name = PyDict_New();
if(name == NULL)
assert(0); // TODO handle python exception;
PyDict_SetItemString (name, "print_name", PyUnicode_FromString(s));
PyDict_SetItemString (peer, "peer", name);
} else {
PyObject *peer_obj;
switch (tgl_get_peer_type (id)) {
case TGL_PEER_USER:
peer_obj = get_user (P);
break;
case TGL_PEER_CHAT:
peer_obj = get_chat (P);
break;
case TGL_PEER_ENCR_CHAT:
peer_obj = get_encr_chat (P);
break;
default:
assert (0);
}
PyDict_SetItemString (peer, "peer", peer_obj);
}
*/
peer
=
tgl_Peer_FromTglPeer
(
P
);
peer
=
tgl_Peer_FromTglPeer
(
P
);
return
peer
;
return
peer
;
}
}
PyObject
*
get_media
(
struct
tgl_message_media
*
M
)
{
PyObject
*
media
;
media
=
PyDict_New
();
if
(
media
==
NULL
)
assert
(
0
);
// TODO handle python exception
switch
(
M
->
type
)
{
case
tgl_message_media_photo
:
py_add_string_field
(
media
,
"type"
,
"photo"
);
py_add_string_field
(
media
,
"caption"
,
M
->
caption
);
break
;
case
tgl_message_media_document
:
case
tgl_message_media_document_encr
:
py_add_string_field
(
media
,
"type"
,
"document"
);
break
;
case
tgl_message_media_unsupported
:
py_add_string_field
(
media
,
"type"
,
"unsupported"
);
break
;
case
tgl_message_media_geo
:
py_add_string_field
(
media
,
"type"
,
"geo"
);
py_add_num_field
(
media
,
"longitude"
,
M
->
geo
.
longitude
);
py_add_num_field
(
media
,
"latitude"
,
M
->
geo
.
latitude
);
break
;
case
tgl_message_media_contact
:
py_add_string_field
(
media
,
"type"
,
"contact"
);
py_add_string_field
(
media
,
"phone"
,
M
->
phone
);
py_add_string_field
(
media
,
"first_name"
,
M
->
first_name
);
py_add_string_field
(
media
,
"last_name"
,
M
->
last_name
);
py_add_num_field
(
media
,
"user_id"
,
M
->
user_id
);
break
;
case
tgl_message_media_webpage
:
py_add_string_field
(
media
,
"type"
,
"webpage"
);
py_add_string_field
(
media
,
"type"
,
"webpage"
);
py_add_string_field
(
media
,
"url"
,
M
->
webpage
->
url
);
py_add_string_field
(
media
,
"title"
,
M
->
webpage
->
title
);
py_add_string_field
(
media
,
"description"
,
M
->
webpage
->
description
);
py_add_string_field
(
media
,
"author"
,
M
->
webpage
->
author
);
break
;
case
tgl_message_media_venue
:
py_add_string_field
(
media
,
"type"
,
"venue"
);
py_add_num_field
(
media
,
"longitude"
,
M
->
venue
.
geo
.
longitude
);
py_add_num_field
(
media
,
"latitude"
,
M
->
venue
.
geo
.
latitude
);
py_add_string_field
(
media
,
"title"
,
M
->
venue
.
title
);
py_add_string_field
(
media
,
"address"
,
M
->
venue
.
address
);
py_add_string_field
(
media
,
"provider"
,
M
->
venue
.
provider
);
py_add_string_field
(
media
,
"venue_id"
,
M
->
venue
.
venue_id
);
break
;
default:
py_add_string_field
(
media
,
"type"
,
"unknown"
);
}
return
media
;
}
PyObject
*
get_message
(
struct
tgl_message
*
M
)
{
PyObject
*
get_message
(
struct
tgl_message
*
M
)
{
assert
(
M
);
assert
(
M
);
PyObject
*
msg
;
PyObject
*
msg
;
msg
=
PyDict_New
();
msg
=
tgl_Msg_FromTglMsg
(
M
);
if
(
msg
==
NULL
)
assert
(
0
);
// TODO handle python exception
static
char
s
[
30
];
snprintf
(
s
,
30
,
"%lld"
,
M
->
id
);
py_add_string_field
(
msg
,
"id"
,
s
);
if
(
!
(
M
->
flags
&
TGLMF_CREATED
))
{
Py_RETURN_NONE
;
}
py_add_num_field
(
msg
,
"flags"
,
M
->
flags
);
if
(
tgl_get_peer_type
(
M
->
fwd_from_id
))
{
PyDict_SetItemString
(
msg
,
"fwd_from"
,
get_peer
(
M
->
fwd_from_id
,
tgl_peer_get
(
TLS
,
M
->
fwd_from_id
)));
PyDict_SetItemString
(
msg
,
"fwd_date"
,
get_datetime
(
M
->
fwd_date
));
}
if
(
M
->
reply_id
)
{
snprintf
(
s
,
30
,
"%lld"
,
M
->
id
);
py_add_string_field
(
msg
,
"reply_id"
,
s
);
struct
tgl_message
*
MR
=
tgl_message_get
(
TLS
,
M
->
reply_id
);
// Message details available only within session for now
if
(
MR
)
{
PyDict_SetItemString
(
msg
,
"reply_to"
,
get_message
(
MR
));
}
}
PyDict_SetItemString
(
msg
,
"from"
,
get_peer
(
M
->
from_id
,
tgl_peer_get
(
TLS
,
M
->
from_id
)));
PyDict_SetItemString
(
msg
,
"to"
,
get_peer
(
M
->
to_id
,
tgl_peer_get
(
TLS
,
M
->
to_id
)));
PyDict_SetItemString
(
msg
,
"mention"
,
((
M
->
flags
&
TGLMF_MENTION
)
?
Py_True
:
Py_False
));
PyDict_SetItemString
(
msg
,
"out"
,
((
M
->
flags
&
TGLMF_OUT
)
?
Py_True
:
Py_False
));
PyDict_SetItemString
(
msg
,
"unread"
,
((
M
->
flags
&
TGLMF_UNREAD
)
?
Py_True
:
Py_False
));
PyDict_SetItemString
(
msg
,
"service"
,
((
M
->
flags
&
TGLMF_SERVICE
)
?
Py_True
:
Py_False
));
PyDict_SetItemString
(
msg
,
"date"
,
get_datetime
(
M
->
date
));
if
(
!
(
M
->
flags
&
TGLMF_SERVICE
))
{
if
(
M
->
message_len
&&
M
->
message
)
{
PyDict_SetItemString
(
msg
,
"text"
,
PyUnicode_FromStringAndSize
(
M
->
message
,
M
->
message_len
));
}
if
(
M
->
media
.
type
&&
M
->
media
.
type
!=
tgl_message_media_none
)
{
PyDict_SetItemString
(
msg
,
"media"
,
get_media
(
&
M
->
media
));
}
}
return
msg
;
return
msg
;
}
}
...
@@ -1301,6 +1090,12 @@ MOD_INIT(tgl)
...
@@ -1301,6 +1090,12 @@ MOD_INIT(tgl)
Py_INCREF
(
&
tgl_PeerType
);
Py_INCREF
(
&
tgl_PeerType
);
PyModule_AddObject
(
m
,
"Peer"
,
(
PyObject
*
)
&
tgl_PeerType
);
PyModule_AddObject
(
m
,
"Peer"
,
(
PyObject
*
)
&
tgl_PeerType
);
if
(
PyType_Ready
(
&
tgl_MsgType
)
<
0
)
return
MOD_ERROR_VAL
;
Py_INCREF
(
&
tgl_MsgType
);
PyModule_AddObject
(
m
,
"Msg"
,
(
PyObject
*
)
&
tgl_MsgType
);
return
MOD_SUCCESS_VAL
(
m
);
return
MOD_SUCCESS_VAL
(
m
);
}
}
...
...
python-types.c
View file @
fe030a69
...
@@ -206,6 +206,28 @@ tgl_Peer_getusername (tgl_Peer *self, void *closure)
...
@@ -206,6 +206,28 @@ tgl_Peer_getusername (tgl_Peer *self, void *closure)
}
}
static
PyObject
*
tgl_Peer_getuser
(
tgl_Peer
*
self
,
void
*
closure
)
{
PyObject
*
ret
;
switch
(
self
->
peer
->
id
.
type
)
{
case
TGL_PEER_ENCR_CHAT
:
ret
=
tgl_Peer_FromTglPeer
(
tgl_peer_get
(
TLS
,
TGL_MK_USER
(
self
->
peer
->
encr_chat
.
user_id
)));
break
;
case
TGL_PEER_USER
:
case
TGL_PEER_CHAT
:
PyErr_SetString
(
PyExc_TypeError
,
"Only peer.type == TGL_PEER_ENCR_CHAT has user"
);
Py_RETURN_NONE
;
break
;
default:
PyErr_SetString
(
PyExc_TypeError
,
"peer.type not supported!"
);
Py_RETURN_NONE
;
}
Py_XINCREF
(
ret
);
return
ret
;
}
static
PyObject
*
static
PyObject
*
tgl_Peer_getid
(
tgl_Peer
*
self
,
void
*
closure
)
tgl_Peer_getid
(
tgl_Peer
*
self
,
void
*
closure
)
...
@@ -234,6 +256,7 @@ static PyGetSetDef tgl_Peer_getseters[] = {
...
@@ -234,6 +256,7 @@ static PyGetSetDef tgl_Peer_getseters[] = {
{
"type"
,
(
getter
)
tgl_Peer_gettype
,
NULL
,
""
,
NULL
},
{
"type"
,
(
getter
)
tgl_Peer_gettype
,
NULL
,
""
,
NULL
},
{
"name"
,
(
getter
)
tgl_Peer_getname
,
NULL
,
""
,
NULL
},
{
"name"
,
(
getter
)
tgl_Peer_getname
,
NULL
,
""
,
NULL
},
{
"user_id"
,
(
getter
)
tgl_Peer_getuser_id
,
NULL
,
""
,
NULL
},
{
"user_id"
,
(
getter
)
tgl_Peer_getuser_id
,
NULL
,
""
,
NULL
},
{
"user"
,
(
getter
)
tgl_Peer_getuser
,
NULL
,
""
,
NULL
},
{
"user_list"
,
(
getter
)
tgl_Peer_getuser_list
,
NULL
,
""
,
NULL
},
{
"user_list"
,
(
getter
)
tgl_Peer_getuser_list
,
NULL
,
""
,
NULL
},
{
"user_status"
,
(
getter
)
tgl_Peer_getuser_status
,
NULL
,
""
,
NULL
},
{
"user_status"
,
(
getter
)
tgl_Peer_getuser_status
,
NULL
,
""
,
NULL
},
{
"phone"
,
(
getter
)
tgl_Peer_getphone
,
NULL
,
""
,
NULL
},
{
"phone"
,
(
getter
)
tgl_Peer_getphone
,
NULL
,
""
,
NULL
},
...
@@ -417,7 +440,7 @@ tgl_Msg_getservice (tgl_Msg *self, void *closure)
...
@@ -417,7 +440,7 @@ tgl_Msg_getservice (tgl_Msg *self, void *closure)
static
PyObject
*
static
PyObject
*
tgl_Msg_get
from
(
tgl_Msg
*
self
,
void
*
closure
)
tgl_Msg_get
src
(
tgl_Msg
*
self
,
void
*
closure
)
{
{
PyObject
*
ret
;
PyObject
*
ret
;
...
@@ -432,7 +455,7 @@ tgl_Msg_getfrom (tgl_Msg *self, void *closure)
...
@@ -432,7 +455,7 @@ tgl_Msg_getfrom (tgl_Msg *self, void *closure)
}
}
static
PyObject
*
static
PyObject
*
tgl_Msg_get
to
(
tgl_Msg
*
self
,
void
*
closure
)
tgl_Msg_get
dest
(
tgl_Msg
*
self
,
void
*
closure
)
{
{
PyObject
*
ret
;
PyObject
*
ret
;
...
@@ -539,7 +562,7 @@ tgl_Msg_getdate (tgl_Msg *self, void *closure)
...
@@ -539,7 +562,7 @@ tgl_Msg_getdate (tgl_Msg *self, void *closure)
}
}
static
PyObject
*
static
PyObject
*
tgl_Msg_getfwd_
from
(
tgl_Msg
*
self
,
void
*
closure
)
tgl_Msg_getfwd_
src
(
tgl_Msg
*
self
,
void
*
closure
)
{
{
PyObject
*
ret
;
PyObject
*
ret
;
...
@@ -614,12 +637,12 @@ static PyGetSetDef tgl_Msg_getseters[] = {
...
@@ -614,12 +637,12 @@ static PyGetSetDef tgl_Msg_getseters[] = {
{
"out"
,
(
getter
)
tgl_Msg_getout
,
NULL
,
""
,
NULL
},
{
"out"
,
(
getter
)
tgl_Msg_getout
,
NULL
,
""
,
NULL
},
{
"unread"
,
(
getter
)
tgl_Msg_getunread
,
NULL
,
""
,
NULL
},
{
"unread"
,
(
getter
)
tgl_Msg_getunread
,
NULL
,
""
,
NULL
},
{
"service"
,
(
getter
)
tgl_Msg_getservice
,
NULL
,
""
,
NULL
},
{
"service"
,
(
getter
)
tgl_Msg_getservice
,
NULL
,
""
,
NULL
},
{
"
from"
,
(
getter
)
tgl_Msg_getfrom
,
NULL
,
""
,
NULL
},
{
"
src"
,
(
getter
)
tgl_Msg_getsrc
,
NULL
,
""
,
NULL
},
{
"
to"
,
(
getter
)
tgl_Msg_getto
,
NULL
,
""
,
NULL
},
{
"
dest"
,
(
getter
)
tgl_Msg_getdest
,
NULL
,
""
,
NULL
},
{
"text"
,
(
getter
)
tgl_Msg_gettext
,
NULL
,
""
,
NULL
},
{
"text"
,
(
getter
)
tgl_Msg_gettext
,
NULL
,
""
,
NULL
},
{
"media"
,
(
getter
)
tgl_Msg_getmedia
,
NULL
,
""
,
NULL
},
{
"media"
,
(
getter
)
tgl_Msg_getmedia
,
NULL
,
""
,
NULL
},
{
"date"
,
(
getter
)
tgl_Msg_getdate
,
NULL
,
""
,
NULL
},
{
"date"
,
(
getter
)
tgl_Msg_getdate
,
NULL
,
""
,
NULL
},
{
"fwd_
from"
,
(
getter
)
tgl_Msg_getfwd_from
,
NULL
,
""
,
NULL
},
{
"fwd_
src"
,
(
getter
)
tgl_Msg_getfwd_src
,
NULL
,
""
,
NULL
},
{
"fwd_date"
,
(
getter
)
tgl_Msg_getfwd_date
,
NULL
,
""
,
NULL
},
{
"fwd_date"
,
(
getter
)
tgl_Msg_getfwd_date
,
NULL
,
""
,
NULL
},
{
"reply"
,
(
getter
)
tgl_Msg_getreply
,
NULL
,
""
,
NULL
},
{
"reply"
,
(
getter
)
tgl_Msg_getreply
,
NULL
,
""
,
NULL
},
{
"reply_id"
,
(
getter
)
tgl_Msg_getreply_id
,
NULL
,
""
,
NULL
},
{
"reply_id"
,
(
getter
)
tgl_Msg_getreply_id
,
NULL
,
""
,
NULL
},
...
@@ -637,7 +660,7 @@ static PyMethodDef tgl_Msg_methods[] = {
...
@@ -637,7 +660,7 @@ static PyMethodDef tgl_Msg_methods[] = {
PyTypeObject
tgl_MsgType
=
{
PyTypeObject
tgl_MsgType
=
{
PyVarObject_HEAD_INIT
(
NULL
,
0
)
PyVarObject_HEAD_INIT
(
NULL
,
0
)
"tgl.
Peer
"
,
/* tp_name */
"tgl.
Msg
"
,
/* tp_name */
sizeof
(
tgl_Msg
),
/* tp_basicsize */
sizeof
(
tgl_Msg
),
/* tp_basicsize */
0
,
/* tp_itemsize */
0
,
/* tp_itemsize */
(
destructor
)
tgl_Msg_dealloc
,
/* tp_dealloc */
(
destructor
)
tgl_Msg_dealloc
,
/* tp_dealloc */
...
@@ -656,7 +679,7 @@ PyTypeObject tgl_MsgType = {
...
@@ -656,7 +679,7 @@ PyTypeObject tgl_MsgType = {
0
,
/* tp_setattro */
0
,
/* tp_setattro */
0
,
/* tp_as_buffer */
0
,
/* tp_as_buffer */
Py_TPFLAGS_DEFAULT
,
/* tp_flags */
Py_TPFLAGS_DEFAULT
,
/* tp_flags */
"tgl
Peer
"
,
/* tp_doc */
"tgl
Message
"
,
/* tp_doc */
0
,
/* tp_traverse */
0
,
/* tp_traverse */
0
,
/* tp_clear */
0
,
/* tp_clear */
0
,
/* tp_richcompare */
0
,
/* tp_richcompare */
...
...
tg-test.py
View file @
fe030a69
...
@@ -32,10 +32,10 @@ def history_cb(msg_list, ptype, pid, success, msgs):
...
@@ -32,10 +32,10 @@ def history_cb(msg_list, ptype, pid, success, msgs):
tgl
.
get_history
(
ptype
,
pid
,
len
(
msg_list
),
HISTORY_QUERY_SIZE
,
partial
(
history_cb
,
msg_list
,
ptype
,
pid
));
tgl
.
get_history
(
ptype
,
pid
,
len
(
msg_list
),
HISTORY_QUERY_SIZE
,
partial
(
history_cb
,
msg_list
,
ptype
,
pid
));
def
on_msg_receive
(
msg
):
def
on_msg_receive
(
msg
):
if
msg
[
"out"
]
and
not
binlog_done
:
if
msg
.
out
and
not
binlog_done
:
return
;
return
;
print
(
msg
[
"to"
]
.
id
)
print
(
"From: {0}, To: {1}"
.
format
(
msg
.
src
.
id
,
msg
.
dest
.
user
)
)
"""
"""
def on_msg_receive(msg):
def on_msg_receive(msg):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment