<commit_before>package main

import (
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/emojis"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/metrics"
	"github.com/Seklfreak/Robyul2/modules"
	"github.com/Seklfreak/Robyul2/ratelimits"
	"github.com/Sirupsen/logrus"
	"github.com/bwmarrin/discordgo"
	"github.com/getsentry/raven-go"
)

// BotOnReady gets called after the gateway connected
func BotOnReady(session *discordgo.Session, event *discordgo.Ready) {
	log := cache.GetLogger()

	log.WithField("module", "bot").Info("Connected to discord!")
	log.WithField("module", "bot").Info("Invite link: " + fmt.Sprintf(
		"https://discordapp.com/oauth2/authorize?client_id=%s&scope=bot&permissions=%s",
		helpers.GetConfig().Path("discord.id").Data().(string),
		helpers.GetConfig().Path("discord.perms").Data().(string),
	))

	// Cache the session
	cache.SetSession(session)

	// Load and init all modules
	modules.Init(session)

	// Run async worker for guild changes
	go helpers.GuildSettingsUpdater()

	// Run async game-changer
	go changeGameInterval(session)

	// request guild members from the gateway
	go func() {
		time.Sleep(30 * time.Second)

		for _, guild := range session.State.Guilds {
			err := session.RequestGuildMembers(guild.ID, "", 0)
			if err != nil {
				raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
			}
		}
	}()

	// Run auto-leaver for non-beta guilds
	//go autoLeaver(session)

	// Run ratelimiter
	ratelimits.Container.Init()

	go func() {
		time.Sleep(3 * time.Second)

		configName := helpers.GetConfig().Path("bot.name").Data().(string)
		configAvatar := helpers.GetConfig().Path("bot.avatar").Data().(string)

		// Change avatar if desired
		if configAvatar != "" && configAvatar != session.State.User.Avatar {
			session.UserUpdate(
				"",
				"",
				session.State.User.Username,
				configAvatar,
				"",
			)
		}

		// Change name if desired
		if configName != "" && configName != session.State.User.Username {
			session.UserUpdate(
				"",
				"",
				configName,
				session.State.User.Avatar,
				"",
			)
		}
	}()

	// Run async game-changer
	//go changeGameInterval(session)

	// Run auto-leaver for non-beta guilds
	//go autoLeaver(session)
}

func BotOnMemberListChunk(session *discordgo.Session, members *discordgo.GuildMembersChunk) {
	cache.GetLogger().WithField("module", "bot").Debug(
		fmt.Sprintf("received guild member chunk for guild: %s (%d members)",
			members.GuildID, len(members.Members)))
	var err error
	for _, member := range members.Members {
		member.GuildID = members.GuildID
		err = session.State.MemberAdd(member)
		if err != nil {
			raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
		}
	}
}

func BotGuildOnPresenceUpdate(session *discordgo.Session, presence *discordgo.PresenceUpdate) {
	if presence.GuildID == "" {
		return
	}

	member, err := cache.GetSession().State.Member(presence.GuildID, presence.User.ID)
	if err != nil {
		if strings.Contains(err.Error(), "state cache not found") {
			return
		}
		raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
		return
	}

	change := false
	if presence.User.Avatar != "" {
		member.User.Avatar = presence.User.Avatar
		change = true
	}
	if presence.User.Discriminator != "" {
		member.User.Discriminator = presence.User.Discriminator
		change = true
	}
	if presence.User.Email != "" {
		member.User.Email = presence.User.Email
		change = true
	}
	if presence.User.Token != "" {
		member.User.Token = presence.User.Token
		change = true
	}
	if presence.User.Username != "" {
		member.User.Username = presence.User.Username
		change = true
	}

	if change == true {
		err = session.State.MemberAdd(member)
		if err != nil {
			raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
		}
	}
}

func BotOnGuildMemberAdd(session *discordgo.Session, member *discordgo.GuildMemberAdd) {
	modules.CallExtendedPluginOnGuildMemberAdd(
		member.Member,
	)
}

func BotOnGuildMemberRemove(session *discordgo.Session, member *discordgo.GuildMemberRemove) {
	modules.CallExtendedPluginOnGuildMemberRemove(
		member.Member,
	)
}

func BotOnGuildBanAdd(session *discordgo.Session, user *discordgo.GuildBanAdd) {
	modules.CallExtendedPluginOnGuildBanAdd(
		user,
	)
}

func BotOnGuildBanRemove(session *discordgo.Session, user *discordgo.GuildBanRemove) {
	modules.CallExtendedPluginOnGuildBanRemove(
		user,
	)
}

// BotOnMessageCreate gets called after a new message was sent
// This will be called after *every* message on *every* server so it should die as soon as possible
// or spawn costly work inside of coroutines.
func BotOnMessageCreate(session *discordgo.Session, message *discordgo.MessageCreate) {
	// Ignore other bots and @everyone/@here
	if message.Author.Bot || message.MentionEveryone {
		return
	}

	if helpers.IsBlacklisted(message.Author.ID) {
		return
	}

	// Get the channel
	// Ignore the event if we cannot resolve the channel
	channel, err := helpers.GetChannel(message.ChannelID)
	if err != nil {
		go raven.CaptureError(err, map[string]string{})
		return
	}

	/*
		if channel.Type == discordgo.ChannelTypeDM {
			// Track usage
			metrics.CleverbotRequests.Add(1)

			// Mark typing
			session.ChannelTyping(message.ChannelID)

			// Prepare content for editing
			msg := message.Content

			/// Remove our @mention
			msg = strings.Replace(msg, "<@"+session.State.User.ID+">", "", -1)

			// Trim message
			msg = strings.TrimSpace(msg)

			// Resolve other @mentions before sending the message
			for _, user := range message.Mentions {
				msg = strings.Replace(msg, "<@"+user.ID+">", user.Username, -1)
			}

			// Remove smileys
			msg = regexp.MustCompile(`:\w+:`).ReplaceAllString(msg, "")

			// Send to cleverbot
			helpers.CleverbotSend(session, channel.ID, msg)
			return
		}*/

	// Check if the message contains @mentions for us
	if strings.HasPrefix(message.Content, "<@") && len(message.Mentions) > 0 && message.Mentions[0].ID == session.State.User.ID {
		// Consume a key for this action
		e := ratelimits.Container.Drain(1, message.Author.ID)
		if e != nil {
			return
		}

		// Prepare content for editing
		msg := message.Content

		/// Remove our @mention
		msg = strings.Replace(msg, "<@"+session.State.User.ID+">", "", -1)

		// Trim message
		msg = strings.TrimSpace(msg)

		// Convert to []byte before matching
		bmsg := []byte(msg)

		// Match against common task patterns
		// Send to cleverbot if nothing matches
		switch {
		case regexp.MustCompile("(?i)^HELP.*").Match(bmsg):
			metrics.CommandsExecuted.Add(1)
			sendHelp(message)
			return

		case regexp.MustCompile("(?i)^PREFIX.*").Match(bmsg):
			metrics.CommandsExecuted.Add(1)
			prefix := helpers.GetPrefixForServer(channel.GuildID)
			if prefix == "" {
				cache.GetSession().ChannelMessageSend(
					channel.ID,
					helpers.GetText("bot.prefix.not-set"),
				)
			}

			cache.GetSession().ChannelMessageSend(
				channel.ID,
				helpers.GetTextF("bot.prefix.is", prefix),
			)
			return

			/*
				case regexp.MustCompile("(?i)^REFRESH CHAT SESSION$").Match(bmsg):
					metrics.CommandsExecuted.Add(1)
					helpers.RequireAdmin(message.Message, func() {
						// Refresh cleverbot session
						helpers.CleverbotRefreshSession(channel.ID)
						cache.GetSession().ChannelMessageSend(channel.ID, helpers.GetText("bot.cleverbot.refreshed"))
					})
					return*/

		case regexp.MustCompile("(?i)^SET PREFIX (.){1,25}$").Match(bmsg):
			metrics.CommandsExecuted.Add(1)
			helpers.RequireAdmin(message.Message, func() {
				// Extract prefix
				prefix := strings.Fields(regexp.MustCompile("(?i)^SET PREFIX\\s").ReplaceAllString(msg, ""))[0]

				// Set new prefix
				err := helpers.SetPrefixForServer(
					channel.GuildID,
					prefix,
				)

				if err != nil {
					helpers.SendError(message.Message, err)
				} else {
					cache.GetSession().ChannelMessageSend(channel.ID, helpers.GetTextF("bot.prefix.saved", prefix))
				}
			})
			return

			/*
				default:
					// Track usage
					metrics.CleverbotRequests.Add(1)

					// Mark typing
					session.ChannelTyping(message.ChannelID)

					// Resolve other @mentions before sending the message
					for _, user := range message.Mentions {
						msg = strings.Replace(msg, "<@"+user.ID+">", user.Username, -1)
					}

					// Remove smileys
					msg = regexp.MustCompile(`:\w+:`).ReplaceAllString(msg, "")

					// Send to cleverbot
					helpers.CleverbotSend(session, channel.ID, msg)
					return*/
		}
	}

	modules.CallExtendedPlugin(
		message.Content,
		message.Message,
	)

	// Only continue if a prefix is set
	prefix := helpers.GetPrefixForServer(channel.GuildID)
	if prefix == "" {
		return
	}

	// Check if the message is prefixed for us
	// If not exit
	if !strings.HasPrefix(message.Content, prefix) {
		return
	}

	// Check if the user is allowed to request commands
	if !ratelimits.Container.HasKeys(message.Author.ID) && !helpers.IsBotAdmin(message.Author.ID) {
		session.ChannelMessageSend(message.ChannelID, helpers.GetTextF("bot.ratelimit.hit", message.Author.ID))

		ratelimits.Container.Set(message.Author.ID, -1)
		return
	}

	// Split the message into parts
	parts := strings.Fields(message.Content)

	// Save a sanitized version of the command (no prefix)
	cmd := strings.Replace(parts[0], prefix, "", 1)

	// Check if the user calls for help
	if cmd == "h" || cmd == "help" {
		metrics.CommandsExecuted.Add(1)
		sendHelp(message)
		return
	}

	// Separate arguments from the command
	content := strings.TrimSpace(strings.Replace(message.Content, prefix+cmd, "", -1))

	// Log commands
	cache.GetLogger().WithFields(logrus.Fields{
		"module":    "bot",
		"channelID": message.ChannelID,
		"userID":    message.Author.ID,
	}).Debug(fmt.Sprintf("%s (#%s): %s",
		message.Author.Username, message.Author.ID, message.Content))

	// Check if a module matches said command
	modules.CallBotPlugin(cmd, content, message.Message)

	// Check if a trigger matches
	modules.CallTriggerPlugin(cmd, content, message.Message)
}

func BotOnMessageDelete(session *discordgo.Session, message *discordgo.MessageDelete) {
	modules.CallExtendedPluginOnMessageDelete(message)
}

// BotOnReactionAdd gets called after a reaction is added
// This will be called after *every* reaction added on *every* server so it
// should die as soon as possible or spawn costly work inside of coroutines.
// This is currently used for the *poll* plugin.
func BotOnReactionAdd(session *discordgo.Session, reaction *discordgo.MessageReactionAdd) {
	modules.CallExtendedPluginOnReactionAdd(reaction)

	if reaction.UserID == session.State.User.ID {
		return
	}

	channel, err := helpers.GetChannel(reaction.ChannelID)
	if err != nil {
		return
	}
	if emojis.ToNumber(reaction.Emoji.Name) == -1 {
		//session.MessageReactionRemove(reaction.ChannelID, reaction.MessageID, reaction.Emoji.Name, reaction.UserID)
		return
	}
	if helpers.VotePollIfItsOne(channel.GuildID, reaction.MessageReaction) {
		helpers.UpdatePollMsg(channel.GuildID, reaction.MessageID)
	}

}

func BotOnReactionRemove(session *discordgo.Session, reaction *discordgo.MessageReactionRemove) {
	modules.CallExtendedPluginOnReactionRemove(reaction)
}

func sendHelp(message *discordgo.MessageCreate) {
	channel, err := helpers.GetChannel(message.ChannelID)
	if err != nil {
		channel.GuildID = ""
	}

	cache.GetSession().ChannelMessageSend(
		message.ChannelID,
		helpers.GetTextF("bot.help", message.Author.ID, channel.GuildID),
	)
}

// Changes the game interval every ten minutes after called
func changeGameInterval(session *discordgo.Session) {
	for {
		users := make(map[string]string)
		guilds := session.State.Guilds

		for _, guild := range guilds {
			for _, u := range guild.Members {
				users[u.User.ID] = u.User.Username
			}
		}

		err := session.UpdateStatus(0, fmt.Sprintf("%d users on %d servers | robyul.chat | _help", len(users), len(guilds)))
		if err != nil {
			raven.CaptureError(err, map[string]string{})
		}

		time.Sleep(1 * time.Hour)
	}
}
<commit_msg>[core] updates game message<commit_after>package main

import (
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/emojis"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/metrics"
	"github.com/Seklfreak/Robyul2/modules"
	"github.com/Seklfreak/Robyul2/ratelimits"
	"github.com/Sirupsen/logrus"
	"github.com/bwmarrin/discordgo"
	"github.com/getsentry/raven-go"
)

