<commit_before>package roll

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
)

// ComparisonType is the type of comparison
type ComparisonType int

const (
	// Equals matches only values that are equal to the comparison value
	Equals ComparisonType = iota
	// GreaterThan matches only values greater than the comparison value
	GreaterThan
	// LessThan matches only values less than the comparison value
	LessThan
)

// ComparisonOp is the operation that defines how you compare against a roll
// to determine whether the result counts
type ComparisonOp struct {
	Type  ComparisonType
	Value int
}

// Match returns true if the given value compares positively against the op val
func (op ComparisonOp) Match(val int) bool {
	switch op.Type {
	case Equals:
		return val == op.Value
	case GreaterThan:
		return val > op.Value
	case LessThan:
		return val < op.Value
	}
	return false
}

// ExplodingType is the type of exploding die
type ExplodingType int

const (
	// Exploding adds new dice for each roll satisfying the exploding condition
	Exploding ExplodingType = iota
	// Compounded adds to a single new result for each roll
	Compounded
	// Penetrating is like Exploding, except each die result has a -1 modifier
	Penetrating
)

// ExplodingOp is the operation that defines how a dice roll explodes
type ExplodingOp struct {
	ComparisonOp
	Type ExplodingType
}

// LimitType is the type of roll limitation
type LimitType int

const (
	// KeepHighest indicated we should keep the highest results
	KeepHighest LimitType = iota
	// KeepLowest indicated we should keep the lowest results
	KeepLowest
	// DropHighest indicated we should drop the highest results
	DropHighest
	// DropLowest indicated we should drop the lowest results
	DropLowest
)

// LimitOp is the operation that defines how dice roll results are limited
type LimitOp struct {
	Amount int
	Type   LimitType
}

// RerollOp is the operation that defines how dice are rerolled
type RerollOp struct {
	ComparisonOp
	Once bool
}

// SortType is the type of sorting to use for dice roll results
type SortType int

const (
	// Ascending sorts dice rolls from lowest to highest
	Ascending SortType = iota
	// Descending sorts dice rolls from highest to lowest
	Descending
)

// Roll is any kind of roll
type Roll interface {
	Roll() Result
	String() string
}

// Result is a collection of die rolls and a count of successes
type Result struct {
	Results   []DieRoll
	Total     int
	Successes int
}

// Len is the number of results
func (r Result) Len() int {
	return len(r.Results)
}

// Less return true if DieRoll at index i is less than the one at index j
func (r Result) Less(i, j int) bool {
	return r.Results[i].Result < r.Results[j].Result
}

// Swap swaps the DieRoll at index i with the one at index j
func (r Result) Swap(i, j int) {
	r.Results[i], r.Results[j] = r.Results[j], r.Results[i]
}

// DiceRoll is an individual Dice Roll
type DiceRoll struct {
	Multiplier int
	Die        Die
	Modifier   int
	Exploding  *ExplodingOp
	Limit      *LimitOp
	Success    *ComparisonOp
	Failure    *ComparisonOp
	Rerolls    []RerollOp
	Sort       *SortType
}

// Roll gets the results of rolling the dice that make up a dice roll
func (dr *DiceRoll) Roll() (result Result) {
	// 1. Do Multiplier rolls of Die
	if dr.Multiplier == 0 {
		return
	}

	totalMultiplier := 1
	if dr.Multiplier < 0 {
		totalMultiplier = -1
	}

	for i := 0; i < dr.Multiplier*totalMultiplier; i++ {
		result.Results = append(result.Results, dr.Die.Roll())
	}

	// 2. For each result, check reroll criteria and reroll if a match
	for _, reroll := range dr.Rerolls {
		for i, roll := range result.Results {
			for reroll.Match(roll.Result) {
				roll = dr.Die.Roll()
			}
			result.Results[i] = roll
		}
	}

	// 3. For each result, check exploding criteria and generate new rolls
	if dr.Exploding != nil {
		switch dr.Exploding.Type {
		case Exploding:
			for _, roll := range result.Results {
				if dr.Exploding.Match(roll.Result) {
					roll = dr.Die.Roll()
					result.Results = append(result.Results, roll)
				}
			}
		case Compounded:
			compound := 0
			for _, roll := range result.Results {
				for dr.Exploding.Match(roll.Result) {
					compound += roll.Result
					roll = dr.Die.Roll()
				}
			}
			result.Results = append(result.Results, DieRoll{compound, strconv.Itoa(compound)})
		case Penetrating:
			for _, roll := range result.Results {
				if dr.Exploding.Match(roll.Result) {
					roll = dr.Die.Roll()
					roll.Result--
					roll.Symbol = strconv.Itoa(roll.Result)
					result.Results = append(result.Results, roll)
				}
			}
		}
	}

	// 4. Check results and apply limit operation
	if dr.Limit != nil {
		var rolls Result
		rolls.Results = result.Results[:]

		// Sort our tmp result copy
		sort.Sort(rolls)

		// Work out limit
		limit := dr.Limit.Amount
		if limit > len(rolls.Results) {
			limit = len(rolls.Results)
		}

		switch dr.Limit.Type {
		case KeepHighest:
			result.Results = rolls.Results[len(rolls.Results)-limit:]
		case KeepLowest:
			result.Results = rolls.Results[:limit]
		case DropHighest:
			result.Results = rolls.Results[:len(rolls.Results)-limit]
		case DropLowest:
			result.Results = rolls.Results[limit:]
		}
	}

	// 5. If success op set, add modifier to each result and add successes for each match
	if dr.Success != nil {
		for _, roll := range result.Results {
			if dr.Success.Match(roll.Result + dr.Modifier) {
				result.Successes++
			}
		}
	}

	// 6. If failure op set, add modifier to each result and subtract successes for each match
	if dr.Failure != nil {
		for _, roll := range result.Results {
			if dr.Failure.Match(roll.Result + dr.Modifier) {
				result.Successes--
			}
		}
	}

	// 7. If sort op set, sort results
	if dr.Sort != nil {
		switch *dr.Sort {
		case Ascending:
			sort.Sort(result)
		case Descending:
			sort.Sort(sort.Reverse(result))
		}
	}

	// 8. If success and failure ops not set, add modifier to total result
	if dr.Success == nil && dr.Failure == nil {
		for _, roll := range result.Results {
			result.Total += roll.Result
		}
		result.Total += dr.Modifier
		result.Total *= totalMultiplier
	}
	return
}

// String represents the dice roll as a string
func (dr *DiceRoll) String() string {
	var output string
	if dr.Multiplier > 1 {
		output += fmt.Sprintf("%d", dr.Multiplier)
	}

	output += dr.Die.String()

	if dr.Modifier != 0 {
		output += fmt.Sprintf("%+d", dr.Modifier)
	}
	return output
}

// GroupedRoll is a group of other rolls. You can have nested groups.
type GroupedRoll struct {
	Rolls    []Roll
	Modifier int
	Limit    LimitOp
	Success  ComparisonOp
	Failure  ComparisonOp
}

// Roll gets the results of rolling the dice that make up a dice roll
func (gr *GroupedRoll) Roll() (result Result) {
	// 1. Generate results for each roll
	// 2.
	return result
}

// String represents the grouped roll as a string
func (gr *GroupedRoll) String() string {
	parts := []string{"{"}
	for _, roll := range gr.Rolls {
		parts = append(parts, roll.String())
	}
	parts = append(parts, "}")

	output := strings.Join(parts, ", ")

	return output
}
<commit_msg>Implement Grouped Roll rolling<commit_after>package roll

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
)

// ComparisonType is the type of comparison
type ComparisonType int

const (
	// Equals matches only values that are equal to the comparison value
	Equals ComparisonType = iota
	// GreaterThan matches only values greater than the comparison value
	GreaterThan
	// LessThan matches only values less than the comparison value
	LessThan
)

// ComparisonOp is the operation that defines how you compare against a roll
// to determine whether the result counts
type ComparisonOp struct {
	Type  ComparisonType
	Value int
}

// Match returns true if the given value compares positively against the op val
func (op ComparisonOp) Match(val int) bool {
	switch op.Type {
	case Equals:
		return val == op.Value
	case GreaterThan:
		return val > op.Value
	case LessThan:
		return val < op.Value
	}
	return false
}

// ExplodingType is the type of exploding die
type ExplodingType int

const (
	// Exploding adds new dice for each roll satisfying the exploding condition
	Exploding ExplodingType = iota
	// Compounded adds to a single new result for each roll
	Compounded
	// Penetrating is like Exploding, except each die result has a -1 modifier
	Penetrating
)

// ExplodingOp is the operation that defines how a dice roll explodes
type ExplodingOp struct {
	ComparisonOp
	Type ExplodingType
}

// LimitType is the type of roll limitation
type LimitType int

const (
	// KeepHighest indicated we should keep the highest results
	KeepHighest LimitType = iota
	// KeepLowest indicated we should keep the lowest results
	KeepLowest
	// DropHighest indicated we should drop the highest results
	DropHighest
	// DropLowest indicated we should drop the lowest results
	DropLowest
)

// LimitOp is the operation that defines how dice roll results are limited
type LimitOp struct {
	Amount int
	Type   LimitType
}

// RerollOp is the operation that defines how dice are rerolled
type RerollOp struct {
	ComparisonOp
	Once bool
}

// SortType is the type of sorting to use for dice roll results
type SortType int

const (
	// Ascending sorts dice rolls from lowest to highest
	Ascending SortType = iota
	// Descending sorts dice rolls from highest to lowest
	Descending
)

// Roll is any kind of roll
type Roll interface {
	Roll() Result
	String() string
}

// Result is a collection of die rolls and a count of successes
type Result struct {
	Results   []DieRoll
	Total     int
	Successes int
}

// Len is the number of results
func (r *Result) Len() int {
	return len(r.Results)
}

// Less return true if DieRoll at index i is less than the one at index j
func (r *Result) Less(i, j int) bool {
	return r.Results[i].Result < r.Results[j].Result
}

// Swap swaps the DieRoll at index i with the one at index j
func (r *Result) Swap(i, j int) {
	r.Results[i], r.Results[j] = r.Results[j], r.Results[i]
}

// DiceRoll is an individual Dice Roll
type DiceRoll struct {
	Multiplier int
	Die        Die
	Modifier   int
	Exploding  *ExplodingOp
	Limit      *LimitOp
	Success    *ComparisonOp
	Failure    *ComparisonOp
	Rerolls    []RerollOp
	Sort       *SortType
}

// Roll gets the results of rolling the dice that make up a dice roll
func (dr *DiceRoll) Roll() (result Result) {
	// 1. Do Multiplier rolls of Die
	if dr.Multiplier == 0 {
		return
	}

	totalMultiplier := 1
	if dr.Multiplier < 0 {
		totalMultiplier = -1
	}

	for i := 0; i < dr.Multiplier*totalMultiplier; i++ {
		result.Results = append(result.Results, dr.Die.Roll())
	}

	// 2. For each result, check reroll criteria and reroll if a match
	for _, reroll := range dr.Rerolls {
		for i, roll := range result.Results {
			for reroll.Match(roll.Result) {
				roll = dr.Die.Roll()
			}
			result.Results[i] = roll
		}
	}

	// 3. For each result, check exploding criteria and generate new rolls
	if dr.Exploding != nil {
		switch dr.Exploding.Type {
		case Exploding:
			for _, roll := range result.Results {
				if dr.Exploding.Match(roll.Result) {
					roll = dr.Die.Roll()
					result.Results = append(result.Results, roll)
				}
			}
		case Compounded:
			compound := 0
			for _, roll := range result.Results {
				for dr.Exploding.Match(roll.Result) {
					compound += roll.Result
					roll = dr.Die.Roll()
				}
			}
			result.Results = append(result.Results, DieRoll{compound, strconv.Itoa(compound)})
		case Penetrating:
			for _, roll := range result.Results {
				if dr.Exploding.Match(roll.Result) {
					roll = dr.Die.Roll()
					roll.Result--
					roll.Symbol = strconv.Itoa(roll.Result)
					result.Results = append(result.Results, roll)
				}
			}
		}
	}

	// 4. Check results and apply limit operation
	applyLimit(dr.Limit, &result)

	// 5. If success op set, add modifier to each result and add successes for each match
	applySuccess(dr.Success, dr.Modifier, &result)

	// 6. If failure op set, add modifier to each result and subtract successes for each match
	applyFailure(dr.Failure, dr.Modifier, &result)

	// 7. If sort op set, sort results
	applySort(dr.Sort, &result)

	// 8. If success and failure ops not set, add modifier to total result
	finaliseTotals(dr.Success, dr.Failure, dr.Modifier, totalMultiplier, &result)

	return
}

// String represents the dice roll as a string
func (dr *DiceRoll) String() string {
	var output string
	if dr.Multiplier > 1 {
		output += fmt.Sprintf("%d", dr.Multiplier)
	}

	output += dr.Die.String()

	if dr.Modifier != 0 {
		output += fmt.Sprintf("%+d", dr.Modifier)
	}
	return output
}

// GroupedRoll is a group of other rolls. You can have nested groups.
type GroupedRoll struct {
	Rolls    []Roll
	Modifier int
	Limit    *LimitOp
	Success  *ComparisonOp
	Failure  *ComparisonOp
	Combined bool
}

