<commit_before>package ethchain

import (
	"bytes"
	"container/list"
	"fmt"
	"math/big"
	"os"
	"sync"
	"time"

	"github.com/ethereum/eth-go/ethcrypto"
	"github.com/ethereum/eth-go/ethlog"
	"github.com/ethereum/eth-go/ethreact"
	"github.com/ethereum/eth-go/ethstate"
	"github.com/ethereum/eth-go/ethutil"
	"github.com/ethereum/eth-go/ethwire"
)

var statelogger = ethlog.NewLogger("STATE")

type BlockProcessor interface {
	ProcessBlock(block *Block)
}

type Peer interface {
	Inbound() bool
	LastSend() time.Time
	LastPong() int64
	Host() []byte
	Port() uint16
	Version() string
	PingTime() string
	Connected() *int32
}

type EthManager interface {
	StateManager() *StateManager
	BlockChain() *BlockChain
	TxPool() *TxPool
	Broadcast(msgType ethwire.MsgType, data []interface{})
	Reactor() *ethreact.ReactorEngine
	PeerCount() int
	IsMining() bool
	IsListening() bool
	Peers() *list.List
	KeyManager() *ethcrypto.KeyManager
	ClientIdentity() ethwire.ClientIdentity
	Db() ethutil.Database
}

type StateManager struct {
	// Mutex for locking the block processor. Blocks can only be handled one at a time
	mutex sync.Mutex
	// Canonical block chain
	bc *BlockChain
	// non-persistent key/value memory storage
	mem map[string]*big.Int
	// Proof of work used for validating
	Pow PoW
	// The ethereum manager interface
	Ethereum EthManager
	// The managed states
	// Transiently state. The trans state isn't ever saved, validated and
	// it could be used for setting account nonces without effecting
	// the main states.
	transState *ethstate.State
	// Mining state. The mining state is used purely and solely by the mining
	// operation.
	miningState *ethstate.State

	// The last attempted block is mainly used for debugging purposes
	// This does not have to be a valid block and will be set during
	// 'Process' & canonical validation.
	lastAttemptedBlock *Block
}

func NewStateManager(ethereum EthManager) *StateManager {
	sm := &StateManager{
		mem:      make(map[string]*big.Int),
		Pow:      &EasyPow{},
		Ethereum: ethereum,
		bc:       ethereum.BlockChain(),
	}
	sm.transState = ethereum.BlockChain().CurrentBlock.State().Copy()
	sm.miningState = ethereum.BlockChain().CurrentBlock.State().Copy()

	return sm
}

func (sm *StateManager) CurrentState() *ethstate.State {
	return sm.Ethereum.BlockChain().CurrentBlock.State()
}

func (sm *StateManager) TransState() *ethstate.State {
	return sm.transState
}

func (sm *StateManager) MiningState() *ethstate.State {
	return sm.miningState
}

func (sm *StateManager) NewMiningState() *ethstate.State {
	sm.miningState = sm.Ethereum.BlockChain().CurrentBlock.State().Copy()

	return sm.miningState
}

func (sm *StateManager) BlockChain() *BlockChain {
	return sm.bc
}

func (self *StateManager) ProcessTransactions(coinbase *ethstate.StateObject, state *ethstate.State, block, parent *Block, txs Transactions) (Receipts, Transactions, Transactions, error) {
	var (
		receipts           Receipts
		handled, unhandled Transactions
		totalUsedGas       = big.NewInt(0)
		err                error
	)

done:
	for i, tx := range txs {
		txGas := new(big.Int).Set(tx.Gas)

		cb := state.GetStateObject(coinbase.Address())
		st := NewStateTransition(cb, tx, state, block)
		err = st.TransitionState()
		if err != nil {
			statelogger.Infoln(err)
			switch {
			case IsNonceErr(err):
				err = nil // ignore error
				continue
			case IsGasLimitErr(err):
				unhandled = txs[i:]

				break done
			default:
				statelogger.Infoln(err)
				err = nil
				//return nil, nil, nil, err
			}
		}

		// Notify all subscribers
		self.Ethereum.Reactor().Post("newTx:post", tx)

		// Update the state with pending changes
		state.Update()

		txGas.Sub(txGas, st.gas)
		accumelative := new(big.Int).Set(totalUsedGas.Add(totalUsedGas, txGas))
		receipt := &Receipt{tx, ethutil.CopyBytes(state.Root().([]byte)), accumelative}

		if i < len(block.Receipts()) {
			original := block.Receipts()[i]
			if !original.Cmp(receipt) {
				if ethutil.Config.Diff {
					os.Exit(1)
				}

				return nil, nil, nil, fmt.Errorf("err diff #%d (r) %v ~ %x  <=>  (c) %v ~ %x (%x)\n", i+1, original.CumulativeGasUsed, original.PostState[0:4], receipt.CumulativeGasUsed, receipt.PostState[0:4], receipt.Tx.Hash())
			}
		}

		receipts = append(receipts, receipt)
		handled = append(handled, tx)

		if ethutil.Config.Diff && ethutil.Config.DiffType == "all" {
			state.CreateOutputForDiff()
		}
	}

	parent.GasUsed = totalUsedGas

	return receipts, handled, unhandled, err
}

func (sm *StateManager) Process(block *Block, dontReact bool) (err error) {
	// Processing a blocks may never happen simultaneously
	sm.mutex.Lock()
	defer sm.mutex.Unlock()

	if sm.bc.HasBlock(block.Hash()) {
		return nil
	}

	if !sm.bc.HasBlock(block.PrevHash) {
		return ParentError(block.PrevHash)
	}

	sm.lastAttemptedBlock = block

	var (
		parent = sm.bc.GetBlock(block.PrevHash)
		state  = parent.State()
	)

	// Defer the Undo on the Trie. If the block processing happened
	// we don't want to undo but since undo only happens on dirty
	// nodes this won't happen because Commit would have been called
	// before that.
	defer state.Reset()

	if ethutil.Config.Diff && ethutil.Config.DiffType == "all" {
		fmt.Printf("## %x %x ##\n", block.Hash(), block.Number)
	}

	receipts, err := sm.ApplyDiff(state, parent, block)
	if err != nil {
		return err
	}

	txSha := CreateTxSha(receipts)
	if bytes.Compare(txSha, block.TxSha) != 0 {
		return fmt.Errorf("Error validating tx sha. Received %x, got %x", block.TxSha, txSha)
	}

	// Block validation
	if err = sm.ValidateBlock(block); err != nil {
		statelogger.Errorln("Error validating block:", err)
		return err
	}

	if err = sm.AccumelateRewards(state, block, parent); err != nil {
		statelogger.Errorln("Error accumulating reward", err)
		return err
	}

	state.Update()

	if !block.State().Cmp(state) {
		err = fmt.Errorf("Invalid merkle root.\nrec: %x\nis:  %x", block.State().Trie.Root, state.Trie.Root)
		return
	}

	// Calculate the new total difficulty and sync back to the db
	if sm.CalculateTD(block) {
		// Sync the current block's state to the database and cancelling out the deferred Undo
		state.Sync()

		// Add the block to the chain
		sm.bc.Add(block)

		sm.transState = state.Copy()

		// Create a bloom bin for this block
		filter := sm.createBloomFilter(state)
		// Persist the data
		fk := append([]byte("bloom"), block.Hash()...)
		sm.Ethereum.Db().Put(fk, filter.Bin())

		statelogger.Infof("Added block #%d (%x)\n", block.Number, block.Hash())
		if dontReact == false {
			sm.Ethereum.Reactor().Post("newBlock", block)

			state.Manifest().Reset()
		}

		sm.Ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val})

		sm.Ethereum.TxPool().RemoveInvalid(state)
	} else {
		statelogger.Errorln("total diff failed")
	}

	return nil
}

func (sm *StateManager) ApplyDiff(state *ethstate.State, parent, block *Block) (receipts Receipts, err error) {
	coinbase := state.GetOrNewStateObject(block.Coinbase)
	coinbase.SetGasPool(block.CalcGasLimit(parent))

	// Process the transactions on to current block
	receipts, _, _, err = sm.ProcessTransactions(coinbase, state, block, parent, block.Transactions())
	if err != nil {
		return nil, err
	}

	return receipts, nil
}

func (sm *StateManager) CalculateTD(block *Block) bool {
	uncleDiff := new(big.Int)
	for _, uncle := range block.Uncles {
		uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty)
	}

	// TD(genesis_block) = 0 and TD(B) = TD(B.parent) + sum(u.difficulty for u in B.uncles) + B.difficulty
	td := new(big.Int)
	td = td.Add(sm.bc.TD, uncleDiff)
	td = td.Add(td, block.Difficulty)

	// The new TD will only be accepted if the new difficulty is
	// is greater than the previous.
	if td.Cmp(sm.bc.TD) > 0 {
		// Set the new total difficulty back to the block chain
		sm.bc.SetTotalDifficulty(td)

		return true
	}

	return false
}

// Validates the current block. Returns an error if the block was invalid,
// an uncle or anything that isn't on the current block chain.
// Validation validates easy over difficult (dagger takes longer time = difficult)
func (sm *StateManager) ValidateBlock(block *Block) error {
	// TODO
	// 2. Check if the difficulty is correct

	// Check each uncle's previous hash. In order for it to be valid
	// is if it has the same block hash as the current
	parent := sm.bc.GetBlock(block.PrevHash)
	/*
		for _, uncle := range block.Uncles {
			if bytes.Compare(uncle.PrevHash,parent.PrevHash) != 0 {
				return ValidationError("Mismatch uncle's previous hash. Expected %x, got %x",parent.PrevHash, uncle.PrevHash)
			}
		}
	*/

	diff := block.Time - parent.Time
	if diff < 0 {
		return ValidationError("Block timestamp less then prev block %v (%v - %v)", diff, block.Time, sm.bc.CurrentBlock.Time)
	}

	/* XXX
	// New blocks must be within the 15 minute range of the last block.
	if diff > int64(15*time.Minute) {
		return ValidationError("Block is too far in the future of last block (> 15 minutes)")
	}
	*/

	// Verify the nonce of the block. Return an error if it's not valid
	if !sm.Pow.Verify(block.HashNoNonce(), block.Difficulty, block.Nonce) {
		return ValidationError("Block's nonce is invalid (= %v)", ethutil.Bytes2Hex(block.Nonce))
	}

	return nil
}

func (sm *StateManager) AccumelateRewards(state *ethstate.State, block, parent *Block) error {
	reward := new(big.Int).Set(BlockReward)

	knownUncles := ethutil.Set(parent.Uncles)
	nonces := ethutil.NewSet(block.Nonce)
	for _, uncle := range block.Uncles {
		if nonces.Include(uncle.Nonce) {
			// Error not unique
			return UncleError("Uncle not unique")
		}

		uncleParent := sm.bc.GetBlock(uncle.PrevHash)
		if uncleParent == nil {
			return UncleError("Uncle's parent unknown")
		}

		if uncleParent.Number.Cmp(new(big.Int).Sub(parent.Number, big.NewInt(6))) < 0 {
			return UncleError("Uncle too old")
		}

		if knownUncles.Include(uncle.Hash()) {
			return UncleError("Uncle in chain")
		}

		nonces.Insert(uncle.Nonce)

		r := new(big.Int)
		r.Mul(BlockReward, big.NewInt(15)).Div(r, big.NewInt(16))

		uncleAccount := state.GetAccount(uncle.Coinbase)
		uncleAccount.AddAmount(r)

		reward.Add(reward, new(big.Int).Div(BlockReward, big.NewInt(32)))
	}

	// Get the account associated with the coinbase
	account := state.GetAccount(block.Coinbase)
	// Reward amount of ether to the coinbase address
	account.AddAmount(reward)

	return nil
}

func (sm *StateManager) Stop() {
	sm.bc.Stop()
}

// Manifest will handle both creating notifications and generating bloom bin data
func (sm *StateManager) createBloomFilter(state *ethstate.State) *BloomFilter {
	bloomf := NewBloomFilter(nil)

	for _, msg := range state.Manifest().Messages {
		bloomf.Set(msg.To)
		bloomf.Set(msg.From)
	}

	sm.Ethereum.Reactor().Post("messages", state.Manifest().Messages)

	return bloomf
}

func (sm *StateManager) GetMessages(block *Block) (messages []*ethstate.Message, err error) {
	if !sm.bc.HasBlock(block.PrevHash) {
		return nil, ParentError(block.PrevHash)
	}

	sm.lastAttemptedBlock = block

	var (
		parent = sm.bc.GetBlock(block.PrevHash)
		state  = parent.State().Copy()
	)

	defer state.Reset()

	sm.ApplyDiff(state, parent, block)

	sm.AccumelateRewards(state, block, parent)

	return state.Manifest().Messages, nil
}
<commit_msg>Info => Debug<commit_after>package ethchain

import (
	"bytes"
	"container/list"
	"fmt"
	"math/big"
	"os"
	"sync"
	"time"

	"github.com/ethereum/eth-go/ethcrypto"
	"github.com/ethereum/eth-go/ethlog"
	"github.com/ethereum/eth-go/ethreact"
	"github.com/ethereum/eth-go/ethstate"
	"github.com/ethereum/eth-go/ethutil"
	"github.com/ethereum/eth-go/ethwire"
)

var statelogger = ethlog.NewLogger("STATE")

type BlockProcessor interface {
	ProcessBlock(block *Block)
}