// BotOnReady gets called after the gateway connected
func BotOnReady(session *discordgo.Session, event *discordgo.Ready) {
	log := cache.GetLogger()

	log.WithField("module", "bot").Info("Connected to discord!")
	log.WithField("module", "bot").Info("Invite link: " + fmt.Sprintf(
		"https://discordapp.com/oauth2/authorize?client_id=%s&scope=bot&permissions=%s",
		helpers.GetConfig().Path("discord.id").Data().(string),
		helpers.GetConfig().Path("discord.perms").Data().(string),
	))

	// Cache the session
	cache.SetSession(session)

	// Load and init all modules
	modules.Init(session)

	// Run async worker for guild changes
	go helpers.GuildSettingsUpdater()

	// Run async game-changer
	go changeGameInterval(session)

	// request guild members from the gateway
	go func() {
		time.Sleep(30 * time.Second)

		for _, guild := range session.State.Guilds {
			err := session.RequestGuildMembers(guild.ID, "", 0)
			if err != nil {
				raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
			}
		}
	}()

	// Run auto-leaver for non-beta guilds
	//go autoLeaver(session)

	// Run ratelimiter
	ratelimits.Container.Init()

	go func() {
		time.Sleep(3 * time.Second)

		configName := helpers.GetConfig().Path("bot.name").Data().(string)
		configAvatar := helpers.GetConfig().Path("bot.avatar").Data().(string)

		// Change avatar if desired
		if configAvatar != "" && configAvatar != session.State.User.Avatar {
			session.UserUpdate(
				"",
				"",
				session.State.User.Username,
				configAvatar,
				"",
			)
		}

		// Change name if desired
		if configName != "" && configName != session.State.User.Username {
			session.UserUpdate(
				"",
				"",
				configName,
				session.State.User.Avatar,
				"",
			)
		}
	}()

	// Run async game-changer
	//go changeGameInterval(session)

	// Run auto-leaver for non-beta guilds
	//go autoLeaver(session)
}

func BotOnMemberListChunk(session *discordgo.Session, members *discordgo.GuildMembersChunk) {
	cache.GetLogger().WithField("module", "bot").Debug(
		fmt.Sprintf("received guild member chunk for guild: %s (%d members)",
			members.GuildID, len(members.Members)))
	var err error
	for _, member := range members.Members {
		member.GuildID = members.GuildID
		err = session.State.MemberAdd(member)
		if err != nil {
			raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
		}
	}
}

func BotGuildOnPresenceUpdate(session *discordgo.Session, presence *discordgo.PresenceUpdate) {
	if presence.GuildID == "" {
		return
	}

	member, err := cache.GetSession().State.Member(presence.GuildID, presence.User.ID)
	if err != nil {
		if strings.Contains(err.Error(), "state cache not found") {
			return
		}
		raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
		return
	}

	change := false
	if presence.User.Avatar != "" {
		member.User.Avatar = presence.User.Avatar
		change = true
	}
	if presence.User.Discriminator != "" {
		member.User.Discriminator = presence.User.Discriminator
		change = true
	}
	if presence.User.Email != "" {
		member.User.Email = presence.User.Email
		change = true
	}
	if presence.User.Token != "" {
		member.User.Token = presence.User.Token
		change = true
	}
	if presence.User.Username != "" {
		member.User.Username = presence.User.Username
		change = true
	}

	if change == true {
		err = session.State.MemberAdd(member)
		if err != nil {
			raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
		}
	}
}

func BotOnGuildMemberAdd(session *discordgo.Session, member *discordgo.GuildMemberAdd) {
	modules.CallExtendedPluginOnGuildMemberAdd(
		member.Member,
	)
}

func BotOnGuildMemberRemove(session *discordgo.Session, member *discordgo.GuildMemberRemove) {
	modules.CallExtendedPluginOnGuildMemberRemove(
		member.Member,
	)
}

func BotOnGuildBanAdd(session *discordgo.Session, user *discordgo.GuildBanAdd) {
	modules.CallExtendedPluginOnGuildBanAdd(
		user,
	)
}

func BotOnGuildBanRemove(session *discordgo.Session, user *discordgo.GuildBanRemove) {
	modules.CallExtendedPluginOnGuildBanRemove(
		user,
	)
}

// BotOnMessageCreate gets called after a new message was sent
// This will be called after *every* message on *every* server so it should die as soon as possible
// or spawn costly work inside of coroutines.
func BotOnMessageCreate(session *discordgo.Session, message *discordgo.MessageCreate) {
	// Ignore other bots and @everyone/@here
	if message.Author.Bot || message.MentionEveryone {
		return
	}

	if helpers.IsBlacklisted(message.Author.ID) {
		return
	}

	// Get the channel
	// Ignore the event if we cannot resolve the channel
	channel, err := helpers.GetChannel(message.ChannelID)
	if err != nil {
		go raven.CaptureError(err, map[string]string{})
		return
	}

	/*
		if channel.Type == discordgo.ChannelTypeDM {
			// Track usage
			metrics.CleverbotRequests.Add(1)

			// Mark typing
			session.ChannelTyping(message.ChannelID)

			// Prepare content for editing
			msg := message.Content

			/// Remove our @mention
			msg = strings.Replace(msg, "<@"+session.State.User.ID+">", "", -1)

			// Trim message
			msg = strings.TrimSpace(msg)

			// Resolve other @mentions before sending the message
			for _, user := range message.Mentions {
				msg = strings.Replace(msg, "<@"+user.ID+">", user.Username, -1)
			}

			// Remove smileys
			msg = regexp.MustCompile(`:\w+:`).ReplaceAllString(msg, "")

			// Send to cleverbot
			helpers.CleverbotSend(session, channel.ID, msg)
			return
		}*/

	// Check if the message contains @mentions for us
	if strings.HasPrefix(message.Content, "<@") && len(message.Mentions) > 0 && message.Mentions[0].ID == session.State.User.ID {
		// Consume a key for this action
		e := ratelimits.Container.Drain(1, message.Author.ID)
		if e != nil {
			return
		}

		// Prepare content for editing
		msg := message.Content

		/// Remove our @mention
		msg = strings.Replace(msg, "<@"+session.State.User.ID+">", "", -1)

		// Trim message
		msg = strings.TrimSpace(msg)

		// Convert to []byte before matching
		bmsg := []byte(msg)

		// Match against common task patterns
		// Send to cleverbot if nothing matches
		switch {
		case regexp.MustCompile("(?i)^HELP.*").Match(bmsg):
			metrics.CommandsExecuted.Add(1)
			sendHelp(message)
			return

		case regexp.MustCompile("(?i)^PREFIX.*").Match(bmsg):
			metrics.CommandsExecuted.Add(1)
			prefix := helpers.GetPrefixForServer(channel.GuildID)
			if prefix == "" {
				cache.GetSession().ChannelMessageSend(
					channel.ID,
					helpers.GetText("bot.prefix.not-set"),
				)
			}

			cache.GetSession().ChannelMessageSend(
				channel.ID,
				helpers.GetTextF("bot.prefix.is", prefix),
			)
			return

			/*
				case regexp.MustCompile("(?i)^REFRESH CHAT SESSION$").Match(bmsg):
					metrics.CommandsExecuted.Add(1)
					helpers.RequireAdmin(message.Message, func() {
						// Refresh cleverbot session
						helpers.CleverbotRefreshSession(channel.ID)
						cache.GetSession().ChannelMessageSend(channel.ID, helpers.GetText("bot.cleverbot.refreshed"))
					})
					return*/

		case regexp.MustCompile("(?i)^SET PREFIX (.){1,25}$").Match(bmsg):
			metrics.CommandsExecuted.Add(1)
			helpers.RequireAdmin(message.Message, func() {
				// Extract prefix
				prefix := strings.Fields(regexp.MustCompile("(?i)^SET PREFIX\\s").ReplaceAllString(msg, ""))[0]

				// Set new prefix
				err := helpers.SetPrefixForServer(
					channel.GuildID,
					prefix,
				)

				if err != nil {
					helpers.SendError(message.Message, err)
				} else {
					cache.GetSession().ChannelMessageSend(channel.ID, helpers.GetTextF("bot.prefix.saved", prefix))
				}
			})
			return

			/*
				default:
					// Track usage
					metrics.CleverbotRequests.Add(1)

					// Mark typing
					session.ChannelTyping(message.ChannelID)

					// Resolve other @mentions before sending the message
					for _, user := range message.Mentions {
						msg = strings.Replace(msg, "<@"+user.ID+">", user.Username, -1)
					}

					// Remove smileys
					msg = regexp.MustCompile(`:\w+:`).ReplaceAllString(msg, "")

					// Send to cleverbot
					helpers.CleverbotSend(session, channel.ID, msg)
					return*/
		}
	}

	modules.CallExtendedPlugin(
		message.Content,
		message.Message,
	)

	// Only continue if a prefix is set
	prefix := helpers.GetPrefixForServer(channel.GuildID)
	if prefix == "" {
		return
	}

	// Check if the message is prefixed for us
	// If not exit
	if !strings.HasPrefix(message.Content, prefix) {
		return
	}

	// Check if the user is allowed to request commands
	if !ratelimits.Container.HasKeys(message.Author.ID) && !helpers.IsBotAdmin(message.Author.ID) {
		session.ChannelMessageSend(message.ChannelID, helpers.GetTextF("bot.ratelimit.hit", message.Author.ID))

		ratelimits.Container.Set(message.Author.ID, -1)
		return
	}

	// Split the message into parts
	parts := strings.Fields(message.Content)

	// Save a sanitized version of the command (no prefix)
	cmd := strings.Replace(parts[0], prefix, "", 1)

	// Check if the user calls for help
	if cmd == "h" || cmd == "help" {
		metrics.CommandsExecuted.Add(1)
		sendHelp(message)
		return
	}

	// Separate arguments from the command
	content := strings.TrimSpace(strings.Replace(message.Content, prefix+cmd, "", -1))

	// Log commands
	cache.GetLogger().WithFields(logrus.Fields{
		"module":    "bot",
		"channelID": message.ChannelID,
		"userID":    message.Author.ID,
	}).Debug(fmt.Sprintf("%s (#%s): %s",
		message.Author.Username, message.Author.ID, message.Content))

	// Check if a module matches said command
	modules.CallBotPlugin(cmd, content, message.Message)

	// Check if a trigger matches
	modules.CallTriggerPlugin(cmd, content, message.Message)
}

func BotOnMessageDelete(session *discordgo.Session, message *discordgo.MessageDelete) {
	modules.CallExtendedPluginOnMessageDelete(message)
}

// BotOnReactionAdd gets called after a reaction is added
// This will be called after *every* reaction added on *every* server so it
// should die as soon as possible or spawn costly work inside of coroutines.
// This is currently used for the *poll* plugin.
func BotOnReactionAdd(session *discordgo.Session, reaction *discordgo.MessageReactionAdd) {
	modules.CallExtendedPluginOnReactionAdd(reaction)

	if reaction.UserID == session.State.User.ID {
		return
	}

	channel, err := helpers.GetChannel(reaction.ChannelID)
	if err != nil {
		return
	}
	if emojis.ToNumber(reaction.Emoji.Name) == -1 {
		//session.MessageReactionRemove(reaction.ChannelID, reaction.MessageID, reaction.Emoji.Name, reaction.UserID)
		return
	}
	if helpers.VotePollIfItsOne(channel.GuildID, reaction.MessageReaction) {
		helpers.UpdatePollMsg(channel.GuildID, reaction.MessageID)
	}

}

func BotOnReactionRemove(session *discordgo.Session, reaction *discordgo.MessageReactionRemove) {
	modules.CallExtendedPluginOnReactionRemove(reaction)
}

func sendHelp(message *discordgo.MessageCreate) {
	channel, err := helpers.GetChannel(message.ChannelID)
	if err != nil {
		channel.GuildID = ""
	}

	cache.GetSession().ChannelMessageSend(
		message.ChannelID,
		helpers.GetTextF("bot.help", message.Author.ID, channel.GuildID),
	)
}

// Changes the game interval every ten minutes after called
func changeGameInterval(session *discordgo.Session) {
	for {
		guilds := session.State.Guilds

		err := session.UpdateStatus(0, fmt.Sprintf("on %d servers | robyul.chat | _help", len(guilds)))
		if err != nil {
			raven.CaptureError(err, map[string]string{})
		}

		time.Sleep(1 * time.Hour)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/nlopes/slack"
)

func main() {
	slackToken := os.Getenv("ARCHIVEBOT_SLACK_TOKEN")
	onErrorNotify := os.Getenv("ARCHIVEBOT_NOTIFY")
	api := slack.New(slackToken)
	//api.SetDebug(true)

	channels, err := api.GetChannels(true)
	if err != nil {
		log.Printf("Error when loading channels: %s\n", err)
		return
	}

	var wg sync.WaitGroup
	wg.Add(2)

	go func(c []slack.Channel) {
		defer wg.Done()
		archiveEmptyChannels(api, c)
	}(channels)

	go func(c []slack.Channel) {
		defer wg.Done()
		archiveInactiveChannels(api, c)
	}(channels)

	wg.Wait()
}

func archiveEmptyChannels(api *slack.Slack, c []slack.Channel) {
	empty := filterEmptyChannels(api, c)
	archiveChannels(api, empty, "emptiness")
}

func archiveInactiveChannels(api *slack.Slack, c []slack.Channel) {
	inactive := filterInactiveChannels(api, c)
	archiveChannels(api, inactive, "inactivity")
}

func archiveChannels(api *slack.Slack, c []slack.Channel, reason string) {
	var wg sync.WaitGroup

	for _, channel := range c {
		fmt.Printf("Archiving #%s (%s) due to %s\n", channel.Name, channel.Id, reason)
		wg.Add(1)

		go func(c slack.Channel) {
			defer wg.Done()
			if err := api.ArchiveChannel(c.Id); err != nil {
				message := fmt.Sprintf("Error archiving channel #%s (%s): %s\n", c.Name, c.Id, err)
				log.Printf(message)
				// send error message in a DM to onErrorNotify user/channel
				params := slack.PostMessageParameters{}
				api.PostMessage(onErrorNotify, message, params)
			}
		}(channel)
	}

	wg.Wait()
}

func filterEmptyChannels(api *slack.Slack, c []slack.Channel) []slack.Channel {
	empty := []slack.Channel{}
	for _, channel := range c {
		if channel.NumMembers == 0 {
			empty = append(empty, channel)
		}
	}
	return empty
}

type LastChannelMessage struct {
	Channel   slack.Channel
	Timestamp int64
}

func filterInactiveChannels(api *slack.Slack, c []slack.Channel) []slack.Channel {
	inactiveDays, _ := strconv.ParseInt(os.Getenv("ARCHIVEBOT_INACTIVE_DAYS"), 10, 32)
	if inactiveDays == 0 {
		inactiveDays = 30
	}

	timeout := int64(time.Now().Unix()) - (86400 * inactiveDays)
	channels := []slack.Channel{}

	res := make(chan LastChannelMessage)
	for _, channel := range c {
		go func(channel slack.Channel) {
			timestamp, _ := lastMessageTimestamp(api, channel)
			res <- LastChannelMessage{Channel: channel, Timestamp: timestamp}
		}(channel)
	}

	for i := 0; i < len(c); i++ {
		lcm := <-res
		if lcm.Timestamp > 0 && lcm.Timestamp < timeout {
			channels = append(channels, lcm.Channel)
		}
	}

	close(res)
	return channels
}

func lastMessageTimestamp(api *slack.Slack, channel slack.Channel) (int64, error) {
	var latest string

	for {
		historyParams := slack.HistoryParameters{Count: 5}
		if latest != "" {
			historyParams.Latest = latest
		}

		history, err := api.GetChannelHistory(channel.Id, historyParams)

		if err != nil {
			return -1, err
		}

		if len(history.Messages) == 0 {
			return -1, nil
		}

		for _, msg := range history.Messages {
			latest = msg.Msg.Timestamp

			if msg.SubType != "channel_join" && msg.SubType != "channel_leave" {
				msgStamp := strings.Split(msg.Msg.Timestamp, ".")
				if timestamp, err := strconv.ParseInt(msgStamp[0], 10, 32); err == nil {
					return timestamp, nil
				}
			}
		}
	}
}
<commit_msg>Move onErrorNotify definition into function.<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/nlopes/slack"
)