// Roll gets the results of rolling the dice that make up a dice roll
func (gr *GroupedRoll) Roll() (result Result) {
	// 1. Generate results for each roll
	for _, roll := range gr.Rolls {
		if gr.Combined {
			// 2. If combined, merge all roll results into one result set
			result.Results = append(result.Results, roll.Roll().Results...)
		} else {
			// 3. If not combined, make new result set out of the totals for each roll
			total := roll.Roll().Total
			result.Results = append(result.Results, DieRoll{total, strconv.Itoa(total)})
		}
	}

	// 4. If limit set, apply limit operation to results
	applyLimit(gr.Limit, &result)

	// 5. If Success set, apply success op to results
	applySuccess(gr.Success, gr.Modifier, &result)

	// 6. If Failure set, apply failure op to results
	applyFailure(gr.Failure, gr.Modifier, &result)

	// 7. Add modifier or tally successes
	finaliseTotals(gr.Success, gr.Failure, gr.Modifier, 1, &result)

	return result
}

// String represents the grouped roll as a string
func (gr *GroupedRoll) String() string {
	parts := []string{"{"}
	for _, roll := range gr.Rolls {
		parts = append(parts, roll.String())
	}
	parts = append(parts, "}")

	sep := ", "
	if gr.Combined {
		sep = "+"
	}

	output := strings.Join(parts, sep)
	if gr.Combined {
		output = strings.Replace(output, "+-", "-", -1)
	}

	return output
}

func applyLimit(limitOp *LimitOp, result *Result) {
	if limitOp != nil {
		var rolls Result
		rolls.Results = result.Results[:]

		// Sort our tmp result copy
		sort.Sort(&rolls)

		// Work out limit
		limit := limitOp.Amount
		if limit > len(rolls.Results) {
			limit = len(rolls.Results)
		}

		switch limitOp.Type {
		case KeepHighest:
			result.Results = rolls.Results[len(rolls.Results)-limit:]
		case KeepLowest:
			result.Results = rolls.Results[:limit]
		case DropHighest:
			result.Results = rolls.Results[:len(rolls.Results)-limit]
		case DropLowest:
			result.Results = rolls.Results[limit:]
		}
	}
}

func applySuccess(successOp *ComparisonOp, modifier int, result *Result) {
	if successOp != nil {
		for _, roll := range result.Results {
			if successOp.Match(roll.Result + modifier) {
				result.Successes++
			}
		}
	}
}

func applyFailure(failureOp *ComparisonOp, modifier int, result *Result) {
	if failureOp != nil {
		for _, roll := range result.Results {
			if failureOp.Match(roll.Result + modifier) {
				result.Successes--
			}
		}
	}
}

func applySort(sortType *SortType, result *Result) {
	if sortType != nil {
		switch *sortType {
		case Ascending:
			sort.Sort(result)
		case Descending:
			sort.Sort(sort.Reverse(result))
		}
	}
}

func finaliseTotals(successOp, failureOp *ComparisonOp, modifier, multiplier int, result *Result) {
	if successOp == nil && failureOp == nil {
		for _, roll := range result.Results {
			result.Total += roll.Result
		}
		result.Total += modifier
		result.Total *= multiplier
	} else {
		result.Total = result.Successes
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"github.com/vaughan0/go-ini"
	"launchpad.net/goamz/aws"
	"os"
	"strings"
)

type AWSConf struct {
	aws.Auth
	Region aws.Region
}

var ErrNoAccessKeyGiven = errors.New("no access key given")
var ErrUnknownRegion = errors.New("unknown region given")

func getAWSConf(projectName string) (conf AWSConf, err error) {
	confFn := os.Getenv("AWS_CONFIG_FILE")
	if confFn == "" {
		confFn = os.Getenv("HOME") + "/.aws/config"
	}

	awsAuth, err := aws.EnvAuth()
	if err == nil {
		conf.Auth = awsAuth

	} else if _, err = os.Stat(confFn); os.IsNotExist(err) {
		return

	} else {
		profiles := make([]string, 0)

		envProfile := os.Getenv("AWS_DEFAULT_PROFILE")
		if envProfile != "" {
			profiles = append(profiles, envProfile)
		}
		profiles = append(profiles, projectName)
		lowerProjectName := strings.ToLower(projectName)
		profiles = append(profiles, strings.Replace(lowerProjectName, " ", "_", -1))
		profiles = append(profiles, strings.Replace(lowerProjectName, " ", "-", -1))

		var iniFile ini.File
		iniFile, err = ini.LoadFile(confFn)
		if err != nil {
			return
		}

		var fileConf ini.Section
		for _, profile := range profiles {
			fileConf = iniFile["profile "+profile]
			if fileConf != nil {
				break
			}
		}

		if fileConf == nil {
			err = errors.New(
				fmt.Sprintf("Couldn't find a suitable AWS config profile; looked for profiles named '%s'. Please add one to your AWS config file at %s",
					strings.Join(profiles, "', '"), confFn))
			return
		}

		conf.AccessKey = fileConf["aws_access_key_id"]
		conf.SecretKey = fileConf["aws_secret_access_key"]
		conf.Region = aws.Regions[fileConf["region"]]
	}

	envRegion := os.Getenv("AWS_DEFAULT_REGION")
	if envRegion != "" {
		conf.Region = aws.Regions[envRegion]
	}

	if conf.AccessKey == "" {
		err = ErrNoAccessKeyGiven
	}

	if conf.Region.Name == "" {
		err = ErrUnknownRegion
	}

	return
}
<commit_msg>Look up ~/.aws/credentials first<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/vaughan0/go-ini"
	"launchpad.net/goamz/aws"
	"os"
	"strings"
)

type AWSConf struct {
	aws.Auth
	Region aws.Region
}

var ErrNoAccessKeyGiven = errors.New("no access key given")
var ErrUnknownRegion = errors.New("unknown region given")

func getAWSConf(projectName string) (conf AWSConf, err error) {
	confFn := os.Getenv("AWS_CONFIG_FILE")
	if confFn == "" {
		confFn = os.Getenv("HOME") + "/.aws/credentials"
		if _, err = os.Stat(confFn); os.IsNotExist(err) {
			confFn = os.Getenv("HOME") + "/.aws/config"
		}
	}

	awsAuth, err := aws.EnvAuth()
	if err == nil {
		conf.Auth = awsAuth

	} else if _, err = os.Stat(confFn); os.IsNotExist(err) {
		return

	} else {
		profiles := make([]string, 0)

		envProfile := os.Getenv("AWS_DEFAULT_PROFILE")
		if envProfile != "" {
			profiles = append(profiles, envProfile)
		}
		profiles = append(profiles, projectName)
		lowerProjectName := strings.ToLower(projectName)
		profiles = append(profiles, strings.Replace(lowerProjectName, " ", "_", -1))
		profiles = append(profiles, strings.Replace(lowerProjectName, " ", "-", -1))

		var iniFile ini.File
		iniFile, err = ini.LoadFile(confFn)
		if err != nil {
			return
		}

		var fileConf ini.Section
		for _, profile := range profiles {
			fileConf = iniFile["profile "+profile]
			if fileConf != nil {
				break
			}
		}

		if fileConf == nil {
			err = errors.New(
				fmt.Sprintf("Couldn't find a suitable AWS config profile; looked for profiles named '%s'. Please add one to your AWS config file at %s",
					strings.Join(profiles, "', '"), confFn))
			return
		}

		conf.AccessKey = fileConf["aws_access_key_id"]
		conf.SecretKey = fileConf["aws_secret_access_key"]
		conf.Region = aws.Regions[fileConf["region"]]
	}

	envRegion := os.Getenv("AWS_DEFAULT_REGION")
	if envRegion != "" {
		conf.Region = aws.Regions[envRegion]
	}

	if conf.AccessKey == "" {
		err = ErrNoAccessKeyGiven
	}

	if conf.Region.Name == "" {
		err = ErrUnknownRegion
	}

	return
}
<|endoftext|>


<commit_before>package mrt

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"time"
)

type BGPPathAttributeOrigin uint8

const (
	BGPPathAttributeOriginIGP BGPPathAttributeOrigin = iota
	BGPPathAttributeOriginEGP
	BGPPathAttributeOriginIncomplete
)

type BGPPathAttributeASPath []*BGPASPathSegment

type BGPASPathSegment struct {
	Type  BGPASPathSegmentType
	Value []AS
}

type BGPASPathSegmentType uint8

const (
	BGPASPathSegmentTypeASSet BGPASPathSegmentType = iota + 1
	BGPASPathSegmentTypeASSequence
)

func decodeASPathAttr(data []byte, as4 bool) (BGPPathAttributeASPath, error) {
	d := &decoder{data}

	var attr BGPPathAttributeASPath

	for d.size() != 0 {
		segment := &BGPASPathSegment{}
		segment.Type = BGPASPathSegmentType(d.uint8())

		n := int(d.uint8())
		segment.Value = make([]AS, n)
		for i := 0; i < n; i++ {
			if as4 {
				segment.Value[i] = AS(d.as4())
			} else {
				segment.Value[i] = AS(d.as2())
			}
		}

		attr = append(attr, segment)
	}

	return attr, nil
}

type BGPPathAttributeAggregator struct {
	AS        AS
	IPAddress net.IP
}

func decodeAggregatorAttr(data []byte, as4 bool) (*BGPPathAttributeAggregator, error) {
	d := &decoder{data}
	attr := &BGPPathAttributeAggregator{}
	if as4 {
		attr.AS = d.as4()
	} else {
		attr.AS = d.as2()
	}
	attr.IPAddress = d.ipv4()
	return attr, nil
}

type BGPPathAttributeCommunities []BGPCommunity

type BGPCommunity uint32

func decodeCommunitiesAttr(data []byte) (BGPPathAttributeCommunities, error) {
	d := &decoder{data}
	n := d.size() / 4
	attr := make(BGPPathAttributeCommunities, n)
	for i := 0; i < n; i++ {
		attr[i] = BGPCommunity(d.uint32())
	}
	return attr, nil
}

type BGPPathAttributeClusterList []BGPClusterID

type BGPClusterID net.IP

func decodeClusterListAttr(data []byte) (BGPPathAttributeClusterList, error) {
	d := &decoder{data}
	n := d.size() / net.IPv4len
	attr := make(BGPPathAttributeClusterList, n)
	for i := 0; i < n; i++ {
		attr[i] = BGPClusterID(d.ipv4())
	}
	return attr, nil
}

type BGPPathAttributeMPReachNLRI struct {
	AFI     AFI
	SAFI    SAFI
	NextHop net.IP
	NLRI    []*net.IPNet
}

func decodeMPReachNLRIAttr(data []byte) (*BGPPathAttributeMPReachNLRI, error) {
	d := &decoder{data}

	attr := &BGPPathAttributeMPReachNLRI{}
	attr.AFI = AFI(d.uint16())
	attr.SAFI = SAFI(d.uint8())

	n := int(d.uint8())
	if attr.AFI == AFIIPv4 {
		attr.NextHop = d.ipv4N(n)
		d.skip(1) // Reserved (1 octet)
		for d.size() != 0 {
			attr.NLRI = append(attr.NLRI, d.nlriIPv4())
		}
	} else if attr.AFI == AFIIPv6 {
		attr.NextHop = d.ipv6N(n)
		d.skip(1) // Reserved (1 octet)
		for d.size() != 0 {
			attr.NLRI = append(attr.NLRI, d.nlriIPv6())
		}
	} else {
		return nil, fmt.Errorf("unknown AFI: %d", attr.AFI)
	}

	return attr, nil
}

type BGPPathAttributeMPUnreachNLRI struct {
	AFI             AFI
	SAFI            SAFI
	WithdrawnRoutes []*net.IPNet
}

func decodeMPUnreachNLRIAttr(data []byte) (*BGPPathAttributeMPUnreachNLRI, error) {
	d := &decoder{data}

	attr := &BGPPathAttributeMPUnreachNLRI{}
	attr.AFI = AFI(d.uint16())
	attr.SAFI = SAFI(d.uint8())

	for d.size() != 0 {
		if attr.AFI == AFIIPv4 {
			attr.WithdrawnRoutes = append(attr.WithdrawnRoutes, d.nlriIPv4())
		} else if attr.AFI == AFIIPv6 {
			attr.WithdrawnRoutes = append(attr.WithdrawnRoutes, d.nlriIPv6())
		} else {
			return nil, fmt.Errorf("unknown AFI: %d", attr.AFI)
		}
	}

	return attr, nil
}

type BGPPathAttributeExtendedCommunities []BGPExtendedCommunity

type BGPExtendedCommunity [8]byte

func decodeExtendedCommunitiesAttr(data []byte) (BGPPathAttributeExtendedCommunities, error) {
	d := &decoder{data}
	n := d.size() / 8
	attr := make(BGPPathAttributeExtendedCommunities, n)
	for i := 0; i < n; i++ {
		d.copy(attr[i][:])
	}
	return attr, nil
}

type BGPPathAttributeLargeCommunities []BGPLargeCommunity

type BGPLargeCommunity [12]byte

func decodeLargeCommunitiesAttr(data []byte) (BGPPathAttributeLargeCommunities, error) {
	d := &decoder{data}
	n := d.size() / 12
	attr := make(BGPPathAttributeLargeCommunities, n)
	for i := 0; i < n; i++ {
		d.copy(attr[i][:])
	}
	return attr, nil
}

type BGPPathAttribute struct {
	Flag     uint8
	TypeCode uint8
	Value    interface{}
}

type bgpPathAttributeReader struct {
	reader io.Reader
	as4    bool
}