type Peer interface {
	Inbound() bool
	LastSend() time.Time
	LastPong() int64
	Host() []byte
	Port() uint16
	Version() string
	PingTime() string
	Connected() *int32
}

type EthManager interface {
	StateManager() *StateManager
	BlockChain() *BlockChain
	TxPool() *TxPool
	Broadcast(msgType ethwire.MsgType, data []interface{})
	Reactor() *ethreact.ReactorEngine
	PeerCount() int
	IsMining() bool
	IsListening() bool
	Peers() *list.List
	KeyManager() *ethcrypto.KeyManager
	ClientIdentity() ethwire.ClientIdentity
	Db() ethutil.Database
}

type StateManager struct {
	// Mutex for locking the block processor. Blocks can only be handled one at a time
	mutex sync.Mutex
	// Canonical block chain
	bc *BlockChain
	// non-persistent key/value memory storage
	mem map[string]*big.Int
	// Proof of work used for validating
	Pow PoW
	// The ethereum manager interface
	Ethereum EthManager
	// The managed states
	// Transiently state. The trans state isn't ever saved, validated and
	// it could be used for setting account nonces without effecting
	// the main states.
	transState *ethstate.State
	// Mining state. The mining state is used purely and solely by the mining
	// operation.
	miningState *ethstate.State

	// The last attempted block is mainly used for debugging purposes
	// This does not have to be a valid block and will be set during
	// 'Process' & canonical validation.
	lastAttemptedBlock *Block
}

func NewStateManager(ethereum EthManager) *StateManager {
	sm := &StateManager{
		mem:      make(map[string]*big.Int),
		Pow:      &EasyPow{},
		Ethereum: ethereum,
		bc:       ethereum.BlockChain(),
	}
	sm.transState = ethereum.BlockChain().CurrentBlock.State().Copy()
	sm.miningState = ethereum.BlockChain().CurrentBlock.State().Copy()

	return sm
}

func (sm *StateManager) CurrentState() *ethstate.State {
	return sm.Ethereum.BlockChain().CurrentBlock.State()
}

func (sm *StateManager) TransState() *ethstate.State {
	return sm.transState
}

func (sm *StateManager) MiningState() *ethstate.State {
	return sm.miningState
}

func (sm *StateManager) NewMiningState() *ethstate.State {
	sm.miningState = sm.Ethereum.BlockChain().CurrentBlock.State().Copy()

	return sm.miningState
}

func (sm *StateManager) BlockChain() *BlockChain {
	return sm.bc
}

func (self *StateManager) ProcessTransactions(coinbase *ethstate.StateObject, state *ethstate.State, block, parent *Block, txs Transactions) (Receipts, Transactions, Transactions, error) {
	var (
		receipts           Receipts
		handled, unhandled Transactions
		totalUsedGas       = big.NewInt(0)
		err                error
	)

done:
	for i, tx := range txs {
		txGas := new(big.Int).Set(tx.Gas)

		cb := state.GetStateObject(coinbase.Address())
		st := NewStateTransition(cb, tx, state, block)
		err = st.TransitionState()
		if err != nil {
			statelogger.Infoln(err)
			switch {
			case IsNonceErr(err):
				err = nil // ignore error
				continue
			case IsGasLimitErr(err):
				unhandled = txs[i:]

				break done
			default:
				statelogger.Infoln(err)
				err = nil
				//return nil, nil, nil, err
			}
		}

		// Notify all subscribers
		self.Ethereum.Reactor().Post("newTx:post", tx)

		// Update the state with pending changes
		state.Update()

		txGas.Sub(txGas, st.gas)
		accumelative := new(big.Int).Set(totalUsedGas.Add(totalUsedGas, txGas))
		receipt := &Receipt{tx, ethutil.CopyBytes(state.Root().([]byte)), accumelative}

		if i < len(block.Receipts()) {
			original := block.Receipts()[i]
			if !original.Cmp(receipt) {
				if ethutil.Config.Diff {
					os.Exit(1)
				}

				return nil, nil, nil, fmt.Errorf("err diff #%d (r) %v ~ %x  <=>  (c) %v ~ %x (%x)\n", i+1, original.CumulativeGasUsed, original.PostState[0:4], receipt.CumulativeGasUsed, receipt.PostState[0:4], receipt.Tx.Hash())
			}
		}

		receipts = append(receipts, receipt)
		handled = append(handled, tx)

		if ethutil.Config.Diff && ethutil.Config.DiffType == "all" {
			state.CreateOutputForDiff()
		}
	}

	parent.GasUsed = totalUsedGas

	return receipts, handled, unhandled, err
}

func (sm *StateManager) Process(block *Block, dontReact bool) (err error) {
	// Processing a blocks may never happen simultaneously
	sm.mutex.Lock()
	defer sm.mutex.Unlock()

	if sm.bc.HasBlock(block.Hash()) {
		return nil
	}

	if !sm.bc.HasBlock(block.PrevHash) {
		return ParentError(block.PrevHash)
	}

	sm.lastAttemptedBlock = block

	var (
		parent = sm.bc.GetBlock(block.PrevHash)
		state  = parent.State()
	)

	// Defer the Undo on the Trie. If the block processing happened
	// we don't want to undo but since undo only happens on dirty
	// nodes this won't happen because Commit would have been called
	// before that.
	defer state.Reset()

	if ethutil.Config.Diff && ethutil.Config.DiffType == "all" {
		fmt.Printf("## %x %x ##\n", block.Hash(), block.Number)
	}

	receipts, err := sm.ApplyDiff(state, parent, block)
	if err != nil {
		return err
	}

	txSha := CreateTxSha(receipts)
	if bytes.Compare(txSha, block.TxSha) != 0 {
		return fmt.Errorf("Error validating tx sha. Received %x, got %x", block.TxSha, txSha)
	}

	// Block validation
	if err = sm.ValidateBlock(block); err != nil {
		statelogger.Errorln("Error validating block:", err)
		return err
	}

	if err = sm.AccumelateRewards(state, block, parent); err != nil {
		statelogger.Errorln("Error accumulating reward", err)
		return err
	}

	state.Update()

	if !block.State().Cmp(state) {
		err = fmt.Errorf("Invalid merkle root.\nrec: %x\nis:  %x", block.State().Trie.Root, state.Trie.Root)
		return
	}

	// Calculate the new total difficulty and sync back to the db
	if sm.CalculateTD(block) {
		// Sync the current block's state to the database and cancelling out the deferred Undo
		state.Sync()

		// Add the block to the chain
		sm.bc.Add(block)

		sm.transState = state.Copy()

		// Create a bloom bin for this block
		filter := sm.createBloomFilter(state)
		// Persist the data
		fk := append([]byte("bloom"), block.Hash()...)
		sm.Ethereum.Db().Put(fk, filter.Bin())

		statelogger.Debugf("Added block #%d (%x)\n", block.Number, block.Hash())
		if dontReact == false {
			sm.Ethereum.Reactor().Post("newBlock", block)

			state.Manifest().Reset()
		}

		sm.Ethereum.TxPool().RemoveInvalid(state)
	} else {
		statelogger.Errorln("total diff failed")
	}

	return nil
}

func (sm *StateManager) ApplyDiff(state *ethstate.State, parent, block *Block) (receipts Receipts, err error) {
	coinbase := state.GetOrNewStateObject(block.Coinbase)
	coinbase.SetGasPool(block.CalcGasLimit(parent))

	// Process the transactions on to current block
	receipts, _, _, err = sm.ProcessTransactions(coinbase, state, block, parent, block.Transactions())
	if err != nil {
		return nil, err
	}

	return receipts, nil
}

func (sm *StateManager) CalculateTD(block *Block) bool {
	uncleDiff := new(big.Int)
	for _, uncle := range block.Uncles {
		uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty)
	}

	// TD(genesis_block) = 0 and TD(B) = TD(B.parent) + sum(u.difficulty for u in B.uncles) + B.difficulty
	td := new(big.Int)
	td = td.Add(sm.bc.TD, uncleDiff)
	td = td.Add(td, block.Difficulty)

	// The new TD will only be accepted if the new difficulty is
	// is greater than the previous.
	if td.Cmp(sm.bc.TD) > 0 {
		// Set the new total difficulty back to the block chain
		sm.bc.SetTotalDifficulty(td)

		return true
	}

	return false
}

// Validates the current block. Returns an error if the block was invalid,
// an uncle or anything that isn't on the current block chain.
// Validation validates easy over difficult (dagger takes longer time = difficult)
func (sm *StateManager) ValidateBlock(block *Block) error {
	// TODO
	// 2. Check if the difficulty is correct

	// Check each uncle's previous hash. In order for it to be valid
	// is if it has the same block hash as the current
	parent := sm.bc.GetBlock(block.PrevHash)
	/*
		for _, uncle := range block.Uncles {
			if bytes.Compare(uncle.PrevHash,parent.PrevHash) != 0 {
				return ValidationError("Mismatch uncle's previous hash. Expected %x, got %x",parent.PrevHash, uncle.PrevHash)
			}
		}
	*/

	diff := block.Time - parent.Time
	if diff < 0 {
		return ValidationError("Block timestamp less then prev block %v (%v - %v)", diff, block.Time, sm.bc.CurrentBlock.Time)
	}

	/* XXX
	// New blocks must be within the 15 minute range of the last block.
	if diff > int64(15*time.Minute) {
		return ValidationError("Block is too far in the future of last block (> 15 minutes)")
	}
	*/

	// Verify the nonce of the block. Return an error if it's not valid
	if !sm.Pow.Verify(block.HashNoNonce(), block.Difficulty, block.Nonce) {
		return ValidationError("Block's nonce is invalid (= %v)", ethutil.Bytes2Hex(block.Nonce))
	}

	return nil
}

func (sm *StateManager) AccumelateRewards(state *ethstate.State, block, parent *Block) error {
	reward := new(big.Int).Set(BlockReward)

	knownUncles := ethutil.Set(parent.Uncles)
	nonces := ethutil.NewSet(block.Nonce)
	for _, uncle := range block.Uncles {
		if nonces.Include(uncle.Nonce) {
			// Error not unique
			return UncleError("Uncle not unique")
		}

		uncleParent := sm.bc.GetBlock(uncle.PrevHash)
		if uncleParent == nil {
			return UncleError("Uncle's parent unknown")
		}

		if uncleParent.Number.Cmp(new(big.Int).Sub(parent.Number, big.NewInt(6))) < 0 {
			return UncleError("Uncle too old")
		}

		if knownUncles.Include(uncle.Hash()) {
			return UncleError("Uncle in chain")
		}

		nonces.Insert(uncle.Nonce)

		r := new(big.Int)
		r.Mul(BlockReward, big.NewInt(15)).Div(r, big.NewInt(16))

		uncleAccount := state.GetAccount(uncle.Coinbase)
		uncleAccount.AddAmount(r)

		reward.Add(reward, new(big.Int).Div(BlockReward, big.NewInt(32)))
	}

	// Get the account associated with the coinbase
	account := state.GetAccount(block.Coinbase)
	// Reward amount of ether to the coinbase address
	account.AddAmount(reward)

	return nil
}

func (sm *StateManager) Stop() {
	sm.bc.Stop()
}

// Manifest will handle both creating notifications and generating bloom bin data
func (sm *StateManager) createBloomFilter(state *ethstate.State) *BloomFilter {
	bloomf := NewBloomFilter(nil)

	for _, msg := range state.Manifest().Messages {
		bloomf.Set(msg.To)
		bloomf.Set(msg.From)
	}

	sm.Ethereum.Reactor().Post("messages", state.Manifest().Messages)

	return bloomf
}

func (sm *StateManager) GetMessages(block *Block) (messages []*ethstate.Message, err error) {
	if !sm.bc.HasBlock(block.PrevHash) {
		return nil, ParentError(block.PrevHash)
	}

	sm.lastAttemptedBlock = block

	var (
		parent = sm.bc.GetBlock(block.PrevHash)
		state  = parent.State().Copy()
	)

	defer state.Reset()

	sm.ApplyDiff(state, parent, block)

	sm.AccumelateRewards(state, block, parent)

	return state.Manifest().Messages, nil
}
<|endoftext|>


<commit_before>package controllers

import (
    "github.com/orc/db"
    "github.com/orc/sessions"
    "github.com/orc/utils"
    "net/http"
    "strconv"
    "time"
    "github.com/orc/mailer"
)

