<commit_before>package btelegram

import (
	"regexp"
	"strconv"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	log "github.com/Sirupsen/logrus"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c       *tgbotapi.BotAPI
	Config  *config.Protocol
	Remote  chan config.Message
	Account string
}

var flog *log.Entry
var protocol = "telegram"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
	b := &Btelegram{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Btelegram) Connect() error {
	var err error
	flog.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	flog.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil

}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	flog.Debugf("Receiving %#v", msg)
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	if b.Config.MessageFormat == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	if msg.Extra != nil {
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			var c tgbotapi.Chattable
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				file := tgbotapi.FileBytes{fi.Name, *fi.Data}
				re := regexp.MustCompile(".(jpg|png)$")
				if re.MatchString(fi.Name) {
					c = tgbotapi.NewPhotoUpload(chatid, file)
				} else {
					c = tgbotapi.NewDocumentUpload(chatid, file)
				}
				_, err := b.c.Send(c)
				if err != nil {
					log.Errorf("file upload failed: %#v", err)
				}
			}
		}
	}

	m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
	if b.Config.MessageFormat == "HTML" {
		m.ParseMode = tgbotapi.ModeHTML
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil

}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		flog.Debugf("Receiving from telegram: %#v", update.Message)
		var message *tgbotapi.Message
		username := ""
		channel := ""
		text := ""

		fmsg := config.Message{Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}
		if update.EditedChannelPost != nil && !b.Config.EditDisable {
			message = update.EditedChannelPost
			message.Text = message.Text + b.Config.EditSuffix
		}
		// handle groups
		if update.Message != nil {
			message = update.Message
		}
		if update.EditedMessage != nil && !b.Config.EditDisable {
			message = update.EditedMessage
			message.Text = message.Text + b.Config.EditSuffix
		}
		if message.From != nil {
			if b.Config.UseFirstName {
				username = message.From.FirstName
			}
			if username == "" {
				username = message.From.UserName
				if username == "" {
					username = message.From.FirstName
				}
			}
			text = message.Text
			channel = strconv.FormatInt(message.Chat.ID, 10)
		}

		if username == "" {
			username = "unknown"
		}
		if message.Sticker != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}
		if message.Video != nil {
			b.handleDownload(message.Video, &fmsg)
		}
		if message.Photo != nil && b.Config.UseInsecureURL {
			b.handleDownload(message.Photo, &fmsg)
		}
		if message.Document != nil && b.Config.UseInsecureURL {
			b.handleDownload(message.Sticker, &fmsg)
			text = text + " " + message.Document.FileName + " : " + b.getFileDirectURL(message.Document.FileID)
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.Config.UseFirstName {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
		}

		if text != "" || len(fmsg.Extra) > 0 {
			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID)}
			flog.Debugf("Message is %#v", msg)
			b.Remote <- msg
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
	size := 0
	url := ""
	name := ""
	text := ""
	switch v := file.(type) {
	case *tgbotapi.Sticker:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "sticker"
		text = " " + url
	case *tgbotapi.Video:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "video"
		text = " " + url
	case *[]tgbotapi.PhotoSize:
		photos := *v
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		name = "photo"
		text = " " + url
	case *tgbotapi.Document:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
	}
	if b.Config.UseInsecureURL {
		msg.Text = text
		return
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	// limit to 1MB for now
	if size <= 1000000 {
		data, err := helper.DownloadFile(url)
		if err != nil {
			flog.Errorf("download %s failed %#v", url, err)
		} else {
			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
		}
	}
}
<commit_msg>Fix telegram photo/document input handling (telegram)<commit_after>package btelegram

import (
	"regexp"
	"strconv"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	log "github.com/Sirupsen/logrus"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c       *tgbotapi.BotAPI
	Config  *config.Protocol
	Remote  chan config.Message
	Account string
}