func (r *bgpPathAttributeReader) Next() (*BGPPathAttribute, error) {
	attrTypeBytes := make([]byte, 2)
	if _, err := io.ReadFull(r.reader, attrTypeBytes); err != nil {
		return nil, err
	}

	attr := &BGPPathAttribute{}
	attr.Flag = attrTypeBytes[0]
	attr.TypeCode = attrTypeBytes[1]

	attrLenSize := 1
	if attr.Flag&0x10 != 0 {
		attrLenSize = 2
	}

	attrLenBytes := make([]byte, attrLenSize)
	if _, err := io.ReadFull(r.reader, attrLenBytes); err != nil {
		return nil, err
	}

	attrLen := int(attrLenBytes[0])
	if attrLenSize == 2 {
		attrLen = int(binary.BigEndian.Uint16(attrLenBytes))
	}

	valueBytes := make([]byte, attrLen)
	if _, err := io.ReadFull(r.reader, valueBytes); err != nil {
		return nil, err
	}

	var err error
	switch attr.TypeCode {
	case 1:
		attr.Value = BGPPathAttributeOrigin(valueBytes[0])
	case 2:
		attr.Value, err = decodeASPathAttr(valueBytes, r.as4)
	case 3:
		attr.Value = net.IP(valueBytes)
	case 4:
		attr.Value = binary.BigEndian.Uint32(valueBytes)
	case 5:
		attr.Value = binary.BigEndian.Uint32(valueBytes)
	case 6:
		// zero-length attribute
	case 7:
		attr.Value, err = decodeAggregatorAttr(valueBytes, r.as4)
	case 8:
		attr.Value, err = decodeCommunitiesAttr(valueBytes)
	case 9:
		attr.Value = net.IP(valueBytes)
	case 10:
		attr.Value, err = decodeClusterListAttr(valueBytes)
	case 14:
		attr.Value, err = decodeMPReachNLRIAttr(valueBytes)
	case 15:
		attr.Value, err = decodeMPUnreachNLRIAttr(valueBytes)
	case 16:
		attr.Value, err = decodeExtendedCommunitiesAttr(valueBytes)
	case 17:
		attr.Value, err = decodeASPathAttr(valueBytes, true)
	case 18:
		attr.Value, err = decodeAggregatorAttr(valueBytes, true)
	case 32:
		attr.Value, err = decodeLargeCommunitiesAttr(valueBytes)
	default:
		return nil, fmt.Errorf("unknown BGP path attribute type code: %d", attr.TypeCode)
	}

	return attr, err
}

func decodeBGPMessage(data []byte, as4 bool, afi AFI) (*BGPMessage, error) {
	msgType := BGPMessageType(data[18])

	var msg BGPMessage
	var err error
	switch msgType {
	case BGPMessageTypeOpen:
		msg, err = decodeOpenMessage(data)
	case BGPMessageTypeUpdate:
		msg, err = decodeUpdateMessage(data, as4, afi)
	case BGPMessageTypeNotification:
		msg, err = decodeNotificationMessage(data)
	case BGPMessageTypeKeepalive:
		msg, err = decodeKeepaliveMessage(data)
	default:
		return nil, fmt.Errorf("unknown BGP message type: %d", msgType)
	}

	return &msg, err
}

type BGPMessageType uint8

const (
	BGPMessageTypeOpen BGPMessageType = iota + 1
	BGPMessageTypeUpdate
	BGPMessageTypeNotification
	BGPMessageTypeKeepalive
)

type BGPMessage interface {
	Type() BGPMessageType
}

type bgpMessageHeader struct {
	type_ BGPMessageType
}

func (h *bgpMessageHeader) Type() BGPMessageType {
	return h.type_
}

func (h *bgpMessageHeader) decodeHeader(d *decoder) error {
	d.skip(16) // Marker (16 octets)
	d.skip(2)  // Length (2 octets)
	h.type_ = BGPMessageType(d.uint8())
	return nil
}

type BGPOpenMessage struct {
	bgpMessageHeader
	Version            uint8
	MyAS               AS
	HoldTime           time.Duration
	BGPIdentifier      net.IP
	OptionalParameters []*OptionalParameter
}

type OptionalParameter struct {
	Type  uint8
	Value []byte
}

func decodeOpenMessage(data []byte) (*BGPOpenMessage, error) {
	d := &decoder{data}
	msg := &BGPOpenMessage{}
	msg.decodeHeader(d)
	msg.Version = d.uint8()
	msg.MyAS = d.as2()
	msg.HoldTime = time.Duration(d.uint16()) * time.Second
	msg.BGPIdentifier = d.ipv4()
	d.skip(1) // Optional Parameters Length (1 octet)
	for d.size() != 0 {
		p := &OptionalParameter{}
		p.Type = d.uint8()
		p.Value = d.bytes(int(d.uint8()))
		msg.OptionalParameters = append(msg.OptionalParameters, p)
	}
	return msg, nil
}

type BGPUpdateMessage struct {
	bgpMessageHeader
	WithdrawnRoutes []*net.IPNet
	PathAttributes  []*BGPPathAttribute
	NLRI            []*net.IPNet
}

func decodeUpdateMessage(data []byte, as4 bool, afi AFI) (*BGPUpdateMessage, error) {
	d := &decoder{data}

	msg := &BGPUpdateMessage{}
	msg.decodeHeader(d)

	routesLen := int(d.uint16())
	restLen := d.size() - routesLen
	for d.size() != restLen {
		if afi == AFIIPv4 {
			msg.WithdrawnRoutes = append(msg.WithdrawnRoutes, d.nlriIPv4())
		} else if afi == AFIIPv6 {
			msg.WithdrawnRoutes = append(msg.WithdrawnRoutes, d.nlriIPv6())
		} else {
			return nil, fmt.Errorf("unknown AFI: %d", afi)
		}
	}

	attrBytes := d.skip(int(d.uint16()))
	reader := bgpPathAttributeReader{reader: bytes.NewReader(attrBytes), as4: as4}
	for {
		attr, err := reader.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}
		msg.PathAttributes = append(msg.PathAttributes, attr)
	}

	for d.size() != 0 {
		if afi == AFIIPv4 {
			msg.NLRI = append(msg.NLRI, d.nlriIPv4())
		} else if afi == AFIIPv6 {
			msg.NLRI = append(msg.NLRI, d.nlriIPv6())
		} else {
			return nil, fmt.Errorf("unknown AFI: %d", afi)
		}
	}

	return msg, nil
}

type BGPNotificationMessage struct {
	bgpMessageHeader
	ErrorCode    uint8
	ErrorSubcode uint8
	Data         []byte
}

func decodeNotificationMessage(data []byte) (*BGPNotificationMessage, error) {
	d := &decoder{data}
	msg := &BGPNotificationMessage{}
	msg.decodeHeader(d)
	msg.ErrorCode = d.uint8()
	msg.ErrorSubcode = d.uint8()
	msg.Data = d.bytes(d.size())
	return msg, nil
}

type BGPKeepaliveMessage struct {
	bgpMessageHeader
}

func decodeKeepaliveMessage(data []byte) (*BGPKeepaliveMessage, error) {
	d := &decoder{data}
	msg := &BGPKeepaliveMessage{}
	msg.decodeHeader(d)
	return msg, nil
}
<commit_msg>Remove redundant type conversion<commit_after>package mrt

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"time"
)

type BGPPathAttributeOrigin uint8

const (
	BGPPathAttributeOriginIGP BGPPathAttributeOrigin = iota
	BGPPathAttributeOriginEGP
	BGPPathAttributeOriginIncomplete
)

type BGPPathAttributeASPath []*BGPASPathSegment

type BGPASPathSegment struct {
	Type  BGPASPathSegmentType
	Value []AS
}

type BGPASPathSegmentType uint8

const (
	BGPASPathSegmentTypeASSet BGPASPathSegmentType = iota + 1
	BGPASPathSegmentTypeASSequence
)

func decodeASPathAttr(data []byte, as4 bool) (BGPPathAttributeASPath, error) {
	d := &decoder{data}

	var attr BGPPathAttributeASPath

	for d.size() != 0 {
		segment := &BGPASPathSegment{}
		segment.Type = BGPASPathSegmentType(d.uint8())

		n := int(d.uint8())
		segment.Value = make([]AS, n)
		for i := 0; i < n; i++ {
			if as4 {
				segment.Value[i] = d.as4()
			} else {
				segment.Value[i] = d.as2()
			}
		}

		attr = append(attr, segment)
	}

	return attr, nil
}

type BGPPathAttributeAggregator struct {
	AS        AS
	IPAddress net.IP
}

func decodeAggregatorAttr(data []byte, as4 bool) (*BGPPathAttributeAggregator, error) {
	d := &decoder{data}
	attr := &BGPPathAttributeAggregator{}
	if as4 {
		attr.AS = d.as4()
	} else {
		attr.AS = d.as2()
	}
	attr.IPAddress = d.ipv4()
	return attr, nil
}

type BGPPathAttributeCommunities []BGPCommunity

type BGPCommunity uint32

func decodeCommunitiesAttr(data []byte) (BGPPathAttributeCommunities, error) {
	d := &decoder{data}
	n := d.size() / 4
	attr := make(BGPPathAttributeCommunities, n)
	for i := 0; i < n; i++ {
		attr[i] = BGPCommunity(d.uint32())
	}
	return attr, nil
}

type BGPPathAttributeClusterList []BGPClusterID

type BGPClusterID net.IP

func decodeClusterListAttr(data []byte) (BGPPathAttributeClusterList, error) {
	d := &decoder{data}
	n := d.size() / net.IPv4len
	attr := make(BGPPathAttributeClusterList, n)
	for i := 0; i < n; i++ {
		attr[i] = BGPClusterID(d.ipv4())
	}
	return attr, nil
}

type BGPPathAttributeMPReachNLRI struct {
	AFI     AFI
	SAFI    SAFI
	NextHop net.IP
	NLRI    []*net.IPNet
}

func decodeMPReachNLRIAttr(data []byte) (*BGPPathAttributeMPReachNLRI, error) {
	d := &decoder{data}

	attr := &BGPPathAttributeMPReachNLRI{}
	attr.AFI = AFI(d.uint16())
	attr.SAFI = SAFI(d.uint8())

	n := int(d.uint8())
	if attr.AFI == AFIIPv4 {
		attr.NextHop = d.ipv4N(n)
		d.skip(1) // Reserved (1 octet)
		for d.size() != 0 {
			attr.NLRI = append(attr.NLRI, d.nlriIPv4())
		}
	} else if attr.AFI == AFIIPv6 {
		attr.NextHop = d.ipv6N(n)
		d.skip(1) // Reserved (1 octet)
		for d.size() != 0 {
			attr.NLRI = append(attr.NLRI, d.nlriIPv6())
		}
	} else {
		return nil, fmt.Errorf("unknown AFI: %d", attr.AFI)
	}

	return attr, nil
}

type BGPPathAttributeMPUnreachNLRI struct {
	AFI             AFI
	SAFI            SAFI
	WithdrawnRoutes []*net.IPNet
}

func decodeMPUnreachNLRIAttr(data []byte) (*BGPPathAttributeMPUnreachNLRI, error) {
	d := &decoder{data}

	attr := &BGPPathAttributeMPUnreachNLRI{}
	attr.AFI = AFI(d.uint16())
	attr.SAFI = SAFI(d.uint8())

	for d.size() != 0 {
		if attr.AFI == AFIIPv4 {
			attr.WithdrawnRoutes = append(attr.WithdrawnRoutes, d.nlriIPv4())
		} else if attr.AFI == AFIIPv6 {
			attr.WithdrawnRoutes = append(attr.WithdrawnRoutes, d.nlriIPv6())
		} else {
			return nil, fmt.Errorf("unknown AFI: %d", attr.AFI)
		}
	}

	return attr, nil
}

type BGPPathAttributeExtendedCommunities []BGPExtendedCommunity

type BGPExtendedCommunity [8]byte

func decodeExtendedCommunitiesAttr(data []byte) (BGPPathAttributeExtendedCommunities, error) {
	d := &decoder{data}
	n := d.size() / 8
	attr := make(BGPPathAttributeExtendedCommunities, n)
	for i := 0; i < n; i++ {
		d.copy(attr[i][:])
	}
	return attr, nil
}

type BGPPathAttributeLargeCommunities []BGPLargeCommunity

type BGPLargeCommunity [12]byte

func decodeLargeCommunitiesAttr(data []byte) (BGPPathAttributeLargeCommunities, error) {
	d := &decoder{data}
	n := d.size() / 12
	attr := make(BGPPathAttributeLargeCommunities, n)
	for i := 0; i < n; i++ {
		d.copy(attr[i][:])
	}
	return attr, nil
}

type BGPPathAttribute struct {
	Flag     uint8
	TypeCode uint8
	Value    interface{}
}

type bgpPathAttributeReader struct {
	reader io.Reader
	as4    bool
}

