github.com/line/line-bot-sdk-go/v7@v7.21.0/linebot/client.go (about) 1 // Copyright 2016 LINE Corporation 2 // 3 // LINE Corporation licenses this file to you under the Apache License, 4 // version 2.0 (the "License"); you may not use this file except in compliance 5 // with the License. You may obtain a copy of the License at: 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations 13 // under the License. 14 15 package linebot 16 17 import ( 18 "bytes" 19 "context" 20 "errors" 21 "fmt" 22 "io" 23 "mime/multipart" 24 "net/http" 25 "net/textproto" 26 "net/url" 27 "path" 28 "time" 29 ) 30 31 // APIEndpoint constants 32 const ( 33 APIEndpointBase = "https://api.line.me" 34 APIEndpointBaseData = "https://api-data.line.me" 35 36 APIEndpointPushMessage = "/v2/bot/message/push" 37 APIEndpointBroadcastMessage = "/v2/bot/message/broadcast" 38 APIEndpointReplyMessage = "/v2/bot/message/reply" 39 APIEndpointMulticast = "/v2/bot/message/multicast" 40 APIEndpointNarrowcast = "/v2/bot/message/narrowcast" 41 APIEndpointValidatePushMessage = "/v2/bot/message/validate/push" 42 APIEndpointValidateBroadcastMessage = "/v2/bot/message/validate/broadcast" 43 APIEndpointValidateReplyMessage = "/v2/bot/message/validate/reply" 44 APIEndpointValidateMulticastMessage = "/v2/bot/message/validate/multicast" 45 APIEndpointValidateNarrowcastMessage = "/v2/bot/message/validate/narrowcast" 46 APIEndpointGetMessageContent = "/v2/bot/message/%s/content" 47 APIEndpointGetMessageQuota = "/v2/bot/message/quota" 48 APIEndpointGetMessageConsumption = "/v2/bot/message/quota/consumption" 49 APIEndpointGetMessageQuotaConsumption = "/v2/bot/message/quota/consumption" 50 APIEndpointLeaveGroup = "/v2/bot/group/%s/leave" 51 APIEndpointLeaveRoom = "/v2/bot/room/%s/leave" 52 APIEndpointGetProfile = "/v2/bot/profile/%s" 53 APIEndpointGetFollowerIDs = "/v2/bot/followers/ids" 54 APIEndpointGetGroupMemberProfile = "/v2/bot/group/%s/member/%s" 55 APIEndpointGetRoomMemberProfile = "/v2/bot/room/%s/member/%s" 56 APIEndpointGetGroupMemberIDs = "/v2/bot/group/%s/members/ids" 57 APIEndpointGetRoomMemberIDs = "/v2/bot/room/%s/members/ids" 58 APIEndpointGetGroupMemberCount = "/v2/bot/group/%s/members/count" 59 APIEndpointGetRoomMemberCount = "/v2/bot/room/%s/members/count" 60 APIEndpointGetGroupSummary = "/v2/bot/group/%s/summary" 61 APIEndpointCreateRichMenu = "/v2/bot/richmenu" 62 APIEndpointGetRichMenu = "/v2/bot/richmenu/%s" 63 APIEndpointListRichMenu = "/v2/bot/richmenu/list" 64 APIEndpointDeleteRichMenu = "/v2/bot/richmenu/%s" 65 APIEndpointGetUserRichMenu = "/v2/bot/user/%s/richmenu" 66 APIEndpointLinkUserRichMenu = "/v2/bot/user/%s/richmenu/%s" 67 APIEndpointUnlinkUserRichMenu = "/v2/bot/user/%s/richmenu" 68 APIEndpointSetDefaultRichMenu = "/v2/bot/user/all/richmenu/%s" 69 APIEndpointDefaultRichMenu = "/v2/bot/user/all/richmenu" // Get: GET / Delete: DELETE 70 APIEndpointDownloadRichMenuImage = "/v2/bot/richmenu/%s/content" // Download: GET / Upload: POST 71 APIEndpointUploadRichMenuImage = "/v2/bot/richmenu/%s/content" // Download: GET / Upload: POST 72 APIEndpointBulkLinkRichMenu = "/v2/bot/richmenu/bulk/link" 73 APIEndpointBulkUnlinkRichMenu = "/v2/bot/richmenu/bulk/unlink" 74 APIEndpointValidateRichMenuObject = "/v2/bot/richmenu/validate" 75 76 APIEndpointCreateRichMenuAlias = "/v2/bot/richmenu/alias" 77 APIEndpointGetRichMenuAlias = "/v2/bot/richmenu/alias/%s" 78 APIEndpointUpdateRichMenuAlias = "/v2/bot/richmenu/alias/%s" 79 APIEndpointDeleteRichMenuAlias = "/v2/bot/richmenu/alias/%s" 80 APIEndpointListRichMenuAlias = "/v2/bot/richmenu/alias/list" 81 82 APIEndpointGetAllLIFFApps = "/liff/v1/apps" 83 APIEndpointAddLIFFApp = "/liff/v1/apps" 84 APIEndpointUpdateLIFFApp = "/liff/v1/apps/%s/view" 85 APIEndpointDeleteLIFFApp = "/liff/v1/apps/%s" 86 87 APIEndpointLinkToken = "/v2/bot/user/%s/linkToken" 88 89 APIEndpointGetMessageDelivery = "/v2/bot/message/delivery/%s" 90 APIEndpointGetMessageProgress = "/v2/bot/message/progress/%s" 91 APIEndpointInsight = "/v2/bot/insight/%s" 92 APIEndpointGetBotInfo = "/v2/bot/info" 93 94 APIEndpointIssueAccessToken = "/v2/oauth/accessToken" 95 APIEndpointRevokeAccessToken = "/v2/oauth/revoke" 96 APIEndpointVerifyAccessToken = "/v2/oauth/verify" 97 98 APIEndpointIssueAccessTokenV2 = "/oauth2/v2.1/token" 99 APIEndpointGetAccessTokensV2 = "/oauth2/v2.1/tokens/kid" 100 APIEndpointRevokeAccessTokenV2 = "/oauth2/v2.1/revoke" 101 102 APIEndpointGetWebhookInfo = "/v2/bot/channel/webhook/endpoint" 103 APIEndpointSetWebhookEndpoint = "/v2/bot/channel/webhook/endpoint" 104 APIEndpointTestWebhook = "/v2/bot/channel/webhook/test" 105 106 APIAudienceGroupUpload = "/v2/bot/audienceGroup/upload" 107 APIAudienceGroupUploadByFile = "/v2/bot/audienceGroup/upload/byFile" 108 APIAudienceGroupClick = "/v2/bot/audienceGroup/click" 109 APIAudienceGroupIMP = "/v2/bot/audienceGroup/imp" 110 APIAudienceGroupUpdateDescription = "/v2/bot/audienceGroup/%d/updateDescription" 111 APIAudienceGroupActivate = "/v2/bot/audienceGroup/%d/activate" 112 APIAudienceGroup = "/v2/bot/audienceGroup/%d" 113 APIAudienceGroupList = "/v2/bot/audienceGroup/list" 114 APIAudienceGroupAuthorityLevel = "/v2/bot/audienceGroup/authorityLevel" 115 ) 116 117 // Client type 118 type Client struct { 119 channelSecret string 120 channelToken string 121 endpointBase *url.URL // default APIEndpointBase 122 endpointBaseData *url.URL // default APIEndpointBaseData 123 httpClient *http.Client // default http.DefaultClient 124 retryKeyID string // X-Line-Retry-Key allows you to safely retry API requests without duplicating messages 125 } 126 127 // ClientOption type 128 type ClientOption func(*Client) error 129 130 // New returns a new bot client instance. 131 func New(channelSecret, channelToken string, options ...ClientOption) (*Client, error) { 132 if channelSecret == "" { 133 return nil, errors.New("missing channel secret") 134 } 135 if channelToken == "" { 136 return nil, errors.New("missing channel access token") 137 } 138 c := &Client{ 139 channelSecret: channelSecret, 140 channelToken: channelToken, 141 httpClient: http.DefaultClient, 142 } 143 for _, option := range options { 144 err := option(c) 145 if err != nil { 146 return nil, err 147 } 148 } 149 if c.endpointBase == nil { 150 u, err := url.ParseRequestURI(APIEndpointBase) 151 if err != nil { 152 return nil, err 153 } 154 c.endpointBase = u 155 } 156 if c.endpointBaseData == nil { 157 u, err := url.ParseRequestURI(APIEndpointBaseData) 158 if err != nil { 159 return nil, err 160 } 161 c.endpointBaseData = u 162 } 163 return c, nil 164 } 165 166 // WithHTTPClient function 167 func WithHTTPClient(c *http.Client) ClientOption { 168 return func(client *Client) error { 169 client.httpClient = c 170 return nil 171 } 172 } 173 174 // WithEndpointBase function 175 func WithEndpointBase(endpointBase string) ClientOption { 176 return func(client *Client) error { 177 u, err := url.ParseRequestURI(endpointBase) 178 if err != nil { 179 return err 180 } 181 client.endpointBase = u 182 return nil 183 } 184 } 185 186 // WithEndpointBaseData function 187 func WithEndpointBaseData(endpointBaseData string) ClientOption { 188 return func(client *Client) error { 189 u, err := url.ParseRequestURI(endpointBaseData) 190 if err != nil { 191 return err 192 } 193 client.endpointBaseData = u 194 return nil 195 } 196 } 197 198 func (client *Client) url(base *url.URL, endpoint string) string { 199 u := *base 200 u.Path = path.Join(u.Path, endpoint) 201 return u.String() 202 } 203 204 func (client *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) { 205 req.Header.Set("Authorization", "Bearer "+client.channelToken) 206 req.Header.Set("User-Agent", "LINE-BotSDK-Go/"+version) 207 if len(client.retryKeyID) > 0 { 208 req.Header.Set("X-Line-Retry-Key", client.retryKeyID) 209 } 210 if ctx != nil { 211 req = req.WithContext(ctx) 212 } 213 return client.httpClient.Do(req) 214 } 215 216 func (client *Client) get(ctx context.Context, base *url.URL, endpoint string, query url.Values) (*http.Response, error) { 217 req, err := http.NewRequest(http.MethodGet, client.url(base, endpoint), nil) 218 if err != nil { 219 return nil, err 220 } 221 if query != nil { 222 req.URL.RawQuery = query.Encode() 223 } 224 return client.do(ctx, req) 225 } 226 227 func (client *Client) post(ctx context.Context, endpoint string, body io.Reader) (*http.Response, error) { 228 req, err := http.NewRequest(http.MethodPost, client.url(client.endpointBase, endpoint), body) 229 if err != nil { 230 return nil, err 231 } 232 req.Header.Set("Content-Type", "application/json; charset=UTF-8") 233 return client.do(ctx, req) 234 } 235 236 func (client *Client) postForm(ctx context.Context, endpoint string, body io.Reader) (*http.Response, error) { 237 req, err := http.NewRequest("POST", client.url(client.endpointBase, endpoint), body) 238 if err != nil { 239 return nil, err 240 } 241 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 242 return client.do(ctx, req) 243 } 244 245 func (client *Client) postFormFile(ctx context.Context, endpoint string, values map[string]io.Reader) (*http.Response, error) { 246 b, contentType, err := uploadFile(values) 247 if err != nil { 248 return nil, err 249 } 250 req, err := http.NewRequest(http.MethodPost, client.url(client.endpointBaseData, endpoint), &b) 251 if err != nil { 252 return nil, err 253 } 254 req.Header.Set("Content-Type", contentType) 255 return client.do(ctx, req) 256 } 257 258 func (client *Client) put(ctx context.Context, endpoint string, body io.Reader) (*http.Response, error) { 259 req, err := http.NewRequest(http.MethodPut, client.url(client.endpointBase, endpoint), body) 260 if err != nil { 261 return nil, err 262 } 263 req.Header.Set("Content-Type", "application/json; charset=UTF-8") 264 return client.do(ctx, req) 265 } 266 267 func (client *Client) putFormFile(ctx context.Context, endpoint string, values map[string]io.Reader) (*http.Response, error) { 268 b, contentType, err := uploadFile(values) 269 if err != nil { 270 return nil, err 271 } 272 req, err := http.NewRequest(http.MethodPut, client.url(client.endpointBaseData, endpoint), &b) 273 if err != nil { 274 return nil, err 275 } 276 req.Header.Set("Content-Type", contentType) 277 return client.do(ctx, req) 278 } 279 280 func (client *Client) delete(ctx context.Context, endpoint string) (*http.Response, error) { 281 req, err := http.NewRequest(http.MethodDelete, client.url(client.endpointBase, endpoint), nil) 282 if err != nil { 283 return nil, err 284 } 285 return client.do(ctx, req) 286 } 287 288 func (client *Client) setRetryKey(retryKey string) { 289 client.retryKeyID = retryKey 290 } 291 292 func closeResponse(res *http.Response) error { 293 defer res.Body.Close() 294 _, err := io.Copy(io.Discard, res.Body) 295 return err 296 } 297 298 func uploadFile(values map[string]io.Reader) (bytes.Buffer, string, error) { 299 var ( 300 b bytes.Buffer 301 err error 302 ) 303 w := multipart.NewWriter(&b) 304 for key, r := range values { 305 var fw io.Writer 306 if x, ok := r.(io.Closer); ok { 307 defer x.Close() 308 } 309 if _, ok := r.(*bytes.Buffer); ok { 310 h := make(textproto.MIMEHeader) 311 h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.txt"`, key, time.Now().Format("20060102150405"))) 312 h.Set("Content-Type", "text/plain") 313 if fw, err = w.CreatePart(h); err != nil { 314 return b, "", err 315 } 316 } else { 317 if fw, err = w.CreateFormField(key); err != nil { 318 return b, "", err 319 } 320 } 321 if _, err := io.Copy(fw, r); err != nil { 322 return b, "", err 323 } 324 } 325 w.Close() 326 return b, w.FormDataContentType(), nil 327 }