var flog *log.Entry
var protocol = "telegram"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
	b := &Btelegram{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Btelegram) Connect() error {
	var err error
	flog.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	flog.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil

}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	flog.Debugf("Receiving %#v", msg)
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	if b.Config.MessageFormat == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	if msg.Extra != nil {
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			var c tgbotapi.Chattable
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				file := tgbotapi.FileBytes{fi.Name, *fi.Data}
				re := regexp.MustCompile(".(jpg|png)$")
				if re.MatchString(fi.Name) {
					c = tgbotapi.NewPhotoUpload(chatid, file)
				} else {
					c = tgbotapi.NewDocumentUpload(chatid, file)
				}
				_, err := b.c.Send(c)
				if err != nil {
					log.Errorf("file upload failed: %#v", err)
				}
			}
		}
	}

	m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
	if b.Config.MessageFormat == "HTML" {
		m.ParseMode = tgbotapi.ModeHTML
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil

}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		flog.Debugf("Receiving from telegram: %#v", update.Message)
		var message *tgbotapi.Message
		username := ""
		channel := ""
		text := ""

		fmsg := config.Message{Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}
		if update.EditedChannelPost != nil && !b.Config.EditDisable {
			message = update.EditedChannelPost
			message.Text = message.Text + b.Config.EditSuffix
		}
		// handle groups
		if update.Message != nil {
			message = update.Message
		}
		if update.EditedMessage != nil && !b.Config.EditDisable {
			message = update.EditedMessage
			message.Text = message.Text + b.Config.EditSuffix
		}
		if message.From != nil {
			if b.Config.UseFirstName {
				username = message.From.FirstName
			}
			if username == "" {
				username = message.From.UserName
				if username == "" {
					username = message.From.FirstName
				}
			}
			text = message.Text
			channel = strconv.FormatInt(message.Chat.ID, 10)
		}

		if username == "" {
			username = "unknown"
		}
		if message.Sticker != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}
		if message.Video != nil {
			b.handleDownload(message.Video, &fmsg)
		}
		if message.Photo != nil {
			b.handleDownload(message.Photo, &fmsg)
		}
		if message.Document != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.Config.UseFirstName {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
		}

		if text != "" || len(fmsg.Extra) > 0 {
			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID)}
			flog.Debugf("Message is %#v", msg)
			b.Remote <- msg
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
	size := 0
	url := ""
	name := ""
	text := ""
	switch v := file.(type) {
	case *tgbotapi.Sticker:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "sticker"
		text = " " + url
	case *tgbotapi.Video:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "video"
		text = " " + url
	case *[]tgbotapi.PhotoSize:
		photos := *v
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		name = "photo"
		text = " " + url
	case *tgbotapi.Document:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
	}
	if b.Config.UseInsecureURL {
		msg.Text = text
		return
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	// limit to 1MB for now
	if size <= 1000000 {
		data, err := helper.DownloadFile(url)
		if err != nil {
			flog.Errorf("download %s failed %#v", url, err)
		} else {
			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Dorival de Moraes Pedroso. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goga

import (
	"encoding/json"

	"github.com/cpmech/gosl/chk"
	"github.com/cpmech/gosl/io"
)

// ConfParams is an auxiliary structure to hold configuration parameters for setting the GA up
type ConfParams struct {

	// initialisation
	Seed   int     // seed to initialise random numbers generator. Seed ≤ 0 means use current time
	Pll    bool    // allow running islands in parallel (go-routines)
	Nisl   int     // number of islands
	Ninds  int     // number of individuals: population size
	Nbases int     // number of bases in chromosome
	Grid   bool    // generate individuals based on grid
	Noise  float64 // apply noise when generate based on grid (if Noise > 0)

	// time control
	Tf    int // number of generations
	Dtout int // increment of time for output
	Dtmig int // increment of time for migration
	Dtreg int // increment of time for regeneration

	// regeneration
	RegIni    int     // time index for initial regeneration. use -1 for none
	RegTol    float64 // tolerance for ρ to activate regeneration
	RegBest   bool    // enforce that regeneration is always based on based individual, regardless the population is homogeneous or not
	RegPct    float64 // percentage of individuals to be regenerated
	RegMmin   float64 // multiplier to decrease reference value; e.g. 0.1
	RegMmax   float64 // multiplier to increase reference value; e.g. 10.0
	UseStdDev bool    // use standard deviation (σ) instead of average deviation in Stat

	// selection and reproduction
	Pc    float64 // probability of crossover
	Pm    float64 // probability of mutation
	Elite bool    // use elitism
	Rws   bool    // use Roulette-Wheel selection method
	Rnk   bool    // ranking
	RnkSp float64 // selective pressure for ranking

	// diversity
	StatOorSkip bool // skip oor individuals from statistics

	// output
	Json      bool   // output results as .json files; not tables
	DirOut    string // directory to save output files. "" means "/tmp/goga"
	FnKey     string // filename key for output files. "" means no output files
	DoPlot    bool   // plot results
	PltTi     int    // initial time for plot
	PltTf     int    // final time for plot
	ShowBases bool   // show also bases when printing results (if any)

	// auxiliary
	Problem  int // problem ID
	Strategy int // strategy for implementing constraints
	Ntrials  int // number of trials

	// crossover
	CxNcuts   map[string]int         // crossover number of cuts for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	CxCuts    map[string][]int       // crossover specific cuts for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	CxProbs   map[string]float64     // crossover probabilities for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	CxFuncs   map[string]interface{} // crossover functions for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	CxIntFunc CxIntFunc_t            // crossover function
	CxFltFunc CxFltFunc_t            // crossover function
	CxStrFunc CxStrFunc_t            // crossover function
	CxKeyFunc CxKeyFunc_t            // crossover function
	CxBytFunc CxBytFunc_t            // crossover function
	CxFunFunc CxFunFunc_t            // crossover function

	// mutation
	MtNchanges map[string]int         // mutation number of changes for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	MtProbs    map[string]float64     // mutation probabilities for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	MtExtra    map[string]interface{} // mutation extra parameters for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	MtIntFunc  MtIntFunc_t            // mutation function
	MtFltFunc  MtFltFunc_t            // mutation function
	MtStrFunc  MtStrFunc_t            // mutation function
	MtKeyFunc  MtKeyFunc_t            // mutation function
	MtBytFunc  MtBytFunc_t            // mutation function
	MtFunFunc  MtFunFunc_t            // mutation function
}

// SetDefault sets default parameters
func (o *ConfParams) SetDefault() {

	// initialisation
	o.Seed = 0
	o.Pll = true
	o.Nisl = 4
	o.Ninds = 20
	o.Nbases = 10
	o.Grid = true
	o.Noise = 0.3

	// time control
	o.Tf = 100
	o.Dtout = 10
	o.Dtmig = 40
	o.Dtreg = 30

	// regeneration
	o.RegIni = 3
	o.RegTol = 1e-2
	o.RegBest = false
	o.RegPct = 0.3
	o.RegMmin = 0.1
	o.RegMmax = 10.0
	o.UseStdDev = false

	// selection and reproduction
	o.Pc = 0.8
	o.Pm = 0.01
	o.Elite = true
	o.Rws = false
	o.Rnk = true
	o.RnkSp = 1.2

	// diversity
	o.StatOorSkip = false

	// output
	o.Json = false
	o.DirOut = "/tmp/goga"
	o.FnKey = ""
	o.DoPlot = false
	o.PltTi = 0
	o.PltTf = -1
	o.ShowBases = false

	// auxiliary
	o.Problem = 1
	o.Strategy = 1
	o.Ntrials = 100

	// number of cuts in chromossome
	o.CxNcuts = map[string]int{"int": 2, "flt": 2, "str": 2, "key": 2, "byt": 2, "fun": 2}
}

// CalcDerived calculates derived quantities
func (o *ConfParams) CalcDerived() {

	// set probabilities
	pc, pm := o.Pc, o.Pm
	o.CxProbs = map[string]float64{"int": pc, "flt": pc, "str": pc, "key": pc, "byt": pc, "fun": pc}
	o.MtProbs = map[string]float64{"int": pm, "flt": pm, "str": pm, "key": pm, "byt": pm, "fun": pm}
}

// NewConfParams returns a new ConfParams structure, with default values set
func NewConfParams() *ConfParams {
	var o ConfParams
	o.SetDefault()
	o.CalcDerived()
	return &o
}

// ReadConfParams reads configuration parameters from JSON file
func ReadConfParams(filenamepath string) *ConfParams {

	// new params
	var o ConfParams
	o.SetDefault()

	// read file
	b, err := io.ReadFile(filenamepath)
	if err != nil {
		chk.Panic("cannot read parameters file %q", filenamepath)
	}

	// decode
	err = json.Unmarshal(b, &o)
	if err != nil {
		chk.Panic("cannot unmarshal parameters file %q", filenamepath)
	}

	// results
	o.CalcDerived()
	return &o
}
<commit_msg>tolerance (eps1) added to parameters list<commit_after>// Copyright 2015 Dorival de Moraes Pedroso. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goga

import (
	"encoding/json"

	"github.com/cpmech/gosl/chk"
	"github.com/cpmech/gosl/io"
)

// ConfParams is an auxiliary structure to hold configuration parameters for setting the GA up
type ConfParams struct {

	// initialisation
	Seed   int     // seed to initialise random numbers generator. Seed ≤ 0 means use current time
	Pll    bool    // allow running islands in parallel (go-routines)
	Nisl   int     // number of islands
	Ninds  int     // number of individuals: population size
	Nbases int     // number of bases in chromosome
	Grid   bool    // generate individuals based on grid
	Noise  float64 // apply noise when generate based on grid (if Noise > 0)

	// time control
	Tf    int // number of generations
	Dtout int // increment of time for output
	Dtmig int // increment of time for migration
	Dtreg int // increment of time for regeneration

	// regeneration
	RegIni    int     // time index for initial regeneration. use -1 for none
	RegTol    float64 // tolerance for ρ to activate regeneration
	RegBest   bool    // enforce that regeneration is always based on based individual, regardless the population is homogeneous or not
	RegPct    float64 // percentage of individuals to be regenerated
	RegMmin   float64 // multiplier to decrease reference value; e.g. 0.1
	RegMmax   float64 // multiplier to increase reference value; e.g. 10.0
	UseStdDev bool    // use standard deviation (σ) instead of average deviation in Stat

	// selection and reproduction
	Pc    float64 // probability of crossover
	Pm    float64 // probability of mutation
	Elite bool    // use elitism
	Rws   bool    // use Roulette-Wheel selection method
	Rnk   bool    // ranking
	RnkSp float64 // selective pressure for ranking

	// diversity
	StatOorSkip bool // skip oor individuals from statistics

	// output
	Json      bool   // output results as .json files; not tables
	DirOut    string // directory to save output files. "" means "/tmp/goga"
	FnKey     string // filename key for output files. "" means no output files
	DoPlot    bool   // plot results
	PltTi     int    // initial time for plot
	PltTf     int    // final time for plot
	ShowBases bool   // show also bases when printing results (if any)

	// auxiliary
	Problem  int     // problem ID
	Strategy int     // strategy for implementing constraints
	Ntrials  int     // number of trials
	Eps1     float64 // tolerance # 1; e.g. for strategy # 2 in reliability analyses

	// crossover
	CxNcuts   map[string]int         // crossover number of cuts for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	CxCuts    map[string][]int       // crossover specific cuts for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	CxProbs   map[string]float64     // crossover probabilities for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	CxFuncs   map[string]interface{} // crossover functions for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	CxIntFunc CxIntFunc_t            // crossover function
	CxFltFunc CxFltFunc_t            // crossover function
	CxStrFunc CxStrFunc_t            // crossover function
	CxKeyFunc CxKeyFunc_t            // crossover function
	CxBytFunc CxBytFunc_t            // crossover function
	CxFunFunc CxFunFunc_t            // crossover function

	// mutation
	MtNchanges map[string]int         // mutation number of changes for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	MtProbs    map[string]float64     // mutation probabilities for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	MtExtra    map[string]interface{} // mutation extra parameters for each 'int', 'flt', 'str', 'key', 'byt', 'fun' tag
	MtIntFunc  MtIntFunc_t            // mutation function
	MtFltFunc  MtFltFunc_t            // mutation function
	MtStrFunc  MtStrFunc_t            // mutation function
	MtKeyFunc  MtKeyFunc_t            // mutation function
	MtBytFunc  MtBytFunc_t            // mutation function
	MtFunFunc  MtFunFunc_t            // mutation function
}

// SetDefault sets default parameters
func (o *ConfParams) SetDefault() {

	// initialisation
	o.Seed = 0
	o.Pll = true
	o.Nisl = 4
	o.Ninds = 20
	o.Nbases = 10
	o.Grid = true
	o.Noise = 0.3

	// time control
	o.Tf = 100
	o.Dtout = 10
	o.Dtmig = 40
	o.Dtreg = 30

	// regeneration
	o.RegIni = 3
	o.RegTol = 1e-2
	o.RegBest = false
	o.RegPct = 0.3
	o.RegMmin = 0.1
	o.RegMmax = 10.0
	o.UseStdDev = false

	// selection and reproduction
	o.Pc = 0.8
	o.Pm = 0.01
	o.Elite = true
	o.Rws = false
	o.Rnk = true
	o.RnkSp = 1.2

	// diversity
	o.StatOorSkip = false

	// output
	o.Json = false
	o.DirOut = "/tmp/goga"
	o.FnKey = ""
	o.DoPlot = false
	o.PltTi = 0
	o.PltTf = -1
	o.ShowBases = false

	// auxiliary
	o.Problem = 1
	o.Strategy = 1
	o.Ntrials = 100
	o.Eps1 = 0.1

	// number of cuts in chromossome
	o.CxNcuts = map[string]int{"int": 2, "flt": 2, "str": 2, "key": 2, "byt": 2, "fun": 2}
}

// CalcDerived calculates derived quantities
func (o *ConfParams) CalcDerived() {

	// set probabilities
	pc, pm := o.Pc, o.Pm
	o.CxProbs = map[string]float64{"int": pc, "flt": pc, "str": pc, "key": pc, "byt": pc, "fun": pc}
	o.MtProbs = map[string]float64{"int": pm, "flt": pm, "str": pm, "key": pm, "byt": pm, "fun": pm}
}

// NewConfParams returns a new ConfParams structure, with default values set
func NewConfParams() *ConfParams {
	var o ConfParams
	o.SetDefault()
	o.CalcDerived()
	return &o
}

// ReadConfParams reads configuration parameters from JSON file
func ReadConfParams(filenamepath string) *ConfParams {

	// new params
	var o ConfParams
	o.SetDefault()

	// read file
	b, err := io.ReadFile(filenamepath)
	if err != nil {
		chk.Panic("cannot read parameters file %q", filenamepath)
	}

	// decode
	err = json.Unmarshal(b, &o)
	if err != nil {
		chk.Panic("cannot unmarshal parameters file %q", filenamepath)
	}

	// results
	o.CalcDerived()
	return &o
}
<|endoftext|>


<commit_before>package btelegram

import (
	"regexp"
	"strconv"
	"strings"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	log "github.com/Sirupsen/logrus"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c       *tgbotapi.BotAPI
	Config  *config.Protocol
	Remote  chan config.Message
	Account string
}

var flog *log.Entry
var protocol = "telegram"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
	b := &Btelegram{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Btelegram) Connect() error {
	var err error
	flog.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	flog.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil

}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	flog.Debugf("Receiving %#v", msg)
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	if b.Config.MessageFormat == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	if msg.Extra != nil {
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			var c tgbotapi.Chattable
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				file := tgbotapi.FileBytes{fi.Name, *fi.Data}
				re := regexp.MustCompile(".(jpg|png)$")
				if re.MatchString(fi.Name) {
					c = tgbotapi.NewPhotoUpload(chatid, file)
				} else {
					c = tgbotapi.NewDocumentUpload(chatid, file)
				}
				_, err := b.c.Send(c)
				if err != nil {
					log.Errorf("file upload failed: %#v", err)
				}
				if fi.Comment != "" {
					b.sendMessage(chatid, msg.Username+fi.Comment)
				}
			}
			return "", nil
		}
	}
	return b.sendMessage(chatid, msg.Username+msg.Text)
}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		flog.Debugf("Receiving from telegram: %#v", update.Message)
		var message *tgbotapi.Message
		username := ""
		channel := ""
		text := ""

		fmsg := config.Message{Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}
		if update.EditedChannelPost != nil && !b.Config.EditDisable {
			message = update.EditedChannelPost
			message.Text = message.Text + b.Config.EditSuffix
		}
		// handle groups
		if update.Message != nil {
			message = update.Message
		}
		if update.EditedMessage != nil && !b.Config.EditDisable {
			message = update.EditedMessage
			message.Text = message.Text + b.Config.EditSuffix
		}
		if message.From != nil {
			if b.Config.UseFirstName {
				username = message.From.FirstName
			}
			if username == "" {
				username = message.From.UserName
				if username == "" {
					username = message.From.FirstName
				}
			}
			text = message.Text
			channel = strconv.FormatInt(message.Chat.ID, 10)
		}

		if username == "" {
			username = "unknown"
		}
		if message.Sticker != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}
		if message.Video != nil {
			b.handleDownload(message.Video, &fmsg)
		}
		if message.Photo != nil {
			b.handleDownload(message.Photo, &fmsg)
		}
		if message.Document != nil {
			b.handleDownload(message.Document, &fmsg)
		}

		if message.ForwardFrom != nil {
			text = "Forward from " + message.ForwardFrom.FirstName
			usernameForward := ""
			if b.Config.UseFirstName {
				usernameForward = message.ForwardFrom.FirstName
			}
			if usernameForward == "" {
				usernameForward = message.ForwardFrom.UserName
				if usernameForward == "" {
					usernameForward = message.ForwardFrom.FirstName
				}
			}
			if usernameForward == "" {
				usernameForward = "unknown"
			}
			text = "Forwarded from " + usernameForward + ": " + text
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.Config.UseFirstName {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
		}

		if text != "" || len(fmsg.Extra) > 0 {
			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID), Extra: fmsg.Extra}
			flog.Debugf("Message is %#v", msg)
			b.Remote <- msg
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
	size := 0
	url := ""
	name := ""
	text := ""
	fileid := ""
	switch v := file.(type) {
	case *tgbotapi.Sticker:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		if !strings.HasSuffix(name, ".webp") {
			name = name + ".webp"
		}
		text = " " + url
		fileid = v.FileID
	case *tgbotapi.Video:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
		fileid = v.FileID
	case *[]tgbotapi.PhotoSize:
		photos := *v
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	case *tgbotapi.Document:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
		fileid = v.FileID
	}
	if b.Config.UseInsecureURL {
		msg.Text = text
		return
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	// limit to 1MB for now
	flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
	if size <= 1000000 {
		data, err := helper.DownloadFile(url)
		if err != nil {
			flog.Errorf("download %s failed %#v", url, err)
		} else {
			flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
		}
	}
}

