Merge branch 'master' into telegram-comments

This commit is contained in:
Wim
2021-12-12 01:23:29 +01:00
committed by GitHub
236 changed files with 15848 additions and 9414 deletions

View File

@@ -188,7 +188,22 @@ linters:
- exhaustivestruct
- forbidigo
- wrapcheck
- varnamelen
- ireturn
- errorlint
- tparallel
- wrapcheck
- paralleltest
- makezero
- thelper
- cyclop
- revive
- importas
- gomoddirectives
- promlinter
- tagliatelle
- errname
- typecheck
# rules to deal with reported isues
issues:
# List of regexps of issue texts to exclude, empty list by default.

View File

@@ -9,7 +9,7 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func (b *Btelegram) handleUpdate(rmsg *config.Message, message, posted, edited *tgbotapi.Message) *tgbotapi.Message {
@@ -94,7 +94,7 @@ func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Messag
// handleUsername handles the correct setting of the username
func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Message) {
if message.From != nil {
rmsg.UserID = strconv.Itoa(message.From.ID)
rmsg.UserID = strconv.FormatInt(message.From.ID, 10)
if b.GetBool("UseFirstName") {
rmsg.Username = message.From.FirstName
}
@@ -167,7 +167,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
// channels don't have (always?) user information. see #410
if message.From != nil {
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.FormatInt(message.From.ID, 10), b.General)
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
@@ -180,42 +180,44 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
// handleDownloadAvatar downloads the avatar of userid from channel
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
// logs an error message if it fails
func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
func (b *Btelegram) handleDownloadAvatar(userid int64, channel string) {
rmsg := config.Message{
Username: "system",
Text: "avatar",
Channel: channel,
Account: b.Account,
UserID: strconv.Itoa(userid),
UserID: strconv.FormatInt(userid, 10),
Event: config.EventAvatarDownload,
Extra: make(map[string][]interface{}),
}
if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok {
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
if _, ok := b.avatarMap[strconv.FormatInt(userid, 10)]; ok {
return
}
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
if err != nil {
b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
}
if len(photos.Photos) > 0 {
photo := photos.Photos[0][0]
url := b.getFileDirectURL(photo.FileID)
name := strconv.FormatInt(userid, 10) + ".png"
b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
if err != nil {
b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
b.Log.Error(err)
return
}
if len(photos.Photos) > 0 {
photo := photos.Photos[0][0]
url := b.getFileDirectURL(photo.FileID)
name := strconv.Itoa(userid) + ".png"
b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
if err != nil {
b.Log.Error(err)
return
}
data, err := helper.DownloadFile(url)
if err != nil {
b.Log.Errorf("download %s failed %#v", url, err)
return
}
helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
b.Remote <- rmsg
data, err := helper.DownloadFile(url)
if err != nil {
b.Log.Errorf("download %s failed %#v", url, err)
return
}
helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
b.Remote <- rmsg
}
}
@@ -272,7 +274,7 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa
name = message.Document.FileName
text = " " + message.Document.FileName + " : " + url
case message.Photo != nil:
photos := *message.Photo
photos := message.Photo
size = photos[len(photos)-1].FileSize
text, name, url = b.getDownloadInfo(photos[len(photos)-1].FileID, "", true)
}
@@ -331,11 +333,15 @@ func (b *Btelegram) handleDelete(msg *config.Message, chatid int64) (string, err
if msg.ID == "" {
return "", nil
}
msgid, err := strconv.Atoi(msg.ID)
if err != nil {
return "", err
}
_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
cfg := tgbotapi.NewDeleteMessage(chatid, msgid)
_, err = b.c.Send(cfg)
return "", err
}
@@ -383,23 +389,23 @@ func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) string {
}
switch filepath.Ext(fi.Name) {
case ".jpg", ".jpe", ".png":
pc := tgbotapi.NewPhotoUpload(chatid, file)
pc := tgbotapi.NewPhoto(chatid, file)
pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = pc
case ".mp4", ".m4v":
vc := tgbotapi.NewVideoUpload(chatid, file)
vc := tgbotapi.NewVideo(chatid, file)
vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = vc
case ".mp3", ".oga":
ac := tgbotapi.NewAudioUpload(chatid, file)
ac := tgbotapi.NewAudio(chatid, file)
ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = ac
case ".ogg":
voc := tgbotapi.NewVoiceUpload(chatid, file)
voc := tgbotapi.NewVoice(chatid, file)
voc.Caption, voc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = voc
default:
dc := tgbotapi.NewDocumentUpload(chatid, file)
dc := tgbotapi.NewDocument(chatid, file)
dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
c = dc
}
@@ -436,10 +442,10 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
return
}
var indexMovedBy = 0
indexMovedBy := 0
// for now only do URL replacements
for _, e := range *message.Entities {
for _, e := range message.Entities {
if e.Type == "text_link" {
url, err := e.ParseURL()
if err != nil {
@@ -456,14 +462,14 @@ func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Messa
}
if e.Type == "code" {
var offset = e.Offset + indexMovedBy
rmsg.Text = rmsg.Text[:offset] + "`" + rmsg.Text[offset:offset + e.Length] + "`" + rmsg.Text[offset + e.Length :]
offset := e.Offset + indexMovedBy
rmsg.Text = rmsg.Text[:offset] + "`" + rmsg.Text[offset:offset+e.Length] + "`" + rmsg.Text[offset+e.Length:]
indexMovedBy += 2
}
if e.Type == "pre" {
var offset = e.Offset + indexMovedBy
rmsg.Text = rmsg.Text[:offset] + "```\n" + rmsg.Text[offset:offset + e.Length] + "\n```" + rmsg.Text[offset + e.Length :]
offset := e.Offset + indexMovedBy
rmsg.Text = rmsg.Text[:offset] + "```\n" + rmsg.Text[offset:offset+e.Length] + "\n```" + rmsg.Text[offset+e.Length:]
indexMovedBy += 8
}
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
const (
@@ -49,11 +49,7 @@ func (b *Btelegram) Connect() error {
}
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates, err := b.c.GetUpdatesChan(u)
if err != nil {
b.Log.Debugf("%#v", err)
return err
}
updates := b.c.GetUpdatesChan(u)
b.Log.Info("Connection succeeded")
go b.handleRecv(updates)
return nil

45
go.mod
View File

@@ -6,30 +6,30 @@ require (
github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c
github.com/SevereCloud/vksdk/v2 v2.10.0
github.com/d5/tengo/v2 v2.8.0
github.com/SevereCloud/vksdk/v2 v2.11.0
github.com/d5/tengo/v2 v2.10.0
github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.5.1
github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81
github.com/gomarkdown/markdown v0.0.0-20210918233619-6c1113f12c4a
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.0
github.com/gomarkdown/markdown v0.0.0-20211207152620-5d6539fd8bfc
github.com/google/gops v0.3.22
github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/golang-lru v0.5.4
github.com/jpillora/backoff v1.0.0
github.com/keybase/go-keybase-chat-bot v0.0.0-20211004153716-fd2ee4d6be11
github.com/keybase/go-keybase-chat-bot v0.0.0-20211201215354-ee4b23828b55
github.com/kyokomi/emoji/v2 v2.2.8
github.com/labstack/echo/v4 v4.6.1
github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b
github.com/lrstanley/girc v0.0.0-20211023233735-147f0ff77566
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696
github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7
github.com/matterbridge/go-xmpp v0.0.0-20210731150933-5702291c239f
github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75
github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
github.com/matterbridge/matterclient v0.0.0-20211024214211-22e762684b4a
github.com/matterbridge/matterclient v0.0.0-20211107234719-faca3cd42315
github.com/mattermost/mattermost-server/v5 v5.39.0
github.com/mattermost/mattermost-server/v6 v6.0.2
github.com/mattermost/mattermost-server/v6 v6.1.0
github.com/mattn/godown v0.0.1
github.com/missdeer/golib v1.0.4
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
@@ -39,15 +39,15 @@ require (
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v1.10.1
github.com/sirupsen/logrus v1.8.1
github.com/slack-go/slack v0.9.5
github.com/slack-go/slack v0.10.0
github.com/spf13/viper v1.9.0
github.com/stretchr/testify v1.7.0
github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50
github.com/vincent-petithory/dataurl v1.0.0
github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/yaegashi/msgraph.go v0.1.4
github.com/zfjagann/golang-ring v0.0.0-20210116075443-7c86fdb43134
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
gomod.garykim.dev/nc-talk v0.3.0
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
layeh.com/gumble v0.0.0-20200818122324-146f9205029b
@@ -65,31 +65,31 @@ require (
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gopackage/ddp v0.0.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kettek/apng v0.0.0-20191108220231-414630eed80f // indirect
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/labstack/gommon v0.3.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect
github.com/mattermost/logr v1.0.13 // indirect
github.com/mattermost/logr/v2 v2.0.10 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattermost/logr/v2 v2.0.15 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.11 // indirect
github.com/minio/minio-go/v7 v7.0.14 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monaco-io/request v1.0.5 // indirect
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
@@ -109,19 +109,18 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tinylib/msgp v1.1.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/wiggin77/cfg v1.0.2 // indirect
github.com/wiggin77/merror v1.0.3 // indirect
github.com/wiggin77/srslog v1.0.1 // indirect
go.uber.org/atomic v1.8.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.17.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211006190231-62292e806868 // indirect
golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect

351
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -1904,7 +1904,7 @@ enable=true
# -------------------------------------------------------------------------------------------------------------------------------------
# xmpp | channel | general | The room name
# -------------------------------------------------------------------------------------------------------------------------------------
# zulip | stream/topic:topic | general/off-topic:food | Do not use the # when specifying a topic
# zulip | stream/topic:topic | general/topic:food | Do not use the # when specifying a topic
# -------------------------------------------------------------------------------------------------------------------------------------
#

View File

@@ -70,7 +70,7 @@ func (myHandler) HandleContactMessage(message whatsapp.ContactMessage) {
fmt.Println(message)
}
func (myHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
func (myHandler) HandleBatteryMessage(message whatsapp.BatteryMessage) {
fmt.Println(message)
}

View File

@@ -526,5 +526,7 @@ func (wac *Conn) Logout() error {
return fmt.Errorf("error writing logout: %v\n", err)
}
wac.loggedIn = false
return nil
}

View File

@@ -48,6 +48,11 @@ linters:
- nilerr
- revive
- wastedassign
- bidichk
- contextcheck
- ireturn
- nilnil
- tenv
# - wrapcheck # TODO: v3 Fix
# - testpackage # TODO: Fix testpackage
@@ -75,6 +80,8 @@ linters:
# - cyclop
# - promlinter
# - tagliatelle
# - errname
# - varnamelen
# depricated
# - maligned

View File

@@ -1,20 +0,0 @@
---
language: go
cache:
directories:
- $HOME/.cache/go-build
- $HOME/gopath/pkg/mod
go:
- 1.x
before_script:
- git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- git describe --tags $(git rev-list --tags --max-count=1) --always
script:
- go test -v -race -coverprofile=coverage.txt -covermode=atomic -p=1 ./...
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -39,6 +39,7 @@ golangci-lint run
# CLIENT_SECRET=""
# USER_TOKEN=""
# WIDGET_TOKEN=""
# MARUSIA_TOKEN=""
# CLIENT_ID="123456"
# GROUP_ID="123456"
# ACCOUNT_ID="123456"
@@ -56,6 +57,7 @@ go test ./...
"go.testEnvVars": {
"SERVICE_TOKEN": "",
"WIDGET_TOKEN": "",
"MARUSIA_TOKEN": "",
"GROUP_TOKEN": "",
"CLIENT_SECRET": "",
"USER_TOKEN": "",

View File

@@ -1,125 +1,124 @@
# VK SDK for Golang
[![Build Status](https://travis-ci.com/SevereCloud/vksdk.svg?branch=master)](https://travis-ci.com/SevereCloud/vksdk)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/SevereCloud/vksdk/v2/v2)](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2?tab=subdirectories)
[![VK Developers](https://img.shields.io/badge/developers-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.com/dev/)
[![codecov](https://codecov.io/gh/SevereCloud/vksdk/branch/master/graph/badge.svg)](https://codecov.io/gh/SevereCloud/vksdk)
[![VK chat](https://img.shields.io/badge/VK%20chat-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.me/join/AJQ1d6Or8Q00Y_CSOESfbqGt)
[![release](https://img.shields.io/github/v/tag/SevereCloud/vksdk?label=release)](https://github.com/SevereCloud/vksdk/releases)
[![license](https://img.shields.io/github/license/SevereCloud/vksdk.svg?maxAge=2592000)](https://github.com/SevereCloud/vksdk/blob/master/LICENSE)
**VK SDK for Golang** ready implementation of the main VK API functions for Go.
[Russian documentation](https://github.com/SevereCloud/vksdk/wiki)
## Features
Version API 5.131.
- [API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api)
- 400+ methods
- Ability to change the request handler
- Ability to modify HTTP client
- Request Limiter
- Token pool
- [Callback API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/callback)
- Tracking tool for users activity in your VK communities
- Supports all events
- Auto setting callback
- [Bots Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-bot)
- Allows you to work with community events in real time
- Supports all events
- Ability to modify HTTP client
- [User Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-user)
- Allows you to work with user events in real time
- Ability to modify HTTP client
- [Streaming API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/streaming)
- Receiving public data from VK by specified keywords
- Ability to modify HTTP client
- [FOAF](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/foaf)
- Machine-readable ontology describing persons
- Works with users and groups
- The only place to get page creation date
- [Games](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/games)
- Checking launch parameters
- Intermediate http handler
- [VK Mini Apps](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/vkapps)
- Checking launch parameters
- Intermediate http handler
- [Payments API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/payments)
- Processes payment notifications
- [Marusia Skills](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/marusia)
- For creating Marusia Skills
- Support SSML
## Install
```bash
# go mod init mymodulename
go get github.com/SevereCloud/vksdk/v2@latest
```
## Use by
- [Joe](https://github.com/go-joe/joe) adapter: <https://github.com/tdakkota/joe-vk-adapter>
- [Logrus](https://github.com/sirupsen/logrus) hook: <https://github.com/SevereCloud/vkrus>
### Example
```go
package main
import (
"context"
"log"
"github.com/SevereCloud/vksdk/v2/api"
"github.com/SevereCloud/vksdk/v2/api/params"
"github.com/SevereCloud/vksdk/v2/events"
"github.com/SevereCloud/vksdk/v2/longpoll-bot"
)
func main() {
token := "<TOKEN>" // use os.Getenv("TOKEN")
vk := api.NewVK(token)
// get information about the group
group, err := vk.GroupsGetByID(nil)
if err != nil {
log.Fatal(err)
}
// Initializing Long Poll
lp, err := longpoll.NewLongPoll(vk, group[0].ID)
if err != nil {
log.Fatal(err)
}
// New message event
lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) {
log.Printf("%d: %s", obj.Message.PeerID, obj.Message.Text)
if obj.Message.Text == "ping" {
b := params.NewMessagesSendBuilder()
b.Message("pong")
b.RandomID(0)
b.PeerID(obj.Message.PeerID)
_, err := vk.MessagesSend(b.Params)
if err != nil {
log.Fatal(err)
}
}
})
// Run Bots Long Poll
log.Println("Start Long Poll")
if err := lp.Run(); err != nil {
log.Fatal(err)
}
}
```
## LICENSE
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk?ref=badge_large)
# VK SDK for Golang
[![PkgGoDev](https://pkg.go.dev/badge/github.com/SevereCloud/vksdk/v2/v2)](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2?tab=subdirectories)
[![VK Developers](https://img.shields.io/badge/developers-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.com/dev/)
[![codecov](https://codecov.io/gh/SevereCloud/vksdk/branch/master/graph/badge.svg)](https://codecov.io/gh/SevereCloud/vksdk)
[![VK chat](https://img.shields.io/badge/VK%20chat-%234a76a8.svg?logo=VK&logoColor=white)](https://vk.me/join/AJQ1d6Or8Q00Y_CSOESfbqGt)
[![release](https://img.shields.io/github/v/tag/SevereCloud/vksdk?label=release)](https://github.com/SevereCloud/vksdk/releases)
[![license](https://img.shields.io/github/license/SevereCloud/vksdk.svg?maxAge=2592000)](https://github.com/SevereCloud/vksdk/blob/master/LICENSE)
**VK SDK for Golang** ready implementation of the main VK API functions for Go.
[Russian documentation](https://github.com/SevereCloud/vksdk/wiki)
## Features
Version API 5.131.
- [API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api)
- 400+ methods
- Ability to change the request handler
- Ability to modify HTTP client
- Request Limiter
- Token pool
- [Callback API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/callback)
- Tracking tool for users activity in your VK communities
- Supports all events
- Auto setting callback
- [Bots Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-bot)
- Allows you to work with community events in real time
- Supports all events
- Ability to modify HTTP client
- [User Long Poll API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/longpoll-user)
- Allows you to work with user events in real time
- Ability to modify HTTP client
- [Streaming API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/streaming)
- Receiving public data from VK by specified keywords
- Ability to modify HTTP client
- [FOAF](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/foaf)
- Machine-readable ontology describing persons
- Works with users and groups
- The only place to get page creation date
- [Games](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/games)
- Checking launch parameters
- Intermediate http handler
- [VK Mini Apps](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/vkapps)
- Checking launch parameters
- Intermediate http handler
- [Payments API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/payments)
- Processes payment notifications
- [Marusia Skills](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/marusia)
- For creating Marusia Skills
- Support SSML
## Install
```bash
# go mod init mymodulename
go get github.com/SevereCloud/vksdk/v2@latest
```
## Use by
- [Joe](https://github.com/go-joe/joe) adapter: <https://github.com/tdakkota/joe-vk-adapter>
- [Logrus](https://github.com/sirupsen/logrus) hook: <https://github.com/SevereCloud/vkrus>
### Example
```go
package main
import (
"context"
"log"
"github.com/SevereCloud/vksdk/v2/api"
"github.com/SevereCloud/vksdk/v2/api/params"
"github.com/SevereCloud/vksdk/v2/events"
"github.com/SevereCloud/vksdk/v2/longpoll-bot"
)
func main() {
token := "<TOKEN>" // use os.Getenv("TOKEN")
vk := api.NewVK(token)
// get information about the group
group, err := vk.GroupsGetByID(nil)
if err != nil {
log.Fatal(err)
}
// Initializing Long Poll
lp, err := longpoll.NewLongPoll(vk, group[0].ID)
if err != nil {
log.Fatal(err)
}
// New message event
lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) {
log.Printf("%d: %s", obj.Message.PeerID, obj.Message.Text)
if obj.Message.Text == "ping" {
b := params.NewMessagesSendBuilder()
b.Message("pong")
b.RandomID(0)
b.PeerID(obj.Message.PeerID)
_, err := vk.MessagesSend(b.Params)
if err != nil {
log.Fatal(err)
}
}
})
// Run Bots Long Poll
log.Println("Start Long Poll")
if err := lp.Run(); err != nil {
log.Fatal(err)
}
}
```
## LICENSE
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FSevereCloud%2Fvksdk?ref=badge_large)

View File

@@ -159,6 +159,9 @@ const (
ErrRateLimit ErrorType = 29
ErrPrivateProfile ErrorType = 30 // This profile is private
// Client version deprecated.
ErrClientVersionDeprecated ErrorType = 34
// Method execution was interrupted due to timeout.
ErrExecutionTimeout ErrorType = 36
@@ -177,6 +180,9 @@ const (
// Additional signup required.
ErrAdditionalSignupRequired ErrorType = 41
// IP is not allowed.
ErrIPNotAllowed ErrorType = 42
// One of the parameters specified was missing or invalid
//
// Check the required parameters list and their format on a method
@@ -586,6 +592,12 @@ const (
// Can't send message, reply timed out.
ErrMessagesReplyTimedOut ErrorType = 950
// You can't access donut chat without subscription.
ErrMessagesAccessDonutChat ErrorType = 962
// This user can't be added to the work chat, as they aren't an employe.
ErrMessagesAccessWorkChat ErrorType = 967
// Invalid phone number.
ErrParamPhone ErrorType = 1000
@@ -598,6 +610,12 @@ const (
// Processing.. Try later.
ErrAuthDelay ErrorType = 1112
// Anonymous token has expired.
ErrAnonymousTokenExpired ErrorType = 1114
// Anonymous token is invalid.
ErrAnonymousTokenInvalid ErrorType = 1116
// Invalid document id.
ErrParamDocID ErrorType = 1150
@@ -724,6 +742,9 @@ const (
// Market was already disabled in this group.
ErrMarketAlreadyDisabled ErrorType = 1432
// Main album can not be hidden.
ErrMainAlbumCantHidden ErrorType = 1446
// Story has already expired.
ErrStoryExpired ErrorType = 1600
@@ -783,6 +804,33 @@ const (
// Can't set AliExpress tag to this type of object.
ErrAliExpressTag ErrorType = 3800
// Invalid upload response.
ErrInvalidUploadResponse ErrorType = 5701
// Invalid upload hash.
ErrInvalidUploadHash ErrorType = 5702
// Invalid upload user.
ErrInvalidUploadUser ErrorType = 5703
// Invalid upload group.
ErrInvalidUploadGroup ErrorType = 5704
// Invalid crop data.
ErrInvalidCropData ErrorType = 5705
// To small avatar.
ErrToSmallAvatar ErrorType = 5706
// Photo not found.
ErrPhotoNotFound ErrorType = 5708
// Invalid Photo.
ErrInvalidPhoto ErrorType = 5709
// Invalid hash.
ErrInvalidHash ErrorType = 5710
)
// ErrorSubtype is the subtype of an error.

View File

@@ -22,6 +22,9 @@ func (vk *VK) ExecuteWithArgs(code string, params Params, obj interface{}) error
}
resp, err := vk.Handler("execute", params, reqParams)
if err != nil {
return err
}
jsonErr := json.Unmarshal(resp.Response, &obj)
if jsonErr != nil {

View File

@@ -318,3 +318,19 @@ func (vk *VK) MarketSearch(params Params) (response MarketSearchResponse, err er
err = vk.RequestUnmarshal("market.search", &response, params)
return
}
// MarketSearchItemsResponse struct.
type MarketSearchItemsResponse struct {
Count int `json:"count"`
ViewType int `json:"view_type"`
Items []object.MarketMarketItem `json:"items"`
Groups []object.GroupsGroup `json:"groups,omitempty"`
}
// MarketSearchItems method.
//
// https://vk.com/dev/market.searchItems
func (vk *VK) MarketSearchItems(params Params) (response MarketSearchItemsResponse, err error) {
err = vk.RequestUnmarshal("market.searchItems", &response, params)
return
}

103
vendor/github.com/SevereCloud/vksdk/v2/api/marusia.go generated vendored Normal file
View File

@@ -0,0 +1,103 @@
package api // import "github.com/SevereCloud/vksdk/v2/api"
import (
"github.com/SevereCloud/vksdk/v2/object"
)
// MarusiaGetPictureUploadLinkResponse struct.
type MarusiaGetPictureUploadLinkResponse struct {
PictureUploadLink string `json:"picture_upload_link"` // Link
}
// MarusiaGetPictureUploadLink method.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) MarusiaGetPictureUploadLink(params Params) (response MarusiaGetPictureUploadLinkResponse, err error) {
err = vk.RequestUnmarshal("marusia.getPictureUploadLink", &response, params)
return
}
// MarusiaSavePictureResponse struct.
type MarusiaSavePictureResponse struct {
AppID int `json:"app_id"`
PhotoID int `json:"photo_id"`
}
// MarusiaSavePicture method.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) MarusiaSavePicture(params Params) (response MarusiaSavePictureResponse, err error) {
err = vk.RequestUnmarshal("marusia.savePicture", &response, params)
return
}
// MarusiaGetPicturesResponse struct.
type MarusiaGetPicturesResponse struct {
Count int `json:"count"`
Items []object.MarusiaPicture `json:"items"`
}
// MarusiaGetPictures method.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) MarusiaGetPictures(params Params) (response MarusiaGetPicturesResponse, err error) {
err = vk.RequestUnmarshal("marusia.getPictures", &response, params)
return
}
// MarusiaDeletePicture delete picture.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) MarusiaDeletePicture(params Params) (response int, err error) {
err = vk.RequestUnmarshal("marusia.deletePicture", &response, params)
return
}
// MarusiaGetAudioUploadLinkResponse struct.
type MarusiaGetAudioUploadLinkResponse struct {
AudioUploadLink string `json:"audio_upload_link"` // Link
}
// MarusiaGetAudioUploadLink method.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) MarusiaGetAudioUploadLink(params Params) (response MarusiaGetAudioUploadLinkResponse, err error) {
err = vk.RequestUnmarshal("marusia.getAudioUploadLink", &response, params)
return
}
// MarusiaCreateAudioResponse struct.
type MarusiaCreateAudioResponse struct {
ID int `json:"id"`
Title string `json:"title"`
}
// MarusiaCreateAudio method.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) MarusiaCreateAudio(params Params) (response MarusiaCreateAudioResponse, err error) {
err = vk.RequestUnmarshal("marusia.createAudio", &response, params)
return
}
// MarusiaGetAudiosResponse struct.
type MarusiaGetAudiosResponse struct {
Count int `json:"count"`
Audios []object.MarusiaAudio `json:"audios"`
}
// MarusiaGetAudios method.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) MarusiaGetAudios(params Params) (response MarusiaGetAudiosResponse, err error) {
err = vk.RequestUnmarshal("marusia.getAudios", &response, params)
return
}
// MarusiaDeleteAudio delete audio.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) MarusiaDeleteAudio(params Params) (response int, err error) {
err = vk.RequestUnmarshal("marusia.deleteAudio", &response, params)
return
}

View File

@@ -959,3 +959,57 @@ func (vk *VK) UploadGroupImage(imageType string, file io.Reader) (response objec
return
}
// UploadMarusiaPicture uploading picture.
//
// Limits: height not more than 600 px,
// aspect ratio of at least 2:1.
func (vk *VK) UploadMarusiaPicture(file io.Reader) (response MarusiaSavePictureResponse, err error) {
uploadServer, err := vk.MarusiaGetPictureUploadLink(nil)
if err != nil {
return
}
bodyContent, err := vk.UploadFile(uploadServer.PictureUploadLink, file, "photo", "photo.jpg")
if err != nil {
return
}
var handler object.MarusiaPictureUploadResponse
err = json.Unmarshal(bodyContent, &handler)
if err != nil {
return
}
photo, _ := json.Marshal(handler.Photo)
response, err = vk.MarusiaSavePicture(Params{
"server": handler.Server,
"photo": string(photo),
"hash": handler.Hash,
})
return
}
// UploadMarusiaAudio uploading audio.
//
// https://vk.com/dev/marusia_skill_docs10
func (vk *VK) UploadMarusiaAudio(file io.Reader) (response MarusiaCreateAudioResponse, err error) {
uploadServer, err := vk.MarusiaGetAudioUploadLink(nil)
if err != nil {
return
}
bodyContent, err := vk.UploadFile(uploadServer.AudioUploadLink, file, "file", "audio.mp3")
if err != nil {
return
}
response, err = vk.MarusiaCreateAudio(Params{
"audio_meta": string(bodyContent),
})
return
}

View File

@@ -7,6 +7,6 @@ package vksdk
// Module constants.
const (
Version = "2.10.0"
Version = "2.11.0"
API = "5.131"
)

View File

@@ -28,6 +28,8 @@ type MarketMarketAlbum struct {
Photo PhotosPhoto `json:"photo"`
Title string `json:"title"` // Market album title
UpdatedTime int `json:"updated_time"` // Date when album has been updated last time in Unixtime
IsMain BaseBoolInt `json:"is_main"`
IsHidden BaseBoolInt `json:"is_hidden"`
}
// ToAttachment return attachment format.

View File

@@ -0,0 +1,52 @@
package object // import "github.com/SevereCloud/vksdk/v2/object"
import (
"encoding/json"
)
// MarusiaPicture struct.
type MarusiaPicture struct {
ID int `json:"id"`
OwnerID int `json:"owner_id"`
}
// MarusiaPictureUploadResponse struct.
type MarusiaPictureUploadResponse struct {
Hash string `json:"hash"` // Uploading hash
Photo json.RawMessage `json:"photo"` // Uploaded photo data
Server int `json:"server"` // Upload server number
AID int `json:"aid"`
MessageCode int `json:"message_code"`
}
// MarusiaAudio struct.
type MarusiaAudio struct {
ID int `json:"id"`
Title string `json:"title"`
OwnerID int `json:"owner_id"`
}
// MarusiaAudioUploadResponse struct.
type MarusiaAudioUploadResponse struct {
Sha string `json:"sha"`
Secret string `json:"secret"`
Meta MarusiaAudioMeta `json:"meta"`
Hash string `json:"hash"`
Server string `json:"server"`
UserID int `json:"user_id"`
RequestID string `json:"request_id"`
}
// MarusiaAudioMeta struct.
type MarusiaAudioMeta struct {
Album string `json:"album"`
Artist string `json:"artist"`
Bitrate string `json:"bitrate"`
Duration string `json:"duration"`
Genre string `json:"genre"`
Kad string `json:"kad"`
Md5 string `json:"md5"`
Md5DataSize string `json:"md5_data_size"`
Samplerate string `json:"samplerate"`
Title string `json:"title"`
}

View File

@@ -375,17 +375,17 @@ type MessagesTemplateElement struct {
// MessagesTemplateElementCarousel struct.
type MessagesTemplateElementCarousel struct {
Title string `json:"title"`
Action MessagesTemplateElementCarouselAction `json:"action"`
Description string `json:"description"`
Photo PhotosPhoto `json:"photo"`
Buttons []MessagesKeyboardButton `json:"buttons"`
Title string `json:"title,omitempty"`
Action MessagesTemplateElementCarouselAction `json:"action,omitempty"`
Description string `json:"description,omitempty"`
Photo *PhotosPhoto `json:"photo,omitempty"`
Buttons []MessagesKeyboardButton `json:"buttons,omitempty"`
}
// MessagesTemplateElementCarouselAction struct.
type MessagesTemplateElementCarouselAction struct {
Type string `json:"type"`
Link string `json:"link"`
Link string `json:"link,omitempty"`
}
// MessageContentSourceMessage ...
@@ -443,6 +443,7 @@ type MessagesChat struct {
AdminID int `json:"admin_id"` // Chat creator ID
ID int `json:"id"` // Chat ID
IsDefaultPhoto BaseBoolInt `json:"is_default_photo"`
IsGroupChannel BaseBoolInt `json:"is_group_channel"`
Photo100 string `json:"photo_100"` // URL of the preview image with 100 px in width
Photo200 string `json:"photo_200"` // URL of the preview image with 200 px in width
Photo50 string `json:"photo_50"` // URL of the preview image with 50 px in width

View File

@@ -251,8 +251,10 @@ type StoriesClickableSticker struct { // nolint: maligned
StickerID int `json:"sticker_id,omitempty"`
StickerPackID int `json:"sticker_pack_id,omitempty"`
// type=place
// type=place or geo
PlaceID int `json:"place_id,omitempty"`
// Title
CategoryID int `json:"category_id,omitempty"`
// type=question
Question string `json:"question,omitempty"`
@@ -267,8 +269,14 @@ type StoriesClickableSticker struct { // nolint: maligned
Hashtag string `json:"hashtag,omitempty"`
// type=link
LinkObject BaseLink `json:"link_object,omitempty"`
TooltipText string `json:"tooltip_text,omitempty"`
LinkObject BaseLink `json:"link_object,omitempty"`
TooltipText string `json:"tooltip_text,omitempty"`
TooltipTextKey string `json:"tooltip_text_key,omitempty"`
// type=time
TimestampMs int64 `json:"timestamp_ms,omitempty"`
Date string `json:"date,omitempty"`
Title string `json:"title,omitempty"`
// type=market_item
Subtype string `json:"subtype,omitempty"`
@@ -290,10 +298,19 @@ type StoriesClickableSticker struct { // nolint: maligned
AudioStartTime int `json:"audio_start_time,omitempty"`
// type=app
App AppsApp `json:"app"`
AppContext string `json:"app_context"`
HasNewInteractions BaseBoolInt `json:"has_new_interactions"`
IsBroadcastNotifyAllowed BaseBoolInt `json:"is_broadcast_notify_allowed"`
App AppsApp `json:"app,omitempty"`
AppContext string `json:"app_context,omitempty"`
HasNewInteractions BaseBoolInt `json:"has_new_interactions,omitempty"`
IsBroadcastNotifyAllowed BaseBoolInt `json:"is_broadcast_notify_allowed,omitempty"`
// type=emoji
Emoji string `json:"emoji,omitempty"`
// type=text
Text string `json:"text,omitempty"`
BackgroundStyle string `json:"background_style,omitempty"`
Alignment string `json:"alignment,omitempty"`
SelectionColor string `json:"selection_color,omitempty"`
}
// TODO: сделать несколько структур для кликабельного стикера
@@ -313,6 +330,10 @@ const (
ClickableStickerPoll = "poll"
ClickableStickerMusic = "music"
ClickableStickerApp = "app"
ClickableStickerTime = "time"
ClickableStickerEmoji = "emoji"
ClickableStickerGeo = "geo"
ClickableStickerText = "text"
)
// Subtype of clickable sticker.

View File

@@ -213,6 +213,7 @@ type VideoVideoFull struct {
Description string `json:"description"` // Video description
Duration int `json:"duration"` // Video duration in seconds
Files VideoVideoFiles `json:"files"`
Trailer VideoVideoFiles `json:"trailer"`
ID int `json:"id"` // Video ID
Likes BaseLikes `json:"likes"`
Live int `json:"live"` // Returns if the video is live translation

View File

@@ -1,7 +1,3 @@
<p align="center">
<img src="https://raw.githubusercontent.com/d5/tengolang-share/master/logo_400.png" width="200" height="200">
</p>
# The Tengo Language
[![GoDoc](https://godoc.org/github.com/d5/tengo/v2?status.svg)](https://godoc.org/github.com/d5/tengo/v2)

View File

@@ -1,9 +1,11 @@
package tengo
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
@@ -45,11 +47,12 @@ type Compiler struct {
parent *Compiler
modulePath string
importDir string
importFileExt []string
constants []Object
symbolTable *SymbolTable
scopes []compilationScope
scopeIndex int
modules *ModuleMap
modules ModuleGetter
compiledModules map[string]*CompiledFunction
allowFileImport bool
loops []*loop
@@ -63,7 +66,7 @@ func NewCompiler(
file *parser.SourceFile,
symbolTable *SymbolTable,
constants []Object,
modules *ModuleMap,
modules ModuleGetter,
trace io.Writer,
) *Compiler {
mainScope := compilationScope{
@@ -96,6 +99,7 @@ func NewCompiler(
trace: trace,
modules: modules,
compiledModules: make(map[string]*CompiledFunction),
importFileExt: []string{SourceFileExtDefault},
}
}
@@ -538,12 +542,8 @@ func (c *Compiler) Compile(node parser.Node) error {
}
} else if c.allowFileImport {
moduleName := node.ModuleName
if !strings.HasSuffix(moduleName, ".tengo") {
moduleName += ".tengo"
}
modulePath, err := filepath.Abs(
filepath.Join(c.importDir, moduleName))
modulePath, err := c.getPathModule(moduleName)
if err != nil {
return c.errorf(node, "module file path error: %s",
err.Error())
@@ -640,6 +640,39 @@ func (c *Compiler) SetImportDir(dir string) {
c.importDir = dir
}
// SetImportFileExt sets the extension name of the source file for loading
// local module files.
//
// Use this method if you want other source file extension than ".tengo".
//
// // this will search for *.tengo, *.foo, *.bar
// err := c.SetImportFileExt(".tengo", ".foo", ".bar")
//
// This function requires at least one argument, since it will replace the
// current list of extension name.
func (c *Compiler) SetImportFileExt(exts ...string) error {
if len(exts) == 0 {
return fmt.Errorf("missing arg: at least one argument is required")
}
for _, ext := range exts {
if ext != filepath.Ext(ext) || ext == "" {
return fmt.Errorf("invalid file extension: %s", ext)
}
}
c.importFileExt = exts // Replace the hole current extension list
return nil
}
// GetImportFileExt returns the current list of extension name.
// Thease are the complementary suffix of the source file to search and load
// local module files.
func (c *Compiler) GetImportFileExt() []string {
return c.importFileExt
}
func (c *Compiler) compileAssign(
node parser.Node,
lhs, rhs []parser.Expr,
@@ -1098,6 +1131,7 @@ func (c *Compiler) fork(
child.parent = c // parent to set to current compiler
child.allowFileImport = c.allowFileImport
child.importDir = c.importDir
child.importFileExt = c.importFileExt
if isFile && c.importDir != "" {
child.importDir = filepath.Dir(modulePath)
}
@@ -1287,6 +1321,28 @@ func (c *Compiler) printTrace(a ...interface{}) {
_, _ = fmt.Fprintln(c.trace, a...)
}
func (c *Compiler) getPathModule(moduleName string) (pathFile string, err error) {
for _, ext := range c.importFileExt {
nameFile := moduleName
if !strings.HasSuffix(nameFile, ext) {
nameFile += ext
}
pathFile, err = filepath.Abs(filepath.Join(c.importDir, nameFile))
if err != nil {
continue
}
// Check if file exists
if _, err := os.Stat(pathFile); !errors.Is(err, os.ErrNotExist) {
return pathFile, nil
}
}
return "", fmt.Errorf("module '%s' not found at: %s", moduleName, pathFile)
}
func resolveAssignLHS(
expr parser.Expr,
) (name string, selectors []parser.Expr) {

View File

@@ -6,6 +6,11 @@ type Importable interface {
Import(moduleName string) (interface{}, error)
}
// ModuleGetter enables implementing dynamic module loading.
type ModuleGetter interface {
Get(name string) Importable
}
// ModuleMap represents a set of named modules. Use NewModuleMap to create a
// new module map.
type ModuleMap struct {

View File

@@ -12,7 +12,7 @@ import (
// Script can simplify compilation and execution of embedded scripts.
type Script struct {
variables map[string]*Variable
modules *ModuleMap
modules ModuleGetter
input []byte
maxAllocs int64
maxConstObjects int
@@ -54,7 +54,7 @@ func (s *Script) Remove(name string) bool {
}
// SetImports sets import modules.
func (s *Script) SetImports(modules *ModuleMap) {
func (s *Script) SetImports(modules ModuleGetter) {
s.modules = modules
}
@@ -219,6 +219,18 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) {
v := NewVM(c.bytecode, c.globals, c.maxAllocs)
ch := make(chan error, 1)
go func() {
defer func() {
if r := recover(); r != nil {
switch e := r.(type) {
case string:
ch <- fmt.Errorf(e)
case error:
ch <- e
default:
ch <- fmt.Errorf("unknown panic: %v", e)
}
}
}()
ch <- v.Run()
}()

View File

@@ -26,6 +26,9 @@ const (
// MaxFrames is the maximum number of function frames for a VM.
MaxFrames = 1024
// SourceFileExtDefault is the default extension for source files.
SourceFileExtDefault = ".tengo"
)
// CallableFunc is a function signature for the callable functions.

View File

@@ -293,7 +293,7 @@ func (v *VM) run() {
case parser.OpMap:
v.ip += 2
numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
kv := make(map[string]Object)
kv := make(map[string]Object, numElements)
for i := v.sp - numElements; i < v.sp; i += 2 {
key := v.stack[i]
value := v.stack[i+1]

View File

@@ -1,8 +0,0 @@
language: go
go:
- '1.10'
- '1.11'
- '1.12'
- '1.13'
- tip

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,990 +0,0 @@
package tgbotapi
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strings"
"time"
)
// APIResponse is a response from the Telegram API with the result
// stored raw.
type APIResponse struct {
Ok bool `json:"ok"`
Result json.RawMessage `json:"result"`
ErrorCode int `json:"error_code"`
Description string `json:"description"`
Parameters *ResponseParameters `json:"parameters"`
}
// ResponseParameters are various errors that can be returned in APIResponse.
type ResponseParameters struct {
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
RetryAfter int `json:"retry_after"` // optional
}
// Update is an update response, from GetUpdates.
type Update struct {
UpdateID int `json:"update_id"`
Message *Message `json:"message"`
EditedMessage *Message `json:"edited_message"`
ChannelPost *Message `json:"channel_post"`
EditedChannelPost *Message `json:"edited_channel_post"`
InlineQuery *InlineQuery `json:"inline_query"`
ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"`
CallbackQuery *CallbackQuery `json:"callback_query"`
ShippingQuery *ShippingQuery `json:"shipping_query"`
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query"`
}
// UpdatesChannel is the channel for getting updates.
type UpdatesChannel <-chan Update
// Clear discards all unprocessed incoming updates.
func (ch UpdatesChannel) Clear() {
for len(ch) != 0 {
<-ch
}
}
// User is a user on Telegram.
type User struct {
ID int `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"` // optional
UserName string `json:"username"` // optional
LanguageCode string `json:"language_code"` // optional
IsBot bool `json:"is_bot"` // optional
}
// String displays a simple text version of a user.
//
// It is normally a user's username, but falls back to a first/last
// name as available.
func (u *User) String() string {
if u == nil {
return ""
}
if u.UserName != "" {
return u.UserName
}
name := u.FirstName
if u.LastName != "" {
name += " " + u.LastName
}
return name
}
// GroupChat is a group chat.
type GroupChat struct {
ID int `json:"id"`
Title string `json:"title"`
}
// ChatPhoto represents a chat photo.
type ChatPhoto struct {
SmallFileID string `json:"small_file_id"`
BigFileID string `json:"big_file_id"`
}
// Chat contains information about the place a message was sent.
type Chat struct {
ID int64 `json:"id"`
Type string `json:"type"`
Title string `json:"title"` // optional
UserName string `json:"username"` // optional
FirstName string `json:"first_name"` // optional
LastName string `json:"last_name"` // optional
AllMembersAreAdmins bool `json:"all_members_are_administrators"` // optional
Photo *ChatPhoto `json:"photo"`
Description string `json:"description,omitempty"` // optional
InviteLink string `json:"invite_link,omitempty"` // optional
PinnedMessage *Message `json:"pinned_message"` // optional
}
// IsPrivate returns if the Chat is a private conversation.
func (c Chat) IsPrivate() bool {
return c.Type == "private"
}
// IsGroup returns if the Chat is a group.
func (c Chat) IsGroup() bool {
return c.Type == "group"
}
// IsSuperGroup returns if the Chat is a supergroup.
func (c Chat) IsSuperGroup() bool {
return c.Type == "supergroup"
}
// IsChannel returns if the Chat is a channel.
func (c Chat) IsChannel() bool {
return c.Type == "channel"
}
// ChatConfig returns a ChatConfig struct for chat related methods.
func (c Chat) ChatConfig() ChatConfig {
return ChatConfig{ChatID: c.ID}
}
// Message is returned by almost every request, and contains data about
// almost anything.
type Message struct {
MessageID int `json:"message_id"`
From *User `json:"from"` // optional
Date int `json:"date"`
Chat *Chat `json:"chat"`
ForwardFrom *User `json:"forward_from"` // optional
ForwardFromChat *Chat `json:"forward_from_chat"` // optional
ForwardFromMessageID int `json:"forward_from_message_id"` // optional
ForwardDate int `json:"forward_date"` // optional
ReplyToMessage *Message `json:"reply_to_message"` // optional
EditDate int `json:"edit_date"` // optional
Text string `json:"text"` // optional
Entities *[]MessageEntity `json:"entities"` // optional
CaptionEntities *[]MessageEntity `json:"caption_entities"` // optional
Audio *Audio `json:"audio"` // optional
Document *Document `json:"document"` // optional
Animation *ChatAnimation `json:"animation"` // optional
Game *Game `json:"game"` // optional
Photo *[]PhotoSize `json:"photo"` // optional
Sticker *Sticker `json:"sticker"` // optional
Video *Video `json:"video"` // optional
VideoNote *VideoNote `json:"video_note"` // optional
Voice *Voice `json:"voice"` // optional
Caption string `json:"caption"` // optional
Contact *Contact `json:"contact"` // optional
Location *Location `json:"location"` // optional
Venue *Venue `json:"venue"` // optional
NewChatMembers *[]User `json:"new_chat_members"` // optional
LeftChatMember *User `json:"left_chat_member"` // optional
NewChatTitle string `json:"new_chat_title"` // optional
NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional
DeleteChatPhoto bool `json:"delete_chat_photo"` // optional
GroupChatCreated bool `json:"group_chat_created"` // optional
SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional
ChannelChatCreated bool `json:"channel_chat_created"` // optional
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional
PinnedMessage *Message `json:"pinned_message"` // optional
Invoice *Invoice `json:"invoice"` // optional
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional
PassportData *PassportData `json:"passport_data,omitempty"` // optional
}
// Time converts the message timestamp into a Time.
func (m *Message) Time() time.Time {
return time.Unix(int64(m.Date), 0)
}
// IsCommand returns true if message starts with a "bot_command" entity.
func (m *Message) IsCommand() bool {
if m.Entities == nil || len(*m.Entities) == 0 {
return false
}
entity := (*m.Entities)[0]
return entity.Offset == 0 && entity.IsCommand()
}
// Command checks if the message was a command and if it was, returns the
// command. If the Message was not a command, it returns an empty string.
//
// If the command contains the at name syntax, it is removed. Use
// CommandWithAt() if you do not want that.
func (m *Message) Command() string {
command := m.CommandWithAt()
if i := strings.Index(command, "@"); i != -1 {
command = command[:i]
}
return command
}
// CommandWithAt checks if the message was a command and if it was, returns the
// command. If the Message was not a command, it returns an empty string.
//
// If the command contains the at name syntax, it is not removed. Use Command()
// if you want that.
func (m *Message) CommandWithAt() string {
if !m.IsCommand() {
return ""
}
// IsCommand() checks that the message begins with a bot_command entity
entity := (*m.Entities)[0]
return m.Text[1:entity.Length]
}
// CommandArguments checks if the message was a command and if it was,
// returns all text after the command name. If the Message was not a
// command, it returns an empty string.
//
// Note: The first character after the command name is omitted:
// - "/foo bar baz" yields "bar baz", not " bar baz"
// - "/foo-bar baz" yields "bar baz", too
// Even though the latter is not a command conforming to the spec, the API
// marks "/foo" as command entity.
func (m *Message) CommandArguments() string {
if !m.IsCommand() {
return ""
}
// IsCommand() checks that the message begins with a bot_command entity
entity := (*m.Entities)[0]
if len(m.Text) == entity.Length {
return "" // The command makes up the whole message
}
return m.Text[entity.Length+1:]
}
// MessageEntity contains information about data in a Message.
type MessageEntity struct {
Type string `json:"type"`
Offset int `json:"offset"`
Length int `json:"length"`
URL string `json:"url"` // optional
User *User `json:"user"` // optional
}
// ParseURL attempts to parse a URL contained within a MessageEntity.
func (e MessageEntity) ParseURL() (*url.URL, error) {
if e.URL == "" {
return nil, errors.New(ErrBadURL)
}
return url.Parse(e.URL)
}
// IsMention returns true if the type of the message entity is "mention" (@username).
func (e MessageEntity) IsMention() bool {
return e.Type == "mention"
}
// IsHashtag returns true if the type of the message entity is "hashtag".
func (e MessageEntity) IsHashtag() bool {
return e.Type == "hashtag"
}
// IsCommand returns true if the type of the message entity is "bot_command".
func (e MessageEntity) IsCommand() bool {
return e.Type == "bot_command"
}
// IsUrl returns true if the type of the message entity is "url".
func (e MessageEntity) IsUrl() bool {
return e.Type == "url"
}
// IsEmail returns true if the type of the message entity is "email".
func (e MessageEntity) IsEmail() bool {
return e.Type == "email"
}
// IsBold returns true if the type of the message entity is "bold" (bold text).
func (e MessageEntity) IsBold() bool {
return e.Type == "bold"
}
// IsItalic returns true if the type of the message entity is "italic" (italic text).
func (e MessageEntity) IsItalic() bool {
return e.Type == "italic"
}
// IsCode returns true if the type of the message entity is "code" (monowidth string).
func (e MessageEntity) IsCode() bool {
return e.Type == "code"
}
// IsPre returns true if the type of the message entity is "pre" (monowidth block).
func (e MessageEntity) IsPre() bool {
return e.Type == "pre"
}
// IsTextLink returns true if the type of the message entity is "text_link" (clickable text URL).
func (e MessageEntity) IsTextLink() bool {
return e.Type == "text_link"
}
// PhotoSize contains information about photos.
type PhotoSize struct {
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
FileSize int `json:"file_size"` // optional
}
// Audio contains information about audio.
type Audio struct {
FileID string `json:"file_id"`
Duration int `json:"duration"`
Performer string `json:"performer"` // optional
Title string `json:"title"` // optional
MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
}
// Document contains information about a document.
type Document struct {
FileID string `json:"file_id"`
Thumbnail *PhotoSize `json:"thumb"` // optional
FileName string `json:"file_name"` // optional
MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
}
// Sticker contains information about a sticker.
type Sticker struct {
FileUniqueID string `json:"file_unique_id"`
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
Thumbnail *PhotoSize `json:"thumb"` // optional
Emoji string `json:"emoji"` // optional
FileSize int `json:"file_size"` // optional
SetName string `json:"set_name"` // optional
IsAnimated bool `json:"is_animated"` // optional
}
type StickerSet struct {
Name string `json:"name"`
Title string `json:"title"`
IsAnimated bool `json:"is_animated"`
ContainsMasks bool `json:"contains_masks"`
Stickers []Sticker `json:"stickers"`
}
// ChatAnimation contains information about an animation.
type ChatAnimation struct {
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
Thumbnail *PhotoSize `json:"thumb"` // optional
FileName string `json:"file_name"` // optional
MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
}
// Video contains information about a video.
type Video struct {
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
Thumbnail *PhotoSize `json:"thumb"` // optional
MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
}
// VideoNote contains information about a video.
type VideoNote struct {
FileID string `json:"file_id"`
Length int `json:"length"`
Duration int `json:"duration"`
Thumbnail *PhotoSize `json:"thumb"` // optional
FileSize int `json:"file_size"` // optional
}
// Voice contains information about a voice.
type Voice struct {
FileID string `json:"file_id"`
Duration int `json:"duration"`
MimeType string `json:"mime_type"` // optional
FileSize int `json:"file_size"` // optional
}
// Contact contains information about a contact.
//
// Note that LastName and UserID may be empty.
type Contact struct {
PhoneNumber string `json:"phone_number"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"` // optional
UserID int `json:"user_id"` // optional
}
// Location contains information about a place.
type Location struct {
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
}
// Venue contains information about a venue, including its Location.
type Venue struct {
Location Location `json:"location"`
Title string `json:"title"`
Address string `json:"address"`
FoursquareID string `json:"foursquare_id"` // optional
}
// UserProfilePhotos contains a set of user profile photos.
type UserProfilePhotos struct {
TotalCount int `json:"total_count"`
Photos [][]PhotoSize `json:"photos"`
}
// File contains information about a file to download from Telegram.
type File struct {
FileID string `json:"file_id"`
FileSize int `json:"file_size"` // optional
FilePath string `json:"file_path"` // optional
}
// Link returns a full path to the download URL for a File.
//
// It requires the Bot Token to create the link.
func (f *File) Link(token string) string {
return fmt.Sprintf(FileEndpoint, token, f.FilePath)
}
// ReplyKeyboardMarkup allows the Bot to set a custom keyboard.
type ReplyKeyboardMarkup struct {
Keyboard [][]KeyboardButton `json:"keyboard"`
ResizeKeyboard bool `json:"resize_keyboard"` // optional
OneTimeKeyboard bool `json:"one_time_keyboard"` // optional
Selective bool `json:"selective"` // optional
}
// KeyboardButton is a button within a custom keyboard.
type KeyboardButton struct {
Text string `json:"text"`
RequestContact bool `json:"request_contact"`
RequestLocation bool `json:"request_location"`
}
// ReplyKeyboardHide allows the Bot to hide a custom keyboard.
type ReplyKeyboardHide struct {
HideKeyboard bool `json:"hide_keyboard"`
Selective bool `json:"selective"` // optional
}
// ReplyKeyboardRemove allows the Bot to hide a custom keyboard.
type ReplyKeyboardRemove struct {
RemoveKeyboard bool `json:"remove_keyboard"`
Selective bool `json:"selective"`
}
// InlineKeyboardMarkup is a custom keyboard presented for an inline bot.
type InlineKeyboardMarkup struct {
InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"`
}
// InlineKeyboardButton is a button within a custom keyboard for
// inline query responses.
//
// Note that some values are references as even an empty string
// will change behavior.
//
// CallbackGame, if set, MUST be first button in first row.
type InlineKeyboardButton struct {
Text string `json:"text"`
URL *string `json:"url,omitempty"` // optional
CallbackData *string `json:"callback_data,omitempty"` // optional
SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional
SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional
CallbackGame *CallbackGame `json:"callback_game,omitempty"` // optional
Pay bool `json:"pay,omitempty"` // optional
}
// CallbackQuery is data sent when a keyboard button with callback data
// is clicked.
type CallbackQuery struct {
ID string `json:"id"`
From *User `json:"from"`
Message *Message `json:"message"` // optional
InlineMessageID string `json:"inline_message_id"` // optional
ChatInstance string `json:"chat_instance"`
Data string `json:"data"` // optional
GameShortName string `json:"game_short_name"` // optional
}
// ForceReply allows the Bot to have users directly reply to it without
// additional interaction.
type ForceReply struct {
ForceReply bool `json:"force_reply"`
Selective bool `json:"selective"` // optional
}
// ChatMember is information about a member in a chat.
type ChatMember struct {
User *User `json:"user"`
Status string `json:"status"`
UntilDate int64 `json:"until_date,omitempty"` // optional
CanBeEdited bool `json:"can_be_edited,omitempty"` // optional
CanChangeInfo bool `json:"can_change_info,omitempty"` // optional
CanPostMessages bool `json:"can_post_messages,omitempty"` // optional
CanEditMessages bool `json:"can_edit_messages,omitempty"` // optional
CanDeleteMessages bool `json:"can_delete_messages,omitempty"` // optional
CanInviteUsers bool `json:"can_invite_users,omitempty"` // optional
CanRestrictMembers bool `json:"can_restrict_members,omitempty"` // optional
CanPinMessages bool `json:"can_pin_messages,omitempty"` // optional
CanPromoteMembers bool `json:"can_promote_members,omitempty"` // optional
CanSendMessages bool `json:"can_send_messages,omitempty"` // optional
CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"` // optional
CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"` // optional
CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"` // optional
}
// IsCreator returns if the ChatMember was the creator of the chat.
func (chat ChatMember) IsCreator() bool { return chat.Status == "creator" }
// IsAdministrator returns if the ChatMember is a chat administrator.
func (chat ChatMember) IsAdministrator() bool { return chat.Status == "administrator" }
// IsMember returns if the ChatMember is a current member of the chat.
func (chat ChatMember) IsMember() bool { return chat.Status == "member" }
// HasLeft returns if the ChatMember left the chat.
func (chat ChatMember) HasLeft() bool { return chat.Status == "left" }
// WasKicked returns if the ChatMember was kicked from the chat.
func (chat ChatMember) WasKicked() bool { return chat.Status == "kicked" }
// Game is a game within Telegram.
type Game struct {
Title string `json:"title"`
Description string `json:"description"`
Photo []PhotoSize `json:"photo"`
Text string `json:"text"`
TextEntities []MessageEntity `json:"text_entities"`
Animation Animation `json:"animation"`
}
// Animation is a GIF animation demonstrating the game.
type Animation struct {
FileID string `json:"file_id"`
Thumb PhotoSize `json:"thumb"`
FileName string `json:"file_name"`
MimeType string `json:"mime_type"`
FileSize int `json:"file_size"`
}
// GameHighScore is a user's score and position on the leaderboard.
type GameHighScore struct {
Position int `json:"position"`
User User `json:"user"`
Score int `json:"score"`
}
// CallbackGame is for starting a game in an inline keyboard button.
type CallbackGame struct{}
// WebhookInfo is information about a currently set webhook.
type WebhookInfo struct {
URL string `json:"url"`
HasCustomCertificate bool `json:"has_custom_certificate"`
PendingUpdateCount int `json:"pending_update_count"`
LastErrorDate int `json:"last_error_date"` // optional
LastErrorMessage string `json:"last_error_message"` // optional
}
// IsSet returns true if a webhook is currently set.
func (info WebhookInfo) IsSet() bool {
return info.URL != ""
}
// InputMediaPhoto contains a photo for displaying as part of a media group.
type InputMediaPhoto struct {
Type string `json:"type"`
Media string `json:"media"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
}
// InputMediaVideo contains a video for displaying as part of a media group.
type InputMediaVideo struct {
Type string `json:"type"`
Media string `json:"media"`
// thumb intentionally missing as it is not currently compatible
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
Width int `json:"width"`
Height int `json:"height"`
Duration int `json:"duration"`
SupportsStreaming bool `json:"supports_streaming"`
}
// InlineQuery is a Query from Telegram for an inline request.
type InlineQuery struct {
ID string `json:"id"`
From *User `json:"from"`
Location *Location `json:"location"` // optional
Query string `json:"query"`
Offset string `json:"offset"`
}
// InlineQueryResultArticle is an inline query response article.
type InlineQueryResultArticle struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
Title string `json:"title"` // required
InputMessageContent interface{} `json:"input_message_content,omitempty"` // required
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
URL string `json:"url"`
HideURL bool `json:"hide_url"`
Description string `json:"description"`
ThumbURL string `json:"thumb_url"`
ThumbWidth int `json:"thumb_width"`
ThumbHeight int `json:"thumb_height"`
}
// InlineQueryResultPhoto is an inline query response photo.
type InlineQueryResultPhoto struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
URL string `json:"photo_url"` // required
MimeType string `json:"mime_type"`
Width int `json:"photo_width"`
Height int `json:"photo_height"`
ThumbURL string `json:"thumb_url"`
Title string `json:"title"`
Description string `json:"description"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultCachedPhoto is an inline query response with cached photo.
type InlineQueryResultCachedPhoto struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
PhotoID string `json:"photo_file_id"` // required
Title string `json:"title"`
Description string `json:"description"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultGIF is an inline query response GIF.
type InlineQueryResultGIF struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
URL string `json:"gif_url"` // required
ThumbURL string `json:"thumb_url"` // required
Width int `json:"gif_width,omitempty"`
Height int `json:"gif_height,omitempty"`
Duration int `json:"gif_duration,omitempty"`
Title string `json:"title,omitempty"`
Caption string `json:"caption,omitempty"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultCachedGIF is an inline query response with cached gif.
type InlineQueryResultCachedGIF struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
GifID string `json:"gif_file_id"` // required
Title string `json:"title"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultMPEG4GIF is an inline query response MPEG4 GIF.
type InlineQueryResultMPEG4GIF struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
URL string `json:"mpeg4_url"` // required
Width int `json:"mpeg4_width"`
Height int `json:"mpeg4_height"`
Duration int `json:"mpeg4_duration"`
ThumbURL string `json:"thumb_url"`
Title string `json:"title"`
Caption string `json:"caption"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultCachedMpeg4Gif is an inline query response with cached
// H.264/MPEG-4 AVC video without sound gif.
type InlineQueryResultCachedMpeg4Gif struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
MGifID string `json:"mpeg4_file_id"` // required
Title string `json:"title"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultVideo is an inline query response video.
type InlineQueryResultVideo struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
URL string `json:"video_url"` // required
MimeType string `json:"mime_type"` // required
ThumbURL string `json:"thumb_url"`
Title string `json:"title"`
Caption string `json:"caption"`
Width int `json:"video_width"`
Height int `json:"video_height"`
Duration int `json:"video_duration"`
Description string `json:"description"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultCachedVideo is an inline query response with cached video.
type InlineQueryResultCachedVideo struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
VideoID string `json:"video_file_id"` // required
Title string `json:"title"` // required
Description string `json:"description"`
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultAudio is an inline query response audio.
type InlineQueryResultAudio struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
URL string `json:"audio_url"` // required
Title string `json:"title"` // required
Caption string `json:"caption"`
Performer string `json:"performer"`
Duration int `json:"audio_duration"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultCachedAudio is an inline query response with cached audio.
type InlineQueryResultCachedAudio struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
AudioID string `json:"audio_file_id"` // required
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultVoice is an inline query response voice.
type InlineQueryResultVoice struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
URL string `json:"voice_url"` // required
Title string `json:"title"` // required
Caption string `json:"caption"`
Duration int `json:"voice_duration"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultCachedVoice is an inline query response with cached voice.
type InlineQueryResultCachedVoice struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
VoiceID string `json:"voice_file_id"` // required
Title string `json:"title"` // required
Caption string `json:"caption"`
ParseMode string `json:"parse_mode"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultDocument is an inline query response document.
type InlineQueryResultDocument struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
Title string `json:"title"` // required
Caption string `json:"caption"`
URL string `json:"document_url"` // required
MimeType string `json:"mime_type"` // required
Description string `json:"description"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
ThumbURL string `json:"thumb_url"`
ThumbWidth int `json:"thumb_width"`
ThumbHeight int `json:"thumb_height"`
}
// InlineQueryResultCachedDocument is an inline query response with cached document.
type InlineQueryResultCachedDocument struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
DocumentID string `json:"document_file_id"` // required
Title string `json:"title"` // required
Caption string `json:"caption"`
Description string `json:"description"`
ParseMode string `json:"parse_mode"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
}
// InlineQueryResultLocation is an inline query response location.
type InlineQueryResultLocation struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
Latitude float64 `json:"latitude"` // required
Longitude float64 `json:"longitude"` // required
Title string `json:"title"` // required
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
ThumbURL string `json:"thumb_url"`
ThumbWidth int `json:"thumb_width"`
ThumbHeight int `json:"thumb_height"`
}
// InlineQueryResultVenue is an inline query response venue.
type InlineQueryResultVenue struct {
Type string `json:"type"` // required
ID string `json:"id"` // required
Latitude float64 `json:"latitude"` // required
Longitude float64 `json:"longitude"` // required
Title string `json:"title"` // required
Address string `json:"address"` // required
FoursquareID string `json:"foursquare_id"`
FoursquareType string `json:"foursquare_type"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"`
ThumbURL string `json:"thumb_url"`
ThumbWidth int `json:"thumb_width"`
ThumbHeight int `json:"thumb_height"`
}
// InlineQueryResultGame is an inline query response game.
type InlineQueryResultGame struct {
Type string `json:"type"`
ID string `json:"id"`
GameShortName string `json:"game_short_name"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
}
// ChosenInlineResult is an inline query result chosen by a User
type ChosenInlineResult struct {
ResultID string `json:"result_id"`
From *User `json:"from"`
Location *Location `json:"location"`
InlineMessageID string `json:"inline_message_id"`
Query string `json:"query"`
}
// InputTextMessageContent contains text for displaying
// as an inline query result.
type InputTextMessageContent struct {
Text string `json:"message_text"`
ParseMode string `json:"parse_mode"`
DisableWebPagePreview bool `json:"disable_web_page_preview"`
}
// InputLocationMessageContent contains a location for displaying
// as an inline query result.
type InputLocationMessageContent struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
// InputVenueMessageContent contains a venue for displaying
// as an inline query result.
type InputVenueMessageContent struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Title string `json:"title"`
Address string `json:"address"`
FoursquareID string `json:"foursquare_id"`
}
// InputContactMessageContent contains a contact for displaying
// as an inline query result.
type InputContactMessageContent struct {
PhoneNumber string `json:"phone_number"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
// Invoice contains basic information about an invoice.
type Invoice struct {
Title string `json:"title"`
Description string `json:"description"`
StartParameter string `json:"start_parameter"`
Currency string `json:"currency"`
TotalAmount int `json:"total_amount"`
}
// LabeledPrice represents a portion of the price for goods or services.
type LabeledPrice struct {
Label string `json:"label"`
Amount int `json:"amount"`
}
// ShippingAddress represents a shipping address.
type ShippingAddress struct {
CountryCode string `json:"country_code"`
State string `json:"state"`
City string `json:"city"`
StreetLine1 string `json:"street_line1"`
StreetLine2 string `json:"street_line2"`
PostCode string `json:"post_code"`
}
// OrderInfo represents information about an order.
type OrderInfo struct {
Name string `json:"name,omitempty"`
PhoneNumber string `json:"phone_number,omitempty"`
Email string `json:"email,omitempty"`
ShippingAddress *ShippingAddress `json:"shipping_address,omitempty"`
}
// ShippingOption represents one shipping option.
type ShippingOption struct {
ID string `json:"id"`
Title string `json:"title"`
Prices *[]LabeledPrice `json:"prices"`
}
// SuccessfulPayment contains basic information about a successful payment.
type SuccessfulPayment struct {
Currency string `json:"currency"`
TotalAmount int `json:"total_amount"`
InvoicePayload string `json:"invoice_payload"`
ShippingOptionID string `json:"shipping_option_id,omitempty"`
OrderInfo *OrderInfo `json:"order_info,omitempty"`
TelegramPaymentChargeID string `json:"telegram_payment_charge_id"`
ProviderPaymentChargeID string `json:"provider_payment_charge_id"`
}
// ShippingQuery contains information about an incoming shipping query.
type ShippingQuery struct {
ID string `json:"id"`
From *User `json:"from"`
InvoicePayload string `json:"invoice_payload"`
ShippingAddress *ShippingAddress `json:"shipping_address"`
}
// PreCheckoutQuery contains information about an incoming pre-checkout query.
type PreCheckoutQuery struct {
ID string `json:"id"`
From *User `json:"from"`
Currency string `json:"currency"`
TotalAmount int `json:"total_amount"`
InvoicePayload string `json:"invoice_payload"`
ShippingOptionID string `json:"shipping_option_id,omitempty"`
OrderInfo *OrderInfo `json:"order_info,omitempty"`
}
// Error is an error containing extra information returned by the Telegram API.
type Error struct {
Code int
Message string
ResponseParameters
}
func (e Error) Error() string {
return e.Message
}

View File

@@ -1,12 +1,14 @@
# Golang bindings for the Telegram Bot API
[![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api)
[![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-telegram-bot-api/telegram-bot-api/v5.svg)](https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5)
[![Test](https://github.com/go-telegram-bot-api/telegram-bot-api/actions/workflows/test.yml/badge.svg)](https://github.com/go-telegram-bot-api/telegram-bot-api/actions/workflows/test.yml)
All methods are fairly self explanatory, and reading the [godoc](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api) page should
All methods are fairly self-explanatory, and reading the [godoc](https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5) page should
explain everything. If something isn't clear, open an issue or submit
a pull request.
There are more tutorials and high-level information on the website, [go-telegram-bot-api.dev](https://go-telegram-bot-api.dev).
The scope of this project is just to provide a wrapper around the API
without any additional features. There are other projects for creating
something with plugins and command handlers without having to design
@@ -18,7 +20,7 @@ you want to ask questions or discuss development.
## Example
First, ensure the library is installed and up to date by running
`go get -u github.com/go-telegram-bot-api/telegram-bot-api`.
`go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5`.
This is a very simple bot that just displays any gotten updates,
then replies it to that chat.
@@ -29,7 +31,7 @@ package main
import (
"log"
"github.com/go-telegram-bot-api/telegram-bot-api"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func main() {
@@ -45,28 +47,21 @@ func main() {
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates, err := bot.GetUpdatesChan(u)
updates := bot.GetUpdatesChan(u)
for update := range updates {
if update.Message == nil { // ignore any non-Message Updates
continue
if update.Message != nil { // If we got a message
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
msg.ReplyToMessageID = update.Message.MessageID
bot.Send(msg)
}
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
msg.ReplyToMessageID = update.Message.MessageID
bot.Send(msg)
}
}
```
There are more examples on the [wiki](https://github.com/go-telegram-bot-api/telegram-bot-api/wiki)
with detailed information on how to do many different kinds of things.
It's a great place to get started on using keyboards, commands, or other
kinds of reply markup.
If you need to use webhooks (if you wish to run on Google App Engine),
you may use a slightly different method.
@@ -77,7 +72,7 @@ import (
"log"
"net/http"
"github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func main() {
@@ -90,17 +85,22 @@ func main() {
log.Printf("Authorized on account %s", bot.Self.UserName)
_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem"))
wh, _ := tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem")
_, err = bot.SetWebhook(wh)
if err != nil {
log.Fatal(err)
}
info, err := bot.GetWebhookInfo()
if err != nil {
log.Fatal(err)
}
if info.LastErrorDate != 0 {
log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
}
updates := bot.ListenForWebhook("/" + bot.Token)
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
@@ -110,7 +110,7 @@ func main() {
}
```
If you need, you may generate a self signed certficate, as this requires
If you need, you may generate a self-signed certificate, as this requires
HTTPS / TLS. The above example tells Telegram that this is your
certificate and that it should be trusted, even though it is not
properly signed.

View File

@@ -0,0 +1,9 @@
[book]
authors = ["Syfaro"]
language = "en"
multilingual = false
src = "docs"
title = "Go Telegram Bot API"
[output.html]
git-repository-url = "https://github.com/go-telegram-bot-api/telegram-bot-api"

View File

@@ -0,0 +1,726 @@
// Package tgbotapi has functions and types used for interacting with
// the Telegram Bot API.
package tgbotapi
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strings"
"time"
)
// HTTPClient is the type needed for the bot to perform HTTP requests.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// BotAPI allows you to interact with the Telegram Bot API.
type BotAPI struct {
Token string `json:"token"`
Debug bool `json:"debug"`
Buffer int `json:"buffer"`
Self User `json:"-"`
Client HTTPClient `json:"-"`
shutdownChannel chan interface{}
apiEndpoint string
}
// NewBotAPI creates a new BotAPI instance.
//
// It requires a token, provided by @BotFather on Telegram.
func NewBotAPI(token string) (*BotAPI, error) {
return NewBotAPIWithClient(token, APIEndpoint, &http.Client{})
}
// NewBotAPIWithAPIEndpoint creates a new BotAPI instance
// and allows you to pass API endpoint.
//
// It requires a token, provided by @BotFather on Telegram and API endpoint.
func NewBotAPIWithAPIEndpoint(token, apiEndpoint string) (*BotAPI, error) {
return NewBotAPIWithClient(token, apiEndpoint, &http.Client{})
}
// NewBotAPIWithClient creates a new BotAPI instance
// and allows you to pass a http.Client.
//
// It requires a token, provided by @BotFather on Telegram and API endpoint.
func NewBotAPIWithClient(token, apiEndpoint string, client HTTPClient) (*BotAPI, error) {
bot := &BotAPI{
Token: token,
Client: client,
Buffer: 100,
shutdownChannel: make(chan interface{}),
apiEndpoint: apiEndpoint,
}
self, err := bot.GetMe()
if err != nil {
return nil, err
}
bot.Self = self
return bot, nil
}
// SetAPIEndpoint changes the Telegram Bot API endpoint used by the instance.
func (bot *BotAPI) SetAPIEndpoint(apiEndpoint string) {
bot.apiEndpoint = apiEndpoint
}
func buildParams(in Params) url.Values {
if in == nil {
return url.Values{}
}
out := url.Values{}
for key, value := range in {
out.Set(key, value)
}
return out
}
// MakeRequest makes a request to a specific endpoint with our token.
func (bot *BotAPI) MakeRequest(endpoint string, params Params) (*APIResponse, error) {
if bot.Debug {
log.Printf("Endpoint: %s, params: %v\n", endpoint, params)
}
method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
values := buildParams(params)
req, err := http.NewRequest("POST", method, strings.NewReader(values.Encode()))
if err != nil {
return &APIResponse{}, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := bot.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var apiResp APIResponse
bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
if err != nil {
return &apiResp, err
}
if bot.Debug {
log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
}
if !apiResp.Ok {
var parameters ResponseParameters
if apiResp.Parameters != nil {
parameters = *apiResp.Parameters
}
return &apiResp, &Error{
Code: apiResp.ErrorCode,
Message: apiResp.Description,
ResponseParameters: parameters,
}
}
return &apiResp, nil
}
// decodeAPIResponse decode response and return slice of bytes if debug enabled.
// If debug disabled, just decode http.Response.Body stream to APIResponse struct
// for efficient memory usage
func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) ([]byte, error) {
if !bot.Debug {
dec := json.NewDecoder(responseBody)
err := dec.Decode(resp)
return nil, err
}
// if debug, read response body
data, err := ioutil.ReadAll(responseBody)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, resp)
if err != nil {
return nil, err
}
return data, nil
}
// UploadFiles makes a request to the API with files.
func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFile) (*APIResponse, error) {
r, w := io.Pipe()
m := multipart.NewWriter(w)
// This code modified from the very helpful @HirbodBehnam
// https://github.com/go-telegram-bot-api/telegram-bot-api/issues/354#issuecomment-663856473
go func() {
defer w.Close()
defer m.Close()
for field, value := range params {
if err := m.WriteField(field, value); err != nil {
w.CloseWithError(err)
return
}
}
for _, file := range files {
if file.Data.NeedsUpload() {
name, reader, err := file.Data.UploadData()
if err != nil {
w.CloseWithError(err)
return
}
part, err := m.CreateFormFile(file.Name, name)
if err != nil {
w.CloseWithError(err)
return
}
if _, err := io.Copy(part, reader); err != nil {
w.CloseWithError(err)
return
}
if closer, ok := reader.(io.ReadCloser); ok {
if err = closer.Close(); err != nil {
w.CloseWithError(err)
return
}
}
} else {
value := file.Data.SendData()
if err := m.WriteField(file.Name, value); err != nil {
w.CloseWithError(err)
return
}
}
}
}()
if bot.Debug {
log.Printf("Endpoint: %s, params: %v, with %d files\n", endpoint, params, len(files))
}
method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
req, err := http.NewRequest("POST", method, r)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", m.FormDataContentType())
resp, err := bot.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var apiResp APIResponse
bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
if err != nil {
return &apiResp, err
}
if bot.Debug {
log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
}
if !apiResp.Ok {
var parameters ResponseParameters
if apiResp.Parameters != nil {
parameters = *apiResp.Parameters
}
return &apiResp, &Error{
Message: apiResp.Description,
ResponseParameters: parameters,
}
}
return &apiResp, nil
}
// GetFileDirectURL returns direct URL to file
//
// It requires the FileID.
func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) {
file, err := bot.GetFile(FileConfig{fileID})
if err != nil {
return "", err
}
return file.Link(bot.Token), nil
}
// GetMe fetches the currently authenticated bot.
//
// This method is called upon creation to validate the token,
// and so you may get this data from BotAPI.Self without the need for
// another request.
func (bot *BotAPI) GetMe() (User, error) {
resp, err := bot.MakeRequest("getMe", nil)
if err != nil {
return User{}, err
}
var user User
err = json.Unmarshal(resp.Result, &user)
return user, err
}
// IsMessageToMe returns true if message directed to this bot.
//
// It requires the Message.
func (bot *BotAPI) IsMessageToMe(message Message) bool {
return strings.Contains(message.Text, "@"+bot.Self.UserName)
}
func hasFilesNeedingUpload(files []RequestFile) bool {
for _, file := range files {
if file.Data.NeedsUpload() {
return true
}
}
return false
}
// Request sends a Chattable to Telegram, and returns the APIResponse.
func (bot *BotAPI) Request(c Chattable) (*APIResponse, error) {
params, err := c.params()
if err != nil {
return nil, err
}
if t, ok := c.(Fileable); ok {
files := t.files()
// If we have files that need to be uploaded, we should delegate the
// request to UploadFile.
if hasFilesNeedingUpload(files) {
return bot.UploadFiles(t.method(), params, files)
}
// However, if there are no files to be uploaded, there's likely things
// that need to be turned into params instead.
for _, file := range files {
params[file.Name] = file.Data.SendData()
}
}
return bot.MakeRequest(c.method(), params)
}
// Send will send a Chattable item to Telegram and provides the
// returned Message.
func (bot *BotAPI) Send(c Chattable) (Message, error) {
resp, err := bot.Request(c)
if err != nil {
return Message{}, err
}
var message Message
err = json.Unmarshal(resp.Result, &message)
return message, err
}
// SendMediaGroup sends a media group and returns the resulting messages.
func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
resp, err := bot.Request(config)
if err != nil {
return nil, err
}
var messages []Message
err = json.Unmarshal(resp.Result, &messages)
return messages, err
}
// GetUserProfilePhotos gets a user's profile photos.
//
// It requires UserID.
// Offset and Limit are optional.
func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
resp, err := bot.Request(config)
if err != nil {
return UserProfilePhotos{}, err
}
var profilePhotos UserProfilePhotos
err = json.Unmarshal(resp.Result, &profilePhotos)
return profilePhotos, err
}
// GetFile returns a File which can download a file from Telegram.
//
// Requires FileID.
func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
resp, err := bot.Request(config)
if err != nil {
return File{}, err
}
var file File
err = json.Unmarshal(resp.Result, &file)
return file, err
}
// GetUpdates fetches updates.
// If a WebHook is set, this will not return any data!
//
// Offset, Limit, Timeout, and AllowedUpdates are optional.
// To avoid stale items, set Offset to one higher than the previous item.
// Set Timeout to a large number to reduce requests, so you can get updates
// instantly instead of having to wait between requests.
func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
resp, err := bot.Request(config)
if err != nil {
return []Update{}, err
}
var updates []Update
err = json.Unmarshal(resp.Result, &updates)
return updates, err
}
// GetWebhookInfo allows you to fetch information about a webhook and if
// one currently is set, along with pending update count and error messages.
func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
resp, err := bot.MakeRequest("getWebhookInfo", nil)
if err != nil {
return WebhookInfo{}, err
}
var info WebhookInfo
err = json.Unmarshal(resp.Result, &info)
return info, err
}
// GetUpdatesChan starts and returns a channel for getting updates.
func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
ch := make(chan Update, bot.Buffer)
go func() {
for {
select {
case <-bot.shutdownChannel:
close(ch)
return
default:
}
updates, err := bot.GetUpdates(config)
if err != nil {
log.Println(err)
log.Println("Failed to get updates, retrying in 3 seconds...")
time.Sleep(time.Second * 3)
continue
}
for _, update := range updates {
if update.UpdateID >= config.Offset {
config.Offset = update.UpdateID + 1
ch <- update
}
}
}
}()
return ch
}
// StopReceivingUpdates stops the go routine which receives updates
func (bot *BotAPI) StopReceivingUpdates() {
if bot.Debug {
log.Println("Stopping the update receiver routine...")
}
close(bot.shutdownChannel)
}
// ListenForWebhook registers a http handler for a webhook.
func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
ch := make(chan Update, bot.Buffer)
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
update, err := bot.HandleUpdate(r)
if err != nil {
errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(errMsg)
return
}
ch <- *update
})
return ch
}
// ListenForWebhookRespReqFormat registers a http handler for a single incoming webhook.
func (bot *BotAPI) ListenForWebhookRespReqFormat(w http.ResponseWriter, r *http.Request) UpdatesChannel {
ch := make(chan Update, bot.Buffer)
func(w http.ResponseWriter, r *http.Request) {
update, err := bot.HandleUpdate(r)
if err != nil {
errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(errMsg)
return
}
ch <- *update
close(ch)
}(w, r)
return ch
}
// HandleUpdate parses and returns update received via webhook
func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) {
if r.Method != http.MethodPost {
err := errors.New("wrong HTTP method required POST")
return nil, err
}
var update Update
err := json.NewDecoder(r.Body).Decode(&update)
if err != nil {
return nil, err
}
return &update, nil
}
// WriteToHTTPResponse writes the request to the HTTP ResponseWriter.
//
// It doesn't support uploading files.
//
// See https://core.telegram.org/bots/api#making-requests-when-getting-updates
// for details.
func WriteToHTTPResponse(w http.ResponseWriter, c Chattable) error {
params, err := c.params()
if err != nil {
return err
}
if t, ok := c.(Fileable); ok {
if hasFilesNeedingUpload(t.files()) {
return errors.New("unable to use http response to upload files")
}
}
values := buildParams(params)
values.Set("method", c.method())
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
_, err = w.Write([]byte(values.Encode()))
return err
}
// GetChat gets information about a chat.
func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
resp, err := bot.Request(config)
if err != nil {
return Chat{}, err
}
var chat Chat
err = json.Unmarshal(resp.Result, &chat)
return chat, err
}
// GetChatAdministrators gets a list of administrators in the chat.
//
// If none have been appointed, only the creator will be returned.
// Bots are not shown, even if they are an administrator.
func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
resp, err := bot.Request(config)
if err != nil {
return []ChatMember{}, err
}
var members []ChatMember
err = json.Unmarshal(resp.Result, &members)
return members, err
}
// GetChatMembersCount gets the number of users in a chat.
func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
resp, err := bot.Request(config)
if err != nil {
return -1, err
}
var count int
err = json.Unmarshal(resp.Result, &count)
return count, err
}
// GetChatMember gets a specific chat member.
func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
resp, err := bot.Request(config)
if err != nil {
return ChatMember{}, err
}
var member ChatMember
err = json.Unmarshal(resp.Result, &member)
return member, err
}
// GetGameHighScores allows you to get the high scores for a game.
func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
resp, err := bot.Request(config)
if err != nil {
return []GameHighScore{}, err
}
var highScores []GameHighScore
err = json.Unmarshal(resp.Result, &highScores)
return highScores, err
}
// GetInviteLink get InviteLink for a chat
func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
resp, err := bot.Request(config)
if err != nil {
return "", err
}
var inviteLink string
err = json.Unmarshal(resp.Result, &inviteLink)
return inviteLink, err
}
// GetStickerSet returns a StickerSet.
func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
resp, err := bot.Request(config)
if err != nil {
return StickerSet{}, err
}
var stickers StickerSet
err = json.Unmarshal(resp.Result, &stickers)
return stickers, err
}
// StopPoll stops a poll and returns the result.
func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
resp, err := bot.Request(config)
if err != nil {
return Poll{}, err
}
var poll Poll
err = json.Unmarshal(resp.Result, &poll)
return poll, err
}
// GetMyCommands gets the currently registered commands.
func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) {
return bot.GetMyCommandsWithConfig(GetMyCommandsConfig{})
}
// GetMyCommandsWithConfig gets the currently registered commands with a config.
func (bot *BotAPI) GetMyCommandsWithConfig(config GetMyCommandsConfig) ([]BotCommand, error) {
resp, err := bot.Request(config)
if err != nil {
return nil, err
}
var commands []BotCommand
err = json.Unmarshal(resp.Result, &commands)
return commands, err
}
// CopyMessage copy messages of any kind. The method is analogous to the method
// forwardMessage, but the copied message doesn't have a link to the original
// message. Returns the MessageID of the sent message on success.
func (bot *BotAPI) CopyMessage(config CopyMessageConfig) (MessageID, error) {
params, err := config.params()
if err != nil {
return MessageID{}, err
}
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return MessageID{}, err
}
var messageID MessageID
err = json.Unmarshal(resp.Result, &messageID)
return messageID, err
}
// EscapeText takes an input text and escape Telegram markup symbols.
// In this way we can send a text without being afraid of having to escape the characters manually.
// Note that you don't have to include the formatting style in the input text, or it will be escaped too.
// If there is an error, an empty string will be returned.
//
// parseMode is the text formatting mode (ModeMarkdown, ModeMarkdownV2 or ModeHTML)
// text is the input string that will be escaped
func EscapeText(parseMode string, text string) string {
var replacer *strings.Replacer
if parseMode == ModeHTML {
replacer = strings.NewReplacer("<", "&lt;", ">", "&gt;", "&", "&amp;")
} else if parseMode == ModeMarkdown {
replacer = strings.NewReplacer("_", "\\_", "*", "\\*", "`", "\\`", "[", "\\[")
} else if parseMode == ModeMarkdownV2 {
replacer = strings.NewReplacer(
"_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(",
"\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>",
"#", "\\#", "+", "\\+", "-", "\\-", "=", "\\=", "|",
"\\|", "{", "\\{", "}", "\\}", ".", "\\.", "!", "\\!",
)
} else {
return ""
}
return replacer.Replace(text)
}

File diff suppressed because it is too large Load Diff

View File

@@ -52,241 +52,117 @@ func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig {
}
}
// NewPhotoUpload creates a new photo uploader.
// NewCopyMessage creates a new copy message.
//
// chatID is where to send it, fromChatID is the source chat,
// and messageID is the ID of the original message.
func NewCopyMessage(chatID int64, fromChatID int64, messageID int) CopyMessageConfig {
return CopyMessageConfig{
BaseChat: BaseChat{ChatID: chatID},
FromChatID: fromChatID,
MessageID: messageID,
}
}
// NewPhoto creates a new sendPhoto request.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
//
// Note that you must send animated GIFs as a document.
func NewPhotoUpload(chatID int64, file interface{}) PhotoConfig {
func NewPhoto(chatID int64, file RequestFileData) PhotoConfig {
return PhotoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
BaseChat: BaseChat{ChatID: chatID},
File: file,
},
}
}
// NewPhotoShare shares an existing photo.
// You may use this to reshare an existing photo without reuploading it.
// NewPhotoToChannel creates a new photo uploader to send a photo to a channel.
//
// chatID is where to send it, fileID is the ID of the file
// already uploaded.
func NewPhotoShare(chatID int64, fileID string) PhotoConfig {
// Note that you must send animated GIFs as a document.
func NewPhotoToChannel(username string, file RequestFileData) PhotoConfig {
return PhotoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
BaseChat: BaseChat{
ChannelUsername: username,
},
File: file,
},
}
}
// NewAudioUpload creates a new audio uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewAudioUpload(chatID int64, file interface{}) AudioConfig {
// NewAudio creates a new sendAudio request.
func NewAudio(chatID int64, file RequestFileData) AudioConfig {
return AudioConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
BaseChat: BaseChat{ChatID: chatID},
File: file,
},
}
}
// NewAudioShare shares an existing audio file.
// You may use this to reshare an existing audio file without
// reuploading it.
//
// chatID is where to send it, fileID is the ID of the audio
// already uploaded.
func NewAudioShare(chatID int64, fileID string) AudioConfig {
return AudioConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewDocumentUpload creates a new document uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewDocumentUpload(chatID int64, file interface{}) DocumentConfig {
// NewDocument creates a new sendDocument request.
func NewDocument(chatID int64, file RequestFileData) DocumentConfig {
return DocumentConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
BaseChat: BaseChat{ChatID: chatID},
File: file,
},
}
}
// NewDocumentShare shares an existing document.
// You may use this to reshare an existing document without
// reuploading it.
//
// chatID is where to send it, fileID is the ID of the document
// already uploaded.
func NewDocumentShare(chatID int64, fileID string) DocumentConfig {
return DocumentConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewStickerUpload creates a new sticker uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewStickerUpload(chatID int64, file interface{}) StickerConfig {
// NewSticker creates a new sendSticker request.
func NewSticker(chatID int64, file RequestFileData) StickerConfig {
return StickerConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
BaseChat: BaseChat{ChatID: chatID},
File: file,
},
}
}
// NewStickerShare shares an existing sticker.
// You may use this to reshare an existing sticker without
// reuploading it.
//
// chatID is where to send it, fileID is the ID of the sticker
// already uploaded.
func NewStickerShare(chatID int64, fileID string) StickerConfig {
return StickerConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewVideoUpload creates a new video uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewVideoUpload(chatID int64, file interface{}) VideoConfig {
// NewVideo creates a new sendVideo request.
func NewVideo(chatID int64, file RequestFileData) VideoConfig {
return VideoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
BaseChat: BaseChat{ChatID: chatID},
File: file,
},
}
}
// NewVideoShare shares an existing video.
// You may use this to reshare an existing video without reuploading it.
//
// chatID is where to send it, fileID is the ID of the video
// already uploaded.
func NewVideoShare(chatID int64, fileID string) VideoConfig {
return VideoConfig{
// NewAnimation creates a new sendAnimation request.
func NewAnimation(chatID int64, file RequestFileData) AnimationConfig {
return AnimationConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
BaseChat: BaseChat{ChatID: chatID},
File: file,
},
}
}
// NewAnimationUpload creates a new animation uploader.
// NewVideoNote creates a new sendVideoNote request.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig {
return AnimationConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
},
}
}
// NewAnimationShare shares an existing animation.
// You may use this to reshare an existing animation without reuploading it.
//
// chatID is where to send it, fileID is the ID of the animation
// already uploaded.
func NewAnimationShare(chatID int64, fileID string) AnimationConfig {
return AnimationConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewVideoNoteUpload creates a new video note uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewVideoNoteUpload(chatID int64, length int, file interface{}) VideoNoteConfig {
func NewVideoNote(chatID int64, length int, file RequestFileData) VideoNoteConfig {
return VideoNoteConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
BaseChat: BaseChat{ChatID: chatID},
File: file,
},
Length: length,
}
}
// NewVideoNoteShare shares an existing video.
// You may use this to reshare an existing video without reuploading it.
//
// chatID is where to send it, fileID is the ID of the video
// already uploaded.
func NewVideoNoteShare(chatID int64, length int, fileID string) VideoNoteConfig {
return VideoNoteConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
Length: length,
}
}
// NewVoiceUpload creates a new voice uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewVoiceUpload(chatID int64, file interface{}) VoiceConfig {
// NewVoice creates a new sendVoice request.
func NewVoice(chatID int64, file RequestFileData) VoiceConfig {
return VoiceConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
},
}
}
// NewVoiceShare shares an existing voice.
// You may use this to reshare an existing voice without reuploading it.
//
// chatID is where to send it, fileID is the ID of the video
// already uploaded.
func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
return VoiceConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
BaseChat: BaseChat{ChatID: chatID},
File: file,
},
}
}
@@ -295,26 +171,58 @@ func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
// two to ten InputMediaPhoto or InputMediaVideo.
func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
return MediaGroupConfig{
BaseChat: BaseChat{
ChatID: chatID,
},
InputMedia: files,
ChatID: chatID,
Media: files,
}
}
// NewInputMediaPhoto creates a new InputMediaPhoto.
func NewInputMediaPhoto(media string) InputMediaPhoto {
func NewInputMediaPhoto(media RequestFileData) InputMediaPhoto {
return InputMediaPhoto{
Type: "photo",
Media: media,
BaseInputMedia{
Type: "photo",
Media: media,
},
}
}
// NewInputMediaVideo creates a new InputMediaVideo.
func NewInputMediaVideo(media string) InputMediaVideo {
func NewInputMediaVideo(media RequestFileData) InputMediaVideo {
return InputMediaVideo{
Type: "video",
Media: media,
BaseInputMedia: BaseInputMedia{
Type: "video",
Media: media,
},
}
}
// NewInputMediaAnimation creates a new InputMediaAnimation.
func NewInputMediaAnimation(media RequestFileData) InputMediaAnimation {
return InputMediaAnimation{
BaseInputMedia: BaseInputMedia{
Type: "animation",
Media: media,
},
}
}
// NewInputMediaAudio creates a new InputMediaAudio.
func NewInputMediaAudio(media RequestFileData) InputMediaAudio {
return InputMediaAudio{
BaseInputMedia: BaseInputMedia{
Type: "audio",
Media: media,
},
}
}
// NewInputMediaDocument creates a new InputMediaDocument.
func NewInputMediaDocument(media RequestFileData) InputMediaDocument {
return InputMediaDocument{
BaseInputMedia: BaseInputMedia{
Type: "document",
Media: media,
},
}
}
@@ -369,7 +277,7 @@ func NewChatAction(chatID int64, action string) ChatActionConfig {
// NewUserProfilePhotos gets user profile photos.
//
// userID is the ID of the user you wish to get profile photos from.
func NewUserProfilePhotos(userID int) UserProfilePhotosConfig {
func NewUserProfilePhotos(userID int64) UserProfilePhotosConfig {
return UserProfilePhotosConfig{
UserID: userID,
Offset: 0,
@@ -392,25 +300,33 @@ func NewUpdate(offset int) UpdateConfig {
// NewWebhook creates a new webhook.
//
// link is the url parsable link you wish to get the updates.
func NewWebhook(link string) WebhookConfig {
u, _ := url.Parse(link)
func NewWebhook(link string) (WebhookConfig, error) {
u, err := url.Parse(link)
if err != nil {
return WebhookConfig{}, err
}
return WebhookConfig{
URL: u,
}
}, nil
}
// NewWebhookWithCert creates a new webhook with a certificate.
//
// link is the url you wish to get webhooks,
// file contains a string to a file, FileReader, or FileBytes.
func NewWebhookWithCert(link string, file interface{}) WebhookConfig {
u, _ := url.Parse(link)
func NewWebhookWithCert(link string, file RequestFileData) (WebhookConfig, error) {
u, err := url.Parse(link)
if err != nil {
return WebhookConfig{}, err
}
return WebhookConfig{
URL: u,
Certificate: file,
}
}, nil
}
// NewInlineQueryResultArticle creates a new inline query article.
@@ -438,6 +354,19 @@ func NewInlineQueryResultArticleMarkdown(id, title, messageText string) InlineQu
}
}
// NewInlineQueryResultArticleMarkdownV2 creates a new inline query article with MarkdownV2 parsing.
func NewInlineQueryResultArticleMarkdownV2(id, title, messageText string) InlineQueryResultArticle {
return InlineQueryResultArticle{
Type: "article",
ID: id,
Title: title,
InputMessageContent: InputTextMessageContent{
Text: messageText,
ParseMode: "MarkdownV2",
},
}
}
// NewInlineQueryResultArticleHTML creates a new inline query article with HTML parsing.
func NewInlineQueryResultArticleHTML(id, title, messageText string) InlineQueryResultArticle {
return InlineQueryResultArticle{
@@ -465,7 +394,7 @@ func NewInlineQueryResultCachedGIF(id, gifID string) InlineQueryResultCachedGIF
return InlineQueryResultCachedGIF{
Type: "gif",
ID: id,
GifID: gifID,
GIFID: gifID,
}
}
@@ -478,12 +407,12 @@ func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF {
}
}
// NewInlineQueryResultCachedPhoto create a new inline query with cached photo.
func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GifID string) InlineQueryResultCachedMpeg4Gif {
return InlineQueryResultCachedMpeg4Gif{
Type: "mpeg4_gif",
ID: id,
MGifID: MPEG4GifID,
// NewInlineQueryResultCachedMPEG4GIF create a new inline query with cached MPEG4 GIF.
func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GIFID string) InlineQueryResultCachedMPEG4GIF {
return InlineQueryResultCachedMPEG4GIF{
Type: "mpeg4_gif",
ID: id,
MPEG4FileID: MPEG4GIFID,
}
}
@@ -534,6 +463,16 @@ func NewInlineQueryResultCachedVideo(id, videoID, title string) InlineQueryResul
}
}
// NewInlineQueryResultCachedSticker create a new inline query with cached sticker.
func NewInlineQueryResultCachedSticker(id, stickerID, title string) InlineQueryResultCachedSticker {
return InlineQueryResultCachedSticker{
Type: "sticker",
ID: id,
StickerID: stickerID,
Title: title,
}
}
// NewInlineQueryResultAudio creates a new inline query audio.
func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio {
return InlineQueryResultAudio{
@@ -628,6 +567,18 @@ func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTex
}
}
// NewEditMessageTextAndMarkup allows you to edit the text and replymarkup of a message.
func NewEditMessageTextAndMarkup(chatID int64, messageID int, text string, replyMarkup InlineKeyboardMarkup) EditMessageTextConfig {
return EditMessageTextConfig{
BaseEdit: BaseEdit{
ChatID: chatID,
MessageID: messageID,
ReplyMarkup: &replyMarkup,
},
Text: text,
}
}
// NewEditMessageCaption allows you to edit the caption of a message.
func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig {
return EditMessageCaptionConfig{
@@ -651,17 +602,6 @@ func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKe
}
}
// NewHideKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone.
func NewHideKeyboard(selective bool) ReplyKeyboardHide {
log.Println("NewHideKeyboard is deprecated, please use NewRemoveKeyboard")
return ReplyKeyboardHide{
HideKeyboard: true,
Selective: selective,
}
}
// NewRemoveKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone.
func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove {
@@ -717,6 +657,13 @@ func NewReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup {
}
}
// NewOneTimeReplyKeyboard creates a new one time keyboard.
func NewOneTimeReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup {
markup := NewReplyKeyboard(rows...)
markup.OneTimeKeyboard = true
return markup
}
// NewInlineKeyboardButtonData creates an inline keyboard button with text
// and data for a callback.
func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton {
@@ -726,6 +673,15 @@ func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton {
}
}
// NewInlineKeyboardButtonLoginURL creates an inline keyboard button with text
// which goes to a LoginURL.
func NewInlineKeyboardButtonLoginURL(text string, loginURL LoginURL) InlineKeyboardButton {
return InlineKeyboardButton{
Text: text,
LoginURL: &loginURL,
}
}
// NewInlineKeyboardButtonURL creates an inline keyboard button with text
// which goes to a URL.
func NewInlineKeyboardButtonURL(text, url string) InlineKeyboardButton {
@@ -784,7 +740,7 @@ func NewCallbackWithAlert(id, text string) CallbackConfig {
}
// NewInvoice creates a new Invoice request to the user.
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices *[]LabeledPrice) InvoiceConfig {
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices []LabeledPrice) InvoiceConfig {
return InvoiceConfig{
BaseChat: BaseChat{ChatID: chatID},
Title: title,
@@ -796,33 +752,176 @@ func NewInvoice(chatID int64, title, description, payload, providerToken, startP
Prices: prices}
}
// NewSetChatPhotoUpload creates a new chat photo uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
//
// Note that you must send animated GIFs as a document.
func NewSetChatPhotoUpload(chatID int64, file interface{}) SetChatPhotoConfig {
// NewChatTitle allows you to update the title of a chat.
func NewChatTitle(chatID int64, title string) SetChatTitleConfig {
return SetChatTitleConfig{
ChatID: chatID,
Title: title,
}
}
// NewChatDescription allows you to update the description of a chat.
func NewChatDescription(chatID int64, description string) SetChatDescriptionConfig {
return SetChatDescriptionConfig{
ChatID: chatID,
Description: description,
}
}
// NewChatPhoto allows you to update the photo for a chat.
func NewChatPhoto(chatID int64, photo RequestFileData) SetChatPhotoConfig {
return SetChatPhotoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
BaseChat: BaseChat{
ChatID: chatID,
},
File: photo,
},
}
}
// NewSetChatPhotoShare shares an existing photo.
// You may use this to reshare an existing photo without reuploading it.
//
// chatID is where to send it, fileID is the ID of the file
// already uploaded.
func NewSetChatPhotoShare(chatID int64, fileID string) SetChatPhotoConfig {
return SetChatPhotoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
// NewDeleteChatPhoto allows you to delete the photo for a chat.
func NewDeleteChatPhoto(chatID int64) DeleteChatPhotoConfig {
return DeleteChatPhotoConfig{
ChatID: chatID,
}
}
// NewPoll allows you to create a new poll.
func NewPoll(chatID int64, question string, options ...string) SendPollConfig {
return SendPollConfig{
BaseChat: BaseChat{
ChatID: chatID,
},
Question: question,
Options: options,
IsAnonymous: true, // This is Telegram's default.
}
}
// NewStopPoll allows you to stop a poll.
func NewStopPoll(chatID int64, messageID int) StopPollConfig {
return StopPollConfig{
BaseEdit{
ChatID: chatID,
MessageID: messageID,
},
}
}
// NewDice allows you to send a random dice roll.
func NewDice(chatID int64) DiceConfig {
return DiceConfig{
BaseChat: BaseChat{
ChatID: chatID,
},
}
}
// NewDiceWithEmoji allows you to send a random roll of one of many types.
//
// Emoji may be 🎲 (1-6), 🎯 (1-6), or 🏀 (1-5).
func NewDiceWithEmoji(chatID int64, emoji string) DiceConfig {
return DiceConfig{
BaseChat: BaseChat{
ChatID: chatID,
},
Emoji: emoji,
}
}
// NewBotCommandScopeDefault represents the default scope of bot commands.
func NewBotCommandScopeDefault() BotCommandScope {
return BotCommandScope{Type: "default"}
}
// NewBotCommandScopeAllPrivateChats represents the scope of bot commands,
// covering all private chats.
func NewBotCommandScopeAllPrivateChats() BotCommandScope {
return BotCommandScope{Type: "all_private_chats"}
}
// NewBotCommandScopeAllGroupChats represents the scope of bot commands,
// covering all group and supergroup chats.
func NewBotCommandScopeAllGroupChats() BotCommandScope {
return BotCommandScope{Type: "all_group_chats"}
}
// NewBotCommandScopeAllChatAdministrators represents the scope of bot commands,
// covering all group and supergroup chat administrators.
func NewBotCommandScopeAllChatAdministrators() BotCommandScope {
return BotCommandScope{Type: "all_chat_administrators"}
}
// NewBotCommandScopeChat represents the scope of bot commands, covering a
// specific chat.
func NewBotCommandScopeChat(chatID int64) BotCommandScope {
return BotCommandScope{
Type: "chat",
ChatID: chatID,
}
}
// NewBotCommandScopeChatAdministrators represents the scope of bot commands,
// covering all administrators of a specific group or supergroup chat.
func NewBotCommandScopeChatAdministrators(chatID int64) BotCommandScope {
return BotCommandScope{
Type: "chat_administrators",
ChatID: chatID,
}
}
// NewBotCommandScopeChatMember represents the scope of bot commands, covering a
// specific member of a group or supergroup chat.
func NewBotCommandScopeChatMember(chatID, userID int64) BotCommandScope {
return BotCommandScope{
Type: "chat_member",
ChatID: chatID,
UserID: userID,
}
}
// NewGetMyCommandsWithScope allows you to set the registered commands for a
// given scope.
func NewGetMyCommandsWithScope(scope BotCommandScope) GetMyCommandsConfig {
return GetMyCommandsConfig{Scope: &scope}
}
// NewGetMyCommandsWithScopeAndLanguage allows you to set the registered
// commands for a given scope and language code.
func NewGetMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string) GetMyCommandsConfig {
return GetMyCommandsConfig{Scope: &scope, LanguageCode: languageCode}
}
// NewSetMyCommands allows you to set the registered commands.
func NewSetMyCommands(commands ...BotCommand) SetMyCommandsConfig {
return SetMyCommandsConfig{Commands: commands}
}
// NewSetMyCommandsWithScope allows you to set the registered commands for a given scope.
func NewSetMyCommandsWithScope(scope BotCommandScope, commands ...BotCommand) SetMyCommandsConfig {
return SetMyCommandsConfig{Commands: commands, Scope: &scope}
}
// NewSetMyCommandsWithScopeAndLanguage allows you to set the registered commands for a given scope
// and language code.
func NewSetMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string, commands ...BotCommand) SetMyCommandsConfig {
return SetMyCommandsConfig{Commands: commands, Scope: &scope, LanguageCode: languageCode}
}
// NewDeleteMyCommands allows you to delete the registered commands.
func NewDeleteMyCommands() DeleteMyCommandsConfig {
return DeleteMyCommandsConfig{}
}
// NewDeleteMyCommandsWithScope allows you to delete the registered commands for a given
// scope.
func NewDeleteMyCommandsWithScope(scope BotCommandScope) DeleteMyCommandsConfig {
return DeleteMyCommandsConfig{Scope: &scope}
}
// NewDeleteMyCommandsWithScopeAndLanguage allows you to delete the registered commands for a given
// scope and language code.
func NewDeleteMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string) DeleteMyCommandsConfig {
return DeleteMyCommandsConfig{Scope: &scope, LanguageCode: languageCode}
}

View File

@@ -0,0 +1,97 @@
package tgbotapi
import (
"encoding/json"
"reflect"
"strconv"
)
// Params represents a set of parameters that gets passed to a request.
type Params map[string]string
// AddNonEmpty adds a value if it not an empty string.
func (p Params) AddNonEmpty(key, value string) {
if value != "" {
p[key] = value
}
}
// AddNonZero adds a value if it is not zero.
func (p Params) AddNonZero(key string, value int) {
if value != 0 {
p[key] = strconv.Itoa(value)
}
}
// AddNonZero64 is the same as AddNonZero except uses an int64.
func (p Params) AddNonZero64(key string, value int64) {
if value != 0 {
p[key] = strconv.FormatInt(value, 10)
}
}
// AddBool adds a value of a bool if it is true.
func (p Params) AddBool(key string, value bool) {
if value {
p[key] = strconv.FormatBool(value)
}
}
// AddNonZeroFloat adds a floating point value that is not zero.
func (p Params) AddNonZeroFloat(key string, value float64) {
if value != 0 {
p[key] = strconv.FormatFloat(value, 'f', 6, 64)
}
}
// AddInterface adds an interface if it is not nil and can be JSON marshalled.
func (p Params) AddInterface(key string, value interface{}) error {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return nil
}
b, err := json.Marshal(value)
if err != nil {
return err
}
p[key] = string(b)
return nil
}
// AddFirstValid attempts to add the first item that is not a default value.
//
// For example, AddFirstValid(0, "", "test") would add "test".
func (p Params) AddFirstValid(key string, args ...interface{}) error {
for _, arg := range args {
switch v := arg.(type) {
case int:
if v != 0 {
p[key] = strconv.Itoa(v)
return nil
}
case int64:
if v != 0 {
p[key] = strconv.FormatInt(v, 10)
return nil
}
case string:
if v != "" {
p[key] = v
return nil
}
case nil:
default:
b, err := json.Marshal(arg)
if err != nil {
return err
}
p[key] = string(b)
return nil
}
}
return nil
}

View File

@@ -54,13 +54,15 @@ type (
Credentials *EncryptedCredentials `json:"credentials"`
}
// PassportFile represents a file uploaded to Telegram Passport. Currently all
// PassportFile represents a file uploaded to Telegram Passport. Currently, all
// Telegram Passport files are in JPEG format when decrypted and don't exceed
// 10MB.
PassportFile struct {
// Unique identifier for this file
FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
// File size
FileSize int `json:"file_size"`
@@ -212,7 +214,7 @@ type (
// PassportElementErrorFile represents an issue with a document scan. The
// error is considered resolved when the file with the document scan changes.
PassportElementErrorFile struct {
// Error source, must be file
// Error source, must be a file
Source string `json:"source"`
// The section of the user's Telegram Passport which has the issue, one

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,12 @@ const (
escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]"
)
const (
captionTable = "Table: "
captionFigure = "Figure: "
captionQuote = "Quote: "
)
var (
reBackslashOrAmp = regexp.MustCompile("[\\&]")
reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
@@ -125,6 +131,16 @@ func (p *Parser) block(data []byte) {
}
if consumed > 0 {
included := f(p.includeStack.Last(), path, address)
// if we find a caption below this, we need to include it in 'included', so
// that the caption will be part of the include text. (+1 to skip newline)
for _, caption := range []string{captionFigure, captionTable, captionQuote} {
if _, _, capcon := p.caption(data[consumed+1:], []byte(caption)); capcon > 0 {
included = append(included, data[consumed+1:consumed+1+capcon]...)
consumed += 1 + capcon
break // there can only be 1 caption.
}
}
p.includeStack.Push(path)
p.block(included)
p.includeStack.Pop()
@@ -295,7 +311,7 @@ func (p *Parser) block(data []byte) {
//
// also works with + or -
if p.uliPrefix(data) > 0 {
data = data[p.list(data, 0, 0):]
data = data[p.list(data, 0, 0, '.'):]
continue
}
@@ -305,14 +321,18 @@ func (p *Parser) block(data []byte) {
// 2. Item 2
if i := p.oliPrefix(data); i > 0 {
start := 0
if i > 2 && p.extensions&OrderedListStart != 0 {
s := string(data[:i-2])
start, _ = strconv.Atoi(s)
if start == 1 {
start = 0
delim := byte('.')
if i > 2 {
if p.extensions&OrderedListStart != 0 {
s := string(data[:i-2])
start, _ = strconv.Atoi(s)
if start == 1 {
start = 0
}
}
delim = data[i-2]
}
data = data[p.list(data, ast.ListTypeOrdered, start):]
data = data[p.list(data, ast.ListTypeOrdered, start, delim):]
continue
}
@@ -326,7 +346,7 @@ func (p *Parser) block(data []byte) {
// : Definition c
if p.extensions&DefinitionLists != 0 {
if p.dliPrefix(data) > 0 {
data = data[p.list(data, ast.ListTypeDefinition, 0):]
data = data[p.list(data, ast.ListTypeDefinition, 0, '.'):]
continue
}
}
@@ -950,7 +970,7 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
}
// Check for caption and if found make it a figure.
if captionContent, id, consumed := p.caption(data[beg:], []byte("Figure: ")); consumed > 0 {
if captionContent, id, consumed := p.caption(data[beg:], []byte(captionFigure)); consumed > 0 {
figure := &ast.CaptionFigure{}
caption := &ast.Caption{}
figure.HeadingID = id
@@ -1070,7 +1090,7 @@ func (p *Parser) quote(data []byte) int {
return end
}
if captionContent, id, consumed := p.caption(data[end:], []byte("Quote: ")); consumed > 0 {
if captionContent, id, consumed := p.caption(data[end:], []byte(captionQuote)); consumed > 0 {
figure := &ast.CaptionFigure{}
caption := &ast.Caption{}
figure.HeadingID = id
@@ -1190,7 +1210,7 @@ func (p *Parser) oliPrefix(data []byte) int {
}
// we need >= 1 digits followed by a dot and a space or a tab
if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') {
if data[i] != '.' && data[i] != ')' || !(data[i+1] == ' ' || data[i+1] == '\t') {
return 0
}
return i + 2
@@ -1210,13 +1230,14 @@ func (p *Parser) dliPrefix(data []byte) int {
}
// parse ordered or unordered list block
func (p *Parser) list(data []byte, flags ast.ListType, start int) int {
func (p *Parser) list(data []byte, flags ast.ListType, start int, delim byte) int {
i := 0
flags |= ast.ListItemBeginningOfList
list := &ast.List{
ListFlags: flags,
Tight: true,
Start: start,
Delimiter: delim,
}
block := p.addBlock(list)
@@ -1305,10 +1326,16 @@ func (p *Parser) listItem(data []byte, flags *ast.ListType) int {
}
}
var bulletChar byte = '*'
var (
bulletChar byte = '*'
delimiter byte = '.'
)
i := p.uliPrefix(data)
if i == 0 {
i = p.oliPrefix(data)
if i > 0 {
delimiter = data[i-2]
}
} else {
bulletChar = data[i-2]
}
@@ -1468,7 +1495,7 @@ gatherlines:
ListFlags: *flags,
Tight: false,
BulletChar: bulletChar,
Delimiter: '.', // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark
Delimiter: delimiter,
}
p.addBlock(listItem)
@@ -1574,7 +1601,7 @@ func (p *Parser) paragraph(data []byte) int {
// did this blank line followed by a definition list item?
if p.extensions&DefinitionLists != 0 {
if i < len(data)-1 && data[i+1] == ':' {
listLen := p.list(data[prev:], ast.ListTypeDefinition, 0)
listLen := p.list(data[prev:], ast.ListTypeDefinition, 0, '.')
return prev + listLen
}
}
@@ -1645,10 +1672,18 @@ func (p *Parser) paragraph(data []byte) int {
}
}
// if there's a table, paragraph is over
if p.extensions&Tables != 0 {
if j, _, _ := p.tableHeader(current, false); j > 0 {
p.renderParagraph(data[:i])
return i
}
}
// if there's a definition list item, prev line is a definition term
if p.extensions&DefinitionLists != 0 {
if p.dliPrefix(current) != 0 {
ret := p.list(data[prev:], ast.ListTypeDefinition, 0)
ret := p.list(data[prev:], ast.ListTypeDefinition, 0, '.')
return ret + prev
}
}

View File

@@ -105,7 +105,7 @@ func (p *Parser) tableFooter(data []byte) bool {
}
// tableHeaders parses the header. If recognized it will also add a table.
func (p *Parser) tableHeader(data []byte) (size int, columns []ast.CellAlignFlags, table ast.Node) {
func (p *Parser) tableHeader(data []byte, doRender bool) (size int, columns []ast.CellAlignFlags, table ast.Node) {
i := 0
colCount := 1
headerIsUnderline := true
@@ -236,11 +236,13 @@ func (p *Parser) tableHeader(data []byte) (size int, columns []ast.CellAlignFlag
return
}
table = &ast.Table{}
p.addBlock(table)
if header != nil {
p.addBlock(&ast.TableHeader{})
p.tableRow(header, columns, true)
if doRender {
table = &ast.Table{}
p.addBlock(table)
if header != nil {
p.addBlock(&ast.TableHeader{})
p.tableRow(header, columns, true)
}
}
size = skipCharN(data, i, '\n', 1)
return
@@ -255,7 +257,7 @@ Bob | 31 | 555-1234
Alice | 27 | 555-4321
*/
func (p *Parser) table(data []byte) int {
i, columns, table := p.tableHeader(data)
i, columns, table := p.tableHeader(data, true)
if i == 0 {
return 0
}
@@ -284,7 +286,7 @@ func (p *Parser) table(data []byte) int {
p.tableRow(data[rowStart:i], columns, false)
}
if captionContent, id, consumed := p.caption(data[i:], []byte("Table: ")); consumed > 0 {
if captionContent, id, consumed := p.caption(data[i:], []byte(captionTable)); consumed > 0 {
caption := &ast.Caption{}
p.Inline(caption, captionContent)

View File

@@ -766,7 +766,22 @@ func entity(p *Parser, data []byte, offset int) (int, ast.Node) {
// undo &amp; escaping or it will be converted to &amp;amp; by another
// escaper in the renderer
if bytes.Equal(ent, []byte("&amp;")) {
ent = []byte{'&'}
return end, newTextNode([]byte{'&'})
}
if len(ent) < 4 {
return end, newTextNode(ent)
}
// if ent consists solely out of numbers (hex or decimal) convert that unicode codepoint to actual rune
codepoint := uint64(0)
var err error
if ent[2] == 'x' || ent[2] == 'X' { // hexadecimal
codepoint, err = strconv.ParseUint(string(ent[3:len(ent)-1]), 16, 64)
} else {
codepoint, err = strconv.ParseUint(string(ent[2:len(ent)-1]), 10, 64)
}
if err == nil { // only if conversion was valid return here.
return end, newTextNode([]byte(string(codepoint)))
}
return end, newTextNode(ent)

118
vendor/github.com/google/uuid/null.go generated vendored Normal file
View File

@@ -0,0 +1,118 @@
// Copyright 2021 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"database/sql/driver"
"encoding/json"
"fmt"
)
var jsonNull = []byte("null")
// NullUUID represents a UUID that may be null.
// NullUUID implements the SQL driver.Scanner interface so
// it can be used as a scan destination:
//
// var u uuid.NullUUID
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
// ...
// if u.Valid {
// // use u.UUID
// } else {
// // NULL value
// }
//
type NullUUID struct {
UUID UUID
Valid bool // Valid is true if UUID is not NULL
}
// Scan implements the SQL driver.Scanner interface.
func (nu *NullUUID) Scan(value interface{}) error {
if value == nil {
nu.UUID, nu.Valid = Nil, false
return nil
}
err := nu.UUID.Scan(value)
if err != nil {
nu.Valid = false
return err
}
nu.Valid = true
return nil
}
// Value implements the driver Valuer interface.
func (nu NullUUID) Value() (driver.Value, error) {
if !nu.Valid {
return nil, nil
}
// Delegate to UUID Value function
return nu.UUID.Value()
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (nu NullUUID) MarshalBinary() ([]byte, error) {
if nu.Valid {
return nu.UUID[:], nil
}
return []byte(nil), nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(nu.UUID[:], data)
nu.Valid = true
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (nu NullUUID) MarshalText() ([]byte, error) {
if nu.Valid {
return nu.UUID.MarshalText()
}
return jsonNull, nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (nu *NullUUID) UnmarshalText(data []byte) error {
id, err := ParseBytes(data)
if err != nil {
nu.Valid = false
return err
}
nu.UUID = id
nu.Valid = true
return nil
}
// MarshalJSON implements json.Marshaler.
func (nu NullUUID) MarshalJSON() ([]byte, error) {
if nu.Valid {
return json.Marshal(nu.UUID)
}
return jsonNull, nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
if bytes.Equal(data, jsonNull) {
*nu = NullUUID{}
return nil // valid null UUID
}
err := json.Unmarshal(data, &nu.UUID)
nu.Valid = err == nil
return err
}

View File

@@ -12,6 +12,7 @@ import (
"fmt"
"io"
"strings"
"sync"
)
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
@@ -33,7 +34,15 @@ const (
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
const randPoolSize = 16 * 16
var (
rander = rand.Reader // random function
poolEnabled = false
poolMu sync.Mutex
poolPos = randPoolSize // protected with poolMu
pool [randPoolSize]byte // protected with poolMu
)
type invalidLengthError struct{ len int }
@@ -41,6 +50,12 @@ func (err invalidLengthError) Error() string {
return fmt.Sprintf("invalid UUID length: %d", err.len)
}
// IsInvalidLengthError is matcher function for custom error invalidLengthError
func IsInvalidLengthError(err error) bool {
_, ok := err.(invalidLengthError)
return ok
}
// Parse decodes s into a UUID or returns an error. Both the standard UUID
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
@@ -249,3 +264,31 @@ func SetRand(r io.Reader) {
}
rander = r
}
// EnableRandPool enables internal randomness pool used for Random
// (Version 4) UUID generation. The pool contains random bytes read from
// the random number generator on demand in batches. Enabling the pool
// may improve the UUID generation throughput significantly.
//
// Since the pool is stored on the Go heap, this feature may be a bad fit
// for security sensitive applications.
//
// Both EnableRandPool and DisableRandPool are not thread-safe and should
// only be called when there is no possibility that New or any other
// UUID Version 4 generation function will be called concurrently.
func EnableRandPool() {
poolEnabled = true
}
// DisableRandPool disables the randomness pool if it was previously
// enabled with EnableRandPool.
//
// Both EnableRandPool and DisableRandPool are not thread-safe and should
// only be called when there is no possibility that New or any other
// UUID Version 4 generation function will be called concurrently.
func DisableRandPool() {
poolEnabled = false
defer poolMu.Unlock()
poolMu.Lock()
poolPos = randPoolSize
}

View File

@@ -27,6 +27,8 @@ func NewString() string {
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// Uses the randomness pool if it was enabled with EnableRandPool.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
@@ -35,7 +37,10 @@ func NewString() string {
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
return NewRandomFromReader(rander)
if !poolEnabled {
return NewRandomFromReader(rander)
}
return newRandomFromPool()
}
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
@@ -49,3 +54,23 @@ func NewRandomFromReader(r io.Reader) (UUID, error) {
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}
func newRandomFromPool() (UUID, error) {
var uuid UUID
poolMu.Lock()
if poolPos == randPoolSize {
_, err := io.ReadFull(rander, pool[:])
if err != nil {
poolMu.Unlock()
return Nil, err
}
poolPos = 0
}
copy(uuid[:], pool[poolPos:(poolPos+16)])
poolPos += 16
poolMu.Unlock()
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

View File

@@ -8,8 +8,6 @@
A high-performance 100% compatible drop-in replacement of "encoding/json"
You can also use thrift like JSON using [thrift-iterator](https://github.com/thrift-iterator/go)
# Benchmark
![benchmark](http://jsoniter.com/benchmarks/go-benchmark.png)

View File

@@ -9,6 +9,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"runtime"
"sync"
"time"
@@ -207,7 +208,9 @@ func (a *API) getUsername(runOpts RunOptions) (username string, err error) {
if err != nil {
return "", err
}
p.ExtraFiles = []*os.File{output.(*os.File)}
if runtime.GOOS != "windows" {
p.ExtraFiles = []*os.File{output.(*os.File)}
}
if err = p.Start(); err != nil {
return "", err
}
@@ -282,7 +285,7 @@ func (a *API) startPipes() (err error) {
defer a.Unlock()
if a.apiCmd != nil {
if err := a.apiCmd.Process.Kill(); err != nil {
return err
return fmt.Errorf("unable to kill previous API command %v", err)
}
}
a.apiCmd = nil
@@ -290,30 +293,32 @@ func (a *API) startPipes() (err error) {
if a.runOpts.StartService {
args := []string{fmt.Sprintf("-enable-bot-lite-mode=%v", a.runOpts.DisableBotLiteMode), "service"}
if err := a.runOpts.Command(args...).Start(); err != nil {
return err
return fmt.Errorf("unable to start service %v", err)
}
}
if a.username, err = a.auth(); err != nil {
return err
return fmt.Errorf("unable to auth: %v", err)
}
cmd := a.runOpts.Command("chat", "notification-settings", fmt.Sprintf("-disable-typing=%v", !a.runOpts.EnableTyping))
if err = cmd.Run(); err != nil {
return err
return fmt.Errorf("unable to set notifiation settings %v", err)
}
a.apiCmd = a.runOpts.Command("chat", "api")
if a.apiInput, err = a.apiCmd.StdinPipe(); err != nil {
return err
return fmt.Errorf("unable to get api stdin: %v", err)
}
output, err := a.apiCmd.StdoutPipe()
if err != nil {
return err
return fmt.Errorf("unabel to get api stdout: %v", err)
}
if runtime.GOOS != "windows" {
a.apiCmd.ExtraFiles = []*os.File{output.(*os.File)}
}
a.apiCmd.ExtraFiles = []*os.File{output.(*os.File)}
if err := a.apiCmd.Start(); err != nil {
return err
return fmt.Errorf("unable to run chat api cmd: %v", err)
}
a.apiOutput = bufio.NewReader(output)
return nil
@@ -508,7 +513,9 @@ func (a *API) Listen(opts ListenOptions) (*Subscription, error) {
time.Sleep(pause)
continue
}
p.ExtraFiles = []*os.File{stderr.(*os.File), output.(*os.File)}
if runtime.GOOS != "windows" {
p.ExtraFiles = []*os.File{stderr.(*os.File), output.(*os.File)}
}
boutput := bufio.NewScanner(output)
if err := p.Start(); err != nil {

View File

@@ -1,67 +0,0 @@
language: go
os:
- linux
- osx
- windows
arch:
- amd64
- arm64
go:
- 1.13.x
- 1.14.x
- 1.15.x
- 1.16.x
- master
env:
- CGO_ENABLED=0
script:
- go vet ./...
- go test -test.v -test.run ^TestCPUID$
- CGO_ENABLED=1 go test -race ./...
- go test -tags=nounsafe -test.v -test.run ^TestCPUID$
- go test -tags=noasm ./...
- go run ./cmd/cpuid/main.go
- go run ./cmd/cpuid/main.go -json
matrix:
allow_failures:
- go: 'master'
fast_finish: true
include:
- stage: other
go: 1.16.x
os: linux
arch: amd64
script:
- diff <(gofmt -d .) <(printf "")
- diff <(gofmt -d ./private) <(printf "")
- curl -sfL https://git.io/goreleaser | VERSION=v0.157.0 sh -s -- check # check goreleaser config for deprecations
- curl -sL https://git.io/goreleaser | VERSION=v0.157.0 sh -s -- --snapshot --skip-publish --rm-dist
- go get github.com/klauspost/asmfmt&&go install github.com/klauspost/asmfmt/cmd/asmfmt
- diff <(asmfmt -d .) <(printf "")
- GOOS=linux GOARCH=386 go test .
- ./test-architectures.sh
- stage: other
go: 1.15.x
os: linux
arch: amd64
script:
- ./test-architectures.sh
deploy:
- provider: script
skip_cleanup: true
script: curl -sL https://git.io/goreleaser | VERSION=v0.157.0 bash || true
on:
tags: true
condition: ($TRAVIS_OS_NAME = linux) && ($TRAVIS_CPU_ARCH = amd64)
go: 1.16.x
branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/

View File

@@ -83,6 +83,7 @@ const (
AVX512DQ // AVX-512 Doubleword and Quadword Instructions
AVX512ER // AVX-512 Exponential and Reciprocal Instructions
AVX512F // AVX-512 Foundation
AVX512FP16 // AVX-512 FP16 Instructions
AVX512IFMA // AVX-512 Integer Fused Multiply-Add Instructions
AVX512PF // AVX-512 Prefetch Instructions
AVX512VBMI // AVX-512 Vector Bit Manipulation Instructions
@@ -96,7 +97,9 @@ const (
BMI2 // Bit Manipulation Instruction Set 2
CLDEMOTE // Cache Line Demote
CLMUL // Carry-less Multiplication
CLZERO // CLZERO instruction supported
CMOV // i686 CMOV
CPBOOST // Core Performance Boost
CX16 // CMPXCHG16B Instruction
ENQCMD // Enqueue Command
ERMS // Enhanced REP MOVSB/STOSB
@@ -106,6 +109,7 @@ const (
GFNI // Galois Field New Instructions
HLE // Hardware Lock Elision
HTT // Hyperthreading (enabled)
HWA // Hardware assert supported. Indicates support for MSRC001_10
HYPERVISOR // This bit has been reserved by Intel & AMD for use by hypervisors
IBPB // Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB)
IBS // Instruction Based Sampling (AMD)
@@ -117,18 +121,25 @@ const (
IBSOPSAM // Instruction Based Sampling Feature (AMD)
IBSRDWROPCNT // Instruction Based Sampling Feature (AMD)
IBSRIPINVALIDCHK // Instruction Based Sampling Feature (AMD)
INT_WBINVD // WBINVD/WBNOINVD are interruptible.
INVLPGB // NVLPGB and TLBSYNC instruction supported
LZCNT // LZCNT instruction
MCAOVERFLOW // MCA overflow recovery support.
MCOMMIT // MCOMMIT instruction supported
MMX // standard MMX
MMXEXT // SSE integer functions or AMD MMX ext
MOVDIR64B // Move 64 Bytes as Direct Store
MOVDIRI // Move Doubleword as Direct Store
MPX // Intel MPX (Memory Protection Extensions)
MSRIRC // Instruction Retired Counter MSR available
NX // NX (No-Execute) bit
POPCNT // POPCNT instruction
RDPRU // RDPRU instruction supported
RDRAND // RDRAND instruction is available
RDSEED // RDSEED instruction is available
RDTSCP // RDTSCP Instruction
RTM // Restricted Transactional Memory
RTM_ALWAYS_ABORT // Indicates that the loaded microcode is forcing RTM abort.
SERIALIZE // Serialize Instruction Execution
SGX // Software Guard Extensions
SGXLC // Software Guard Extensions Launch Control
@@ -141,6 +152,7 @@ const (
SSE4A // AMD Barcelona microarchitecture SSE4a instructions
SSSE3 // Conroe SSSE3 functions
STIBP // Single Thread Indirect Branch Predictors
SUCCOR // Software uncorrectable error containment and recovery capability.
TBM // AMD Trailing Bit Manipulation
TSXLDTRK // Intel TSX Suspend Load Address Tracking
VAES // Vector AES
@@ -194,7 +206,8 @@ type CPUInfo struct {
Family int // CPU family number
Model int // CPU model number
CacheLine int // Cache line size in bytes. Will be 0 if undetectable.
Hz int64 // Clock speed, if known, 0 otherwise
Hz int64 // Clock speed, if known, 0 otherwise. Will attempt to contain base clock speed.
BoostFreq int64 // Max clock speed, if known, 0 otherwise
Cache struct {
L1I int // L1 Instruction Cache (per core or shared). Will be -1 if undetected
L1D int // L1 Data Cache (per core or shared). Will be -1 if undetected
@@ -363,25 +376,42 @@ func (c CPUInfo) LogicalCPU() int {
return int(ebx >> 24)
}
// hertz tries to compute the clock speed of the CPU. If leaf 15 is
// frequencies tries to compute the clock speed of the CPU. If leaf 15 is
// supported, use it, otherwise parse the brand string. Yes, really.
func hertz(model string) int64 {
func (c *CPUInfo) frequencies() {
c.Hz, c.BoostFreq = 0, 0
mfi := maxFunctionID()
if mfi >= 0x15 {
eax, ebx, ecx, _ := cpuid(0x15)
if eax != 0 && ebx != 0 && ecx != 0 {
return int64((int64(ecx) * int64(ebx)) / int64(eax))
c.Hz = (int64(ecx) * int64(ebx)) / int64(eax)
}
}
if mfi >= 0x16 {
a, b, _, _ := cpuid(0x16)
// Base...
if a&0xffff > 0 {
c.Hz = int64(a&0xffff) * 1_000_000
}
// Boost...
if b&0xffff > 0 {
c.BoostFreq = int64(b&0xffff) * 1_000_000
}
}
if c.Hz > 0 {
return
}
// computeHz determines the official rated speed of a CPU from its brand
// string. This insanity is *actually the official documented way to do
// this according to Intel*, prior to leaf 0x15 existing. The official
// documentation only shows this working for exactly `x.xx` or `xxxx`
// cases, e.g., `2.50GHz` or `1300MHz`; this parser will accept other
// sizes.
model := c.BrandName
hz := strings.LastIndex(model, "Hz")
if hz < 3 {
return 0
return
}
var multiplier int64
switch model[hz-1] {
@@ -393,7 +423,7 @@ func hertz(model string) int64 {
multiplier = 1000 * 1000 * 1000 * 1000
}
if multiplier == 0 {
return 0
return
}
freq := int64(0)
divisor := int64(0)
@@ -405,21 +435,22 @@ func hertz(model string) int64 {
decimalShift *= 10
} else if model[i] == '.' {
if divisor != 0 {
return 0
return
}
divisor = decimalShift
} else {
return 0
return
}
}
// we didn't find a space
if i < 0 {
return 0
return
}
if divisor != 0 {
return (freq * multiplier) / divisor
c.Hz = (freq * multiplier) / divisor
return
}
return freq * multiplier
c.Hz = freq * multiplier
}
// VM Will return true if the cpu id indicates we are in
@@ -911,6 +942,7 @@ func support() flagSet {
fs.setIf(ecx&(1<<29) != 0, ENQCMD)
fs.setIf(ecx&(1<<30) != 0, SGXLC)
// CPUID.(EAX=7, ECX=0).EDX
fs.setIf(edx&(1<<11) != 0, RTM_ALWAYS_ABORT)
fs.setIf(edx&(1<<14) != 0, SERIALIZE)
fs.setIf(edx&(1<<16) != 0, TSXLDTRK)
fs.setIf(edx&(1<<26) != 0, IBPB)
@@ -949,6 +981,7 @@ func support() flagSet {
// edx
fs.setIf(edx&(1<<8) != 0, AVX512VP2INTERSECT)
fs.setIf(edx&(1<<22) != 0, AMXBF16)
fs.setIf(edx&(1<<23) != 0, AVX512FP16)
fs.setIf(edx&(1<<24) != 0, AMXTILE)
fs.setIf(edx&(1<<25) != 0, AMXINT8)
// eax1 = CPUID.(EAX=7, ECX=1).EAX
@@ -980,9 +1013,23 @@ func support() flagSet {
}
}
if maxExtendedFunction() >= 0x80000007 {
_, b, _, d := cpuid(0x80000007)
fs.setIf((b&(1<<0)) != 0, MCAOVERFLOW)
fs.setIf((b&(1<<1)) != 0, SUCCOR)
fs.setIf((b&(1<<2)) != 0, HWA)
fs.setIf((d&(1<<9)) != 0, CPBOOST)
}
if maxExtendedFunction() >= 0x80000008 {
_, b, _, _ := cpuid(0x80000008)
fs.setIf((b&(1<<9)) != 0, WBNOINVD)
fs.setIf((b&(1<<8)) != 0, MCOMMIT)
fs.setIf((b&(1<<13)) != 0, INT_WBINVD)
fs.setIf((b&(1<<4)) != 0, RDPRU)
fs.setIf((b&(1<<3)) != 0, INVLPGB)
fs.setIf((b&(1<<1)) != 0, MSRIRC)
fs.setIf((b&(1<<0)) != 0, CLZERO)
}
if maxExtendedFunction() >= 0x8000001b && fs.inSet(IBS) {

View File

@@ -30,6 +30,6 @@ func addInfo(c *CPUInfo, safe bool) {
c.LogicalCores = logicalCores()
c.PhysicalCores = physicalCores()
c.VendorID, c.VendorString = vendorID()
c.Hz = hertz(c.BrandName)
c.cacheSize()
c.frequencies()
}

View File

@@ -24,103 +24,115 @@ func _() {
_ = x[AVX512DQ-14]
_ = x[AVX512ER-15]
_ = x[AVX512F-16]
_ = x[AVX512IFMA-17]
_ = x[AVX512PF-18]
_ = x[AVX512VBMI-19]
_ = x[AVX512VBMI2-20]
_ = x[AVX512VL-21]
_ = x[AVX512VNNI-22]
_ = x[AVX512VP2INTERSECT-23]
_ = x[AVX512VPOPCNTDQ-24]
_ = x[AVXSLOW-25]
_ = x[BMI1-26]
_ = x[BMI2-27]
_ = x[CLDEMOTE-28]
_ = x[CLMUL-29]
_ = x[CMOV-30]
_ = x[CX16-31]
_ = x[ENQCMD-32]
_ = x[ERMS-33]
_ = x[F16C-34]
_ = x[FMA3-35]
_ = x[FMA4-36]
_ = x[GFNI-37]
_ = x[HLE-38]
_ = x[HTT-39]
_ = x[HYPERVISOR-40]
_ = x[IBPB-41]
_ = x[IBS-42]
_ = x[IBSBRNTRGT-43]
_ = x[IBSFETCHSAM-44]
_ = x[IBSFFV-45]
_ = x[IBSOPCNT-46]
_ = x[IBSOPCNTEXT-47]
_ = x[IBSOPSAM-48]
_ = x[IBSRDWROPCNT-49]
_ = x[IBSRIPINVALIDCHK-50]
_ = x[LZCNT-51]
_ = x[MMX-52]
_ = x[MMXEXT-53]
_ = x[MOVDIR64B-54]
_ = x[MOVDIRI-55]
_ = x[MPX-56]
_ = x[NX-57]
_ = x[POPCNT-58]
_ = x[RDRAND-59]
_ = x[RDSEED-60]
_ = x[RDTSCP-61]
_ = x[RTM-62]
_ = x[SERIALIZE-63]
_ = x[SGX-64]
_ = x[SGXLC-65]
_ = x[SHA-66]
_ = x[SSE-67]
_ = x[SSE2-68]
_ = x[SSE3-69]
_ = x[SSE4-70]
_ = x[SSE42-71]
_ = x[SSE4A-72]
_ = x[SSSE3-73]
_ = x[STIBP-74]
_ = x[TBM-75]
_ = x[TSXLDTRK-76]
_ = x[VAES-77]
_ = x[VMX-78]
_ = x[VPCLMULQDQ-79]
_ = x[WAITPKG-80]
_ = x[WBNOINVD-81]
_ = x[XOP-82]
_ = x[AESARM-83]
_ = x[ARMCPUID-84]
_ = x[ASIMD-85]
_ = x[ASIMDDP-86]
_ = x[ASIMDHP-87]
_ = x[ASIMDRDM-88]
_ = x[ATOMICS-89]
_ = x[CRC32-90]
_ = x[DCPOP-91]
_ = x[EVTSTRM-92]
_ = x[FCMA-93]
_ = x[FP-94]
_ = x[FPHP-95]
_ = x[GPA-96]
_ = x[JSCVT-97]
_ = x[LRCPC-98]
_ = x[PMULL-99]
_ = x[SHA1-100]
_ = x[SHA2-101]
_ = x[SHA3-102]
_ = x[SHA512-103]
_ = x[SM3-104]
_ = x[SM4-105]
_ = x[SVE-106]
_ = x[lastID-107]
_ = x[AVX512FP16-17]
_ = x[AVX512IFMA-18]
_ = x[AVX512PF-19]
_ = x[AVX512VBMI-20]
_ = x[AVX512VBMI2-21]
_ = x[AVX512VL-22]
_ = x[AVX512VNNI-23]
_ = x[AVX512VP2INTERSECT-24]
_ = x[AVX512VPOPCNTDQ-25]
_ = x[AVXSLOW-26]
_ = x[BMI1-27]
_ = x[BMI2-28]
_ = x[CLDEMOTE-29]
_ = x[CLMUL-30]
_ = x[CLZERO-31]
_ = x[CMOV-32]
_ = x[CPBOOST-33]
_ = x[CX16-34]
_ = x[ENQCMD-35]
_ = x[ERMS-36]
_ = x[F16C-37]
_ = x[FMA3-38]
_ = x[FMA4-39]
_ = x[GFNI-40]
_ = x[HLE-41]
_ = x[HTT-42]
_ = x[HWA-43]
_ = x[HYPERVISOR-44]
_ = x[IBPB-45]
_ = x[IBS-46]
_ = x[IBSBRNTRGT-47]
_ = x[IBSFETCHSAM-48]
_ = x[IBSFFV-49]
_ = x[IBSOPCNT-50]
_ = x[IBSOPCNTEXT-51]
_ = x[IBSOPSAM-52]
_ = x[IBSRDWROPCNT-53]
_ = x[IBSRIPINVALIDCHK-54]
_ = x[INT_WBINVD-55]
_ = x[INVLPGB-56]
_ = x[LZCNT-57]
_ = x[MCAOVERFLOW-58]
_ = x[MCOMMIT-59]
_ = x[MMX-60]
_ = x[MMXEXT-61]
_ = x[MOVDIR64B-62]
_ = x[MOVDIRI-63]
_ = x[MPX-64]
_ = x[MSRIRC-65]
_ = x[NX-66]
_ = x[POPCNT-67]
_ = x[RDPRU-68]
_ = x[RDRAND-69]
_ = x[RDSEED-70]
_ = x[RDTSCP-71]
_ = x[RTM-72]
_ = x[RTM_ALWAYS_ABORT-73]
_ = x[SERIALIZE-74]
_ = x[SGX-75]
_ = x[SGXLC-76]
_ = x[SHA-77]
_ = x[SSE-78]
_ = x[SSE2-79]
_ = x[SSE3-80]
_ = x[SSE4-81]
_ = x[SSE42-82]
_ = x[SSE4A-83]
_ = x[SSSE3-84]
_ = x[STIBP-85]
_ = x[SUCCOR-86]
_ = x[TBM-87]
_ = x[TSXLDTRK-88]
_ = x[VAES-89]
_ = x[VMX-90]
_ = x[VPCLMULQDQ-91]
_ = x[WAITPKG-92]
_ = x[WBNOINVD-93]
_ = x[XOP-94]
_ = x[AESARM-95]
_ = x[ARMCPUID-96]
_ = x[ASIMD-97]
_ = x[ASIMDDP-98]
_ = x[ASIMDHP-99]
_ = x[ASIMDRDM-100]
_ = x[ATOMICS-101]
_ = x[CRC32-102]
_ = x[DCPOP-103]
_ = x[EVTSTRM-104]
_ = x[FCMA-105]
_ = x[FP-106]
_ = x[FPHP-107]
_ = x[GPA-108]
_ = x[JSCVT-109]
_ = x[LRCPC-110]
_ = x[PMULL-111]
_ = x[SHA1-112]
_ = x[SHA2-113]
_ = x[SHA3-114]
_ = x[SHA512-115]
_ = x[SM3-116]
_ = x[SM4-117]
_ = x[SVE-118]
_ = x[lastID-119]
_ = x[firstID-0]
}
const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXINT8AMXTILEAVXAVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXSLOWBMI1BMI2CLDEMOTECLMULCMOVCX16ENQCMDERMSF16CFMA3FMA4GFNIHLEHTTHYPERVISORIBPBIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKLZCNTMMXMMXEXTMOVDIR64BMOVDIRIMPXNXPOPCNTRDRANDRDSEEDRDTSCPRTMSERIALIZESGXSGXLCSHASSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPTBMTSXLDTRKVAESVMXVPCLMULQDQWAITPKGWBNOINVDXOPAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID"
const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXINT8AMXTILEAVXAVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXSLOWBMI1BMI2CLDEMOTECLMULCLZEROCMOVCPBOOSTCX16ENQCMDERMSF16CFMA3FMA4GFNIHLEHTTHWAHYPERVISORIBPBIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKINT_WBINVDINVLPGBLZCNTMCAOVERFLOWMCOMMITMMXMMXEXTMOVDIR64BMOVDIRIMPXMSRIRCNXPOPCNTRDPRURDRANDRDSEEDRDTSCPRTMRTM_ALWAYS_ABORTSERIALIZESGXSGXLCSHASSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSUCCORTBMTSXLDTRKVAESVMXVPCLMULQDQWAITPKGWBNOINVDXOPAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID"
var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 58, 62, 72, 84, 92, 100, 108, 116, 123, 133, 141, 151, 162, 170, 180, 198, 213, 220, 224, 228, 236, 241, 245, 249, 255, 259, 263, 267, 271, 275, 278, 281, 291, 295, 298, 308, 319, 325, 333, 344, 352, 364, 380, 385, 388, 394, 403, 410, 413, 415, 421, 427, 433, 439, 442, 451, 454, 459, 462, 465, 469, 473, 477, 482, 487, 492, 497, 500, 508, 512, 515, 525, 532, 540, 543, 549, 557, 562, 569, 576, 584, 591, 596, 601, 608, 612, 614, 618, 621, 626, 631, 636, 640, 644, 648, 654, 657, 660, 663, 669}
var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 58, 62, 72, 84, 92, 100, 108, 116, 123, 133, 143, 151, 161, 172, 180, 190, 208, 223, 230, 234, 238, 246, 251, 257, 261, 268, 272, 278, 282, 286, 290, 294, 298, 301, 304, 307, 317, 321, 324, 334, 345, 351, 359, 370, 378, 390, 406, 416, 423, 428, 439, 446, 449, 455, 464, 471, 474, 480, 482, 488, 493, 499, 505, 511, 514, 530, 539, 542, 547, 550, 553, 557, 561, 565, 570, 575, 580, 585, 591, 594, 602, 606, 609, 619, 626, 634, 637, 643, 651, 656, 663, 670, 678, 685, 690, 695, 702, 706, 708, 712, 715, 720, 725, 730, 734, 738, 742, 748, 751, 754, 757, 763}
func (i FeatureID) String() string {
if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) {

View File

@@ -108,7 +108,10 @@ func nickCollisionHandler(c *Client, e Event) {
return
}
c.Cmd.Nick(c.Config.HandleNickCollide(c.GetNick()))
newNick := c.Config.HandleNickCollide(c.GetNick())
if newNick != "" {
c.Cmd.Nick(newNick)
}
}
// handlePING helps respond to ping requests from the server.

View File

@@ -168,6 +168,9 @@ type Config struct {
// an invalid nickname. For example, if "test" is already in use, or is
// blocked by the network/a service, the client will try and use "test_",
// then it will attempt "test__", "test___", and so on.
//
// If HandleNickCollide returns an empty string, the client will not
// attempt to fix nickname collisions, and you must handle this yourself.
HandleNickCollide func(oldNick string) (newNick string)
}

View File

@@ -71,6 +71,7 @@ type Client struct {
WsConnected bool
OnWsConnect func()
reconnectBusy bool
Timeout int
logger *logrus.Entry
rootLogger *logrus.Logger
@@ -80,6 +81,8 @@ type Client struct {
lastPong time.Time
}
var Matterircd bool
func New(login string, pass string, team string, server string, mfatoken string) *Client {
rootLogger := logrus.New()
rootLogger.SetFormatter(&prefixed.TextFormatter{
@@ -229,7 +232,12 @@ func (m *Client) initClient(b *backoff.Backoff) error {
},
Proxy: http.ProxyFromEnvironment,
}
m.Client.HTTPClient.Timeout = time.Second * 10
if m.Timeout == 0 {
m.Timeout = 10
}
m.Client.HTTPClient.Timeout = time.Second * time.Duration(m.Timeout)
// handle MMAUTHTOKEN and personal token
if err := m.handleLoginToken(); err != nil {
@@ -613,7 +621,9 @@ func (m *Client) WsReceiver(ctx context.Context) {
Team: m.Credentials.Team,
}
m.parseMessage(msg)
if !Matterircd {
m.parseMessage(msg)
}
m.MessageChan <- msg
case response := <-m.WsClient.ResponseChannel:

28
vendor/github.com/mattermost/logr/v2/buffer.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
package logr
import (
"bytes"
"sync"
)
// Buffer provides a thread-safe buffer useful for logging to memory in unit tests.
type Buffer struct {
buf bytes.Buffer
mux sync.Mutex
}
func (b *Buffer) Read(p []byte) (n int, err error) {
b.mux.Lock()
defer b.mux.Unlock()
return b.buf.Read(p)
}
func (b *Buffer) Write(p []byte) (n int, err error) {
b.mux.Lock()
defer b.mux.Unlock()
return b.buf.Write(p)
}
func (b *Buffer) String() string {
b.mux.Lock()
defer b.mux.Unlock()
return b.buf.String()
}

View File

@@ -31,8 +31,8 @@ type TargetFactory func(targetType string, options json.RawMessage) (logr.Target
type FormatterFactory func(format string, options json.RawMessage) (logr.Formatter, error)
type Factories struct {
targetFactory TargetFactory // can be nil
formatterFactory FormatterFactory // can be nil
TargetFactory TargetFactory // can be nil
FormatterFactory FormatterFactory // can be nil
}
var removeAll = func(ti logr.TargetInfo) bool { return true }
@@ -56,7 +56,7 @@ func ConfigureTargets(lgr *logr.Logr, config map[string]TargetCfg, factories *Fa
}
for name, tcfg := range config {
target, err := newTarget(tcfg.Type, tcfg.Options, factories.targetFactory)
target, err := newTarget(tcfg.Type, tcfg.Options, factories.TargetFactory)
if err != nil {
return fmt.Errorf("error creating log target %s: %w", name, err)
}
@@ -65,7 +65,7 @@ func ConfigureTargets(lgr *logr.Logr, config map[string]TargetCfg, factories *Fa
continue
}
formatter, err := newFormatter(tcfg.Format, tcfg.FormatOptions, factories.formatterFactory)
formatter, err := newFormatter(tcfg.Format, tcfg.FormatOptions, factories.FormatterFactory)
if err != nil {
return fmt.Errorf("error creating formatter for log target %s: %w", name, err)
}

View File

@@ -15,7 +15,7 @@ var (
Space = []byte{' '}
Newline = []byte{'\n'}
Quote = []byte{'"'}
Colon = []byte{'"'}
Colon = []byte{':'}
)
// LogCloner is implemented by `Any` types that require a clone to be provided

View File

@@ -11,6 +11,7 @@ type StdFilter struct {
// is enabled for this filter.
func (lt StdFilter) GetEnabledLevel(level Level) (Level, bool) {
enabled := level.ID <= lt.Lvl.ID
stackTrace := level.ID <= lt.Stacktrace.ID
var levelEnabled Level
if enabled {
@@ -33,6 +34,11 @@ func (lt StdFilter) GetEnabledLevel(level Level) (Level, bool) {
levelEnabled = level
}
}
if stackTrace {
levelEnabled.Stacktrace = true
}
return levelEnabled, enabled
}

View File

@@ -117,3 +117,81 @@ func (s Sugar) Fatalf(format string, args ...interface{}) {
func (s Sugar) Panicf(format string, args ...interface{}) {
s.Logf(Panic, format, args...)
}
//
// K/V style
//
// With returns a new Sugar logger with the specified key/value pairs added to the
// fields list.
func (s Sugar) With(keyValuePairs ...interface{}) Sugar {
return s.logger.With(s.argsToFields(keyValuePairs)...).Sugar()
}
// Tracew outputs at trace level with the specified key/value pairs converted to fields.
func (s Sugar) Tracew(msg string, keyValuePairs ...interface{}) {
s.logger.Log(Trace, msg, s.argsToFields(keyValuePairs)...)
}
// Debugw outputs at debug level with the specified key/value pairs converted to fields.
func (s Sugar) Debugw(msg string, keyValuePairs ...interface{}) {
s.logger.Log(Debug, msg, s.argsToFields(keyValuePairs)...)
}
// Infow outputs at info level with the specified key/value pairs converted to fields.
func (s Sugar) Infow(msg string, keyValuePairs ...interface{}) {
s.logger.Log(Info, msg, s.argsToFields(keyValuePairs)...)
}
// Warnw outputs at warn level with the specified key/value pairs converted to fields.
func (s Sugar) Warnw(msg string, keyValuePairs ...interface{}) {
s.logger.Log(Warn, msg, s.argsToFields(keyValuePairs)...)
}
// Errorw outputs at error level with the specified key/value pairs converted to fields.
func (s Sugar) Errorw(msg string, keyValuePairs ...interface{}) {
s.logger.Log(Error, msg, s.argsToFields(keyValuePairs)...)
}
// Fatalw outputs at fatal level with the specified key/value pairs converted to fields.
func (s Sugar) Fatalw(msg string, keyValuePairs ...interface{}) {
s.logger.Log(Fatal, msg, s.argsToFields(keyValuePairs)...)
}
// Panicw outputs at panic level with the specified key/value pairs converted to fields.
func (s Sugar) Panicw(msg string, keyValuePairs ...interface{}) {
s.logger.Log(Panic, msg, s.argsToFields(keyValuePairs)...)
}
// argsToFields converts an array of args, possibly containing name/value pairs
// into a []Field.
func (s Sugar) argsToFields(keyValuePairs []interface{}) []Field {
if len(keyValuePairs) == 0 {
return nil
}
fields := make([]Field, 0, len(keyValuePairs))
count := len(keyValuePairs)
for i := 0; i < count; {
if fld, ok := keyValuePairs[i].(Field); ok {
fields = append(fields, fld)
i++
continue
}
if i == count-1 {
s.logger.Error("invalid key/value pair", Any("arg", keyValuePairs[i]))
break
}
// we should have a key/value pair now. The key must be a string.
if key, ok := keyValuePairs[i].(string); !ok {
s.logger.Error("invalid key for key/value pair", Int("pos", i))
} else {
fields = append(fields, Any(key, keyValuePairs[i+1]))
}
i += 2
}
return fields
}

View File

@@ -0,0 +1,72 @@
package targets
import (
"strings"
"sync"
"testing"
"github.com/mattermost/logr/v2"
"github.com/mattermost/logr/v2/formatters"
)
// Testing is a simple log target that writes to a (*testing.T) log.
type Testing struct {
mux sync.Mutex
t *testing.T
}
func NewTestingTarget(t *testing.T) *Testing {
return &Testing{
t: t,
}
}
// Init is called once to initialize the target.
func (tt *Testing) Init() error {
return nil
}
// Write outputs bytes to this file target.
func (tt *Testing) Write(p []byte, rec *logr.LogRec) (int, error) {
tt.mux.Lock()
defer tt.mux.Unlock()
if tt.t != nil {
s := strings.TrimSpace(string(p))
tt.t.Log(s)
}
return len(p), nil
}
// Shutdown is called once to free/close any resources.
// Target queue is already drained when this is called.
func (tt *Testing) Shutdown() error {
tt.mux.Lock()
defer tt.mux.Unlock()
tt.t = nil
return nil
}
// CreateTestLogger creates a logger for unit tests. Log records are output to `(*testing.T).Log`.
// A new logger is returned along with a method to shutdown the new logger.
func CreateTestLogger(t *testing.T, levels ...logr.Level) (logger logr.Logger, shutdown func() error) {
lgr, _ := logr.New()
filter := logr.NewCustomFilter(levels...)
formatter := &formatters.Plain{EnableCaller: true}
target := NewTestingTarget(t)
if err := lgr.AddTarget(target, "test", filter, formatter, 1000); err != nil {
t.Fail()
}
shutdown = func() error {
err := lgr.Shutdown()
if err != nil {
target.mux.Lock()
target.t.Error("error shutting down test logger", err)
target.mux.Unlock()
}
return err
}
return lgr.NewLogger(), shutdown
}

View File

@@ -4,6 +4,8 @@
package model
import (
"strings"
"github.com/francoispqt/gojay"
)
@@ -268,7 +270,10 @@ func newAuditCommandArgs(ca *CommandArgs) auditCommandArgs {
cmdargs.ChannelID = ca.ChannelId
cmdargs.TeamID = ca.TeamId
cmdargs.TriggerID = ca.TriggerId
cmdargs.Command = ca.Command
cmdFields := strings.Fields(ca.Command)
if len(cmdFields) > 0 {
cmdargs.Command = cmdFields[0]
}
}
return cmdargs
}

View File

@@ -63,12 +63,8 @@ func (b *Bot) Clone() *Bot {
return &copy
}
// IsValid validates the bot and returns an error if it isn't configured correctly.
func (b *Bot) IsValid() *AppError {
if !IsValidId(b.UserId) {
return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
// IsValidCreate validates bot for Create call. This skips validations of fields that are auto-filled on Create
func (b *Bot) IsValidCreate() *AppError {
if !IsValidUsername(b.Username) {
return NewAppError("Bot.IsValid", "model.bot.is_valid.username.app_error", b.Trace(), "", http.StatusBadRequest)
}
@@ -85,6 +81,15 @@ func (b *Bot) IsValid() *AppError {
return NewAppError("Bot.IsValid", "model.bot.is_valid.creator_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
return nil
}
// IsValid validates the bot and returns an error if it isn't configured correctly.
func (b *Bot) IsValid() *AppError {
if !IsValidId(b.UserId) {
return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
if b.CreateAt == 0 {
return NewAppError("Bot.IsValid", "model.bot.is_valid.create_at.app_error", b.Trace(), "", http.StatusBadRequest)
}
@@ -92,8 +97,7 @@ func (b *Bot) IsValid() *AppError {
if b.UpdateAt == 0 {
return NewAppError("Bot.IsValid", "model.bot.is_valid.update_at.app_error", b.Trace(), "", http.StatusBadRequest)
}
return nil
return b.IsValidCreate()
}
// PreSave should be run before saving a new bot to the database.

View File

@@ -56,6 +56,7 @@ type Channel struct {
Shared *bool `json:"shared"`
TotalMsgCountRoot int64 `json:"total_msg_count_root"`
PolicyID *string `json:"policy_id" db:"-"`
LastRootPostAt int64 `json:"last_root_post_at"`
}
type ChannelWithTeamData struct {

View File

@@ -69,7 +69,6 @@ type ChannelMemberForExport struct {
}
func (o *ChannelMember) IsValid() *AppError {
if !IsValidId(o.ChannelId) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
}
@@ -106,6 +105,11 @@ func (o *ChannelMember) IsValid() *AppError {
}
}
if len(o.Roles) > UserRolesMaxLength {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.roles_limit.app_error",
map[string]interface{}{"Limit": UserRolesMaxLength}, "", http.StatusBadRequest)
}
return nil
}

View File

@@ -3899,7 +3899,13 @@ func (c *Client4) SearchPostsWithParams(teamId string, params *SearchParameter)
if jsonErr != nil {
return nil, nil, NewAppError("SearchFilesWithParams", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
}
r, err := c.DoAPIPost(c.teamRoute(teamId)+"/posts/search", string(js))
var route string
if teamId == "" {
route = c.postsRoute() + "/search"
} else {
route = c.teamRoute(teamId) + "/posts/search"
}
r, err := c.DoAPIPost(route, string(js))
if err != nil {
return nil, BuildResponse(r), err
}
@@ -3917,7 +3923,13 @@ func (c *Client4) SearchPostsWithParams(teamId string, params *SearchParameter)
// SearchPostsWithMatches returns any posts with matching terms string, including.
func (c *Client4) SearchPostsWithMatches(teamId string, terms string, isOrSearch bool) (*PostSearchResults, *Response, error) {
requestBody := map[string]interface{}{"terms": terms, "is_or_search": isOrSearch}
r, err := c.DoAPIPost(c.teamRoute(teamId)+"/posts/search", StringInterfaceToJSON(requestBody))
var route string
if teamId == "" {
route = c.postsRoute() + "/search"
} else {
route = c.teamRoute(teamId) + "/posts/search"
}
r, err := c.DoAPIPost(route, StringInterfaceToJSON(requestBody))
if err != nil {
return nil, BuildResponse(r), err
}

View File

@@ -20,8 +20,9 @@ var MockCWS string
type BillingScheme string
const (
BillingSchemePerSeat = BillingScheme("per_seat")
BillingSchemeFlatFee = BillingScheme("flat_fee")
BillingSchemePerSeat = BillingScheme("per_seat")
BillingSchemeFlatFee = BillingScheme("flat_fee")
BillingSchemeSalesServe = BillingScheme("sales_serve")
)
type RecurringInterval string
@@ -104,7 +105,7 @@ type Address struct {
// PaymentMethod represents methods of payment for a customer.
type PaymentMethod struct {
Type string `json:"type"`
LastFour int `json:"last_four"`
LastFour string `json:"last_four"`
ExpMonth int `json:"exp_month"`
ExpYear int `json:"exp_year"`
CardBrand string `json:"card_brand"`
@@ -169,7 +170,7 @@ type CWSWebhookPayload struct {
type FailedPayment struct {
CardBrand string `json:"card_brand"`
LastFour int `json:"last_four"`
LastFour string `json:"last_four"`
FailureMessage string `json:"failure_message"`
}

View File

@@ -352,6 +352,7 @@ type ServiceSettings struct {
EnableBotAccountCreation *bool `access:"integrations_bot_accounts"`
EnableSVGs *bool `access:"site_posts"`
EnableLatex *bool `access:"site_posts"`
EnableInlineLatex *bool `access:"site_posts"`
EnableAPIChannelDeletion *bool
EnableLocalMode *bool
LocalModeSocketLocation *string // telemetry: none
@@ -736,6 +737,10 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
}
}
if s.EnableInlineLatex == nil {
s.EnableInlineLatex = NewBool(true)
}
if s.EnableLocalMode == nil {
s.EnableLocalMode = NewBool(false)
}
@@ -2610,8 +2615,9 @@ func (s *DataRetentionSettings) SetDefaults() {
}
type JobSettings struct {
RunJobs *bool `access:"write_restrictable,cloud_restrictable"`
RunScheduler *bool `access:"write_restrictable,cloud_restrictable"`
RunJobs *bool `access:"write_restrictable,cloud_restrictable"`
RunScheduler *bool `access:"write_restrictable,cloud_restrictable"`
CleanupJobsThresholdDays *int `access:"write_restrictable,cloud_restrictable"`
}
func (s *JobSettings) SetDefaults() {
@@ -2622,6 +2628,10 @@ func (s *JobSettings) SetDefaults() {
if s.RunScheduler == nil {
s.RunScheduler = NewBool(true)
}
if s.CleanupJobsThresholdDays == nil {
s.CleanupJobsThresholdDays = NewInt(-1)
}
}
type CloudSettings struct {
@@ -3736,9 +3746,11 @@ func (o *Config) Sanitize() {
*o.LdapSettings.BindPassword = FakeSetting
}
*o.FileSettings.PublicLinkSalt = FakeSetting
if o.FileSettings.PublicLinkSalt != nil {
*o.FileSettings.PublicLinkSalt = FakeSetting
}
if *o.FileSettings.AmazonS3SecretAccessKey != "" {
if o.FileSettings.AmazonS3SecretAccessKey != nil && *o.FileSettings.AmazonS3SecretAccessKey != "" {
*o.FileSettings.AmazonS3SecretAccessKey = FakeSetting
}
@@ -3746,7 +3758,7 @@ func (o *Config) Sanitize() {
*o.EmailSettings.SMTPPassword = FakeSetting
}
if *o.GitLabSettings.Secret != "" {
if o.GitLabSettings.Secret != nil && *o.GitLabSettings.Secret != "" {
*o.GitLabSettings.Secret = FakeSetting
}
@@ -3762,10 +3774,17 @@ func (o *Config) Sanitize() {
*o.OpenIdSettings.Secret = FakeSetting
}
*o.SqlSettings.DataSource = FakeSetting
*o.SqlSettings.AtRestEncryptKey = FakeSetting
if o.SqlSettings.DataSource != nil {
*o.SqlSettings.DataSource = FakeSetting
}
*o.ElasticsearchSettings.Password = FakeSetting
if o.SqlSettings.AtRestEncryptKey != nil {
*o.SqlSettings.AtRestEncryptKey = FakeSetting
}
if o.ElasticsearchSettings.Password != nil {
*o.ElasticsearchSettings.Password = FakeSetting
}
for i := range o.SqlSettings.DataSourceReplicas {
o.SqlSettings.DataSourceReplicas[i] = FakeSetting
@@ -3775,7 +3794,9 @@ func (o *Config) Sanitize() {
o.SqlSettings.DataSourceSearchReplicas[i] = FakeSetting
}
if o.MessageExportSettings.GlobalRelaySettings.SMTPPassword != nil && *o.MessageExportSettings.GlobalRelaySettings.SMTPPassword != "" {
if o.MessageExportSettings.GlobalRelaySettings != nil &&
o.MessageExportSettings.GlobalRelaySettings.SMTPPassword != nil &&
*o.MessageExportSettings.GlobalRelaySettings.SMTPPassword != "" {
*o.MessageExportSettings.GlobalRelaySettings.SMTPPassword = FakeSetting
}
@@ -3783,7 +3804,9 @@ func (o *Config) Sanitize() {
*o.ServiceSettings.GfycatAPISecret = FakeSetting
}
*o.ServiceSettings.SplitKey = FakeSetting
if o.ServiceSettings.SplitKey != nil {
*o.ServiceSettings.SplitKey = FakeSetting
}
}
// structToMapFilteredByTag converts a struct into a map removing those fields that has the tag passed

View File

@@ -33,9 +33,6 @@ type FeatureFlags struct {
PluginApps string `plugin_id:"com.mattermost.apps"`
PluginFocalboard string `plugin_id:"focalboard"`
// Enable timed dnd support for user status
TimedDND bool
PermalinkPreviews bool
// Enable the Global Header
@@ -43,6 +40,23 @@ type FeatureFlags struct {
// Enable different team menu button treatments, possible values = ("none", "by_team_name", "inverted_sidebar_bg_color")
AddChannelButton string
// Enable different treatments for first time users, possible values = ("none", "tour_point", "around_input")
PrewrittenMessages string
// Enable different treatments for first time users, possible values = ("none", "tips_and_next_steps")
DownloadAppsCTA string
// Determine whether when a user gets created, they'll have noisy notifications e.g. Send desktop notifications for all activity
NewAccountNoisy bool
// Enable Boards Unfurl Preview
BoardsUnfurl bool
// Enable Calls plugin support in the mobile app
CallsMobile bool
// Start A/B tour tips automatically, possible values = ("none", "auto")
AutoTour string
}
func (f *FeatureFlags) SetDefaults() {
@@ -54,10 +68,15 @@ func (f *FeatureFlags) SetDefaults() {
f.AppsEnabled = false
f.PluginApps = ""
f.PluginFocalboard = ""
f.TimedDND = false
f.PermalinkPreviews = true
f.GlobalHeader = true
f.AddChannelButton = "by_team_name"
f.PrewrittenMessages = "tour_point"
f.DownloadAppsCTA = "tips_and_next_steps"
f.NewAccountNoisy = false
f.BoardsUnfurl = true
f.CallsMobile = false
f.AutoTour = "none"
}
func (f *FeatureFlags) Plugins() map[string]string {

View File

@@ -115,6 +115,14 @@ func (p *PostAction) Equals(input *PostAction) bool {
}
// Compare PostActionIntegration
// If input is nil, then return true if original is also nil.
// Else return false.
if input.Integration == nil {
return p.Integration == nil
}
// Both are unequal and not nil.
if p.Integration.URL != input.Integration.URL {
return false
}

View File

@@ -9,6 +9,7 @@ const (
PostEmbedOpengraph PostEmbedType = "opengraph"
PostEmbedLink PostEmbedType = "link"
PostEmbedPermalink PostEmbedType = "permalink"
PostEmbedBoards PostEmbedType = "boards"
)
type PostEmbedType string

View File

@@ -4,6 +4,7 @@
package model
import (
"net/http"
"strconv"
"strings"
@@ -78,6 +79,27 @@ func (s *Session) DeepCopy() *Session {
return &copySession
}
func (s *Session) IsValid() *AppError {
if !IsValidId(s.Id) {
return NewAppError("Session.IsValid", "model.session.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(s.UserId) {
return NewAppError("Session.IsValid", "model.session.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if s.CreateAt == 0 {
return NewAppError("Session.IsValid", "model.session.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
if len(s.Roles) > UserRolesMaxLength {
return NewAppError("Session.IsValid", "model.session.is_valid.roles_limit.app_error",
map[string]interface{}{"Limit": UserRolesMaxLength}, "session_id="+s.Id, http.StatusBadRequest)
}
return nil
}
func (s *Session) PreSave() {
if s.Id == "" {
s.Id = NewId()

View File

@@ -238,6 +238,7 @@ func (scf *SharedChannelAttachment) IsValid() *AppError {
type SharedChannelFilterOpts struct {
TeamId string
CreatorId string
MemberId string
ExcludeHome bool
ExcludeRemote bool
}

View File

@@ -98,7 +98,6 @@ func TeamMemberWithErrorToString(o *TeamMemberWithError) string {
}
func (o *TeamMember) IsValid() *AppError {
if !IsValidId(o.TeamId) {
return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
}
@@ -107,6 +106,11 @@ func (o *TeamMember) IsValid() *AppError {
return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Roles) > UserRolesMaxLength {
return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.roles_limit.app_error",
map[string]interface{}{"Limit": UserRolesMaxLength}, "", http.StatusBadRequest)
}
return nil
}

View File

@@ -60,6 +60,7 @@ const (
UserPasswordMaxLength = 72
UserLocaleMaxLength = 5
UserTimezoneMaxRunes = 256
UserRolesMaxLength = 256
)
//msgp:tuple User
@@ -261,7 +262,6 @@ func (u *User) DeepCopy() *User {
// IsValid validates the user and returns an error if it isn't configured
// correctly.
func (u *User) IsValid() *AppError {
if !IsValidId(u.Id) {
return InvalidUserError("id", "")
}
@@ -332,6 +332,11 @@ func (u *User) IsValid() *AppError {
}
}
if len(u.Roles) > UserRolesMaxLength {
return NewAppError("User.IsValid", "model.user.is_valid.roles_limit.app_error",
map[string]interface{}{"Limit": UserRolesMaxLength}, "user_id="+u.Id, http.StatusBadRequest)
}
return nil
}

View File

@@ -6,6 +6,7 @@ package model
import (
"bytes"
"crypto/rand"
"database/sql/driver"
"encoding/base32"
"encoding/json"
"fmt"
@@ -24,6 +25,7 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/i18n"
"github.com/pborman/uuid"
"github.com/pkg/errors"
)
const (
@@ -72,6 +74,30 @@ func (sa StringArray) Equals(input StringArray) bool {
return true
}
// Value converts StringArray to database value
func (sa StringArray) Value() (driver.Value, error) {
return json.Marshal(sa)
}
// Scan converts database column value to StringArray
func (sa *StringArray) Scan(value interface{}) error {
if value == nil {
return nil
}
buf, ok := value.([]byte)
if ok {
return json.Unmarshal(buf, sa)
}
str, ok := value.(string)
if ok {
return json.Unmarshal([]byte(str), sa)
}
return errors.New("received value is neither a byte slice nor string")
}
var translateFunc i18n.TranslateFunc
var translateFuncOnce sync.Once

View File

@@ -13,8 +13,7 @@ import (
// It should be maintained in chronological order with most current
// release at the front of the list.
var versions = []string{
"6.0.2",
"6.0.1",
"6.1.0",
"6.0.0",
"5.39.0",
"5.38.0",

View File

@@ -49,6 +49,9 @@ type LogRec = logr.LogRec
type LogCloner = logr.LogCloner
type MetricsCollector = logr.MetricsCollector
type TargetCfg = logrcfg.TargetCfg
type TargetFactory = logrcfg.TargetFactory
type FormatterFactory = logrcfg.FormatterFactory
type Factories = logrcfg.Factories
type Sugar = logr.Sugar
// LoggerConfiguration is a map of LogTarget configurations.
@@ -179,7 +182,10 @@ func NewLogger(options ...Option) (*Logger, error) {
// For each case JSON containing log targets is provided. Target name collisions are resolved
// using the following precedence:
// cfgFile > cfgEscaped
func (l *Logger) Configure(cfgFile string, cfgEscaped string) error {
//
// An optional set of factories can be provided which will be called to create any target
// types or formatters not built-in.
func (l *Logger) Configure(cfgFile string, cfgEscaped string, factories *Factories) error {
if atomic.LoadInt32(l.lockConfig) != 0 {
return ErrConfigurationLock
}
@@ -213,16 +219,18 @@ func (l *Logger) Configure(cfgFile string, cfgEscaped string) error {
return nil
}
return logrcfg.ConfigureTargets(l.log.Logr(), cfgMap.toTargetCfg(), nil)
return logrcfg.ConfigureTargets(l.log.Logr(), cfgMap.toTargetCfg(), factories)
}
// ConfigureTargets provides a new configuration for this logger via a `LoggerConfig` map.
// Typically `mlog.Configure` is used instead which accepts JSON formatted configuration.
func (l *Logger) ConfigureTargets(cfg LoggerConfiguration) error {
// An optional set of factories can be provided which will be called to create any target
// types or formatters not built-in.
func (l *Logger) ConfigureTargets(cfg LoggerConfiguration, factories *Factories) error {
if atomic.LoadInt32(l.lockConfig) != 0 {
return ErrConfigurationLock
}
return logrcfg.ConfigureTargets(l.log.Logr(), cfg.toTargetCfg(), nil)
return logrcfg.ConfigureTargets(l.log.Logr(), cfg.toTargetCfg(), factories)
}
// LockConfiguration disallows further configuration changes until `UnlockConfiguration`
@@ -405,6 +413,22 @@ func GetPackageName(f string) string {
return f
}
// ShouldQuote returns true if val contains any characters that might be unsafe
// when injecting log output into an aggregator, viewer or report.
// Returning true means that val should be surrounded by quotation marks before being
// output into logs.
func ShouldQuote(val string) bool {
for _, c := range val {
if !((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
c == '-' || c == '.' || c == '_' || c == '/' || c == '@' || c == '^' || c == '+') {
return true
}
}
return false
}
type logWriter struct {
logger *Logger
}

View File

@@ -1,15 +0,0 @@
language: go
sudo: false
go:
- 1.13.x
- tip
before_install:
- go get -t -v ./...
script:
- ./go.test.sh
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -1,6 +1,6 @@
# go-colorable
[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable)
[![Build Status](https://github.com/mattn/go-colorable/workflows/test/badge.svg)](https://github.com/mattn/go-colorable/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/mattn/go-colorable/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-colorable)
[![GoDoc](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable)

View File

@@ -1,3 +1,4 @@
//go:build appengine
// +build appengine
package colorable

View File

@@ -1,5 +1,5 @@
// +build !windows
// +build !appengine
//go:build !windows && !appengine
// +build !windows,!appengine
package colorable

View File

@@ -1,5 +1,5 @@
// +build windows
// +build !appengine
//go:build windows && !appengine
// +build windows,!appengine
package colorable
@@ -452,18 +452,22 @@ func (w *Writer) Write(data []byte) (n int, err error) {
} else {
er = bytes.NewReader(data)
}
var bw [1]byte
var plaintext bytes.Buffer
loop:
for {
c1, err := er.ReadByte()
if err != nil {
plaintext.WriteTo(w.out)
break loop
}
if c1 != 0x1b {
bw[0] = c1
w.out.Write(bw[:])
plaintext.WriteByte(c1)
continue
}
_, err = plaintext.WriteTo(w.out)
if err != nil {
break loop
}
c2, err := er.ReadByte()
if err != nil {
break loop

View File

@@ -18,18 +18,22 @@ func NewNonColorable(w io.Writer) io.Writer {
// Write writes data on console
func (w *NonColorable) Write(data []byte) (n int, err error) {
er := bytes.NewReader(data)
var bw [1]byte
var plaintext bytes.Buffer
loop:
for {
c1, err := er.ReadByte()
if err != nil {
plaintext.WriteTo(w.out)
break loop
}
if c1 != 0x1b {
bw[0] = c1
w.out.Write(bw[:])
plaintext.WriteByte(c1)
continue
}
_, err = plaintext.WriteTo(w.out)
if err != nil {
break loop
}
c2, err := er.ReadByte()
if err != nil {
break loop

View File

@@ -1,4 +1,4 @@
# MinIO Go Client SDK for Amazon S3 Compatible Cloud Storage [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io) [![Sourcegraph](https://sourcegraph.com/github.com/minio/minio-go/-/badge.svg)](https://sourcegraph.com/github.com/minio/minio-go?badge) [![Apache V2 License](http://img.shields.io/badge/license-Apache%20V2-blue.svg)](https://github.com/minio/minio-go/blob/master/LICENSE)
# MinIO Go Client SDK for Amazon S3 Compatible Cloud Storage [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io) [![Sourcegraph](https://sourcegraph.com/github.com/minio/minio-go/-/badge.svg)](https://sourcegraph.com/github.com/minio/minio-go?badge) [![Apache V2 License](https://img.shields.io/badge/license-Apache%20V2-blue.svg)](https://github.com/minio/minio-go/blob/master/LICENSE)
The MinIO Go Client SDK provides simple APIs to access any Amazon S3 compatible object storage.
@@ -171,9 +171,9 @@ The full API Reference is available here.
* [`PresignedPostPolicy`](https://docs.min.io/docs/golang-client-api-reference#PresignedPostPolicy)
### API Reference : Client custom settings
* [`SetAppInfo`](http://docs.min.io/docs/golang-client-api-reference#SetAppInfo)
* [`TraceOn`](http://docs.min.io/docs/golang-client-api-reference#TraceOn)
* [`TraceOff`](http://docs.min.io/docs/golang-client-api-reference#TraceOff)
* [`SetAppInfo`](https://docs.min.io/docs/golang-client-api-reference#SetAppInfo)
* [`TraceOn`](https://docs.min.io/docs/golang-client-api-reference#TraceOn)
* [`TraceOff`](https://docs.min.io/docs/golang-client-api-reference#TraceOff)
## Full Examples
@@ -248,4 +248,4 @@ The full API Reference is available here.
[Contributors Guide](https://github.com/minio/minio-go/blob/master/CONTRIBUTING.md)
## License
This SDK is distributed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0), see [LICENSE](https://github.com/minio/minio-go/blob/master/LICENSE) and [NOTICE](https://github.com/minio/minio-go/blob/master/NOTICE) for more information.
This SDK is distributed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0), see [LICENSE](https://github.com/minio/minio-go/blob/master/LICENSE) and [NOTICE](https://github.com/minio/minio-go/blob/master/NOTICE) for more information.

View File

@@ -27,6 +27,7 @@ import (
"net/url"
"time"
"github.com/google/uuid"
"github.com/minio/minio-go/v7/pkg/replication"
"github.com/minio/minio-go/v7/pkg/s3utils"
)
@@ -187,12 +188,39 @@ func (c Client) GetBucketReplicationMetrics(ctx context.Context, bucketName stri
return s, nil
}
// mustGetUUID - get a random UUID.
func mustGetUUID() string {
u, err := uuid.NewRandom()
if err != nil {
return ""
}
return u.String()
}
// ResetBucketReplication kicks off replication of previously replicated objects if ExistingObjectReplication
// is enabled in the replication config
func (c Client) ResetBucketReplication(ctx context.Context, bucketName string, olderThan time.Duration) (resetID string, err error) {
func (c Client) ResetBucketReplication(ctx context.Context, bucketName string, olderThan time.Duration) (rID string, err error) {
rID = mustGetUUID()
_, err = c.resetBucketReplicationOnTarget(ctx, bucketName, olderThan, "", rID)
if err != nil {
return rID, err
}
return rID, nil
}
// ResetBucketReplication kicks off replication of previously replicated objects if ExistingObjectReplication
// is enabled in the replication config
func (c Client) ResetBucketReplicationOnTarget(ctx context.Context, bucketName string, olderThan time.Duration, tgtArn string) (rinfo replication.ResyncTargetsInfo, err error) {
rID := mustGetUUID()
return c.resetBucketReplicationOnTarget(ctx, bucketName, olderThan, tgtArn, rID)
}
// ResetBucketReplication kicks off replication of previously replicated objects if ExistingObjectReplication
// is enabled in the replication config
func (c Client) resetBucketReplicationOnTarget(ctx context.Context, bucketName string, olderThan time.Duration, tgtArn string, resetID string) (rinfo replication.ResyncTargetsInfo, err error) {
// Input validation.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return "", err
if err = s3utils.CheckValidBucketName(bucketName); err != nil {
return
}
// Get resources properly escaped and lined up before
// using them in http request.
@@ -201,7 +229,10 @@ func (c Client) ResetBucketReplication(ctx context.Context, bucketName string, o
if olderThan > 0 {
urlValues.Set("older-than", olderThan.String())
}
if tgtArn != "" {
urlValues.Set("arn", tgtArn)
}
urlValues.Set("reset-id", resetID)
// Execute GET on bucket to get replication config.
resp, err := c.executeMethod(ctx, http.MethodPut, requestMetadata{
bucketName: bucketName,
@@ -210,19 +241,19 @@ func (c Client) ResetBucketReplication(ctx context.Context, bucketName string, o
defer closeResponse(resp)
if err != nil {
return "", err
return rinfo, err
}
if resp.StatusCode != http.StatusOK {
return "", httpRespToErrorResponse(resp, bucketName, "")
return rinfo, httpRespToErrorResponse(resp, bucketName, "")
}
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
return rinfo, err
}
if err := json.Unmarshal(respBytes, &resetID); err != nil {
return "", err
if err := json.Unmarshal(respBytes, &rinfo); err != nil {
return rinfo, err
}
return resetID, nil
return rinfo, nil
}

View File

@@ -223,6 +223,16 @@ func (c Client) copyObjectDo(ctx context.Context, srcBucket, srcObject, destBuck
if dstOpts.Internal.ReplicationRequest {
headers.Set(minIOBucketReplicationRequest, "")
}
if !dstOpts.Internal.LegalholdTimestamp.IsZero() {
headers.Set(minIOBucketReplicationObjectLegalHoldTimestamp, dstOpts.Internal.LegalholdTimestamp.Format(time.RFC3339Nano))
}
if !dstOpts.Internal.RetentionTimestamp.IsZero() {
headers.Set(minIOBucketReplicationObjectRetentionTimestamp, dstOpts.Internal.RetentionTimestamp.Format(time.RFC3339Nano))
}
if !dstOpts.Internal.TaggingTimestamp.IsZero() {
headers.Set(minIOBucketReplicationTaggingTimestamp, dstOpts.Internal.TaggingTimestamp.Format(time.RFC3339Nano))
}
if len(dstOpts.UserTags) != 0 {
headers.Set(amzTaggingHeader, s3utils.TagEncode(dstOpts.UserTags))
}
@@ -513,7 +523,7 @@ func (c Client) ComposeObject(ctx context.Context, dst CopyDestOptions, srcs ...
// 4. Make final complete-multipart request.
uploadInfo, err := c.completeMultipartUpload(ctx, dst.Bucket, dst.Object, uploadID,
completeMultipartUpload{Parts: objParts})
completeMultipartUpload{Parts: objParts}, PutObjectOptions{})
if err != nil {
return UploadInfo{}, err
}

View File

@@ -64,8 +64,9 @@ func (m *StringMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
// Owner name.
type Owner struct {
DisplayName string `json:"name"`
ID string `json:"id"`
XMLName xml.Name `xml:"Owner" json:"owner"`
DisplayName string `xml:"ID" json:"name"`
ID string `xml:"DisplayName" json:"id"`
}
// UploadInfo contains information about the
@@ -85,6 +86,14 @@ type UploadInfo struct {
ExpirationRuleID string
}
// RestoreInfo contains information of the restore operation of an archived object
type RestoreInfo struct {
// Is the restoring operation is still ongoing
OngoingRestore bool
// When the restored copy of the archived object will be removed
ExpiryTime time.Time
}
// ObjectInfo container for object metadata.
type ObjectInfo struct {
// An ETag is optionally set to md5sum of an object. In case of multipart objects,
@@ -115,14 +124,7 @@ type ObjectInfo struct {
Owner Owner
// ACL grant.
Grant []struct {
Grantee struct {
ID string `xml:"ID"`
DisplayName string `xml:"DisplayName"`
URI string `xml:"URI"`
} `xml:"Grantee"`
Permission string `xml:"Permission"`
} `xml:"Grant"`
Grant []Grant
// The class of storage used to store the object.
StorageClass string `json:"storageClass"`
@@ -144,6 +146,8 @@ type ObjectInfo struct {
Expiration time.Time
ExpirationRuleID string
Restore *RestoreInfo
// Error
Err error `json:"-"`
}

View File

@@ -19,25 +19,36 @@ package minio
import (
"context"
"encoding/xml"
"net/http"
"net/url"
)
// Grantee represents the person being granted permissions.
type Grantee struct {
XMLName xml.Name `xml:"Grantee"`
ID string `xml:"ID"`
DisplayName string `xml:"DisplayName"`
URI string `xml:"URI"`
}
// Grant holds grant information
type Grant struct {
XMLName xml.Name `xml:"Grant"`
Grantee Grantee
Permission string `xml:"Permission"`
}
// AccessControlList contains the set of grantees and the permissions assigned to each grantee.
type AccessControlList struct {
XMLName xml.Name `xml:"AccessControlList"`
Grant []Grant
Permission string `xml:"Permission"`
}
type accessControlPolicy struct {
Owner struct {
ID string `xml:"ID"`
DisplayName string `xml:"DisplayName"`
} `xml:"Owner"`
AccessControlList struct {
Grant []struct {
Grantee struct {
ID string `xml:"ID"`
DisplayName string `xml:"DisplayName"`
URI string `xml:"URI"`
} `xml:"Grantee"`
Permission string `xml:"Permission"`
} `xml:"Grant"`
} `xml:"AccessControlList"`
Owner
AccessControlList
}
// GetObjectACL get object ACLs

View File

@@ -56,14 +56,13 @@ func (c Client) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
return listAllMyBucketsResult.Buckets.Bucket, nil
}
/// Bucket Read Operations.
func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix string, recursive, metadata bool, maxKeys int) <-chan ObjectInfo {
/// Bucket List Operations.
func (c Client) listObjectsV2(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
// Allocate new list objects channel.
objectStatCh := make(chan ObjectInfo, 1)
// Default listing is delimited at "/"
delimiter := "/"
if recursive {
if opts.Recursive {
// If recursive we do not delimit.
delimiter = ""
}
@@ -81,7 +80,7 @@ func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix stri
}
// Validate incoming object prefix.
if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil {
if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
defer close(objectStatCh)
objectStatCh <- ObjectInfo{
Err: err,
@@ -96,8 +95,8 @@ func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix stri
var continuationToken string
for {
// Get list of objects a maximum of 1000 per request.
result, err := c.listObjectsV2Query(ctx, bucketName, objectPrefix, continuationToken,
fetchOwner, metadata, delimiter, maxKeys)
result, err := c.listObjectsV2Query(ctx, bucketName, opts.Prefix, continuationToken,
fetchOwner, opts.WithMetadata, delimiter, opts.StartAfter, opts.MaxKeys, opts.headers)
if err != nil {
objectStatCh <- ObjectInfo{
Err: err,
@@ -148,12 +147,13 @@ func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix stri
// You can use the request parameters as selection criteria to return a subset of the objects in a bucket.
// request parameters :-
// ---------
// ?continuation-token - Used to continue iterating over a set of objects
// ?delimiter - A delimiter is a character you use to group keys.
// ?prefix - Limits the response to keys that begin with the specified prefix.
// ?max-keys - Sets the maximum number of keys returned in the response body.
// ?continuation-token - Used to continue iterating over a set of objects
// ?metadata - Specifies if we want metadata for the objects as part of list operation.
func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix, continuationToken string, fetchOwner, metadata bool, delimiter string, maxkeys int) (ListBucketV2Result, error) {
// ?delimiter - A delimiter is a character you use to group keys.
// ?start-after - Sets a marker to start listing lexically at this key onwards.
// ?max-keys - Sets the maximum number of keys returned in the response body.
func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix, continuationToken string, fetchOwner, metadata bool, delimiter string, startAfter string, maxkeys int, headers http.Header) (ListBucketV2Result, error) {
// Validate bucket name.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return ListBucketV2Result{}, err
@@ -173,6 +173,11 @@ func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix
urlValues.Set("metadata", "true")
}
// Set this conditionally if asked
if startAfter != "" {
urlValues.Set("start-after", startAfter)
}
// Always set encoding-type in ListObjects V2
urlValues.Set("encoding-type", "url")
@@ -202,6 +207,7 @@ func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix
bucketName: bucketName,
queryValues: urlValues,
contentSHA256Hex: emptySHA256Hex,
customHeader: headers,
})
defer closeResponse(resp)
if err != nil {
@@ -246,12 +252,12 @@ func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix
return listBucketResult, nil
}
func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix string, recursive bool, maxKeys int) <-chan ObjectInfo {
func (c Client) listObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
// Allocate new list objects channel.
objectStatCh := make(chan ObjectInfo, 1)
// Default listing is delimited at "/"
delimiter := "/"
if recursive {
if opts.Recursive {
// If recursive we do not delimit.
delimiter = ""
}
@@ -264,7 +270,7 @@ func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix string
return objectStatCh
}
// Validate incoming object prefix.
if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil {
if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
defer close(objectStatCh)
objectStatCh <- ObjectInfo{
Err: err,
@@ -276,10 +282,10 @@ func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix string
go func(objectStatCh chan<- ObjectInfo) {
defer close(objectStatCh)
marker := ""
marker := opts.StartAfter
for {
// Get list of objects a maximum of 1000 per request.
result, err := c.listObjectsQuery(ctx, bucketName, objectPrefix, marker, delimiter, maxKeys)
result, err := c.listObjectsQuery(ctx, bucketName, opts.Prefix, marker, delimiter, opts.MaxKeys, opts.headers)
if err != nil {
objectStatCh <- ObjectInfo{
Err: err,
@@ -326,12 +332,12 @@ func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix string
return objectStatCh
}
func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix string, recursive bool, maxKeys int) <-chan ObjectInfo {
func (c Client) listObjectVersions(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
// Allocate new list objects channel.
resultCh := make(chan ObjectInfo, 1)
// Default listing is delimited at "/"
delimiter := "/"
if recursive {
if opts.Recursive {
// If recursive we do not delimit.
delimiter = ""
}
@@ -346,7 +352,7 @@ func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix strin
}
// Validate incoming object prefix.
if err := s3utils.CheckValidObjectNamePrefix(prefix); err != nil {
if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
defer close(resultCh)
resultCh <- ObjectInfo{
Err: err,
@@ -365,7 +371,7 @@ func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix strin
for {
// Get list of objects a maximum of 1000 per request.
result, err := c.listObjectVersionsQuery(ctx, bucketName, prefix, keyMarker, versionIDMarker, delimiter, maxKeys)
result, err := c.listObjectVersionsQuery(ctx, bucketName, opts.Prefix, keyMarker, versionIDMarker, delimiter, opts.MaxKeys, opts.headers)
if err != nil {
resultCh <- ObjectInfo{
Err: err,
@@ -376,15 +382,14 @@ func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix strin
// If contents are available loop through and send over channel.
for _, version := range result.Versions {
info := ObjectInfo{
ETag: trimEtag(version.ETag),
Key: version.Key,
LastModified: version.LastModified,
Size: version.Size,
Owner: version.Owner,
StorageClass: version.StorageClass,
IsLatest: version.IsLatest,
VersionID: version.VersionID,
ETag: trimEtag(version.ETag),
Key: version.Key,
LastModified: version.LastModified,
Size: version.Size,
Owner: version.Owner,
StorageClass: version.StorageClass,
IsLatest: version.IsLatest,
VersionID: version.VersionID,
IsDeleteMarker: version.isDeleteMarker,
}
select {
@@ -438,7 +443,7 @@ func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix strin
// ?delimiter - A delimiter is a character you use to group keys.
// ?prefix - Limits the response to keys that begin with the specified prefix.
// ?max-keys - Sets the maximum number of keys returned in the response body.
func (c Client) listObjectVersionsQuery(ctx context.Context, bucketName, prefix, keyMarker, versionIDMarker, delimiter string, maxkeys int) (ListVersionsResult, error) {
func (c Client) listObjectVersionsQuery(ctx context.Context, bucketName, prefix, keyMarker, versionIDMarker, delimiter string, maxkeys int, headers http.Header) (ListVersionsResult, error) {
// Validate bucket name.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return ListVersionsResult{}, err
@@ -483,6 +488,7 @@ func (c Client) listObjectVersionsQuery(ctx context.Context, bucketName, prefix,
bucketName: bucketName,
queryValues: urlValues,
contentSHA256Hex: emptySHA256Hex,
customHeader: headers,
})
defer closeResponse(resp)
if err != nil {
@@ -534,7 +540,7 @@ func (c Client) listObjectVersionsQuery(ctx context.Context, bucketName, prefix,
// ?delimiter - A delimiter is a character you use to group keys.
// ?prefix - Limits the response to keys that begin with the specified prefix.
// ?max-keys - Sets the maximum number of keys returned in the response body.
func (c Client) listObjectsQuery(ctx context.Context, bucketName, objectPrefix, objectMarker, delimiter string, maxkeys int) (ListBucketResult, error) {
func (c Client) listObjectsQuery(ctx context.Context, bucketName, objectPrefix, objectMarker, delimiter string, maxkeys int, headers http.Header) (ListBucketResult, error) {
// Validate bucket name.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return ListBucketResult{}, err
@@ -571,6 +577,7 @@ func (c Client) listObjectsQuery(ctx context.Context, bucketName, objectPrefix,
bucketName: bucketName,
queryValues: urlValues,
contentSHA256Hex: emptySHA256Hex,
customHeader: headers,
})
defer closeResponse(resp)
if err != nil {
@@ -626,9 +633,25 @@ type ListObjectsOptions struct {
// batch, advanced use-case not useful for most
// applications
MaxKeys int
// StartAfter start listing lexically at this
// object onwards, this value can also be set
// for Marker when `UseV1` is set to true.
StartAfter string
// Use the deprecated list objects V1 API
UseV1 bool
headers http.Header
}
// Set adds a key value pair to the options. The
// key-value pair will be part of the HTTP GET request
// headers.
func (o *ListObjectsOptions) Set(key, value string) {
if o.headers == nil {
o.headers = make(http.Header)
}
o.headers.Set(key, value)
}
// ListObjects returns objects list after evaluating the passed options.
@@ -640,22 +663,22 @@ type ListObjectsOptions struct {
//
func (c Client) ListObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
if opts.WithVersions {
return c.listObjectVersions(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
return c.listObjectVersions(ctx, bucketName, opts)
}
// Use legacy list objects v1 API
if opts.UseV1 {
return c.listObjects(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
return c.listObjects(ctx, bucketName, opts)
}
// Check whether this is snowball region, if yes ListObjectsV2 doesn't work, fallback to listObjectsV1.
if location, ok := c.bucketLocCache.Get(bucketName); ok {
if location == "snowball" {
return c.listObjects(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
return c.listObjects(ctx, bucketName, opts)
}
}
return c.listObjectsV2(ctx, bucketName, opts.Prefix, opts.Recursive, opts.WithMetadata, opts.MaxKeys)
return c.listObjectsV2(ctx, bucketName, opts)
}
// ListIncompleteUploads - List incompletely uploaded multipart objects.

View File

@@ -176,7 +176,7 @@ func (c Client) putObjectMultipartNoStream(ctx context.Context, bucketName, obje
// Sort all completed parts.
sort.Sort(completedParts(complMultipartUpload.Parts))
uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload)
uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, PutObjectOptions{})
if err != nil {
return UploadInfo{}, err
}
@@ -309,7 +309,7 @@ func (c Client) uploadPart(ctx context.Context, bucketName, objectName, uploadID
// completeMultipartUpload - Completes a multipart upload by assembling previously uploaded parts.
func (c Client) completeMultipartUpload(ctx context.Context, bucketName, objectName, uploadID string,
complete completeMultipartUpload) (UploadInfo, error) {
complete completeMultipartUpload, opts PutObjectOptions) (UploadInfo, error) {
// Input validation.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return UploadInfo{}, err
@@ -336,6 +336,7 @@ func (c Client) completeMultipartUpload(ctx context.Context, bucketName, objectN
contentBody: completeMultipartUploadBuffer,
contentLength: int64(len(completeMultipartUploadBytes)),
contentSHA256Hex: sum256Hex(completeMultipartUploadBytes),
customHeader: opts.Header(),
}
// Execute POST to complete multipart upload for an objectName.

View File

@@ -231,7 +231,7 @@ func (c Client) putObjectMultipartStreamFromReadAt(ctx context.Context, bucketNa
// Sort all completed parts.
sort.Sort(completedParts(complMultipartUpload.Parts))
uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload)
uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, PutObjectOptions{})
if err != nil {
return UploadInfo{}, err
}
@@ -358,7 +358,7 @@ func (c Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, bu
// Sort all completed parts.
sort.Sort(completedParts(complMultipartUpload.Parts))
uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload)
uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, PutObjectOptions{})
if err != nil {
return UploadInfo{}, err
}

View File

@@ -60,6 +60,9 @@ type AdvancedPutOptions struct {
ReplicationStatus ReplicationStatus
SourceMTime time.Time
ReplicationRequest bool
RetentionTimestamp time.Time
TaggingTimestamp time.Time
LegalholdTimestamp time.Time
}
// PutObjectOptions represents options specified by user for PutObject call
@@ -156,6 +159,16 @@ func (opts PutObjectOptions) Header() (header http.Header) {
if opts.Internal.ReplicationRequest {
header.Set(minIOBucketReplicationRequest, "")
}
if !opts.Internal.LegalholdTimestamp.IsZero() {
header.Set(minIOBucketReplicationObjectLegalHoldTimestamp, opts.Internal.LegalholdTimestamp.Format(time.RFC3339Nano))
}
if !opts.Internal.RetentionTimestamp.IsZero() {
header.Set(minIOBucketReplicationObjectRetentionTimestamp, opts.Internal.RetentionTimestamp.Format(time.RFC3339Nano))
}
if !opts.Internal.TaggingTimestamp.IsZero() {
header.Set(minIOBucketReplicationTaggingTimestamp, opts.Internal.TaggingTimestamp.Format(time.RFC3339Nano))
}
if len(opts.UserTags) != 0 {
header.Set(amzTaggingHeader, s3utils.TagEncode(opts.UserTags))
}
@@ -360,7 +373,7 @@ func (c Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketName
// Sort all completed parts.
sort.Sort(completedParts(complMultipartUpload.Parts))
uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload)
uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, PutObjectOptions{})
if err != nil {
return UploadInfo{}, err
}

View File

@@ -29,6 +29,50 @@ import (
"github.com/minio/minio-go/v7/pkg/s3utils"
)
// BucketOptions special headers to purge buckets, only
// useful when endpoint is MinIO
type BucketOptions struct {
ForceDelete bool
}
// RemoveBucketWithOptions deletes the bucket name.
//
// All objects (including all object versions and delete markers)
// in the bucket will be deleted forcibly if bucket options set
// ForceDelete to 'true'.
func (c Client) RemoveBucketWithOptions(ctx context.Context, bucketName string, opts BucketOptions) error {
// Input validation.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return err
}
// Build headers.
headers := make(http.Header)
if opts.ForceDelete {
headers.Set(minIOForceDelete, "true")
}
// Execute DELETE on bucket.
resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
bucketName: bucketName,
contentSHA256Hex: emptySHA256Hex,
customHeader: headers,
})
defer closeResponse(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusNoContent {
return httpRespToErrorResponse(resp, bucketName, "")
}
}
// Remove the location from cache on a successful delete.
c.bucketLocCache.Delete(bucketName)
return nil
}
// RemoveBucket deletes the bucket name.
//
// All objects (including all object versions and delete markers).
@@ -69,6 +113,7 @@ type AdvancedRemoveOptions struct {
// RemoveObjectOptions represents options specified by user for RemoveObject call
type RemoveObjectOptions struct {
ForceDelete bool
GovernanceBypass bool
VersionID string
Internal AdvancedRemoveOptions
@@ -116,6 +161,9 @@ func (c Client) removeObject(ctx context.Context, bucketName, objectName string,
if opts.Internal.ReplicationRequest {
headers.Set(minIOBucketReplicationRequest, "")
}
if opts.ForceDelete {
headers.Set(minIOForceDelete, "true")
}
// Execute DELETE on objectName.
resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
bucketName: bucketName,

182
vendor/github.com/minio/minio-go/v7/api-restore.go generated vendored Normal file
View File

@@ -0,0 +1,182 @@
/*
* MinIO Go Library for Amazon S3 Compatible Cloud Storage
* (C) 2018-2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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.
*/
package minio
import (
"bytes"
"context"
"encoding/xml"
"net/http"
"net/url"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/tags"
)
// RestoreType represents the restore request type
type RestoreType string
const (
// RestoreSelect represents the restore SELECT operation
RestoreSelect = RestoreType("SELECT")
)
// TierType represents a retrieval tier
type TierType string
const (
// TierStandard is the standard retrieval tier
TierStandard = TierType("Standard")
// TierBulk is the bulk retrieval tier
TierBulk = TierType("Bulk")
// TierExpedited is the expedited retrieval tier
TierExpedited = TierType("Expedited")
)
// GlacierJobParameters represents the retrieval tier parameter
type GlacierJobParameters struct {
Tier TierType
}
// Encryption contains the type of server-side encryption used during object retrieval
type Encryption struct {
EncryptionType string
KMSContext string
KMSKeyID string `xml:"KMSKeyId"`
}
// MetadataEntry represents a metadata information of the restored object.
type MetadataEntry struct {
Name string
Value string
}
// S3 holds properties of the copy of the archived object
type S3 struct {
AccessControlList *AccessControlList `xml:"AccessControlList,omiempty"`
BucketName string
Prefix string
CannedACL *string `xml:"CannedACL,omitempty"`
Encryption *Encryption `xml:"Encryption,omitempty"`
StorageClass *string `xml:"StorageClass,omitempty"`
Tagging *tags.Tags `xml:"Tagging,omitempty"`
UserMetadata *MetadataEntry `xml:"UserMetadata,omitempty"`
}
// SelectParameters holds the select request parameters
type SelectParameters struct {
XMLName xml.Name `xml:"SelectParameters"`
ExpressionType QueryExpressionType
Expression string
InputSerialization SelectObjectInputSerialization
OutputSerialization SelectObjectOutputSerialization
}
// OutputLocation holds properties of the copy of the archived object
type OutputLocation struct {
XMLName xml.Name `xml:"OutputLocation"`
S3 S3 `xml:"S3"`
}
// RestoreRequest holds properties of the restore object request
type RestoreRequest struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ RestoreRequest"`
Type *RestoreType `xml:"Type,omitempty"`
Tier *TierType `xml:"Tier,omitempty"`
Days *int `xml:"Days,omitempty"`
GlacierJobParameters *GlacierJobParameters `xml:"GlacierJobParameters,omitempty"`
Description *string `xml:"Description,omitempty"`
SelectParameters *SelectParameters `xml:"SelectParameters,omitempty"`
OutputLocation *OutputLocation `xml:"OutputLocation,omitempty"`
}
// SetDays sets the days parameter of the restore request
func (r *RestoreRequest) SetDays(v int) {
r.Days = &v
}
// SetDays sets the GlacierJobParameters of the restore request
func (r *RestoreRequest) SetGlacierJobParameters(v GlacierJobParameters) {
r.GlacierJobParameters = &v
}
// SetType sets the type of the restore request
func (r *RestoreRequest) SetType(v RestoreType) {
r.Type = &v
}
// SetTier sets the retrieval tier of the restore request
func (r *RestoreRequest) SetTier(v TierType) {
r.Tier = &v
}
// SetDescription sets the description of the restore request
func (r *RestoreRequest) SetDescription(v string) {
r.Description = &v
}
// SetSelectParameters sets SelectParameters of the restore select request
func (r *RestoreRequest) SetSelectParameters(v SelectParameters) {
r.SelectParameters = &v
}
// SetOutputLocation sets the properties of the copy of the archived object
func (r *RestoreRequest) SetOutputLocation(v OutputLocation) {
r.OutputLocation = &v
}
// RestoreObject is a implementation of https://docs.aws.amazon.com/AmazonS3/latest/API/API_RestoreObject.html AWS S3 API
func (c Client) RestoreObject(ctx context.Context, bucketName, objectName, versionID string, req RestoreRequest) error {
// Input validation.
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return err
}
if err := s3utils.CheckValidObjectName(objectName); err != nil {
return err
}
restoreRequestBytes, err := xml.Marshal(req)
if err != nil {
return err
}
urlValues := make(url.Values)
urlValues.Set("restore", "")
if versionID != "" {
urlValues.Set("versionId", versionID)
}
// Execute POST on bucket/object.
resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
bucketName: bucketName,
objectName: objectName,
queryValues: urlValues,
contentMD5Base64: sumMD5Base64(restoreRequestBytes),
contentSHA256Hex: sum256Hex(restoreRequestBytes),
contentBody: bytes.NewReader(restoreRequestBytes),
contentLength: int64(len(restoreRequestBytes)),
})
defer closeResponse(resp)
if err != nil {
return err
}
if resp.StatusCode != http.StatusAccepted {
return httpRespToErrorResponse(resp, bucketName, "")
}
return nil
}

View File

@@ -54,6 +54,13 @@ const (
SelectCompressionNONE SelectCompressionType = "NONE"
SelectCompressionGZIP = "GZIP"
SelectCompressionBZIP = "BZIP2"
// Non-standard compression schemes, supported by MinIO hosts:
SelectCompressionZSTD = "ZSTD" // Zstandard compression.
SelectCompressionLZ4 = "LZ4" // LZ4 Stream
SelectCompressionS2 = "S2" // S2 Stream
SelectCompressionSNAPPY = "SNAPPY" // Snappy stream
)
// CSVQuoteFields - is the parameter for how CSV fields are quoted.
@@ -330,10 +337,10 @@ func (j JSONOutputOptions) MarshalXML(e *xml.Encoder, start xml.StartElement) er
// SelectObjectInputSerialization - input serialization parameters
type SelectObjectInputSerialization struct {
CompressionType SelectCompressionType
Parquet *ParquetInputOptions `xml:"Parquet,omitempty"`
CSV *CSVInputOptions `xml:"CSV,omitempty"`
JSON *JSONInputOptions `xml:"JSON,omitempty"`
CompressionType SelectCompressionType `xml:"CompressionType,omitempty"`
Parquet *ParquetInputOptions `xml:"Parquet,omitempty"`
CSV *CSVInputOptions `xml:"CSV,omitempty"`
JSON *JSONInputOptions `xml:"JSON,omitempty"`
}
// SelectObjectOutputSerialization - output serialization parameters.

Some files were not shown because too many files have changed in this diff Show More