func (this *GridHandler) RegGroup() {
    userId, err := this.CheckSid()
    if err != nil {
        http.Redirect(this.Response, this.Request, "/", http.StatusUnauthorized)
        return
    }

    request, err := utils.ParseJS(this.Request, this.Response)
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
    }

    groupId, err := strconv.Atoi(request["group_id"].(string))
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
    }

    eventId, err := strconv.Atoi(request["event_id"].(string))
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
    }

    event := this.GetModel("events")
    event.LoadWherePart(map[string]interface{}{"id": eventId})

    var eventName string
    err = db.SelectRow(event, []string{"name"}).Scan(&eventName)
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
        return
    }

    faceId, groupName := -1, ""
    query := `SELECT groups.face_id, groups.name FROM groups
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 AND groups.id = $2;`
    err = db.QueryRow(query, []interface{}{userId, groupId}).Scan(&faceId, &groupName)

    if err != nil || faceId == -1 || groupName == "" {
        utils.SendJSReply(map[string]interface{}{"result": "Вы не являетесь владельцем группы"}, this.Response)
        return
    }

    var groupregId int
    groupReg := this.GetModel("group_registrations")
    groupReg.LoadModelData(map[string]interface{}{"event_id": eventId, "group_id": groupId})
    db.QueryInsert_(groupReg, "RETURNING id").Scan(&groupregId)

    query = `SELECT persons.name, persons.email, persons.status, users.id as user_id FROM persons
        INNER JOIN groups ON groups.id = persons.group_id
        INNER JOIN faces ON faces.id = persons.face_id
        INNER JOIN users ON users.id = faces.user_id
        INNER JOIN registrations ON registrations.face_id = faces.id
        INNER JOIN events ON events.id = registrations.event_id
        WHERE groups.id = $1 AND events.id = 1;`
    data := db.Query(query, []interface{}{groupId})

    query = `SELECT params.id FROM events_forms
        INNER JOIN events ON events.id = events_forms.event_id
        INNER JOIN forms ON forms.id = events_forms.form_id
        INNER JOIN params ON forms.id = params.form_id
        WHERE events.id = $1 ORDER BY forms.id;`
    params := db.Query(query, []interface{}{eventId})

    date := time.Now().Format("2006-01-02T15:04:05Z00:00")
    for _, v := range data {
        status := v.(map[string]interface{})["status"].(bool)
        personUserId := v.(map[string]interface{})["user_id"].(int)

        if !status {
            continue
        }

        var faceId int
        face := this.GetModel("faces")
        face.LoadModelData(map[string]interface{}{"user_id": personUserId})
        db.QueryInsert_(face, "RETURNING id").Scan(&faceId)

        var regId int
        regs := this.GetModel("registrations")
        regs.LoadModelData(map[string]interface{}{"face_id": faceId, "event_id": eventId})
        db.QueryInsert_(regs, "RETURNING id").Scan(&regId)

        to := v.(map[string]interface{})["name"].(string)
        address := v.(map[string]interface{})["email"].(string)
        if !mailer.AttendAnEvent(to, address, eventName, groupName) {
            utils.SendJSReply(map[string]interface{}{"result": "Ошибка. Письмо с уведомлением не отправлено."}, this.Response)
        }
        regsGroupRegs := this.GetModel("regs_groupregs")
        regsGroupRegs.LoadModelData(map[string]interface{}{"groupreg_id": groupregId, "reg_id": regId})
        db.QueryInsert_(regsGroupRegs, "").Scan()

        for _, elem := range params {
            param_id := int(elem.(map[string]interface{})["id"].(int))

            var paramValId int
            paramValues := this.GetModel("param_values")
            paramValues.LoadModelData(map[string]interface{}{"param_id": param_id, "value": " ", "date": date})
            db.QueryInsert_(paramValues, "RETURNING id").Scan(&paramValId)

            regParamValue := this.GetModel("reg_param_vals")
            regParamValue.LoadModelData(map[string]interface{}{
                "reg_id":        regId,
                "param_val_id":  paramValId})
            db.QueryInsert_(regParamValue, "").Scan()
        }

    }

    utils.SendJSReply(map[string]interface{}{"result": "ok"}, this.Response)
}

func (this *Handler) ConfirmInvitationToGroup(token string) {
    userId, err := this.CheckSid()
    if err != nil {
        http.Redirect(this.Response, this.Request, "/", http.StatusUnauthorized)
        return
    }

    var faceId int
    query := `SELECT faces.id
        FROM registrations
        INNER JOIN faces ON faces.id = registrations.face_id
        INNER JOIN events ON events.id = registrations.event_id
        INNER JOIN users ON faces.user_id = users.id
        WHERE users.id = $1 AND events.id = $2;`
    db.QueryRow(query, []interface{}{userId, 1}).Scan(&faceId)

    person := this.GetModel("persons")
    person.LoadWherePart(map[string]interface{}{"token": token})

    var groupId int
    err = db.SelectRow(person, []string{"group_id",}).Scan(&groupId)

    if err != nil {
        if this.Response != nil {
            this.Render([]string{"mvc/views/msg.html"}, "msg", "Неверный токен.")
        }
        return
    }

    if db.IsExists_("persons", []string{"face_id", "group_id"}, []interface{}{faceId, groupId}) {
        if this.Response != nil {
            this.Render([]string{"mvc/views/msg.html"}, "msg", "Вы уже состоите в группе.")
        }
        return
    }

    person = this.GetModel("persons")
    person.LoadModelData(map[string]interface{}{"face_id": faceId, "status": true, "token": " "})
    person.LoadWherePart(map[string]interface{}{"token": token})
    db.QueryUpdate_(person).Scan()

    if this.Response != nil {
        this.Render([]string{"mvc/views/msg.html"}, "msg", "Вы успешно присоединены к группе.")
    }
}

func (this *Handler) RejectInvitationToGroup(token string) {
    if !sessions.CheckSession(this.Response, this.Request) {
        http.Redirect(this.Response, this.Request, "/", http.StatusUnauthorized)
        return
    }

    db.Exec("DELETE FROM persons WHERE token = $1;", []interface{}{token})

    if this.Response != nil {
        this.Render([]string{"mvc/views/msg.html"}, "msg", "Запрос о присоединении к группе успешно отклонен.")
    }
}
<commit_msg>add IsRegGroup function!!!<commit_after>package controllers

import (
    "github.com/orc/db"
    "github.com/orc/sessions"
    "github.com/orc/utils"
    "net/http"
    "strconv"
    "time"
    "github.com/orc/mailer"
)

func (this *GridHandler) RegGroup() {
    userId, err := this.CheckSid()
    if err != nil {
        http.Redirect(this.Response, this.Request, "/", http.StatusUnauthorized)
        return
    }

    request, err := utils.ParseJS(this.Request, this.Response)
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
    }

    groupId, err := strconv.Atoi(request["group_id"].(string))
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
    }

    eventId, err := strconv.Atoi(request["event_id"].(string))
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
    }

    event := this.GetModel("events")
    event.LoadWherePart(map[string]interface{}{"id": eventId})

    var eventName string
    err = db.SelectRow(event, []string{"name"}).Scan(&eventName)
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
        return
    }

    faceId, groupName := -1, ""
    query := `SELECT groups.face_id, groups.name FROM groups
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 AND groups.id = $2;`
    err = db.QueryRow(query, []interface{}{userId, groupId}).Scan(&faceId, &groupName)

    if err != nil || faceId == -1 || groupName == "" {
        utils.SendJSReply(map[string]interface{}{"result": "Вы не являетесь владельцем группы"}, this.Response)
        return
    }

    var groupregId int
    groupReg := this.GetModel("group_registrations")
    groupReg.LoadModelData(map[string]interface{}{"event_id": eventId, "group_id": groupId})
    db.QueryInsert_(groupReg, "RETURNING id").Scan(&groupregId)

    query = `SELECT persons.name, persons.email, persons.status, users.id as user_id FROM persons
        INNER JOIN groups ON groups.id = persons.group_id
        INNER JOIN faces ON faces.id = persons.face_id
        INNER JOIN users ON users.id = faces.user_id
        INNER JOIN registrations ON registrations.face_id = faces.id
        INNER JOIN events ON events.id = registrations.event_id
        WHERE groups.id = $1 AND events.id = 1;`
    data := db.Query(query, []interface{}{groupId})

    query = `SELECT params.id FROM events_forms
        INNER JOIN events ON events.id = events_forms.event_id
        INNER JOIN forms ON forms.id = events_forms.form_id
        INNER JOIN params ON forms.id = params.form_id
        WHERE events.id = $1 ORDER BY forms.id;`
    params := db.Query(query, []interface{}{eventId})

    date := time.Now().Format("2006-01-02T15:04:05Z00:00")
    for _, v := range data {
        status := v.(map[string]interface{})["status"].(bool)
        personUserId := v.(map[string]interface{})["user_id"].(int)

        if !status {
            continue
        }

        var faceId int
        face := this.GetModel("faces")
        face.LoadModelData(map[string]interface{}{"user_id": personUserId})
        db.QueryInsert_(face, "RETURNING id").Scan(&faceId)

        var regId int
        regs := this.GetModel("registrations")
        regs.LoadModelData(map[string]interface{}{"face_id": faceId, "event_id": eventId})
        db.QueryInsert_(regs, "RETURNING id").Scan(&regId)

        to := v.(map[string]interface{})["name"].(string)
        address := v.(map[string]interface{})["email"].(string)
        if !mailer.AttendAnEvent(to, address, eventName, groupName) {
            utils.SendJSReply(map[string]interface{}{"result": "Ошибка. Письмо с уведомлением не отправлено."}, this.Response)
        }
        regsGroupRegs := this.GetModel("regs_groupregs")
        regsGroupRegs.LoadModelData(map[string]interface{}{"groupreg_id": groupregId, "reg_id": regId})
        db.QueryInsert_(regsGroupRegs, "").Scan()

        for _, elem := range params {
            param_id := int(elem.(map[string]interface{})["id"].(int))

            var paramValId int
            paramValues := this.GetModel("param_values")
            paramValues.LoadModelData(map[string]interface{}{"param_id": param_id, "value": " ", "date": date})
            db.QueryInsert_(paramValues, "RETURNING id").Scan(&paramValId)

            regParamValue := this.GetModel("reg_param_vals")
            regParamValue.LoadModelData(map[string]interface{}{
                "reg_id":        regId,
                "param_val_id":  paramValId})
            db.QueryInsert_(regParamValue, "").Scan()
        }

    }

    utils.SendJSReply(map[string]interface{}{"result": "ok"}, this.Response)
}

func (this *Handler) ConfirmInvitationToGroup(token string) {
    userId, err := this.CheckSid()
    if err != nil {
        http.Redirect(this.Response, this.Request, "/", http.StatusUnauthorized)
        return
    }

    var faceId int
    query := `SELECT faces.id
        FROM registrations
        INNER JOIN faces ON faces.id = registrations.face_id
        INNER JOIN events ON events.id = registrations.event_id
        INNER JOIN users ON faces.user_id = users.id
        WHERE users.id = $1 AND events.id = $2;`
    db.QueryRow(query, []interface{}{userId, 1}).Scan(&faceId)

    person := this.GetModel("persons")
    person.LoadWherePart(map[string]interface{}{"token": token})

    var groupId int
    err = db.SelectRow(person, []string{"group_id",}).Scan(&groupId)

    if err != nil {
        if this.Response != nil {
            this.Render([]string{"mvc/views/msg.html"}, "msg", "Неверный токен.")
        }
        return
    }

    if db.IsExists_("persons", []string{"face_id", "group_id"}, []interface{}{faceId, groupId}) {
        if this.Response != nil {
            this.Render([]string{"mvc/views/msg.html"}, "msg", "Вы уже состоите в группе.")
        }
        return
    }

    person = this.GetModel("persons")
    person.LoadModelData(map[string]interface{}{"face_id": faceId, "status": true, "token": " "})
    person.LoadWherePart(map[string]interface{}{"token": token})
    db.QueryUpdate_(person).Scan()

    if this.Response != nil {
        this.Render([]string{"mvc/views/msg.html"}, "msg", "Вы успешно присоединены к группе.")
    }
}

func (this *Handler) RejectInvitationToGroup(token string) {
    if !sessions.CheckSession(this.Response, this.Request) {
        http.Redirect(this.Response, this.Request, "/", http.StatusUnauthorized)
        return
    }

    db.Exec("DELETE FROM persons WHERE token = $1;", []interface{}{token})

    if this.Response != nil {
        this.Render([]string{"mvc/views/msg.html"}, "msg", "Запрос о присоединении к группе успешно отклонен.")
    }
}

func (this *GridHandler) IsRegGroup() {
    _, err := this.CheckSid()
    if err != nil {
        http.Redirect(this.Response, this.Request, "/", http.StatusUnauthorized)
        return
    }

    request, err := utils.ParseJS(this.Request, this.Response)
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
    }

    groupId, err := strconv.Atoi(request["group_id"].(string))
    if err != nil {
        utils.SendJSReply(map[string]interface{}{"result": err.Error()}, this.Response)
    }

    addDelFlag := !db.IsExists_("group_registrations", []string{"group_id"}, []interface{}{groupId})
    utils.SendJSReply(map[string]interface{}{"result": "ok", "addDelFlag": addDelFlag}, this.Response)
}
<|endoftext|>


<commit_before>package synco

import "code.google.com/p/go-imap/go1/imap"
import (
	"bytes"
	"fmt"
	"encoding/json"
	"log"
	"net/mail"
	"os"
	"time"
)

type IMAPServer struct {
	Host string
	Port uint16
}

type IMAPAccount struct {
	Username string
	Password string
	Server *IMAPServer
}

type UIDFetchJob struct {
	LoUID, HiUID uint32
}

var	mbox string = "[Gmail]/All Mail"