func (b *Btelegram) sendMessage(chatid int64, text string) (string, error) {
	m := tgbotapi.NewMessage(chatid, text)
	if b.Config.MessageFormat == "HTML" {
		m.ParseMode = tgbotapi.ModeHTML
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil
}
<commit_msg>Add support for Audio/Voice files (telegram). Closes #314<commit_after>package btelegram

import (
	"regexp"
	"strconv"
	"strings"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	log "github.com/Sirupsen/logrus"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c       *tgbotapi.BotAPI
	Config  *config.Protocol
	Remote  chan config.Message
	Account string
}

var flog *log.Entry
var protocol = "telegram"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
	b := &Btelegram{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Btelegram) Connect() error {
	var err error
	flog.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	flog.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil

}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	flog.Debugf("Receiving %#v", msg)
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	if b.Config.MessageFormat == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	if msg.Extra != nil {
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			var c tgbotapi.Chattable
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				file := tgbotapi.FileBytes{fi.Name, *fi.Data}
				re := regexp.MustCompile(".(jpg|png)$")
				if re.MatchString(fi.Name) {
					c = tgbotapi.NewPhotoUpload(chatid, file)
				} else {
					c = tgbotapi.NewDocumentUpload(chatid, file)
				}
				_, err := b.c.Send(c)
				if err != nil {
					log.Errorf("file upload failed: %#v", err)
				}
				if fi.Comment != "" {
					b.sendMessage(chatid, msg.Username+fi.Comment)
				}
			}
			return "", nil
		}
	}
	return b.sendMessage(chatid, msg.Username+msg.Text)
}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		flog.Debugf("Receiving from telegram: %#v", update.Message)
		var message *tgbotapi.Message
		username := ""
		channel := ""
		text := ""

		fmsg := config.Message{Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}
		if update.EditedChannelPost != nil && !b.Config.EditDisable {
			message = update.EditedChannelPost
			message.Text = message.Text + b.Config.EditSuffix
		}
		// handle groups
		if update.Message != nil {
			message = update.Message
		}
		if update.EditedMessage != nil && !b.Config.EditDisable {
			message = update.EditedMessage
			message.Text = message.Text + b.Config.EditSuffix
		}
		if message.From != nil {
			if b.Config.UseFirstName {
				username = message.From.FirstName
			}
			if username == "" {
				username = message.From.UserName
				if username == "" {
					username = message.From.FirstName
				}
			}
			text = message.Text
			channel = strconv.FormatInt(message.Chat.ID, 10)
		}

		if username == "" {
			username = "unknown"
		}
		if message.Sticker != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}
		if message.Video != nil {
			b.handleDownload(message.Video, &fmsg)
		}
		if message.Photo != nil {
			b.handleDownload(message.Photo, &fmsg)
		}
		if message.Document != nil {
			b.handleDownload(message.Document, &fmsg)
		}
		if message.Voice != nil {
			b.handleDownload(message.Voice, &fmsg)
		}
		if message.Audio != nil {
			b.handleDownload(message.Audio, &fmsg)
		}

		if message.ForwardFrom != nil {
			text = "Forward from " + message.ForwardFrom.FirstName
			usernameForward := ""
			if b.Config.UseFirstName {
				usernameForward = message.ForwardFrom.FirstName
			}
			if usernameForward == "" {
				usernameForward = message.ForwardFrom.UserName
				if usernameForward == "" {
					usernameForward = message.ForwardFrom.FirstName
				}
			}
			if usernameForward == "" {
				usernameForward = "unknown"
			}
			text = "Forwarded from " + usernameForward + ": " + text
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.Config.UseFirstName {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
		}

		if text != "" || len(fmsg.Extra) > 0 {
			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID), Extra: fmsg.Extra}
			flog.Debugf("Message is %#v", msg)
			b.Remote <- msg
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
	size := 0
	url := ""
	name := ""
	text := ""
	fileid := ""
	switch v := file.(type) {
	case *tgbotapi.Audio:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
		fileid = v.FileID
	case *tgbotapi.Voice:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
		if !strings.HasSuffix(name, ".ogg") {
			name = name + ".ogg"
		}
		fileid = v.FileID
	case *tgbotapi.Sticker:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		if !strings.HasSuffix(name, ".webp") {
			name = name + ".webp"
		}
		text = " " + url
		fileid = v.FileID
	case *tgbotapi.Video:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
		fileid = v.FileID
	case *[]tgbotapi.PhotoSize:
		photos := *v
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	case *tgbotapi.Document:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
		fileid = v.FileID
	}
	if b.Config.UseInsecureURL {
		msg.Text = text
		return
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	// limit to 1MB for now
	flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
	if size <= 1000000 {
		data, err := helper.DownloadFile(url)
		if err != nil {
			flog.Errorf("download %s failed %#v", url, err)
		} else {
			flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
		}
	}
}

func (b *Btelegram) sendMessage(chatid int64, text string) (string, error) {
	m := tgbotapi.NewMessage(chatid, text)
	if b.Config.MessageFormat == "HTML" {
		m.ParseMode = tgbotapi.ModeHTML
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil
}
<|endoftext|>


<commit_before>package socketio

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/googollee/go-engine.io"
	"io"
	"io/ioutil"
	"strconv"
)

const Protocol = 4

type packetType int

const (
	_CONNECT packetType = iota
	_DISCONNECT
	_EVENT
	_ACK
	_ERROR
	_BINARY_EVENT
	_BINARY_ACK
)

func (t packetType) String() string {
	switch t {
	case _CONNECT:
		return "connect"
	case _DISCONNECT:
		return "disconnect"
	case _EVENT:
		return "event"
	case _ACK:
		return "ack"
	case _ERROR:
		return "error"
	case _BINARY_EVENT:
		return "binary_event"
	case _BINARY_ACK:
		return "binary_ack"
	}
	return fmt.Sprintf("unknown(%d)", t)
}

type frameReader interface {
	NextReader() (engineio.MessageType, io.ReadCloser, error)
}

type frameWriter interface {
	NextWriter(engineio.MessageType) (io.WriteCloser, error)
}

type packet struct {
	Type         packetType
	NSP          string
	Id           int
	Data         interface{}
	attachNumber int
}

type encoder struct {
	w   frameWriter
	err error
}

func newEncoder(w frameWriter) *encoder {
	return &encoder{
		w: w,
	}
}

func (e *encoder) Encode(v packet) error {
	attachments := encodeAttachments(v.Data)
	v.attachNumber = len(attachments)
	if v.attachNumber > 0 {
		v.Type += _BINARY_EVENT - _EVENT
	}
	if err := e.encodePacket(v); err != nil {
		return err
	}
	for _, a := range attachments {
		if err := e.writeBinary(a); err != nil {
			return err
		}
	}
	return nil
}

func (e *encoder) encodePacket(v packet) error {
	writer, err := e.w.NextWriter(engineio.MessageText)
	if err != nil {
		return err
	}
	defer writer.Close()

	w := newTrimWriter(writer, "\n")
	wh := newWriterHelper(w)
	wh.Write([]byte{byte(v.Type) + '0'})
	if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
		wh.Write([]byte(fmt.Sprintf("%d-", v.attachNumber)))
	}
	needEnd := false
	if v.NSP != "" {
		wh.Write([]byte(v.NSP))
		needEnd = true
	}
	if v.Id >= 0 {
		f := "%d"
		if needEnd {
			f = ",%d"
			needEnd = false
		}
		wh.Write([]byte(fmt.Sprintf(f, v.Id)))
	}
	if v.Data != nil {
		if needEnd {
			wh.Write([]byte{','})
			needEnd = false
		}
		if wh.Error() != nil {
			return wh.Error()
		}
		encoder := json.NewEncoder(w)
		return encoder.Encode(v.Data)
	}
	return wh.Error()
}