func main() {
	slackToken := os.Getenv("ARCHIVEBOT_SLACK_TOKEN")
	api := slack.New(slackToken)
	//api.SetDebug(true)

	channels, err := api.GetChannels(true)
	if err != nil {
		log.Printf("Error when loading channels: %s\n", err)
		return
	}

	var wg sync.WaitGroup
	wg.Add(2)

	go func(c []slack.Channel) {
		defer wg.Done()
		archiveEmptyChannels(api, c)
	}(channels)

	go func(c []slack.Channel) {
		defer wg.Done()
		archiveInactiveChannels(api, c)
	}(channels)

	wg.Wait()
}

func archiveEmptyChannels(api *slack.Slack, c []slack.Channel) {
	empty := filterEmptyChannels(api, c)
	archiveChannels(api, empty, "emptiness")
}

func archiveInactiveChannels(api *slack.Slack, c []slack.Channel) {
	inactive := filterInactiveChannels(api, c)
	archiveChannels(api, inactive, "inactivity")
}

func archiveChannels(api *slack.Slack, c []slack.Channel, reason string) {
	var wg sync.WaitGroup

	for _, channel := range c {
		fmt.Printf("Archiving #%s (%s) due to %s\n", channel.Name, channel.Id, reason)
		wg.Add(1)

		go func(c slack.Channel) {
			defer wg.Done()
			if err := api.ArchiveChannel(c.Id); err != nil {
				message := fmt.Sprintf("Error archiving channel #%s (%s): %s\n", c.Name, c.Id, err)
				log.Printf(message)
				// send error message in a DM to onErrorNotify user/channel
				onErrorNotify := os.Getenv("ARCHIVEBOT_NOTIFY")
				params := slack.PostMessageParameters{}
				api.PostMessage(onErrorNotify, message, params)
			}
		}(channel)
	}

	wg.Wait()
}

func filterEmptyChannels(api *slack.Slack, c []slack.Channel) []slack.Channel {
	empty := []slack.Channel{}
	for _, channel := range c {
		if channel.NumMembers == 0 {
			empty = append(empty, channel)
		}
	}
	return empty
}

type LastChannelMessage struct {
	Channel   slack.Channel
	Timestamp int64
}

func filterInactiveChannels(api *slack.Slack, c []slack.Channel) []slack.Channel {
	inactiveDays, _ := strconv.ParseInt(os.Getenv("ARCHIVEBOT_INACTIVE_DAYS"), 10, 32)
	if inactiveDays == 0 {
		inactiveDays = 30
	}

	timeout := int64(time.Now().Unix()) - (86400 * inactiveDays)
	channels := []slack.Channel{}

	res := make(chan LastChannelMessage)
	for _, channel := range c {
		go func(channel slack.Channel) {
			timestamp, _ := lastMessageTimestamp(api, channel)
			res <- LastChannelMessage{Channel: channel, Timestamp: timestamp}
		}(channel)
	}

	for i := 0; i < len(c); i++ {
		lcm := <-res
		if lcm.Timestamp > 0 && lcm.Timestamp < timeout {
			channels = append(channels, lcm.Channel)
		}
	}

	close(res)
	return channels
}