func PrintMail(acct *IMAPAccount, query string) {
	imap.DefaultLogger = log.New(os.Stdout, "", 0)
//	imap.DefaultLogMask = imap.LogConn | imap.LogRaw

	log.Printf("Running for user '%s' on IMAP server '%s:%d'", acct.Username, acct.Server.Host, acct.Server.Port)

	conns := 1
	partsize := 250

	// Fetch UIDs.
	c := Dial(acct.Server)
	login(c, acct.Username, acct.Password)
	c.Select(mbox, true)
	uids, _ := SearchUIDs(c, query)
	c.Close(false)
	c.Logout(-1)

	timestarted := time.Now()

	// Fetch message MIME bodies.
	nparts := (len(uids) + partsize - 1) / partsize
	jobs := make([]UIDFetchJob, nparts)
	for i := 0; i < nparts; i++ {
		lo := i * partsize
		hi_exclusive := (i + 1) * partsize
		if hi_exclusive >= len(uids) {
			hi_exclusive = len(uids) - 1
			for uids[hi_exclusive] == 0 { // hacky
				hi_exclusive--
			}
		}
		loUID := uids[lo]
		hiUID := uids[hi_exclusive]
		job := UIDFetchJob{ loUID, hiUID }
		jobs[i] = job
	}

	log.Printf("%d UIDs total, %d jobs of size <= %d\n", len(uids), len(jobs), partsize)

	// open conns
	if conns > len(jobs) {
		conns = len(jobs)
	}
	jobchan := make(chan UIDFetchJob)
	for i := 0; i < conns; i++ {
		go StartWorker(i, acct, jobchan)
	}

	nextjobindex := 0
	quits := 0
	for {
		if quits == conns {
			// have sent quits to everyone
			break
		}

		wantjob := <-jobchan
		if wantjob.LoUID == 0 {
			if nextjobindex < len(jobs) {
				jobchan <- jobs[nextjobindex]
				nextjobindex++
			} else {
				jobchan <- UIDFetchJob{ 0, 0 }
				quits++
			}
		} else {
			panic("unknown wantjob value")
		}
	}

	timeelapsed := time.Since(timestarted)
	msecpermessage := timeelapsed.Seconds() / float64(len(uids)) * 1000
	messagespersec := float64(len(uids)) / timeelapsed.Seconds()
	log.Printf("Finished fetching %d messages in %.2fs (%.1fms per message; %.1f messages per second)\n", len(uids), timeelapsed.Seconds(), msecpermessage, messagespersec)

}

func SearchUIDs(c *imap.Client, query string) (uids []uint32, err error) {
	cmd, err := c.UIDSearch("X-GM-RAW", fmt.Sprint("\"", query, "\""))

	for cmd.InProgress() {
		c.Recv(-1)
		for _, rsp := range cmd.Data {
			uids = rsp.SearchResults()
		}
		cmd.Data = nil
	}
	return
}

func FetchAllUIDs(c *imap.Client) (uids []uint32, err error) {
	maxmessages := 150000
	uids = make([]uint32, maxmessages)

	set, _ := imap.NewSeqSet("1:*")
	cmd, err := c.UIDFetch(set, "RFC822.SIZE")

	messagenum := uint32(0)
	for cmd.InProgress() {
		c.Recv(-1)
		for _, rsp := range cmd.Data {
			uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"])
			uids[messagenum] = uid
		}
		cmd.Data = nil
		messagenum++
	}

	uids = uids[:messagenum]
	return
}

func StartWorker(workerid int, acct *IMAPAccount, jobchan chan UIDFetchJob) {
	c := Dial(acct.Server)
	login(c, acct.Username, acct.Password)
	c.Select(mbox, true)

	for {
		jobchan <- UIDFetchJob{ 0, 0 }
		job := <-jobchan
		if job.LoUID != 0 {
			FetchMessages(c, job.LoUID, job.HiUID)
			log.Printf("Worker %d finished one job\n", workerid)
		} else {
			break
		}
	}
}

func FetchMessages(c *imap.Client, loUID, hiUID uint32) (err error) {
	set, _ := imap.NewSeqSet(fmt.Sprintf("%d:%d", loUID, hiUID))
	cmd, err := c.UIDFetch(set, "RFC822")

	for cmd.InProgress() {
		c.Recv(-1)
		for _, rsp := range cmd.Data {
			uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"])
			mime := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822"])
			if msg, _ := mail.ReadMessage(bytes.NewReader(mime)); msg != nil {
				PrintMessageAsJSON(msg, uid)
			}
		}
		cmd.Data = nil
	}

	return
}

func PrintMessageAsJSON(msg *mail.Message, uid uint32) {
	var msgdata = map[string] string { }

	for headerkey := range msg.Header {
		val := msg.Header.Get(headerkey)
		msgdata[headerkey] = val
	}

	msgdata["imap_uid"] = fmt.Sprintf("%d", uid)

	if b, err := TextBody(msg); err == nil {
		msgdata["text_body"] = b
	}
	if b, err := HTMLBody(msg); err == nil {
		msgdata["html_body"] = b
	}

	o, err := json.Marshal(msgdata)
	if err != nil {
		log.Println("error marshaling message as JSON: ", err.Error()[:100])
	} else {
		fmt.Println(string(o))
	}
	return
}

func Dial(server *IMAPServer) (c *imap.Client) {
	var err error
	addr := fmt.Sprintf("%s:%d", server.Host, server.Port)
	c, err = imap.DialTLS(addr, nil)
	if err != nil {
		panic(err)
	}
	return c
}

func login(c *imap.Client, user, pass string) (cmd *imap.Command, err error) {
	defer c.SetLogMask(sensitive(c, "LOGIN"))
	return c.Login(user, pass)
}

func sensitive(c *imap.Client, action string) imap.LogMask {
	mask := c.SetLogMask(imap.LogConn)
	hide := imap.LogCmd | imap.LogRaw
	if mask&hide != 0 {
		c.Logln(imap.LogConn, "Raw logging disabled during", action)
	}
	c.SetLogMask(mask &^ hide)
	return mask
}
<commit_msg>removed parallelism<commit_after>package synco

import "code.google.com/p/go-imap/go1/imap"
import (
	"bytes"
	"fmt"
	"encoding/json"
	"log"
	"net/mail"
	"os"
	"time"
)

type IMAPServer struct {
	Host string
	Port uint16
}

type IMAPAccount struct {
	Username string
	Password string
	Server *IMAPServer
}

type UIDFetchJob struct {
	uids []uint32
}

var	mbox string = "[Gmail]/All Mail"

func PrintMail(acct *IMAPAccount, query string) {
	imap.DefaultLogger = log.New(os.Stdout, "", 0)
//	imap.DefaultLogMask = imap.LogConn | imap.LogRaw

	log.Printf("Running for user '%s' on IMAP server '%s:%d'", acct.Username, acct.Server.Host, acct.Server.Port)

	jobsize := 250

	// Fetch UIDs.
	c := Dial(acct.Server)
	login(c, acct.Username, acct.Password)
	c.Select(mbox, true)
	uids, _ := SearchUIDs(c, query)

	timestarted := time.Now()

	nparts := (len(uids) + jobsize - 1) / jobsize
	jobs := make([]*imap.SeqSet, nparts)
	for i := 0; i < nparts; i++ {
		lo := i * jobsize
		hi_exclusive := (i + 1) * jobsize
		if hi_exclusive >= len(uids) {
			hi_exclusive = len(uids) - 1
			for uids[hi_exclusive] == 0 { // hacky
				hi_exclusive--
			}
		}
		set, _ := imap.NewSeqSet("")
		set.AddNum(uids[lo:hi_exclusive]...)
		jobs[i] = set
	}

	log.Printf("%d UIDs total, %d jobs of size <= %d\n", len(uids), len(jobs), jobsize)

	for _, jobUIDs := range jobs {
		FetchMessages(c, jobUIDs)
	}

	timeelapsed := time.Since(timestarted)
	msecpermessage := timeelapsed.Seconds() / float64(len(uids)) * 1000
	messagespersec := float64(len(uids)) / timeelapsed.Seconds()
	log.Printf("Finished fetching %d messages in %.2fs (%.1fms per message; %.1f messages per second)\n", len(uids), timeelapsed.Seconds(), msecpermessage, messagespersec)

	c.Close(false)
	c.Logout(-1)
}

func SearchUIDs(c *imap.Client, query string) (uids []uint32, err error) {
	cmd, err := c.UIDSearch("X-GM-RAW", fmt.Sprint("\"", query, "\""))

	for cmd.InProgress() {
		c.Recv(-1)
		for _, rsp := range cmd.Data {
			uids = rsp.SearchResults()
		}
		cmd.Data = nil
	}
	return
}

func FetchAllUIDs(c *imap.Client) (uids []uint32, err error) {
	maxmessages := 150000
	uids = make([]uint32, maxmessages)

	set, _ := imap.NewSeqSet("1:*")
	cmd, err := c.UIDFetch(set, "RFC822.SIZE")

	messagenum := uint32(0)
	for cmd.InProgress() {
		c.Recv(-1)
		for _, rsp := range cmd.Data {
			uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"])
			uids[messagenum] = uid
		}
		cmd.Data = nil
		messagenum++
	}

	uids = uids[:messagenum]
	return
}

func FetchMessages(c *imap.Client, uidSet *imap.SeqSet) (err error) {
	cmd, err := c.UIDFetch(uidSet, "RFC822")

	for cmd.InProgress() {
		c.Recv(-1)
		for _, rsp := range cmd.Data {
			uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"])
			mime := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822"])
			if msg, _ := mail.ReadMessage(bytes.NewReader(mime)); msg != nil {
				PrintMessageAsJSON(msg, uid)
			}
		}
		cmd.Data = nil
	}

	return
}

func PrintMessageAsJSON(msg *mail.Message, uid uint32) {
	var msgdata = map[string] string { }

	for headerkey := range msg.Header {
		val := msg.Header.Get(headerkey)
		msgdata[headerkey] = val
	}

	msgdata["imap_uid"] = fmt.Sprintf("%d", uid)

	if b, err := TextBody(msg); err == nil {
		msgdata["text_body"] = b
	}
	if b, err := HTMLBody(msg); err == nil {
		msgdata["html_body"] = b
	}

	o, err := json.Marshal(msgdata)
	if err != nil {
		log.Println("error marshaling message as JSON: ", err.Error()[:100])
	} else {
		fmt.Println(string(o))
	}
	return
}

func Dial(server *IMAPServer) (c *imap.Client) {
	var err error
	addr := fmt.Sprintf("%s:%d", server.Host, server.Port)
	c, err = imap.DialTLS(addr, nil)
	if err != nil {
		panic(err)
	}
	return c
}

func login(c *imap.Client, user, pass string) (cmd *imap.Command, err error) {
	defer c.SetLogMask(sensitive(c, "LOGIN"))
	return c.Login(user, pass)
}

func sensitive(c *imap.Client, action string) imap.LogMask {
	mask := c.SetLogMask(imap.LogConn)
	hide := imap.LogCmd | imap.LogRaw
	if mask&hide != 0 {
		c.Logln(imap.LogConn, "Raw logging disabled during", action)
	}
	c.SetLogMask(mask &^ hide)
	return mask
}
<|endoftext|>


<commit_before>// Package evaler implements a simple fp arithmetic expression evaluator.
//
// See README.md for documentation.

package evaler

import (
	"fmt"
	"math"
	"math/big"
	"regexp"
	"strconv"
	"strings"

	"github.com/howeyc/sc/evaler/stack"
)

var whitespace_rx = regexp.MustCompile(`\s+`)
var fp_rx = regexp.MustCompile(`(\d+(?:\.\d)?)`) // simple fp number
var tokenize_regex = regexp.MustCompile(`([A-Z]*\d+(?:\.\d)?)`)
var cell_addr = regexp.MustCompile(`([A-Z]+\d+)`)
var operators = "-+**/<>"

// prec returns the operator's precedence
func prec(op string) (result int) {
	if op == "-" || op == "+" {
		result = 1
	} else if op == "*" || op == "/" {
		result = 2
	} else if op == "**" {
		result = 3
	}
	return
}

// opGTE returns true if op1's precedence is >= op2
func opGTE(op1, op2 string) bool {
	return prec(op1) >= prec(op2)
}

// isOperator returns true if token is an operator
func isOperator(token string) bool {
	return strings.Contains(operators, token)
}

// isOperand returns true if token is an operand
func isOperand(token string) bool {
	return fp_rx.MatchString(token)
}

// isCellAddr returns true if token is a cell address
func IsCellAddr(token string) bool {
	return cell_addr.MatchString(token)
}

// convert2postfix converts an infix expression to postfix
func convert2postfix(tokens []string) []string {
	var stack stack.Stack
	var result []string
	for _, token := range tokens {

		if isOperator(token) {

		OPERATOR:
			for {
				top, err := stack.Top()
				if err == nil && top != "(" {
					if opGTE(top.(string), token) {
						pop, _ := stack.Pop()
						result = append(result, pop.(string))
					} else {
						break OPERATOR
					}
				}
				break OPERATOR
			}
			stack.Push(token)

		} else if token == "(" {
			stack.Push(token)

		} else if token == ")" {
		PAREN:
			for {
				top, err := stack.Top()
				if err == nil && top != "(" {
					pop, _ := stack.Pop()
					result = append(result, pop.(string))
				} else {
					stack.Pop() // pop off "("
					break PAREN
				}
			}

		} else if isOperand(token) || IsCellAddr(token) {
			result = append(result, token)
		}

	}

	for !stack.IsEmpty() {
		pop, _ := stack.Pop()
		result = append(result, pop.(string))
	}

	return result
}