func (r *bgpPathAttributeReader) Next() (*BGPPathAttribute, error) {
	attrTypeBytes := make([]byte, 2)
	if _, err := io.ReadFull(r.reader, attrTypeBytes); err != nil {
		return nil, err
	}

	attr := &BGPPathAttribute{}
	attr.Flag = attrTypeBytes[0]
	attr.TypeCode = attrTypeBytes[1]

	attrLenSize := 1
	if attr.Flag&0x10 != 0 {
		attrLenSize = 2
	}

	attrLenBytes := make([]byte, attrLenSize)
	if _, err := io.ReadFull(r.reader, attrLenBytes); err != nil {
		return nil, err
	}

	attrLen := int(attrLenBytes[0])
	if attrLenSize == 2 {
		attrLen = int(binary.BigEndian.Uint16(attrLenBytes))
	}

	valueBytes := make([]byte, attrLen)
	if _, err := io.ReadFull(r.reader, valueBytes); err != nil {
		return nil, err
	}

	var err error
	switch attr.TypeCode {
	case 1:
		attr.Value = BGPPathAttributeOrigin(valueBytes[0])
	case 2:
		attr.Value, err = decodeASPathAttr(valueBytes, r.as4)
	case 3:
		attr.Value = net.IP(valueBytes)
	case 4:
		attr.Value = binary.BigEndian.Uint32(valueBytes)
	case 5:
		attr.Value = binary.BigEndian.Uint32(valueBytes)
	case 6:
		// zero-length attribute
	case 7:
		attr.Value, err = decodeAggregatorAttr(valueBytes, r.as4)
	case 8:
		attr.Value, err = decodeCommunitiesAttr(valueBytes)
	case 9:
		attr.Value = net.IP(valueBytes)
	case 10:
		attr.Value, err = decodeClusterListAttr(valueBytes)
	case 14:
		attr.Value, err = decodeMPReachNLRIAttr(valueBytes)
	case 15:
		attr.Value, err = decodeMPUnreachNLRIAttr(valueBytes)
	case 16:
		attr.Value, err = decodeExtendedCommunitiesAttr(valueBytes)
	case 17:
		attr.Value, err = decodeASPathAttr(valueBytes, true)
	case 18:
		attr.Value, err = decodeAggregatorAttr(valueBytes, true)
	case 32:
		attr.Value, err = decodeLargeCommunitiesAttr(valueBytes)
	default:
		return nil, fmt.Errorf("unknown BGP path attribute type code: %d", attr.TypeCode)
	}

	return attr, err
}

func decodeBGPMessage(data []byte, as4 bool, afi AFI) (*BGPMessage, error) {
	msgType := BGPMessageType(data[18])

	var msg BGPMessage
	var err error
	switch msgType {
	case BGPMessageTypeOpen:
		msg, err = decodeOpenMessage(data)
	case BGPMessageTypeUpdate:
		msg, err = decodeUpdateMessage(data, as4, afi)
	case BGPMessageTypeNotification:
		msg, err = decodeNotificationMessage(data)
	case BGPMessageTypeKeepalive:
		msg, err = decodeKeepaliveMessage(data)
	default:
		return nil, fmt.Errorf("unknown BGP message type: %d", msgType)
	}

	return &msg, err
}

type BGPMessageType uint8

const (
	BGPMessageTypeOpen BGPMessageType = iota + 1
	BGPMessageTypeUpdate
	BGPMessageTypeNotification
	BGPMessageTypeKeepalive
)

type BGPMessage interface {
	Type() BGPMessageType
}

type bgpMessageHeader struct {
	type_ BGPMessageType
}

func (h *bgpMessageHeader) Type() BGPMessageType {
	return h.type_
}

func (h *bgpMessageHeader) decodeHeader(d *decoder) error {
	d.skip(16) // Marker (16 octets)
	d.skip(2)  // Length (2 octets)
	h.type_ = BGPMessageType(d.uint8())
	return nil
}

type BGPOpenMessage struct {
	bgpMessageHeader
	Version            uint8
	MyAS               AS
	HoldTime           time.Duration
	BGPIdentifier      net.IP
	OptionalParameters []*OptionalParameter
}

type OptionalParameter struct {
	Type  uint8
	Value []byte
}

func decodeOpenMessage(data []byte) (*BGPOpenMessage, error) {
	d := &decoder{data}
	msg := &BGPOpenMessage{}
	msg.decodeHeader(d)
	msg.Version = d.uint8()
	msg.MyAS = d.as2()
	msg.HoldTime = time.Duration(d.uint16()) * time.Second
	msg.BGPIdentifier = d.ipv4()
	d.skip(1) // Optional Parameters Length (1 octet)
	for d.size() != 0 {
		p := &OptionalParameter{}
		p.Type = d.uint8()
		p.Value = d.bytes(int(d.uint8()))
		msg.OptionalParameters = append(msg.OptionalParameters, p)
	}
	return msg, nil
}

type BGPUpdateMessage struct {
	bgpMessageHeader
	WithdrawnRoutes []*net.IPNet
	PathAttributes  []*BGPPathAttribute
	NLRI            []*net.IPNet
}

func decodeUpdateMessage(data []byte, as4 bool, afi AFI) (*BGPUpdateMessage, error) {
	d := &decoder{data}

	msg := &BGPUpdateMessage{}
	msg.decodeHeader(d)

	routesLen := int(d.uint16())
	restLen := d.size() - routesLen
	for d.size() != restLen {
		if afi == AFIIPv4 {
			msg.WithdrawnRoutes = append(msg.WithdrawnRoutes, d.nlriIPv4())
		} else if afi == AFIIPv6 {
			msg.WithdrawnRoutes = append(msg.WithdrawnRoutes, d.nlriIPv6())
		} else {
			return nil, fmt.Errorf("unknown AFI: %d", afi)
		}
	}

	attrBytes := d.skip(int(d.uint16()))
	reader := bgpPathAttributeReader{reader: bytes.NewReader(attrBytes), as4: as4}
	for {
		attr, err := reader.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}
		msg.PathAttributes = append(msg.PathAttributes, attr)
	}

	for d.size() != 0 {
		if afi == AFIIPv4 {
			msg.NLRI = append(msg.NLRI, d.nlriIPv4())
		} else if afi == AFIIPv6 {
			msg.NLRI = append(msg.NLRI, d.nlriIPv6())
		} else {
			return nil, fmt.Errorf("unknown AFI: %d", afi)
		}
	}

	return msg, nil
}

type BGPNotificationMessage struct {
	bgpMessageHeader
	ErrorCode    uint8
	ErrorSubcode uint8
	Data         []byte
}

func decodeNotificationMessage(data []byte) (*BGPNotificationMessage, error) {
	d := &decoder{data}
	msg := &BGPNotificationMessage{}
	msg.decodeHeader(d)
	msg.ErrorCode = d.uint8()
	msg.ErrorSubcode = d.uint8()
	msg.Data = d.bytes(d.size())
	return msg, nil
}

type BGPKeepaliveMessage struct {
	bgpMessageHeader
}

func decodeKeepaliveMessage(data []byte) (*BGPKeepaliveMessage, error) {
	d := &decoder{data}
	msg := &BGPKeepaliveMessage{}
	msg.decodeHeader(d)
	return msg, nil
}
<|endoftext|>


<commit_before>package seabird

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/codegangsta/inject"
	"github.com/sirupsen/logrus"

	"github.com/belak/go-plugin"
	irc "github.com/go-irc/irc/v2"
)

type coreConfig struct {
	Nick string
	User string
	Name string
	Pass string

	PingFrequency duration
	PingTimeout   duration

	Host        string
	TLS         bool
	TLSNoVerify bool
	TLSCert     string
	TLSKey      string

	Cmds   []string
	Prefix string

	Plugins []string

	Debug    bool
	LogLevel string
}

type duration struct {
	time.Duration
}

func (d *duration) UnmarshalText(text []byte) error {
	var err error
	d.Duration, err = time.ParseDuration(string(text))
	return err
}

// A Bot is our wrapper around the irc.Client. It could be used for a general
// client, but the provided convenience functions are designed around using this
// package to write a bot.
type Bot struct {
	mux *BasicMux

	// Config stuff
	confValues map[string]toml.Primitive
	md         toml.MetaData
	config     coreConfig

	// Internal things
	client   *irc.Client
	registry *plugin.Registry
	log      *logrus.Entry
	injector inject.Injector
}

// NewBot will return a new Bot given an io.Reader pointing to a
// config file.
func NewBot(confReader io.Reader) (*Bot, error) {
	var err error

	b := &Bot{
		mux:        NewBasicMux(),
		confValues: make(map[string]toml.Primitive),
		md:         toml.MetaData{},
		registry:   plugins.Copy(),
	}

	// Decode the file, but leave all the config sections intact so we can
	// decode those later.
	b.md, err = toml.DecodeReader(confReader, &b.confValues)
	if err != nil {
		return nil, err
	}

	// Load up the core config
	err = b.Config("core", &b.config)
	if err != nil {
		return nil, err
	}

	// Set up logging/debugging
	b.log = logrus.NewEntry(logrus.New())

	b.log.Logger.Level = logrus.InfoLevel
	if b.config.LogLevel != "" {
		level, err := logrus.ParseLevel(b.config.LogLevel)
		if err != nil {
			return nil, err
		}
		b.log.Logger.Level = level
	} else if b.config.Debug {
		b.log.Warn("The Debug config option has been replaced with LogLevel")
		b.log.Logger.Level = logrus.DebugLevel
	}

	commandMux := NewCommandMux(b.config.Prefix)
	mentionMux := NewMentionMux()

	b.mux.Event("PRIVMSG", commandMux.HandleEvent)
	b.mux.Event("PRIVMSG", mentionMux.HandleEvent)

	// Register all the things we want with the plugin registry.
	b.registry.RegisterProvider("seabird/core", func() (*Bot, *BasicMux, *CommandMux, *MentionMux) {
		return b, b.mux, commandMux, mentionMux
	})

	return b, nil
}

// GetLogger grabs the underlying logger for this bot.
func (b *Bot) GetLogger() *logrus.Entry {
	return b.log
}

// CurrentNick returns the current nick of the bot.
func (b *Bot) CurrentNick() string {
	return b.client.CurrentNick()
}

// Config will decode the config section for the given name into the given
// interface{}
func (b *Bot) Config(name string, c interface{}) error {
	if v, ok := b.confValues[name]; ok {
		return b.md.PrimitiveDecode(v, c)
	}

	return fmt.Errorf("Config section for %q missing", name)
}

// Send is a simple function to send an IRC event
func (b *Bot) Send(m *irc.Message) {
	b.client.WriteMessage(m)
}

// Reply to an irc.Message with a convenience wrapper around fmt.Sprintf
func (b *Bot) Reply(m *irc.Message, format string, v ...interface{}) error {
	if len(m.Params) < 1 || len(m.Params[0]) < 1 {
		return errors.New("Invalid IRC message")
	}

	target := m.Prefix.Name
	if b.FromChannel(m) {
		target = m.Params[0]
	}

	fullMsg := fmt.Sprintf(format, v...)
	for _, resp := range strings.Split(fullMsg, "\n") {
		b.Send(&irc.Message{
			Prefix:  &irc.Prefix{},
			Command: "PRIVMSG",
			Params: []string{
				target,
				resp,
			},
		})
	}

	return nil
}

// MentionReply acts the same as Bot.Reply but it will prefix it with the user's
// nick if we are in a channel.
func (b *Bot) MentionReply(m *irc.Message, format string, v ...interface{}) error {
	if len(m.Params) < 1 || len(m.Params[0]) < 1 {
		return errors.New("Invalid IRC message")
	}

	target := m.Prefix.Name
	prefix := ""
	if b.FromChannel(m) {
		target = m.Params[0]
		prefix = m.Prefix.Name + ": "
	}

	fullMsg := fmt.Sprintf(format, v...)
	for _, resp := range strings.Split(fullMsg, "\n") {
		b.Send(&irc.Message{
			Prefix:  &irc.Prefix{},
			Command: "PRIVMSG",
			Params: []string{
				target,
				prefix + resp,
			},
		})
	}

	return nil
}

// PrivateReply is similar to Reply, but it will always send privately.
func (b *Bot) PrivateReply(m *irc.Message, format string, v ...interface{}) {
	b.Send(&irc.Message{
		Prefix:  &irc.Prefix{},
		Command: "PRIVMSG",
		Params: []string{
			m.Prefix.Name,
			fmt.Sprintf(format, v...),
		},
	})
}

// CTCPReply is a convenience function to respond to CTCP requests.
func (b *Bot) CTCPReply(m *irc.Message, format string, v ...interface{}) error {
	if m.Command != "CTCP" {
		return errors.New("Invalid CTCP message")
	}

	b.Send(&irc.Message{
		Prefix:  &irc.Prefix{},
		Command: "NOTICE",
		Params: []string{
			m.Prefix.Name,
			fmt.Sprintf(format, v...),
		},
	})

	return nil
}

func (b *Bot) handshake() {
	b.client.Writef("CAP END")
	b.client.Writef("NICK %s", b.config.Nick)
	b.client.Writef("USER %s 0.0.0.0 0.0.0.0 :%s", b.config.User, b.config.Name)
}

// Write will write an raw IRC message to the stream
func (b *Bot) Write(line string) {
	b.client.Write(line)
}

// Writef is a convenience method around fmt.Sprintf and Bot.Write
func (b *Bot) Writef(format string, args ...interface{}) {
	b.client.Writef(format, args...)
}

// FromChannel is a wrapper around the irc package's FromChannel.
func (b *Bot) FromChannel(m *irc.Message) bool {
	return b.client.FromChannel(m)
}

func (b *Bot) handler(c *irc.Client, m *irc.Message) {
	// Handle the event and pass it along
	if m.Command == "001" {
		b.log.Info("Connected")

		for _, v := range b.config.Cmds {
			b.Write(v)
		}
	} else if m.Command == "PRIVMSG" {
		// Clean up CTCP stuff so plugins don't need to parse it manually
		lastArg := m.Trailing()
		lastIdx := len(lastArg) - 1
		if lastIdx > 0 && lastArg[0] == '\x01' && lastArg[lastIdx] == '\x01' {
			m.Command = "CTCP"
			m.Params[len(m.Params)-1] = lastArg[1:lastIdx]
		}
	}

	b.mux.HandleEvent(b, m)
}