func lastMessageTimestamp(api *slack.Slack, channel slack.Channel) (int64, error) {
	var latest string

	for {
		historyParams := slack.HistoryParameters{Count: 5}
		if latest != "" {
			historyParams.Latest = latest
		}

		history, err := api.GetChannelHistory(channel.Id, historyParams)

		if err != nil {
			return -1, err
		}

		if len(history.Messages) == 0 {
			return -1, nil
		}

		for _, msg := range history.Messages {
			latest = msg.Msg.Timestamp

			if msg.SubType != "channel_join" && msg.SubType != "channel_leave" {
				msgStamp := strings.Split(msg.Msg.Timestamp, ".")
				if timestamp, err := strconv.ParseInt(msgStamp[0], 10, 32); err == nil {
					return timestamp, nil
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package slack

import (
	"net/http"
	"net/url"

	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
)

const (
	// Version is the semantic version of this library.
	Version = "0.2.0"
)

// Bot encapsulates all the data needed to interact with Slack.
type Bot struct {
	Token        string
	Name         string
	ID           string
	Handlers     map[string]([]BotAction)
	Subhandlers  map[string](map[string]([]BotAction))
	Users        map[string]string
	Channels     map[string]string
	reconnectURL string
}

// NewBot constructs a new bot with the passed-in Slack API token.
func NewBot(token string) *Bot {
	return &Bot{
		Token:        token,
		Name:         "",
		ID:           "",
		Handlers:     make(map[string]([]BotAction)),
		Subhandlers:  make(map[string](map[string]([]BotAction))),
		Users:        make(map[string]string),
		Channels:     make(map[string]string),
		reconnectURL: "",
	}
}

func StoreReconnectURL(bot *Bot, event map[string]interface{}) (*Message, Status) {
	bot.reconnectURL = event["url"].(string)
	return nil, Continue
}

// Start initiates the bot's interaction with Slack. It obtains a websockect
// URL, connects to it, and then starts the main loop.
func (bot *Bot) Start() error {
	payload, err := bot.Call("rtm.start", url.Values{})
	if err != nil {
		return err
	}
	success, ok := payload["ok"].(bool)
	if !(ok && success) {
		return &Error{"could not connect to RTM API"}
	}
	websocketURL, _ := payload["url"].(string)
	self := payload["self"].(map[string]interface{})
	channels := payload["channels"].([]interface{})
	for _, channelMap := range channels {
		channel := channelMap.(map[string]interface{})
		channelID := channel["id"].(string)
		channelName := channel["name"].(string)
		bot.Channels[channelName] = channelID
		bot.Channels[channelID] = channelName
	}
	users := payload["users"].([]interface{})
	for _, userMap := range users {
		user := userMap.(map[string]interface{})
		userID := user["id"].(string)
		userName := user["name"].(string)
		bot.Users[userName] = userID
		bot.Users[userID] = userName
	}
	bot.Name = self["name"].(string)
	bot.ID = self["id"].(string)
	log.WithFields(log.Fields{
		"id":   bot.ID,
		"name": bot.Name,
	}).Info("bot authenticated")
	bot.OnEvent("reconnect_url", StoreReconnectURL)
	for {
		reconnect, err := bot.connect(websocketURL)
		if reconnect && bot.reconnectURL != "" {
			websocketURL = bot.reconnectURL
		} else {
			return err
		}
	}
	return nil
}

func (bot *Bot) connect(websocketURL string) (bool, error) {
	dialer := websocket.Dialer{}
	conn, _, err := dialer.Dial(websocketURL, http.Header{})
	if err != nil {
		return false, err
	}
	return bot.loop(conn), nil
}

func (bot *Bot) loop(conn *websocket.Conn) bool {
	defer conn.Close()
	for {
		messageType, bytes, err := conn.ReadMessage()
		if err != nil {
			// ReadMessage returns an error if the connection is closed
			return false
		}
		if messageType == websocket.BinaryMessage {
			continue // ignore binary messages
		}
		event, err := unpackJSON(bytes)
		if err != nil {
			log.WithFields(log.Fields{
				"raw bytes": bytes,
				"error":     err,
			}).Warn("message could not be unpacked")
			continue
		}
		log.WithFields(log.Fields{
			"event": event,
		}).Info("received event")
		eventType, ok := event["type"]
		if ok && eventType.(string) == "team_migration_started" {
			return true
		}
		wrappers := bot.handle(event)
		closeConnection := sendResponses(wrappers, conn)
		if closeConnection {
			return false
		}
	}
}

func sendResponses(wrappers []messageWrapper, conn *websocket.Conn) bool {
	abort := false
	for _, wrapper := range wrappers {
		message := wrapper.message
		switch wrapper.status {
		case Continue:
			if message != nil {
				conn.WriteJSON(message.toMap())
			}
		case Shutdown:
			if message != nil {
				conn.WriteJSON(message.toMap())
			}
			abort = true
		case ShutdownNow:
			return true
		}
	}
	return abort
}
<commit_msg>docs for StoreReconnectURL. closes #20<commit_after>package slack

import (
	"net/http"
	"net/url"

	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
)

const (
	// Version is the semantic version of this library.
	Version = "0.2.0"
)

// Bot encapsulates all the data needed to interact with Slack.
type Bot struct {
	Token        string
	Name         string
	ID           string
	Handlers     map[string]([]BotAction)
	Subhandlers  map[string](map[string]([]BotAction))
	Users        map[string]string
	Channels     map[string]string
	reconnectURL string
}

// NewBot constructs a new bot with the passed-in Slack API token.
func NewBot(token string) *Bot {
	return &Bot{
		Token:        token,
		Name:         "",
		ID:           "",
		Handlers:     make(map[string]([]BotAction)),
		Subhandlers:  make(map[string](map[string]([]BotAction))),
		Users:        make(map[string]string),
		Channels:     make(map[string]string),
		reconnectURL: "",
	}
}

// StoreReconnectURL takes a "url" from an event and stores it. This is done so
// that when Slack migrates a team to a new host, the bot can use the reconnect
// URL to reattach to the team.
func StoreReconnectURL(bot *Bot, event map[string]interface{}) (*Message, Status) {
	bot.reconnectURL = event["url"].(string)
	return nil, Continue
}

// Start initiates the bot's interaction with Slack. It obtains a websockect
// URL, connects to it, and then starts the main loop.
func (bot *Bot) Start() error {
	payload, err := bot.Call("rtm.start", url.Values{})
	if err != nil {
		return err
	}
	success, ok := payload["ok"].(bool)
	if !(ok && success) {
		return &Error{"could not connect to RTM API"}
	}
	websocketURL, _ := payload["url"].(string)
	self := payload["self"].(map[string]interface{})
	channels := payload["channels"].([]interface{})
	for _, channelMap := range channels {
		channel := channelMap.(map[string]interface{})
		channelID := channel["id"].(string)
		channelName := channel["name"].(string)
		bot.Channels[channelName] = channelID
		bot.Channels[channelID] = channelName
	}
	users := payload["users"].([]interface{})
	for _, userMap := range users {
		user := userMap.(map[string]interface{})
		userID := user["id"].(string)
		userName := user["name"].(string)
		bot.Users[userName] = userID
		bot.Users[userID] = userName
	}
	bot.Name = self["name"].(string)
	bot.ID = self["id"].(string)
	log.WithFields(log.Fields{
		"id":   bot.ID,
		"name": bot.Name,
	}).Info("bot authenticated")
	bot.OnEvent("reconnect_url", StoreReconnectURL)
	for {
		reconnect, err := bot.connect(websocketURL)
		if reconnect && bot.reconnectURL != "" {
			websocketURL = bot.reconnectURL
		} else {
			return err
		}
	}
	return nil
}

func (bot *Bot) connect(websocketURL string) (bool, error) {
	dialer := websocket.Dialer{}
	conn, _, err := dialer.Dial(websocketURL, http.Header{})
	if err != nil {
		return false, err
	}
	return bot.loop(conn), nil
}

func (bot *Bot) loop(conn *websocket.Conn) bool {
	defer conn.Close()
	for {
		messageType, bytes, err := conn.ReadMessage()
		if err != nil {
			// ReadMessage returns an error if the connection is closed
			return false
		}
		if messageType == websocket.BinaryMessage {
			continue // ignore binary messages
		}
		event, err := unpackJSON(bytes)
		if err != nil {
			log.WithFields(log.Fields{
				"raw bytes": bytes,
				"error":     err,
			}).Warn("message could not be unpacked")
			continue
		}
		log.WithFields(log.Fields{
			"event": event,
		}).Info("received event")
		eventType, ok := event["type"]
		if ok && eventType.(string) == "team_migration_started" {
			return true
		}
		wrappers := bot.handle(event)
		closeConnection := sendResponses(wrappers, conn)
		if closeConnection {
			return false
		}
	}
}

func sendResponses(wrappers []messageWrapper, conn *websocket.Conn) bool {
	abort := false
	for _, wrapper := range wrappers {
		message := wrapper.message
		switch wrapper.status {
		case Continue:
			if message != nil {
				conn.WriteJSON(message.toMap())
			}
		case Shutdown:
			if message != nil {
				conn.WriteJSON(message.toMap())
			}
			abort = true
		case ShutdownNow:
			return true
		}
	}
	return abort
}
<|endoftext|>


<commit_before>package slack

import (
	"net/http"
	"net/url"

	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
)

const (
	// Version is the semantic version of this library.
	Version = "0.1.0"
)

// Bot encapsulates all the data needed to interact with Slack.
type Bot struct {
	Token       string
	Name        string
	ID          string
	Handlers    map[string]([]BotAction)
	Subhandlers map[string](map[string]([]BotAction))
	Users       map[string]string
	Channels    map[string]string
}

// NewBot constructs a new bot with the passed-in Slack API token.
func NewBot(token string) *Bot {
	return &Bot{
		Token:       token,
		Name:        "",
		ID:          "",
		Handlers:    make(map[string]([]BotAction)),
		Subhandlers: make(map[string](map[string]([]BotAction))),
		Users:       make(map[string]string),
		Channels:    make(map[string]string),
	}
}

// Start initiates the bot's interaction with Slack. It obtains a websockect
// URL, connects to it, and then starts the main loop.
func (bot *Bot) Start() error {
	payload, err := bot.Call("rtm.start", url.Values{})
	if err != nil {
		return err
	}
	success, ok := payload["ok"].(bool)
	if !(ok && success) {
		return &Error{"could not connect to RTM API"}
	}
	websocketURL, _ := payload["url"].(string)
	self := payload["self"].(map[string]interface{})
	channels := payload["channels"].([]interface{})
	for _, channelMap := range channels {
		channel := channelMap.(map[string]interface{})
		channelID := channel["id"].(string)
		channelName := channel["name"].(string)
		bot.Channels[channelName] = channelID
		bot.Channels[channelID] = channelName
	}
	users := payload["users"].([]interface{})
	for _, userMap := range users {
		user := userMap.(map[string]interface{})
		userID := user["id"].(string)
		userName := user["name"].(string)
		bot.Users[userName] = userID
		bot.Users[userID] = userName
	}
	bot.Name = self["name"].(string)
	bot.ID = self["id"].(string)
	return bot.connect(websocketURL)
}

func (bot *Bot) connect(websocketURL string) error {
	dialer := websocket.Dialer{}
	conn, _, err := dialer.Dial(websocketURL, http.Header{})
	if err != nil {
		return err
	}
	bot.loop(conn)
	return nil
}

func (bot *Bot) loop(conn *websocket.Conn) {
	for {
		messageType, bytes, err := conn.ReadMessage()
		if err != nil {
			// ReadMessage returns an error if the connection is closed
			conn.Close()
			return
		}
		if messageType == websocket.BinaryMessage {
			continue // ignore binary messages
		}
		event, err := unpackEvent(bytes)
		if err != nil {
			log.WithFields(log.Fields{
				"raw bytes": bytes,
				"error":     err,
			}).Warn("message could not be unpacked")
		}
		wrappers := bot.handle(event)
		closeConnection := sendResponses(wrappers, conn)
		if closeConnection {
			conn.Close()
			return
		}
	}
}

func sendResponses(wrappers []messageWrapper, conn *websocket.Conn) bool {
	abort := false
	for _, wrapper := range wrappers {
		message := wrapper.message
		switch wrapper.status {
		case Continue:
			if message != nil {
				conn.WriteJSON(message.toMap())
			}
		case Shutdown:
			if message != nil {
				conn.WriteJSON(message.toMap())
			}
			abort = true
		case ShutdownNow:
			return true
		}
	}
	return abort
}
<commit_msg>bump version, since Status names changed<commit_after>package slack

import (
	"net/http"
	"net/url"

	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
)

const (
	// Version is the semantic version of this library.
	Version = "0.1.1"
)

// Bot encapsulates all the data needed to interact with Slack.
type Bot struct {
	Token       string
	Name        string
	ID          string
	Handlers    map[string]([]BotAction)
	Subhandlers map[string](map[string]([]BotAction))
	Users       map[string]string
	Channels    map[string]string
}

// NewBot constructs a new bot with the passed-in Slack API token.
func NewBot(token string) *Bot {
	return &Bot{
		Token:       token,
		Name:        "",
		ID:          "",
		Handlers:    make(map[string]([]BotAction)),
		Subhandlers: make(map[string](map[string]([]BotAction))),
		Users:       make(map[string]string),
		Channels:    make(map[string]string),
	}
}

// Start initiates the bot's interaction with Slack. It obtains a websockect
// URL, connects to it, and then starts the main loop.
func (bot *Bot) Start() error {
	payload, err := bot.Call("rtm.start", url.Values{})
	if err != nil {
		return err
	}
	success, ok := payload["ok"].(bool)
	if !(ok && success) {
		return &Error{"could not connect to RTM API"}
	}
	websocketURL, _ := payload["url"].(string)
	self := payload["self"].(map[string]interface{})
	channels := payload["channels"].([]interface{})
	for _, channelMap := range channels {
		channel := channelMap.(map[string]interface{})
		channelID := channel["id"].(string)
		channelName := channel["name"].(string)
		bot.Channels[channelName] = channelID
		bot.Channels[channelID] = channelName
	}
	users := payload["users"].([]interface{})
	for _, userMap := range users {
		user := userMap.(map[string]interface{})
		userID := user["id"].(string)
		userName := user["name"].(string)
		bot.Users[userName] = userID
		bot.Users[userID] = userName
	}
	bot.Name = self["name"].(string)
	bot.ID = self["id"].(string)
	return bot.connect(websocketURL)
}

func (bot *Bot) connect(websocketURL string) error {
	dialer := websocket.Dialer{}
	conn, _, err := dialer.Dial(websocketURL, http.Header{})
	if err != nil {
		return err
	}
	bot.loop(conn)
	return nil
}

func (bot *Bot) loop(conn *websocket.Conn) {
	for {
		messageType, bytes, err := conn.ReadMessage()
		if err != nil {
			// ReadMessage returns an error if the connection is closed
			conn.Close()
			return
		}
		if messageType == websocket.BinaryMessage {
			continue // ignore binary messages
		}
		event, err := unpackEvent(bytes)
		if err != nil {
			log.WithFields(log.Fields{
				"raw bytes": bytes,
				"error":     err,
			}).Warn("message could not be unpacked")
		}
		wrappers := bot.handle(event)
		closeConnection := sendResponses(wrappers, conn)
		if closeConnection {
			conn.Close()
			return
		}
	}
}

func sendResponses(wrappers []messageWrapper, conn *websocket.Conn) bool {
	abort := false
	for _, wrapper := range wrappers {
		message := wrapper.message
		switch wrapper.status {
		case Continue:
			if message != nil {
				conn.WriteJSON(message.toMap())
			}
		case Shutdown:
			if message != nil {
				conn.WriteJSON(message.toMap())
			}
			abort = true
		case ShutdownNow:
			return true
		}
	}
	return abort
}
<|endoftext|>


<commit_before>package ircbot

import (
	"bufio"
	"crypto/rand"
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/textproto"
	"strings"
	"time"
)

type IrcBot struct {
	// identity
	User string
	Nick string

	// server info
	Server  string
	Port    string
	Channel []string

	// tcp communication
	conn   net.Conn
	reader *textproto.Reader
	writer *textproto.Writer

	// crypto
	Encrypted bool
	config    tls.Config

	// data flow
	In    chan *IrcMsg
	Out   chan *IrcMsg
	Error chan error

	// exit flag
	Exit chan bool

	//action handlers
	Handlers map[string][]ActionFunc

	//are we joined in channel?
	joined bool
}

func NewIrcBot() *IrcBot {
	bot := IrcBot{
		Handlers: make(map[string][]ActionFunc),
		In:       make(chan *IrcMsg),
		Out:      make(chan *IrcMsg),
		Error:    make(chan error),
		Exit:     make(chan bool),
		joined:   false,
	}
}

func (b *IrcBot) url() string {
	return fmt.Sprintf("%s:%s", b.Server, b.Port)
}

func (b *IrcBot) loadCert() {

}

func (b *IrcBot) Connect() {
	//launch a go routine that handle errors
	// b.handleError()

	log.Println("Info> connection to", b.url())

	var tcpCon net.Conn
	var err error
	if b.Encrypted {
		cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
		b.errChk(err)

		config := tls.Config{Certificates: []tls.Certificate{cert}}
		config.Rand = rand.Reader
		tcpCon, err = tls.Dial("tcp", b.url(), &config)
		b.errChk(err)

	} else {
		tcpCon, err = net.Dial("tcp", b.url())
		b.errChk(err)
	}

	b.conn = tcpCon
	r := bufio.NewReader(b.conn)
	w := bufio.NewWriter(b.conn)
	b.reader = textproto.NewReader(r)
	b.writer = textproto.NewWriter(w)

	b.writer.PrintfLine("USER %s 8 * :%s", b.Nick, b.Nick)
	b.writer.PrintfLine("NICK %s", b.Nick)
}

func (b *IrcBot) Join() {

	//prevent to send JOIN command before we are conected
	for {
		if !b.joined {
			time.Sleep(1 * time.Second)
			continue
		}
		break
	}

	for _, v := range b.Channel {
		s := fmt.Sprintf("JOIN %s", v)
		fmt.Println("irc >> ", s)
		b.writer.PrintfLine(s)
	}
	b.joined = true
}

func (b *IrcBot) Listen() {

	go func() {

		for {
			//block read line from socket
			line, err := b.reader.ReadLine()
			if err != nil {
				b.Error <- err
			}
			//convert line into IrcMsg
			msg := Parseline(line)
			b.In <- msg
		}

	}()
}

func (b *IrcBot) Say(s string) {
	msg := NewIrcMsg()
	msg.command = "PRIVMSG"
	msg.args = append(msg.args, s)

	b.Out <- msg
}

func (b *IrcBot) AddAction(command string, action ActionFunc) {
	b.Handlers[command] = append(b.Handlers[command], action)
}

func (b *IrcBot) handleActionIn() {
	go func() {
		for {
			//receive new message
			msg := <-b.In
			fmt.Println("irc << ", msg.raw)
			//handle action
			actions := b.Handlers[msg.Command]
			if len(actions) > 0 {
				for _, action := range actions {
					action(b, msg)
				}
			}
		}
	}()
}

func (b *IrcBot) HandleActionOut() {
	go func() {
		for {
			msg := <-b.Out

			//we send nothing before we sure we join channel
			if b.joined == false {
				continue
			}

			s := fmt.Sprintf("%s %s", msg.command, strings.Join(msg.args, " "))
			fmt.Println("irc >> ", s)
			b.writer.PrintfLine(s)
		}
	}()
}

func (b *IrcBot) HandleError() {
	go func() {
		for {
			err := <-b.Error
			fmt.Printf("error > %s", err)
			if err != nil {
				b.Disconnect()
				log.Fatalln("Error ocurs :", err)
			}
		}
	}()
}

func (b *IrcBot) errChk(err error) {
	if err != nil {
		log.Println("Error> ", err)
		b.Error <- err
	}
}

func (b *IrcBot) Disconnect() {
	b.writer.PrintfLine("QUIT")
	b.conn.Close()
}

func (b *IrcBot) String() string {
	s := fmt.Sprintf("server: %s\n", b.Server)
	s += fmt.Sprintf("port: %s\n", b.Port)
	s += fmt.Sprintf("ssl: %t\n", b.Encrypted)

	if len(b.Channel) > 0 {
		s += "channels: "
		for _, v := range b.Channel {
			s += fmt.Sprintf("%s ", v)
		}
	}

	return s
}
<commit_msg>export some attributes<commit_after>package ircbot

import (
	"bufio"
	"crypto/rand"
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/textproto"
	"strings"
	"time"
)

type IrcBot struct {
	// identity
	User string
	Nick string

	// server info
	Server  string
	Port    string
	Channel []string

	// tcp communication
	conn   net.Conn
	reader *textproto.Reader
	writer *textproto.Writer

	// crypto
	Encrypted bool
	config    tls.Config

	// data flow
	In    chan *IrcMsg
	Out   chan *IrcMsg
	Error chan error

	// exit flag
	Exit chan bool

	//action handlers
	Handlers map[string][]ActionFunc

	//are we Joined in channel?
	Joined bool
}

func NewIrcBot() *IrcBot {
	bot := IrcBot{
		Handlers: make(map[string][]ActionFunc),
		In:       make(chan *IrcMsg),
		Out:      make(chan *IrcMsg),
		Error:    make(chan error),
		Exit:     make(chan bool),
		Joined:   false,
	}
}

func (b *IrcBot) url() string {
	return fmt.Sprintf("%s:%s", b.Server, b.Port)
}

func (b *IrcBot) loadCert() {

}

func (b *IrcBot) Connect() {
	//launch a go routine that handle errors
	// b.handleError()

	log.Println("Info> connection to", b.url())

	var tcpCon net.Conn
	var err error
	if b.Encrypted {
		cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
		b.errChk(err)

		config := tls.Config{Certificates: []tls.Certificate{cert}}
		config.Rand = rand.Reader
		tcpCon, err = tls.Dial("tcp", b.url(), &config)
		b.errChk(err)

	} else {
		tcpCon, err = net.Dial("tcp", b.url())
		b.errChk(err)
	}

	b.conn = tcpCon
	r := bufio.NewReader(b.conn)
	w := bufio.NewWriter(b.conn)
	b.reader = textproto.NewReader(r)
	b.writer = textproto.NewWriter(w)

	//connect to server
	b.writer.PrintfLine("USER %s 8 * :%s", b.Nick, b.Nick)
	b.writer.PrintfLine("NICK %s", b.Nick)

	//launch go routines that handle requests
	b.listen()
	b.handleActionIn()
	b.handleActionOut()
	b.HandleError()

	//join all channels
	b.join()
}

func (b *IrcBot) join() {

	//prevent to send JOIN command before we are conected
	for {
		if !b.Joined {
			time.Sleep(1 * time.Second)
			continue
		}
		break
	}

	for _, v := range b.Channel {
		s := fmt.Sprintf("JOIN %s", v)
		fmt.Println("irc >> ", s)
		b.writer.PrintfLine(s)
	}
	b.Joined = true
}

func (b *IrcBot) listen() {

	go func() {

		for {
			//block read line from socket
			line, err := b.reader.ReadLine()
			if err != nil {
				b.Error <- err
			}
			//convert line into IrcMsg
			msg := Parseline(line)
			b.In <- msg
		}

	}()
}

func (b *IrcBot) Say(s string) {
	msg := NewIrcMsg()
	msg.command = "PRIVMSG"
	msg.args = append(msg.args, s)

	b.Out <- msg
}

func (b *IrcBot) AddAction(command string, action ActionFunc) {
	b.Handlers[command] = append(b.Handlers[command], action)
}

func (b *IrcBot) handleActionIn() {
	go func() {
		for {
			//receive new message
			msg := <-b.In
			fmt.Println("irc << ", msg.raw)
			//handle action
			actions := b.Handlers[msg.Command]
			if len(actions) > 0 {
				for _, action := range actions {
					action(b, msg)
				}
			}
		}
	}()
}

func (b *IrcBot) handleActionOut() {
	go func() {
		for {
			msg := <-b.Out

			//we send nothing before we sure we join channel
			if b.Joined == false {
				continue
			}

			s := fmt.Sprintf("%s %s %s", msg.Command, msg.Channel, strings.Join(msg.Args, " "))
			fmt.Println("irc >> ", s)
			b.writer.PrintfLine(s)
		}
	}()
}

func (b *IrcBot) HandleError() {
	go func() {
		for {
			err := <-b.Error
			fmt.Printf("error > %s", err)
			if err != nil {
				b.Disconnect()
				log.Fatalln("Error ocurs :", err)
			}
		}
	}()
}

func (b *IrcBot) errChk(err error) {
	if err != nil {
		log.Println("Error> ", err)
		b.Error <- err
	}
}

func (b *IrcBot) Disconnect() {
	b.writer.PrintfLine("QUIT")
	b.conn.Close()
}

func (b *IrcBot) String() string {
	s := fmt.Sprintf("server: %s\n", b.Server)
	s += fmt.Sprintf("port: %s\n", b.Port)
	s += fmt.Sprintf("ssl: %t\n", b.Encrypted)

	if len(b.Channel) > 0 {
		s += "channels: "
		for _, v := range b.Channel {
			s += fmt.Sprintf("%s ", v)
		}
	}

	return s
}
<|endoftext|>


<commit_before>package bagman

import (
	"crypto/md5"
	"fmt"
	"github.com/crowdmob/goamz/aws"
	"github.com/crowdmob/goamz/s3"
	"io"
	"os"
	"strings"
)

// Constants
const GIGABYTE int64 = int64(1024 * 1024 * 1024)

// Files over 5GB in size must be uploaded via multi-part put.
const S3_LARGE_FILE int64 = int64(5 * GIGABYTE)

// Chunk size for multipart puts to S3: 100 MB
const S3_CHUNK_SIZE = int64(100000000)

type S3Client struct {
	S3 *s3.S3
}

// Returns an S3Client for the specified region, using
// AWS credentials from the environment. Please keep your AWS
// keys out of the source code repos! Store them somewhere
// else and load them into environment variables AWS_ACCESS_KEY_ID
// and AWS_SECRET_ACCESS_KEY.
func NewS3Client(region aws.Region) (*S3Client, error) {
	auth, err := aws.EnvAuth()
	if err != nil {
		return nil, err
	}
	s3Client := s3.New(auth, region)
	return &S3Client{S3: s3Client}, nil
}

// Returns a list of keys in the specified bucket.
// If limit is zero, this will return all the keys in the bucket;
// otherwise, it will return only the number of keys specifed.
// Note that listing all keys may result in the underlying client
// issuing multiple requests.
func (client *S3Client) ListBucket(bucketName string, limit int) (keys []s3.Key, err error) {
	bucket := client.S3.Bucket(bucketName)
	if bucket == nil {
		err = fmt.Errorf("Cannot retrieve bucket: %s", bucketName)
		return nil, err
	}
	actualLimit := limit
	if limit == 0 {
		actualLimit = 1000
	}
	bucketList, err := bucket.List("", "/", "", actualLimit)
	if err != nil {
		return nil, err
	}
	contents := bucketList.Contents
	if len(contents) == 0 {
		return contents, nil
	}
	for limit == 0 {
		lastKey := contents[len(contents)-1].Key
		bucketList, err := bucket.List("", "/", lastKey, actualLimit)
		if err != nil {
			return nil, err
		}
		contents = append(contents, bucketList.Contents...)
		if !bucketList.IsTruncated {
			break
		}
	}
	return contents, nil
}

// Fetches key from bucket and saves it to path.
// This validates the md5 sum of the byte stream before
// saving to disk. If the md5 sum of the downloaded bytes
// does not match the md5 sum in the key, this will not
// save the file. It will just return an error.
func (client *S3Client) FetchToFile(bucketName string, key s3.Key, path string) (fetchResult *FetchResult) {
	bucket := client.S3.Bucket(bucketName)
	result := new(FetchResult)
	result.BucketName = bucketName
	result.Key = key.Key
	result.LocalTarFile = path

	// In general, we want to retry if the fetch operation
	// fails. We will override this in certain cases below.
	result.Retry = true

	// S3 etag is md5 hex string enclosed in quotes,
	// unless file was a multipart upload. See below for that.
	result.RemoteMd5 = strings.Replace(key.ETag, "\"", "", -1)

	// Fetch the file into a reader instead of using the usual bucket.Get().
	// Files may be up to 250GB, so we want to process them as streams.
	// If we get an error here, it's typically a network error, and we
	// will want to retry later. Try up to 5 times to download the file.
	var readCloser io.ReadCloser = nil
	var err error = nil
	for attemptNumber := 0; attemptNumber < 5; attemptNumber++ {
		readCloser, err = bucket.GetReader(key.Key)
		if err == nil {
			break
		}
	}
	if readCloser != nil {
		defer readCloser.Close()
	}
	if err != nil {
		result.ErrorMessage = fmt.Sprintf("Error retrieving file from receiving bucket: %v", err)
		if strings.Contains(err.Error(), "key does not exist") {
			result.Retry = false
		}
		return result
	}

	// Write the contents of the stream into both our md5 hasher
	// and the file.
	md5Hash := md5.New()
	outputFile, err := os.Create(path)
	if outputFile != nil {
		defer outputFile.Close()
	}
	if err != nil {
		result.ErrorMessage = fmt.Sprintf("Could not create local file %s: %v", path, err)
		return result
	}

	multiWriter := io.MultiWriter(outputFile, md5Hash)
	bytesWritten, err := io.Copy(multiWriter, readCloser)
	if err != nil {
		result.ErrorMessage = fmt.Sprintf("Error copying file from receiving bucket: %v", err)
		return result
	}
	if bytesWritten != key.Size {
		result.ErrorMessage = fmt.Sprintf("While downloading from receiving bucket, "+
			"copied only %d of %d bytes for %s", bytesWritten, key.Size, key.Key)
		return result
	}

	result.LocalMd5 = fmt.Sprintf("%x", md5Hash.Sum(nil))

	// ETag for S3 multi-part upload is not an accurate md5 sum.
	// If the ETag ends with a dash and some number, it's a
	// multi-part upload.
	if strings.Contains(result.RemoteMd5, "-") {
		result.Warning = fmt.Sprintf("Skipping md5 check on %s: this was a multi-part upload", key.Key)
		result.Md5Verified = false
		result.Md5Verifiable = false
	} else {
		result.Md5Verifiable = true
		result.Md5Verified = true
		if result.LocalMd5 != result.RemoteMd5 {
			os.Remove(path)
			result.ErrorMessage = fmt.Sprintf("Our md5 sum '%x' does not match the S3 md5 sum '%s'",
				result.LocalMd5, result.RemoteMd5)
			result.Md5Verified = false
			// Don't bother reprocessing this item.
			result.Retry = false
		}
	}
	return result
}

// Collects info about all of the buckets listed in buckets.
// TODO: Write unit test
func (client *S3Client) CheckAllBuckets(buckets []string) (bucketSummaries []*BucketSummary, err error) {
	bucketSummaries = make([]*BucketSummary, 0)
	for _, bucketName := range buckets {
		bucketSummary, err := client.CheckBucket(bucketName)
		if err != nil {
			return bucketSummaries, err
		}
		bucketSummaries = append(bucketSummaries, bucketSummary)
	}
	return bucketSummaries, nil
}

// Returns info about the contents of the bucket named bucketName.
// BucketSummary contains the bucket name, a list of keys, and the
// size of the largest file in the bucket.
// TODO: Write unit test
func (client *S3Client) CheckBucket(bucketName string) (bucketSummary *BucketSummary, err error) {
	bucket := client.S3.Bucket(bucketName)
	if bucket == nil {
		err = fmt.Errorf("Cannot retrieve bucket: %s", bucketName)
		return nil, err
	}
	bucketSummary = new(BucketSummary)
	bucketSummary.BucketName = bucketName
	bucketSummary.Keys, err = client.ListBucket(bucketName, 0)
	if err != nil {
		return nil, err
	}
	return bucketSummary, nil
}

// Creates an options struct that adds metadata headers to the S3 put.
func (client *S3Client) MakeOptions(md5sum string, metadata map[string][]string) s3.Options {
	if md5sum != "" {
		return s3.Options{
			ContentMD5: md5sum,
			Meta:       metadata,
		}
	} else {
		return s3.Options{
			Meta: metadata,
		}
	}
}

// Saves a file to S3 with default access of Private.
// The underlying S3 client does not return the md5 checksum
// from s3, but we already have this info elsewhere. If the
// PUT produces no error, we assume the copy worked and the
// files md5 sum is the same on S3 as here.
func (client *S3Client) SaveToS3(bucketName, fileName, contentType string, reader io.Reader, byteCount int64, options s3.Options) (url string, err error) {
	bucket := client.S3.Bucket(bucketName)
	putErr := bucket.PutReader(fileName, reader, byteCount,
		contentType, s3.Private, options)
	if putErr != nil {
		err = fmt.Errorf("Error saving file '%s' to bucket '%s': %v",
			fileName, bucketName, putErr)
		return "", err
	}
	url = fmt.Sprintf("https://s3.amazonaws.com/%s/%s", bucketName, fileName)
	return url, nil
}

// Returns an S3 key object for the specified file in the
// specified bucket. The key object has the ETag, last mod
// date, size and other useful info.
func (client *S3Client) GetKey(bucketName, fileName string) (*s3.Key, error) {
	bucket := client.S3.Bucket(bucketName)
	listResp, err := bucket.List(fileName, "", "", 1)
	if err != nil {
		err = fmt.Errorf("Error checking key '%s' in bucket '%s': '%v'",
			fileName, bucketName, err)
		return nil, err
	}
	if listResp == nil || len(listResp.Contents) < 1 {
		err = fmt.Errorf("Key '%s' not found in bucket '%s'",
			fileName, bucketName)
		return nil, err
	}
	return &listResp.Contents[0], nil
}

// Deletes an item from S3
func (client *S3Client) Delete(bucketName, fileName string) error {
	bucket := client.S3.Bucket(bucketName)
	return bucket.Del(fileName)
}

// Sends a large file (>= 5GB) to S3 in 200MB chunks. This operation
// may take several minutes to complete. Note that os.File satisfies
// the s3.ReaderAtSeeker interface.
func (client *S3Client) SaveLargeFileToS3(bucketName, fileName, contentType string,
	reader s3.ReaderAtSeeker, byteCount int64, options s3.Options, chunkSize int64) (url string, err error) {

	bucket := client.S3.Bucket(bucketName)
	multipartPut, err := bucket.InitMulti(fileName, contentType, s3.Private, options)
	if err != nil {
		return "", err
	}

	// Send all of the individual parts to S3 in chunks
	parts, err := multipartPut.PutAll(reader, chunkSize)
	if err != nil {
		abortErr := multipartPut.Abort()
		if abortErr != nil {
			return "", fmt.Errorf("Multipart put failed with error %v "+
				"while uploading a part and abort failed with error %v. "+
				"YOU WILL BE CHARGED FOR THESE FILE PARTS UNTIL YOU DELETE THEM! "+
				"Use multi.ListMulti in the S3 package to list orphaned parts.",
				err, abortErr)
		}
		return "", err
	}

	// This command tells S3 to stitch all the parts into a single file.
	err = multipartPut.Complete(parts)
	if err != nil {
		abortErr := multipartPut.Abort()
		if abortErr != nil {
			return "", fmt.Errorf("Multipart put failed in 'complete' stage "+
				"with error %v and abort failed with error %v",
				err, abortErr)
		}
		return "", err
	}

	resp, err := bucket.Head(fileName, nil)
	if err != nil {
		return "", fmt.Errorf("Files were uploaded to S3, but attempt to "+
			"confirm metadata returned this error: %v", err)
	}

	// Make sure all the meta data made it there.
	// Var metadata is the metadata we sent to S3.
	metadata := options.Meta
	notVerified := ""

	if !metadataMatches(metadata, "institution", resp.Header, "X-Amz-Meta-Institution") {
		notVerified += "institution, "
	}
	if !metadataMatches(metadata, "bag", resp.Header, "X-Amz-Meta-Bag") {
		notVerified += "bag, "
	}
	if !metadataMatches(metadata, "bagpath", resp.Header, "X-Amz-Meta-Bagpath") {
		notVerified += "bagpath, "
	}
	if !metadataMatches(metadata, "md5", resp.Header, "X-Amz-Meta-Md5") {
		notVerified += "md5"
	}
	if len(notVerified) > 0 {
		return "", fmt.Errorf("Multi-part upload succeeded, but S3 does not return "+
			"the following metadata: %s", notVerified)
	}

	url = fmt.Sprintf("https://s3.amazonaws.com/%s/%s", bucketName, fileName)
	return url, nil
}

func metadataMatches(metadata map[string][]string, key string, s3headers map[string][]string, headerName string) bool {
	metaValue, keyExists := metadata[key]
	headerValue, headerExists := s3headers[headerName]

	// If we didn't send this metadata in the first place, we
	// don't care if S3 has it.
	if !keyExists {
		return true
	}

	// If we sent the metadata, test whether S3 returned
	// what we sent.
	if keyExists && len(metaValue) > 0 && headerExists && len(headerValue) > 0 {
		return metaValue[0] == headerValue[0]
	}

	// If we get here, the key exists in the metadata we
	// sent, but not in the S3 headers.
	return false
}
<commit_msg>Skip md5 sum on large tar file download<commit_after>package bagman

import (
	"crypto/md5"
	"hash"
	"fmt"
	"github.com/crowdmob/goamz/aws"
	"github.com/crowdmob/goamz/s3"
	"io"
	"os"
	"strings"
)

// Constants
const GIGABYTE int64 = int64(1024 * 1024 * 1024)

// Files over 5GB in size must be uploaded via multi-part put.
const S3_LARGE_FILE int64 = int64(5 * GIGABYTE)

// Chunk size for multipart puts to S3: 100 MB
const S3_CHUNK_SIZE = int64(100000000)

type S3Client struct {
	S3 *s3.S3
}

// Returns an S3Client for the specified region, using
// AWS credentials from the environment. Please keep your AWS
// keys out of the source code repos! Store them somewhere
// else and load them into environment variables AWS_ACCESS_KEY_ID
// and AWS_SECRET_ACCESS_KEY.
func NewS3Client(region aws.Region) (*S3Client, error) {
	auth, err := aws.EnvAuth()
	if err != nil {
		return nil, err
	}
	s3Client := s3.New(auth, region)
	return &S3Client{S3: s3Client}, nil
}

// Returns a list of keys in the specified bucket.
// If limit is zero, this will return all the keys in the bucket;
// otherwise, it will return only the number of keys specifed.
// Note that listing all keys may result in the underlying client
// issuing multiple requests.
func (client *S3Client) ListBucket(bucketName string, limit int) (keys []s3.Key, err error) {
	bucket := client.S3.Bucket(bucketName)
	if bucket == nil {
		err = fmt.Errorf("Cannot retrieve bucket: %s", bucketName)
		return nil, err
	}
	actualLimit := limit
	if limit == 0 {
		actualLimit = 1000
	}
	bucketList, err := bucket.List("", "/", "", actualLimit)
	if err != nil {
		return nil, err
	}
	contents := bucketList.Contents
	if len(contents) == 0 {
		return contents, nil
	}
	for limit == 0 {
		lastKey := contents[len(contents)-1].Key
		bucketList, err := bucket.List("", "/", lastKey, actualLimit)
		if err != nil {
			return nil, err
		}
		contents = append(contents, bucketList.Contents...)
		if !bucketList.IsTruncated {
			break
		}
	}
	return contents, nil
}

// Fetches key from bucket and saves it to path.
// This validates the md5 sum of the byte stream before
// saving to disk. If the md5 sum of the downloaded bytes
// does not match the md5 sum in the key, this will not
// save the file. It will just return an error.
func (client *S3Client) FetchToFile(bucketName string, key s3.Key, path string) (fetchResult *FetchResult) {
	bucket := client.S3.Bucket(bucketName)
	result := new(FetchResult)
	result.BucketName = bucketName
	result.Key = key.Key
	result.LocalTarFile = path

	// In general, we want to retry if the fetch operation
	// fails. We will override this in certain cases below.
	result.Retry = true

	// S3 etag is md5 hex string enclosed in quotes,
	// unless file was a multipart upload. See below for that.
	result.RemoteMd5 = strings.Replace(key.ETag, "\"", "", -1)

	// Fetch the file into a reader instead of using the usual bucket.Get().
	// Files may be up to 250GB, so we want to process them as streams.
	// If we get an error here, it's typically a network error, and we
	// will want to retry later. Try up to 5 times to download the file.
	var readCloser io.ReadCloser = nil
	var err error = nil
	for attemptNumber := 0; attemptNumber < 5; attemptNumber++ {
		readCloser, err = bucket.GetReader(key.Key)
		if err == nil {
			break
		}
	}
	if readCloser != nil {
		defer readCloser.Close()
	}
	if err != nil {
		result.ErrorMessage = fmt.Sprintf("Error retrieving file from receiving bucket: %v", err)
		if strings.Contains(err.Error(), "key does not exist") {
			result.Retry = false
		}
		return result
	}

	outputFile, err := os.Create(path)
	if outputFile != nil {
		defer outputFile.Close()
	}
	if err != nil {
		result.ErrorMessage = fmt.Sprintf("Could not create local file %s: %v", path, err)
		return result
	}

	// If this is a huge file, the e-tag will include a dash,
	// indicating it was a multi-part upload, and we can't do
	// our standard md5 check on it. We don't want to anyway
	// for files >5GB, since it eats up too much CPU and we're
	// going to validate the md5 checksums of its individual
	// generic files later.
	var md5Hash hash.Hash = nil
	var multiWriter io.Writer = nil
	if strings.Contains(result.RemoteMd5, "-") {
		multiWriter = io.MultiWriter(outputFile)
	} else {
		md5Hash = md5.New()
		multiWriter = io.MultiWriter(outputFile, md5Hash)
	}

	bytesWritten := int64(0)
	for attemptNumber := 0; attemptNumber < 5; attemptNumber++ {
		bytesWritten, err = io.Copy(multiWriter, readCloser)
		if err == nil {
			break
		}
	}

	if err != nil {
		result.ErrorMessage = fmt.Sprintf("Error copying file from receiving bucket: %v", err)
		return result
	}
	if bytesWritten != key.Size {
		result.ErrorMessage = fmt.Sprintf("While downloading from receiving bucket, "+
			"copied only %d of %d bytes for %s", bytesWritten, key.Size, key.Key)
		return result
	}

	// ETag for S3 multi-part upload is not an accurate md5 sum.
	// If the ETag ends with a dash and some number, it's a
	// multi-part upload.
	if md5Hash == nil {
		result.Warning = fmt.Sprintf("Skipping md5 check on %s: this was a multi-part upload", key.Key)
		result.Md5Verified = false
		result.Md5Verifiable = false
	} else {
		result.LocalMd5 = fmt.Sprintf("%x", md5Hash.Sum(nil))
		result.Md5Verifiable = true
		result.Md5Verified = true
		if result.LocalMd5 != result.RemoteMd5 {
			os.Remove(path)
			result.ErrorMessage = fmt.Sprintf("Our md5 sum '%x' does not match the S3 md5 sum '%s'",
				result.LocalMd5, result.RemoteMd5)
			result.Md5Verified = false
			// Don't bother reprocessing this item.
			result.Retry = false
		}
	}
	return result
}

// Collects info about all of the buckets listed in buckets.
// TODO: Write unit test
func (client *S3Client) CheckAllBuckets(buckets []string) (bucketSummaries []*BucketSummary, err error) {
	bucketSummaries = make([]*BucketSummary, 0)
	for _, bucketName := range buckets {
		bucketSummary, err := client.CheckBucket(bucketName)
		if err != nil {
			return bucketSummaries, err
		}
		bucketSummaries = append(bucketSummaries, bucketSummary)
	}
	return bucketSummaries, nil
}

// Returns info about the contents of the bucket named bucketName.
// BucketSummary contains the bucket name, a list of keys, and the
// size of the largest file in the bucket.
// TODO: Write unit test
func (client *S3Client) CheckBucket(bucketName string) (bucketSummary *BucketSummary, err error) {
	bucket := client.S3.Bucket(bucketName)
	if bucket == nil {
		err = fmt.Errorf("Cannot retrieve bucket: %s", bucketName)
		return nil, err
	}
	bucketSummary = new(BucketSummary)
	bucketSummary.BucketName = bucketName
	bucketSummary.Keys, err = client.ListBucket(bucketName, 0)
	if err != nil {
		return nil, err
	}
	return bucketSummary, nil
}

// Creates an options struct that adds metadata headers to the S3 put.
func (client *S3Client) MakeOptions(md5sum string, metadata map[string][]string) s3.Options {
	if md5sum != "" {
		return s3.Options{
			ContentMD5: md5sum,
			Meta:       metadata,
		}
	} else {
		return s3.Options{
			Meta: metadata,
		}
	}
}

// Saves a file to S3 with default access of Private.
// The underlying S3 client does not return the md5 checksum
// from s3, but we already have this info elsewhere. If the
// PUT produces no error, we assume the copy worked and the
// files md5 sum is the same on S3 as here.
func (client *S3Client) SaveToS3(bucketName, fileName, contentType string, reader io.Reader, byteCount int64, options s3.Options) (url string, err error) {
	bucket := client.S3.Bucket(bucketName)
	putErr := bucket.PutReader(fileName, reader, byteCount,
		contentType, s3.Private, options)
	if putErr != nil {
		err = fmt.Errorf("Error saving file '%s' to bucket '%s': %v",
			fileName, bucketName, putErr)
		return "", err
	}
	url = fmt.Sprintf("https://s3.amazonaws.com/%s/%s", bucketName, fileName)
	return url, nil
}

// Returns an S3 key object for the specified file in the
// specified bucket. The key object has the ETag, last mod
// date, size and other useful info.
func (client *S3Client) GetKey(bucketName, fileName string) (*s3.Key, error) {
	bucket := client.S3.Bucket(bucketName)
	listResp, err := bucket.List(fileName, "", "", 1)
	if err != nil {
		err = fmt.Errorf("Error checking key '%s' in bucket '%s': '%v'",
			fileName, bucketName, err)
		return nil, err
	}
	if listResp == nil || len(listResp.Contents) < 1 {
		err = fmt.Errorf("Key '%s' not found in bucket '%s'",
			fileName, bucketName)
		return nil, err
	}
	return &listResp.Contents[0], nil
}

// Deletes an item from S3
func (client *S3Client) Delete(bucketName, fileName string) error {
	bucket := client.S3.Bucket(bucketName)
	return bucket.Del(fileName)
}

// Sends a large file (>= 5GB) to S3 in 200MB chunks. This operation
// may take several minutes to complete. Note that os.File satisfies
// the s3.ReaderAtSeeker interface.
func (client *S3Client) SaveLargeFileToS3(bucketName, fileName, contentType string,
	reader s3.ReaderAtSeeker, byteCount int64, options s3.Options, chunkSize int64) (url string, err error) {

	bucket := client.S3.Bucket(bucketName)
	multipartPut, err := bucket.InitMulti(fileName, contentType, s3.Private, options)
	if err != nil {
		return "", err
	}

	// Send all of the individual parts to S3 in chunks
	parts, err := multipartPut.PutAll(reader, chunkSize)
	if err != nil {
		abortErr := multipartPut.Abort()
		if abortErr != nil {
			return "", fmt.Errorf("Multipart put failed with error %v "+
				"while uploading a part and abort failed with error %v. "+
				"YOU WILL BE CHARGED FOR THESE FILE PARTS UNTIL YOU DELETE THEM! "+
				"Use multi.ListMulti in the S3 package to list orphaned parts.",
				err, abortErr)
		}
		return "", err
	}

	// This command tells S3 to stitch all the parts into a single file.
	err = multipartPut.Complete(parts)
	if err != nil {
		abortErr := multipartPut.Abort()
		if abortErr != nil {
			return "", fmt.Errorf("Multipart put failed in 'complete' stage "+
				"with error %v and abort failed with error %v",
				err, abortErr)
		}
		return "", err
	}

	resp, err := bucket.Head(fileName, nil)
	if err != nil {
		return "", fmt.Errorf("Files were uploaded to S3, but attempt to "+
			"confirm metadata returned this error: %v", err)
	}

	// Make sure all the meta data made it there.
	// Var metadata is the metadata we sent to S3.
	metadata := options.Meta
	notVerified := ""

	if !metadataMatches(metadata, "institution", resp.Header, "X-Amz-Meta-Institution") {
		notVerified += "institution, "
	}
	if !metadataMatches(metadata, "bag", resp.Header, "X-Amz-Meta-Bag") {
		notVerified += "bag, "
	}
	if !metadataMatches(metadata, "bagpath", resp.Header, "X-Amz-Meta-Bagpath") {
		notVerified += "bagpath, "
	}
	if !metadataMatches(metadata, "md5", resp.Header, "X-Amz-Meta-Md5") {
		notVerified += "md5"
	}
	if len(notVerified) > 0 {
		return "", fmt.Errorf("Multi-part upload succeeded, but S3 does not return "+
			"the following metadata: %s", notVerified)
	}

	url = fmt.Sprintf("https://s3.amazonaws.com/%s/%s", bucketName, fileName)
	return url, nil
}

func metadataMatches(metadata map[string][]string, key string, s3headers map[string][]string, headerName string) bool {
	metaValue, keyExists := metadata[key]
	headerValue, headerExists := s3headers[headerName]

	// If we didn't send this metadata in the first place, we
	// don't care if S3 has it.
	if !keyExists {
		return true
	}

	// If we sent the metadata, test whether S3 returned
	// what we sent.
	if keyExists && len(metaValue) > 0 && headerExists && len(headerValue) > 0 {
		return metaValue[0] == headerValue[0]
	}

	// If we get here, the key exists in the metadata we
	// sent, but not in the S3 headers.
	return false
}
<|endoftext|>


<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strings"
	"time"
)

type slackWebHookNotifier struct {
	url string
}

func (s slackWebHookNotifier) Notify(repos map[string]time.Time) error {
	keys := make([]string, 0, len(repos))
	for k := range repos {
		keys = append(keys, k)
	}
	sort.Sort(sort.StringSlice(keys))

	var lines []string
	for _, k := range keys {
		line := fmt.Sprintf("Repo <https://github.com/%v|%v> was updated at %v", k, k, repos[k])
		lines = append(lines, line)
	}

	payload := struct {
		Text string `json:"text"`
	}{
		Text: strings.Join(lines, "\n"),
	}
	b, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("error marshallaing payload: %v", err)
	}

	resp, err := http.Post(s.url, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return fmt.Errorf("error performing POST request: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status: %v", resp.Status)
	}
	return nil
}
<commit_msg>Refactored sort.StringSlice -> sort.Strings<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strings"
	"time"
)

type slackWebHookNotifier struct {
	url string
}

func (s slackWebHookNotifier) Notify(repos map[string]time.Time) error {
	keys := make([]string, 0, len(repos))
	for k := range repos {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var lines []string
	for _, k := range keys {
		line := fmt.Sprintf("Repo <https://github.com/%v|%v> was updated at %v", k, k, repos[k])
		lines = append(lines, line)
	}

	payload := struct {
		Text string `json:"text"`
	}{
		Text: strings.Join(lines, "\n"),
	}
	b, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("error marshallaing payload: %v", err)
	}

	resp, err := http.Post(s.url, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return fmt.Errorf("error performing POST request: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status: %v", resp.Status)
	}
	return nil
}
<|endoftext|>


<commit_before>package sleapi

import (
	"fmt"
	//"io"
	"net/http"
	"os"
)

type Napper func(w http.ResponseWriter, req *http.Request)

type Sleep struct {
	Port   string
	Router *Router
	Naps   []Napper
}

func Sleeper() *Sleep {
	r := NewRouter()
	s := &Sleep{Port: "3030", Router: r}
	s.Naps = make([]Napper, 0)

	s.Naps = append(s.Naps, Static("/static/"))
	s.Naps = append(s.Naps, r.FindRoute)

	return s
}

// hello world, the web server
func (this *Sleep) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	//io.WriteString(w, "hello, world!\n")
	fmt.Println("Server: ", req.URL.Path)

	for _, h := range this.Naps {
		h(w, req)
	}

	//this.Router.FindRoute(w, req)
}

func (this *Sleep) Run() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "3030"
	}

	http.ListenAndServe(":"+port, this)
}
<commit_msg>Printing error from ListenAndServe<commit_after>package sleapi