// evaluatePostfix takes a postfix expression and evaluates it
func EvaluatePostfix(postfix []string) (*big.Rat, error) {
	var stack stack.Stack
	result := new(big.Rat) // note: a new(big.Rat) has value "0/1" ie zero
	for _, token := range postfix {
		if isOperand(token) {
			bigrat := new(big.Rat)
			if _, err := fmt.Sscan(token, bigrat); err != nil {
				return nil, fmt.Errorf("unable to scan %s", token)
			}
			stack.Push(bigrat)
		} else if isOperator(token) {

			op2, err2 := stack.Pop()
			if err2 != nil {
				return nil, err2
			}
			op1, err1 := stack.Pop()
			if err1 != nil {
				return nil, err1
			}

			dummy := new(big.Rat)
			switch token {
			case "**":
				float1 := BigratToFloat(op1.(*big.Rat))
				float2 := BigratToFloat(op2.(*big.Rat))
				float_result := math.Pow(float1, float2)
				stack.Push(FloatToBigrat(float_result))
			case "*":
				result := dummy.Mul(op1.(*big.Rat), op2.(*big.Rat))
				stack.Push(result)
			case "/":
				result := dummy.Quo(op1.(*big.Rat), op2.(*big.Rat))
				stack.Push(result)
			case "+":
				result = dummy.Add(op1.(*big.Rat), op2.(*big.Rat))
				stack.Push(result)
			case "-":
				result = dummy.Sub(op1.(*big.Rat), op2.(*big.Rat))
				stack.Push(result)
			case "<":
				if op1.(*big.Rat).Cmp(op2.(*big.Rat)) <= -1 {
					stack.Push(big.NewRat(1, 1))
				} else {
					stack.Push(new(big.Rat))
				}
			case ">":
				if op1.(*big.Rat).Cmp(op2.(*big.Rat)) >= 1 {
					stack.Push(big.NewRat(1, 1))
				} else {
					stack.Push(new(big.Rat))
				}
			}
		} else {
			return nil, fmt.Errorf("unknown token %v", token)
		}
	}

	retval, err := stack.Pop()
	if err != nil {
		return nil, err
	}
	return retval.(*big.Rat), nil
}

// tokenise takes an expr string and converts it to a slice of tokens
//
// tokenise puts spaces around all non-numbers, removes leading and
// trailing spaces, then splits on spaces
//
func tokenise(expr string) []string {
	spaced := tokenize_regex.ReplaceAllString(expr, " ${1} ")
	symbols := []string{"(", ")"}
	for _, symbol := range symbols {
		spaced = strings.Replace(spaced, symbol, fmt.Sprintf(" %s ", symbol), -1)
	}
	stripped := whitespace_rx.ReplaceAllString(strings.TrimSpace(spaced), "|")
	return strings.Split(stripped, "|")
}

// Eval takes an infix string arithmetic expression, and evaluates it
//
// Usage:
//   result, err := evaler.Eval("1+2")
// Returns: the result of the evaluation, and any errors
//
func Eval(expr string) (result *big.Rat, err error) {
	defer func() {
		if e := recover(); e != nil {
			result = nil
			err = fmt.Errorf("Invalid Expression: %s", expr)
		}
	}()

	tokens := tokenise(expr)
	postfix := convert2postfix(tokens)
	return EvaluatePostfix(postfix)
}

func GetPostfix(expr string) []string {
	tokens := tokenise(expr)
	return convert2postfix(tokens)
}

// BigratToInt converts a *big.Rat to an int64 (with truncation); it
// returns an error for integer overflows.
func BigratToInt(bigrat *big.Rat) (int64, error) {
	float_string := bigrat.FloatString(0)
	return strconv.ParseInt(float_string, 10, 64)
}

// BigratToInt converts a *big.Rat to a *big.Int (with truncation)
func BigratToBigint(bigrat *big.Rat) *big.Int {
	int_string := bigrat.FloatString(0)
	bigint := new(big.Int)
	// no error scenario could be imagined in testing, so discard err
	fmt.Sscan(int_string, bigint)
	return bigint
}

// BigratToFloat converts a *big.Rat to a float64 (with loss of
// precision).
func BigratToFloat(bigrat *big.Rat) float64 {
	float_string := bigrat.FloatString(10) // arbitrary largish precision
	// no error scenario could be imagined in testing, so discard err
	float, _ := strconv.ParseFloat(float_string, 64)
	return float
}

// FloatToBigrat converts a float64 to a *big.Rat.
func FloatToBigrat(float float64) *big.Rat {
	float_string := fmt.Sprintf("%g", float)
	bigrat := new(big.Rat)
	// no error scenario could be imagined in testing, so discard err
	fmt.Sscan(float_string, bigrat)
	return bigrat
}
<commit_msg>Fix values to have more than one digit after decimal.<commit_after>// Package evaler implements a simple fp arithmetic expression evaluator.
//
// See README.md for documentation.

package evaler

import (
	"fmt"
	"math"
	"math/big"
	"regexp"
	"strconv"
	"strings"

	"github.com/howeyc/sc/evaler/stack"
)

var whitespace_rx = regexp.MustCompile(`\s+`)
var fp_rx = regexp.MustCompile(`(\d+(?:\.\d+)?)`) // simple fp number
var tokenize_regex = regexp.MustCompile(`([A-Z]*\d+(?:\.\d+)?)`)
var cell_addr = regexp.MustCompile(`([A-Z]+\d+)`)
var operators = "-+**/<>"

// prec returns the operator's precedence
func prec(op string) (result int) {
	if op == "-" || op == "+" {
		result = 1
	} else if op == "*" || op == "/" {
		result = 2
	} else if op == "**" {
		result = 3
	}
	return
}

// opGTE returns true if op1's precedence is >= op2
func opGTE(op1, op2 string) bool {
	return prec(op1) >= prec(op2)
}

// isOperator returns true if token is an operator
func isOperator(token string) bool {
	return strings.Contains(operators, token)
}

// isOperand returns true if token is an operand
func isOperand(token string) bool {
	return fp_rx.MatchString(token)
}

// isCellAddr returns true if token is a cell address
func IsCellAddr(token string) bool {
	return cell_addr.MatchString(token)
}

// convert2postfix converts an infix expression to postfix
func convert2postfix(tokens []string) []string {
	var stack stack.Stack
	var result []string
	for _, token := range tokens {

		if isOperator(token) {

		OPERATOR:
			for {
				top, err := stack.Top()
				if err == nil && top != "(" {
					if opGTE(top.(string), token) {
						pop, _ := stack.Pop()
						result = append(result, pop.(string))
					} else {
						break OPERATOR
					}
				}
				break OPERATOR
			}
			stack.Push(token)

		} else if token == "(" {
			stack.Push(token)

		} else if token == ")" {
		PAREN:
			for {
				top, err := stack.Top()
				if err == nil && top != "(" {
					pop, _ := stack.Pop()
					result = append(result, pop.(string))
				} else {
					stack.Pop() // pop off "("
					break PAREN
				}
			}

		} else if isOperand(token) || IsCellAddr(token) {
			result = append(result, token)
		}

	}

	for !stack.IsEmpty() {
		pop, _ := stack.Pop()
		result = append(result, pop.(string))
	}

	return result
}

// evaluatePostfix takes a postfix expression and evaluates it
func EvaluatePostfix(postfix []string) (*big.Rat, error) {
	var stack stack.Stack
	result := new(big.Rat) // note: a new(big.Rat) has value "0/1" ie zero
	for _, token := range postfix {
		if isOperand(token) {
			bigrat := new(big.Rat)
			if _, err := fmt.Sscan(token, bigrat); err != nil {
				return nil, fmt.Errorf("unable to scan %s", token)
			}
			stack.Push(bigrat)
		} else if isOperator(token) {

			op2, err2 := stack.Pop()
			if err2 != nil {
				return nil, err2
			}
			op1, err1 := stack.Pop()
			if err1 != nil {
				return nil, err1
			}

			dummy := new(big.Rat)
			switch token {
			case "**":
				float1 := BigratToFloat(op1.(*big.Rat))
				float2 := BigratToFloat(op2.(*big.Rat))
				float_result := math.Pow(float1, float2)
				stack.Push(FloatToBigrat(float_result))
			case "*":
				result := dummy.Mul(op1.(*big.Rat), op2.(*big.Rat))
				stack.Push(result)
			case "/":
				result := dummy.Quo(op1.(*big.Rat), op2.(*big.Rat))
				stack.Push(result)
			case "+":
				result = dummy.Add(op1.(*big.Rat), op2.(*big.Rat))
				stack.Push(result)
			case "-":
				result = dummy.Sub(op1.(*big.Rat), op2.(*big.Rat))
				stack.Push(result)
			case "<":
				if op1.(*big.Rat).Cmp(op2.(*big.Rat)) <= -1 {
					stack.Push(big.NewRat(1, 1))
				} else {
					stack.Push(new(big.Rat))
				}
			case ">":
				if op1.(*big.Rat).Cmp(op2.(*big.Rat)) >= 1 {
					stack.Push(big.NewRat(1, 1))
				} else {
					stack.Push(new(big.Rat))
				}
			}
		} else {
			return nil, fmt.Errorf("unknown token %v", token)
		}
	}

	retval, err := stack.Pop()
	if err != nil {
		return nil, err
	}
	return retval.(*big.Rat), nil
}

// tokenise takes an expr string and converts it to a slice of tokens
//
// tokenise puts spaces around all non-numbers, removes leading and
// trailing spaces, then splits on spaces
//
func tokenise(expr string) []string {
	spaced := tokenize_regex.ReplaceAllString(expr, " ${1} ")
	symbols := []string{"(", ")"}
	for _, symbol := range symbols {
		spaced = strings.Replace(spaced, symbol, fmt.Sprintf(" %s ", symbol), -1)
	}
	stripped := whitespace_rx.ReplaceAllString(strings.TrimSpace(spaced), "|")
	return strings.Split(stripped, "|")
}

// Eval takes an infix string arithmetic expression, and evaluates it
//
// Usage:
//   result, err := evaler.Eval("1+2")
// Returns: the result of the evaluation, and any errors
//
func Eval(expr string) (result *big.Rat, err error) {
	defer func() {
		if e := recover(); e != nil {
			result = nil
			err = fmt.Errorf("Invalid Expression: %s", expr)
		}
	}()

	tokens := tokenise(expr)
	postfix := convert2postfix(tokens)
	return EvaluatePostfix(postfix)
}

func GetPostfix(expr string) []string {
	tokens := tokenise(expr)
	return convert2postfix(tokens)
}

// BigratToInt converts a *big.Rat to an int64 (with truncation); it
// returns an error for integer overflows.
func BigratToInt(bigrat *big.Rat) (int64, error) {
	float_string := bigrat.FloatString(0)
	return strconv.ParseInt(float_string, 10, 64)
}

// BigratToInt converts a *big.Rat to a *big.Int (with truncation)
func BigratToBigint(bigrat *big.Rat) *big.Int {
	int_string := bigrat.FloatString(0)
	bigint := new(big.Int)
	// no error scenario could be imagined in testing, so discard err
	fmt.Sscan(int_string, bigint)
	return bigint
}

// BigratToFloat converts a *big.Rat to a float64 (with loss of
// precision).
func BigratToFloat(bigrat *big.Rat) float64 {
	float_string := bigrat.FloatString(10) // arbitrary largish precision
	// no error scenario could be imagined in testing, so discard err
	float, _ := strconv.ParseFloat(float_string, 64)
	return float
}

// FloatToBigrat converts a float64 to a *big.Rat.
func FloatToBigrat(float float64) *big.Rat {
	float_string := fmt.Sprintf("%g", float)
	bigrat := new(big.Rat)
	// no error scenario could be imagined in testing, so discard err
	fmt.Sscan(float_string, bigrat)
	return bigrat
}
<|endoftext|>


<commit_before>package irc

import (
	"fmt"
	"strings"
	"time"
)

func joinedLen(names []string) int {
	var l = len(names) - 1 // " " between names
	for _, name := range names {
		l += len(name)
	}
	return l
}

type BaseReply struct {
	id      string
	message string
	source  Identifier
}

func (reply *BaseReply) SetSource(source Identifier) {
	reply.id = source.Id()
	reply.source = source
}

func (reply *BaseReply) Source() Identifier {
	return reply.source
}

type StringReply struct {
	BaseReply
	code string
}

func NewStringReply(source Identifier, code string,
	format string, args ...interface{}) *StringReply {
	reply := &StringReply{
		code: code,
	}
	reply.SetSource(source)
	reply.message = fmt.Sprintf(format, args...)
	return reply
}

func (reply *StringReply) Format(client *Client) []string {
	message := fmt.Sprintf(":%s %s %s",
		reply.id, reply.code, reply.message)
	return []string{message}
}

func (reply *StringReply) String() string {
	return fmt.Sprintf("Reply(source=%s, code=%s, message=%s)",
		reply.id, reply.code, reply.message)
}

type NumericReply struct {
	BaseReply
	code Numeric
}

func NewNumericReply(source Identifier, code Numeric, format string,
	args ...interface{}) *NumericReply {
	reply := &NumericReply{
		code: code,
	}
	reply.SetSource(source)
	reply.message = fmt.Sprintf(format, args...)
	return reply
}

func (reply *NumericReply) Format(client *Client) []string {
	message := fmt.Sprintf(":%s %s %s %s",
		reply.id, reply.code, client.Nick(), reply.message)
	return []string{message}
}

func (reply *NumericReply) String() string {
	return fmt.Sprintf("Reply(source=%s, code=%d, message=%s)",
		reply.id, reply.code, reply.message)
}