// ConnectAndRun is a convenience function which will pull the
// connection information out of the config and connect, then call
// Run.
func (b *Bot) ConnectAndRun() error {
	// The ReadWriteCloser will contain either a *net.Conn or *tls.Conn
	var c io.ReadWriteCloser
	var err error
	if b.config.TLS {
		conf := &tls.Config{
			InsecureSkipVerify: b.config.TLSNoVerify,
		}

		if b.config.TLSCert != "" && b.config.TLSKey != "" {
			var cert tls.Certificate
			cert, err = tls.LoadX509KeyPair(b.config.TLSCert, b.config.TLSKey)
			if err != nil {
				return err
			}

			conf.Certificates = []tls.Certificate{cert}
			conf.BuildNameToCertificate()
		}

		c, err = tls.Dial("tcp", b.config.Host, conf)
	} else {
		c, err = net.Dial("tcp", b.config.Host)
	}

	if err != nil {
		return err
	}

	return b.Run(c)
}

// Run starts the bot and loops until it dies. It accepts a
// ReadWriter. If you wish to use the connection feature from the
// config, use ConnectAndRun.
func (b *Bot) Run(c io.ReadWriter) error {
	var err error

	b.injector, err = b.registry.Load(b.config.Plugins, nil)
	if err != nil {
		return err
	}

	// Create a client from the connection we've just opened
	rc := irc.ClientConfig{
		Nick: b.config.Nick,
		Pass: b.config.Pass,
		User: b.config.User,
		Name: b.config.Name,

		PingFrequency: b.config.PingFrequency.Duration,
		PingTimeout:   b.config.PingTimeout.Duration,

		Handler: irc.HandlerFunc(b.handler),
	}

	b.client = irc.NewClient(c, rc)

	// Now that we have a client, set up debug callbacks
	b.client.Reader.DebugCallback = func(line string) {
		b.log.Debug("<-- ", strings.Trim(line, "\r\n"))
	}
	b.client.Writer.DebugCallback = func(line string) {
		if len(line) > 512 {
			b.log.Warnf("Line longer than 512 chars: %s", strings.Trim(line, "\r\n"))
		}
		b.log.Debug("--> ", strings.Trim(line, "\r\n"))
	}

	// Start the main loop
	return b.client.Run()
}
<commit_msg>core: add support for SendLimit and SendBurst<commit_after>package seabird

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/codegangsta/inject"
	"github.com/sirupsen/logrus"

	"github.com/belak/go-plugin"
	irc "github.com/go-irc/irc/v2"
)

type coreConfig struct {
	Nick string
	User string
	Name string
	Pass string

	PingFrequency duration
	PingTimeout   duration

	Host        string
	TLS         bool
	TLSNoVerify bool
	TLSCert     string
	TLSKey      string

	Cmds   []string
	Prefix string

	Plugins []string

	Debug    bool
	LogLevel string

	SendLimit duration
	SendBurst int
}

type duration struct {
	time.Duration
}

func (d *duration) UnmarshalText(text []byte) error {
	var err error
	d.Duration, err = time.ParseDuration(string(text))
	return err
}

// A Bot is our wrapper around the irc.Client. It could be used for a general
// client, but the provided convenience functions are designed around using this
// package to write a bot.
type Bot struct {
	mux *BasicMux

	// Config stuff
	confValues map[string]toml.Primitive
	md         toml.MetaData
	config     coreConfig

	// Internal things
	client   *irc.Client
	registry *plugin.Registry
	log      *logrus.Entry
	injector inject.Injector
}

// NewBot will return a new Bot given an io.Reader pointing to a
// config file.
func NewBot(confReader io.Reader) (*Bot, error) {
	var err error

	b := &Bot{
		mux:        NewBasicMux(),
		confValues: make(map[string]toml.Primitive),
		md:         toml.MetaData{},
		registry:   plugins.Copy(),
	}

	// Decode the file, but leave all the config sections intact so we can
	// decode those later.
	b.md, err = toml.DecodeReader(confReader, &b.confValues)
	if err != nil {
		return nil, err
	}

	// Load up the core config
	err = b.Config("core", &b.config)
	if err != nil {
		return nil, err
	}

	// Set up logging/debugging
	b.log = logrus.NewEntry(logrus.New())

	b.log.Logger.Level = logrus.InfoLevel
	if b.config.LogLevel != "" {
		level, err := logrus.ParseLevel(b.config.LogLevel)
		if err != nil {
			return nil, err
		}
		b.log.Logger.Level = level
	} else if b.config.Debug {
		b.log.Warn("The Debug config option has been replaced with LogLevel")
		b.log.Logger.Level = logrus.DebugLevel
	}

	commandMux := NewCommandMux(b.config.Prefix)
	mentionMux := NewMentionMux()

	b.mux.Event("PRIVMSG", commandMux.HandleEvent)
	b.mux.Event("PRIVMSG", mentionMux.HandleEvent)

	// Register all the things we want with the plugin registry.
	b.registry.RegisterProvider("seabird/core", func() (*Bot, *BasicMux, *CommandMux, *MentionMux) {
		return b, b.mux, commandMux, mentionMux
	})

	return b, nil
}

// GetLogger grabs the underlying logger for this bot.
func (b *Bot) GetLogger() *logrus.Entry {
	return b.log
}

// CurrentNick returns the current nick of the bot.
func (b *Bot) CurrentNick() string {
	return b.client.CurrentNick()
}

// Config will decode the config section for the given name into the given
// interface{}
func (b *Bot) Config(name string, c interface{}) error {
	if v, ok := b.confValues[name]; ok {
		return b.md.PrimitiveDecode(v, c)
	}

	return fmt.Errorf("Config section for %q missing", name)
}

// Send is a simple function to send an IRC event
func (b *Bot) Send(m *irc.Message) {
	b.client.WriteMessage(m)
}

// Reply to an irc.Message with a convenience wrapper around fmt.Sprintf
func (b *Bot) Reply(m *irc.Message, format string, v ...interface{}) error {
	if len(m.Params) < 1 || len(m.Params[0]) < 1 {
		return errors.New("Invalid IRC message")
	}

	target := m.Prefix.Name
	if b.FromChannel(m) {
		target = m.Params[0]
	}

	fullMsg := fmt.Sprintf(format, v...)
	for _, resp := range strings.Split(fullMsg, "\n") {
		b.Send(&irc.Message{
			Prefix:  &irc.Prefix{},
			Command: "PRIVMSG",
			Params: []string{
				target,
				resp,
			},
		})
	}

	return nil
}

// MentionReply acts the same as Bot.Reply but it will prefix it with the user's
// nick if we are in a channel.
func (b *Bot) MentionReply(m *irc.Message, format string, v ...interface{}) error {
	if len(m.Params) < 1 || len(m.Params[0]) < 1 {
		return errors.New("Invalid IRC message")
	}

	target := m.Prefix.Name
	prefix := ""
	if b.FromChannel(m) {
		target = m.Params[0]
		prefix = m.Prefix.Name + ": "
	}

	fullMsg := fmt.Sprintf(format, v...)
	for _, resp := range strings.Split(fullMsg, "\n") {
		b.Send(&irc.Message{
			Prefix:  &irc.Prefix{},
			Command: "PRIVMSG",
			Params: []string{
				target,
				prefix + resp,
			},
		})
	}

	return nil
}

// PrivateReply is similar to Reply, but it will always send privately.
func (b *Bot) PrivateReply(m *irc.Message, format string, v ...interface{}) {
	b.Send(&irc.Message{
		Prefix:  &irc.Prefix{},
		Command: "PRIVMSG",
		Params: []string{
			m.Prefix.Name,
			fmt.Sprintf(format, v...),
		},
	})
}

// CTCPReply is a convenience function to respond to CTCP requests.
func (b *Bot) CTCPReply(m *irc.Message, format string, v ...interface{}) error {
	if m.Command != "CTCP" {
		return errors.New("Invalid CTCP message")
	}

	b.Send(&irc.Message{
		Prefix:  &irc.Prefix{},
		Command: "NOTICE",
		Params: []string{
			m.Prefix.Name,
			fmt.Sprintf(format, v...),
		},
	})

	return nil
}

func (b *Bot) handshake() {
	b.client.Writef("CAP END")
	b.client.Writef("NICK %s", b.config.Nick)
	b.client.Writef("USER %s 0.0.0.0 0.0.0.0 :%s", b.config.User, b.config.Name)
}

// Write will write an raw IRC message to the stream
func (b *Bot) Write(line string) {
	b.client.Write(line)
}

// Writef is a convenience method around fmt.Sprintf and Bot.Write
func (b *Bot) Writef(format string, args ...interface{}) {
	b.client.Writef(format, args...)
}

// FromChannel is a wrapper around the irc package's FromChannel.
func (b *Bot) FromChannel(m *irc.Message) bool {
	return b.client.FromChannel(m)
}

func (b *Bot) handler(c *irc.Client, m *irc.Message) {
	// Handle the event and pass it along
	if m.Command == "001" {
		b.log.Info("Connected")

		for _, v := range b.config.Cmds {
			b.Write(v)
		}
	} else if m.Command == "PRIVMSG" {
		// Clean up CTCP stuff so plugins don't need to parse it manually
		lastArg := m.Trailing()
		lastIdx := len(lastArg) - 1
		if lastIdx > 0 && lastArg[0] == '\x01' && lastArg[lastIdx] == '\x01' {
			m.Command = "CTCP"
			m.Params[len(m.Params)-1] = lastArg[1:lastIdx]
		}
	}

	b.mux.HandleEvent(b, m)
}

// ConnectAndRun is a convenience function which will pull the
// connection information out of the config and connect, then call
// Run.
func (b *Bot) ConnectAndRun() error {
	// The ReadWriteCloser will contain either a *net.Conn or *tls.Conn
	var c io.ReadWriteCloser
	var err error
	if b.config.TLS {
		conf := &tls.Config{
			InsecureSkipVerify: b.config.TLSNoVerify,
		}

		if b.config.TLSCert != "" && b.config.TLSKey != "" {
			var cert tls.Certificate
			cert, err = tls.LoadX509KeyPair(b.config.TLSCert, b.config.TLSKey)
			if err != nil {
				return err
			}

			conf.Certificates = []tls.Certificate{cert}
			conf.BuildNameToCertificate()
		}

		c, err = tls.Dial("tcp", b.config.Host, conf)
	} else {
		c, err = net.Dial("tcp", b.config.Host)
	}

	if err != nil {
		return err
	}

	return b.Run(c)
}

// Run starts the bot and loops until it dies. It accepts a
// ReadWriter. If you wish to use the connection feature from the
// config, use ConnectAndRun.
func (b *Bot) Run(c io.ReadWriter) error {
	var err error

	b.injector, err = b.registry.Load(b.config.Plugins, nil)
	if err != nil {
		return err
	}

	// Create a client from the connection we've just opened
	rc := irc.ClientConfig{
		Nick: b.config.Nick,
		Pass: b.config.Pass,
		User: b.config.User,
		Name: b.config.Name,

		PingFrequency: b.config.PingFrequency.Duration,
		PingTimeout:   b.config.PingTimeout.Duration,

		SendLimit: b.config.SendLimit.Duration,
		SendBurst: b.config.SendBurst,

		Handler: irc.HandlerFunc(b.handler),
	}

	b.client = irc.NewClient(c, rc)

	// Now that we have a client, set up debug callbacks
	b.client.Reader.DebugCallback = func(line string) {
		b.log.Debug("<-- ", strings.Trim(line, "\r\n"))
	}
	b.client.Writer.DebugCallback = func(line string) {
		if len(line) > 512 {
			b.log.Warnf("Line longer than 512 chars: %s", strings.Trim(line, "\r\n"))
		}
		b.log.Debug("--> ", strings.Trim(line, "\r\n"))
	}

	// Start the main loop
	return b.client.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/ElvisChiang/tgpokemongobot/process"
	"github.com/mrd0ll4r/tbotapi"
	"github.com/mrd0ll4r/tbotapi/examples/boilerplate"
)

// DEBUG show verbose debug msg
const DEBUG = true

var pokedexFile = "./data/pokedex.csv"

var gameData []process.GameData

func main() {
	ok := false
	gameData, ok = process.LoadGameData(pokedexFile)
	if !ok {
		fmt.Printf("Game data loading fail\n")
		return
	}
	if DEBUG {
		for _, data := range gameData {
			fmt.Printf("#%d: %s %v %v [%s/%s] %s\n",
				data.Number,
				data.Name,
				data.Evolve,
				data.Nickname,
				data.Type1, data.Type2,
				data.AvatarFile)
		}
	}
	startBot()
	// Command(nil, nil, "/pm")
	// Command(nil, nil, "/pm 1")
	// Command(nil, nil, "/pm 031")
	// Command(nil, nil, "/pm 種子")
	// Command(nil, nil, "/pm23")
	// Command(nil, nil, "123")
	// Command(nil, nil, "456")
	// Command(nil, nil, "種子")
	// Command(nil, nil, "/help")
}

func sendText(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, text string) (ok bool) {
	if api == nil || chat == nil {
		fmt.Printf("Error sending text: %s, err = no api or chat\n", text)
		return false
	}
	outMsg, err := api.NewOutgoingMessage(tbotapi.NewRecipientFromChat(*chat), text).Send()
	if err != nil {
		fmt.Printf("Error sending text: %s, err = %s\n", text, err)
		return false
	}
	fmt.Printf("->%d, To:\t%s, %s\n", outMsg.Message.ID, outMsg.Message.Chat, text)
	return true
}

