4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-06-27 09:59:24 +00:00

Compare commits

...

32 Commits

Author SHA1 Message Date
Wim
858e16d34f Release v1.14.1 2019-03-21 21:07:11 +01:00
Wim
a60e62efb1 Update doc wrt rocketchat api issue 2019-03-21 21:05:27 +01:00
97f9d4be67 Fix double unlock (slack) (#771) 2019-03-21 17:30:28 +01:00
Wim
fa4eec41f7 Release v1.14.0 2019-03-20 23:30:03 +01:00
Wim
77516c97db Allow the # in rocketchat channels (backward compatible) (#769) 2019-03-20 23:19:27 +01:00
Wim
cba01f0865 Update rocketchat documentation 2019-03-20 23:18:40 +01:00
8b754017ca Fix race-condition in populateUser() (#767)
Fix the root-cause of #759 by introducing synchronisation points for
individual user fetches.
2019-03-20 22:54:31 +01:00
Wim
a27600046e Fix regression for legacy slack by #766 (#768) 2019-03-20 22:52:23 +01:00
fb2667631d Refactor channel and user management (slack) (#766) 2019-03-15 21:23:09 +01:00
b638f7037a Force Slack link unfurling (#763) 2019-03-12 22:56:43 +01:00
74699a8262 Split-out Slack user and channel management (#762) 2019-03-12 22:52:36 +01:00
eabf2a4582 Check module files in CI run (#761) 2019-03-12 22:47:18 +01:00
Wim
325d62b41c Update vendor d5/tengo 2019-03-05 23:10:45 +01:00
Wim
e955a056e2 Trim <p> and </p> tags (matrix). Closes #686 (#753) 2019-03-03 00:29:29 +01:00
Wim
723f8c5fd5 Only build travis on master branch 2019-03-03 00:24:49 +01:00
Wim
a16137f53f Bump version 2019-03-02 23:48:46 +01:00
Wim
d60b8b97f9 Add related projects to README 2019-03-02 23:48:30 +01:00
Wim
7b0bc51183 Release v1.14.0-rc2 2019-03-02 23:23:43 +01:00
Wim
53aa076555 Do not send duplicate messages (rocketchat). Fixes #745 (#752)
For an unknown reason we get duplicate messages (from the same channel)
using the realtime API when we have > 1 channel subscribed on.
Solution for now is caching the message ID in a LRU cache and ignoring
the duplicates.

This should be reviewed when we have actual editing support from the
realtime API
2019-03-02 22:58:14 +01:00
Wim
f57370f33a Add support for URL in messageEntities (telegram). Fixes #735 (#736) 2019-03-02 22:38:44 +01:00
Wim
c557d51b6f Need to specify /topic:mytopic for channel configuration (zulip). (#751)
Breaking change for zulip channel configuration.

For zulip the channel configuration will now need to specify also
the topic with /topic:yourtopic.

Example:
[[gateway.inout]]
account="zulip.streamchat"
channel="general/topic:mytopic"

This fixes the incorrect PR #701 which didn't work with multiple
gateways.
2019-03-02 20:31:38 +01:00
Wim
df3fdc26a0 Use whatsapp forks (#750) 2019-03-02 13:04:28 +01:00
Wim
af00c34aac Do not relay any bot messages (discord) (#743) 2019-02-28 12:59:52 +01:00
Wim
120bf39f55 Handle file upload/download only once for each message (#742) 2019-02-27 20:52:05 +01:00
Wim
26a7e35f27 Add MediaConvertWebPToPNG option (telegram). (#741)
* Add MediaConvertWebPToPNG option (telegram).

When enabled matterbridge will convert .webp files to .png files
before uploading them to the mediaserver of the other bridges.

Fixes #398
2019-02-27 00:41:50 +01:00
Wim
d44d2a5f00 Build on all branches 2019-02-26 20:47:30 +01:00
Wim
7f1d86b338 Fail gracefully on incorrect human input. Fixes #739 (#740) 2019-02-26 18:03:50 +01:00
Wim
d8816280f0 Update changelog 2019-02-26 17:44:35 +01:00
Wim
b09a73040f Print errors as string instead of %#v (#738) 2019-02-26 17:21:23 +01:00
Wim
740b5f2602 Keep reconnecting until succeed (zulip) (#737) 2019-02-26 17:08:20 +01:00
Wim
96841c70c7 Fix regression in HTML handling (telegram). Closes #734
* Revert back to blackfriday v1
* Add testing
2019-02-24 15:13:56 +01:00
Wim
f92735d35d Add goreleaser.yml 2019-02-24 01:09:53 +01:00
192 changed files with 9760 additions and 8140 deletions

View File

@ -158,7 +158,6 @@ linters-settings:
- regexpMust
- singleCaseSwitch
- sloppyLen
- sloppyReassign
- switchTrue
- typeSwitchVar
- typeUnparen

34
.goreleaser.yml Normal file
View File

@ -0,0 +1,34 @@
release:
prerelease: auto
name_template: "{{.ProjectName}} v{{.Version}}"
builds:
- env:
- CGO_ENABLED=0
goos:
- freebsd
- windows
- darwin
- linux
- dragonfly
- netbsd
- openbsd
goarch:
- amd64
- arm
- arm64
- 386
ldflags:
- -s -w -X main.githash={{.ShortCommit}}
archive:
name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
format: binary
files:
- none*
replacements:
386: 32bit
amd64: 64bit
checksum:
name_template: 'checksums.txt'

View File

@ -12,6 +12,7 @@ git:
env:
global:
- GOOS=linux GOARCH=amd64
- GO111MODULE=on
- GOLANGCI_VERSION="v1.14.0"
matrix:
@ -36,10 +37,17 @@ before_script:
- ./cc-test-reporter before-build
script:
# Ensure that the module files are being kept correctly and that vendored dependencies are up-to-date.
- go mod tidy
- go mod vendor
- git diff --exit-code --quiet || (echo "Please run 'go mod tidy' to clean up the 'go.mod' and 'go.sum' files."; false)
# Run the linter.
- golangci-lint run
# Run all the tests with the race detector and generate coverage.
- go test -v -race -coverprofile c.out ./...
# Run the build script to generate the necessary binaries and images.
- /bin/bash ci/bintray.sh
@ -47,11 +55,21 @@ after_script:
# Upload test coverage to CodeClimate.
- ./cc-test-reporter after-build --exit-code ${TRAVIS_TEST_RESULT}
branches:
only:
- master
deploy:
on:
all_branches: true
provider: bintray
on:
all_branches: true
edge:
branch: v1.8.47
file: ci/deploy.json
user: 42wim
on:
all_branches: true
key:
secure: "CeXXe6JOmt7HYR81MdWLua0ltQHhDdkIeRGBFbgd7hkb1wi8eF9DgpAcQrTso8NIlHNZmSAP46uhFgsRvkuezzX0ygalZ7DCJyAyn3sAMEh+UQSHV1WGThRehTtidqRGjetzsIGSwdrJOWil+XTfbO1Z8DGzfakhSuAZka8CM4BAoe3YeP9rYK8h+84x0GHfczvsLtXZ3mWLvQuwe4pK6+ItBCUg0ae7O7ZUpWHy0xQQkkWztY/6RAzXfaG7DuGjIw+20fhx3WOXRNpHCtZ6Bc3qERCpk0s1HhlQWlrN9wDaFTBWYwlvSnNgvxxMbNXJ6RrRJ0l0bA7FUswYwyroxhzrGLdzWDg8dHaQkypocngdalfhpsnoO9j3ApJhomUFJ3UoEq5nOGRUrKn8MPi+dP0zE4kNQ3e4VNa1ufNrvfpWolMg3xh8OXuhQdD5wIM5zFAbRJLqWSCVAjPq4DDPecmvXBOlIial7oa312lN5qnBnUjvAcxszZ+FUyDHT1Grxzna4tMwxY9obPzZUzm7359AOCCwIQFVB8GLqD2nwIstcXS0zGRz+fhviPipHuBa02q5bGUZwmkvrSNab0s8Jo7pCrel2Rz3nWPKaiCfq2WjbW1CLheSMkOQrjsdUd1hhbqNWFPUjJPInTc77NAKCfm5runv5uyowRLh4NNd0sI="

View File

@ -88,6 +88,7 @@
* [Minecraft](https://github.com/elytra/MatterLink)
* [Reddit](https://github.com/bonehurtingjuice/mattereddit)
* [Facebook messenger](https://github.com/VictorNine/fbridge)
* [Discourse](https://github.com/DeclanHoare/matterbabble)
### API
The API is very basic at the moment.
@ -99,6 +100,7 @@ Used by the projects below. Feel free to make a PR to add your project to this l
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
* [Mattereddit](https://github.com/bonehurtingjuice/mattereddit) (Reddit chat support)
* [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
* [matterbabble](https://github.com/DeclanHoare/matterbabble) (Discourse support)
## Chat with us
@ -121,7 +123,7 @@ See https://github.com/42wim/matterbridge/wiki
## Installing
### Binaries
* Latest stable release [v1.13.1](https://github.com/42wim/matterbridge/releases/latest)
* Latest stable release [v1.14.1](https://github.com/42wim/matterbridge/releases/latest)
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
### Packages
@ -237,10 +239,6 @@ See [changelog.md](https://github.com/42wim/matterbridge/blob/master/changelog.m
See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ)
Want to tip ?
* eth: 0xb3f9b5387c66ad6be892bcb7bbc67862f3abc16f
* btc: 1N7cKHj5SfqBHBzDJ6kad4BzeqUBBS2zhs
## Related projects
* [FOSSRIT/infrastructure - roles/matterbridge](https://github.com/FOSSRIT/infrastructure/tree/master/roles/matterbridge) (Ansible role used to automate deployments of Matterbridge)
* [matterbridge autoconfig](https://github.com/patcon/matterbridge-autoconfig)
@ -251,6 +249,8 @@ Want to tip ?
* [mattermost-plugin](https://github.com/matterbridge/mattermost-plugin) - Run matterbridge as a plugin in mattermost
* [pyCord](https://github.com/NikkyAI/pyCord) (crossplatform chatbot)
* [fbridge](https://github.com/VictorNine/fbridge) (Facebook messenger support)
* [isla](https://github.com/alphachung/isla) (Bot for Discord-Telegram groups used alongside matterbridge)
* [matterbabble](https://github.com/DeclanHoare/matterbabble) (Connect Discourse threads to Matterbridge)
## Articles
* [matterbridge on kubernetes](https://medium.freecodecamp.org/using-kubernetes-to-deploy-a-chat-gateway-or-when-technology-works-like-its-supposed-to-a169a8cd69a3)

View File

@ -93,6 +93,7 @@ type Protocol struct {
MediaDownloadSize int // all protocols
MediaServerDownload string
MediaServerUpload string
MediaConvertWebPToPNG bool // telegram
MessageDelay int // IRC, time in millisecond to wait between messages
MessageFormat string // telegram
MessageLength int // IRC, max length of a message allowed

View File

@ -246,7 +246,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
b.Log.Debugf("Setting webhook channel to \"%s\"", msg.Channel)
_, err := b.c.WebhookEdit(wID, "", "", channelID)
if err != nil {
b.Log.Errorf("Could not set webhook channel: %v", err)
b.Log.Errorf("Could not set webhook channel: %s", err)
return "", err
}
}
@ -278,7 +278,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
rmsg.Text = helper.ClipMessage(rmsg.Text, MessageLength)
if _, err := b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text); err != nil {
b.Log.Errorf("Could not send message %#v: %v", rmsg, err)
b.Log.Errorf("Could not send message %#v: %s", rmsg, err)
}
}
// check if we have files to upload (from slack, telegram or mattermost)
@ -360,7 +360,7 @@ func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (stri
}
_, err = b.c.ChannelMessageSendComplex(channelID, &m)
if err != nil {
return "", fmt.Errorf("file upload failed: %#v", err)
return "", fmt.Errorf("file upload failed: %s", err)
}
}
return "", nil

View File

@ -36,7 +36,7 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
return
}
// if using webhooks, do not relay if it's ours
if b.useWebhook() && m.Author.Bot && b.isWebhookID(m.Author.ID) {
if b.useWebhook() && m.Author.Bot { // && b.isWebhookID(m.Author.ID) {
return
}

View File

@ -25,7 +25,7 @@ func (b *Bdiscord) getNick(user *discordgo.User) string {
// If we didn't find nick, search for it.
member, err := b.c.GuildMember(b.guildID, user.ID)
if err != nil {
b.Log.Warnf("Failed to fetch information for member %#v: %#v", user, err)
b.Log.Warnf("Failed to fetch information for member %#v: %s", user, err)
return user.Username
} else if member == nil {
b.Log.Warnf("Got no information for member %#v", user)

View File

@ -3,6 +3,7 @@ package helper
import (
"bytes"
"fmt"
"image/png"
"io"
"net/http"
"regexp"
@ -10,6 +11,8 @@ import (
"time"
"unicode/utf8"
"golang.org/x/image/webp"
"github.com/42wim/matterbridge/bridge/config"
"github.com/sirupsen/logrus"
"gitlab.com/golang-commonmark/markdown"
@ -175,5 +178,24 @@ func ClipMessage(text string, length int) string {
func ParseMarkdown(input string) string {
md := markdown.New(markdown.XHTMLOutput(true), markdown.Breaks(true))
return (md.RenderToString([]byte(input)))
res := md.RenderToString([]byte(input))
res = strings.TrimPrefix(res, "<p>")
res = strings.TrimSuffix(res, "</p>\n")
return res
}
// ConvertWebPToPNG convert input data (which should be WebP format to PNG format)
func ConvertWebPToPNG(data *[]byte) error {
r := bytes.NewReader(*data)
m, err := webp.Decode(r)
if err != nil {
return err
}
var output []byte
w := bytes.NewBuffer(output)
if err := png.Encode(w, m); err != nil {
return err
}
*data = w.Bytes()
return nil
}

View File

@ -1,6 +1,8 @@
package helper
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
@ -103,3 +105,22 @@ func TestGetSubLines(t *testing.T) {
assert.Equalf(t, testcase.nonSplitOutput, nonSplitLines, "'%s' testcase should give expected lines without splitting.", testname)
}
}
func TestConvertWebPToPNG(t *testing.T) {
if os.Getenv("LOCAL_TEST") == "" {
t.Skip()
}
input, err := ioutil.ReadFile("test.webp")
if err != nil {
t.Fail()
}
d := &input
err = ConvertWebPToPNG(d)
if err != nil {
t.Fail()
}
err = ioutil.WriteFile("test.png", *d, 0644)
if err != nil {
t.Fail()
}
}

View File

@ -40,6 +40,11 @@ func (b *Brocketchat) handleRocketHook(messages chan *config.Message) {
func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
for message := range b.messageChan {
// skip messages with same ID, apparently messages get duplicated for an unknown reason
if _, ok := b.cache.Get(message.ID); ok {
continue
}
b.cache.Add(message.ID, true)
b.Log.Debugf("message %#v", message)
m := message
if b.skipMessage(&m) {

View File

@ -2,6 +2,7 @@ package brocketchat
import (
"errors"
"strings"
"sync"
"github.com/42wim/matterbridge/bridge"
@ -9,16 +10,18 @@ import (
"github.com/42wim/matterbridge/bridge/helper"
"github.com/42wim/matterbridge/hook/rockethook"
"github.com/42wim/matterbridge/matterhook"
lru "github.com/hashicorp/golang-lru"
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
"github.com/matterbridge/Rocket.Chat.Go.SDK/realtime"
"github.com/matterbridge/Rocket.Chat.Go.SDK/rest"
)
type Brocketchat struct {
mh *matterhook.Client
rh *rockethook.Client
c *realtime.Client
r *rest.Client
mh *matterhook.Client
rh *rockethook.Client
c *realtime.Client
r *rest.Client
cache *lru.Cache
*bridge.Config
messageChan chan models.Message
channelMap map[string]string
@ -27,9 +30,16 @@ type Brocketchat struct {
}
func New(cfg *bridge.Config) bridge.Bridger {
b := &Brocketchat{Config: cfg}
b.messageChan = make(chan models.Message)
b.channelMap = make(map[string]string)
newCache, err := lru.New(100)
if err != nil {
cfg.Log.Fatalf("Could not create LRU cache for rocketchat bridge: %v", err)
}
b := &Brocketchat{
Config: cfg,
messageChan: make(chan models.Message),
channelMap: make(map[string]string),
cache: newCache,
}
b.Log.Debugf("enabling rocketchat")
return b
}
@ -76,14 +86,14 @@ func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
if b.c == nil {
return nil
}
id, err := b.c.GetChannelId(channel.Name)
id, err := b.c.GetChannelId(strings.TrimPrefix(channel.Name, "#"))
if err != nil {
return err
}
b.Lock()
b.channelMap[id] = channel.Name
b.Unlock()
mychannel := &models.Channel{ID: id, Name: channel.Name}
mychannel := &models.Channel{ID: id, Name: strings.TrimPrefix(channel.Name, "#")}
if err := b.c.JoinChannel(id); err != nil {
return err
}
@ -94,6 +104,8 @@ func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
}
func (b *Brocketchat) Send(msg config.Message) (string, error) {
// strip the # if people has set this
msg.Channel = strings.TrimPrefix(msg.Channel, "#")
channel := &models.Channel{ID: b.getChannelID(msg.Channel), Name: msg.Channel}
// Delete message
@ -122,6 +134,8 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) {
// Upload a file if it exists
if msg.Extra != nil {
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
// strip the # if people has set this
rmsg.Channel = strings.TrimPrefix(rmsg.Channel, "#")
smsg := &models.Message{
RoomID: b.getChannelID(rmsg.Channel),
Msg: rmsg.Username + rmsg.Text,

View File

@ -34,7 +34,7 @@ func (b *Bslack) handleSlack() {
message.Text = html.UnescapeString(message.Text)
// Add the avatar
message.Avatar = b.getAvatar(message.UserID)
message.Avatar = b.users.getAvatar(message.UserID)
b.Log.Debugf("<= Message is %#v", message)
b.Remote <- *message
@ -75,20 +75,17 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
// When we join a channel we update the full list of users as
// well as the information for the channel that we joined as this
// should now tell that we are a member of it.
b.channelsMutex.Lock()
b.channelsByID[ev.Channel.ID] = &ev.Channel
b.channelsByName[ev.Channel.Name] = &ev.Channel
b.channelsMutex.Unlock()
b.channels.registerChannel(ev.Channel)
case *slack.ConnectedEvent:
b.si = ev.Info
b.populateChannels(true)
b.populateUsers(true)
b.channels.populateChannels(true)
b.users.populateUsers(true)
case *slack.InvalidAuthEvent:
b.Log.Fatalf("Invalid Token %#v", ev)
case *slack.ConnectionErrorEvent:
b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
case *slack.MemberJoinedChannelEvent:
b.populateUser(ev.User)
b.users.populateUser(ev.User)
case *slack.LatencyReport:
continue
default:
@ -210,7 +207,7 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
rmsg.Username = sSystemUser
rmsg.Event = config.EventJoinLeave
case sChannelTopic, sChannelPurpose:
b.populateChannels(false)
b.channels.populateChannels(false)
rmsg.Event = config.EventTopicChange
case sMessageChanged:
rmsg.Text = ev.SubMessage.Text
@ -266,7 +263,7 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
}
func (b *Bslack) handleTypingEvent(ev *slack.UserTypingEvent) (*config.Message, error) {
channelInfo, err := b.getChannelByID(ev.Channel)
channelInfo, err := b.channels.getChannelByID(ev.Channel)
if err != nil {
return nil, err
}
@ -316,36 +313,7 @@ func (b *Bslack) handleGetChannelMembers(rmsg *config.Message) bool {
return false
}
cMembers := config.ChannelMembers{}
b.channelMembersMutex.RLock()
for channelID, members := range b.channelMembers {
for _, member := range members {
channelName := ""
userName := ""
userNick := ""
user := b.getUser(member)
if user != nil {
userName = user.Name
userNick = user.Profile.DisplayName
}
channel, _ := b.getChannelByID(channelID)
if channel != nil {
channelName = channel.Name
}
cMember := config.ChannelMember{
Username: userName,
Nick: userNick,
UserID: member,
ChannelID: channelID,
ChannelName: channelName,
}
cMembers = append(cMembers, cMember)
}
}
b.channelMembersMutex.RUnlock()
cMembers := b.channels.getChannelMembers(b.users)
extra := make(map[string][]interface{})
extra[config.EventGetChannelMembers] = append(extra[config.EventGetChannelMembers], cMembers)

View File

@ -1,7 +1,6 @@
package bslack
import (
"context"
"fmt"
"regexp"
"strings"
@ -9,225 +8,14 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/nlopes/slack"
"github.com/sirupsen/logrus"
)
func (b *Bslack) getUser(id string) *slack.User {
b.usersMutex.RLock()
user, ok := b.users[id]
b.usersMutex.RUnlock()
if ok {
return user
}
b.populateUser(id)
b.usersMutex.RLock()
defer b.usersMutex.RUnlock()
return b.users[id]
}
func (b *Bslack) getUsername(id string) string {
if user := b.getUser(id); user != nil {
if user.Profile.DisplayName != "" {
return user.Profile.DisplayName
}
return user.Name
}
b.Log.Warnf("Could not find user with ID '%s'", id)
return ""
}
func (b *Bslack) getAvatar(id string) string {
if user := b.getUser(id); user != nil {
return user.Profile.Image48
}
return ""
}
func (b *Bslack) getChannel(channel string) (*slack.Channel, error) {
if strings.HasPrefix(channel, "ID:") {
return b.getChannelByID(strings.TrimPrefix(channel, "ID:"))
}
return b.getChannelByName(channel)
}
func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) {
return b.getChannelBy(name, b.channelsByName)
}
func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) {
return b.getChannelBy(ID, b.channelsByID)
}
func (b *Bslack) getChannelBy(lookupKey string, lookupMap map[string]*slack.Channel) (*slack.Channel, error) {
b.channelsMutex.RLock()
defer b.channelsMutex.RUnlock()
if channel, ok := lookupMap[lookupKey]; ok {
return channel, nil
}
return nil, fmt.Errorf("%s: channel %s not found", b.Account, lookupKey)
}
const minimumRefreshInterval = 10 * time.Second
func (b *Bslack) populateUser(userID string) {
b.usersMutex.RLock()
_, exists := b.users[userID]
b.usersMutex.RUnlock()
if exists {
// already in cache
return
}
user, err := b.sc.GetUserInfo(userID)
if err != nil {
b.Log.Debugf("GetUserInfo failed for %v: %v", userID, err)
return
}
b.usersMutex.Lock()
b.users[userID] = user
b.usersMutex.Unlock()
}
func (b *Bslack) populateUsers(wait bool) {
b.refreshMutex.Lock()
if !wait && (time.Now().Before(b.earliestUserRefresh) || b.refreshInProgress) {
b.Log.Debugf("Not refreshing user list as it was done less than %v ago.",
minimumRefreshInterval)
b.refreshMutex.Unlock()
return
}
for b.refreshInProgress {
b.refreshMutex.Unlock()
time.Sleep(time.Second)
b.refreshMutex.Lock()
}
b.refreshInProgress = true
b.refreshMutex.Unlock()
newUsers := map[string]*slack.User{}
pagination := b.sc.GetUsersPaginated(slack.GetUsersOptionLimit(200))
count := 0
for {
var err error
pagination, err = pagination.Next(context.Background())
time.Sleep(time.Second)
if err != nil {
if pagination.Done(err) {
break
}
if err = b.handleRateLimit(err); err != nil {
b.Log.Errorf("Could not retrieve users: %#v", err)
return
}
continue
}
for i := range pagination.Users {
newUsers[pagination.Users[i].ID] = &pagination.Users[i]
}
b.Log.Debugf("getting %d users", len(pagination.Users))
count++
// more > 2000 users, slack will complain and ratelimit. break
if count > 10 {
b.Log.Info("Large slack detected > 2000 users, skipping loading complete userlist.")
break
}
}
b.usersMutex.Lock()
defer b.usersMutex.Unlock()
b.users = newUsers
b.refreshMutex.Lock()
defer b.refreshMutex.Unlock()
b.earliestUserRefresh = time.Now().Add(minimumRefreshInterval)
b.refreshInProgress = false
}
func (b *Bslack) populateChannels(wait bool) {
b.refreshMutex.Lock()
if !wait && (time.Now().Before(b.earliestChannelRefresh) || b.refreshInProgress) {
b.Log.Debugf("Not refreshing channel list as it was done less than %v seconds ago.",
minimumRefreshInterval)
b.refreshMutex.Unlock()
return
}
for b.refreshInProgress {
b.refreshMutex.Unlock()
time.Sleep(time.Second)
b.refreshMutex.Lock()
}
b.refreshInProgress = true
b.refreshMutex.Unlock()
newChannelsByID := map[string]*slack.Channel{}
newChannelsByName := map[string]*slack.Channel{}
newChannelMembers := make(map[string][]string)
// We only retrieve public and private channels, not IMs
// and MPIMs as those do not have a channel name.
queryParams := &slack.GetConversationsParameters{
ExcludeArchived: "true",
Types: []string{"public_channel,private_channel"},
}
for {
channels, nextCursor, err := b.sc.GetConversations(queryParams)
if err != nil {
if err = b.handleRateLimit(err); err != nil {
b.Log.Errorf("Could not retrieve channels: %#v", err)
return
}
continue
}
for i := range channels {
newChannelsByID[channels[i].ID] = &channels[i]
newChannelsByName[channels[i].Name] = &channels[i]
// also find all the members in every channel
// comment for now, issues on big slacks
/*
members, err := b.getUsersInConversation(channels[i].ID)
if err != nil {
if err = b.handleRateLimit(err); err != nil {
b.Log.Errorf("Could not retrieve channel members: %#v", err)
return
}
continue
}
newChannelMembers[channels[i].ID] = members
*/
}
if nextCursor == "" {
break
}
queryParams.Cursor = nextCursor
}
b.channelsMutex.Lock()
defer b.channelsMutex.Unlock()
b.channelsByID = newChannelsByID
b.channelsByName = newChannelsByName
b.channelMembersMutex.Lock()
defer b.channelMembersMutex.Unlock()
b.channelMembers = newChannelMembers
b.refreshMutex.Lock()
defer b.refreshMutex.Unlock()
b.earliestChannelRefresh = time.Now().Add(minimumRefreshInterval)
b.refreshInProgress = false
}
// populateReceivedMessage shapes the initial Matterbridge message that we will forward to the
// router before we apply message-dependent modifications.
func (b *Bslack) populateReceivedMessage(ev *slack.MessageEvent) (*config.Message, error) {
// Use our own func because rtm.GetChannelInfo doesn't work for private channels.
channel, err := b.getChannelByID(ev.Channel)
channel, err := b.channels.getChannelByID(ev.Channel)
if err != nil {
return nil, err
}
@ -289,7 +77,7 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi
return nil
}
user := b.getUser(userID)
user := b.users.getUser(userID)
if user == nil {
return fmt.Errorf("could not find information for user with id %s", ev.User)
}
@ -315,7 +103,7 @@ func (b *Bslack) populateMessageWithBotInfo(ev *slack.MessageEvent, rmsg *config
break
}
if err = b.handleRateLimit(err); err != nil {
if err = handleRateLimit(b.Log, err); err != nil {
b.Log.Errorf("Could not retrieve bot information: %#v", err)
return err
}
@ -360,7 +148,7 @@ func (b *Bslack) extractTopicOrPurpose(text string) (string, string) {
func (b *Bslack) replaceMention(text string) string {
replaceFunc := func(match string) string {
userID := strings.Trim(match, "@<>")
if username := b.getUsername(userID); userID != "" {
if username := b.users.getUsername(userID); userID != "" {
return "@" + username
}
return match
@ -404,16 +192,6 @@ func (b *Bslack) replaceCodeFence(text string) string {
return codeFenceRE.ReplaceAllString(text, "```")
}
func (b *Bslack) handleRateLimit(err error) error {
rateLimit, ok := err.(*slack.RateLimitedError)
if !ok {
return err
}
b.Log.Infof("Rate-limited by Slack. Sleeping for %v", rateLimit.RetryAfter)
time.Sleep(rateLimit.RetryAfter)
return nil
}
// getUsersInConversation returns an array of userIDs that are members of channelID
func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) {
channelMembers := []string{}
@ -424,7 +202,7 @@ func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) {
members, nextCursor, err := b.sc.GetUsersInConversation(queryParams)
if err != nil {
if err = b.handleRateLimit(err); err != nil {
if err = handleRateLimit(b.Log, err); err != nil {
return channelMembers, fmt.Errorf("Could not retrieve users in channels: %#v", err)
}
continue
@ -439,3 +217,13 @@ func (b *Bslack) getUsersInConversation(channelID string) ([]string, error) {
}
return channelMembers, nil
}
func handleRateLimit(log *logrus.Entry, err error) error {
rateLimit, ok := err.(*slack.RateLimitedError)
if !ok {
return err
}
log.Infof("Rate-limited by Slack. Sleeping for %v", rateLimit.RetryAfter)
time.Sleep(rateLimit.RetryAfter)
return nil
}

View File

@ -55,14 +55,18 @@ func (b *BLegacy) Connect() error {
})
if b.GetString(tokenConfig) != "" {
b.Log.Info("Connecting using token (receiving)")
b.sc = slack.New(b.GetString(tokenConfig))
b.sc = slack.New(b.GetString(tokenConfig), slack.OptionDebug(b.GetBool("debug")))
b.channels = newChannelManager(b.Log, b.sc)
b.users = newUserManager(b.Log, b.sc)
b.rtm = b.sc.NewRTM()
go b.rtm.ManageConnection()
go b.handleSlack()
}
} else if b.GetString(tokenConfig) != "" {
b.Log.Info("Connecting using token (sending and receiving)")
b.sc = slack.New(b.GetString(tokenConfig))
b.sc = slack.New(b.GetString(tokenConfig), slack.OptionDebug(b.GetBool("debug")))
b.channels = newChannelManager(b.Log, b.sc)
b.users = newUserManager(b.Log, b.sc)
b.rtm = b.sc.NewRTM()
go b.rtm.ManageConnection()
go b.handleSlack()

View File

@ -30,20 +30,8 @@ type Bslack struct {
uuid string
useChannelID bool
users map[string]*slack.User
usersMutex sync.RWMutex
channelsByID map[string]*slack.Channel
channelsByName map[string]*slack.Channel
channelsMutex sync.RWMutex
channelMembers map[string][]string
channelMembersMutex sync.RWMutex
refreshInProgress bool
earliestChannelRefresh time.Time
earliestUserRefresh time.Time
refreshMutex sync.Mutex
channels *channels
users *users
}
const (
@ -94,14 +82,9 @@ func newBridge(cfg *bridge.Config) *Bslack {
cfg.Log.Fatalf("Could not create LRU cache for Slack bridge: %v", err)
}
b := &Bslack{
Config: cfg,
uuid: xid.New().String(),
cache: newCache,
users: map[string]*slack.User{},
channelsByID: map[string]*slack.Channel{},
channelsByName: map[string]*slack.Channel{},
earliestChannelRefresh: time.Now(),
earliestUserRefresh: time.Now(),
Config: cfg,
uuid: xid.New().String(),
cache: newCache,
}
return b
}
@ -121,7 +104,12 @@ func (b *Bslack) Connect() error {
// If we have a token we use the Slack websocket-based RTM for both sending and receiving.
if token := b.GetString(tokenConfig); token != "" {
b.Log.Info("Connecting using token")
b.sc = slack.New(token, slack.OptionDebug(b.GetBool("Debug")))
b.channels = newChannelManager(b.Log, b.sc)
b.users = newUserManager(b.Log, b.sc)
b.rtm = b.sc.NewRTM()
go b.rtm.ManageConnection()
go b.handleSlack()
@ -163,9 +151,9 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
return nil
}
b.populateChannels(false)
b.channels.populateChannels(false)
channelInfo, err := b.getChannel(channel.Name)
channelInfo, err := b.channels.getChannel(channel.Name)
if err != nil {
return fmt.Errorf("could not join channel: %#v", err)
}
@ -275,7 +263,7 @@ func (b *Bslack) sendRTM(msg config.Message) (string, error) {
return "", nil
}
channelInfo, err := b.getChannel(msg.Channel)
channelInfo, err := b.channels.getChannel(msg.Channel)
if err != nil {
return "", fmt.Errorf("could not send message: %v", err)
}
@ -351,7 +339,7 @@ func (b *Bslack) updateTopicOrPurpose(msg *config.Message, channelInfo *slack.Ch
if err == nil {
return nil
}
if err = b.handleRateLimit(err); err != nil {
if err = handleRateLimit(b.Log, err); err != nil {
return err
}
}
@ -392,7 +380,7 @@ func (b *Bslack) deleteMessage(msg *config.Message, channelInfo *slack.Channel)
return true, nil
}
if err = b.handleRateLimit(err); err != nil {
if err = handleRateLimit(b.Log, err); err != nil {
b.Log.Errorf("Failed to delete user message from Slack: %#v", err)
return true, err
}
@ -411,7 +399,7 @@ func (b *Bslack) editMessage(msg *config.Message, channelInfo *slack.Channel) (b
return true, nil
}
if err = b.handleRateLimit(err); err != nil {
if err = handleRateLimit(b.Log, err); err != nil {
b.Log.Errorf("Failed to edit user message on Slack: %#v", err)
return true, err
}
@ -424,14 +412,18 @@ func (b *Bslack) postMessage(msg *config.Message, channelInfo *slack.Channel) (s
return "", nil
}
messageOptions := b.prepareMessageOptions(msg)
messageOptions = append(messageOptions, slack.MsgOptionText(msg.Text, false))
messageOptions = append(
messageOptions,
slack.MsgOptionText(msg.Text, false),
slack.MsgOptionEnableLinkUnfurl(),
)
for {
_, id, err := b.rtm.PostMessage(channelInfo.ID, messageOptions...)
if err == nil {
return id, nil
}
if err = b.handleRateLimit(err); err != nil {
if err = handleRateLimit(b.Log, err); err != nil {
b.Log.Errorf("Failed to sent user message to Slack: %#v", err)
return "", err
}

View File

@ -0,0 +1,335 @@
package bslack
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/42wim/matterbridge/bridge/config"
"github.com/nlopes/slack"
"github.com/sirupsen/logrus"
)
const minimumRefreshInterval = 10 * time.Second
type users struct {
log *logrus.Entry
sc *slack.Client
users map[string]*slack.User
usersMutex sync.RWMutex
usersSyncPoints map[string]chan struct{}
refreshInProgress bool
earliestRefresh time.Time
refreshMutex sync.Mutex
}
func newUserManager(log *logrus.Entry, sc *slack.Client) *users {
return &users{
log: log,
sc: sc,
users: make(map[string]*slack.User),
usersSyncPoints: make(map[string]chan struct{}),
earliestRefresh: time.Now(),
}
}
func (b *users) getUser(id string) *slack.User {
b.usersMutex.RLock()
user, ok := b.users[id]
b.usersMutex.RUnlock()
if ok {
return user
}
b.populateUser(id)
b.usersMutex.RLock()
defer b.usersMutex.RUnlock()
return b.users[id]
}
func (b *users) getUsername(id string) string {
if user := b.getUser(id); user != nil {
if user.Profile.DisplayName != "" {
return user.Profile.DisplayName
}
return user.Name
}
b.log.Warnf("Could not find user with ID '%s'", id)
return ""
}
func (b *users) getAvatar(id string) string {
if user := b.getUser(id); user != nil {
return user.Profile.Image48
}
return ""
}
func (b *users) populateUser(userID string) {
for {
b.usersMutex.Lock()
_, exists := b.users[userID]
if exists {
// already in cache
b.usersMutex.Unlock()
return
}
if syncPoint, ok := b.usersSyncPoints[userID]; ok {
// Another goroutine is already populating this user for us so wait on it to finish.
b.usersMutex.Unlock()
<-syncPoint
// We do not return and iterate again to check that the entry does indeed exist
// in case the previous query failed for some reason.
} else {
b.usersSyncPoints[userID] = make(chan struct{})
break
}
}
// Do not hold the lock while fetching information from Slack
// as this might take an unbounded amount of time.
b.usersMutex.Unlock()
user, err := b.sc.GetUserInfo(userID)
if err != nil {
b.log.Debugf("GetUserInfo failed for %v: %v", userID, err)
return
}
b.usersMutex.Lock()
defer b.usersMutex.Unlock()
// Register user information.
b.users[userID] = user
// Wake up any waiting goroutines and remove the synchronization point.
close(b.usersSyncPoints[userID])
delete(b.usersSyncPoints, userID)
}
func (b *users) populateUsers(wait bool) {
b.refreshMutex.Lock()
if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
b.log.Debugf("Not refreshing user list as it was done less than %v ago.", minimumRefreshInterval)
b.refreshMutex.Unlock()
return
}
for b.refreshInProgress {
b.refreshMutex.Unlock()
time.Sleep(time.Second)
b.refreshMutex.Lock()
}
b.refreshInProgress = true
b.refreshMutex.Unlock()
newUsers := map[string]*slack.User{}
pagination := b.sc.GetUsersPaginated(slack.GetUsersOptionLimit(200))
count := 0
for {
var err error
pagination, err = pagination.Next(context.Background())
time.Sleep(time.Second)
if err != nil {
if pagination.Done(err) {
break
}
if err = handleRateLimit(b.log, err); err != nil {
b.log.Errorf("Could not retrieve users: %#v", err)
return
}
continue
}
for i := range pagination.Users {
newUsers[pagination.Users[i].ID] = &pagination.Users[i]
}
b.log.Debugf("getting %d users", len(pagination.Users))
count++
// more > 2000 users, slack will complain and ratelimit. break
if count > 10 {
b.log.Info("Large slack detected > 2000 users, skipping loading complete userlist.")
break
}
}
b.usersMutex.Lock()
defer b.usersMutex.Unlock()
b.users = newUsers
b.refreshMutex.Lock()
defer b.refreshMutex.Unlock()
b.earliestRefresh = time.Now().Add(minimumRefreshInterval)
b.refreshInProgress = false
}
type channels struct {
log *logrus.Entry
sc *slack.Client
channelsByID map[string]*slack.Channel
channelsByName map[string]*slack.Channel
channelsMutex sync.RWMutex
channelMembers map[string][]string
channelMembersMutex sync.RWMutex
refreshInProgress bool
earliestRefresh time.Time
refreshMutex sync.Mutex
}
func newChannelManager(log *logrus.Entry, sc *slack.Client) *channels {
return &channels{
log: log,
sc: sc,
channelsByID: make(map[string]*slack.Channel),
channelsByName: make(map[string]*slack.Channel),
earliestRefresh: time.Now(),
}
}
func (b *channels) getChannel(channel string) (*slack.Channel, error) {
if strings.HasPrefix(channel, "ID:") {
return b.getChannelByID(strings.TrimPrefix(channel, "ID:"))
}
return b.getChannelByName(channel)
}
func (b *channels) getChannelByName(name string) (*slack.Channel, error) {
return b.getChannelBy(name, b.channelsByName)
}
func (b *channels) getChannelByID(id string) (*slack.Channel, error) {
return b.getChannelBy(id, b.channelsByID)
}
func (b *channels) getChannelBy(lookupKey string, lookupMap map[string]*slack.Channel) (*slack.Channel, error) {
b.channelsMutex.RLock()
defer b.channelsMutex.RUnlock()
if channel, ok := lookupMap[lookupKey]; ok {
return channel, nil
}
return nil, fmt.Errorf("channel %s not found", lookupKey)
}
func (b *channels) getChannelMembers(users *users) config.ChannelMembers {
b.channelMembersMutex.RLock()
defer b.channelMembersMutex.RUnlock()
membersInfo := config.ChannelMembers{}
for channelID, members := range b.channelMembers {
for _, member := range members {
channelName := ""
userName := ""
userNick := ""
user := users.getUser(member)
if user != nil {
userName = user.Name
userNick = user.Profile.DisplayName
}
channel, _ := b.getChannelByID(channelID)
if channel != nil {
channelName = channel.Name
}
memberInfo := config.ChannelMember{
Username: userName,
Nick: userNick,
UserID: member,
ChannelID: channelID,
ChannelName: channelName,
}
membersInfo = append(membersInfo, memberInfo)
}
}
return membersInfo
}
func (b *channels) registerChannel(channel slack.Channel) {
b.channelsMutex.Lock()
defer b.channelsMutex.Unlock()
b.channelsByID[channel.ID] = &channel
b.channelsByName[channel.Name] = &channel
}
func (b *channels) populateChannels(wait bool) {
b.refreshMutex.Lock()
if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
b.log.Debugf("Not refreshing channel list as it was done less than %v seconds ago.", minimumRefreshInterval)
b.refreshMutex.Unlock()
return
}
for b.refreshInProgress {
b.refreshMutex.Unlock()
time.Sleep(time.Second)
b.refreshMutex.Lock()
}
b.refreshInProgress = true
b.refreshMutex.Unlock()
newChannelsByID := map[string]*slack.Channel{}
newChannelsByName := map[string]*slack.Channel{}
newChannelMembers := make(map[string][]string)
// We only retrieve public and private channels, not IMs
// and MPIMs as those do not have a channel name.
queryParams := &slack.GetConversationsParameters{
ExcludeArchived: "true",
Types: []string{"public_channel,private_channel"},
}
for {
channels, nextCursor, err := b.sc.GetConversations(queryParams)
if err != nil {
if err = handleRateLimit(b.log, err); err != nil {
b.log.Errorf("Could not retrieve channels: %#v", err)
return
}
continue
}
for i := range channels {
newChannelsByID[channels[i].ID] = &channels[i]
newChannelsByName[channels[i].Name] = &channels[i]
// also find all the members in every channel
// comment for now, issues on big slacks
/*
members, err := b.getUsersInConversation(channels[i].ID)
if err != nil {
if err = b.handleRateLimit(err); err != nil {
b.Log.Errorf("Could not retrieve channel members: %#v", err)
return
}
continue
}
newChannelMembers[channels[i].ID] = members
*/
}
if nextCursor == "" {
break
}
queryParams.Cursor = nextCursor
}
b.channelsMutex.Lock()
defer b.channelsMutex.Unlock()
b.channelsByID = newChannelsByID
b.channelsByName = newChannelsByName
b.channelMembersMutex.Lock()
defer b.channelMembersMutex.Unlock()
b.channelMembers = newChannelMembers
b.refreshMutex.Lock()
defer b.refreshMutex.Unlock()
b.earliestRefresh = time.Now().Add(minimumRefreshInterval)
b.refreshInProgress = false
}

View File

@ -144,6 +144,9 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
// quote the previous message
b.handleQuoting(&rmsg, message)
// handle entities (adding URLs)
b.handleEntities(&rmsg, message)
if rmsg.Text != "" || len(rmsg.Extra) > 0 {
rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
// channels don't have (always?) user information. see #410
@ -245,6 +248,15 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa
if err != nil {
return err
}
if strings.HasSuffix(name, ".webp") && b.GetBool("MediaConvertWebPToPNG") {
b.Log.Debugf("WebP to PNG conversion enabled, converting %s", name)
err := helper.ConvertWebPToPNG(data)
if err != nil {
b.Log.Errorf("conversion failed: %s", err)
} else {
name = strings.Replace(name, ".webp", ".png", 1)
}
}
helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
return nil
}
@ -344,3 +356,22 @@ func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string
format = strings.Replace(format, "{QUOTEMESSAGE}", quoteMessage, -1)
return format
}
// handleEntities handles messageEntities
func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Message) {
if message.Entities == nil {
return
}
// for now only do URL replacements
for _, e := range *message.Entities {
if e.Type == "text_link" {
url, err := e.ParseURL()
if err != nil {
b.Log.Errorf("entity text_link url parse failed: %s", err)
continue
}
link := rmsg.Text[e.Offset : e.Offset+e.Length]
rmsg.Text = strings.Replace(rmsg.Text, link, url.String(), 1)
}
}
}

View File

@ -3,7 +3,6 @@ package btelegram
import (
"bytes"
"html"
"io"
"github.com/russross/blackfriday"
)
@ -33,7 +32,7 @@ func (options *customHTML) Header(out *bytes.Buffer, text func() bool, level int
options.Paragraph(out, text)
}
func (options *customHTML) HRule(out io.ByteWriter) {
func (options *customHTML) HRule(out *bytes.Buffer) {
out.WriteByte('\n') //nolint:errcheck
}
@ -54,16 +53,13 @@ func (options *customHTML) ListItem(out *bytes.Buffer, text []byte, flags int) {
}
func makeHTML(input string) string {
extensions := blackfriday.NoIntraEmphasis |
blackfriday.FencedCode |
blackfriday.Autolink |
blackfriday.SpaceHeadings |
blackfriday.HeadingIDs |
blackfriday.BackslashLineBreak |
blackfriday.DefinitionLists
renderer := &customHTML{blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
Flags: blackfriday.UseXHTML | blackfriday.SkipImages,
})}
return string(blackfriday.Run([]byte(input), blackfriday.WithExtensions(extensions), blackfriday.WithRenderer(renderer)))
return string(blackfriday.Markdown([]byte(input),
&customHTML{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")},
blackfriday.EXTENSION_NO_INTRA_EMPHASIS|
blackfriday.EXTENSION_FENCED_CODE|
blackfriday.EXTENSION_AUTOLINK|
blackfriday.EXTENSION_SPACE_HEADERS|
blackfriday.EXTENSION_HEADER_IDS|
blackfriday.EXTENSION_BACKSLASH_LINE_BREAK|
blackfriday.EXTENSION_DEFINITION_LISTS))
}

View File

@ -6,9 +6,9 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
whatsappExt "maunium.net/go/mautrix-whatsapp/whatsapp-ext"
whatsappExt "github.com/matterbridge/mautrix-whatsapp/whatsapp-ext"
)
/*

View File

@ -6,7 +6,7 @@ import (
"os"
qrcodeTerminal "github.com/Baozisoftware/qrcode-terminal-go"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
func qrFromTerminal(invert bool) chan string {

View File

@ -12,9 +12,9 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
whatsappExt "maunium.net/go/mautrix-whatsapp/whatsapp-ext"
whatsappExt "github.com/matterbridge/mautrix-whatsapp/whatsapp-ext"
)
const (

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"io/ioutil"
"strconv"
"strings"
"sync"
"time"
@ -18,12 +19,11 @@ type Bzulip struct {
bot *gzb.Bot
streams map[int]string
*bridge.Config
channelToTopic map[string]string
sync.RWMutex
}
func New(cfg *bridge.Config) bridge.Bridger {
return &Bzulip{Config: cfg, streams: make(map[int]string), channelToTopic: make(map[string]string)}
return &Bzulip{Config: cfg, streams: make(map[int]string)}
}
func (b *Bzulip) Connect() error {
@ -48,9 +48,6 @@ func (b *Bzulip) Disconnect() error {
}
func (b *Bzulip) JoinChannel(channel config.ChannelInfo) error {
b.Lock()
defer b.Unlock()
b.channelToTopic[channel.Name] = channel.Options.Topic
return nil
}
@ -116,11 +113,13 @@ func (b *Bzulip) handleQueue() error {
case gzb.BadEventQueueError:
b.Log.Info("got a bad event queue id error, reconnecting")
b.bot.Queues = nil
b.q, err = b.bot.RegisterAll()
if err != nil {
b.Log.Errorf("reconnecting failed: %s. Sleeping 10 seconds", err)
time.Sleep(time.Second * 10)
continue
for {
b.q, err = b.bot.RegisterAll()
if err != nil {
b.Log.Errorf("reconnecting failed: %s. Sleeping 10 seconds", err)
time.Sleep(time.Second * 10)
}
break
}
case gzb.HeartbeatError:
b.Log.Debug("heartbeat received.")
@ -136,7 +135,14 @@ func (b *Bzulip) handleQueue() error {
if m.SenderEmail == b.GetString("login") {
continue
}
rmsg := config.Message{Username: m.SenderFullName, Text: m.Content, Channel: b.getChannel(m.StreamID), Account: b.Account, UserID: strconv.Itoa(m.SenderID), Avatar: m.AvatarURL}
rmsg := config.Message{
Username: m.SenderFullName,
Text: m.Content,
Channel: b.getChannel(m.StreamID) + "/topic:" + m.Subject,
Account: b.Account,
UserID: strconv.Itoa(m.SenderID),
Avatar: m.AvatarURL,
}
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
@ -147,12 +153,11 @@ func (b *Bzulip) handleQueue() error {
}
func (b *Bzulip) sendMessage(msg config.Message) (string, error) {
topic := "matterbridge"
if b.GetString("topic") != "" {
topic = b.GetString("topic")
}
if res := b.getTopic(msg.Channel); res != "" {
topic = res
topic := ""
if strings.Contains(msg.Channel, "/topic:") {
res := strings.Split(msg.Channel, "/topic:")
topic = res[1]
msg.Channel = res[0]
}
m := gzb.Message{
Stream: msg.Channel,
@ -200,9 +205,3 @@ func (b *Bzulip) handleUploadFile(msg *config.Message) (string, error) {
}
return "", nil
}
func (b *Bzulip) getTopic(channel string) string {
b.RLock()
defer b.RUnlock()
return b.channelToTopic[channel]
}

View File

@ -1,4 +1,11 @@
# v1.14.0-rc1
# v1.14.1
## Bugfix
* slack: Fix crash double unlock (slack) (#771)
# v1.14.0
## Breaking
* zulip: Need to specify /topic:mytopic for channel configuration (zulip). (#751)
## New features
* whatsapp: new protocol added. Add initial WhatsApp support (#711) Thanks to @KrzysztofMadejski
@ -10,18 +17,29 @@
* rocketchat: add support for the rocketchat API. Sending to rocketchat now supports uploading of files, editing and deleting of messages.
* discord: Support join/leaves from discord. Closes #654 (#721)
* discord: Allow sending discriminator with Discord username (#726). See `UseDiscriminator` in matterbridge.toml.sample
* zulip: Allow zulip bridge to specify topic per channel. Closes #701 (#723). See `Topic` in matterbridge.toml.sample
* slack: Add extra debug option (slack). See `Debug` in the slack section in matterbridge.toml.sample
* telegram: Add support for URL in messageEntities (telegram). Fixes #735 (#736)
* telegram: Add MediaConvertWebPToPNG option (telegram). (#741). See `MediaConvertWebPToPNG` in matterbridge.toml.sample
## Enhancements
* general: Fail gracefully on incorrect human input. Fixes #739 (#740)
* matrix: Detect html nicks in RemoteNickFormat (matrix). Fixes #696 (#719)
* matrix: Send notices on join/parts (matrix). Fixes #712 (#716)
## Bugfix
* general: Handle file upload/download only once for each message (#742)
* zulip: Fix error handling on bad event queue id (zulip). Closes #694
* zulip: Keep reconnecting until succeed (zulip) (#737)
* irc: add support for (older) unrealircd versions. #708
* irc: Support quits from irc correctly. Fixes #722 (#724)
* matrix: Send username when uploading video/images (matrix). Fixes #715 (#717)
* matrix: Send notices on join/parts (matrix). Fixes #712 (#716)
* matrix: Detect html nicks in RemoteNickFormat (matrix). Fixes #696 (#719)
* matrix: Trim <p> and </p> tags (matrix). Closes #686 (#753)
* slack: Hint at thread replies when messages are unthreaded (slack) (#684)
* slack: Fix race-condition in populateUser() (#767)
* xmpp: Do not send topic changes on connect (xmpp). Fixes #732 (#733)
* telegram: Fix regression in HTML handling (telegram). Closes #734
* discord: Do not relay any bot messages (discord) (#743)
* rocketchat: Do not send duplicate messages (rocketchat). Fixes #745 (#752)
## Contributors
This release couldn't exist without the following contributors:

View File

@ -92,6 +92,9 @@ func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
Bridge: br,
}
// add the actual bridger for this protocol to this bridge using the bridgeMap
if _, ok := gw.Router.BridgeMap[br.Protocol]; !ok {
gw.logger.Fatalf("Incorrect protocol %s specified in gateway configuration %s, exiting.", br.Protocol, cfg.Account)
}
br.Bridger = gw.Router.BridgeMap[br.Protocol](brconfig)
}
gw.mapChannelsToBridge(br)
@ -156,6 +159,10 @@ func (gw *Gateway) mapChannelConfig(cfg []config.Bridge, direction string) {
gw.logger.Errorf("Mattermost channels do not start with a #: remove the # in %s", br.Channel)
os.Exit(1)
}
if strings.HasPrefix(br.Account, "zulip.") && !strings.Contains(br.Channel, "/topic:") {
gw.logger.Errorf("Breaking change, since matterbridge 1.14.0 zulip channels need to specify the topic with channel/topic:mytopic in %s of %s", br.Channel, br.Account)
os.Exit(1)
}
ID := br.Channel + br.Account
if _, ok := gw.Channels[ID]; !ok {
channel := &config.ChannelInfo{

View File

@ -125,6 +125,7 @@ func (r *Router) handleReceive() {
r.handleEventGetChannelMembers(&msg)
r.handleEventFailure(&msg)
r.handleEventRejoinChannels(&msg)
idx := 0
for _, gw := range r.Gateways {
// record all the message ID's of the different bridges
var msgIDs []*BrMsgID
@ -133,7 +134,9 @@ func (r *Router) handleReceive() {
}
msg.Timestamp = time.Now()
gw.modifyMessage(&msg)
gw.handleFiles(&msg)
if idx == 0 {
gw.handleFiles(&msg)
}
for _, br := range gw.Bridges {
msgIDs = append(msgIDs, gw.handleMessage(&msg, br)...)
}
@ -141,6 +144,7 @@ func (r *Router) handleReceive() {
if _, ok := gw.Messages.Get(msg.Protocol + " " + msg.ID); !ok && msg.ID != "" {
gw.Messages.Add(msg.Protocol+" "+msg.ID, msgIDs)
}
idx++
}
}
}

9
go.mod
View File

@ -6,9 +6,8 @@ require (
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 // indirect
github.com/Jeffail/gabs v1.1.1 // indirect
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
github.com/Rhymen/go-whatsapp v0.0.0-20190208184307-c9a81e957884
github.com/bwmarrin/discordgo v0.19.0
github.com/d5/tengo v1.9.2
github.com/d5/tengo v1.12.1
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
github.com/fsnotify/fsnotify v1.4.7
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible
@ -28,10 +27,12 @@ require (
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
github.com/matterbridge/go-whatsapp v0.0.1-0.20190301204034-f2f1b29d441b
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea
github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61
github.com/matterbridge/mautrix-whatsapp v0.0.0-20190301210046-3539cf52ed6e
github.com/mattermost/mattermost-server v5.5.0+incompatible
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
@ -46,7 +47,7 @@ require (
github.com/peterhellberg/emojilib v0.0.0-20190124112554-c18758d55320
github.com/pkg/errors v0.8.0 // indirect
github.com/rs/xid v1.2.1
github.com/russross/blackfriday v2.0.0+incompatible
github.com/russross/blackfriday v1.5.2
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296
github.com/sirupsen/logrus v1.3.0
@ -66,8 +67,8 @@ require (
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
maunium.net/go/mautrix-whatsapp v0.0.0-20190127121751-281b3e8f77f3
)

45
go.sum
View File

@ -8,11 +8,6 @@ github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E=
github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 h1:xZBoq249G9MSt+XuY7sVQzcfONJ6IQuwpCK+KAaOpnY=
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg=
github.com/Rhymen/go-whatsapp v0.0.0-20181218094654-2ca6af00572c h1:ldRXgMEfKmzBomrZusl3edG9AGEeztA7jovLEQy62us=
github.com/Rhymen/go-whatsapp v0.0.0-20181218094654-2ca6af00572c/go.mod h1:MSDmePOOkbFFbVW2WRRppBcbA+aabwpXRgyIIG7jDFQ=
github.com/Rhymen/go-whatsapp v0.0.0-20190208184307-c9a81e957884 h1:2AxfzkQi2L4QGBvUCZoWD6hQuUJa5MG54wiYyNqJlf4=
github.com/Rhymen/go-whatsapp v0.0.0-20190208184307-c9a81e957884/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
github.com/alexcesaro/log v0.0.0-20150915221235-61e686294e58 h1:MkpmYfld/S8kXqTYI68DfL8/hHXjHogL120Dy00TIxc=
github.com/alexcesaro/log v0.0.0-20150915221235-61e686294e58/go.mod h1:YNfsMyWSs+h+PaYkxGeMVmVCX75Zj/pqdjbu12ciCYE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5awQiY=
@ -20,8 +15,8 @@ github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVO
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/d5/tengo v1.9.2 h1:UE/X8PYl7bLS4Ww2zGeh91nq5PTnkhe8ncgNeA5PK7k=
github.com/d5/tengo v1.9.2/go.mod h1:gsbjo7lBXzBIWBd6NQp1lRKqqiDDANqBOyhW8rTlFsY=
github.com/d5/tengo v1.12.1 h1:libKkDM95CsZgYs6E5eiEaM9sbcw2EzJRSkr9o5NO4s=
github.com/d5/tengo v1.12.1/go.mod h1:gsbjo7lBXzBIWBd6NQp1lRKqqiDDANqBOyhW8rTlFsY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -43,6 +38,7 @@ github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/x
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ=
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/schema v1.0.2 h1:sAgNfOcNYvdDSrzGHVy9nzCQahG+qmsg+nE8dK85QRA=
github.com/gorilla/schema v1.0.2/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
@ -82,6 +78,8 @@ github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDe
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d h1:F+Sr+C0ojSlYQ37BLylQtSFmyQULe3jbAygcyXQ9mVs=
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A=
github.com/matterbridge/go-whatsapp v0.0.1-0.20190301204034-f2f1b29d441b h1:cO6Z+yj4Ivq/ay/IxSrV90oSIW/SSXWLa+XHsiLKMrw=
github.com/matterbridge/go-whatsapp v0.0.1-0.20190301204034-f2f1b29d441b/go.mod h1:dW19fYkkdUZsBAx7zv9fDh0n6NRqYIaKwB2JEBw8d0U=
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 h1:KzDEcy8eDbTx881giW8a6llsAck3e2bJvMyKvh1IK+k=
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q=
github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea h1:kaADGqpK4gGO2BpzEyJrBxq2Jc57Rsar4i2EUxcACUc=
@ -90,12 +88,16 @@ github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18 h1:fLhwXtW
github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18/go.mod h1:yAjnZ34DuDyPHMPHHjOsTk/FefW4JJjoMMCGt/8uuQA=
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61 h1:R/MgM/eUyRBQx2FiH6JVmXck8PaAuKfe2M1tWIzW7nE=
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61/go.mod h1:iXGEotOvwI1R1SjLxRc+BF5rUORTMtE0iMZBT2lxqAU=
github.com/matterbridge/mautrix-whatsapp v0.0.0-20190301210046-3539cf52ed6e h1:1NqciL8sz+0UYeFrd/UQlL8tJPhFxOBmg+a94DN2sJU=
github.com/matterbridge/mautrix-whatsapp v0.0.0-20190301210046-3539cf52ed6e/go.mod h1:DrIFGcFumRlEW5k3PJjWGKPd4+w37d3SwOxlh1ZAL+4=
github.com/mattermost/mattermost-server v5.5.0+incompatible h1:0wcLGgYtd+YImtLDPf2AOfpBHxbU4suATx+6XKw1XbU=
github.com/mattermost/mattermost-server v5.5.0+incompatible/go.mod h1:5L6MjAec+XXQwMIt791Ganu45GKsSiM+I0tLR9wUj8Y=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@ -129,8 +131,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
@ -138,11 +140,9 @@ github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1 h1:Lx3BlDGFElJt4u/zK
github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI=
github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296 h1:8RLq547MSVc6vhOuCl4Ca0TsAQknj6NX6ZLSZ3+xmio=
github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296/go.mod h1:1GLXsL4esywkpNId3v4QWuMf3THtWGitWvtQ/L3aSA4=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/skip2/go-qrcode v0.0.0-20171229120447-cf5f9fa2f0d8/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 h1:lXQ+j+KwZcbwrbgU0Rp4Eglg3EJLHbuZU3BbOqAGBmg=
@ -160,9 +160,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -197,25 +195,24 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 h1:YbZJ76lQ1BqNhVe7dKTSB67wDrc2VPRR75IyGyyPDX8=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f h1:qWFY9ZxP3tfI37wYIs/MnIAqK0vlXp1xnYEa5HxFSSY=
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9 h1:+vH8qNweCrORN49012OX3h0oWEXO3p+rRnpAGQinddk=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181212120007-b05ddf57801d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222171317-cd391775e71e h1:oF7qaQxUH6KzFdKN4ww7NpPdo53SZi4UlcksLrb2y/o=
golang.org/x/sys v0.0.0-20190222171317-cd391775e71e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -233,5 +230,3 @@ maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfk
maunium.net/go/maulogger/v2 v2.0.0/go.mod h1:Hbbkq3NV6jvJodByZu1mgEF3fpT7Kz9z0MjEZ3/BusI=
maunium.net/go/mautrix v0.1.0-alpha.3/go.mod h1:GTVu6WDHR+98DKOrYetWsXorvUeKQV3jsSWO6ScbuFI=
maunium.net/go/mautrix-appservice v0.1.0-alpha.3/go.mod h1:wOnWOIuprYad7ly12rHIo3JLCPh4jwvx1prVrAB9RhM=
maunium.net/go/mautrix-whatsapp v0.0.0-20190127121751-281b3e8f77f3 h1:A18t5Lp7I3aK0V7B7zdpb0hb/PBlu0X/Ai2AyU/XEk4=
maunium.net/go/mautrix-whatsapp v0.0.0-20190127121751-281b3e8f77f3/go.mod h1:r5E3J4urDEsjfui9OYZYMLBfCliaAqcCwM2xeczta6k=

View File

@ -15,7 +15,7 @@ import (
)
var (
version = "1.14.0-rc1"
version = "1.14.1"
githash string
flagConfig = flag.String("conf", "matterbridge.toml", "config file")

View File

@ -913,6 +913,11 @@ QuoteDisable=false
#OPTIONAL (default "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})")
QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
#Convert WebP images to PNG before upload.
#https://github.com/42wim/matterbridge/issues/398
#OPTIONAL (default false)
MediaConvertWebPToPNG=false
#Disable sending of edits to other bridges
#OPTIONAL (default false)
EditDisable=false
@ -1002,9 +1007,10 @@ ShowTopicChange=false
Server="https://yourrocketchatserver.domain.com:443"
#login/pass of your bot.
#login needs to be the login with email address! user@domain.com
#Use a dedicated user for this and not your own!
#REQUIRED (when not using webhooks)
Login="yourlogin"
Login="yourlogin@domain.com"
Password="yourpass"
#### Settings for webhook matterbridge.
@ -1045,6 +1051,8 @@ SkipTLSVerify=true
#Useful if username overrides for incoming webhooks isn't enabled on the
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
#from bridge to rocketchat will by default be prefixed by the RemoteNickFormat setting. i
#if you're using login/pass you can better enable because of this bug:
#https://github.com/RocketChat/Rocket.Chat/issues/7549
#OPTIONAL (default false)
PrefixMessagesWithNick=false
@ -1358,12 +1366,6 @@ Login="yourbot-bot@yourserver.zulipchat.com"
#REQUIRED
Server="https://yourserver.zulipchat.com"
#Topic of the messages matterbridge will use
#OPTIONAL (default "matterbridge")
#You can specify a specific topic for each channel using [gateway.inout.options]
#See more information below at the gateway configuration
Topic="matterbridge"
## RELOADABLE SETTINGS
## Settings below can be reloaded by editing the file
@ -1603,7 +1605,7 @@ enable=true
# if you specify an empty string bridge will list all the possibilities
# - "Group Name" if you specify a group name the bridge will hint its JID to specify
# as group names might change in time and contain weird emoticons
# zulip - stream (without the #)
# zulip - stream/topic:topicname (without the #)
#
# REQUIRED
channel="#testing"
@ -1645,10 +1647,7 @@ enable=true
[[gateway.inout]]
account="zulip.streamchat"
channel="general"
#OPTIONAL - topic only works for zulip
[gateway.inout.options]
topic="topic1"
channel="general/topic:mytopic"
#API example
#[[gateway.inout]]

1
vendor/github.com/d5/tengo/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
dist/

23
vendor/github.com/d5/tengo/.goreleaser.yml generated vendored Normal file
View File

@ -0,0 +1,23 @@
builds:
- env:
- CGO_ENABLED=0
main: ./cmd/tengo/main.go
goos:
- darwin
- linux
- windows
- env:
- CGO_ENABLED=0
main: ./cmd/tengomin/main.go
binary: tengomin
goos:
- darwin
- linux
- windows
archive:
files:
- none*
checksum:
name_template: 'checksums.txt'
changelog:
sort: asc

17
vendor/github.com/d5/tengo/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,17 @@
language: go
go:
- 1.9
install:
- go get -u golang.org/x/lint/golint
script:
- make test
deploy:
- provider: script
skip_cleanup: true
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true

11
vendor/github.com/d5/tengo/Makefile generated vendored Normal file
View File

@ -0,0 +1,11 @@
vet:
go vet ./...
lint:
golint -set_exit_status ./...
test: vet lint
go test -race -cover ./...
fmt:
go fmt ./...

74
vendor/github.com/d5/tengo/README.md generated vendored Normal file
View File

@ -0,0 +1,74 @@
<p align="center">
<img src="https://raw.githubusercontent.com/d5/tengolang.com/master/logo_400.png" width="200" height="200">
</p>
# The Tengo Language
[![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo/script)
[![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo)
[![Build Status](https://travis-ci.org/d5/tengo.svg?branch=master)](https://travis-ci.org/d5/tengo)
[![](https://img.shields.io/badge/Support%20Tengo-%241-brightgreen.svg)](https://www.patreon.com/tengolang)
**Tengo is a small, dynamic, fast, secure script language for Go.**
Tengo is **[fast](#benchmark)** and secure because it's compiled/executed as bytecode on stack-based VM that's written in native Go.
```golang
/* The Tengo Language */
each := func(seq, fn) {
for x in seq { fn(x) }
}
sum := func(init, seq) {
each(seq, func(x) { init += x })
return init
}
n := sum(0, [1, 2, 3]) // == 6
s := sum("", [1, 2, 3]) // == "123"
```
> Run this code in the [Playground](https://tengolang.com/?s=d01cf9ed81daba939e26618530eb171f7397d9c9)
## Features
- Simple and highly readable [Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
- Dynamic typing with type coercion
- Higher-order functions and closures
- Immutable values
- Garbage collection
- [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md) and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.md)
- Compiler/runtime written in native Go _(no external deps or cgo)_
- Executable as a [standalone](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) language / REPL
## Benchmark
| | fib(35) | fibt(35) | Type |
| :--- | ---: | ---: | :---: |
| Go | `58ms` | `4ms` | Go (native) |
| [**Tengo**](https://github.com/d5/tengo) | `4,180ms` | `5ms` | VM on Go |
| Lua | `1,695ms` | `3ms` | Lua (native) |
| [go-lua](https://github.com/Shopify/go-lua) | `5,163ms` | `5ms` | Lua VM on Go |
| [GopherLua](https://github.com/yuin/gopher-lua) | `5,525ms` | `5ms` | Lua VM on Go |
| Python | `3,097ms` | `27ms` | Python (native) |
| [starlark-go](https://github.com/google/starlark-go) | `15,307ms` | `5ms` | Python-like Interpreter on Go |
| [gpython](https://github.com/go-python/gpython) | `17,656ms` | `5ms` | Python Interpreter on Go |
| [goja](https://github.com/dop251/goja) | `6,876ms` | `5ms` | JS VM on Go |
| [otto](https://github.com/robertkrimen/otto) | `81,886ms` | `12ms` | JS Interpreter on Go |
| [Anko](https://github.com/mattn/anko) | `97,517ms` | `14ms` | Interpreter on Go |
_* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo): Fibonacci(35)_
_* [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo): [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of Fibonacci(35)_
_* **Go** does not read the source code from file, while all other cases do_
_* See [here](https://github.com/d5/tengobench) for commands/codes used_
## References
- [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
- [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md)
- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) and [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md)
- [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md)
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
- [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md)
- [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md)

View File

@ -5,11 +5,11 @@ import (
"io"
"reflect"
"github.com/d5/tengo"
"github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib"
)
// Compiler compiles the AST into a bytecode.
@ -54,9 +54,6 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object
// builtin modules
if builtinModules == nil {
builtinModules = make(map[string]bool)
for name := range stdlib.Modules {
builtinModules[name] = true
}
}
return &Compiler{
@ -195,6 +192,10 @@ func (c *Compiler) Compile(node ast.Node) error {
}
case *ast.StringLit:
if len(node.Value) > tengo.MaxStringLen {
return c.error(node, objects.ErrStringLimit)
}
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value}))
case *ast.CharLit:
@ -332,6 +333,9 @@ func (c *Compiler) Compile(node ast.Node) error {
case *ast.MapLit:
for _, elt := range node.Elements {
// key
if len(elt.Key) > tengo.MaxStringLen {
return c.error(node, objects.ErrStringLimit)
}
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key}))
// value
@ -507,6 +511,10 @@ func (c *Compiler) Compile(node ast.Node) error {
case *ast.ImportExpr:
if c.builtinModules[node.ModuleName] {
if len(node.ModuleName) > tengo.MaxStringLen {
return c.error(node, objects.ErrStringLimit)
}
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName}))
c.emit(node, OpGetBuiltinModule)
} else {
@ -610,6 +618,14 @@ func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *Symbo
return child
}
func (c *Compiler) error(node ast.Node, err error) error {
return &Error{
fileSet: c.file.Set(),
node: node,
error: err,
}
}
func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error {
return &Error{
fileSet: c.file.Set(),

View File

@ -77,11 +77,8 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp
symbolTable := NewSymbolTable()
// inherit builtin functions
for idx, fn := range objects.Builtins {
s, _, ok := c.symbolTable.Resolve(fn.Name)
if ok && s.Scope == ScopeBuiltin {
symbolTable.DefineBuiltin(idx, fn.Name)
}
for _, sym := range c.symbolTable.BuiltinSymbols() {
symbolTable.DefineBuiltin(sym.Index, sym.Name)
}
// no global scope for the module

View File

@ -1,28 +0,0 @@
package parser
import (
"io"
"github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/source"
)
// ParseFile parses a file with a given src.
func ParseFile(file *source.File, src []byte, trace io.Writer) (res *ast.File, err error) {
p := NewParser(file, src, trace)
defer func() {
if e := recover(); e != nil {
if _, ok := e.(bailout); !ok {
panic(e)
}
}
p.errors.Sort()
err = p.errors.Err()
}()
res, err = p.ParseFile()
return
}

View File

@ -12,5 +12,6 @@ func ParseSource(filename string, src []byte, trace io.Writer) (res *ast.File, e
fileSet := source.NewFileSet()
file := fileSet.AddFile(filename, -1, len(src))
return ParseFile(file, src, trace)
p := NewParser(file, src, trace)
return p.ParseFile()
}

View File

@ -57,7 +57,18 @@ func NewParser(file *source.File, src []byte, trace io.Writer) *Parser {
}
// ParseFile parses the source and returns an AST file unit.
func (p *Parser) ParseFile() (*ast.File, error) {
func (p *Parser) ParseFile() (file *ast.File, err error) {
defer func() {
if e := recover(); e != nil {
if _, ok := e.(bailout); !ok {
panic(e)
}
}
p.errors.Sort()
err = p.errors.Err()
}()
if p.trace {
defer un(trace(p, "File"))
}
@ -71,10 +82,12 @@ func (p *Parser) ParseFile() (*ast.File, error) {
return nil, p.errors.Err()
}
return &ast.File{
file = &ast.File{
InputFile: p.file,
Stmts: stmts,
}, nil
}
return
}
func (p *Parser) parseExpr() ast.Expr {
@ -1002,16 +1015,26 @@ func (p *Parser) parseMapElementLit() *ast.MapElementLit {
defer un(trace(p, "MapElementLit"))
}
// key: read identifier token but it's not actually an identifier
ident := p.parseIdent()
pos := p.pos
name := "_"
if p.token == token.Ident {
name = p.tokenLit
} else if p.token == token.String {
v, _ := strconv.Unquote(p.tokenLit)
name = v
} else {
p.errorExpected(pos, "map key")
}
p.next()
colonPos := p.expect(token.Colon)
valueExpr := p.parseExpr()
return &ast.MapElementLit{
Key: ident.Name,
KeyPos: ident.NamePos,
Key: name,
KeyPos: pos,
ColonPos: colonPos,
Value: valueExpr,
}

View File

@ -2,12 +2,13 @@ package compiler
// SymbolTable represents a symbol table.
type SymbolTable struct {
parent *SymbolTable
block bool
store map[string]*Symbol
numDefinition int
maxDefinition int
freeSymbols []*Symbol
parent *SymbolTable
block bool
store map[string]*Symbol
numDefinition int
maxDefinition int
freeSymbols []*Symbol
builtinSymbols []*Symbol
}
// NewSymbolTable creates a SymbolTable.
@ -37,6 +38,10 @@ func (t *SymbolTable) Define(name string) *Symbol {
// DefineBuiltin adds a symbol for builtin function.
func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol {
if t.parent != nil {
return t.parent.DefineBuiltin(index, name)
}
symbol := &Symbol{
Name: name,
Index: index,
@ -45,6 +50,8 @@ func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol {
t.store[name] = symbol
t.builtinSymbols = append(t.builtinSymbols, symbol)
return symbol
}
@ -101,6 +108,15 @@ func (t *SymbolTable) FreeSymbols() []*Symbol {
return t.freeSymbols
}
// BuiltinSymbols returns builtin symbols for the scope.
func (t *SymbolTable) BuiltinSymbols() []*Symbol {
if t.parent != nil {
return t.parent.BuiltinSymbols()
}
return t.builtinSymbols
}
// Names returns the name of all the symbols.
func (t *SymbolTable) Names() []string {
var names []string

View File

@ -1,5 +1,7 @@
package objects
import "github.com/d5/tengo"
func builtinString(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
@ -12,6 +14,10 @@ func builtinString(args ...Object) (Object, error) {
v, ok := ToString(args[0])
if ok {
if len(v) > tengo.MaxStringLen {
return nil, ErrStringLimit
}
return &String{Value: v}, nil
}
@ -117,11 +123,19 @@ func builtinBytes(args ...Object) (Object, error) {
// bytes(N) => create a new bytes with given size N
if n, ok := args[0].(*Int); ok {
if n.Value > int64(tengo.MaxBytesLen) {
return nil, ErrBytesLimit
}
return &Bytes{Value: make([]byte, int(n.Value))}, nil
}
v, ok := ToByteSlice(args[0])
if ok {
if len(v) > tengo.MaxBytesLen {
return nil, ErrBytesLimit
}
return &Bytes{Value: v}, nil
}

View File

@ -2,6 +2,8 @@ package objects
import (
"encoding/json"
"github.com/d5/tengo"
)
// to_json(v object) => bytes
@ -15,6 +17,10 @@ func builtinToJSON(args ...Object) (Object, error) {
return &Error{Value: &String{Value: err.Error()}}, nil
}
if len(res) > tengo.MaxBytesLen {
return nil, ErrBytesLimit
}
return &Bytes{Value: res}, nil
}

View File

@ -2,6 +2,8 @@ package objects
import (
"fmt"
"github.com/d5/tengo"
)
// print(args...)
@ -71,5 +73,11 @@ func builtinSprintf(args ...Object) (Object, error) {
formatArgs[idx] = objectToInterface(arg)
}
return &String{Value: fmt.Sprintf(format.Value, formatArgs...)}, nil
s := fmt.Sprintf(format.Value, formatArgs...)
if len(s) > tengo.MaxStringLen {
return nil, ErrStringLimit
}
return &String{Value: s}, nil
}

View File

@ -1,135 +1,164 @@
package objects
// NamedBuiltinFunc is a named builtin function.
type NamedBuiltinFunc struct {
Name string
Func CallableFunc
// Builtins contains all default builtin functions.
// Use GetBuiltinFunctions instead of accessing Builtins directly.
var Builtins = []BuiltinFunction{
{
Name: "print",
Value: builtinPrint,
},
{
Name: "printf",
Value: builtinPrintf,
},
{
Name: "sprintf",
Value: builtinSprintf,
},
{
Name: "len",
Value: builtinLen,
},
{
Name: "copy",
Value: builtinCopy,
},
{
Name: "append",
Value: builtinAppend,
},
{
Name: "string",
Value: builtinString,
},
{
Name: "int",
Value: builtinInt,
},
{
Name: "bool",
Value: builtinBool,
},
{
Name: "float",
Value: builtinFloat,
},
{
Name: "char",
Value: builtinChar,
},
{
Name: "bytes",
Value: builtinBytes,
},
{
Name: "time",
Value: builtinTime,
},
{
Name: "is_int",
Value: builtinIsInt,
},
{
Name: "is_float",
Value: builtinIsFloat,
},
{
Name: "is_string",
Value: builtinIsString,
},
{
Name: "is_bool",
Value: builtinIsBool,
},
{
Name: "is_char",
Value: builtinIsChar,
},
{
Name: "is_bytes",
Value: builtinIsBytes,
},
{
Name: "is_array",
Value: builtinIsArray,
},
{
Name: "is_immutable_array",
Value: builtinIsImmutableArray,
},
{
Name: "is_map",
Value: builtinIsMap,
},
{
Name: "is_immutable_map",
Value: builtinIsImmutableMap,
},
{
Name: "is_time",
Value: builtinIsTime,
},
{
Name: "is_error",
Value: builtinIsError,
},
{
Name: "is_undefined",
Value: builtinIsUndefined,
},
{
Name: "is_function",
Value: builtinIsFunction,
},
{
Name: "is_callable",
Value: builtinIsCallable,
},
{
Name: "to_json",
Value: builtinToJSON,
},
{
Name: "from_json",
Value: builtinFromJSON,
},
{
Name: "type_name",
Value: builtinTypeName,
},
}
// Builtins contains all default builtin functions.
var Builtins = []NamedBuiltinFunc{
{
Name: "print",
Func: builtinPrint,
},
{
Name: "printf",
Func: builtinPrintf,
},
{
Name: "sprintf",
Func: builtinSprintf,
},
{
Name: "len",
Func: builtinLen,
},
{
Name: "copy",
Func: builtinCopy,
},
{
Name: "append",
Func: builtinAppend,
},
{
Name: "string",
Func: builtinString,
},
{
Name: "int",
Func: builtinInt,
},
{
Name: "bool",
Func: builtinBool,
},
{
Name: "float",
Func: builtinFloat,
},
{
Name: "char",
Func: builtinChar,
},
{
Name: "bytes",
Func: builtinBytes,
},
{
Name: "time",
Func: builtinTime,
},
{
Name: "is_int",
Func: builtinIsInt,
},
{
Name: "is_float",
Func: builtinIsFloat,
},
{
Name: "is_string",
Func: builtinIsString,
},
{
Name: "is_bool",
Func: builtinIsBool,
},
{
Name: "is_char",
Func: builtinIsChar,
},
{
Name: "is_bytes",
Func: builtinIsBytes,
},
{
Name: "is_array",
Func: builtinIsArray,
},
{
Name: "is_immutable_array",
Func: builtinIsImmutableArray,
},
{
Name: "is_map",
Func: builtinIsMap,
},
{
Name: "is_immutable_map",
Func: builtinIsImmutableMap,
},
{
Name: "is_time",
Func: builtinIsTime,
},
{
Name: "is_error",
Func: builtinIsError,
},
{
Name: "is_undefined",
Func: builtinIsUndefined,
},
{
Name: "is_function",
Func: builtinIsFunction,
},
{
Name: "is_callable",
Func: builtinIsCallable,
},
{
Name: "to_json",
Func: builtinToJSON,
},
{
Name: "from_json",
Func: builtinFromJSON,
},
{
Name: "type_name",
Func: builtinTypeName,
},
// AllBuiltinFunctionNames returns a list of all default builtin function names.
func AllBuiltinFunctionNames() []string {
var names []string
for _, bf := range Builtins {
names = append(names, bf.Name)
}
return names
}
// GetBuiltinFunctions returns a slice of builtin function objects.
// GetBuiltinFunctions removes the duplicate names, and, the returned builtin functions
// are not guaranteed to be in the same order as names.
func GetBuiltinFunctions(names ...string) []*BuiltinFunction {
include := make(map[string]bool)
for _, name := range names {
include[name] = true
}
var builtinFuncs []*BuiltinFunction
for _, bf := range Builtins {
if include[bf.Name] {
bf := bf
builtinFuncs = append(builtinFuncs, &bf)
}
}
return builtinFuncs
}
// GetAllBuiltinFunctions returns all builtin functions.
func GetAllBuiltinFunctions() []*BuiltinFunction {
return GetBuiltinFunctions(AllBuiltinFunctionNames()...)
}

View File

@ -3,6 +3,7 @@ package objects
import (
"bytes"
"github.com/d5/tengo"
"github.com/d5/tengo/compiler/token"
)
@ -27,6 +28,10 @@ func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) {
case token.Add:
switch rhs := rhs.(type) {
case *Bytes:
if len(o.Value)+len(rhs.Value) > tengo.MaxBytesLen {
return nil, ErrBytesLimit
}
return &Bytes{Value: append(o.Value, rhs.Value...)}, nil
}
}

View File

@ -1,4 +1,4 @@
package objects
// CallableFunc is a function signature for the callable functions.
type CallableFunc func(args ...Object) (ret Object, err error)
type CallableFunc = func(args ...Object) (ret Object, err error)

View File

@ -4,6 +4,8 @@ import (
"fmt"
"strconv"
"time"
"github.com/d5/tengo"
)
// ToString will try to convert object o to string value.
@ -194,6 +196,9 @@ func FromInterface(v interface{}) (Object, error) {
case nil:
return UndefinedValue, nil
case string:
if len(v) > tengo.MaxStringLen {
return nil, ErrStringLimit
}
return &String{Value: v}, nil
case int64:
return &Int{Value: v}, nil
@ -211,6 +216,9 @@ func FromInterface(v interface{}) (Object, error) {
case float64:
return &Float{Value: v}, nil
case []byte:
if len(v) > tengo.MaxBytesLen {
return nil, ErrBytesLimit
}
return &Bytes{Value: v}, nil
case error:
return &Error{Value: &String{Value: v.Error()}}, nil
@ -243,6 +251,8 @@ func FromInterface(v interface{}) (Object, error) {
return &Time{Value: v}, nil
case Object:
return v, nil
case CallableFunc:
return &UserFunction{Value: v}, nil
}
return nil, fmt.Errorf("cannot convert to object: %T", v)

View File

@ -20,6 +20,12 @@ var ErrInvalidOperator = errors.New("invalid operator")
// ErrWrongNumArguments represents a wrong number of arguments error.
var ErrWrongNumArguments = errors.New("wrong number of arguments")
// ErrBytesLimit represents an error where the size of bytes value exceeds the limit.
var ErrBytesLimit = errors.New("exceeding bytes size limit")
// ErrStringLimit represents an error where the size of string value exceeds the limit.
var ErrStringLimit = errors.New("exceeding string size limit")
// ErrInvalidArgumentType represents an invalid argument value type error.
type ErrInvalidArgumentType struct {
Name string

View File

@ -3,6 +3,7 @@ package objects
import (
"strconv"
"github.com/d5/tengo"
"github.com/d5/tengo/compiler/token"
)
@ -28,9 +29,16 @@ func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) {
case token.Add:
switch rhs := rhs.(type) {
case *String:
if len(o.Value)+len(rhs.Value) > tengo.MaxStringLen {
return nil, ErrStringLimit
}
return &String{Value: o.Value + rhs.Value}, nil
default:
return &String{Value: o.Value + rhs.String()}, nil
rhsStr := rhs.String()
if len(o.Value)+len(rhsStr) > tengo.MaxStringLen {
return nil, ErrStringLimit
}
return &String{Value: o.Value + rhsStr}, nil
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,16 +9,15 @@ import (
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/runtime"
"github.com/d5/tengo/stdlib"
)
// Script can simplify compilation and execution of embedded scripts.
type Script struct {
variables map[string]*Variable
removedBuiltins map[string]bool
removedStdModules map[string]bool
userModuleLoader compiler.ModuleLoader
input []byte
variables map[string]*Variable
builtinFuncs []objects.Object
builtinModules map[string]*objects.Object
userModuleLoader compiler.ModuleLoader
input []byte
}
// New creates a Script instance with an input script.
@ -56,22 +55,28 @@ func (s *Script) Remove(name string) bool {
return true
}
// DisableBuiltinFunction disables a builtin function.
func (s *Script) DisableBuiltinFunction(name string) {
if s.removedBuiltins == nil {
s.removedBuiltins = make(map[string]bool)
// SetBuiltinFunctions allows to define builtin functions.
func (s *Script) SetBuiltinFunctions(funcs []*objects.BuiltinFunction) {
if funcs != nil {
s.builtinFuncs = make([]objects.Object, len(funcs))
for idx, fn := range funcs {
s.builtinFuncs[idx] = fn
}
} else {
s.builtinFuncs = []objects.Object{}
}
s.removedBuiltins[name] = true
}
// DisableStdModule disables a standard library module.
func (s *Script) DisableStdModule(name string) {
if s.removedStdModules == nil {
s.removedStdModules = make(map[string]bool)
// SetBuiltinModules allows to define builtin modules.
func (s *Script) SetBuiltinModules(modules map[string]*objects.ImmutableMap) {
if modules != nil {
s.builtinModules = make(map[string]*objects.Object, len(modules))
for k, mod := range modules {
s.builtinModules[k] = objectPtr(mod)
}
} else {
s.builtinModules = map[string]*objects.Object{}
}
s.removedStdModules[name] = true
}
// SetUserModuleLoader sets the user module loader for the compiler.
@ -81,7 +86,7 @@ func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
// Compile compiles the script with all the defined variables, and, returns Compiled object.
func (s *Script) Compile() (*Compiled, error) {
symbolTable, stdModules, globals, err := s.prepCompile()
symbolTable, builtinModules, globals, err := s.prepCompile()
if err != nil {
return nil, err
}
@ -92,10 +97,10 @@ func (s *Script) Compile() (*Compiled, error) {
p := parser.NewParser(srcFile, s.input, nil)
file, err := p.ParseFile()
if err != nil {
return nil, fmt.Errorf("parse error: %s", err.Error())
return nil, err
}
c := compiler.NewCompiler(srcFile, symbolTable, nil, stdModules, nil)
c := compiler.NewCompiler(srcFile, symbolTable, nil, builtinModules, nil)
if s.userModuleLoader != nil {
c.SetModuleLoader(s.userModuleLoader)
@ -107,7 +112,7 @@ func (s *Script) Compile() (*Compiled, error) {
return &Compiled{
symbolTable: symbolTable,
machine: runtime.NewVM(c.Bytecode(), globals, nil),
machine: runtime.NewVM(c.Bytecode(), globals, s.builtinFuncs, s.builtinModules),
}, nil
}
@ -136,24 +141,36 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error)
return
}
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]bool, globals []*objects.Object, err error) {
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, builtinModules map[string]bool, globals []*objects.Object, err error) {
var names []string
for name := range s.variables {
names = append(names, name)
}
symbolTable = compiler.NewSymbolTable()
for idx, fn := range objects.Builtins {
if !s.removedBuiltins[fn.Name] {
symbolTable.DefineBuiltin(idx, fn.Name)
if s.builtinFuncs == nil {
s.builtinFuncs = make([]objects.Object, len(objects.Builtins))
for idx, fn := range objects.Builtins {
s.builtinFuncs[idx] = &objects.BuiltinFunction{
Name: fn.Name,
Value: fn.Value,
}
}
}
stdModules = make(map[string]bool)
for name := range stdlib.Modules {
if !s.removedStdModules[name] {
stdModules[name] = true
}
if s.builtinModules == nil {
s.builtinModules = make(map[string]*objects.Object)
}
for idx, fn := range s.builtinFuncs {
f := fn.(*objects.BuiltinFunction)
symbolTable.DefineBuiltin(idx, f.Name)
}
builtinModules = make(map[string]bool)
for name := range s.builtinModules {
builtinModules[name] = true
}
globals = make([]*objects.Object, runtime.GlobalsSize, runtime.GlobalsSize)
@ -178,3 +195,7 @@ func (s *Script) copyVariables() map[string]*Variable {
return vars
}
func objectPtr(o objects.Object) *objects.Object {
return &o
}

View File

@ -1,11 +0,0 @@
package stdlib
import "github.com/d5/tengo/objects"
func wrapError(err error) objects.Object {
if err == nil {
return objects.TrueValue
}
return &objects.Error{Value: &objects.String{Value: err.Error()}}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +0,0 @@
package stdlib
import (
"math"
"github.com/d5/tengo/objects"
)
var mathModule = map[string]objects.Object{
"e": &objects.Float{Value: math.E},
"pi": &objects.Float{Value: math.Pi},
"phi": &objects.Float{Value: math.Phi},
"sqrt2": &objects.Float{Value: math.Sqrt2},
"sqrtE": &objects.Float{Value: math.SqrtE},
"sqrtPi": &objects.Float{Value: math.SqrtPi},
"sqrtPhi": &objects.Float{Value: math.SqrtPhi},
"ln2": &objects.Float{Value: math.Ln2},
"log2E": &objects.Float{Value: math.Log2E},
"ln10": &objects.Float{Value: math.Ln10},
"log10E": &objects.Float{Value: math.Log10E},
"abs": &objects.UserFunction{Name: "abs", Value: FuncAFRF(math.Abs)},
"acos": &objects.UserFunction{Name: "acos", Value: FuncAFRF(math.Acos)},
"acosh": &objects.UserFunction{Name: "acosh", Value: FuncAFRF(math.Acosh)},
"asin": &objects.UserFunction{Name: "asin", Value: FuncAFRF(math.Asin)},
"asinh": &objects.UserFunction{Name: "asinh", Value: FuncAFRF(math.Asinh)},
"atan": &objects.UserFunction{Name: "atan", Value: FuncAFRF(math.Atan)},
"atan2": &objects.UserFunction{Name: "atan2", Value: FuncAFFRF(math.Atan2)},
"atanh": &objects.UserFunction{Name: "atanh", Value: FuncAFRF(math.Atanh)},
"cbrt": &objects.UserFunction{Name: "cbrt", Value: FuncAFRF(math.Cbrt)},
"ceil": &objects.UserFunction{Name: "ceil", Value: FuncAFRF(math.Ceil)},
"copysign": &objects.UserFunction{Name: "copysign", Value: FuncAFFRF(math.Copysign)},
"cos": &objects.UserFunction{Name: "cos", Value: FuncAFRF(math.Cos)},
"cosh": &objects.UserFunction{Name: "cosh", Value: FuncAFRF(math.Cosh)},
"dim": &objects.UserFunction{Name: "dim", Value: FuncAFFRF(math.Dim)},
"erf": &objects.UserFunction{Name: "erf", Value: FuncAFRF(math.Erf)},
"erfc": &objects.UserFunction{Name: "erfc", Value: FuncAFRF(math.Erfc)},
"exp": &objects.UserFunction{Name: "exp", Value: FuncAFRF(math.Exp)},
"exp2": &objects.UserFunction{Name: "exp2", Value: FuncAFRF(math.Exp2)},
"expm1": &objects.UserFunction{Name: "expm1", Value: FuncAFRF(math.Expm1)},
"floor": &objects.UserFunction{Name: "floor", Value: FuncAFRF(math.Floor)},
"gamma": &objects.UserFunction{Name: "gamma", Value: FuncAFRF(math.Gamma)},
"hypot": &objects.UserFunction{Name: "hypot", Value: FuncAFFRF(math.Hypot)},
"ilogb": &objects.UserFunction{Name: "ilogb", Value: FuncAFRI(math.Ilogb)},
"inf": &objects.UserFunction{Name: "inf", Value: FuncAIRF(math.Inf)},
"is_inf": &objects.UserFunction{Name: "is_inf", Value: FuncAFIRB(math.IsInf)},
"is_nan": &objects.UserFunction{Name: "is_nan", Value: FuncAFRB(math.IsNaN)},
"j0": &objects.UserFunction{Name: "j0", Value: FuncAFRF(math.J0)},
"j1": &objects.UserFunction{Name: "j1", Value: FuncAFRF(math.J1)},
"jn": &objects.UserFunction{Name: "jn", Value: FuncAIFRF(math.Jn)},
"ldexp": &objects.UserFunction{Name: "ldexp", Value: FuncAFIRF(math.Ldexp)},
"log": &objects.UserFunction{Name: "log", Value: FuncAFRF(math.Log)},
"log10": &objects.UserFunction{Name: "log10", Value: FuncAFRF(math.Log10)},
"log1p": &objects.UserFunction{Name: "log1p", Value: FuncAFRF(math.Log1p)},
"log2": &objects.UserFunction{Name: "log2", Value: FuncAFRF(math.Log2)},
"logb": &objects.UserFunction{Name: "logb", Value: FuncAFRF(math.Logb)},
"max": &objects.UserFunction{Name: "max", Value: FuncAFFRF(math.Max)},
"min": &objects.UserFunction{Name: "min", Value: FuncAFFRF(math.Min)},
"mod": &objects.UserFunction{Name: "mod", Value: FuncAFFRF(math.Mod)},
"nan": &objects.UserFunction{Name: "nan", Value: FuncARF(math.NaN)},
"nextafter": &objects.UserFunction{Name: "nextafter", Value: FuncAFFRF(math.Nextafter)},
"pow": &objects.UserFunction{Name: "pow", Value: FuncAFFRF(math.Pow)},
"pow10": &objects.UserFunction{Name: "pow10", Value: FuncAIRF(math.Pow10)},
"remainder": &objects.UserFunction{Name: "remainder", Value: FuncAFFRF(math.Remainder)},
"signbit": &objects.UserFunction{Name: "signbit", Value: FuncAFRB(math.Signbit)},
"sin": &objects.UserFunction{Name: "sin", Value: FuncAFRF(math.Sin)},
"sinh": &objects.UserFunction{Name: "sinh", Value: FuncAFRF(math.Sinh)},
"sqrt": &objects.UserFunction{Name: "sqrt", Value: FuncAFRF(math.Sqrt)},
"tan": &objects.UserFunction{Name: "tan", Value: FuncAFRF(math.Tan)},
"tanh": &objects.UserFunction{Name: "tanh", Value: FuncAFRF(math.Tanh)},
"trunc": &objects.UserFunction{Name: "trunc", Value: FuncAFRF(math.Trunc)},
"y0": &objects.UserFunction{Name: "y0", Value: FuncAFRF(math.Y0)},
"y1": &objects.UserFunction{Name: "y1", Value: FuncAFRF(math.Y1)},
"yn": &objects.UserFunction{Name: "yn", Value: FuncAIFRF(math.Yn)},
}

View File

@ -1,437 +0,0 @@
package stdlib
import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"github.com/d5/tengo/objects"
)
var osModule = map[string]objects.Object{
"o_rdonly": &objects.Int{Value: int64(os.O_RDONLY)},
"o_wronly": &objects.Int{Value: int64(os.O_WRONLY)},
"o_rdwr": &objects.Int{Value: int64(os.O_RDWR)},
"o_append": &objects.Int{Value: int64(os.O_APPEND)},
"o_create": &objects.Int{Value: int64(os.O_CREATE)},
"o_excl": &objects.Int{Value: int64(os.O_EXCL)},
"o_sync": &objects.Int{Value: int64(os.O_SYNC)},
"o_trunc": &objects.Int{Value: int64(os.O_TRUNC)},
"mode_dir": &objects.Int{Value: int64(os.ModeDir)},
"mode_append": &objects.Int{Value: int64(os.ModeAppend)},
"mode_exclusive": &objects.Int{Value: int64(os.ModeExclusive)},
"mode_temporary": &objects.Int{Value: int64(os.ModeTemporary)},
"mode_symlink": &objects.Int{Value: int64(os.ModeSymlink)},
"mode_device": &objects.Int{Value: int64(os.ModeDevice)},
"mode_named_pipe": &objects.Int{Value: int64(os.ModeNamedPipe)},
"mode_socket": &objects.Int{Value: int64(os.ModeSocket)},
"mode_setuid": &objects.Int{Value: int64(os.ModeSetuid)},
"mode_setgui": &objects.Int{Value: int64(os.ModeSetgid)},
"mode_char_device": &objects.Int{Value: int64(os.ModeCharDevice)},
"mode_sticky": &objects.Int{Value: int64(os.ModeSticky)},
"mode_type": &objects.Int{Value: int64(os.ModeType)},
"mode_perm": &objects.Int{Value: int64(os.ModePerm)},
"path_separator": &objects.Char{Value: os.PathSeparator},
"path_list_separator": &objects.Char{Value: os.PathListSeparator},
"dev_null": &objects.String{Value: os.DevNull},
"seek_set": &objects.Int{Value: int64(io.SeekStart)},
"seek_cur": &objects.Int{Value: int64(io.SeekCurrent)},
"seek_end": &objects.Int{Value: int64(io.SeekEnd)},
"args": &objects.UserFunction{Value: osArgs}, // args() => array(string)
"chdir": &objects.UserFunction{Name: "chdir", Value: FuncASRE(os.Chdir)}, // chdir(dir string) => error
"chmod": osFuncASFmRE(os.Chmod), // chmod(name string, mode int) => error
"chown": &objects.UserFunction{Name: "chown", Value: FuncASIIRE(os.Chown)}, // chown(name string, uid int, gid int) => error
"clearenv": &objects.UserFunction{Name: "clearenv", Value: FuncAR(os.Clearenv)}, // clearenv()
"environ": &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)}, // environ() => array(string)
"exit": &objects.UserFunction{Name: "exit", Value: FuncAIR(os.Exit)}, // exit(code int)
"expand_env": &objects.UserFunction{Name: "expand_env", Value: FuncASRS(os.ExpandEnv)}, // expand_env(s string) => string
"getegid": &objects.UserFunction{Name: "getegid", Value: FuncARI(os.Getegid)}, // getegid() => int
"getenv": &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)}, // getenv(s string) => string
"geteuid": &objects.UserFunction{Name: "geteuid", Value: FuncARI(os.Geteuid)}, // geteuid() => int
"getgid": &objects.UserFunction{Name: "getgid", Value: FuncARI(os.Getgid)}, // getgid() => int
"getgroups": &objects.UserFunction{Name: "getgroups", Value: FuncARIsE(os.Getgroups)}, // getgroups() => array(string)/error
"getpagesize": &objects.UserFunction{Name: "getpagesize", Value: FuncARI(os.Getpagesize)}, // getpagesize() => int
"getpid": &objects.UserFunction{Name: "getpid", Value: FuncARI(os.Getpid)}, // getpid() => int
"getppid": &objects.UserFunction{Name: "getppid", Value: FuncARI(os.Getppid)}, // getppid() => int
"getuid": &objects.UserFunction{Name: "getuid", Value: FuncARI(os.Getuid)}, // getuid() => int
"getwd": &objects.UserFunction{Name: "getwd", Value: FuncARSE(os.Getwd)}, // getwd() => string/error
"hostname": &objects.UserFunction{Name: "hostname", Value: FuncARSE(os.Hostname)}, // hostname() => string/error
"lchown": &objects.UserFunction{Name: "lchown", Value: FuncASIIRE(os.Lchown)}, // lchown(name string, uid int, gid int) => error
"link": &objects.UserFunction{Name: "link", Value: FuncASSRE(os.Link)}, // link(oldname string, newname string) => error
"lookup_env": &objects.UserFunction{Value: osLookupEnv}, // lookup_env(key string) => string/false
"mkdir": osFuncASFmRE(os.Mkdir), // mkdir(name string, perm int) => error
"mkdir_all": osFuncASFmRE(os.MkdirAll), // mkdir_all(name string, perm int) => error
"readlink": &objects.UserFunction{Name: "readlink", Value: FuncASRSE(os.Readlink)}, // readlink(name string) => string/error
"remove": &objects.UserFunction{Name: "remove", Value: FuncASRE(os.Remove)}, // remove(name string) => error
"remove_all": &objects.UserFunction{Name: "remove_all", Value: FuncASRE(os.RemoveAll)}, // remove_all(name string) => error
"rename": &objects.UserFunction{Name: "rename", Value: FuncASSRE(os.Rename)}, // rename(oldpath string, newpath string) => error
"setenv": &objects.UserFunction{Name: "setenv", Value: FuncASSRE(os.Setenv)}, // setenv(key string, value string) => error
"symlink": &objects.UserFunction{Name: "symlink", Value: FuncASSRE(os.Symlink)}, // symlink(oldname string newname string) => error
"temp_dir": &objects.UserFunction{Name: "temp_dir", Value: FuncARS(os.TempDir)}, // temp_dir() => string
"truncate": &objects.UserFunction{Name: "truncate", Value: FuncASI64RE(os.Truncate)}, // truncate(name string, size int) => error
"unsetenv": &objects.UserFunction{Name: "unsetenv", Value: FuncASRE(os.Unsetenv)}, // unsetenv(key string) => error
"create": &objects.UserFunction{Value: osCreate}, // create(name string) => imap(file)/error
"open": &objects.UserFunction{Value: osOpen}, // open(name string) => imap(file)/error
"open_file": &objects.UserFunction{Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error
"find_process": &objects.UserFunction{Value: osFindProcess}, // find_process(pid int) => imap(process)/error
"start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
"exec_look_path": &objects.UserFunction{Name: "exec_look_path", Value: FuncASRSE(exec.LookPath)}, // exec_look_path(file) => string/error
"exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command
"stat": &objects.UserFunction{Value: osStat}, // stat(name) => imap(fileinfo)/error
"read_file": &objects.UserFunction{Value: osReadFile}, // readfile(name) => array(byte)/error
}
func osReadFile(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
fname, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
bytes, err := ioutil.ReadFile(fname)
if err != nil {
return wrapError(err), nil
}
return &objects.Bytes{Value: bytes}, nil
}
func osStat(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
fname, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
stat, err := os.Stat(fname)
if err != nil {
return wrapError(err), nil
}
fstat := &objects.ImmutableMap{
Value: map[string]objects.Object{
"name": &objects.String{Value: stat.Name()},
"mtime": &objects.Time{Value: stat.ModTime()},
"size": &objects.Int{Value: stat.Size()},
"mode": &objects.Int{Value: int64(stat.Mode())},
},
}
if stat.IsDir() {
fstat.Value["directory"] = objects.TrueValue
} else {
fstat.Value["directory"] = objects.FalseValue
}
return fstat, nil
}
func osCreate(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
res, err := os.Create(s1)
if err != nil {
return wrapError(err), nil
}
return makeOSFile(res), nil
}
func osOpen(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
res, err := os.Open(s1)
if err != nil {
return wrapError(err), nil
}
return makeOSFile(res), nil
}
func osOpenFile(args ...objects.Object) (objects.Object, error) {
if len(args) != 3 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
i2, ok := objects.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
}
i3, ok := objects.ToInt(args[2])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
}
res, err := os.OpenFile(s1, i2, os.FileMode(i3))
if err != nil {
return wrapError(err), nil
}
return makeOSFile(res), nil
}
func osArgs(args ...objects.Object) (objects.Object, error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
arr := &objects.Array{}
for _, osArg := range os.Args {
arr.Value = append(arr.Value, &objects.String{Value: osArg})
}
return arr, nil
}
func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (objects.Object, error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
i2, ok := objects.ToInt64(args[1])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
}
return wrapError(fn(s1, os.FileMode(i2))), nil
},
}
}
func osLookupEnv(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
res, ok := os.LookupEnv(s1)
if !ok {
return objects.FalseValue, nil
}
return &objects.String{Value: res}, nil
}
func osExec(args ...objects.Object) (objects.Object, error) {
if len(args) == 0 {
return nil, objects.ErrWrongNumArguments
}
name, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
var execArgs []string
for idx, arg := range args[1:] {
execArg, ok := objects.ToString(arg)
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: fmt.Sprintf("args[%d]", idx),
Expected: "string(compatible)",
Found: args[1+idx].TypeName(),
}
}
execArgs = append(execArgs, execArg)
}
return makeOSExecCommand(exec.Command(name, execArgs...)), nil
}
func osFindProcess(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
proc, err := os.FindProcess(i1)
if err != nil {
return wrapError(err), nil
}
return makeOSProcess(proc), nil
}
func osStartProcess(args ...objects.Object) (objects.Object, error) {
if len(args) != 4 {
return nil, objects.ErrWrongNumArguments
}
name, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
var argv []string
var err error
switch arg1 := args[1].(type) {
case *objects.Array:
argv, err = stringArray(arg1.Value, "second")
if err != nil {
return nil, err
}
case *objects.ImmutableArray:
argv, err = stringArray(arg1.Value, "second")
if err != nil {
return nil, err
}
default:
return nil, objects.ErrInvalidArgumentType{
Name: "second",
Expected: "array",
Found: arg1.TypeName(),
}
}
dir, ok := objects.ToString(args[2])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "third",
Expected: "string(compatible)",
Found: args[2].TypeName(),
}
}
var env []string
switch arg3 := args[3].(type) {
case *objects.Array:
env, err = stringArray(arg3.Value, "fourth")
if err != nil {
return nil, err
}
case *objects.ImmutableArray:
env, err = stringArray(arg3.Value, "fourth")
if err != nil {
return nil, err
}
default:
return nil, objects.ErrInvalidArgumentType{
Name: "fourth",
Expected: "array",
Found: arg3.TypeName(),
}
}
proc, err := os.StartProcess(name, argv, &os.ProcAttr{
Dir: dir,
Env: env,
})
if err != nil {
return wrapError(err), nil
}
return makeOSProcess(proc), nil
}
func stringArray(arr []objects.Object, argName string) ([]string, error) {
var sarr []string
for idx, elem := range arr {
str, ok := elem.(*objects.String)
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: fmt.Sprintf("%s[%d]", argName, idx),
Expected: "string",
Found: elem.TypeName(),
}
}
sarr = append(sarr, str.Value)
}
return sarr, nil
}

View File

@ -1,109 +0,0 @@
package stdlib
import (
"os/exec"
"github.com/d5/tengo/objects"
)
func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// combined_output() => bytes/error
"combined_output": &objects.UserFunction{Name: "combined_output", Value: FuncARYE(cmd.CombinedOutput)}, //
// output() => bytes/error
"output": &objects.UserFunction{Name: "output", Value: FuncARYE(cmd.Output)}, //
// run() => error
"run": &objects.UserFunction{Name: "run", Value: FuncARE(cmd.Run)}, //
// start() => error
"start": &objects.UserFunction{Name: "start", Value: FuncARE(cmd.Start)}, //
// wait() => error
"wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, //
// set_path(path string)
"set_path": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
cmd.Path = s1
return objects.UndefinedValue, nil
},
},
// set_dir(dir string)
"set_dir": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
cmd.Dir = s1
return objects.UndefinedValue, nil
},
},
// set_env(env array(string))
"set_env": &objects.UserFunction{
Value: func(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
var env []string
var err error
switch arg0 := args[0].(type) {
case *objects.Array:
env, err = stringArray(arg0.Value, "first")
if err != nil {
return nil, err
}
case *objects.ImmutableArray:
env, err = stringArray(arg0.Value, "first")
if err != nil {
return nil, err
}
default:
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "array",
Found: arg0.TypeName(),
}
}
cmd.Env = env
return objects.UndefinedValue, nil
},
},
// process() => imap(process)
"process": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
return makeOSProcess(cmd.Process), nil
},
},
},
}
}

View File

@ -1,93 +0,0 @@
package stdlib
import (
"os"
"github.com/d5/tengo/objects"
)
func makeOSFile(file *os.File) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// chdir() => true/error
"chdir": &objects.UserFunction{Name: "chdir", Value: FuncARE(file.Chdir)}, //
// chown(uid int, gid int) => true/error
"chown": &objects.UserFunction{Name: "chown", Value: FuncAIIRE(file.Chown)}, //
// close() => error
"close": &objects.UserFunction{Name: "close", Value: FuncARE(file.Close)}, //
// name() => string
"name": &objects.UserFunction{Name: "name", Value: FuncARS(file.Name)}, //
// readdirnames(n int) => array(string)/error
"readdirnames": &objects.UserFunction{Name: "readdirnames", Value: FuncAIRSsE(file.Readdirnames)}, //
// sync() => error
"sync": &objects.UserFunction{Name: "sync", Value: FuncARE(file.Sync)}, //
// write(bytes) => int/error
"write": &objects.UserFunction{Name: "write", Value: FuncAYRIE(file.Write)}, //
// write(string) => int/error
"write_string": &objects.UserFunction{Name: "write_string", Value: FuncASRIE(file.WriteString)}, //
// read(bytes) => int/error
"read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, //
// chmod(mode int) => error
"chmod": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt64(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
return wrapError(file.Chmod(os.FileMode(i1))), nil
},
},
// seek(offset int, whence int) => int/error
"seek": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt64(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
i2, ok := objects.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
}
res, err := file.Seek(i1, i2)
if err != nil {
return wrapError(err), nil
}
return &objects.Int{Value: res}, nil
},
},
// stat() => imap(fileinfo)/error
"stat": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
return osStat(&objects.String{Value: file.Name()})
},
},
},
}
}

View File

@ -1,60 +0,0 @@
package stdlib
import (
"os"
"syscall"
"github.com/d5/tengo/objects"
)
func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
"exited": &objects.UserFunction{Name: "exited", Value: FuncARB(state.Exited)}, //
"pid": &objects.UserFunction{Name: "pid", Value: FuncARI(state.Pid)}, //
"string": &objects.UserFunction{Name: "string", Value: FuncARS(state.String)}, //
"success": &objects.UserFunction{Name: "success", Value: FuncARB(state.Success)}, //
},
}
}
func makeOSProcess(proc *os.Process) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
"kill": &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)}, //
"release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, //
"signal": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt64(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
return wrapError(proc.Signal(syscall.Signal(i1))), nil
},
},
"wait": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
state, err := proc.Wait()
if err != nil {
return wrapError(err), nil
}
return makeOSProcessState(state), nil
},
},
},
}
}

View File

@ -1,99 +0,0 @@
package stdlib
import (
"math/rand"
"github.com/d5/tengo/objects"
)
var randModule = map[string]objects.Object{
"int": &objects.UserFunction{Name: "int", Value: FuncARI64(rand.Int63)},
"float": &objects.UserFunction{Name: "float", Value: FuncARF(rand.Float64)},
"intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(rand.Int63n)},
"exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(rand.ExpFloat64)},
"norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(rand.NormFloat64)},
"perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(rand.Perm)},
"seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)},
"read": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
y1, ok := args[0].(*objects.Bytes)
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "bytes",
Found: args[0].TypeName(),
}
}
res, err := rand.Read(y1.Value)
if err != nil {
ret = wrapError(err)
return
}
return &objects.Int{Value: int64(res)}, nil
},
},
"rand": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt64(args[0])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
src := rand.NewSource(i1)
return randRand(rand.New(src)), nil
},
},
}
func randRand(r *rand.Rand) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
"int": &objects.UserFunction{Name: "int", Value: FuncARI64(r.Int63)},
"float": &objects.UserFunction{Name: "float", Value: FuncARF(r.Float64)},
"intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(r.Int63n)},
"exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(r.ExpFloat64)},
"norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(r.NormFloat64)},
"perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)},
"seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)},
"read": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
y1, ok := args[0].(*objects.Bytes)
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "bytes",
Found: args[0].TypeName(),
}
}
res, err := r.Read(y1.Value)
if err != nil {
ret = wrapError(err)
return
}
return &objects.Int{Value: int64(res)}, nil
},
},
},
}
}

View File

@ -1,16 +0,0 @@
package stdlib
import "github.com/d5/tengo/objects"
// Modules contain the standard modules.
var Modules = map[string]*objects.Object{
"math": objectPtr(&objects.ImmutableMap{Value: mathModule}),
"os": objectPtr(&objects.ImmutableMap{Value: osModule}),
"text": objectPtr(&objects.ImmutableMap{Value: textModule}),
"times": objectPtr(&objects.ImmutableMap{Value: timesModule}),
"rand": objectPtr(&objects.ImmutableMap{Value: randModule}),
}
func objectPtr(o objects.Object) *objects.Object {
return &o
}

View File

@ -1,585 +0,0 @@
package stdlib
import (
"regexp"
"strconv"
"strings"
"github.com/d5/tengo/objects"
)
var textModule = map[string]objects.Object{
"re_match": &objects.UserFunction{Value: textREMatch}, // re_match(pattern, text) => bool/error
"re_find": &objects.UserFunction{Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined
"re_replace": &objects.UserFunction{Value: textREReplace}, // re_replace(pattern, text, repl) => string/error
"re_split": &objects.UserFunction{Value: textRESplit}, // re_split(pattern, text, count) => [string]/error
"re_compile": &objects.UserFunction{Value: textRECompile}, // re_compile(pattern) => Regexp/error
"compare": &objects.UserFunction{Name: "compare", Value: FuncASSRI(strings.Compare)}, // compare(a, b) => int
"contains": &objects.UserFunction{Name: "contains", Value: FuncASSRB(strings.Contains)}, // contains(s, substr) => bool
"contains_any": &objects.UserFunction{Name: "contains_any", Value: FuncASSRB(strings.ContainsAny)}, // contains_any(s, chars) => bool
"count": &objects.UserFunction{Name: "count", Value: FuncASSRI(strings.Count)}, // count(s, substr) => int
"equal_fold": &objects.UserFunction{Name: "equal_fold", Value: FuncASSRB(strings.EqualFold)}, // "equal_fold(s, t) => bool
"fields": &objects.UserFunction{Name: "fields", Value: FuncASRSs(strings.Fields)}, // fields(s) => [string]
"has_prefix": &objects.UserFunction{Name: "has_prefix", Value: FuncASSRB(strings.HasPrefix)}, // has_prefix(s, prefix) => bool
"has_suffix": &objects.UserFunction{Name: "has_suffix", Value: FuncASSRB(strings.HasSuffix)}, // has_suffix(s, suffix) => bool
"index": &objects.UserFunction{Name: "index", Value: FuncASSRI(strings.Index)}, // index(s, substr) => int
"index_any": &objects.UserFunction{Name: "index_any", Value: FuncASSRI(strings.IndexAny)}, // index_any(s, chars) => int
"join": &objects.UserFunction{Name: "join", Value: FuncASsSRS(strings.Join)}, // join(arr, sep) => string
"last_index": &objects.UserFunction{Name: "last_index", Value: FuncASSRI(strings.LastIndex)}, // last_index(s, substr) => int
"last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int
"repeat": &objects.UserFunction{Name: "repeat", Value: FuncASIRS(strings.Repeat)}, // repeat(s, count) => string
"replace": &objects.UserFunction{Value: textReplace}, // replace(s, old, new, n) => string
"split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string]
"split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string]
"split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string]
"split_n": &objects.UserFunction{Name: "split_n", Value: FuncASSIRSs(strings.SplitN)}, // split_n(s, sep, n) => [string]
"title": &objects.UserFunction{Name: "title", Value: FuncASRS(strings.Title)}, // title(s) => string
"to_lower": &objects.UserFunction{Name: "to_lower", Value: FuncASRS(strings.ToLower)}, // to_lower(s) => string
"to_title": &objects.UserFunction{Name: "to_title", Value: FuncASRS(strings.ToTitle)}, // to_title(s) => string
"to_upper": &objects.UserFunction{Name: "to_upper", Value: FuncASRS(strings.ToUpper)}, // to_upper(s) => string
"trim_left": &objects.UserFunction{Name: "trim_left", Value: FuncASSRS(strings.TrimLeft)}, // trim_left(s, cutset) => string
"trim_prefix": &objects.UserFunction{Name: "trim_prefix", Value: FuncASSRS(strings.TrimPrefix)}, // trim_prefix(s, prefix) => string
"trim_right": &objects.UserFunction{Name: "trim_right", Value: FuncASSRS(strings.TrimRight)}, // trim_right(s, cutset) => string
"trim_space": &objects.UserFunction{Name: "trim_space", Value: FuncASRS(strings.TrimSpace)}, // trim_space(s) => string
"trim_suffix": &objects.UserFunction{Name: "trim_suffix", Value: FuncASSRS(strings.TrimSuffix)}, // trim_suffix(s, suffix) => string
"atoi": &objects.UserFunction{Name: "atoi", Value: FuncASRIE(strconv.Atoi)}, // atoi(str) => int/error
"format_bool": &objects.UserFunction{Value: textFormatBool}, // format_bool(b) => string
"format_float": &objects.UserFunction{Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string
"format_int": &objects.UserFunction{Value: textFormatInt}, // format_int(i, base) => string
"itoa": &objects.UserFunction{Name: "itoa", Value: FuncAIRS(strconv.Itoa)}, // itoa(i) => string
"parse_bool": &objects.UserFunction{Value: textParseBool}, // parse_bool(str) => bool/error
"parse_float": &objects.UserFunction{Value: textParseFloat}, // parse_float(str, bits) => float/error
"parse_int": &objects.UserFunction{Value: textParseInt}, // parse_int(str, base, bits) => int/error
"quote": &objects.UserFunction{Name: "quote", Value: FuncASRS(strconv.Quote)}, // quote(str) => string
"unquote": &objects.UserFunction{Name: "unquote", Value: FuncASRSE(strconv.Unquote)}, // unquote(str) => string/error
}
func textREMatch(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
matched, err := regexp.MatchString(s1, s2)
if err != nil {
ret = wrapError(err)
return
}
if matched {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func textREFind(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs != 2 && numArgs != 3 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
re, err := regexp.Compile(s1)
if err != nil {
ret = wrapError(err)
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
if numArgs < 3 {
m := re.FindStringSubmatchIndex(s2)
if m == nil {
ret = objects.UndefinedValue
return
}
arr := &objects.Array{}
for i := 0; i < len(m); i += 2 {
arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
"text": &objects.String{Value: s2[m[i]:m[i+1]]},
"begin": &objects.Int{Value: int64(m[i])},
"end": &objects.Int{Value: int64(m[i+1])},
}})
}
ret = &objects.Array{Value: []objects.Object{arr}}
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
return
}
m := re.FindAllStringSubmatchIndex(s2, i3)
if m == nil {
ret = objects.UndefinedValue
return
}
arr := &objects.Array{}
for _, m := range m {
subMatch := &objects.Array{}
for i := 0; i < len(m); i += 2 {
subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
"text": &objects.String{Value: s2[m[i]:m[i+1]]},
"begin": &objects.Int{Value: int64(m[i])},
"end": &objects.Int{Value: int64(m[i+1])},
}})
}
arr.Value = append(arr.Value, subMatch)
}
ret = arr
return
}
func textREReplace(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 3 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
s3, ok := objects.ToString(args[2])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "string(compatible)",
Found: args[2].TypeName(),
}
return
}
re, err := regexp.Compile(s1)
if err != nil {
ret = wrapError(err)
} else {
ret = &objects.String{Value: re.ReplaceAllString(s2, s3)}
}
return
}
func textRESplit(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs != 2 && numArgs != 3 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
var i3 = -1
if numArgs > 2 {
i3, ok = objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
return
}
}
re, err := regexp.Compile(s1)
if err != nil {
ret = wrapError(err)
return
}
arr := &objects.Array{}
for _, s := range re.Split(s2, i3) {
arr.Value = append(arr.Value, &objects.String{Value: s})
}
ret = arr
return
}
func textRECompile(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
re, err := regexp.Compile(s1)
if err != nil {
ret = wrapError(err)
} else {
ret = makeTextRegexp(re)
}
return
}
func textReplace(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 4 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
s3, ok := objects.ToString(args[2])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "string(compatible)",
Found: args[2].TypeName(),
}
return
}
i4, ok := objects.ToInt(args[3])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "fourth",
Expected: "int(compatible)",
Found: args[3].TypeName(),
}
return
}
ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)}
return
}
func textFormatBool(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
b1, ok := args[0].(*objects.Bool)
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "bool",
Found: args[0].TypeName(),
}
return
}
if b1 == objects.TrueValue {
ret = &objects.String{Value: "true"}
} else {
ret = &objects.String{Value: "false"}
}
return
}
func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 4 {
err = objects.ErrWrongNumArguments
return
}
f1, ok := args[0].(*objects.Float)
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "float",
Found: args[0].TypeName(),
}
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
return
}
i4, ok := objects.ToInt(args[3])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "fourth",
Expected: "int(compatible)",
Found: args[3].TypeName(),
}
return
}
ret = &objects.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)}
return
}
func textFormatInt(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := args[0].(*objects.Int)
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int",
Found: args[0].TypeName(),
}
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
ret = &objects.String{Value: strconv.FormatInt(i1.Value, i2)}
return
}
func textParseBool(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := args[0].(*objects.String)
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string",
Found: args[0].TypeName(),
}
return
}
parsed, err := strconv.ParseBool(s1.Value)
if err != nil {
ret = wrapError(err)
return
}
if parsed {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func textParseFloat(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := args[0].(*objects.String)
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string",
Found: args[0].TypeName(),
}
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
parsed, err := strconv.ParseFloat(s1.Value, i2)
if err != nil {
ret = wrapError(err)
return
}
ret = &objects.Float{Value: parsed}
return
}
func textParseInt(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 3 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := args[0].(*objects.String)
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string",
Found: args[0].TypeName(),
}
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
return
}
parsed, err := strconv.ParseInt(s1.Value, i2, i3)
if err != nil {
ret = wrapError(err)
return
}
ret = &objects.Int{Value: parsed}
return
}

View File

@ -1,195 +0,0 @@
package stdlib
import (
"regexp"
"github.com/d5/tengo/objects"
)
func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// match(text) => bool
"match": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
if re.MatchString(s1) {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
},
},
// find(text) => array(array({text:,begin:,end:}))/undefined
// find(text, maxCount) => array(array({text:,begin:,end:}))/undefined
"find": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs != 1 && numArgs != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
if numArgs == 1 {
m := re.FindStringSubmatchIndex(s1)
if m == nil {
ret = objects.UndefinedValue
return
}
arr := &objects.Array{}
for i := 0; i < len(m); i += 2 {
arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
"text": &objects.String{Value: s1[m[i]:m[i+1]]},
"begin": &objects.Int{Value: int64(m[i])},
"end": &objects.Int{Value: int64(m[i+1])},
}})
}
ret = &objects.Array{Value: []objects.Object{arr}}
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
m := re.FindAllStringSubmatchIndex(s1, i2)
if m == nil {
ret = objects.UndefinedValue
return
}
arr := &objects.Array{}
for _, m := range m {
subMatch := &objects.Array{}
for i := 0; i < len(m); i += 2 {
subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
"text": &objects.String{Value: s1[m[i]:m[i+1]]},
"begin": &objects.Int{Value: int64(m[i])},
"end": &objects.Int{Value: int64(m[i+1])},
}})
}
arr.Value = append(arr.Value, subMatch)
}
ret = arr
return
},
},
// replace(src, repl) => string
"replace": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
ret = &objects.String{Value: re.ReplaceAllString(s1, s2)}
return
},
},
// split(text) => array(string)
// split(text, maxCount) => array(string)
"split": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs != 1 && numArgs != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
var i2 = -1
if numArgs > 1 {
i2, ok = objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
}
arr := &objects.Array{}
for _, s := range re.Split(s1, i2) {
arr.Value = append(arr.Value, &objects.String{Value: s})
}
ret = arr
return
},
},
},
}
}

View File

@ -1,982 +0,0 @@
package stdlib
import (
"time"
"github.com/d5/tengo/objects"
)
var timesModule = map[string]objects.Object{
"format_ansic": &objects.String{Value: time.ANSIC},
"format_unix_date": &objects.String{Value: time.UnixDate},
"format_ruby_date": &objects.String{Value: time.RubyDate},
"format_rfc822": &objects.String{Value: time.RFC822},
"format_rfc822z": &objects.String{Value: time.RFC822Z},
"format_rfc850": &objects.String{Value: time.RFC850},
"format_rfc1123": &objects.String{Value: time.RFC1123},
"format_rfc1123z": &objects.String{Value: time.RFC1123Z},
"format_rfc3339": &objects.String{Value: time.RFC3339},
"format_rfc3339_nano": &objects.String{Value: time.RFC3339Nano},
"format_kitchen": &objects.String{Value: time.Kitchen},
"format_stamp": &objects.String{Value: time.Stamp},
"format_stamp_milli": &objects.String{Value: time.StampMilli},
"format_stamp_micro": &objects.String{Value: time.StampMicro},
"format_stamp_nano": &objects.String{Value: time.StampNano},
"nanosecond": &objects.Int{Value: int64(time.Nanosecond)},
"microsecond": &objects.Int{Value: int64(time.Microsecond)},
"millisecond": &objects.Int{Value: int64(time.Millisecond)},
"second": &objects.Int{Value: int64(time.Second)},
"minute": &objects.Int{Value: int64(time.Minute)},
"hour": &objects.Int{Value: int64(time.Hour)},
"january": &objects.Int{Value: int64(time.January)},
"february": &objects.Int{Value: int64(time.February)},
"march": &objects.Int{Value: int64(time.March)},
"april": &objects.Int{Value: int64(time.April)},
"may": &objects.Int{Value: int64(time.May)},
"june": &objects.Int{Value: int64(time.June)},
"july": &objects.Int{Value: int64(time.July)},
"august": &objects.Int{Value: int64(time.August)},
"september": &objects.Int{Value: int64(time.September)},
"october": &objects.Int{Value: int64(time.October)},
"november": &objects.Int{Value: int64(time.November)},
"december": &objects.Int{Value: int64(time.December)},
"sleep": &objects.UserFunction{Name: "sleep", Value: timesSleep}, // sleep(int)
"parse_duration": &objects.UserFunction{Name: "parse_duration", Value: timesParseDuration}, // parse_duration(str) => int
"since": &objects.UserFunction{Name: "since", Value: timesSince}, // since(time) => int
"until": &objects.UserFunction{Name: "until", Value: timesUntil}, // until(time) => int
"duration_hours": &objects.UserFunction{Name: "duration_hours", Value: timesDurationHours}, // duration_hours(int) => float
"duration_minutes": &objects.UserFunction{Name: "duration_minutes", Value: timesDurationMinutes}, // duration_minutes(int) => float
"duration_nanoseconds": &objects.UserFunction{Name: "duration_nanoseconds", Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int
"duration_seconds": &objects.UserFunction{Name: "duration_seconds", Value: timesDurationSeconds}, // duration_seconds(int) => float
"duration_string": &objects.UserFunction{Name: "duration_string", Value: timesDurationString}, // duration_string(int) => string
"month_string": &objects.UserFunction{Name: "month_string", Value: timesMonthString}, // month_string(int) => string
"date": &objects.UserFunction{Name: "date", Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time
"now": &objects.UserFunction{Name: "now", Value: timesNow}, // now() => time
"parse": &objects.UserFunction{Name: "parse", Value: timesParse}, // parse(format, str) => time
"unix": &objects.UserFunction{Name: "unix", Value: timesUnix}, // unix(sec, nsec) => time
"add": &objects.UserFunction{Name: "add", Value: timesAdd}, // add(time, int) => time
"add_date": &objects.UserFunction{Name: "add_date", Value: timesAddDate}, // add_date(time, years, months, days) => time
"sub": &objects.UserFunction{Name: "sub", Value: timesSub}, // sub(t time, u time) => int
"after": &objects.UserFunction{Name: "after", Value: timesAfter}, // after(t time, u time) => bool
"before": &objects.UserFunction{Name: "before", Value: timesBefore}, // before(t time, u time) => bool
"time_year": &objects.UserFunction{Name: "time_year", Value: timesTimeYear}, // time_year(time) => int
"time_month": &objects.UserFunction{Name: "time_month", Value: timesTimeMonth}, // time_month(time) => int
"time_day": &objects.UserFunction{Name: "time_day", Value: timesTimeDay}, // time_day(time) => int
"time_weekday": &objects.UserFunction{Name: "time_weekday", Value: timesTimeWeekday}, // time_weekday(time) => int
"time_hour": &objects.UserFunction{Name: "time_hour", Value: timesTimeHour}, // time_hour(time) => int
"time_minute": &objects.UserFunction{Name: "time_minute", Value: timesTimeMinute}, // time_minute(time) => int
"time_second": &objects.UserFunction{Name: "time_second", Value: timesTimeSecond}, // time_second(time) => int
"time_nanosecond": &objects.UserFunction{Name: "time_nanosecond", Value: timesTimeNanosecond}, // time_nanosecond(time) => int
"time_unix": &objects.UserFunction{Name: "time_unix", Value: timesTimeUnix}, // time_unix(time) => int
"time_unix_nano": &objects.UserFunction{Name: "time_unix_nano", Value: timesTimeUnixNano}, // time_unix_nano(time) => int
"time_format": &objects.UserFunction{Name: "time_format", Value: timesTimeFormat}, // time_format(time, format) => string
"time_location": &objects.UserFunction{Name: "time_location", Value: timesTimeLocation}, // time_location(time) => string
"time_string": &objects.UserFunction{Name: "time_string", Value: timesTimeString}, // time_string(time) => string
"is_zero": &objects.UserFunction{Name: "is_zero", Value: timesIsZero}, // is_zero(time) => bool
"to_local": &objects.UserFunction{Name: "to_local", Value: timesToLocal}, // to_local(time) => time
"to_utc": &objects.UserFunction{Name: "to_utc", Value: timesToUTC}, // to_utc(time) => time
}
func timesSleep(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
time.Sleep(time.Duration(i1))
ret = objects.UndefinedValue
return
}
func timesParseDuration(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
dur, err := time.ParseDuration(s1)
if err != nil {
ret = wrapError(err)
return
}
ret = &objects.Int{Value: int64(dur)}
return
}
func timesSince(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(time.Since(t1))}
return
}
func timesUntil(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(time.Until(t1))}
return
}
func timesDurationHours(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Float{Value: time.Duration(i1).Hours()}
return
}
func timesDurationMinutes(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Float{Value: time.Duration(i1).Minutes()}
return
}
func timesDurationNanoseconds(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: time.Duration(i1).Nanoseconds()}
return
}
func timesDurationSeconds(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Float{Value: time.Duration(i1).Seconds()}
return
}
func timesDurationString(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.String{Value: time.Duration(i1).String()}
return
}
func timesMonthString(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.String{Value: time.Month(i1).String()}
return
}
func timesDate(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 7 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
return
}
i4, ok := objects.ToInt(args[3])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "fourth",
Expected: "int(compatible)",
Found: args[3].TypeName(),
}
return
}
i5, ok := objects.ToInt(args[4])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "fifth",
Expected: "int(compatible)",
Found: args[4].TypeName(),
}
return
}
i6, ok := objects.ToInt(args[5])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "sixth",
Expected: "int(compatible)",
Found: args[5].TypeName(),
}
return
}
i7, ok := objects.ToInt(args[6])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "seventh",
Expected: "int(compatible)",
Found: args[6].TypeName(),
}
return
}
ret = &objects.Time{Value: time.Date(i1, time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location())}
return
}
func timesNow(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
err = objects.ErrWrongNumArguments
return
}
ret = &objects.Time{Value: time.Now()}
return
}
func timesParse(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
parsed, err := time.Parse(s1, s2)
if err != nil {
ret = wrapError(err)
return
}
ret = &objects.Time{Value: parsed}
return
}
func timesUnix(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return
}
i2, ok := objects.ToInt64(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
ret = &objects.Time{Value: time.Unix(i1, i2)}
return
}
func timesAdd(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
i2, ok := objects.ToInt64(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
ret = &objects.Time{Value: t1.Add(time.Duration(i2))}
return
}
func timesSub(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
t2, ok := objects.ToTime(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "time(compatible)",
Found: args[1].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Sub(t2))}
return
}
func timesAddDate(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 4 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
return
}
i4, ok := objects.ToInt(args[3])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "fourth",
Expected: "int(compatible)",
Found: args[3].TypeName(),
}
return
}
ret = &objects.Time{Value: t1.AddDate(i2, i3, i4)}
return
}
func timesAfter(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
t2, ok := objects.ToTime(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "time(compatible)",
Found: args[1].TypeName(),
}
return
}
if t1.After(t2) {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func timesBefore(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
t2, ok := objects.ToTime(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
if t1.Before(t2) {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func timesTimeYear(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Year())}
return
}
func timesTimeMonth(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Month())}
return
}
func timesTimeDay(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Day())}
return
}
func timesTimeWeekday(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Weekday())}
return
}
func timesTimeHour(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Hour())}
return
}
func timesTimeMinute(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Minute())}
return
}
func timesTimeSecond(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Second())}
return
}
func timesTimeNanosecond(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Nanosecond())}
return
}
func timesTimeUnix(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.Unix())}
return
}
func timesTimeUnixNano(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Int{Value: int64(t1.UnixNano())}
return
}
func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return
}
ret = &objects.String{Value: t1.Format(s2)}
return
}
func timesIsZero(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
if t1.IsZero() {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func timesToLocal(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Time{Value: t1.Local()}
return
}
func timesToUTC(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.Time{Value: t1.UTC()}
return
}
func timesTimeLocation(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.String{Value: t1.Location().String()}
return
}
func timesTimeString(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return
}
ret = &objects.String{Value: t1.String()}
return
}

11
vendor/github.com/d5/tengo/tengo.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
package tengo
var (
// MaxStringLen is the maximum byte-length for string value.
// Note this limit applies to all compiler/VM instances in the process.
MaxStringLen = 2147483647
// MaxBytesLen is the maximum length for bytes value.
// Note this limit applies to all compiler/VM instances in the process.
MaxBytesLen = 2147483647
)

View File

@ -2,7 +2,7 @@ package binary
import (
"fmt"
"github.com/Rhymen/go-whatsapp/binary/token"
"github.com/matterbridge/go-whatsapp/binary/token"
"io"
"strconv"
)

View File

@ -2,7 +2,7 @@ package binary
import (
"fmt"
"github.com/Rhymen/go-whatsapp/binary/token"
"github.com/matterbridge/go-whatsapp/binary/token"
"math"
"strconv"
"strings"

View File

@ -2,7 +2,7 @@ package binary
import (
"fmt"
pb "github.com/Rhymen/go-whatsapp/binary/proto"
pb "github.com/matterbridge/go-whatsapp/binary/proto"
"github.com/golang/protobuf/proto"
)

View File

@ -14,9 +14,9 @@ import (
"sync"
"time"
"github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/gorilla/websocket"
"github.com/matterbridge/go-whatsapp/binary"
"github.com/matterbridge/go-whatsapp/crypto/cbc"
)
type metric byte
@ -128,7 +128,7 @@ func NewConn(timeout time.Duration) (*Conn, error) {
go wac.readPump()
go wac.writePump()
go wac.keepAlive(20000, 90000)
go wac.keepAlive(20000, 60000)
return wac, nil
}

View File

@ -2,7 +2,7 @@ package whatsapp
import (
"fmt"
"github.com/Rhymen/go-whatsapp/binary"
"github.com/matterbridge/go-whatsapp/binary"
"strconv"
"time"
)
@ -13,8 +13,8 @@ const (
PresenceAvailable = "available"
PresenceUnavailable = "unavailable"
PresenceComposing = "composing"
PresenceRecording = "recording"
PresencePaused = "paused"
PresenceRecording = "recording"
PresencePaused = "paused"
)
//TODO: filename? WhatsApp uses Store.Contacts for these functions
@ -30,44 +30,11 @@ func (wac *Conn) GetStatus(jid string) (<-chan string, error) {
return wac.write(data)
}
func (wac *Conn) GetGroupMetaData(jid string) (<-chan string, error) {
data := []interface{}{"query", "GroupMetadata", jid}
return wac.write(data)
}
func (wac *Conn) SubscribePresence(jid string) (<-chan string, error) {
data := []interface{}{"action", "presence", "subscribe", jid}
return wac.write(data)
}
func (wac *Conn) CreateGroup(subject string, participants []string) (<-chan string, error) {
return wac.setGroup("create", "", subject, participants)
}
func (wac *Conn) UpdateGroupSubject(subject string, jid string) (<-chan string, error) {
return wac.setGroup("subject", jid, subject, nil)
}
func (wac *Conn) SetAdmin(jid string, participants []string) (<-chan string, error) {
return wac.setGroup("promote", jid, "", participants)
}
func (wac *Conn) RemoveAdmin(jid string, participants []string) (<-chan string, error) {
return wac.setGroup("demote", jid, "", participants)
}
func (wac *Conn) AddMember(jid string, participants []string) (<-chan string, error) {
return wac.setGroup("add", jid, "", participants)
}
func (wac *Conn) RemoveMember(jid string, participants []string) (<-chan string, error) {
return wac.setGroup("remove", jid, "", participants)
}
func (wac *Conn) LeaveGroup(jid string) (<-chan string, error) {
return wac.setGroup("leave", jid, "", nil)
}
func (wac *Conn) Search(search string, count, page int) (*binary.Node, error) {
return wac.query("search", "", "", "", "", search, count, page)
}

View File

@ -1,4 +1,4 @@
module github.com/Rhymen/go-whatsapp
module github.com/matterbridge/go-whatsapp
require (
github.com/golang/protobuf v1.2.0

View File

@ -1,4 +1,7 @@
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

65
vendor/github.com/matterbridge/go-whatsapp/group.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package whatsapp
import (
"encoding/json"
"fmt"
"time"
)
func (wac *Conn) GetGroupMetaData(jid string) (<-chan string, error) {
data := []interface{}{"query", "GroupMetadata", jid}
return wac.write(data)
}
func (wac *Conn) CreateGroup(subject string, participants []string) (<-chan string, error) {
return wac.setGroup("create", "", subject, participants)
}
func (wac *Conn) UpdateGroupSubject(subject string, jid string) (<-chan string, error) {
return wac.setGroup("subject", jid, subject, nil)
}
func (wac *Conn) SetAdmin(jid string, participants []string) (<-chan string, error) {
return wac.setGroup("promote", jid, "", participants)
}
func (wac *Conn) RemoveAdmin(jid string, participants []string) (<-chan string, error) {
return wac.setGroup("demote", jid, "", participants)
}
func (wac *Conn) AddMember(jid string, participants []string) (<-chan string, error) {
return wac.setGroup("add", jid, "", participants)
}
func (wac *Conn) RemoveMember(jid string, participants []string) (<-chan string, error) {
return wac.setGroup("remove", jid, "", participants)
}
func (wac *Conn) LeaveGroup(jid string) (<-chan string, error) {
return wac.setGroup("leave", jid, "", nil)
}
func (wac *Conn) GroupInviteLink(jid string) (string, error) {
request := []interface{}{"query", "inviteCode", jid}
ch, err := wac.write(request)
if err != nil {
return "", err
}
var response map[string]interface{}
select {
case r := <-ch:
if err := json.Unmarshal([]byte(r), &response); err != nil {
return "", fmt.Errorf("error decoding response message: %v\n", err)
}
case <-time.After(wac.msgTimeout):
return "", fmt.Errorf("request timed out")
}
if int(response["status"].(float64)) != 200 {
return "", fmt.Errorf("request responded with %d", response["status"])
}
return response["code"].(string), nil
}

View File

@ -2,8 +2,8 @@ package whatsapp
import (
"fmt"
"github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/binary/proto"
"github.com/matterbridge/go-whatsapp/binary"
"github.com/matterbridge/go-whatsapp/binary/proto"
"os"
)

View File

@ -8,8 +8,8 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/Rhymen/go-whatsapp/crypto/hkdf"
"github.com/matterbridge/go-whatsapp/crypto/cbc"
"github.com/matterbridge/go-whatsapp/crypto/hkdf"
"io"
"io/ioutil"
"mime/multipart"

View File

@ -4,8 +4,8 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/binary/proto"
"github.com/matterbridge/go-whatsapp/binary"
"github.com/matterbridge/go-whatsapp/binary/proto"
"io"
"math/rand"
"strconv"
@ -363,6 +363,7 @@ type DocumentMessage struct {
Title string
PageCount uint32
Type string
FileName string
Thumbnail []byte
Content io.Reader
url string
@ -376,15 +377,16 @@ func getDocumentMessage(msg *proto.WebMessageInfo) DocumentMessage {
doc := msg.GetMessage().GetDocumentMessage()
return DocumentMessage{
Info: getMessageInfo(msg),
Title: doc.GetTitle(),
PageCount: doc.GetPageCount(),
Type: doc.GetMimetype(),
FileName: doc.GetFileName(),
Thumbnail: doc.GetJpegThumbnail(),
url: doc.GetUrl(),
mediaKey: doc.GetMediaKey(),
fileEncSha256: doc.GetFileEncSha256(),
fileSha256: doc.GetFileSha256(),
fileLength: doc.GetFileLength(),
PageCount: doc.GetPageCount(),
Title: doc.GetTitle(),
Type: doc.GetMimetype(),
}
}

View File

@ -9,9 +9,9 @@ import (
"fmt"
"time"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/Rhymen/go-whatsapp/crypto/curve25519"
"github.com/Rhymen/go-whatsapp/crypto/hkdf"
"github.com/matterbridge/go-whatsapp/crypto/cbc"
"github.com/matterbridge/go-whatsapp/crypto/curve25519"
"github.com/matterbridge/go-whatsapp/crypto/hkdf"
)
/*

View File

@ -1,7 +1,7 @@
package whatsapp
import (
"github.com/Rhymen/go-whatsapp/binary"
"github.com/matterbridge/go-whatsapp/binary"
"strings"
)

View File

@ -20,7 +20,7 @@ import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
type ChatUpdateCommand string

View File

@ -20,7 +20,7 @@ import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
type CommandType string

View File

@ -19,7 +19,7 @@ package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
type ConnInfo struct {

View File

@ -19,7 +19,7 @@ package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
type JSONMessage []json.RawMessage

View File

@ -20,7 +20,7 @@ import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
type MsgInfoCommand string

View File

@ -20,7 +20,7 @@ import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
type PresenceType string

View File

@ -19,7 +19,7 @@ package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
type ProtocolProps struct {

View File

@ -19,7 +19,7 @@ package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
type StreamType string

View File

@ -24,7 +24,7 @@ import (
"net/http"
"strings"
"github.com/Rhymen/go-whatsapp"
"github.com/matterbridge/go-whatsapp"
)
const (

View File

@ -29,6 +29,15 @@ const (
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
)
const (
genericRead = 0x80000000
genericWrite = 0x40000000
)
const (
consoleTextmodeBuffer = 0x1
)
type wchar uint16
type short int16
type dword uint32
@ -69,14 +78,17 @@ var (
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW")
procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
)
// Writer provide colorable Writer to the console
type Writer struct {
out io.Writer
handle syscall.Handle
oldattr word
oldpos coord
out io.Writer
handle syscall.Handle
althandle syscall.Handle
oldattr word
oldpos coord
rest bytes.Buffer
}
// NewColorable return new instance of Writer which handle escape sequence from File.
@ -407,7 +419,18 @@ func (w *Writer) Write(data []byte) (n int, err error) {
var csbi consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
er := bytes.NewReader(data)
handle := w.handle
var er *bytes.Reader
if w.rest.Len() > 0 {
var rest bytes.Buffer
w.rest.WriteTo(&rest)
w.rest.Reset()
rest.Write(data)
er = bytes.NewReader(rest.Bytes())
} else {
er = bytes.NewReader(data)
}
var bw [1]byte
loop:
for {
@ -425,29 +448,55 @@ loop:
break loop
}
if c2 == ']' {
if err := doTitleSequence(er); err != nil {
switch c2 {
case '>':
continue
case ']':
w.rest.WriteByte(c1)
w.rest.WriteByte(c2)
er.WriteTo(&w.rest)
if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 {
break loop
}
continue
}
if c2 != 0x5b {
continue
}
var buf bytes.Buffer
var m byte
for {
c, err := er.ReadByte()
er = bytes.NewReader(w.rest.Bytes()[2:])
err := doTitleSequence(er)
if err != nil {
break loop
}
w.rest.Reset()
continue
// https://github.com/mattn/go-colorable/issues/27
case '7':
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
w.oldpos = csbi.cursorPosition
continue
case '8':
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
continue
case 0x5b:
// execute part after switch
default:
continue
}
w.rest.WriteByte(c1)
w.rest.WriteByte(c2)
er.WriteTo(&w.rest)
var buf bytes.Buffer
var m byte
for i, c := range w.rest.Bytes()[2:] {
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
m = c
er = bytes.NewReader(w.rest.Bytes()[2+i+1:])
w.rest.Reset()
break
}
buf.Write([]byte(string(c)))
}
if m == 0 {
break loop
}
switch m {
case 'A':
@ -455,61 +504,64 @@ loop:
if err != nil {
continue
}
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
csbi.cursorPosition.y -= short(n)
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'B':
n, err = strconv.Atoi(buf.String())
if err != nil {
continue
}
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
csbi.cursorPosition.y += short(n)
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'C':
n, err = strconv.Atoi(buf.String())
if err != nil {
continue
}
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
csbi.cursorPosition.x += short(n)
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'D':
n, err = strconv.Atoi(buf.String())
if err != nil {
continue
}
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
csbi.cursorPosition.x -= short(n)
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
if csbi.cursorPosition.x < 0 {
csbi.cursorPosition.x = 0
}
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'E':
n, err = strconv.Atoi(buf.String())
if err != nil {
continue
}
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
csbi.cursorPosition.x = 0
csbi.cursorPosition.y += short(n)
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'F':
n, err = strconv.Atoi(buf.String())
if err != nil {
continue
}
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
csbi.cursorPosition.x = 0
csbi.cursorPosition.y -= short(n)
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'G':
n, err = strconv.Atoi(buf.String())
if err != nil {
continue
}
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
csbi.cursorPosition.x = short(n - 1)
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'H', 'f':
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
if buf.Len() > 0 {
token := strings.Split(buf.String(), ";")
switch len(token) {
@ -534,7 +586,7 @@ loop:
} else {
csbi.cursorPosition.y = 0
}
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'J':
n := 0
if buf.Len() > 0 {
@ -545,20 +597,20 @@ loop:
}
var count, written dword
var cursor coord
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
switch n {
case 0:
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x)
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
case 1:
cursor = coord{x: csbi.window.left, y: csbi.window.top}
count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.window.top-csbi.cursorPosition.y)*csbi.size.x)
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x)
case 2:
cursor = coord{x: csbi.window.left, y: csbi.window.top}
count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x)
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
}
procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
case 'K':
n := 0
if buf.Len() > 0 {
@ -567,28 +619,28 @@ loop:
continue
}
}
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
var cursor coord
var count, written dword
switch n {
case 0:
cursor = coord{x: csbi.cursorPosition.x + 1, y: csbi.cursorPosition.y}
count = dword(csbi.size.x - csbi.cursorPosition.x - 1)
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
count = dword(csbi.size.x - csbi.cursorPosition.x)
case 1:
cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y}
cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
count = dword(csbi.size.x - csbi.cursorPosition.x)
case 2:
cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y}
cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
count = dword(csbi.size.x)
}
procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
case 'm':
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
attr := csbi.attributes
cs := buf.String()
if cs == "" {
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr))
procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr))
continue
}
token := strings.Split(cs, ";")
@ -627,6 +679,21 @@ loop:
attr |= n256foreAttr[n256]
i += 2
}
} else if len(token) == 5 && token[i+1] == "2" {
var r, g, b int
r, _ = strconv.Atoi(token[i+2])
g, _ = strconv.Atoi(token[i+3])
b, _ = strconv.Atoi(token[i+4])
i += 4
if r > 127 {
attr |= foregroundRed
}
if g > 127 {
attr |= foregroundGreen
}
if b > 127 {
attr |= foregroundBlue
}
} else {
attr = attr & (w.oldattr & backgroundMask)
}
@ -654,6 +721,21 @@ loop:
attr |= n256backAttr[n256]
i += 2
}
} else if len(token) == 5 && token[i+1] == "2" {
var r, g, b int
r, _ = strconv.Atoi(token[i+2])
g, _ = strconv.Atoi(token[i+3])
b, _ = strconv.Atoi(token[i+4])
i += 4
if r > 127 {
attr |= backgroundRed
}
if g > 127 {
attr |= backgroundGreen
}
if b > 127 {
attr |= backgroundBlue
}
} else {
attr = attr & (w.oldattr & foregroundMask)
}
@ -685,38 +767,52 @@ loop:
attr |= backgroundBlue
}
}
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr))
}
}
case 'h':
var ci consoleCursorInfo
cs := buf.String()
if cs == "5>" {
procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
ci.visible = 0
procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
} else if cs == "?25" {
procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
ci.visible = 1
procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
} else if cs == "?1049" {
if w.althandle == 0 {
h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0)
w.althandle = syscall.Handle(h)
if w.althandle != 0 {
handle = w.althandle
}
}
}
case 'l':
var ci consoleCursorInfo
cs := buf.String()
if cs == "5>" {
procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
ci.visible = 1
procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
} else if cs == "?25" {
procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
ci.visible = 0
procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
} else if cs == "?1049" {
if w.althandle != 0 {
syscall.CloseHandle(w.althandle)
w.althandle = 0
handle = w.handle
}
}
case 's':
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
w.oldpos = csbi.cursorPosition
case 'u':
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
}
}

3
vendor/github.com/mattn/go-colorable/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/mattn/go-colorable
require github.com/mattn/go-isatty v0.0.5

4
vendor/github.com/mattn/go-colorable/go.sum generated vendored Normal file
View File

@ -0,0 +1,4 @@
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

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