func (e *encoder) writeBinary(r io.Reader) error {
	writer, err := e.w.NextWriter(engineio.MessageBinary)
	if err != nil {
		return err
	}
	defer writer.Close()

	if _, err := io.Copy(writer, r); err != nil {
		return err
	}
	return nil

}

type decoder struct {
	reader        frameReader
	message       string
	current       io.Reader
	currentCloser io.Closer
}

func newDecoder(r frameReader) *decoder {
	return &decoder{
		reader: r,
	}
}

func (d *decoder) Decode(v *packet) error {
	ty, r, err := d.reader.NextReader()
	if err != nil {
		return err
	}
	if d.current != nil {
		d.currentCloser.Close()
		d.current = nil
		d.currentCloser = nil
	}
	defer func() {
		if d.current == nil {
			r.Close()
		}
	}()

	if ty != engineio.MessageText {
		return fmt.Errorf("need text package")
	}
	reader := bufio.NewReader(r)

	v.Id = -1

	t, err := reader.ReadByte()
	if err != nil {
		return err
	}
	v.Type = packetType(t - '0')

	if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
		num, err := reader.ReadBytes('-')
		if err != nil {
			return err
		}
		numLen := len(num)
		if numLen == 0 {
			return fmt.Errorf("invalid packet")
		}
		n, err := strconv.ParseInt(string(num[:numLen-1]), 10, 64)
		if err != nil {
			return fmt.Errorf("invalid packet")
		}
		v.attachNumber = int(n)
	}

	next, err := reader.Peek(1)
	if err == io.EOF {
		return nil
	}
	if err != nil {
		return err
	}
	if len(next) == 0 {
		return fmt.Errorf("invalid packet")
	}

	if next[0] == '/' {
		path, err := reader.ReadBytes(',')
		if err != nil && err != io.EOF {
			return err
		}
		pathLen := len(path)
		if pathLen == 0 {
			return fmt.Errorf("invalid packet")
		}
		if err == nil {
			path = path[:pathLen-1]
		}
		v.NSP = string(path)
		if err == io.EOF {
			return nil
		}
	}

	id := bytes.NewBuffer(nil)
	finish := false
	for {
		next, err := reader.Peek(1)
		if err == io.EOF {
			finish = true
			break
		}
		if err != nil {
			return err
		}
		if '0' <= next[0] && next[0] <= '9' {
			if err := id.WriteByte(next[0]); err != nil {
				return err
			}
		} else {
			break
		}
		reader.ReadByte()
	}
	if id.Len() > 0 {
		id, err := strconv.ParseInt(id.String(), 10, 64)
		if err != nil {
			return err
		}
		v.Id = int(id)
	}
	if finish {
		return nil
	}

	switch v.Type {
	case _EVENT:
		fallthrough
	case _BINARY_EVENT:
		msgReader, err := newMessageReader(reader)
		if err != nil {
			return err
		}
		d.message = msgReader.Message()
		d.current = msgReader
		d.currentCloser = r
	case _ACK:
		fallthrough
	case _BINARY_ACK:
		d.current = reader
		d.currentCloser = r
	}
	return nil
}

func (d *decoder) Message() string {
	return d.message
}

func (d *decoder) DecodeData(v *packet) error {
	if d.current == nil {
		return nil
	}
	defer func() {
		d.currentCloser.Close()
		d.current = nil
		d.currentCloser = nil
	}()
	decoder := json.NewDecoder(d.current)
	if err := decoder.Decode(v.Data); err != nil {
		return err
	}
	if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
		binary, err := d.decodeBinary(v.attachNumber)
		if err != nil {
			return err
		}
		if err := decodeAttachments(v.Data, binary); err != nil {
			return err
		}
		v.Type -= _BINARY_EVENT - _EVENT
	}
	return nil
}

func (d *decoder) decodeBinary(num int) ([][]byte, error) {
	ret := make([][]byte, num)
	for i := 0; i < num; i++ {
		t, r, err := d.reader.NextReader()
		if err != nil {
			return nil, err
		}
		if t == engineio.MessageText {
			return nil, fmt.Errorf("need binary")
		}
		b, err := ioutil.ReadAll(r)
		if err != nil {
			return nil, err
		}
		ret[i] = b
	}
	return ret, nil
}
<commit_msg>fixes #48 close the open chanel before waiting for nextReader<commit_after>package socketio

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/googollee/go-engine.io"
	"io"
	"io/ioutil"
	"strconv"
)

const Protocol = 4

type packetType int

const (
	_CONNECT packetType = iota
	_DISCONNECT
	_EVENT
	_ACK
	_ERROR
	_BINARY_EVENT
	_BINARY_ACK
)

func (t packetType) String() string {
	switch t {
	case _CONNECT:
		return "connect"
	case _DISCONNECT:
		return "disconnect"
	case _EVENT:
		return "event"
	case _ACK:
		return "ack"
	case _ERROR:
		return "error"
	case _BINARY_EVENT:
		return "binary_event"
	case _BINARY_ACK:
		return "binary_ack"
	}
	return fmt.Sprintf("unknown(%d)", t)
}

type frameReader interface {
	NextReader() (engineio.MessageType, io.ReadCloser, error)
}

type frameWriter interface {
	NextWriter(engineio.MessageType) (io.WriteCloser, error)
}

type packet struct {
	Type         packetType
	NSP          string
	Id           int
	Data         interface{}
	attachNumber int
}

type encoder struct {
	w   frameWriter
	err error
}

func newEncoder(w frameWriter) *encoder {
	return &encoder{
		w: w,
	}
}

func (e *encoder) Encode(v packet) error {
	attachments := encodeAttachments(v.Data)
	v.attachNumber = len(attachments)
	if v.attachNumber > 0 {
		v.Type += _BINARY_EVENT - _EVENT
	}
	if err := e.encodePacket(v); err != nil {
		return err
	}
	for _, a := range attachments {
		if err := e.writeBinary(a); err != nil {
			return err
		}
	}
	return nil
}

func (e *encoder) encodePacket(v packet) error {
	writer, err := e.w.NextWriter(engineio.MessageText)
	if err != nil {
		return err
	}
	defer writer.Close()

	w := newTrimWriter(writer, "\n")
	wh := newWriterHelper(w)
	wh.Write([]byte{byte(v.Type) + '0'})
	if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
		wh.Write([]byte(fmt.Sprintf("%d-", v.attachNumber)))
	}
	needEnd := false
	if v.NSP != "" {
		wh.Write([]byte(v.NSP))
		needEnd = true
	}
	if v.Id >= 0 {
		f := "%d"
		if needEnd {
			f = ",%d"
			needEnd = false
		}
		wh.Write([]byte(fmt.Sprintf(f, v.Id)))
	}
	if v.Data != nil {
		if needEnd {
			wh.Write([]byte{','})
			needEnd = false
		}
		if wh.Error() != nil {
			return wh.Error()
		}
		encoder := json.NewEncoder(w)
		return encoder.Encode(v.Data)
	}
	return wh.Error()
}

func (e *encoder) writeBinary(r io.Reader) error {
	writer, err := e.w.NextWriter(engineio.MessageBinary)
	if err != nil {
		return err
	}
	defer writer.Close()

	if _, err := io.Copy(writer, r); err != nil {
		return err
	}
	return nil

}

type decoder struct {
	reader        frameReader
	message       string
	current       io.Reader
	currentCloser io.Closer
}

func newDecoder(r frameReader) *decoder {
	return &decoder{
		reader: r,
	}
}

func (d *decoder) Decode(v *packet) error {
	ty, r, err := d.reader.NextReader()
	if err != nil {
		return err
	}
	if d.current != nil {
		d.currentCloser.Close()
		d.current = nil
		d.currentCloser = nil
	}
	defer func() {
		if d.current == nil {
			r.Close()
		}
	}()

	if ty != engineio.MessageText {
		return fmt.Errorf("need text package")
	}
	reader := bufio.NewReader(r)

	v.Id = -1

	t, err := reader.ReadByte()
	if err != nil {
		return err
	}
	v.Type = packetType(t - '0')

	if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
		num, err := reader.ReadBytes('-')
		if err != nil {
			return err
		}
		numLen := len(num)
		if numLen == 0 {
			return fmt.Errorf("invalid packet")
		}
		n, err := strconv.ParseInt(string(num[:numLen-1]), 10, 64)
		if err != nil {
			return fmt.Errorf("invalid packet")
		}
		v.attachNumber = int(n)
	}

	next, err := reader.Peek(1)
	if err == io.EOF {
		return nil
	}
	if err != nil {
		return err
	}
	if len(next) == 0 {
		return fmt.Errorf("invalid packet")
	}

	if next[0] == '/' {
		path, err := reader.ReadBytes(',')
		if err != nil && err != io.EOF {
			return err
		}
		pathLen := len(path)
		if pathLen == 0 {
			return fmt.Errorf("invalid packet")
		}
		if err == nil {
			path = path[:pathLen-1]
		}
		v.NSP = string(path)
		if err == io.EOF {
			return nil
		}
	}

	id := bytes.NewBuffer(nil)
	finish := false
	for {
		next, err := reader.Peek(1)
		if err == io.EOF {
			finish = true
			break
		}
		if err != nil {
			return err
		}
		if '0' <= next[0] && next[0] <= '9' {
			if err := id.WriteByte(next[0]); err != nil {
				return err
			}
		} else {
			break
		}
		reader.ReadByte()
	}
	if id.Len() > 0 {
		id, err := strconv.ParseInt(id.String(), 10, 64)
		if err != nil {
			return err
		}
		v.Id = int(id)
	}
	if finish {
		return nil
	}

	switch v.Type {
	case _EVENT:
		fallthrough
	case _BINARY_EVENT:
		msgReader, err := newMessageReader(reader)
		if err != nil {
			return err
		}
		d.message = msgReader.Message()
		d.current = msgReader
		d.currentCloser = r
	case _ACK:
		fallthrough
	case _BINARY_ACK:
		d.current = reader
		d.currentCloser = r
	}
	return nil
}

