Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
U
utils
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
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
library
utils
Commits
12e7a5fb
Verified
Commit
12e7a5fb
authored
Jan 16, 2019
by
Cui
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add sender
parent
a3a23bc5
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
506 additions
and
1 deletion
+506
-1
math.go
math.go
+6
-0
messageSender.go
messageSender.go
+189
-0
messageStructs.go
messageStructs.go
+282
-0
tools.go
tools.go
+29
-1
No files found.
math.go
View file @
12e7a5fb
...
...
@@ -7,9 +7,15 @@ package utils
import
(
"crypto/rand"
"math"
"math/big"
r
"math/rand"
)
func
RandInt
()
int
{
return
int
(
math
.
Round
(
r
.
Float64
()
*
99999
))
}
func
RandInt64
(
min
,
max
int64
)
int64
{
maxBigInt
:=
big
.
NewInt
(
max
)
i
,
_
:=
rand
.
Int
(
rand
.
Reader
,
maxBigInt
)
...
...
messageSender.go
0 → 100644
View file @
12e7a5fb
/*
* @Author: cuiweiqiang
* @Date: 2019-01-15 15:06
*/
package
utils
import
(
"encoding/json"
"github.com/sirupsen/logrus"
"strconv"
"time"
)
const
(
CUSTOMURI
=
"https://api.weixin.qq.com/cgi-bin/message/custom/send"
MASSURI
=
"https://api.weixin.qq.com/cgi-bin/message/mass/send"
MASSPREVIEWURI
=
"https://api.weixin.qq.com/cgi-bin/message/mass/preview"
TEMPLATEURI
=
"https://api.weixin.qq.com/cgi-bin/message/template/send"
SUBSCRIBEURI
=
"https://api.weixin.qq.com/cgi-bin/message/template/subscribe"
NationCode
=
"86"
SMSSendUri
=
"https://yun.tim.qq.com/v5/tlssmssvr/sendsms"
)
type
WxResponse
struct
{
Errcode
int
`json:"errcode"`
Errmsg
string
`json:"errmsg"`
Msgid
float32
`json:"msgid"`
}
func
(
wr
*
WxResponse
)
Tofields
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"errcode"
]
=
wr
.
Errcode
fields
[
"errmsg"
]
=
wr
.
Errmsg
fields
[
"msgid"
]
=
wr
.
Msgid
return
}
type
CustomerMessageInterface
interface
{
GetToUser
()
interface
{}
ToFieldsCustomerMessage
()
logrus
.
Fields
}
func
CustomerMessageSender
(
accessToken
string
,
cmi
CustomerMessageInterface
,
preview
bool
)
(
int
,
string
)
{
log
:=
Logger
.
WithFields
(
logrus
.
Fields
{
"position"
:
"messageSender.go"
,
"func"
:
"CustomerMessageSender"
,
"accessToken"
:
accessToken
,
})
var
sendUri
string
switch
v
:=
cmi
.
GetToUser
()
.
(
type
)
{
case
string
:
sendUri
=
CUSTOMURI
case
[]
string
:
sendUri
=
MASSURI
default
:
log
.
Print
(
"type "
+
v
.
(
string
)
+
" is not in list"
)
}
if
preview
{
sendUri
=
MASSPREVIEWURI
}
uri
:=
sendUri
+
"?access_token="
+
accessToken
data
,
_
:=
json
.
Marshal
(
cmi
)
body
,
_
,
err
:=
FasthttpPost
(
uri
,
data
)
var
result
WxResponse
if
err
!=
nil
{
log
.
WithFields
(
cmi
.
ToFieldsCustomerMessage
())
.
Warn
(
"send message to user fail"
)
result
.
Errcode
=
-
100001
result
.
Errmsg
=
err
.
Error
()
}
else
{
_
=
json
.
Unmarshal
(
body
,
&
result
)
log
.
WithFields
(
result
.
Tofields
())
.
WithFields
(
cmi
.
ToFieldsCustomerMessage
())
.
Info
(
"send message to user"
)
}
return
result
.
Errcode
,
result
.
Errmsg
}
func
TemplateMessageSender
(
accessToken
string
,
tm
TemplateMessage
)
(
int
,
string
)
{
log
:=
Logger
.
WithFields
(
logrus
.
Fields
{
"position"
:
"messageSender.go"
,
"func"
:
"TemplateMessageSender"
,
"accessToken"
:
accessToken
,
})
uri
:=
TEMPLATEURI
+
"?access_token="
+
accessToken
data
,
_
:=
json
.
Marshal
(
tm
)
body
,
_
,
err
:=
FasthttpPost
(
uri
,
data
)
var
result
WxResponse
if
err
!=
nil
{
log
.
WithFields
(
tm
.
ToFields
())
.
Warn
(
"send TemplateMessage to user fail"
)
result
.
Errcode
=
-
100001
result
.
Errmsg
=
err
.
Error
()
}
else
{
_
=
json
.
Unmarshal
(
body
,
&
result
)
log
.
WithFields
(
result
.
Tofields
())
.
WithFields
(
tm
.
ToFields
())
.
Info
(
"send TemplateMessage to user"
)
}
return
result
.
Errcode
,
result
.
Errmsg
}
func
SubscribeMessageSender
(
accessToken
string
,
sm
SubscribeMessage
)
(
int
,
string
)
{
log
:=
Logger
.
WithFields
(
logrus
.
Fields
{
"position"
:
"messageSender.go"
,
"func"
:
"SubscribeMessageSender"
,
"accessToken"
:
accessToken
,
})
uri
:=
SUBSCRIBEURI
+
"?access_token="
+
accessToken
data
,
_
:=
json
.
Marshal
(
sm
)
body
,
_
,
err
:=
FasthttpPost
(
uri
,
data
)
var
result
WxResponse
if
err
!=
nil
{
log
.
WithFields
(
sm
.
ToFields
())
.
Warn
(
"send SubscribeMessage to user fail"
)
result
.
Errcode
=
-
100001
result
.
Errmsg
=
err
.
Error
()
}
else
{
_
=
json
.
Unmarshal
(
body
,
&
result
)
log
.
WithFields
(
result
.
Tofields
())
.
WithFields
(
sm
.
ToFields
())
.
Info
(
"send SubscribeMessage to user"
)
}
return
result
.
Errcode
,
result
.
Errmsg
}
func
SmsMessageSender
(
sm
SmsMessage
,
appId
int
,
appKey
string
)
(
int
,
string
)
{
log
:=
Logger
.
WithFields
(
logrus
.
Fields
{
"position"
:
"messageSender.go"
,
"func"
:
"SmsMessageSender"
,
"accessToken"
:
appId
,
})
randInt
:=
RandInt
()
uri
:=
SMSSendUri
+
"?sdkappid="
+
strconv
.
Itoa
(
appId
)
+
"&random="
+
strconv
.
Itoa
(
randInt
)
sm
.
Sign
=
""
sm
.
Ext
=
""
sm
.
Extend
=
""
sm
.
Time
=
time
.
Now
()
.
Unix
()
sm
.
Sig
=
SmsDoSign
(
appKey
,
[]
string
{
sm
.
Tel
.
Mobile
},
sm
.
Time
,
randInt
)
sm
.
Tel
.
Nationcode
=
NationCode
reqBody
,
_
:=
json
.
Marshal
(
sm
)
body
,
statusCode
,
err
:=
FasthttpPost
(
uri
,
reqBody
)
if
err
!=
nil
{
log
.
WithField
(
"error"
,
err
)
.
Error
(
"SmsMessageSender Error!"
)
}
else
{
log
.
WithFields
(
sm
.
ToFields
())
.
WithField
(
"response"
,
string
(
body
))
.
Info
(
"SmsMessageSender send success !"
)
}
return
statusCode
,
""
}
messageStructs.go
0 → 100644
View file @
12e7a5fb
/*
* @Author: cuiweiqiang
* @Date: 2019-01-15 15:06
*/
package
utils
import
(
"encoding/json"
"github.com/sirupsen/logrus"
)
type
TemplateMessage
struct
{
Touser
string
`json:"touser" bson:"touser"`
// 接收者openid
TemplateId
string
`json:"template_id" bson:"template_id"`
// 模板ID
Url
string
`json:"url" bson:"url"`
// 模板跳转链接(海外帐号没有跳转能力)
Miniprogram
struct
{
Appid
string
`json:"appid" bson:"appid"`
Pagepath
string
`json:"pagepath" bson:"pagepath"`
// 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),暂不支持小游戏
}
`json:"miniprogram" bson:"miniprogram"`
// 跳小程序所需数据,不需跳小程序可不用传该数据
Data
map
[
string
]
struct
{
Value
string
`json:"value" bson:"value"`
Color
string
`json:"color" bson:"color"`
// 模板内容字体颜色,不填默认为黑色
}
`json:"data" bson:"data"`
// 模板数据
}
func
(
tm
*
TemplateMessage
)
ToFields
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
tm
.
Touser
fields
[
"template_id"
]
=
tm
.
TemplateId
fields
[
"url"
]
=
tm
.
Url
fields
[
"miniprogram.Appid"
]
=
tm
.
Miniprogram
.
Appid
fields
[
"miniprogram.Pagepath"
]
=
tm
.
Miniprogram
.
Pagepath
data
,
_
:=
json
.
Marshal
(
tm
.
Data
)
fields
[
"data"
]
=
data
return
}
type
CustomerMessage
struct
{
Touser
interface
{}
`json:"touser" bson:"touser"`
// 接收者openid or openids[]
MsgType
string
`json:"msgtype" bson:"msgtype"`
// enum: ['text', 'image', 'voice', 'video', 'music', 'news', 'mpnews', 'wxcard', 'miniprogrampage']
}
func
(
cm
*
CustomerMessage
)
GetToUser
()
interface
{}
{
return
cm
.
Touser
}
type
TextCustomerMessage
struct
{
CustomerMessage
Text
struct
{
Content
string
`json:"content" bson:"content"`
// 文本消息内容
}
`json:"text" bson:"text"`
// 发送文本消息
}
func
(
tcm
*
TextCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
tcm
.
Touser
fields
[
"msgtype"
]
=
tcm
.
MsgType
fields
[
"text.content"
]
=
tcm
.
Text
.
Content
return
}
type
ImageCustomerMessage
struct
{
CustomerMessage
Image
struct
{
MediaId
string
`json:"media_id" bson:"media_id"`
// 发送的图片/语音/视频/图文消息(点击跳转到图文消息页)的媒体ID
}
`json:"image" bson:"image"`
// 发送图片消息
}
func
(
icm
*
ImageCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
icm
.
Touser
fields
[
"msgtype"
]
=
icm
.
MsgType
fields
[
"image.media_id"
]
=
icm
.
Image
.
MediaId
return
}
type
VoiceCustomerMessage
struct
{
CustomerMessage
Voice
struct
{
MediaId
string
`json:"media_id" bson:"media_id"`
// 发送的图片/语音/视频/图文消息(点击跳转到图文消息页)的媒体ID
}
`json:"voice" bson:"voice"`
// 发送语音消息
}
func
(
vcm
*
VoiceCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
vcm
.
Touser
fields
[
"msgtype"
]
=
vcm
.
MsgType
fields
[
"voice.media_id"
]
=
vcm
.
Voice
.
MediaId
return
}
type
VideoCustomerMessage
struct
{
CustomerMessage
Video
struct
{
MediaId
string
`json:"media_id" bson:"media_id"`
// 发送的图片/语音/视频/图文消息(点击跳转到图文消息页)的媒体ID
ThumbMediaId
string
`json:"thumb_media_id" bson:"thumb_media_id"`
// 缩略图/小程序卡片图片的媒体ID,小程序卡片图片建议大小为520*416
Title
string
`json:"title" bson:"title"`
// 图文消息/视频消息/音乐消息/小程序卡片的标题
Description
string
`json:"description" bson:"description"`
// 图文消息/视频消息/音乐消息的描述
}
`json:"video" bson:"video"`
// 发送视频消息
}
func
(
vcm
*
VideoCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
vcm
.
Touser
fields
[
"msgtype"
]
=
vcm
.
MsgType
fields
[
"video.media_id"
]
=
vcm
.
Video
.
MediaId
fields
[
"video.thumb_media_id"
]
=
vcm
.
Video
.
ThumbMediaId
fields
[
"video.title"
]
=
vcm
.
Video
.
Title
fields
[
"video.description"
]
=
vcm
.
Video
.
Description
return
}
type
MusicCustomerMessage
struct
{
CustomerMessage
Music
struct
{
Title
string
`json:"title" bson:"title"`
// 图文消息/视频消息/音乐消息/小程序卡片的标题
Description
string
`json:"description" bson:"description"`
// 图文消息/视频消息/音乐消息的描述
Musicurl
string
`json:"musicurl" bson:"musicurl"`
// 音乐链接
Hqmusicurl
string
`json:"hqmusicurl" bson:"hqmusicurl"`
// 高品质音乐链接,wifi环境优先使用该链接播放音乐
ThumbMediaId
string
`json:"thumb_media_id" bson:"thumb_media_id"`
// 缩略图/小程序卡片图片的媒体ID,小程序卡片图片建议大小为520*416
}
`json:"music" bson:"music"`
// 发送音乐消息
}
func
(
mcm
*
MusicCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
mcm
.
Touser
fields
[
"msgtype"
]
=
mcm
.
MsgType
fields
[
"music.title"
]
=
mcm
.
Music
.
Title
fields
[
"music.description"
]
=
mcm
.
Music
.
Description
fields
[
"music.musicurl"
]
=
mcm
.
Music
.
Musicurl
fields
[
"music.hqmusicurl"
]
=
mcm
.
Music
.
Hqmusicurl
fields
[
"music.thumb_media_id"
]
=
mcm
.
Music
.
ThumbMediaId
return
}
type
NewsCustomerMessage
struct
{
CustomerMessage
News
struct
{
Articles
[]
struct
{
Title
string
`json:"title" bson:"title"`
// 图文消息/视频消息/音乐消息/小程序卡片的标题
Description
string
`json:"description" bson:"description"`
// 图文消息/视频消息/音乐消息的描述
Url
string
`json:"url" bson:"url"`
// 图文消息被点击后跳转的链接
Picurl
string
`json:"picurl" bson:"picurl"`
// 图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图640*320,小图80*80
}
`json:"articles" bson:"articles"`
}
`json:"news" bson:"news"`
// 发送图文消息(点击跳转到外链) 图文消息条数限制在1条以内,注意,如果图文数超过1,则将会返回错误码45008
}
func
(
ncm
*
NewsCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
ncm
.
Touser
fields
[
"msgtype"
]
=
ncm
.
MsgType
return
}
type
MpnewsCustomerMessage
struct
{
CustomerMessage
Mpnews
struct
{
MediaId
string
`json:"media_id" bson:"media_id"`
}
`json:"mpnews" bson:"mpnews"`
// 发送图文消息(点击跳转到图文消息页面) 图文消息条数限制在1条以内,注意,如果图文数超过1,则将会返回错误码45008
}
func
(
mcm
*
MpnewsCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
mcm
.
Touser
fields
[
"msgtype"
]
=
mcm
.
MsgType
fields
[
"mpnews.media_id"
]
=
mcm
.
Mpnews
.
MediaId
return
}
type
WxcardCustomerMessage
struct
{
CustomerMessage
Wxcard
struct
{
CardId
string
`json:"card_id" bson:"card_id"`
}
`json:"wxcard" bson:"wxcard"`
// 发送卡券
}
func
(
wcm
*
WxcardCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
wcm
.
Touser
fields
[
"msgtype"
]
=
wcm
.
MsgType
fields
[
"wxcard.card_id"
]
=
wcm
.
Wxcard
.
CardId
return
}
type
MiniprogrampageCustomerMessage
struct
{
CustomerMessage
Miniprogrampage
struct
{
Title
string
`json:"title" bson:"title"`
// 图文消息/视频消息/音乐消息/小程序卡片的标题
Appid
string
`json:"appid" bson:"appid"`
// 小程序的appid,要求小程序的appid需要与公众号有关联关系
Pagepath
string
`json:"pagepath" bson:"pagepath"`
// 小程序的页面路径,跟app.json对齐,支持参数,比如pages/index/index?foo=bar
ThumbMediaId
string
`json:"thumb_media_id" bson:"thumb_media_id"`
// 缩略图/小程序卡片图片的媒体ID,小程序卡片图片建议大小为520*416
}
`json:"miniprogrampage" bson:"miniprogrampage"`
// 发送小程序卡片
}
func
(
mcm
*
MiniprogrampageCustomerMessage
)
ToFieldsCustomerMessage
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"touser"
]
=
mcm
.
Touser
fields
[
"msgtype"
]
=
mcm
.
MsgType
fields
[
"miniprogrampage.title"
]
=
mcm
.
Miniprogrampage
.
Title
fields
[
"miniprogrampage.appid"
]
=
mcm
.
Miniprogrampage
.
Appid
fields
[
"miniprogrampage.pagepath"
]
=
mcm
.
Miniprogrampage
.
Pagepath
fields
[
"miniprogrampage.thumb_media_id"
]
=
mcm
.
Miniprogrampage
.
ThumbMediaId
return
}
// 订阅消息
type
SubscribeMessage
struct
{
Appid
string
`json:"appid" bson:"appid"`
// 所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,并且小程序要求是已发布的)
TemplateId
string
`json:"template_id" bson:"template_id"`
// 订阅消息模板ID
Url
string
`json:"url" bson:"url"`
// 点击消息跳转的链接,需要有ICP备案
Miniprogram
string
`json:"miniprogram" bson:"miniprogram"`
// 跳小程序所需数据,不需跳小程序可不用传该数据
Pagepath
string
`json:"pagepath" bson:"pagepath"`
// 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),暂不支持小游戏
Scene
string
`json:"scene" bson:"scene"`
// 订阅场景值
Title
string
`json:"title" bson:"title"`
// 消息标题,15字以内
Data
struct
{
Content
struct
{
Value
string
`json:"value" bson:"value"`
Color
string
`json:"color" bson:"color"`
}
`json:"content" bson:"content"`
}
`json:"data" bson:"data"`
// 消息正文,value为消息内容文本(200字以内),没有固定格式,可用\n换行,color为整段消息内容的字体颜色(目前仅支持整段消息为一种颜色)
}
func
(
subM
*
SubscribeMessage
)
ToFields
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"appid"
]
=
subM
.
Appid
fields
[
"template_id"
]
=
subM
.
TemplateId
fields
[
"url"
]
=
subM
.
Url
fields
[
"miniprogram"
]
=
subM
.
Miniprogram
fields
[
"pagepath"
]
=
subM
.
Pagepath
fields
[
"scene"
]
=
subM
.
Scene
fields
[
"title"
]
=
subM
.
Title
fields
[
"data.content"
]
=
subM
.
Data
.
Content
return
}
type
Tel
struct
{
Mobile
string
`json:"mobile"`
Nationcode
string
`json:"nationcode"`
}
// 短信消息
type
SmsMessage
struct
{
Ext
string
`json:"ext"`
Extend
string
`json:"extend"`
Sig
string
`json:"sig"`
Sign
string
`json:"sign"`
Tel
Tel
`json:"tel"`
TplId
string
`json:"tpl_id" bson:"tpl_id"`
// 短信模板 ID
Params
[]
string
`json:"params" bson:"params"`
// 短信参数
Time
int64
`json:"time"`
}
func
(
sm
*
SmsMessage
)
ToFields
()
(
fields
logrus
.
Fields
)
{
fields
=
make
(
logrus
.
Fields
)
fields
[
"ext"
]
=
sm
.
Ext
fields
[
"extend"
]
=
sm
.
Extend
fields
[
"sig"
]
=
sm
.
Sig
fields
[
"sign"
]
=
sm
.
Sign
fields
[
"tel"
]
=
sm
.
Tel
fields
[
"tpl_id"
]
=
sm
.
TplId
fields
[
"params"
]
=
sm
.
Params
fields
[
"time"
]
=
sm
.
Time
return
}
tools.go
View file @
12e7a5fb
...
...
@@ -6,11 +6,14 @@
package
utils
import
(
"crypto/sha256"
"database/sql"
"encoding/hex"
"errors"
"github.com/sirupsen/logrus"
"io"
"reflect"
"strconv"
)
func
IOClose
(
fio
io
.
ReadCloser
)
{
...
...
@@ -21,10 +24,35 @@ func IOClose(fio io.ReadCloser) {
err
:=
fio
.
Close
()
if
err
!=
nil
{
log
.
WithField
(
"error"
,
err
)
.
Error
(
"io.ReadCloser close error"
)
log
.
WithField
(
"error"
,
err
)
.
Error
(
"io.ReadCloser close error"
)
}
}
func
SmsDoSign
(
appKey
string
,
tels
[]
string
,
time
int64
,
rand
int
)
string
{
var
err
error
h
:=
sha256
.
New
()
if
len
(
tels
)
!=
0
{
var
telStr
string
for
_
,
v
:=
range
tels
{
telStr
=
telStr
+
v
+
","
}
telStr
=
telStr
[
:
len
(
telStr
)
-
1
]
_
,
err
=
h
.
Write
([]
byte
(
"appkey="
+
appKey
+
"&random="
+
strconv
.
Itoa
(
rand
)
+
"&time="
+
strconv
.
FormatInt
(
time
,
10
)
+
"&mobile="
+
telStr
))
if
err
!=
nil
{
return
""
}
}
else
{
_
,
err
=
h
.
Write
([]
byte
(
"appkey="
+
appKey
+
"&random="
+
strconv
.
Itoa
(
rand
)
+
"&time="
+
strconv
.
FormatInt
(
time
,
10
)))
if
err
!=
nil
{
return
""
}
}
return
hex
.
EncodeToString
(
h
.
Sum
(
nil
))
}
// Deep Copy
func
Copy
(
toValue
interface
{},
fromValue
interface
{})
(
err
error
)
{
var
(
...
...
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