import (
	"fmt"
	//"io"
	"net/http"
	"os"
)

type Napper func(w http.ResponseWriter, req *http.Request)

type Sleep struct {
	Port   string
	Router *Router
	Naps   []Napper
}

func Sleeper() *Sleep {
	r := NewRouter()
	s := &Sleep{Port: "3030", Router: r}
	s.Naps = make([]Napper, 0)

	s.Naps = append(s.Naps, Static("/static/"))
	s.Naps = append(s.Naps, r.FindRoute)

	return s
}

// hello world, the web server
func (this *Sleep) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	//io.WriteString(w, "hello, world!\n")
	fmt.Println("Server: ", req.URL.Path)

	for _, h := range this.Naps {
		h(w, req)
	}

	//this.Router.FindRoute(w, req)
}

func (this *Sleep) Run() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "3030"
	}

	err:= http.ListenAndServe(":"+port, this)
	if err != nil{
		fmt.Println("ListenAndServer Error: ", err.Error())
	}
}
<|endoftext|>


<commit_before>// TODO: split 1 layer into sublayers, so that they can have different infills etc.

// Package slice provides types and functions for slicing and compiling STL format 3D models
// into G-code to be used for 3D printing.
package slice // import "sigint.ca/slice"

import (
	"fmt"
	"io"
	"os"
	"sync"
)