func (d *decoder) Message() string {
	return d.message
}

func (d *decoder) DecodeData(v *packet) error {
	if d.current == nil {
		return nil
	}
	defer func() {
		d.currentCloser.Close()
		d.current = nil
		d.currentCloser = nil
	}()
	decoder := json.NewDecoder(d.current)
	if err := decoder.Decode(v.Data); err != nil {
		return err
	}
	if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
		binary, err := d.decodeBinary(v.attachNumber)
		if err != nil {
			return err
		}
		if err := decodeAttachments(v.Data, binary); err != nil {
			return err
		}
		v.Type -= _BINARY_EVENT - _EVENT
	}
	return nil
}

func (d *decoder) decodeBinary(num int) ([][]byte, error) {
	ret := make([][]byte, num)
	for i := 0; i < num; i++ {
		d.currentCloser.Close()
		t, r, err := d.reader.NextReader()
		if err != nil {
			return nil, err
		}
		d.currentCloser = r
		if t == engineio.MessageText {
			return nil, fmt.Errorf("need binary")
		}
		b, err := ioutil.ReadAll(r)
		if err != nil {
			return nil, err
		}
		ret[i] = b
	}
	return ret, nil
}
<|endoftext|>


<commit_before>package btelegram

import (
	"regexp"
	"strconv"
	"strings"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c *tgbotapi.BotAPI
	*bridge.Config
	avatarMap map[string]string // keep cache of userid and avatar sha
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
}

func (b *Btelegram) Connect() error {
	var err error
	b.Log.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.GetString("Token"))
	if err != nil {
		b.Log.Debugf("%#v", err)
		return err
	}
	u := tgbotapi.NewUpdate(0)
	u.Timeout = 60
	updates, err := b.c.GetUpdatesChan(u)
	if err != nil {
		b.Log.Debugf("%#v", err)
		return err
	}
	b.Log.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil
}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Receiving %#v", msg)

	// get the chatid
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	// map the file SHA to our user (caches the avatar)
	if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
		return b.cacheAvatar(&msg)
	}

	if b.GetString("MessageFormat") == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	// Delete message
	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// Upload a file if it exists
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.sendMessage(chatid, rmsg.Username, rmsg.Text)
		}
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			b.handleUploadFile(&msg, chatid)
		}
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		if b.GetString("MessageFormat") == "HTML" {
			b.Log.Debug("Using mode HTML")
			m.ParseMode = tgbotapi.ModeHTML
		}
		if b.GetString("MessageFormat") == "Markdown" {
			b.Log.Debug("Using mode markdown")
			m.ParseMode = tgbotapi.ModeMarkdown
		}
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	// Post normal message
	return b.sendMessage(chatid, msg.Username, msg.Text)
}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		b.Log.Debugf("== Receiving event: %#v", update.Message)

		if update.Message == nil && update.ChannelPost == nil {
			b.Log.Error("Getting nil messages, this shouldn't happen.")
			continue
		}

		var message *tgbotapi.Message

		rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}

		// edited channel message
		if update.EditedChannelPost != nil && !b.GetBool("EditDisable") {
			message = update.EditedChannelPost
			rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
		}

		// handle groups
		if update.Message != nil {
			message = update.Message
		}

		// edited group message
		if update.EditedMessage != nil && !b.GetBool("EditDisable") {
			message = update.EditedMessage
			rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
		}

		// set the ID's from the channel or group message
		rmsg.ID = strconv.Itoa(message.MessageID)
		rmsg.UserID = strconv.Itoa(message.From.ID)
		rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)

		// handle username
		if message.From != nil {
			if b.GetBool("UseFirstName") {
				rmsg.Username = message.From.FirstName
			}
			if rmsg.Username == "" {
				rmsg.Username = message.From.UserName
				if rmsg.Username == "" {
					rmsg.Username = message.From.FirstName
				}
			}
			rmsg.Text += message.Text
			// only download avatars if we have a place to upload them (configured mediaserver)
			if b.General.MediaServerUpload != "" {
				b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
			}
		}

		// if we really didn't find a username, set it to unknown
		if rmsg.Username == "" {
			rmsg.Username = "unknown"
		}

		// handle any downloads
		err := b.handleDownload(message, &rmsg)
		if err != nil {
			b.Log.Errorf("download failed: %s", err)
		}

		// handle forwarded messages
		if message.ForwardFrom != nil {
			usernameForward := ""
			if b.GetBool("UseFirstName") {
				usernameForward = message.ForwardFrom.FirstName
			}
			if usernameForward == "" {
				usernameForward = message.ForwardFrom.UserName
				if usernameForward == "" {
					usernameForward = message.ForwardFrom.FirstName
				}
			}
			if usernameForward == "" {
				usernameForward = "unknown"
			}
			rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.GetBool("UseFirstName") {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			if !b.GetBool("QuoteDisable") {
				rmsg.Text = rmsg.Text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
			}
		}

		if rmsg.Text != "" || len(rmsg.Extra) > 0 {
			rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
			rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)

			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
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

// handleDownloadAvatar downloads the avatar of userid from channel
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
// logs an error message if it fails
func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
	rmsg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: strconv.Itoa(userid), Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})}
	if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok {
		photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
		if err != nil {
			b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
		}

		if len(photos.Photos) > 0 {
			photo := photos.Photos[0][0]
			url := b.getFileDirectURL(photo.FileID)
			name := strconv.Itoa(userid) + ".png"
			b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)

			err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
			if err != nil {
				b.Log.Error(err)
				return
			}
			data, err := helper.DownloadFile(url)
			if err != nil {
				b.Log.Errorf("download %s failed %#v", url, err)
				return
			}
			helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
			b.Remote <- rmsg
		}
	}
}

// handleDownloadFile handles file download
func (b *Btelegram) handleDownload(message *tgbotapi.Message, rmsg *config.Message) error {
	size := 0
	var url, name, text string

	if message.Sticker != nil {
		v := message.Sticker
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		if !strings.HasSuffix(name, ".webp") {
			name = name + ".webp"
		}
		text = " " + url
	}
	if message.Video != nil {
		v := message.Video
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	}
	if message.Photo != nil {
		photos := *message.Photo
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	}
	if message.Document != nil {
		v := message.Document
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
	}
	if message.Voice != nil {
		v := message.Voice
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
		if !strings.HasSuffix(name, ".ogg") {
			name = name + ".ogg"
		}
	}
	if message.Audio != nil {
		v := message.Audio
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	}
	// if name is empty we didn't match a thing to download
	if name == "" {
		return nil
	}
	// use the URL instead of native upload
	if b.GetBool("UseInsecureURL") {
		b.Log.Debugf("Setting message text to :%s", text)
		rmsg.Text = rmsg.Text + text
		return nil
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General)
	if err != nil {
		return err
	}
	data, err := helper.DownloadFile(url)
	if err != nil {
		return err
	}
	helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
	return nil
}

// handleUploadFile handles native upload of files
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) (string, error) {
	var c tgbotapi.Chattable
	for _, f := range msg.Extra["file"] {
		fi := f.(config.FileInfo)
		file := tgbotapi.FileBytes{fi.Name, *fi.Data}
		re := regexp.MustCompile(".(jpg|png)$")
		if re.MatchString(fi.Name) {
			c = tgbotapi.NewPhotoUpload(chatid, file)
		} else {
			c = tgbotapi.NewDocumentUpload(chatid, file)
		}
		_, err := b.c.Send(c)
		if err != nil {
			b.Log.Errorf("file upload failed: %#v", err)
		}
		if fi.Comment != "" {
			b.sendMessage(chatid, msg.Username, fi.Comment)
		}
	}
	return "", nil
}

func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) {
	m := tgbotapi.NewMessage(chatid, "")
	m.Text = username + text
	if b.GetString("MessageFormat") == "HTML" {
		b.Log.Debug("Using mode HTML")
		m.Text = username + text
		m.ParseMode = tgbotapi.ModeHTML
	}
	if b.GetString("MessageFormat") == "Markdown" {
		b.Log.Debug("Using mode markdown")
		m.ParseMode = tgbotapi.ModeMarkdown
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil
}

func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
	fi := msg.Extra["file"][0].(config.FileInfo)
	/* if we have a sha we have successfully uploaded the file to the media server,
	so we can now cache the sha */
	if fi.SHA != "" {
		b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
		b.avatarMap[msg.UserID] = fi.SHA
	}
	return "", nil
}
<commit_msg>Fix panic (telegram). Closes #410<commit_after>package btelegram

import (
	"regexp"
	"strconv"
	"strings"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c *tgbotapi.BotAPI
	*bridge.Config
	avatarMap map[string]string // keep cache of userid and avatar sha
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
}