func sendPokemonPic(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, pokemon process.GameData) (ok bool) {
	ok = false
	// send a photo
	file, err := os.Open(pokemon.AvatarFile)
	if err != nil {
		fmt.Printf("Error opening file: %s\n", err)
		ok = false
		return
	}
	defer file.Close()
	caption := fmt.Sprintf("# %d: %s\n暱稱: %v\n屬性: %s %s\n",
		pokemon.Number, pokemon.Name, pokemon.Nickname, pokemon.Type1, pokemon.Type2)
	for _, evolve := range pokemon.Evolve {
		caption += fmt.Sprintf("進化: /pm%s\n", evolve)
	}
	caption += fmt.Sprintf("")
	fmt.Println(caption)
	if api == nil || chat == nil {
		fmt.Println("Error sending pic, err = no api or chat")
		return false
	}
	photo := api.NewOutgoingPhoto(tbotapi.NewRecipientFromChat(*chat), "pokemon.png", file)
	photo.SetCaption(caption)
	outMsg, err := photo.Send()
	if err != nil {
		fmt.Printf("Error sending photo: %s\n", err)
		return
	}
	fmt.Printf("->%d, To:\t%s, (Photo)\n", outMsg.Message.ID, outMsg.Message.Chat)
	ok = true
	return
}

func sendSticker(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, id string) (ok bool) {
	outMsg, err := api.NewOutgoingStickerResend(tbotapi.NewRecipientFromChat(*chat), id).Send()
	if err != nil {
		fmt.Printf("Error sending sticker: %s, err = %s\n", id, err)
		return false
	}
	fmt.Printf("->%d, To:\t%s, sticker %s\n", outMsg.Message.ID, outMsg.Message.Chat, id)
	return true
}

// Command parse tg command line
func Command(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, msg string) (ok bool) {

	fmt.Println("- raw msg = " + msg)

	ok = false
	result := strings.Fields(msg)
	if len(result) == 0 {
		return
	}
	command := result[0]
	lowerCmd := strings.ToLower(command)
	lowerCmd = strings.Replace(lowerCmd, "@pgoplusbot", "", -1)
	// fmt.Println("lowerCmd: " + lowerCmd)
	if lowerCmd == "/help" || lowerCmd == "/start" {
		text := "/pm 名字, 暱稱, 編號"
		sendText(api, chat, text)
		ok = true
		return
	}

	msg = strings.TrimPrefix(msg, command)
	msg = strings.TrimSpace(msg)

	if len(msg) == 0 && strings.HasPrefix(lowerCmd, "/pm") {
		msg = strings.Replace(lowerCmd, "/pm", "", -1)
		if len(msg) == 0 {
			return
		}
	} else if len(msg) == 0 {
		msg = lowerCmd
	}
	fmt.Printf("msg = %s, len = %d\n", msg, len(msg))

	pokemon, found := process.FindPokemon(gameData, msg)
	if !found {
		text := "醒醒吧，你沒有" + msg
		sendText(api, chat, text)
		fmt.Println(text)
		return
	}
	ok = true

	sendPokemonPic(api, chat, pokemon)
	return ok
}

func startBot() {
	updateFunc := func(update tbotapi.Update, api *tbotapi.TelegramBotAPI) {
		switch update.Type() {
		case tbotapi.MessageUpdate:
			msg := update.Message
			typ := msg.Type()
			text := "(nil)"
			if typ == tbotapi.StickerMessage {
				sticker := update.Message.Sticker
				fmt.Printf("\tSticker id: %s size: %d\n",
					sticker.FileBase.ID, sticker.FileBase.Size)
			}
			if typ == tbotapi.TextMessage {
				text = *msg.Text
			}
			fmt.Printf("<-%d, From:\t%s, Text: %s \n", msg.ID, msg.Chat, text)
			if typ != tbotapi.TextMessage {
				//ignore non-text messages for now
				fmt.Println("Ignoring non-text message")
				return
			}
			// Note: Bots cannot receive from channels, at least no text messages. So we don't have to distinguish anything here

			// display the incoming message
			// msg.Chat implements fmt.Stringer, so it'll display nicely
			// we know it's a text message, so we can safely use the Message.Text pointer
			// fmt.Printf("<-%d, From:\t%s, Text: %s \n", msg.ID, msg.Chat, *msg.Text)

			Command(api, &msg.Chat, *msg.Text)
		case tbotapi.InlineQueryUpdate:
			fmt.Println("Ignoring received inline query: ", update.InlineQuery.Query)
		case tbotapi.ChosenInlineResultUpdate:
			fmt.Println("Ignoring chosen inline query result (ID): ", update.ChosenInlineResult.ID)
		default:
			fmt.Printf("Ignoring unknown Update type.")
		}
	}

	// run the bot, this will block
	boilerplate.RunBot(apiToken, updateFunc, "pgoplusbot", "Reply Pokemon Go information")
}
<commit_msg>disable replied messaged<commit_after>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/ElvisChiang/tgpokemongobot/process"
	"github.com/mrd0ll4r/tbotapi"
	"github.com/mrd0ll4r/tbotapi/examples/boilerplate"
)

// DEBUG show verbose debug msg
const DEBUG = true

var pokedexFile = "./data/pokedex.csv"

var gameData []process.GameData

func main() {
	ok := false
	gameData, ok = process.LoadGameData(pokedexFile)
	if !ok {
		fmt.Printf("Game data loading fail\n")
		return
	}
	if DEBUG {
		for _, data := range gameData {
			fmt.Printf("#%d: %s %v %v [%s/%s] %s\n",
				data.Number,
				data.Name,
				data.Evolve,
				data.Nickname,
				data.Type1, data.Type2,
				data.AvatarFile)
		}
	}
	startBot()
	// Command(nil, nil, "/pm")
	// Command(nil, nil, "/pm 1")
	// Command(nil, nil, "/pm 031")
	// Command(nil, nil, "/pm 種子")
	// Command(nil, nil, "/pm23")
	// Command(nil, nil, "123")
	// Command(nil, nil, "456")
	// Command(nil, nil, "種子")
	// Command(nil, nil, "/help")
}

func sendText(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, text string) (ok bool) {
	if api == nil || chat == nil {
		fmt.Printf("Error sending text: %s, err = no api or chat\n", text)
		return false
	}
	outMsg, err := api.NewOutgoingMessage(tbotapi.NewRecipientFromChat(*chat), text).Send()
	if err != nil {
		fmt.Printf("Error sending text: %s, err = %s\n", text, err)
		return false
	}
	fmt.Printf("->%d, To:\t%s, %s\n", outMsg.Message.ID, outMsg.Message.Chat, text)
	return true
}

func sendPokemonPic(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, pokemon process.GameData) (ok bool) {
	ok = false
	// send a photo
	file, err := os.Open(pokemon.AvatarFile)
	if err != nil {
		fmt.Printf("Error opening file: %s\n", err)
		ok = false
		return
	}
	defer file.Close()
	caption := fmt.Sprintf("# %d: %s\n暱稱: %v\n屬性: %s %s\n",
		pokemon.Number, pokemon.Name, pokemon.Nickname, pokemon.Type1, pokemon.Type2)
	for _, evolve := range pokemon.Evolve {
		caption += fmt.Sprintf("進化: /pm%s\n", evolve)
	}
	caption += fmt.Sprintf("")
	fmt.Println(caption)
	if api == nil || chat == nil {
		fmt.Println("Error sending pic, err = no api or chat")
		return false
	}
	photo := api.NewOutgoingPhoto(tbotapi.NewRecipientFromChat(*chat), "pokemon.png", file)
	photo.SetCaption(caption)
	outMsg, err := photo.Send()
	if err != nil {
		fmt.Printf("Error sending photo: %s\n", err)
		return
	}
	fmt.Printf("->%d, To:\t%s, (Photo)\n", outMsg.Message.ID, outMsg.Message.Chat)
	ok = true
	return
}

func sendSticker(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, id string) (ok bool) {
	outMsg, err := api.NewOutgoingStickerResend(tbotapi.NewRecipientFromChat(*chat), id).Send()
	if err != nil {
		fmt.Printf("Error sending sticker: %s, err = %s\n", id, err)
		return false
	}
	fmt.Printf("->%d, To:\t%s, sticker %s\n", outMsg.Message.ID, outMsg.Message.Chat, id)
	return true
}

// Command parse tg command line
func Command(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, msg string) (ok bool) {

	fmt.Println("- raw msg = " + msg)

	ok = false
	result := strings.Fields(msg)
	if len(result) == 0 {
		return
	}
	command := result[0]
	lowerCmd := strings.ToLower(command)
	lowerCmd = strings.Replace(lowerCmd, "@pgoplusbot", "", -1)
	// fmt.Println("lowerCmd: " + lowerCmd)
	if lowerCmd == "/help" || lowerCmd == "/start" {
		text := "/pm 名字, 暱稱, 編號"
		sendText(api, chat, text)
		ok = true
		return
	}

	msg = strings.TrimPrefix(msg, command)
	msg = strings.TrimSpace(msg)

	if len(msg) == 0 && strings.HasPrefix(lowerCmd, "/pm") {
		msg = strings.Replace(lowerCmd, "/pm", "", -1)
		if len(msg) == 0 {
			return
		}
	} else if len(msg) == 0 {
		msg = lowerCmd
	}
	fmt.Printf("msg = %s, len = %d\n", msg, len(msg))

	pokemon, found := process.FindPokemon(gameData, msg)
	if !found {
		text := "醒醒吧，你沒有" + msg
		sendText(api, chat, text)
		fmt.Println(text)
		return
	}
	ok = true

	sendPokemonPic(api, chat, pokemon)
	return ok
}

func startBot() {
	updateFunc := func(update tbotapi.Update, api *tbotapi.TelegramBotAPI) {
		switch update.Type() {
		case tbotapi.MessageUpdate:
			msg := update.Message
			typ := msg.Type()
			text := "(nil)"
			if typ == tbotapi.StickerMessage {
				sticker := update.Message.Sticker
				fmt.Printf("\tSticker id: %s size: %d\n",
					sticker.FileBase.ID, sticker.FileBase.Size)
			}
			if typ == tbotapi.TextMessage {
				text = *msg.Text
			}
			fmt.Printf("<-%d, From:\t%s, Text: %s \n", msg.ID, msg.Chat, text)
			if typ != tbotapi.TextMessage {
				//ignore non-text messages for now
				fmt.Println("Ignoring non-text message")
				return
			}
			if msg.IsReply() {
				fmt.Println("Ignoring replied message")
				return
			}

			// Note: Bots cannot receive from channels, at least no text messages. So we don't have to distinguish anything here

			// display the incoming message
			// msg.Chat implements fmt.Stringer, so it'll display nicely
			// we know it's a text message, so we can safely use the Message.Text pointer
			// fmt.Printf("<-%d, From:\t%s, Text: %s \n", msg.ID, msg.Chat, *msg.Text)

			Command(api, &msg.Chat, *msg.Text)
		case tbotapi.InlineQueryUpdate:
			fmt.Println("Ignoring received inline query: ", update.InlineQuery.Query)
		case tbotapi.ChosenInlineResultUpdate:
			fmt.Println("Ignoring chosen inline query result (ID): ", update.ChosenInlineResult.ID)
		default:
			fmt.Printf("Ignoring unknown Update type.")
		}
	}

	// run the bot, this will block
	boilerplate.RunBot(apiToken, updateFunc, "pgoplusbot", "Reply Pokemon Go information")
}
<|endoftext|>


<commit_before>package slackbot

import (
	"fmt"
	"time"

	"golang.org/x/net/context"

	"github.com/nlopes/slack"
)

func New(slackToken string) *Bot {
	b := &Bot{Client: slack.New(slackToken)}
	return b
}

type Bot struct {
	SimpleRouter
	// Routes to be matched, in order.
	routes []*Route
	// Slack UserID of the bot UserID
	botUserID string
	// Slack API
	Client *slack.Client
	RTM    *slack.RTM
}

// Run start the bot
func (b *Bot) Run() {
	b.RTM = b.Client.NewRTM()
	go b.RTM.ManageConnection()
	for {
		select {
		case msg := <-b.RTM.IncomingEvents:
			ctx := context.Background()
			ctx = AddBotToContext(ctx, b)
			switch ev := msg.Data.(type) {
			case *slack.ConnectedEvent:
				fmt.Printf("Connected: %#v\n", ev.Info.User)
				b.SetBotID(ev.Info.User.ID)
			case *slack.MessageEvent:
				// ignore messages from the current user, the bot user
				if b.botUserID == ev.User {
					continue
				}

				fmt.Printf("Message: %#v\n", ev.Text)
				ctx = AddMessageToContext(ctx, ev)
				var match RouteMatch
				if matched, ctx := b.Match(ctx, &match); matched {
					match.Handler(ctx)
				}

			case *slack.RTMError:
				fmt.Printf("Error: %s\n", ev.Error())

			case *slack.InvalidAuthEvent:
				fmt.Printf("Invalid credentials")
				break

			default:

				// Ignore other events..
				// fmt.Printf("Unexpected: %v\n", msg.Data)
			}
		}
	}

}

// Reply to a message and simulate typing through the realtime messaging API
func (b *Bot) ReplyAndType(evt *slack.MessageEvent, text string) {
	b.Type(evt.Channel, text)
	b.Reply(evt, text)
}