// names reply

type NamesReply struct {
	BaseReply
	channel *Channel
}

func NewNamesReply(channel *Channel) Reply {
	reply := &NamesReply{
		channel: channel,
	}
	reply.SetSource(channel)
	return reply
}

func (reply *NamesReply) Format(client *Client) []string {
	lines := make([]string, 0)
	base := RplNamReply(reply.channel, []string{})
	baseLen := len(base.Format(client)[0])
	tooLong := func(names []string) bool {
		return (baseLen + joinedLen(names)) > MAX_REPLY_LEN
	}
	from, to := 0, 1
	nicks := reply.channel.Nicks()
	for to < len(nicks) {
		if (from < (to - 1)) && tooLong(nicks[from:to]) {
			lines = append(lines, RplNamReply(reply.channel, nicks[from:to-1]).Format(client)...)
			from, to = to-1, to
		} else {
			to += 1
		}
	}
	if from < len(nicks) {
		lines = append(lines, RplNamReply(reply.channel, nicks[from:]).Format(client)...)
	}
	lines = append(lines, RplEndOfNames(reply.channel).Format(client)...)
	return lines
}

func (reply *NamesReply) String() string {
	return fmt.Sprintf("NamesReply(channel=%s, names=%s)",
		reply.channel, reply.channel.Nicks())
}

// messaging replies

func RplPrivMsg(source Identifier, target Identifier, message string) Reply {
	return NewStringReply(source, "PRIVMSG", "%s :%s", target.Nick(), message)
}

func RplNotice(source Identifier, target Identifier, message string) Reply {
	return NewStringReply(source, "NOTICE", "%s :%s", target.Nick(), message)
}

func RplNick(source Identifier, newNick string) Reply {
	return NewStringReply(source, "NICK", newNick)
}

func RplJoin(client *Client, channel *Channel) Reply {
	return NewStringReply(client, "JOIN", channel.name)
}

func RplPart(client *Client, channel *Channel, message string) Reply {
	return NewStringReply(client, "PART", "%s :%s", channel.name, message)
}

func RplTopicMsg(source Identifier, channel *Channel) Reply {
	return NewStringReply(source, "TOPIC", "%s :%s", channel.name, channel.topic)
}

func RplPing(server *Server, target Identifier) Reply {
	return NewStringReply(server, "PING", target.Nick())
}

func RplPong(server *Server, client *Client) Reply {
	return NewStringReply(server, "PONG", client.Nick())
}

func RplQuit(client *Client, message string) Reply {
	return NewStringReply(client, "QUIT", ":%s", message)
}

func RplError(server *Server, target Identifier) Reply {
	return NewStringReply(server, "ERROR", target.Nick())
}

func RplInviteMsg(channel *Channel, inviter *Client) Reply {
	return NewStringReply(inviter, "INVITE", channel.name)
}

// numeric replies

func RplWelcome(source Identifier, client *Client) Reply {
	return NewNumericReply(source, RPL_WELCOME,
		"Welcome to the Internet Relay Network %s", client.Id())
}

func RplYourHost(server *Server) Reply {
	return NewNumericReply(server, RPL_YOURHOST,
		"Your host is %s, running version %s", server.name, VERSION)
}

func RplCreated(server *Server) Reply {
	return NewNumericReply(server, RPL_CREATED,
		"This server was created %s", server.ctime.Format(time.RFC1123))
}

func RplMyInfo(server *Server) Reply {
	return NewNumericReply(server, RPL_MYINFO,
		"%s %s aiOorsw abeIikmntpqrsl", server.name, VERSION)
}

func RplUModeIs(server *Server, client *Client) Reply {
	return NewNumericReply(server, RPL_UMODEIS, client.ModeString())
}

func RplNoTopic(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_NOTOPIC,
		"%s :No topic is set", channel.name)
}

func RplTopic(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_TOPIC,
		"%s :%s", channel.name, channel.topic)
}

func RplInvitingMsg(channel *Channel, invitee *Client) Reply {
	return NewNumericReply(channel.server, RPL_INVITING,
		"%s %s", channel.name, invitee.Nick())
}

func RplNamReply(channel *Channel, names []string) *NumericReply {
	return NewNumericReply(channel.server, RPL_NAMREPLY, "= %s :%s",
		channel.name, strings.Join(names, " "))
}

func RplEndOfNames(channel *Channel) Reply {
	return NewNumericReply(channel, RPL_ENDOFNAMES,
		"%s :End of NAMES list", channel.name)
}

// :You are now an IRC operator
func RplYoureOper(server *Server) Reply {
	return NewNumericReply(server, RPL_YOUREOPER, ":You are now an IRC operator")
}

func RplWhoisUser(server *Server, client *Client) Reply {
	return NewNumericReply(server, RPL_WHOISUSER, "%s %s %s * :%s",
		client.Nick(), client.username, client.hostname, client.realname)
}

func RplEndOfWhois(server *Server) Reply {
	return NewNumericReply(server, RPL_ENDOFWHOIS, ":End of WHOIS list")
}

func RplChannelModeIs(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_CHANNELMODEIS, "%s %s",
		channel.name, channel.ModeString())
}

// <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
// :<hopcount> <real name>
func RplWhoReply(server *Server, channel *Channel, client *Client) Reply {
	channelName := "*"
	if channel != nil {
		channelName = channel.name
	}
	return NewNumericReply(server, RPL_WHOREPLY, "%s %s %s %s %s H :0 %s",
		channelName, client.username, client.hostname, server.name, client.Nick(),
		client.realname)
}

// <name> :End of WHO list
func RplEndOfWho(server *Server, name string) Reply {
	return NewNumericReply(server, RPL_ENDOFWHO, "%s :End of WHO list", name)
}

func RplBanList(channel *Channel, ban UserMask) Reply {
	return NewNumericReply(channel.server, RPL_BANLIST, "%s %s", channel.name, ban)
}

func RplEndOfBanList(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_ENDOFBANLIST,
		"%s :End of channel ban list", channel.name)
}

func RplNowAway(server *Server) Reply {
	return NewNumericReply(server, RPL_NOWAWAY,
		":You have been marked as being away")
}

func RplUnAway(server *Server) Reply {
	return NewNumericReply(server, RPL_UNAWAY,
		":You are no longer marked as being away")
}

func RplAway(server *Server, client *Client) Reply {
	return NewNumericReply(server, RPL_AWAY,
		"%s :%s", client.Nick(), client.awayMessage)
}

func RplIsOn(server *Server, nicks []string) Reply {
	return NewNumericReply(server, RPL_ISON,
		":%s", strings.Join(nicks, " "))
}

func RplMOTDStart(server *Server) Reply {
	return NewNumericReply(server, RPL_MOTDSTART,
		":- %s Message of the day - ", server.name)
}

func RplMOTD(server *Server, line string) Reply {
	return NewNumericReply(server, RPL_MOTD,
		":- %s", line)
}

func RplMOTDEnd(server *Server) Reply {
	return NewNumericReply(server, RPL_ENDOFMOTD,
		":End of MOTD command")
}

//
// errors (also numeric)
//

func ErrAlreadyRegistered(source Identifier) Reply {
	return NewNumericReply(source, ERR_ALREADYREGISTRED,
		":You may not reregister")
}

func ErrNickNameInUse(source Identifier, nick string) Reply {
	return NewNumericReply(source, ERR_NICKNAMEINUSE,
		"%s :Nickname is already in use", nick)
}

func ErrUnknownCommand(source Identifier, command string) Reply {
	return NewNumericReply(source, ERR_UNKNOWNCOMMAND,
		"%s :Unknown command", command)
}

func ErrUsersDontMatch(source Identifier) Reply {
	return NewNumericReply(source, ERR_USERSDONTMATCH,
		":Cannot change mode for other users")
}

func ErrNeedMoreParams(source Identifier, command string) Reply {
	return NewNumericReply(source, ERR_NEEDMOREPARAMS,
		"%s :Not enough parameters", command)
}

func ErrNoSuchChannel(server *Server, channel string) Reply {
	return NewNumericReply(server, ERR_NOSUCHCHANNEL,
		"%s :No such channel", channel)
}

func ErrUserOnChannel(channel *Channel, member *Client) Reply {
	return NewNumericReply(channel.server, ERR_USERONCHANNEL,
		"%s %s :is already on channel", member.Nick(), channel.name)
}

func ErrNotOnChannel(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_NOTONCHANNEL,
		"%s :You're not on that channel", channel.name)
}

func ErrInviteOnlyChannel(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_INVITEONLYCHAN,
		"%s :Cannot join channel (+i)", channel.name)
}

func ErrBadChannelKey(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_BADCHANNELKEY,
		"%s :Cannot join channel (+k)", channel.name)
}

func ErrNoSuchNick(source Identifier, nick string) Reply {
	return NewNumericReply(source, ERR_NOSUCHNICK,
		"%s :No such nick/channel", nick)
}

func ErrPasswdMismatch(server *Server) Reply {
	return NewNumericReply(server, ERR_PASSWDMISMATCH, ":Password incorrect")
}

func ErrNoChanModes(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_NOCHANMODES,
		"%s :Channel doesn't support modes", channel.name)
}

func ErrNoPrivileges(server *Server) Reply {
	return NewNumericReply(server, ERR_NOPRIVILEGES, ":Permission Denied")
}

func ErrRestricted(server *Server) Reply {
	return NewNumericReply(server, ERR_RESTRICTED, ":Your connection is restricted!")
}

func ErrNoSuchServer(server *Server, target string) Reply {
	return NewNumericReply(server, ERR_NOSUCHSERVER, "%s :No such server", target)
}

func ErrUserNotInChannel(server *Server, nick string, channel *Channel) Reply {
	return NewNumericReply(server, ERR_USERNOTINCHANNEL,
		"%s %s :They aren't on that channel", nick, channel.name)
}

func ErrCannotSendToChan(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_CANNOTSENDTOCHAN,
		"%s :Cannot send to channel", channel.name)
}

// <channel> :You're not channel operator
func ErrChanOPrivIsNeeded(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_CHANOPRIVSNEEDED,
		"%s :You're not channel operator", channel.name)
}

func ErrNoMOTD(server *Server) Reply {
	return NewNumericReply(server, ERR_NOMOTD, ":MOTD File is missing")
}

func ErrNoNicknameGiven(server *Server) Reply {
	return NewNumericReply(server, ERR_NONICKNAMEGIVEN, ":No nickname given")
}
<commit_msg>RPL_ENDOFNAMES should come from server<commit_after>package irc

import (
	"fmt"
	"strings"
	"time"
)

func joinedLen(names []string) int {
	var l = len(names) - 1 // " " between names
	for _, name := range names {
		l += len(name)
	}
	return l
}

type BaseReply struct {
	id      string
	message string
	source  Identifier
}

func (reply *BaseReply) SetSource(source Identifier) {
	reply.id = source.Id()
	reply.source = source
}

func (reply *BaseReply) Source() Identifier {
	return reply.source
}

type StringReply struct {
	BaseReply
	code string
}

func NewStringReply(source Identifier, code string,
	format string, args ...interface{}) *StringReply {
	reply := &StringReply{
		code: code,
	}
	reply.SetSource(source)
	reply.message = fmt.Sprintf(format, args...)
	return reply
}

func (reply *StringReply) Format(client *Client) []string {
	message := fmt.Sprintf(":%s %s %s",
		reply.id, reply.code, reply.message)
	return []string{message}
}

func (reply *StringReply) String() string {
	return fmt.Sprintf("Reply(source=%s, code=%s, message=%s)",
		reply.id, reply.code, reply.message)
}

type NumericReply struct {
	BaseReply
	code Numeric
}

func NewNumericReply(source Identifier, code Numeric, format string,
	args ...interface{}) *NumericReply {
	reply := &NumericReply{
		code: code,
	}
	reply.SetSource(source)
	reply.message = fmt.Sprintf(format, args...)
	return reply
}

func (reply *NumericReply) Format(client *Client) []string {
	message := fmt.Sprintf(":%s %s %s %s",
		reply.id, reply.code, client.Nick(), reply.message)
	return []string{message}
}

func (reply *NumericReply) String() string {
	return fmt.Sprintf("Reply(source=%s, code=%d, message=%s)",
		reply.id, reply.code, reply.message)
}

// names reply

type NamesReply struct {
	BaseReply
	channel *Channel
}

func NewNamesReply(channel *Channel) Reply {
	reply := &NamesReply{
		channel: channel,
	}
	reply.SetSource(channel)
	return reply
}

func (reply *NamesReply) Format(client *Client) []string {
	lines := make([]string, 0)
	base := RplNamReply(reply.channel, []string{})
	baseLen := len(base.Format(client)[0])
	tooLong := func(names []string) bool {
		return (baseLen + joinedLen(names)) > MAX_REPLY_LEN
	}
	from, to := 0, 1
	nicks := reply.channel.Nicks()
	for to < len(nicks) {
		if (from < (to - 1)) && tooLong(nicks[from:to]) {
			lines = append(lines, RplNamReply(reply.channel, nicks[from:to-1]).Format(client)...)
			from, to = to-1, to
		} else {
			to += 1
		}
	}
	if from < len(nicks) {
		lines = append(lines, RplNamReply(reply.channel, nicks[from:]).Format(client)...)
	}
	lines = append(lines, RplEndOfNames(reply.channel).Format(client)...)
	return lines
}

