// Package quotedb implements a plugin to store quotes for channels. We pop them // into a database to aid indexing / etc. Recommend SQLite. package quotedb import ( "fmt" "log" "math/rand" "regexp" "sort" "strconv" "strings" "github.com/tcolgate/hugot" "golang.org/x/net/context" ) var ( hearAddQuote = regexp.MustCompile(`^\!addquote (.*)$`) hearLastQuote = regexp.MustCompile(`^\!lastquote$`) hearFindQuote = regexp.MustCompile(`^\!findquote (.*)$`) hearRandQuote = regexp.MustCompile(`^\!randquote$`) hearQuote = regexp.MustCompile(`^\!quote (\d+)$`) ) type AddQuoteHandler struct { QuoteDB *QuoteDB } type LastQuoteHandler struct { QuoteDB *QuoteDB } type FindQuoteHandler struct { QuoteDB *QuoteDB } type RandQuoteHandler struct { QuoteDB *QuoteDB } type QuoteHandler struct { QuoteDB *QuoteDB } func (h *AddQuoteHandler) Describe() (string, string) { return "addquote", "Add a quote to the database" } func (h *AddQuoteHandler) Hears() *regexp.Regexp { return hearAddQuote } func (h *AddQuoteHandler) Heard(ctx context.Context, w hugot.ResponseWriter, m *hugot.Message, submatches [][]string) { if len(submatches) != 1 || len(submatches[0]) != 2 { log.Printf("Submatches weird! %+v", submatches) return } var rsp string quote, err := h.QuoteDB.AddQuote(m.Channel, submatches[0][1], m.From) if err != nil { log.Printf("%s: AddQuote error: %v", m.Channel, err) rsp = "Failed to add quote, sorry!" } else { rsp = fmt.Sprintf("Quote %d added", quote.QuoteId) } w.Send(ctx, m.Reply(rsp)) } func (h *LastQuoteHandler) Describe() (string, string) { return "lastquote", "Show the last-added quote" } func (h *LastQuoteHandler) Hears() *regexp.Regexp { return hearLastQuote } func (h *LastQuoteHandler) Heard(ctx context.Context, w hugot.ResponseWriter, m *hugot.Message, submatches [][]string) { quote, err := h.QuoteDB.FindLastQuote(m.Channel) if err != nil { log.Printf("%s: Error in LastQuote: %v", m.Channel, err) return } rsp := fmt.Sprintf("Quote %d by %s: %s", quote.QuoteId, quote.Author, quote.Data) w.Send(ctx, m.Reply(rsp)) } func (h *FindQuoteHandler) Describe() (string, string) { return "findquote", "Search for a quote by substring" } func (h *FindQuoteHandler) Hears() *regexp.Regexp { return hearFindQuote } func (h *FindQuoteHandler) find(channel, term string) string { quotes, err := h.QuoteDB.FindQuotesByString(term, channel) if err != nil { return "Couldn't find any quotes matching search term" } switch len(quotes) { case 0: return "No results" case 1: return fmt.Sprintf("1 match. ID %d: %s: %s", quotes[0].QuoteId, quotes[0].Author, quotes[0].Data) } quote := quotes[0] quoteCount := strconv.Itoa(len(quotes)) if len(quotes) > 5 { quoteCount = "More than 5" } quoteIds := make([]int, 0) max := 5 if len(quotes) < max { max = len(quotes) } indices := rand.Perm(len(quotes))[0:max] for _, i := range indices { if quotes[i] == quote || quotes[i] == nil { continue } quoteIds = append(quoteIds, quotes[i].QuoteId) } quoteIds = append(quoteIds, quote.QuoteId) sort.Ints(quoteIds) quoteIdStrs := make([]string, len(quoteIds)) for i, d := range quoteIds { quoteIdStrs[i] = strconv.Itoa(d) } idStr := strings.Join(quoteIdStrs, ", ") return fmt.Sprintf("%s matches. IDs: %s. Most recent: %s: %s", quoteCount, idStr, quote.Author, quote.Data) } func (h *FindQuoteHandler) Heard(ctx context.Context, w hugot.ResponseWriter, m *hugot.Message, submatches [][]string) { if len(submatches) != 1 || len(submatches[0]) != 2 { log.Printf("Submatches weird! %+v", submatches) return } rsp := h.find(m.Channel, submatches[0][1]) w.Send(ctx, m.Reply(rsp)) } func (h *RandQuoteHandler) Describe() (string, string) { return "randquote", "Show a random quote" } func (h *RandQuoteHandler) Hears() *regexp.Regexp { return hearRandQuote } func (h *RandQuoteHandler) Heard(ctx context.Context, w hugot.ResponseWriter, m *hugot.Message, submatches [][]string) { quote, err := h.QuoteDB.FindRandomQuote(m.Channel) if err != nil { log.Printf("%s: Error in RandQuote: %v", m.Channel, err) return } rsp := fmt.Sprintf("Quote %d by %s: %s", quote.QuoteId, quote.Author, quote.Data) w.Send(ctx, m.Reply(rsp)) } func (h *QuoteHandler) Describe() (string, string) { return "quote", "show a specific quote, identified by ID" } func (h *QuoteHandler) Hears() *regexp.Regexp { return hearQuote } func (h *QuoteHandler) show(channel, qid string) string { id, err := strconv.Atoi(qid) if err != nil { return "invalid ID" } quote, err := h.QuoteDB.FindQuoteByQuoteId(id, channel) if err != nil { return fmt.Sprintf("Couldn't find quote %d", id) } if quote.DeletedAt.Valid { delName := "(unknown)" if quote.DeletedBy.Valid { delName = quote.DeletedBy.String } return fmt.Sprintf("Quote %d was deleted by %s at %s", quote.Id, delName, quote.DeletedAt.Time.String()) } return fmt.Sprintf("%s: %s", quote.Author, quote.Data) } func (h *QuoteHandler) Heard(ctx context.Context, w hugot.ResponseWriter, m *hugot.Message, submatches [][]string) { if len(submatches) != 1 || len(submatches[0]) != 2 { log.Printf("Submatches weird! %+v", submatches) return } rsp := h.show(m.Channel, submatches[0][1]) w.Send(ctx, m.Reply(rsp)) }