// Reply to a message through the realtime messaging API
func (b *Bot) Reply(evt *slack.MessageEvent, text string) {
	b.RTM.SendMessage(b.RTM.NewOutgoingMessage(text, evt.Channel))
}

// Type sends a typing message and calls time.Sleep to simulate a delay
func (b *Bot) Type(channel, text string) {
	sleepDuration := time.Minute * time.Duration(len(text)) / 3000
	b.RTM.SendMessage(b.RTM.NewTypingMessage(channel))
	time.Sleep(sleepDuration)
}
<commit_msg>Add exported BotUserID methods to enable consumers to get the bot user id.<commit_after>package slackbot

import (
	"fmt"
	"time"

	"golang.org/x/net/context"

	"github.com/nlopes/slack"
)

func New(slackToken string) *Bot {
	b := &Bot{Client: slack.New(slackToken)}
	return b
}

type Bot struct {
	SimpleRouter
	// Routes to be matched, in order.
	routes []*Route
	// Slack UserID of the bot UserID
	botUserID string
	// Slack API
	Client *slack.Client
	RTM    *slack.RTM
}

// Run start the bot
func (b *Bot) Run() {
	b.RTM = b.Client.NewRTM()
	go b.RTM.ManageConnection()
	for {
		select {
		case msg := <-b.RTM.IncomingEvents:
			ctx := context.Background()
			ctx = AddBotToContext(ctx, b)
			switch ev := msg.Data.(type) {
			case *slack.ConnectedEvent:
				fmt.Printf("Connected: %#v\n", ev.Info.User)
				b.SetBotID(ev.Info.User.ID)
			case *slack.MessageEvent:
				// ignore messages from the current user, the bot user
				if b.botUserID == ev.User {
					continue
				}

				fmt.Printf("Message: %#v\n", ev.Text)
				ctx = AddMessageToContext(ctx, ev)
				var match RouteMatch
				if matched, ctx := b.Match(ctx, &match); matched {
					match.Handler(ctx)
				}

			case *slack.RTMError:
				fmt.Printf("Error: %s\n", ev.Error())

			case *slack.InvalidAuthEvent:
				fmt.Printf("Invalid credentials")
				break

			default:

				// Ignore other events..
				// fmt.Printf("Unexpected: %v\n", msg.Data)
			}
		}
	}

}

// Reply to a message and simulate typing through the realtime messaging API
func (b *Bot) ReplyAndType(evt *slack.MessageEvent, text string) {
	b.Type(evt.Channel, text)
	b.Reply(evt, text)
}

// Reply to a message through the realtime messaging API
func (b *Bot) Reply(evt *slack.MessageEvent, text string) {
	b.RTM.SendMessage(b.RTM.NewOutgoingMessage(text, evt.Channel))
}

// Type sends a typing message and calls time.Sleep to simulate a delay
func (b *Bot) Type(channel, text string) {
	sleepDuration := time.Minute * time.Duration(len(text)) / 3000
	b.RTM.SendMessage(b.RTM.NewTypingMessage(channel))
	time.Sleep(sleepDuration)
}

func (b *Bot) BotUserID() string {
	return b.botUserID
}

func (b *Bot) SetBotID(ID string) {
	b.botUserID = ID
}
<|endoftext|>


<commit_before>package tlbot

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"strconv"
)

const baseURL = "https://api.telegram.org/bot"

type ParseMode string

// Parse modes
const (
	ModeNone     ParseMode = ""
	ModeMarkdown ParseMode = "markdown"
)

// Bot represent a Telegram bot.
type Bot struct {
	token string
	Info  User
}

// New creates a new Telegram bot with the given token, which is given by
// Botfather. See https://core.telegram.org/bots#botfather
func New(token string) Bot {
	u, _ := getMe(token)

	return Bot{token: token, Info: u}
}

// Listen listens on the given address addr and returns a read-only Message
// channel.
func (b Bot) Listen(addr string) <-chan Message {
	messageCh := make(chan Message)

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		defer w.WriteHeader(http.StatusOK)

		var u Update
		if err := json.NewDecoder(req.Body).Decode(&u); err != nil {
			log.Printf("error decoding request body: %v\n", err)
			return

		}
		messageCh <- u.Payload
	})

	go func() {
		// ListenAndServe always returns non-nil error
		err := http.ListenAndServe(addr, mux)
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}()

	return messageCh
}

// SetWebhook assigns bot's webhook url with the given url.
func (b Bot) SetWebhook(webhook string) error {
	urlvalues := url.Values{"url": {webhook}}
	resp, err := http.PostForm(baseURL+b.token+"/setWebhook", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		ErrCode int    `json:"errorcode"`
		Desc    string `json:"description"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return err
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

// SendMessage sends text message to the recipient. Callers can send plain
// text or markdown messages by setting mode parameter.
func (b Bot) SendMessage(recipient int, message string, mode ParseMode, preview bool, opts *SendOptions) error {
	urlvalues := url.Values{
		"chat_id":                  {strconv.Itoa(recipient)},
		"text":                     {message},
		"parse_mode":               {string(mode)},
		"disable_web_page_preview": {strconv.FormatBool(!preview)},
	}
	if opts != nil && (opts.ReplyMarkup.Keyboard != nil || opts.ReplyMarkup.ForceReply || opts.ReplyMarkup.Hide) {
		replymarkup, _ := json.Marshal(opts.ReplyMarkup)
		urlvalues.Set("reply_markup", string(replymarkup))
	}

	resp, err := http.PostForm(baseURL+b.token+"/sendMessage", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return err
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

// TODO(ig): implement
func (b Bot) forwardMessage(recipient User, message Message) error {
	panic("not implemented yet")
}

// SendPhoto sends given photo to recipient. Only remote URLs are supported for now.
// A trivial example is:
//
//  b := bot.New("your-token-here")
//  photo := bot.Photo{FileURL: "http://i.imgur.com/6S9naG6.png"}
//  err := b.SendPhoto(recipient, photo, "sample image", nil)
//
func (b Bot) SendPhoto(recipient int, photo Photo, caption string, opts *SendOptions) error {
	// TODO(ig): implement sending already sent photos
	if photo.Exists() {
		panic("files reside in telegram servers can not be sent for now.")
	}

	// TODO(ig): implement local file upload
	if photo.IsLocal() {
		panic("local files can not be sent for now.")
	}

	resp, err := http.Get(photo.FileURL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Fetch failed (errcode: %v). Remote URL: '%v'", resp.StatusCode, photo.FileURL)
	}

	var buf bytes.Buffer
	w := multipart.NewWriter(&buf)
	part, err := w.CreateFormFile("photo", "image.jpg")
	if err != nil {
		return err
	}
	if _, err := io.Copy(part, resp.Body); err != nil {
		return err
	}

	w.WriteField("chat_id", strconv.Itoa(recipient))
	if err := w.Close(); err != nil {
		return err
	}

	resp, err = http.Post(baseURL+b.token+"/sendPhoto", w.FormDataContentType(), &buf)
	if err != nil {
		return fmt.Errorf("Error while sending image to Telegram servers: %v", err)
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		ErrCode int    `json:"error_code"`
		Desc    string `json:"description"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return fmt.Errorf("Error while decoding response: %v", err)
	}
	if !r.OK {
		return fmt.Errorf("Error returned from Telegram servers after sending photo: %v (ErrorCode: %v)", r.Desc, r.ErrCode)
	}
	return nil
}