func (b *Btelegram) Connect() error {
	var err error
	b.Log.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.GetString("Token"))
	if err != nil {
		b.Log.Debugf("%#v", err)
		return err
	}
	u := tgbotapi.NewUpdate(0)
	u.Timeout = 60
	updates, err := b.c.GetUpdatesChan(u)
	if err != nil {
		b.Log.Debugf("%#v", err)
		return err
	}
	b.Log.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil
}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Receiving %#v", msg)

	// get the chatid
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	// map the file SHA to our user (caches the avatar)
	if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
		return b.cacheAvatar(&msg)
	}

	if b.GetString("MessageFormat") == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	// Delete message
	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// Upload a file if it exists
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.sendMessage(chatid, rmsg.Username, rmsg.Text)
		}
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			b.handleUploadFile(&msg, chatid)
		}
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		if b.GetString("MessageFormat") == "HTML" {
			b.Log.Debug("Using mode HTML")
			m.ParseMode = tgbotapi.ModeHTML
		}
		if b.GetString("MessageFormat") == "Markdown" {
			b.Log.Debug("Using mode markdown")
			m.ParseMode = tgbotapi.ModeMarkdown
		}
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	// Post normal message
	return b.sendMessage(chatid, msg.Username, msg.Text)
}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		b.Log.Debugf("== Receiving event: %#v", update.Message)

		if update.Message == nil && update.ChannelPost == nil {
			b.Log.Error("Getting nil messages, this shouldn't happen.")
			continue
		}

		var message *tgbotapi.Message

		rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}

		// edited channel message
		if update.EditedChannelPost != nil && !b.GetBool("EditDisable") {
			message = update.EditedChannelPost
			rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
		}

		// handle groups
		if update.Message != nil {
			message = update.Message
		}

		// edited group message
		if update.EditedMessage != nil && !b.GetBool("EditDisable") {
			message = update.EditedMessage
			rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
		}

		// set the ID's from the channel or group message
		rmsg.ID = strconv.Itoa(message.MessageID)
		rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)

		// handle username
		if message.From != nil {
			rmsg.UserID = strconv.Itoa(message.From.ID)
			if b.GetBool("UseFirstName") {
				rmsg.Username = message.From.FirstName
			}
			if rmsg.Username == "" {
				rmsg.Username = message.From.UserName
				if rmsg.Username == "" {
					rmsg.Username = message.From.FirstName
				}
			}
			rmsg.Text += message.Text
			// only download avatars if we have a place to upload them (configured mediaserver)
			if b.General.MediaServerUpload != "" {
				b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
			}
		}

		// if we really didn't find a username, set it to unknown
		if rmsg.Username == "" {
			rmsg.Username = "unknown"
		}

		// handle any downloads
		err := b.handleDownload(message, &rmsg)
		if err != nil {
			b.Log.Errorf("download failed: %s", err)
		}

		// handle forwarded messages
		if message.ForwardFrom != nil {
			usernameForward := ""
			if b.GetBool("UseFirstName") {
				usernameForward = message.ForwardFrom.FirstName
			}
			if usernameForward == "" {
				usernameForward = message.ForwardFrom.UserName
				if usernameForward == "" {
					usernameForward = message.ForwardFrom.FirstName
				}
			}
			if usernameForward == "" {
				usernameForward = "unknown"
			}
			rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.GetBool("UseFirstName") {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			if !b.GetBool("QuoteDisable") {
				rmsg.Text = rmsg.Text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
			}
		}

		if rmsg.Text != "" || len(rmsg.Extra) > 0 {
			rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
			rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)

			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
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

// handleDownloadAvatar downloads the avatar of userid from channel
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
// logs an error message if it fails
func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
	rmsg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: strconv.Itoa(userid), Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})}
	if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok {
		photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
		if err != nil {
			b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
		}

		if len(photos.Photos) > 0 {
			photo := photos.Photos[0][0]
			url := b.getFileDirectURL(photo.FileID)
			name := strconv.Itoa(userid) + ".png"
			b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)

			err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
			if err != nil {
				b.Log.Error(err)
				return
			}
			data, err := helper.DownloadFile(url)
			if err != nil {
				b.Log.Errorf("download %s failed %#v", url, err)
				return
			}
			helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
			b.Remote <- rmsg
		}
	}
}

// handleDownloadFile handles file download
func (b *Btelegram) handleDownload(message *tgbotapi.Message, rmsg *config.Message) error {
	size := 0
	var url, name, text string

	if message.Sticker != nil {
		v := message.Sticker
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		if !strings.HasSuffix(name, ".webp") {
			name = name + ".webp"
		}
		text = " " + url
	}
	if message.Video != nil {
		v := message.Video
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	}
	if message.Photo != nil {
		photos := *message.Photo
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	}
	if message.Document != nil {
		v := message.Document
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
	}
	if message.Voice != nil {
		v := message.Voice
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
		if !strings.HasSuffix(name, ".ogg") {
			name = name + ".ogg"
		}
	}
	if message.Audio != nil {
		v := message.Audio
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		urlPart := strings.Split(url, "/")
		name = urlPart[len(urlPart)-1]
		text = " " + url
	}
	// if name is empty we didn't match a thing to download
	if name == "" {
		return nil
	}
	// use the URL instead of native upload
	if b.GetBool("UseInsecureURL") {
		b.Log.Debugf("Setting message text to :%s", text)
		rmsg.Text = rmsg.Text + text
		return nil
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General)
	if err != nil {
		return err
	}
	data, err := helper.DownloadFile(url)
	if err != nil {
		return err
	}
	helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
	return nil
}

// handleUploadFile handles native upload of files
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) (string, error) {
	var c tgbotapi.Chattable
	for _, f := range msg.Extra["file"] {
		fi := f.(config.FileInfo)
		file := tgbotapi.FileBytes{fi.Name, *fi.Data}
		re := regexp.MustCompile(".(jpg|png)$")
		if re.MatchString(fi.Name) {
			c = tgbotapi.NewPhotoUpload(chatid, file)
		} else {
			c = tgbotapi.NewDocumentUpload(chatid, file)
		}
		_, err := b.c.Send(c)
		if err != nil {
			b.Log.Errorf("file upload failed: %#v", err)
		}
		if fi.Comment != "" {
			b.sendMessage(chatid, msg.Username, fi.Comment)
		}
	}
	return "", nil
}

func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) {
	m := tgbotapi.NewMessage(chatid, "")
	m.Text = username + text
	if b.GetString("MessageFormat") == "HTML" {
		b.Log.Debug("Using mode HTML")
		m.Text = username + text
		m.ParseMode = tgbotapi.ModeHTML
	}
	if b.GetString("MessageFormat") == "Markdown" {
		b.Log.Debug("Using mode markdown")
		m.ParseMode = tgbotapi.ModeMarkdown
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil
}

func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
	fi := msg.Extra["file"][0].(config.FileInfo)
	/* if we have a sha we have successfully uploaded the file to the media server,
	so we can now cache the sha */
	if fi.SHA != "" {
		b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
		b.avatarMap[msg.UserID] = fi.SHA
	}
	return "", nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"time"
)

const (
	DefaultBasename = "frame"  // Default frame basename
	FramesDirectory = "frames" // FramesDirectory is the directory containing all animation frames
)

// Parser is a script parser
type Parser struct {
	drawer   *Drawer   // image drawer
	lexer    *Lexer    // lexer
	backup   []Token   // token backup
	commands []Command // list of commands

	isAnimated   bool                 // whether or not to parse as an animation
	frames       int                  // number of frames in the animation
	basename     string               // animation basename
	formatString string               // format string for each frame of the animation
	symbols      *SymbolTable         // symbol table
	knobs        map[string][]float64 // knob table
}

// NewParser returns a new parser
func NewParser() *Parser {
	return &Parser{
		drawer:     NewDrawer(DefaultHeight, DefaultWidth),
		backup:     make([]Token, 0, 50),
		isAnimated: false,
		symbols:    NewSymbolTable(),
		knobs:      make(map[string][]float64),
	}
}

// ParseInput parses a file for commands and executes them
func (p *Parser) ParseInput() error {
	scanner := bufio.NewScanner(os.Stdin)
	var input bytes.Buffer
	for scanner.Scan() {
		input.Write(scanner.Bytes())
		input.WriteRune('\n')
	}
	err := p.ParseString(input.String())
	return err
}

// ParseFile parses a file for commands and executes them
func (p *Parser) ParseFile(filename string) error {
	input, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = p.ParseString(string(input))
	return err
}

// ParseString parses a string for commands and executes them
func (p *Parser) ParseString(input string) error {
	p.lexer = Lex(input)
	commands, err := p.parse()
	if err == nil {
		p.commands = commands
		err = p.process()
	}
	return err
}

func (p *Parser) parse() ([]Command, error) {
	commands := make([]Command, 0, 50)
	for {
		t := p.nextToken()
		switch t.tt {
		case tError:
			return nil, errors.New(t.value)
		case tEOF:
			if p.isAnimated {
				if p.basename == "" {
					fmt.Fprintf(os.Stderr, "No basename provided: using default basename '%s'\n", DefaultBasename)
					p.basename = DefaultBasename
					p.formatString = fmt.Sprintf("%s/%s-%%0%dd.png", FramesDirectory, p.basename, len(strconv.Itoa(p.frames)))
				}
			}
			return commands, nil
		case tIdent:
			var command Command
			switch LookupIdent(t.value) {
			case MOVE:
				c := MoveCommand{}
				c.args = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				if p.peek().tt == tString {
					c.knob = p.nextToken().value
					p.symbols.Set(c.knob, 0)
				}
				command = c
			case SCALE:
				c := ScaleCommand{}
				c.args = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				if p.peek().tt == tString {
					c.knob = p.nextToken().value
					p.symbols.Set(c.knob, 0)
				}
				command = c
			case ROTATE:
				c := RotateCommand{}
				c.axis = p.nextIdent()
				c.degrees = p.nextFloat()
				if p.peek().tt == tString {
					c.knob = p.nextToken().value
					p.symbols.Set(c.knob, 0)
				}
				command = c
			case LINE:
				c := LineCommand{}
				if p.peek().tt == tString {
					c.constants = p.nextToken().value
				}
				c.p1 = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				if p.peek().tt == tString {
					c.cs = p.nextToken().value
				}
				c.p2 = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				if p.peek().tt == tString {
					c.cs2 = p.nextToken().value
				}
				command = c
			case SPHERE:
				c := SphereCommand{}
				if p.peek().tt == tString {
					c.constants = p.nextToken().value
				}
				c.center = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				c.radius = p.nextFloat()
				if p.peek().tt == tString {
					c.cs = p.nextToken().value
				}
				command = c
			case TORUS:
				c := TorusCommand{}
				if p.peek().tt == tString {
					c.constants = p.nextToken().value
				}
				c.center = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				c.r1 = p.nextFloat()
				c.r2 = p.nextFloat()
				if p.peek().tt == tString {
					c.cs = p.nextToken().value
				}
				command = c
			case BOX:
				c := BoxCommand{}
				if p.peek().tt == tString {
					c.constants = p.nextToken().value
				}
				c.p1 = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				c.width = p.nextFloat()
				c.height = p.nextFloat()
				c.depth = p.nextFloat()
				if p.peek().tt == tString {
					c.cs = p.nextToken().value
				}
				command = c
			case POP:
				command = PopCommand{}
			case PUSH:
				command = PushCommand{}
			case SAVE:
				command = SaveCommand{
					filename: p.nextString(),
				}
			case DISPLAY:
				command = DisplayCommand{}
			case VARY:
				if p.frames == 0 {
					return nil, errors.New("number of frames is not set")
				}
				name := p.nextString()
				knob, found := p.knobs[name]
				if !found {
					knob = make([]float64, p.frames)
				}
				startFrame := p.nextInt()
				if startFrame < 0 || startFrame >= p.frames {
					return nil, fmt.Errorf("invalid start frame %d for knob %s", name, startFrame)
				}
				endFrame := p.nextInt()
				if endFrame < 0 || endFrame >= p.frames || endFrame < startFrame {
					return nil, fmt.Errorf("invalid end frame %d for knob %s", name, endFrame)
				}
				startValue := p.nextFloat()
				endValue := p.nextFloat()
				length := endFrame - startFrame
				delta := (endValue - startValue) / float64(length+1)
				for frame := startFrame; frame <= endFrame; frame++ {
					knob[frame] = startValue
					startValue += delta
				}
				p.knobs[name] = knob
				p.isAnimated = true
			case BASENAME:
				if p.basename != "" {
					fmt.Fprintln(os.Stderr, "Setting the basename multiple times")
				}
				p.basename = p.nextString()
				p.formatString = fmt.Sprintf("%s/%s-%%0%dd.png", FramesDirectory, p.basename, len(strconv.Itoa(p.frames)))
				p.isAnimated = true
			case FRAMES:
				if p.frames != 0 {
					fmt.Fprintln(os.Stderr, "Setting the number of frames multiple times")
				}
				p.frames = p.nextInt()
				if p.frames <= 0 {
					return nil, errors.New("number of frames must be greater than zero")
				}
				p.isAnimated = true
			case SET:
				c := SetCommand{
					name:  p.nextString(),
					value: p.nextFloat(),
				}
				command = c
			case SETALL:
				c := SetAllCommand{
					value: p.nextFloat(),
				}
				command = c
			}
			if command != nil {
				commands = append(commands, command)
			}
			if err := p.expect(tNewline); err != nil {
				return nil, fmt.Errorf("unexpected %v at end of statement", p.peek().tt)
			}
		case tString:
			return nil, fmt.Errorf("unrecognized identifier: \"%s\"", t.value)
		}
	}
	return commands, nil
}