func (reply *NamesReply) String() string {
	return fmt.Sprintf("NamesReply(channel=%s, names=%s)",
		reply.channel, reply.channel.Nicks())
}

// messaging replies

func RplPrivMsg(source Identifier, target Identifier, message string) Reply {
	return NewStringReply(source, "PRIVMSG", "%s :%s", target.Nick(), message)
}

func RplNotice(source Identifier, target Identifier, message string) Reply {
	return NewStringReply(source, "NOTICE", "%s :%s", target.Nick(), message)
}

func RplNick(source Identifier, newNick string) Reply {
	return NewStringReply(source, "NICK", newNick)
}

func RplJoin(client *Client, channel *Channel) Reply {
	return NewStringReply(client, "JOIN", channel.name)
}

func RplPart(client *Client, channel *Channel, message string) Reply {
	return NewStringReply(client, "PART", "%s :%s", channel.name, message)
}

func RplTopicMsg(source Identifier, channel *Channel) Reply {
	return NewStringReply(source, "TOPIC", "%s :%s", channel.name, channel.topic)
}

func RplPing(server *Server, target Identifier) Reply {
	return NewStringReply(server, "PING", target.Nick())
}

func RplPong(server *Server, client *Client) Reply {
	return NewStringReply(server, "PONG", client.Nick())
}

func RplQuit(client *Client, message string) Reply {
	return NewStringReply(client, "QUIT", ":%s", message)
}

func RplError(server *Server, target Identifier) Reply {
	return NewStringReply(server, "ERROR", target.Nick())
}

func RplInviteMsg(channel *Channel, inviter *Client) Reply {
	return NewStringReply(inviter, "INVITE", channel.name)
}

// numeric replies

func RplWelcome(source Identifier, client *Client) Reply {
	return NewNumericReply(source, RPL_WELCOME,
		"Welcome to the Internet Relay Network %s", client.Id())
}

func RplYourHost(server *Server) Reply {
	return NewNumericReply(server, RPL_YOURHOST,
		"Your host is %s, running version %s", server.name, VERSION)
}

func RplCreated(server *Server) Reply {
	return NewNumericReply(server, RPL_CREATED,
		"This server was created %s", server.ctime.Format(time.RFC1123))
}

func RplMyInfo(server *Server) Reply {
	return NewNumericReply(server, RPL_MYINFO,
		"%s %s aiOorsw abeIikmntpqrsl", server.name, VERSION)
}

func RplUModeIs(server *Server, client *Client) Reply {
	return NewNumericReply(server, RPL_UMODEIS, client.ModeString())
}

func RplNoTopic(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_NOTOPIC,
		"%s :No topic is set", channel.name)
}

func RplTopic(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_TOPIC,
		"%s :%s", channel.name, channel.topic)
}

func RplInvitingMsg(channel *Channel, invitee *Client) Reply {
	return NewNumericReply(channel.server, RPL_INVITING,
		"%s %s", channel.name, invitee.Nick())
}

func RplNamReply(channel *Channel, names []string) *NumericReply {
	return NewNumericReply(channel.server, RPL_NAMREPLY, "= %s :%s",
		channel.name, strings.Join(names, " "))
}

func RplEndOfNames(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_ENDOFNAMES,
		"%s :End of NAMES list", channel.name)
}

// :You are now an IRC operator
func RplYoureOper(server *Server) Reply {
	return NewNumericReply(server, RPL_YOUREOPER, ":You are now an IRC operator")
}

func RplWhoisUser(server *Server, client *Client) Reply {
	return NewNumericReply(server, RPL_WHOISUSER, "%s %s %s * :%s",
		client.Nick(), client.username, client.hostname, client.realname)
}

func RplEndOfWhois(server *Server) Reply {
	return NewNumericReply(server, RPL_ENDOFWHOIS, ":End of WHOIS list")
}

func RplChannelModeIs(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_CHANNELMODEIS, "%s %s",
		channel.name, channel.ModeString())
}

// <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
// :<hopcount> <real name>
func RplWhoReply(server *Server, channel *Channel, client *Client) Reply {
	channelName := "*"
	if channel != nil {
		channelName = channel.name
	}
	return NewNumericReply(server, RPL_WHOREPLY, "%s %s %s %s %s H :0 %s",
		channelName, client.username, client.hostname, server.name, client.Nick(),
		client.realname)
}

// <name> :End of WHO list
func RplEndOfWho(server *Server, name string) Reply {
	return NewNumericReply(server, RPL_ENDOFWHO, "%s :End of WHO list", name)
}

func RplBanList(channel *Channel, ban UserMask) Reply {
	return NewNumericReply(channel.server, RPL_BANLIST, "%s %s", channel.name, ban)
}

func RplEndOfBanList(channel *Channel) Reply {
	return NewNumericReply(channel.server, RPL_ENDOFBANLIST,
		"%s :End of channel ban list", channel.name)
}

func RplNowAway(server *Server) Reply {
	return NewNumericReply(server, RPL_NOWAWAY,
		":You have been marked as being away")
}

func RplUnAway(server *Server) Reply {
	return NewNumericReply(server, RPL_UNAWAY,
		":You are no longer marked as being away")
}

func RplAway(server *Server, client *Client) Reply {
	return NewNumericReply(server, RPL_AWAY,
		"%s :%s", client.Nick(), client.awayMessage)
}

func RplIsOn(server *Server, nicks []string) Reply {
	return NewNumericReply(server, RPL_ISON,
		":%s", strings.Join(nicks, " "))
}

func RplMOTDStart(server *Server) Reply {
	return NewNumericReply(server, RPL_MOTDSTART,
		":- %s Message of the day - ", server.name)
}

func RplMOTD(server *Server, line string) Reply {
	return NewNumericReply(server, RPL_MOTD,
		":- %s", line)
}

func RplMOTDEnd(server *Server) Reply {
	return NewNumericReply(server, RPL_ENDOFMOTD,
		":End of MOTD command")
}

//
// errors (also numeric)
//

func ErrAlreadyRegistered(source Identifier) Reply {
	return NewNumericReply(source, ERR_ALREADYREGISTRED,
		":You may not reregister")
}

func ErrNickNameInUse(source Identifier, nick string) Reply {
	return NewNumericReply(source, ERR_NICKNAMEINUSE,
		"%s :Nickname is already in use", nick)
}

func ErrUnknownCommand(source Identifier, command string) Reply {
	return NewNumericReply(source, ERR_UNKNOWNCOMMAND,
		"%s :Unknown command", command)
}

func ErrUsersDontMatch(source Identifier) Reply {
	return NewNumericReply(source, ERR_USERSDONTMATCH,
		":Cannot change mode for other users")
}

func ErrNeedMoreParams(source Identifier, command string) Reply {
	return NewNumericReply(source, ERR_NEEDMOREPARAMS,
		"%s :Not enough parameters", command)
}

func ErrNoSuchChannel(server *Server, channel string) Reply {
	return NewNumericReply(server, ERR_NOSUCHCHANNEL,
		"%s :No such channel", channel)
}

func ErrUserOnChannel(channel *Channel, member *Client) Reply {
	return NewNumericReply(channel.server, ERR_USERONCHANNEL,
		"%s %s :is already on channel", member.Nick(), channel.name)
}

func ErrNotOnChannel(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_NOTONCHANNEL,
		"%s :You're not on that channel", channel.name)
}

func ErrInviteOnlyChannel(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_INVITEONLYCHAN,
		"%s :Cannot join channel (+i)", channel.name)
}

func ErrBadChannelKey(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_BADCHANNELKEY,
		"%s :Cannot join channel (+k)", channel.name)
}

func ErrNoSuchNick(source Identifier, nick string) Reply {
	return NewNumericReply(source, ERR_NOSUCHNICK,
		"%s :No such nick/channel", nick)
}

func ErrPasswdMismatch(server *Server) Reply {
	return NewNumericReply(server, ERR_PASSWDMISMATCH, ":Password incorrect")
}

func ErrNoChanModes(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_NOCHANMODES,
		"%s :Channel doesn't support modes", channel.name)
}

func ErrNoPrivileges(server *Server) Reply {
	return NewNumericReply(server, ERR_NOPRIVILEGES, ":Permission Denied")
}

func ErrRestricted(server *Server) Reply {
	return NewNumericReply(server, ERR_RESTRICTED, ":Your connection is restricted!")
}

func ErrNoSuchServer(server *Server, target string) Reply {
	return NewNumericReply(server, ERR_NOSUCHSERVER, "%s :No such server", target)
}

func ErrUserNotInChannel(server *Server, nick string, channel *Channel) Reply {
	return NewNumericReply(server, ERR_USERNOTINCHANNEL,
		"%s %s :They aren't on that channel", nick, channel.name)
}

func ErrCannotSendToChan(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_CANNOTSENDTOCHAN,
		"%s :Cannot send to channel", channel.name)
}

// <channel> :You're not channel operator
func ErrChanOPrivIsNeeded(channel *Channel) Reply {
	return NewNumericReply(channel.server, ERR_CHANOPRIVSNEEDED,
		"%s :You're not channel operator", channel.name)
}

func ErrNoMOTD(server *Server) Reply {
	return NewNumericReply(server, ERR_NOMOTD, ":MOTD File is missing")
}