// TODO(ig): implement
//
// SendAudio sends audio files, if you want Telegram clients to display
// them in the music player. audio must be in the .mp3 format and must not
// exceed 50 MB in size.
func (b Bot) sendAudio(recipient User, audio Audio, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
// SendDocument sends general files. Documents must not exceed 50 MB in size.
func (b Bot) sendDocument(recipient User, document Document, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
//SendSticker sends stickers with .webp extensions.
func (b Bot) sendSticker(recipient User, sticker Sticker, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
// SendVideo sends video files. Telegram clients support mp4 videos (other
// formats may be sent as Document). Video files must not exceed 50 MB in size.
func (b Bot) sendVideo(recipient User, video Video, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
// SendVoice sends audio files, if you want Telegram clients to display
// the file as a playable voice message. For this to work, your audio must be
// in an .ogg file encoded with OPUS (other formats may be sent as Audio or
// Document). audio must not exceed 50 MB in size.
func (b Bot) sendVoice(recipient User, audio Audio, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
// SendLocation sends location point on the map.
func (b Bot) SendLocation(recipient int, location Location, opts *SendOptions) error {
	urlvalues := url.Values{
		"chat_id":   {strconv.Itoa(recipient)},
		"latitude":  {strconv.FormatFloat(location.Lat, 'f', -1, 64)},
		"longitude": {strconv.FormatFloat(location.Long, 'f', -1, 64)},
	}
	resp, err := http.PostForm(baseURL+b.token+"/sendLocation", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return err
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

// SendVenue sends information about a venue.
func (b Bot) SendVenue(recipient int, venue Venue, opts *SendOptions) error {
	urlvalues := url.Values{
		"chat_id":   {strconv.Itoa(recipient)},
		"latitude":  {strconv.FormatFloat(venue.Location.Lat, 'f', -1, 64)},
		"longitude": {strconv.FormatFloat(venue.Location.Long, 'f', -1, 64)},
		"title":     {venue.Title},
		"address":   {venue.Address},
	}
	resp, err := http.PostForm(baseURL+b.token+"/sendVenue", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return err
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

// SendChatAction broadcasts type of action to recipient, such as `typing`,
// `uploading a photo` etc.
func (b Bot) SendChatAction(recipient int, action Action) error {
	urlvalues := url.Values{
		"chat_id": {strconv.Itoa(recipient)},
		"action":  {string(action)},
	}
	resp, err := http.PostForm(baseURL+b.token+"/sendChatAction", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		ErrCode int    `json:"error_code"`
		Desc    string `json:"description"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return nil
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

type SendOptions struct {
	// If the message is a reply, ID of the original message
	ReplyToMessageID int

	ReplyMarkup ReplyMarkup
}

func getMe(token string) (User, error) {
	resp, err := http.PostForm(baseURL+token+"/getMe", url.Values{})
	if err != nil {
		return User{}, err
	}
	defer resp.Body.Close()
	var r struct {
		OK      bool   `json:"ok"`
		User    User   `json:"result"`
		Desc    string `json:"description"`
		ErrCode int    `json:"error_code"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return User{}, err
	}
	if !r.OK {
		return User{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.User, nil
}
<commit_msg>bot: add GetFile, GetFileDownloadURL methods<commit_after>package tlbot

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"strconv"
)

const baseURL = "https://api.telegram.org/bot"

type ParseMode string

// Parse modes
const (
	ModeNone     ParseMode = ""
	ModeMarkdown ParseMode = "markdown"
)

// Bot represent a Telegram bot.
type Bot struct {
	token string
	Info  User
}

// New creates a new Telegram bot with the given token, which is given by
// Botfather. See https://core.telegram.org/bots#botfather
func New(token string) Bot {
	u, _ := getMe(token)

	return Bot{token: token, Info: u}
}

// Listen listens on the given address addr and returns a read-only Message
// channel.
func (b Bot) Listen(addr string) <-chan Message {
	messageCh := make(chan Message)

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		defer w.WriteHeader(http.StatusOK)

		var u Update
		if err := json.NewDecoder(req.Body).Decode(&u); err != nil {
			log.Printf("error decoding request body: %v\n", err)
			return

		}
		messageCh <- u.Payload
	})

	go func() {
		// ListenAndServe always returns non-nil error
		err := http.ListenAndServe(addr, mux)
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}()

	return messageCh
}

// SetWebhook assigns bot's webhook url with the given url.
func (b Bot) SetWebhook(webhook string) error {
	urlvalues := url.Values{"url": {webhook}}
	resp, err := http.PostForm(baseURL+b.token+"/setWebhook", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		ErrCode int    `json:"errorcode"`
		Desc    string `json:"description"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return err
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

// SendMessage sends text message to the recipient. Callers can send plain
// text or markdown messages by setting mode parameter.
func (b Bot) SendMessage(recipient int, message string, opts *SendOptions) error {
	params := url.Values{
		"chat_id": {strconv.Itoa(recipient)},
		"text":    {message},
	}

	mapSendOptions(&params, opts)

	resp, err := http.PostForm(baseURL+b.token+"/sendMessage", params)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return err
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

// TODO(ig): implement
func (b Bot) forwardMessage(recipient User, message Message) error {
	panic("not implemented yet")
}

// SendPhoto sends given photo to recipient. Only remote URLs are supported for now.
// A trivial example is:
//
//  b := bot.New("your-token-here")
//  photo := bot.Photo{URL: "http://i.imgur.com/6S9naG6.png"}
//  err := b.SendPhoto(recipient, photo, "sample image", nil)
//
func (b Bot) SendPhoto(recipient int, photo Photo, opts *SendOptions) error {
	params := url.Values{}
	params.Set("chat_id", strconv.Itoa(recipient))
	params.Set("caption", photo.Caption)

	mapSendOptions(&params, opts)

	resp, err := http.Get(photo.URL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Fetch failed (errcode: %v). Remote URL: '%v'", resp.StatusCode, photo.URL)
	}

	var buf bytes.Buffer
	w := multipart.NewWriter(&buf)
	part, err := w.CreateFormFile("photo", "image.jpg")
	if err != nil {
		return err
	}
	if _, err := io.Copy(part, resp.Body); err != nil {
		return err
	}

	for k, v := range params {
		w.WriteField(k, v[0])
	}

	if err := w.Close(); err != nil {
		return err
	}

	resp, err = http.Post(baseURL+b.token+"/sendPhoto", w.FormDataContentType(), &buf)
	if err != nil {
		return fmt.Errorf("Error while sending image to Telegram servers: %v", err)
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		ErrCode int    `json:"error_code"`
		Desc    string `json:"description"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return fmt.Errorf("Error while decoding response: %v", err)
	}
	if !r.OK {
		return fmt.Errorf("Error returned from Telegram servers after sending photo: %v (ErrorCode: %v)", r.Desc, r.ErrCode)
	}
	return nil
}

// TODO(ig): implement
//
// SendAudio sends audio files, if you want Telegram clients to display
// them in the music player. audio must be in the .mp3 format and must not
// exceed 50 MB in size.
func (b Bot) sendAudio(recipient User, audio Audio, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
// SendDocument sends general files. Documents must not exceed 50 MB in size.
func (b Bot) sendDocument(recipient User, document Document, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
//SendSticker sends stickers with .webp extensions.
func (b Bot) sendSticker(recipient User, sticker Sticker, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
// SendVideo sends video files. Telegram clients support mp4 videos (other
// formats may be sent as Document). Video files must not exceed 50 MB in size.
func (b Bot) sendVideo(recipient User, video Video, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
// SendVoice sends audio files, if you want Telegram clients to display
// the file as a playable voice message. For this to work, your audio must be
// in an .ogg file encoded with OPUS (other formats may be sent as Audio or
// Document). audio must not exceed 50 MB in size.
func (b Bot) sendVoice(recipient User, audio Audio, opts *SendOptions) error {
	panic("not implemented yet")
}

// TODO(ig): implement
//
// SendLocation sends location point on the map.
func (b Bot) SendLocation(recipient int, location Location, opts *SendOptions) error {
	urlvalues := url.Values{
		"chat_id":   {strconv.Itoa(recipient)},
		"latitude":  {strconv.FormatFloat(location.Lat, 'f', -1, 64)},
		"longitude": {strconv.FormatFloat(location.Long, 'f', -1, 64)},
	}
	resp, err := http.PostForm(baseURL+b.token+"/sendLocation", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return err
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

// SendVenue sends information about a venue.
func (b Bot) SendVenue(recipient int, venue Venue, opts *SendOptions) error {
	urlvalues := url.Values{
		"chat_id":   {strconv.Itoa(recipient)},
		"latitude":  {strconv.FormatFloat(venue.Location.Lat, 'f', -1, 64)},
		"longitude": {strconv.FormatFloat(venue.Location.Long, 'f', -1, 64)},
		"title":     {venue.Title},
		"address":   {venue.Address},
	}
	resp, err := http.PostForm(baseURL+b.token+"/sendVenue", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return err
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

// SendChatAction broadcasts type of action to recipient, such as `typing`,
// `uploading a photo` etc.
func (b Bot) SendChatAction(recipient int, action Action) error {
	urlvalues := url.Values{
		"chat_id": {strconv.Itoa(recipient)},
		"action":  {string(action)},
	}
	resp, err := http.PostForm(baseURL+b.token+"/sendChatAction", urlvalues)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		ErrCode int    `json:"error_code"`
		Desc    string `json:"description"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return nil
	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return nil
}

type SendOptions struct {
	ReplyTo int

	ParseMode ParseMode

	DisableWebPagePreview bool

	DisableNotification bool

	ReplyMarkup ReplyMarkup
}

func (b Bot) GetFile(fileID string) (File, error) {
	urlvalues := url.Values{
		"file_id": {fileID},
	}

	resp, err := http.PostForm(baseURL+b.token+"/getFile", urlvalues)
	if err != nil {
		return File{}, err
	}
	defer resp.Body.Close()

	var r struct {
		OK      bool   `json:"ok"`
		Desc    string `json:"description"`
		ErrCode int    `json:"errorcode"`
		File    File   `json:"result"`
	}

	err = json.NewDecoder(resp.Body).Decode(&r)
	if err != nil {
		return File{}, err
	}

	if !r.OK {
		return File{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.File, nil
}

func (b Bot) GetFileDownloadURL(fileID string) (string, error) {
	f, err := b.GetFile(fileID)
	if err != nil {
		return "", err
	}

	u := "https://api.telegram.org/file/bot" + b.token + "/" + f.FilePath
	return u, nil
}

func getMe(token string) (User, error) {
	resp, err := http.PostForm(baseURL+token+"/getMe", url.Values{})
	if err != nil {
		return User{}, err
	}
	defer resp.Body.Close()
	var r struct {
		OK      bool   `json:"ok"`
		User    User   `json:"result"`
		Desc    string `json:"description"`
		ErrCode int    `json:"error_code"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return User{}, err
	}
	if !r.OK {
		return User{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.User, nil
}

func mapSendOptions(m *url.Values, opts *SendOptions) {
	if opts == nil {
		return
	}

	if opts.ReplyTo != 0 {
		m.Set("reply_to_message_id", strconv.Itoa(opts.ReplyTo))
	}

	if opts.DisableWebPagePreview {
		m.Set("disable_web_page_preview", "true")
	}

	if opts.DisableNotification {
		m.Set("disable_notification", "true")
	}

	if opts.ParseMode != ModeNone {
		m.Set("parse_mode", string(opts.ParseMode))
	}

	// TODO: map ReplyMarkup options as well
}
<|endoftext|>


<commit_before>package rolerules

import (
	"crypto/sha256"
	"fmt"
	"strings"

	"github.com/bwmarrin/discordgo"
	"github.com/camd67/moebot/moebot_bot/util"
	"github.com/camd67/moebot/moebot_bot/util/db/types"
)

type Confirmation struct {
	ComPrefix string
}

func (r *Confirmation) Check(session *discordgo.Session, action *RoleAction) (success bool, message string) {
	if action.Action == RoleRemove {
		return true, ""
	}
	var confirmCodes []string
	for _, param := range strings.Split(action.OriginalMessage.Content, " ") {
		if strings.HasPrefix(param, "-") {
			confirmCodes = append(confirmCodes, param)
		}
	}
	// we only want to check for a confirmation when we have an actual confirmation message and they don't already have the role
	if action.Role.ConfirmationMessage.Valid && action.Role.ConfirmationMessage.String != "" && !util.StrContains(action.Member.Roles, action.Role.RoleUid, util.CaseSensitive) {
		// no confirm codes provided, given them their confirmation code
		if len(confirmCodes) <= 0 {
			err := r.sendConfirmationMessage(session, action.Channel, action.Role, action.Member)
			if err != nil {
				return false, "Sorry, I couldn't send you a PM! Please check your settings to allow direct messages from users on this server."
			}
			return true, action.Member.User.Mention() + " check your PM's for further instructions!"
		}

		session.ChannelMessageDelete(action.Channel.ID, action.OriginalMessage.ID)

		if action.Role.ConfirmationSecurityAnswer.Valid && action.Role.ConfirmationSecurityAnswer.String != "" {
			if len(confirmCodes) != 2 {
				return false, "Sorry, you need to insert a confirmation code and security answer to access " +
					"this role. Use `" + r.ComPrefix + " " + action.Role.Trigger.String + "` to receive a DM containing detailed instructions."
			}
			if !util.StrContains(confirmCodes, action.Role.ConfirmationSecurityAnswer.String, util.CaseSensitive) ||
				!util.StrContains(confirmCodes, "-"+r.getRoleCode(action.Role.RoleUid, action.Member.User.ID), util.CaseSensitive) {
				return false, "Sorry, you need to insert the correct confirmation code to access this role."
			}
		} else {
			if len(confirmCodes) != 1 {
				return false, "Sorry, you need to insert a confirmation code to access this role. Use `" +
					r.ComPrefix + " " + action.Role.Trigger.String + "` to receive a DM containing detailed instructions."
			}
			if !util.StrContains(confirmCodes, "-"+r.getRoleCode(action.Role.RoleUid, action.Member.User.ID), util.CaseSensitive) {
				return false, "Sorry, you need to insert the correct confirmation code to access this role."
			}
		}
	}
	return true, ""
}

func (r *Confirmation) Apply(session *discordgo.Session, action *RoleAction) (success bool, message string) {
	return true, ""
}

func (r *Confirmation) sendConfirmationMessage(session *discordgo.Session, channel *discordgo.Channel, role *types.Role, member *discordgo.Member) error {
	userChannel, err := session.UserChannelCreate(member.User.ID)
	if err != nil {
		// could log error creating user channel, but seems like it'll clutter the logs for a valid scenario..
		return err
	}
	roleCode := r.getRoleCode(role.RoleUid, member.User.ID)
	var messageText string
	if strings.Contains(strings.ToLower(role.ConfirmationMessage.String), types.RoleCodeSearchText) {
		messageText = strings.Replace(role.ConfirmationMessage.String, types.RoleCodeSearchText, roleCode, -1)
	} else {
		messageText = role.ConfirmationMessage.String + "\nYour confirmation code: `-" + roleCode + "`"
	}
	_, err = session.ChannelMessageSend(userChannel.ID, messageText)
	return err
}

/*
Returns a 6 character role code string that is unique per user and per role.
This should NOT be used for security features as it is not a secure algorithm.
*/
func (r *Confirmation) getRoleCode(roleUID, userUID string) string {
	hash := sha256.New()
	hash.Write([]byte(roleUID + userUID))
	return string(fmt.Sprintf("%x", hash.Sum(nil))[0:types.RoleCodeLength])
}
<commit_msg>fixed confirmation role bug<commit_after>package rolerules

import (
	"crypto/sha256"
	"fmt"
	"strings"

	"github.com/bwmarrin/discordgo"
	"github.com/camd67/moebot/moebot_bot/util"
	"github.com/camd67/moebot/moebot_bot/util/db/types"
)

type Confirmation struct {
	ComPrefix string
}

func (r *Confirmation) Check(session *discordgo.Session, action *RoleAction) (success bool, message string) {
	if action.Action == RoleRemove {
		return true, ""
	}
	var confirmCodes []string
	for _, param := range strings.Split(action.OriginalMessage.Content, " ") {
		if strings.HasPrefix(param, "-") {
			confirmCodes = append(confirmCodes, param)
		}
	}
	// we only want to check for a confirmation when we have an actual confirmation message and they don't already have the role
	if action.Role.ConfirmationMessage.Valid && action.Role.ConfirmationMessage.String != "" && !util.StrContains(action.Member.Roles, action.Role.RoleUid, util.CaseSensitive) {
		// no confirm codes provided, given them their confirmation code
		if len(confirmCodes) <= 0 {
			err := r.sendConfirmationMessage(session, action.Channel, action.Role, action.Member)
			if err != nil {
				return false, "Sorry, I couldn't send you a PM! Please check your settings to allow direct messages from users on this server."
			}
			return false, action.Member.User.Mention() + " check your PM's for further instructions!"
		}

		session.ChannelMessageDelete(action.Channel.ID, action.OriginalMessage.ID)

		if action.Role.ConfirmationSecurityAnswer.Valid && action.Role.ConfirmationSecurityAnswer.String != "" {
			if len(confirmCodes) != 2 {
				return false, "Sorry, you need to insert a confirmation code and security answer to access " +
					"this role. Use `" + r.ComPrefix + " " + action.Role.Trigger.String + "` to receive a DM containing detailed instructions."
			}
			if !util.StrContains(confirmCodes, action.Role.ConfirmationSecurityAnswer.String, util.CaseSensitive) ||
				!util.StrContains(confirmCodes, "-"+r.getRoleCode(action.Role.RoleUid, action.Member.User.ID), util.CaseSensitive) {
				return false, "Sorry, you need to insert the correct confirmation code to access this role."
			}
		} else {
			if len(confirmCodes) != 1 {
				return false, "Sorry, you need to insert a confirmation code to access this role. Use `" +
					r.ComPrefix + " " + action.Role.Trigger.String + "` to receive a DM containing detailed instructions."
			}
			if !util.StrContains(confirmCodes, "-"+r.getRoleCode(action.Role.RoleUid, action.Member.User.ID), util.CaseSensitive) {
				return false, "Sorry, you need to insert the correct confirmation code to access this role."
			}
		}
	}
	return true, ""
}

func (r *Confirmation) Apply(session *discordgo.Session, action *RoleAction) (success bool, message string) {
	return true, ""
}

func (r *Confirmation) sendConfirmationMessage(session *discordgo.Session, channel *discordgo.Channel, role *types.Role, member *discordgo.Member) error {
	userChannel, err := session.UserChannelCreate(member.User.ID)
	if err != nil {
		// could log error creating user channel, but seems like it'll clutter the logs for a valid scenario..
		return err
	}
	roleCode := r.getRoleCode(role.RoleUid, member.User.ID)
	var messageText string
	if strings.Contains(strings.ToLower(role.ConfirmationMessage.String), types.RoleCodeSearchText) {
		messageText = strings.Replace(role.ConfirmationMessage.String, types.RoleCodeSearchText, roleCode, -1)
	} else {
		messageText = role.ConfirmationMessage.String + "\nYour confirmation code: `-" + roleCode + "`"
	}
	_, err = session.ChannelMessageSend(userChannel.ID, messageText)
	return err
}

/*
Returns a 6 character role code string that is unique per user and per role.
This should NOT be used for security features as it is not a secure algorithm.
*/
func (r *Confirmation) getRoleCode(roleUID, userUID string) string {
	hash := sha256.New()
	hash.Write([]byte(roleUID + userUID))
	return string(fmt.Sprintf("%x", hash.Sum(nil))[0:types.RoleCodeLength])
}
<|endoftext|>