func (p *Parser) process() error {
	if p.isAnimated {
		os.RemoveAll(FramesDirectory)
		os.Mkdir(FramesDirectory, 0755)
	} else {
		p.frames = 1
	}
	var err error
	for frame := 0; frame < p.frames; frame++ {
		for knob := range p.knobs {
			p.symbols.Set(knob, p.knobs[knob][frame])
		}
		if p.isAnimated {
			fmt.Printf("Rendering frame %d/%d", frame+1, p.frames)
		}
		start := time.Now()
		for _, command := range p.commands {
			switch command.(type) {
			case MoveCommand:
				c := command.(MoveCommand)
				x, y, z := c.args[0], c.args[1], c.args[2]
				if c.knob != "" {
					if knob, err := p.getSymbolValue(c.knob); err == nil {
						x *= knob
						y *= knob
						z *= knob
					} else {
						return err
					}
				}
				err = p.drawer.Move(x, y, z)
			case ScaleCommand:
				c := command.(ScaleCommand)
				x, y, z := c.args[0], c.args[1], c.args[2]
				if c.knob != "" {
					if knob, err := p.getSymbolValue(c.knob); err == nil {
						x *= knob
						y *= knob
						z *= knob
					} else {
						return err
					}
				}
				err = p.drawer.Scale(x, y, z)
			case RotateCommand:
				c := command.(RotateCommand)
				degrees := c.degrees
				if c.knob != "" {
					if knob, err := p.getSymbolValue(c.knob); err == nil {
						degrees *= knob
					} else {
						return err
					}
				}
				err = p.drawer.Rotate(c.axis, degrees)
			case LineCommand:
				c := command.(LineCommand)
				err = p.drawer.Line(c.p1[0], c.p1[1], c.p1[2], c.p2[0], c.p2[1], c.p2[2])
			case SphereCommand:
				c := command.(SphereCommand)
				err = p.drawer.Sphere(c.center[0], c.center[1], c.center[2], c.radius)
			case TorusCommand:
				c := command.(TorusCommand)
				err = p.drawer.Torus(c.center[0], c.center[1], c.center[2], c.r1, c.r2)
			case BoxCommand:
				c := command.(BoxCommand)
				err = p.drawer.Box(c.p1[0], c.p1[1], c.p1[2], c.width, c.height, c.depth)
			case PopCommand:
				p.drawer.Pop()
			case PushCommand:
				p.drawer.Push()
			case SaveCommand:
				c := command.(SaveCommand)
				err = p.drawer.Save(c.filename)
			case DisplayCommand:
				err = p.drawer.Display()
			case SetCommand:
				c := command.(SetCommand)
				p.symbols.Set(c.name, c.value)
			case SetAllCommand:
				c := command.(SetAllCommand)
				for key := range p.symbols.table {
					p.symbols.Set(key, c.value)
				}
			}
			if err != nil {
				return err
			}
		}
		elapsed := time.Since(start)
		if p.isAnimated {
			fmt.Printf(" - %s\n", elapsed)
			err = p.drawer.Save(fmt.Sprintf(p.formatString, frame))
			if err != nil {
				return err
			}
			p.drawer.Reset()
		}
	}
	if p.isAnimated {
		fmt.Println("Making animation...")
		err = MakeAnimation(p.basename)
	}
	return err
}

func (p *Parser) getSymbolValue(symbolName string) (float64, error) {
	if value, found := p.symbols.Get(symbolName); found {
		return value, nil
	} else {
		return 0, fmt.Errorf("undefined symbol '%s'", symbolName)
	}
}

// nextToken returns the nextToken token from the lexer
func (p *Parser) nextToken() Token {
	lenBackup := len(p.backup)
	// Use the token from backup if it exists
	if lenBackup > 0 {
		token := p.backup[lenBackup-1]
		p.backup = p.backup[:lenBackup-1]
		return token
	}
	token := p.lexer.NextToken()
	return token
}

// nextInt returns the next integer token from the lexer
// Panics if the next token is not an integer
func (p *Parser) nextInt() int {
	if p.expect(tInt) != nil {
		panic(fmt.Errorf("expected %v, got %v", tInt, p.peek().tt))
	}
	v, _ := strconv.Atoi(p.nextToken().value)
	return v
}

// nextFloat returns the next token from the lexer as a float.
// Panics if the next token is not a float or integer
func (p *Parser) nextFloat() float64 {
	if p.expect(tInt) != nil && p.expect(tFloat) != nil {
		panic(fmt.Errorf("expected %v, got %v", tFloat, p.peek().tt))
	}
	v, _ := strconv.ParseFloat(p.nextToken().value, 64)
	return v
}

// nextString returns the next token from the lexer.
// Panics if the next token is not a string
func (p *Parser) nextString() string {
	if p.expect(tString) != nil {
		panic(fmt.Errorf("expected %v, got %v", tString, p.peek().tt))
	}
	return p.nextToken().value
}

// nextIdent returns the next identifier from the lexer as a string.
// Panics if the next token is not an identifier
func (p *Parser) nextIdent() string {
	if p.expect(tIdent) != nil {
		panic(fmt.Errorf("expected %v, got %v", tIdent, p.peek().tt))
	}
	return p.nextToken().value
}

// unread adds the token to the list of backup tokens.
// Since channels cannot be "unread", we use a list to backup these tokens
func (p *Parser) unread(token Token) {
	p.backup = append(p.backup, token)
}

// peek returns the next token without consuming it
func (p *Parser) peek() Token {
	token := p.next()
	p.unread(token)
	return token
}

// expect returns an error if the next token is not a certain type
func (p *Parser) expect(tt TokenType) error {
	other := p.peek().tt
	if other != tt {
		return fmt.Errorf("expected %v, got %v", tt, other)
	}
	return nil
}
<commit_msg>Simplify next() functions<commit_after>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"time"
)

const (
	DefaultBasename = "frame"  // Default frame basename
	FramesDirectory = "frames" // FramesDirectory is the directory containing all animation frames
)

// Parser is a script parser
type Parser struct {
	drawer   *Drawer   // image drawer
	lexer    *Lexer    // lexer
	backup   []Token   // token backup
	commands []Command // list of commands

	isAnimated   bool                 // whether or not to parse as an animation
	frames       int                  // number of frames in the animation
	basename     string               // animation basename
	formatString string               // format string for each frame of the animation
	symbols      *SymbolTable         // symbol table
	knobs        map[string][]float64 // knob table
}

// NewParser returns a new parser
func NewParser() *Parser {
	return &Parser{
		drawer:     NewDrawer(DefaultHeight, DefaultWidth),
		backup:     make([]Token, 0, 50),
		isAnimated: false,
		symbols:    NewSymbolTable(),
		knobs:      make(map[string][]float64),
	}
}

// ParseInput parses a file for commands and executes them
func (p *Parser) ParseInput() error {
	scanner := bufio.NewScanner(os.Stdin)
	var input bytes.Buffer
	for scanner.Scan() {
		input.Write(scanner.Bytes())
		input.WriteRune('\n')
	}
	err := p.ParseString(input.String())
	return err
}

// ParseFile parses a file for commands and executes them
func (p *Parser) ParseFile(filename string) error {
	input, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = p.ParseString(string(input))
	return err
}

// ParseString parses a string for commands and executes them
func (p *Parser) ParseString(input string) error {
	p.lexer = Lex(input)
	commands, err := p.parse()
	if err == nil {
		p.commands = commands
		err = p.process()
	}
	return err
}