func ErrNoNicknameGiven(server *Server) Reply {
	return NewNumericReply(server, ERR_NONICKNAMEGIVEN, ":No nickname given")
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"encoding/xml"
	"flag"
	irc "github.com/fluffle/goirc/client"
	_ "github.com/go-sql-driver/mysql"
	"github.com/tv42/base58"
	"html"
	"io"
	"io/ioutil"
	"log"
	"math/big"
	"math/rand"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

var (
	config                  Config
	urlRegex, regexErr      = regexp.Compile(`(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s` + "`" + `!()\[\]{};:'".,<>?«»“”‘’]))`)
	httpRegex, httpRegexErr = regexp.Compile(`http(s)?://.*`)
)

const flickrApiUrl = "http://api.flickr.com/services/rest/"

type Config struct {
	Channel      string
	DBConn       string
	Nick         string
	Ident        string
	FullName     string
	FlickrAPIKey string
	IRCPass      string
	Commands     []Command `xml:">Command"`
}

type Command struct {
	Name string
	Text string
}

type Setresp struct {
	Sets []Set `xml:"collections>collection>set"`
}

type Set struct {
	Id          string `xml:"id,attr"`
	Title       string `xml:"title,attr"`
	Description string `xml:"description,attr"`
}

type Photoresp struct {
	Photos []Photo `xml:"photoset>photo"`
}

type Photo struct {
	Id        int64  `xml:"id,attr"`
	Secret    string `xml:"secret,attr"`
	Server    string `xml:"server,attr"`
	Farm      string `xml:"farm,attr"`
	Title     string `xml:"title,attr"`
	Isprimary string `xml:"isprimary,attr"`
}

func htmlfetch(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	respbody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return respbody, nil
}

func random(limit int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(limit)
}

func sendUrl(channel, postedUrl string, conn *irc.Conn) {
	log.Println("Fetching title for " + postedUrl + " In channel " + channel)
	if !httpRegex.MatchString(postedUrl) {
		postedUrl = "http://" + postedUrl
	}

	resp, err := http.Get(postedUrl)
	if err != nil {
		log.Println(err)
		return
	}
	defer resp.Body.Close()
	buf := make([]byte, 1024)
	respbody := []byte{}
	for i := 0; i < 30; i++ {
		n, err := resp.Body.Read(buf)
		if err != nil && err != io.EOF {
			return
		}
		if n == 0 {
			break
		}
		respbody = append(respbody, buf[:n]...)
	}

	stringbody := string(respbody)
	titlestart := strings.Index(stringbody, "<title>")
	titleend := strings.Index(stringbody, "</title>")
	if titlestart != -1 && titlestart != -1 {
		title := string(respbody[titlestart+7 : titleend])
		title = strings.TrimSpace(title)
		if title != "" {
			parsedurl, err := url.Parse(postedUrl)
			if err == nil {
				postedUrl = parsedurl.Host
			}
			title = "Title: " + html.UnescapeString(title) + " (at " + postedUrl + ")"
			log.Println(title)
			conn.Privmsg(channel, title)
		}
	}
}

func dance(channel string, conn *irc.Conn) {
	conn.Privmsg(channel, ":D-<")
	time.Sleep(500 * time.Millisecond)
	conn.Privmsg(channel, ":D|<")
	time.Sleep(500 * time.Millisecond)
	conn.Privmsg(channel, ":D/<")
}

func haata(channel string, conn *irc.Conn) {
	flickrUrl, err := url.Parse(flickrApiUrl)
	if err != nil {
		log.Println(err)
		return
	}
	v := flickrUrl.Query()
	v.Set("method", "flickr.collections.getTree")
	v.Set("api_key", config.FlickrAPIKey)
	v.Set("user_id", "57321699@N06")
	v.Set("collection_id", "57276377-72157635417889224")
	flickrUrl.RawQuery = v.Encode()

	sets, err := htmlfetch(flickrUrl.String())
	if err != nil {
		log.Println(err)
		return
	}
	var setresp Setresp
	xml.Unmarshal(sets, &setresp)
	randsetindex := random(len(setresp.Sets))
	randset := setresp.Sets[randsetindex].Id

	flickrUrl, err = url.Parse(flickrApiUrl)
	if err != nil {
		log.Println(err)
		return
	}
	v = flickrUrl.Query()
	v.Set("method", "flickr.photosets.getPhotos")
	v.Set("api_key", config.FlickrAPIKey)
	v.Set("photoset_id", randset)
	flickrUrl.RawQuery = v.Encode()

	pics, err := htmlfetch(flickrUrl.String())
	if err != nil {
		log.Println(err)
		return
	}
	var photoresp Photoresp
	xml.Unmarshal(pics, &photoresp)
	randpic := random(len(photoresp.Photos))
	photostring := string(base58.EncodeBig([]byte{}, big.NewInt(photoresp.Photos[randpic].Id)))
	conn.Privmsg(channel, strings.TrimSpace(setresp.Sets[randsetindex].Title)+`: http://flic.kr/p/`+photostring)
}

func googSearch(channel, query string, conn *irc.Conn) {
	query = strings.TrimSpace(query[7:])
	if query == "" {
		conn.Privmsg(channel, "Example: !search stuff and junk")
		return
	}
	searchUrl, err := url.Parse("https://google.com/search")
	if err != nil {
		panic(err)
	}
	v := searchUrl.Query()
	v.Set("q", query)
	searchUrl.RawQuery = v.Encode()
	conn.Privmsg(channel, searchUrl.String())
}

func handleMessage(conn *irc.Conn, line *irc.Line) {
	urllist := []string{}
	numlinks := 0

	// Special commands
	switch strings.TrimSpace(strings.Split(line.Args[1], " ")[0]) {
	case "!dance":
		if line.Nick == "sadbox" {
			go dance(line.Args[0], conn)
		}
	case "!audio":
		if line.Nick == "sadbox" {
			conn.Privmsg(line.Args[0], "https://sadbox.org/static/audiophile.html")
		}
	case "!cst":
		if line.Nick == "sadbox" {
			conn.Privmsg(line.Args[0], "13,8#CSTMASTERRACE")
		}
	case "!haata":
		go haata(line.Args[0], conn)
	case "!search":
		go googSearch(line.Args[0], line.Args[1], conn)
	}

	// Commands that are read in from the config file
	for _, command := range config.Commands {
		if strings.HasPrefix(line.Args[1], command.Name) {
			conn.Privmsg(line.Args[0], command.Text)
		}
	}

NextWord:
	for _, word := range strings.Split(line.Args[1], " ") {
		word = strings.TrimSpace(word)
		if urlRegex.MatchString(word) {
			for _, subUrl := range urllist {
				if subUrl == word {
					continue NextWord
				}
			}
			numlinks++
			if numlinks > 3 {
				break
			}
			urllist = append(urllist, word)
			go sendUrl(line.Args[0], word, conn)
		}

	}
	db, err := sql.Open("mysql", config.DBConn)
	if err != nil {
		log.Println(err)
	}
	defer db.Close()
	_, err = db.Exec("insert into messages (Nick, Ident, Host, Src, Cmd, Channel, Message, Time) values (?, ?, ?, ?, ?, ?, ?, ?)", line.Nick, line.Ident, line.Host, line.Src, line.Cmd, line.Args[0], line.Args[1], line.Time)
	if err != nil {
		log.Println(err)
	}
}

func init() {
	log.Println("Starting sadbot")

	flag.Parse()

	if regexErr != nil {
		log.Panic(regexErr)
	}
	if httpRegexErr != nil {
		log.Panic(httpRegexErr)
	}

	xmlFile, err := ioutil.ReadFile("config.xml")
	if err != nil {
		log.Fatal(err)
	}
	xml.Unmarshal(xmlFile, &config)
	log.Println("Loaded config file!")
	log.Printf("Joining channel %s", config.Channel)
	log.Printf("Nick: %s", config.Nick)
	log.Printf("Ident: %s", config.Ident)
	log.Printf("FullName: %s", config.FullName)

	log.Printf("Found %d commands", len(config.Commands))
	for index, command := range config.Commands {
		log.Printf("%d %s: %s", index+1, command.Name, command.Text)
	}
}

func main() {
	c := irc.SimpleClient(config.Nick, config.Ident, config.FullName)

	c.AddHandler(irc.CONNECTED,
		func(conn *irc.Conn, line *irc.Line) {
			conn.Join(config.Channel)
			conn.Privmsg("nickserv", "identify "+config.Nick+" "+config.IRCPass)
			log.Println("Connected!")
		})

	quit := make(chan bool)

	c.AddHandler(irc.DISCONNECTED,
		func(conn *irc.Conn, line *irc.Line) { quit <- true })

	c.AddHandler("PRIVMSG", handleMessage)

	if err := c.Connect("irc.freenode.net"); err != nil {
		log.Fatalln("Connection error: %s\n", err)
	}

	<-quit
}
<commit_msg>Dont re-split the message 3 times<commit_after>package main

import (
	"database/sql"
	"encoding/xml"
	"flag"
	irc "github.com/fluffle/goirc/client"
	_ "github.com/go-sql-driver/mysql"
	"github.com/tv42/base58"
	"html"
	"io"
	"io/ioutil"
	"log"
	"math/big"
	"math/rand"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

var (
	config                  Config
	urlRegex, regexErr      = regexp.Compile(`(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s` + "`" + `!()\[\]{};:'".,<>?«»“”‘’]))`)
	httpRegex, httpRegexErr = regexp.Compile(`http(s)?://.*`)
)

const flickrApiUrl = "http://api.flickr.com/services/rest/"

type Config struct {
	Channel      string
	DBConn       string
	Nick         string
	Ident        string
	FullName     string
	FlickrAPIKey string
	IRCPass      string
	Commands     []Command `xml:">Command"`
}

type Command struct {
	Name string
	Text string
}

type Setresp struct {
	Sets []Set `xml:"collections>collection>set"`
}

type Set struct {
	Id          string `xml:"id,attr"`
	Title       string `xml:"title,attr"`
	Description string `xml:"description,attr"`
}

type Photoresp struct {
	Photos []Photo `xml:"photoset>photo"`
}

type Photo struct {
	Id        int64  `xml:"id,attr"`
	Secret    string `xml:"secret,attr"`
	Server    string `xml:"server,attr"`
	Farm      string `xml:"farm,attr"`
	Title     string `xml:"title,attr"`
	Isprimary string `xml:"isprimary,attr"`
}

func htmlfetch(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	respbody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return respbody, nil
}

func random(limit int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(limit)
}

func sendUrl(channel, postedUrl string, conn *irc.Conn) {
	log.Println("Fetching title for " + postedUrl + " In channel " + channel)
	if !httpRegex.MatchString(postedUrl) {
		postedUrl = "http://" + postedUrl
	}

	resp, err := http.Get(postedUrl)
	if err != nil {
		log.Println(err)
		return
	}
	defer resp.Body.Close()
	buf := make([]byte, 1024)
	respbody := []byte{}
	for i := 0; i < 30; i++ {
		n, err := resp.Body.Read(buf)
		if err != nil && err != io.EOF {
			return
		}
		if n == 0 {
			break
		}
		respbody = append(respbody, buf[:n]...)
	}

	stringbody := string(respbody)
	titlestart := strings.Index(stringbody, "<title>")
	titleend := strings.Index(stringbody, "</title>")
	if titlestart != -1 && titlestart != -1 {
		title := string(respbody[titlestart+7 : titleend])
		title = strings.TrimSpace(title)
		if title != "" {
			parsedurl, err := url.Parse(postedUrl)
			if err == nil {
				postedUrl = parsedurl.Host
			}
			title = "Title: " + html.UnescapeString(title) + " (at " + postedUrl + ")"
			log.Println(title)
			conn.Privmsg(channel, title)
		}
	}
}

func dance(channel string, conn *irc.Conn) {
	conn.Privmsg(channel, ":D-<")
	time.Sleep(500 * time.Millisecond)
	conn.Privmsg(channel, ":D|<")
	time.Sleep(500 * time.Millisecond)
	conn.Privmsg(channel, ":D/<")
}

func haata(channel string, conn *irc.Conn) {
	flickrUrl, err := url.Parse(flickrApiUrl)
	if err != nil {
		log.Println(err)
		return
	}
	v := flickrUrl.Query()
	v.Set("method", "flickr.collections.getTree")
	v.Set("api_key", config.FlickrAPIKey)
	v.Set("user_id", "57321699@N06")
	v.Set("collection_id", "57276377-72157635417889224")
	flickrUrl.RawQuery = v.Encode()

	sets, err := htmlfetch(flickrUrl.String())
	if err != nil {
		log.Println(err)
		return
	}
	var setresp Setresp
	xml.Unmarshal(sets, &setresp)
	randsetindex := random(len(setresp.Sets))
	randset := setresp.Sets[randsetindex].Id

	flickrUrl, err = url.Parse(flickrApiUrl)
	if err != nil {
		log.Println(err)
		return
	}
	v = flickrUrl.Query()
	v.Set("method", "flickr.photosets.getPhotos")
	v.Set("api_key", config.FlickrAPIKey)
	v.Set("photoset_id", randset)
	flickrUrl.RawQuery = v.Encode()

	pics, err := htmlfetch(flickrUrl.String())
	if err != nil {
		log.Println(err)
		return
	}
	var photoresp Photoresp
	xml.Unmarshal(pics, &photoresp)
	randpic := random(len(photoresp.Photos))
	photostring := string(base58.EncodeBig([]byte{}, big.NewInt(photoresp.Photos[randpic].Id)))
	conn.Privmsg(channel, strings.TrimSpace(setresp.Sets[randsetindex].Title)+`: http://flic.kr/p/`+photostring)
}

func googSearch(channel, query string, conn *irc.Conn) {
	query = strings.TrimSpace(query[7:])
	if query == "" {
		conn.Privmsg(channel, "Example: !search stuff and junk")
		return
	}
	searchUrl, err := url.Parse("https://google.com/search")
	if err != nil {
		panic(err)
	}
	v := searchUrl.Query()
	v.Set("q", query)
	searchUrl.RawQuery = v.Encode()
	conn.Privmsg(channel, searchUrl.String())
}

func handleMessage(conn *irc.Conn, line *irc.Line) {
	urllist := []string{}
	numlinks := 0

	splitmessage := strings.Split(line.Args[1], " ")

	// Special commands
	switch strings.TrimSpace(splitmessage[0]) {
	case "!dance":
		if line.Nick == "sadbox" {
			go dance(line.Args[0], conn)
		}
	case "!audio":
		if line.Nick == "sadbox" {
			conn.Privmsg(line.Args[0], "https://sadbox.org/static/audiophile.html")
		}
	case "!cst":
		if line.Nick == "sadbox" {
			conn.Privmsg(line.Args[0], "13,8#CSTMASTERRACE")
		}
	case "!haata":
		go haata(line.Args[0], conn)
	case "!search":
		go googSearch(line.Args[0], line.Args[1], conn)
	}

	// Commands that are read in from the config file
	for _, command := range config.Commands {
		if strings.TrimSpace(splitmessage[0]) == command.Name {
			conn.Privmsg(line.Args[0], command.Text)
		}
	}

NextWord:
	for _, word := range splitmessage {
		word = strings.TrimSpace(word)
		if urlRegex.MatchString(word) {
			for _, subUrl := range urllist {
				if subUrl == word {
					continue NextWord
				}
			}
			numlinks++
			if numlinks > 3 {
				break
			}
			urllist = append(urllist, word)
			go sendUrl(line.Args[0], word, conn)
		}

	}
	db, err := sql.Open("mysql", config.DBConn)
	if err != nil {
		log.Println(err)
	}
	defer db.Close()
	_, err = db.Exec("insert into messages (Nick, Ident, Host, Src, Cmd, Channel, Message, Time) values (?, ?, ?, ?, ?, ?, ?, ?)", line.Nick, line.Ident, line.Host, line.Src, line.Cmd, line.Args[0], line.Args[1], line.Time)
	if err != nil {
		log.Println(err)
	}
}

func init() {
	log.Println("Starting sadbot")

	flag.Parse()

	if regexErr != nil {
		log.Panic(regexErr)
	}
	if httpRegexErr != nil {
		log.Panic(httpRegexErr)
	}

	xmlFile, err := ioutil.ReadFile("config.xml")
	if err != nil {
		log.Fatal(err)
	}
	xml.Unmarshal(xmlFile, &config)
	log.Println("Loaded config file!")
	log.Printf("Joining channel %s", config.Channel)
	log.Printf("Nick: %s", config.Nick)
	log.Printf("Ident: %s", config.Ident)
	log.Printf("FullName: %s", config.FullName)

	log.Printf("Found %d commands", len(config.Commands))
	for index, command := range config.Commands {
		log.Printf("%d %s: %s", index+1, command.Name, command.Text)
	}
}

func main() {
	c := irc.SimpleClient(config.Nick, config.Ident, config.FullName)

	c.AddHandler(irc.CONNECTED,
		func(conn *irc.Conn, line *irc.Line) {
			conn.Join(config.Channel)
			conn.Privmsg("nickserv", "identify "+config.Nick+" "+config.IRCPass)
			log.Println("Connected!")
		})

	quit := make(chan bool)

	c.AddHandler(irc.DISCONNECTED,
		func(conn *irc.Conn, line *irc.Line) { quit <- true })

	c.AddHandler("PRIVMSG", handleMessage)

	if err := c.Connect("irc.freenode.net"); err != nil {
		log.Fatalln("Connection error: %s\n", err)
	}

	<-quit
}
<|endoftext|>