var debug bool

// A Config variable specifies a slicing configuration.
type Config struct {
	DebugMode bool

	LayerHeight float64
	LineWidth   float64

	InfillSpacing float64
	InfillAngle   float64 // in degrees
}

func dprintf(format string, args ...interface{}) {
	if debug {
		fmt.Fprintf(os.Stderr, "[ "+format+" ]\n", args...)
	}
}

func wprintf(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "WARNING: "+format+"\n", args...)
}

// Slice divides parsed STL data into layers and optionally compiles G-code
// for each layer. The G-code is written to w, if w is not nil.
// After Slice returns, the resulting layers can be accessed as the STL's
// Layers variable.
func (s *STL) Slice(w io.Writer, cfg Config) error {
	debug = cfg.DebugMode

	var wg sync.WaitGroup
	nLayers := int(0.5 + (s.Max.Z-s.Min.Z)/cfg.LayerHeight)
	s.Layers = make([]*Layer, nLayers)
	h := cfg.LayerHeight

	// slice in parallel if not in debug mode
	if debug {
		for i := range s.Layers {
			s.Layers[i] = s.sliceLayer(i, s.Min.Z+0.001+float64(i)*h, cfg)
		}
	} else {
		for i := range s.Layers {
			wg.Add(1)
			go func(i int, z float64) {
				s.Layers[i] = s.sliceLayer(i, z, cfg)
				wg.Done()
			}(i, 0.001+float64(i)*h)
		}
		wg.Wait()
	}

	dprintf("sliced %d layers", nLayers)

	if w == nil {
		return nil
	}
	for _, l := range s.Layers {
		_, err := w.Write(l.Gcode())
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>fix first layer Z in normal mode, not just debug mode<commit_after>// TODO: split 1 layer into sublayers, so that they can have different infills etc.

// Package slice provides types and functions for slicing and compiling STL format 3D models
// into G-code to be used for 3D printing.
package slice // import "sigint.ca/slice"

import (
	"fmt"
	"io"
	"os"
	"sync"
)

var debug bool

// A Config variable specifies a slicing configuration.
type Config struct {
	DebugMode bool

	LayerHeight float64
	LineWidth   float64

	InfillSpacing float64
	InfillAngle   float64 // in degrees
}

func dprintf(format string, args ...interface{}) {
	if debug {
		fmt.Fprintf(os.Stderr, "[ "+format+" ]\n", args...)
	}
}

func wprintf(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "WARNING: "+format+"\n", args...)
}

// Slice divides parsed STL data into layers and optionally compiles G-code
// for each layer. The G-code is written to w, if w is not nil.
// After Slice returns, the resulting layers can be accessed as the STL's
// Layers variable.
func (s *STL) Slice(w io.Writer, cfg Config) error {
	debug = cfg.DebugMode

	var wg sync.WaitGroup
	nLayers := int(0.5 + (s.Max.Z-s.Min.Z)/cfg.LayerHeight)
	s.Layers = make([]*Layer, nLayers)
	h := cfg.LayerHeight

	// slice in parallel if not in debug mode
	if debug {
		for i := range s.Layers {
			s.Layers[i] = s.sliceLayer(i, s.Min.Z+0.01+float64(i)*h, cfg)
		}
	} else {
		for i := range s.Layers {
			wg.Add(1)
			go func(i int, z float64) {
				s.Layers[i] = s.sliceLayer(i, z, cfg)
				wg.Done()
			}(i, s.Min.Z+0.01+float64(i)*h)
		}
		wg.Wait()
	}

	dprintf("sliced %d layers", nLayers)

	if w == nil {
		return nil
	}
	for _, l := range s.Layers {
		_, err := w.Write(l.Gcode())
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package ari

import (
	"sync"

	"golang.org/x/net/context"
)

// ALL signifies that the subscriber wants all events
const ALL string = "all"

// Bus is an event bus for ARI events.  It receives and
// redistributes events based on a subscription
// model.
type Bus struct {
	subs []*Subscription // The list of subscriptions

	mu sync.Mutex
}

func (b *Bus) addSubscription(s *Subscription) {
	b.mu.Lock()
	b.subs = append(b.subs, s)
	b.mu.Unlock()
}

func (b *Bus) removeSubscription(s *Subscription) {
	b.mu.Lock()
	defer b.mu.Unlock()
	for i, si := range b.subs {
		if s == si {
			// Subs are pointers, so we have to explicitly remove them
			// to prevent memory leaks
			b.subs[i] = b.subs[len(b.subs)-1] // replace the current with the end
			b.subs[len(b.subs)-1] = nil       // remove the end
			b.subs = b.subs[:len(b.subs)-1]   // lop off the end
			return
		}
	}
}

func (b *Bus) send(msg *Message) {
	var e Eventer
	switch msg.Type {
	case "BridgeAttendedTransfer":
		e = &BridgeAttendedTransfer{}
	case "BridgeBlindTransfer":
		e = &BridgeBlindTransfer{}
	case "BridgeCreated":
		e = &BridgeCreated{}
	case "BridgeDestroyed":
		e = &BridgeDestroyed{}
	case "BridgeMerged":
		e = &BridgeMerged{}
	case "ChannelCallerId":
		e = &ChannelCallerId{}
	case "ChannelConnectedLine":
		e = &ChannelConnectedLine{}
	case "ChannelCreated":
		e = &ChannelCreated{}
	case "ChannelDestroyed":
		e = &ChannelDestroyed{}
	case "ChannelDialplan":
		e = &ChannelDialplan{}
	case "ChannelDtmfReceived":
		e = &ChannelDtmfReceived{}
	case "ChannelEnteredBridge":
		e = &ChannelEnteredBridge{}
	case "ChannelHangupRequest":
		e = &ChannelHangupRequest{}
	case "ChannelHold":
		e = &ChannelHold{}
	case "ChannelLeftBridge":
		e = &ChannelLeftBridge{}
	case "ChannelStateChange":
		e = &ChannelStateChange{}
	case "ChannelTalkingFinished":
		e = &ChannelTalkingFinished{}
	case "ChannelTalkingStarted":
		e = &ChannelTalkingStarted{}
	case "ChannelUnhold":
		e = &ChannelUnhold{}
	case "ChannelUserevent":
		e = &ChannelUserevent{}
	case "ChannelVarset":
		e = &ChannelVarset{}
	case "ContactStatusChanged":
		e = &ContactStatusChanged{}
	case "DeviceStateChanged":
		e = &DeviceStateChanged{}
	case "Dial":
		e = &Dial{}
	case "EndpointStateChange":
		e = &EndpointStateChange{}
	case "PeerStatusChange":
		e = &PeerStatusChange{}
	case "PlaybackFinished":
		e = &PlaybackFinished{}
	case "PlaybackStarted":
		e = &PlaybackStarted{}
	case "RecordingFailed":
		e = &RecordingFailed{}
	case "RecordingFinished":
		e = &RecordingFinished{}
	case "RecordingStarted":
		e = &RecordingStarted{}
	case "StasisEnd":
		e = &StasisEnd{}
	case "StasisStart":
		e = &StasisStart{}
	case "TextMessageReceived":
		e = &TextMessageReceived{}
	default:
		Logger.Debug("Unhandled event received:", "type", msg.Type)
		e = &Event{}
	}
	err := msg.DecodeAs(e)
	if err != nil {
		Logger.Error("Failed to decode message", "error", err)
		return
	}
	Logger.Debug("Received event", "event", e)

	// Disseminate the message to the subscribers
	for _, s := range b.subs {
		for _, topic := range s.events {
			if topic == e.GetType() || topic == ALL {
				select {
				case s.C <- e:
				default: // never block
				}
			}
		}
	}
}

// StartBus creates and returns the event bus.
func StartBus(ctx context.Context) *Bus {
	b := &Bus{
		subs: []*Subscription{},
	}

	// Listen for stop and shut down subscriptions, as required
	go func() {
		<-ctx.Done()
		b.Stop()
		return
	}()

	return b
}

// Stop the bus.  Cancels all subscriptions
// and stops listening for events.
func (b *Bus) Stop() {
	// Close all subscriptions
	b.mu.Lock()
	if b.subs != nil {
		for i, s := range b.subs {
			s.closeChan()
			b.subs[i] = nil
		}
		b.subs = nil
	}
	b.mu.Unlock()
}

// A Subscription is a wrapped channel for receiving
// events from the ARI event bus.
type Subscription struct {
	b      *Bus         // reference to the event bus
	events []string     // list of events to listen for
	C      chan Eventer // channel for sending events to the subscriber
	mu     sync.Mutex
}

// Subscribe returns a subscription to the given list
// of event types
func (b *Bus) Subscribe(eTypes ...string) *Subscription {
	s := &Subscription{
		b:      b,
		events: eTypes,
		C:      make(chan Eventer, 1),
	}
	b.addSubscription(s)
	return s
}

// Next blocks for the next event in the subscription,
// returning that event when it arrives or nil if
// the subscription is canceled.
// Normally, one would listen to subscription.C directly,
// but this is a convenience function for providing a
// context to alternately cancel.
func (s *Subscription) Next(ctx context.Context) Eventer {
	select {
	case <-ctx.Done():
		return nil
	case e := <-s.C:
		return e
	}
}

func (s *Subscription) closeChan() {
	s.mu.Lock()
	if s.C != nil {
		close(s.C)
		s.C = nil
	}
	s.mu.Unlock()
}

// Cancel cancels the subscription and removes it from
// the event bus.
func (s *Subscription) Cancel() {
	s.b.removeSubscription(s)
	s.closeChan()
}

// Once listens for the first event of the provided types,
// returning a channel which supplies that event.
func (b *Bus) Once(ctx context.Context, eTypes ...string) <-chan Eventer {
	s := b.Subscribe(eTypes...)
	ret := make(chan Eventer, 1)

	// Stop subscription after one event
	go func() {
		select {
		case ret <- <-s.C:
		case <-ctx.Done():
		}
		close(ret)
		s.Cancel()
	}()
	return ret
}
<commit_msg>bus - increase the subscription buffer size<commit_after>package ari

import (
	"sync"

	"golang.org/x/net/context"
)

// BuxMaxChannelBuffer defines the buffer size of the subscription channels
var BusChannelBuffer = 100

// ALL signifies that the subscriber wants all events
const ALL string = "all"

// Bus is an event bus for ARI events.  It receives and
// redistributes events based on a subscription
// model.
type Bus struct {
	subs []*Subscription // The list of subscriptions

	mu sync.Mutex
}

func (b *Bus) addSubscription(s *Subscription) {
	b.mu.Lock()
	b.subs = append(b.subs, s)
	b.mu.Unlock()
}

func (b *Bus) removeSubscription(s *Subscription) {
	b.mu.Lock()
	defer b.mu.Unlock()
	for i, si := range b.subs {
		if s == si {
			// Subs are pointers, so we have to explicitly remove them
			// to prevent memory leaks
			b.subs[i] = b.subs[len(b.subs)-1] // replace the current with the end
			b.subs[len(b.subs)-1] = nil       // remove the end
			b.subs = b.subs[:len(b.subs)-1]   // lop off the end
			return
		}
	}
}

func (b *Bus) send(msg *Message) {
	var e Eventer
	switch msg.Type {
	case "BridgeAttendedTransfer":
		e = &BridgeAttendedTransfer{}
	case "BridgeBlindTransfer":
		e = &BridgeBlindTransfer{}
	case "BridgeCreated":
		e = &BridgeCreated{}
	case "BridgeDestroyed":
		e = &BridgeDestroyed{}
	case "BridgeMerged":
		e = &BridgeMerged{}
	case "ChannelCallerId":
		e = &ChannelCallerId{}
	case "ChannelConnectedLine":
		e = &ChannelConnectedLine{}
	case "ChannelCreated":
		e = &ChannelCreated{}
	case "ChannelDestroyed":
		e = &ChannelDestroyed{}
	case "ChannelDialplan":
		e = &ChannelDialplan{}
	case "ChannelDtmfReceived":
		e = &ChannelDtmfReceived{}
	case "ChannelEnteredBridge":
		e = &ChannelEnteredBridge{}
	case "ChannelHangupRequest":
		e = &ChannelHangupRequest{}
	case "ChannelHold":
		e = &ChannelHold{}
	case "ChannelLeftBridge":
		e = &ChannelLeftBridge{}
	case "ChannelStateChange":
		e = &ChannelStateChange{}
	case "ChannelTalkingFinished":
		e = &ChannelTalkingFinished{}
	case "ChannelTalkingStarted":
		e = &ChannelTalkingStarted{}
	case "ChannelUnhold":
		e = &ChannelUnhold{}
	case "ChannelUserevent":
		e = &ChannelUserevent{}
	case "ChannelVarset":
		e = &ChannelVarset{}
	case "ContactStatusChanged":
		e = &ContactStatusChanged{}
	case "DeviceStateChanged":
		e = &DeviceStateChanged{}
	case "Dial":
		e = &Dial{}
	case "EndpointStateChange":
		e = &EndpointStateChange{}
	case "PeerStatusChange":
		e = &PeerStatusChange{}
	case "PlaybackFinished":
		e = &PlaybackFinished{}
	case "PlaybackStarted":
		e = &PlaybackStarted{}
	case "RecordingFailed":
		e = &RecordingFailed{}
	case "RecordingFinished":
		e = &RecordingFinished{}
	case "RecordingStarted":
		e = &RecordingStarted{}
	case "StasisEnd":
		e = &StasisEnd{}
	case "StasisStart":
		e = &StasisStart{}
	case "TextMessageReceived":
		e = &TextMessageReceived{}
	default:
		Logger.Debug("Unhandled event received:", "type", msg.Type)
		e = &Event{}
	}
	err := msg.DecodeAs(e)
	if err != nil {
		Logger.Error("Failed to decode message", "error", err)
		return
	}
	Logger.Debug("Received event", "event", e)

	// Disseminate the message to the subscribers
	for _, s := range b.subs {
		for _, topic := range s.events {
			if topic == e.GetType() || topic == ALL {
				select {
				case s.C <- e:
				default: // never block
				}
			}
		}
	}
}

// StartBus creates and returns the event bus.
func StartBus(ctx context.Context) *Bus {
	b := &Bus{
		subs: []*Subscription{},
	}

	// Listen for stop and shut down subscriptions, as required
	go func() {
		<-ctx.Done()
		b.Stop()
		return
	}()

	return b
}

// Stop the bus.  Cancels all subscriptions
// and stops listening for events.
func (b *Bus) Stop() {
	// Close all subscriptions
	b.mu.Lock()
	if b.subs != nil {
		for i, s := range b.subs {
			s.closeChan()
			b.subs[i] = nil
		}
		b.subs = nil
	}
	b.mu.Unlock()
}

// A Subscription is a wrapped channel for receiving
// events from the ARI event bus.
type Subscription struct {
	b      *Bus         // reference to the event bus
	events []string     // list of events to listen for
	C      chan Eventer // channel for sending events to the subscriber
	mu     sync.Mutex
}

// Subscribe returns a subscription to the given list
// of event types
func (b *Bus) Subscribe(eTypes ...string) *Subscription {
	s := &Subscription{
		b:      b,
		events: eTypes,
		C:      make(chan Eventer, BusChannelBuffer),
	}
	b.addSubscription(s)
	return s
}

// Next blocks for the next event in the subscription,
// returning that event when it arrives or nil if
// the subscription is canceled.
// Normally, one would listen to subscription.C directly,
// but this is a convenience function for providing a
// context to alternately cancel.
func (s *Subscription) Next(ctx context.Context) Eventer {
	select {
	case <-ctx.Done():
		return nil
	case e := <-s.C:
		return e
	}
}

func (s *Subscription) closeChan() {
	s.mu.Lock()
	if s.C != nil {
		close(s.C)
		s.C = nil
	}
	s.mu.Unlock()
}

// Cancel cancels the subscription and removes it from
// the event bus.
func (s *Subscription) Cancel() {
	s.b.removeSubscription(s)
	s.closeChan()
}

// Once listens for the first event of the provided types,
// returning a channel which supplies that event.
func (b *Bus) Once(ctx context.Context, eTypes ...string) <-chan Eventer {
	s := b.Subscribe(eTypes...)
	ret := make(chan Eventer, BusChannelBuffer)

	// Stop subscription after one event
	go func() {
		select {
		case ret <- <-s.C:
		case <-ctx.Done():
		}
		close(ret)
		s.Cancel()
	}()
	return ret
}
<|endoftext|>


<commit_before>package jptel

import (
	"errors"
	"strings"
)

// from: http://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/number_shitei.html
//  0 + [xxxx]  ~  [xxxx] + [xxxx]
//  1 + <- total 5 num -> = 6

const (
	totalCodeLen = 6

	mobileCodePrefixLen = 3
	mobileCodeLen       = 4
	freeDialPrefixLen   = 4
	freeDialCodeLen     = 3
	otherCodePrefixLen  = 4
	otherCodeLen        = 3
)

var mobileCode = []string{
	"050", // IP電話
	"070", // 携帯電話/PHS
	"080", // 携帯電話
	"090", // 携帯電話
	"020", // その他
}

var freeDialCode = []string{
	"0120", // フリーダイヤル 0120-xxx-xxx
	"0800", // フリーダイヤル 0800-xxx-xxxx
}

var otherCode = []string{
	"0570", // ナビダイヤル 0570-xxx-xxx
	"0990", // ダイヤルQ2 0990-xxx-xxx
}

var areaCodes = [][]string{
	areaCode5,
	areaCode4,
	areaCode3,
	areaCode2,
}

var (
	shortError = errors.New("telephone number is too short.")
	matchError = errors.New("telephone number does not match any area code.")
)

// Split splits japaneses telephone number to a slice consist of AreaCode, CityCode, SubscriberNumber.
func Split(tel string) (JPTelephoneNumber, error) {
	// 固定電話
	for _, areaCode := range areaCodes {
		for _, code := range areaCode {
			if strings.HasPrefix(tel, code) {
				if len(tel) < totalCodeLen {
					return JPTelephoneNumber{}, shortError
				}
				codeLen := len(code)
				return JPTelephoneNumber{
					AreaCode:       tel[:codeLen],
					CityCode:       tel[codeLen:totalCodeLen],
					SubscriberCode: tel[totalCodeLen:],
				}, nil
			}
		}
	}

	// フリーダイヤル
	for _, code := range freeDialCode {
		if strings.HasPrefix(tel, code) {
			if len(tel) < freeDialPrefixLen+freeDialCodeLen {
				return JPTelephoneNumber{}, shortError
			}
			return JPTelephoneNumber{
				AreaCode:       tel[:freeDialPrefixLen],
				CityCode:       tel[freeDialPrefixLen : freeDialPrefixLen+freeDialCodeLen],
				SubscriberCode: tel[freeDialPrefixLen+freeDialCodeLen:],
			}, nil
		}
	}

	// 携帯番号
	for _, code := range mobileCode {
		if strings.HasPrefix(tel, code) {
			if len(tel) < mobileCodePrefixLen+mobileCodeLen {
				return JPTelephoneNumber{}, shortError
			}
			return JPTelephoneNumber{
				AreaCode:       tel[:mobileCodePrefixLen],
				CityCode:       tel[mobileCodePrefixLen : mobileCodePrefixLen+mobileCodeLen],
				SubscriberCode: tel[mobileCodePrefixLen+mobileCodeLen:],
			}, nil
		}
	}

	// その他番号
	for _, code := range otherCode {
		if strings.HasPrefix(tel, code) {
			if len(tel) < otherCodePrefixLen+otherCodeLen {
				return JPTelephoneNumber{}, shortError
			}
			return JPTelephoneNumber{
				AreaCode:       tel[:otherCodePrefixLen],
				CityCode:       tel[otherCodePrefixLen : otherCodePrefixLen+otherCodeLen],
				SubscriberCode: tel[otherCodePrefixLen+otherCodeLen:],
			}, nil
		}
	}

	return JPTelephoneNumber{}, matchError
}
<commit_msg>improve Split implementation<commit_after>package jptel

import (
	"errors"
	"strings"
)

// from: http://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/number_shitei.html
//  0 + [xxxx]  ~  [xxxx] + [xxxx]
//  1 + <- total 5 num -> = 6

const (
	totalCodeLen = 6

	mobileCodePrefixLen = 3
	mobileCodeLen       = 4
	freeDialPrefixLen   = 4
	freeDialCodeLen     = 3
	otherCodePrefixLen  = 4
	otherCodeLen        = 3
)

var mobileCode = []string{
	"050", // IP電話
	"070", // 携帯電話/PHS
	"080", // 携帯電話
	"090", // 携帯電話
	"020", // その他
}

var freeDialCode = []string{
	"0120", // フリーダイヤル 0120-xxx-xxx
	"0800", // フリーダイヤル 0800-xxx-xxxx
}

var otherCode = []string{
	"0570", // ナビダイヤル 0570-xxx-xxx
	"0990", // ダイヤルQ2 0990-xxx-xxx
}

var areaCodes = [][]string{
	areaCode5,
	areaCode4,
	areaCode3,
	areaCode2,
}

var (
	shortError = errors.New("telephone number is too short.")
	matchError = errors.New("telephone number does not match any area code.")
)

func generateJPTelephoneNumber(tel string, areaCodeLength, cityCodeLength int) (JPTelephoneNumber, error) {
	var totalCodeLength = areaCodeLength + cityCodeLength
	if len(tel) < totalCodeLength {
		return JPTelephoneNumber{}, shortError
	}

	return JPTelephoneNumber{
		AreaCode:       tel[:areaCodeLength],
		CityCode:       tel[areaCodeLength:totalCodeLength],
		SubscriberCode: tel[totalCodeLength:],
	}, nil
}

// Split splits japaneses telephone number to a slice consist of AreaCode, CityCode, SubscriberNumber.
func Split(tel string) (JPTelephoneNumber, error) {
	// 固定電話
	for _, areaCode := range areaCodes {
		for _, code := range areaCode {
			if strings.HasPrefix(tel, code) {
				areaCodeLength := len(code)
				cityCodeLength := totalCodeLen - areaCodeLength
				return generateJPTelephoneNumber(tel, areaCodeLength, cityCodeLength)
			}
		}
	}

	// フリーダイヤル
	for _, code := range freeDialCode {
		if strings.HasPrefix(tel, code) {
			return generateJPTelephoneNumber(tel, freeDialPrefixLen, freeDialCodeLen)
		}
	}

	// 携帯番号
	for _, code := range mobileCode {
		if strings.HasPrefix(tel, code) {
			return generateJPTelephoneNumber(tel, mobileCodePrefixLen, mobileCodeLen)
		}
	}

	// その他番号
	for _, code := range otherCode {
		if strings.HasPrefix(tel, code) {
			return generateJPTelephoneNumber(tel, otherCodePrefixLen, otherCodeLen)
		}
	}

	return JPTelephoneNumber{}, matchError
}
<|endoftext|>