func (p *Parser) parse() ([]Command, error) {
	commands := make([]Command, 0, 50)
	for {
		t := p.nextToken()
		switch t.tt {
		case tError:
			return nil, errors.New(t.value)
		case tEOF:
			if p.isAnimated {
				if p.basename == "" {
					fmt.Fprintf(os.Stderr, "No basename provided: using default basename '%s'\n", DefaultBasename)
					p.basename = DefaultBasename
					p.formatString = fmt.Sprintf("%s/%s-%%0%dd.png", FramesDirectory, p.basename, len(strconv.Itoa(p.frames)))
				}
			}
			return commands, nil
		case tIdent:
			var command Command
			switch LookupIdent(t.value) {
			case MOVE:
				c := MoveCommand{}
				c.args = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				if p.peek().tt == tString {
					c.knob = p.nextToken().value
					p.symbols.Set(c.knob, 0)
				}
				command = c
			case SCALE:
				c := ScaleCommand{}
				c.args = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				if p.peek().tt == tString {
					c.knob = p.nextToken().value
					p.symbols.Set(c.knob, 0)
				}
				command = c
			case ROTATE:
				c := RotateCommand{}
				c.axis = p.nextIdent()
				c.degrees = p.nextFloat()
				if p.peek().tt == tString {
					c.knob = p.nextToken().value
					p.symbols.Set(c.knob, 0)
				}
				command = c
			case LINE:
				c := LineCommand{}
				if p.peek().tt == tString {
					c.constants = p.nextToken().value
				}
				c.p1 = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				if p.peek().tt == tString {
					c.cs = p.nextToken().value
				}
				c.p2 = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				if p.peek().tt == tString {
					c.cs2 = p.nextToken().value
				}
				command = c
			case SPHERE:
				c := SphereCommand{}
				if p.peek().tt == tString {
					c.constants = p.nextToken().value
				}
				c.center = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				c.radius = p.nextFloat()
				if p.peek().tt == tString {
					c.cs = p.nextToken().value
				}
				command = c
			case TORUS:
				c := TorusCommand{}
				if p.peek().tt == tString {
					c.constants = p.nextToken().value
				}
				c.center = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				c.r1 = p.nextFloat()
				c.r2 = p.nextFloat()
				if p.peek().tt == tString {
					c.cs = p.nextToken().value
				}
				command = c
			case BOX:
				c := BoxCommand{}
				if p.peek().tt == tString {
					c.constants = p.nextToken().value
				}
				c.p1 = []float64{p.nextFloat(), p.nextFloat(), p.nextFloat()}
				c.width = p.nextFloat()
				c.height = p.nextFloat()
				c.depth = p.nextFloat()
				if p.peek().tt == tString {
					c.cs = p.nextToken().value
				}
				command = c
			case POP:
				command = PopCommand{}
			case PUSH:
				command = PushCommand{}
			case SAVE:
				command = SaveCommand{
					filename: p.nextString(),
				}
			case DISPLAY:
				command = DisplayCommand{}
			case VARY:
				if p.frames == 0 {
					return nil, errors.New("number of frames is not set")
				}
				name := p.nextString()
				knob, found := p.knobs[name]
				if !found {
					knob = make([]float64, p.frames)
				}
				startFrame := p.nextInt()
				if startFrame < 0 || startFrame >= p.frames {
					return nil, fmt.Errorf("invalid start frame %d for knob %s", name, startFrame)
				}
				endFrame := p.nextInt()
				if endFrame < 0 || endFrame >= p.frames || endFrame < startFrame {
					return nil, fmt.Errorf("invalid end frame %d for knob %s", name, endFrame)
				}
				startValue := p.nextFloat()
				endValue := p.nextFloat()
				length := endFrame - startFrame
				delta := (endValue - startValue) / float64(length+1)
				for frame := startFrame; frame <= endFrame; frame++ {
					knob[frame] = startValue
					startValue += delta
				}
				p.knobs[name] = knob
				p.isAnimated = true
			case BASENAME:
				if p.basename != "" {
					fmt.Fprintln(os.Stderr, "Setting the basename multiple times")
				}
				p.basename = p.nextString()
				p.formatString = fmt.Sprintf("%s/%s-%%0%dd.png", FramesDirectory, p.basename, len(strconv.Itoa(p.frames)))
				p.isAnimated = true
			case FRAMES:
				if p.frames != 0 {
					fmt.Fprintln(os.Stderr, "Setting the number of frames multiple times")
				}
				p.frames = p.nextInt()
				if p.frames <= 0 {
					return nil, errors.New("number of frames must be greater than zero")
				}
				p.isAnimated = true
			case SET:
				c := SetCommand{
					name:  p.nextString(),
					value: p.nextFloat(),
				}
				command = c
			case SETALL:
				c := SetAllCommand{
					value: p.nextFloat(),
				}
				command = c
			}
			if command != nil {
				commands = append(commands, command)
			}
			if err := p.expect(tNewline); err != nil {
				return nil, fmt.Errorf("unexpected %v at end of statement", p.peek().tt)
			}
		case tString:
			return nil, fmt.Errorf("unrecognized identifier: \"%s\"", t.value)
		}
	}
	return commands, nil
}

func (p *Parser) process() error {
	if p.isAnimated {
		os.RemoveAll(FramesDirectory)
		os.Mkdir(FramesDirectory, 0755)
	} else {
		p.frames = 1
	}
	var err error
	for frame := 0; frame < p.frames; frame++ {
		for knob := range p.knobs {
			p.symbols.Set(knob, p.knobs[knob][frame])
		}
		if p.isAnimated {
			fmt.Printf("Rendering frame %d/%d", frame+1, p.frames)
		}
		start := time.Now()
		for _, command := range p.commands {
			switch command.(type) {
			case MoveCommand:
				c := command.(MoveCommand)
				x, y, z := c.args[0], c.args[1], c.args[2]
				if c.knob != "" {
					if knob, err := p.getSymbolValue(c.knob); err == nil {
						x *= knob
						y *= knob
						z *= knob
					} else {
						return err
					}
				}
				err = p.drawer.Move(x, y, z)
			case ScaleCommand:
				c := command.(ScaleCommand)
				x, y, z := c.args[0], c.args[1], c.args[2]
				if c.knob != "" {
					if knob, err := p.getSymbolValue(c.knob); err == nil {
						x *= knob
						y *= knob
						z *= knob
					} else {
						return err
					}
				}
				err = p.drawer.Scale(x, y, z)
			case RotateCommand:
				c := command.(RotateCommand)
				degrees := c.degrees
				if c.knob != "" {
					if knob, err := p.getSymbolValue(c.knob); err == nil {
						degrees *= knob
					} else {
						return err
					}
				}
				err = p.drawer.Rotate(c.axis, degrees)
			case LineCommand:
				c := command.(LineCommand)
				err = p.drawer.Line(c.p1[0], c.p1[1], c.p1[2], c.p2[0], c.p2[1], c.p2[2])
			case SphereCommand:
				c := command.(SphereCommand)
				err = p.drawer.Sphere(c.center[0], c.center[1], c.center[2], c.radius)
			case TorusCommand:
				c := command.(TorusCommand)
				err = p.drawer.Torus(c.center[0], c.center[1], c.center[2], c.r1, c.r2)
			case BoxCommand:
				c := command.(BoxCommand)
				err = p.drawer.Box(c.p1[0], c.p1[1], c.p1[2], c.width, c.height, c.depth)
			case PopCommand:
				p.drawer.Pop()
			case PushCommand:
				p.drawer.Push()
			case SaveCommand:
				c := command.(SaveCommand)
				err = p.drawer.Save(c.filename)
			case DisplayCommand:
				err = p.drawer.Display()
			case SetCommand:
				c := command.(SetCommand)
				p.symbols.Set(c.name, c.value)
			case SetAllCommand:
				c := command.(SetAllCommand)
				for key := range p.symbols.table {
					p.symbols.Set(key, c.value)
				}
			}
			if err != nil {
				return err
			}
		}
		elapsed := time.Since(start)
		if p.isAnimated {
			fmt.Printf(" - %s\n", elapsed)
			err = p.drawer.Save(fmt.Sprintf(p.formatString, frame))
			if err != nil {
				return err
			}
			p.drawer.Reset()
		}
	}
	if p.isAnimated {
		fmt.Println("Making animation...")
		err = MakeAnimation(p.basename)
	}
	return err
}

func (p *Parser) getSymbolValue(symbolName string) (float64, error) {
	if value, found := p.symbols.Get(symbolName); found {
		return value, nil
	} else {
		return 0, fmt.Errorf("undefined symbol '%s'", symbolName)
	}
}

// nextToken returns the nextToken token from the lexer
func (p *Parser) nextToken() Token {
	lenBackup := len(p.backup)
	// Use the token from backup if it exists
	if lenBackup > 0 {
		token := p.backup[lenBackup-1]
		p.backup = p.backup[:lenBackup-1]
		return token
	}
	token := p.lexer.NextToken()
	return token
}

// next returns the value of the next token if its type is valid
func (p *Parser) next(typs ...TokenType) string {
	next := p.peek()
	for _, tt := range typs {
		if next.tt == tt {
			return next.value
		}
	}
	panic(fmt.Errorf("expected %v, got %v", typs, next.tt))
}

// nextInt returns the next integer token from the lexer
// Panics if the next token is not an integer
func (p *Parser) nextInt() int {
	v, _ := strconv.Atoi(p.next(tInt))
	return v
}

// nextFloat returns the next token from the lexer as a float.
// Panics if the next token is not a float or integer
func (p *Parser) nextFloat() float64 {
	v, _ := strconv.ParseFloat(p.next(tInt, tFloat), 64)
	return v
}

// nextString returns the next token from the lexer.
// Panics if the next token is not a string
func (p *Parser) nextString() string {
	return p.next(tString)
}

// nextIdent returns the next identifier from the lexer as a string.
// Panics if the next token is not an identifier
func (p *Parser) nextIdent() string {
	return p.next(tIdent)
}

// unread adds the token to the list of backup tokens.
// Since channels cannot be "unread", we use a list to backup these tokens
func (p *Parser) unread(token Token) {
	p.backup = append(p.backup, token)
}

// peek returns the next token without consuming it
func (p *Parser) peek() Token {
	token := p.nextToken()
	p.unread(token)
	return token
}
<|endoftext|>