<commit_before>package oauth2

import (
	"errors"
	"net/http"
	"strconv"
	"testing"
)

func TestErrorText(t *testing.T) {
	tests := []struct {
		status   int
		text     string
		expected string
	}{
		{http.StatusContinue, "foo", "foo"},
		{http.StatusSwitchingProtocols, "foo", "foo"},
		{http.StatusOK, "foo", "foo"},
		{http.StatusCreated, "foo", "foo"},
		{http.StatusAccepted, "foo", "foo"},
		{http.StatusNonAuthoritativeInfo, "foo", "foo"},
		{http.StatusNoContent, "foo", "foo"},
		{http.StatusResetContent, "foo", "foo"},
		{http.StatusPartialContent, "foo", "foo"},
		{http.StatusMultipleChoices, "foo", "foo"},
		{http.StatusMovedPermanently, "foo", "foo"},
		{http.StatusFound, "foo", "foo"},
		{http.StatusSeeOther, "foo", "foo"},
		{http.StatusNotModified, "foo", "foo"},
		{http.StatusUseProxy, "foo", "foo"},
		{http.StatusTemporaryRedirect, "foo", "foo"},
		{http.StatusBadRequest, "foo", "foo"},
		{http.StatusUnauthorized, "foo", "foo"},
		{http.StatusPaymentRequired, "foo", "foo"},
		{http.StatusForbidden, "foo", "foo"},
		{http.StatusNotFound, "foo", "foo"},
		{http.StatusMethodNotAllowed, "foo", "foo"},
		{http.StatusNotAcceptable, "foo", "foo"},
		{http.StatusProxyAuthRequired, "foo", "foo"},
		{http.StatusRequestTimeout, "foo", "foo"},
		{http.StatusConflict, "foo", "foo"},
		{http.StatusGone, "foo", "foo"},
		{http.StatusLengthRequired, "foo", "foo"},
		{http.StatusPreconditionFailed, "foo", "foo"},
		{http.StatusRequestEntityTooLarge, "foo", "foo"},
		{http.StatusRequestURITooLong, "foo", "foo"},
		{http.StatusUnsupportedMediaType, "foo", "foo"},
		{http.StatusRequestedRangeNotSatisfiable, "foo", "foo"},
		{http.StatusExpectationFailed, "foo", "foo"},
		{http.StatusTeapot, "foo", "foo"},
		{http.StatusPreconditionRequired, "foo", "foo"},
		{http.StatusTooManyRequests, "foo", "foo"},
		{http.StatusRequestHeaderFieldsTooLarge, "foo", "foo"},
		{http.StatusUnavailableForLegalReasons, "foo", "foo"},
		{http.StatusInternalServerError, "foo", http.StatusText(http.StatusInternalServerError)},
		{http.StatusNotImplemented, "foo", http.StatusText(http.StatusNotImplemented)},
		{http.StatusBadGateway, "foo", http.StatusText(http.StatusBadGateway)},
		{http.StatusServiceUnavailable, "foo", http.StatusText(http.StatusServiceUnavailable)},
		{http.StatusGatewayTimeout, "foo", http.StatusText(http.StatusGatewayTimeout)},
		{http.StatusHTTPVersionNotSupported, "foo", http.StatusText(http.StatusHTTPVersionNotSupported)},
		{http.StatusNetworkAuthenticationRequired, "foo", http.StatusText(http.StatusNetworkAuthenticationRequired)},
	}

	for _, tt := range tests {
		t.Run(strconv.Itoa(tt.status), func(t *testing.T) {
			got := errorText(tt.status, errors.New(tt.text))
			if got != tt.expected {
				t.Errorf("errorText(%v, %v) => %v, expected %v", tt.status, tt.text, got, tt.expected)
			}
		})
	}
}

func TestIsServerError(t *testing.T) {
	tests := []struct {
		status   int
		expected bool
	}{
		{http.StatusContinue, false},
		{http.StatusSwitchingProtocols, false},
		{http.StatusOK, false},
		{http.StatusCreated, false},
		{http.StatusAccepted, false},
		{http.StatusNonAuthoritativeInfo, false},
		{http.StatusNoContent, false},
		{http.StatusResetContent, false},
		{http.StatusPartialContent, false},
		{http.StatusMultipleChoices, false},
		{http.StatusMovedPermanently, false},
		{http.StatusFound, false},
		{http.StatusSeeOther, false},
		{http.StatusNotModified, false},
		{http.StatusUseProxy, false},
		{http.StatusTemporaryRedirect, false},
		{http.StatusBadRequest, false},
		{http.StatusUnauthorized, false},
		{http.StatusPaymentRequired, false},
		{http.StatusForbidden, false},
		{http.StatusNotFound, false},
		{http.StatusMethodNotAllowed, false},
		{http.StatusNotAcceptable, false},
		{http.StatusProxyAuthRequired, false},
		{http.StatusRequestTimeout, false},
		{http.StatusConflict, false},
		{http.StatusGone, false},
		{http.StatusLengthRequired, false},
		{http.StatusPreconditionFailed, false},
		{http.StatusRequestEntityTooLarge, false},
		{http.StatusRequestURITooLong, false},
		{http.StatusUnsupportedMediaType, false},
		{http.StatusRequestedRangeNotSatisfiable, false},
		{http.StatusExpectationFailed, false},
		{http.StatusTeapot, false},
		{http.StatusPreconditionRequired, false},
		{http.StatusTooManyRequests, false},
		{http.StatusRequestHeaderFieldsTooLarge, false},
		{http.StatusUnavailableForLegalReasons, false},
		{http.StatusInternalServerError, true},
		{http.StatusNotImplemented, true},
		{http.StatusBadGateway, true},
		{http.StatusServiceUnavailable, true},
		{http.StatusGatewayTimeout, true},
		{http.StatusHTTPVersionNotSupported, true},
		{http.StatusNetworkAuthenticationRequired, true},
	}

	for _, tt := range tests {
		t.Run(strconv.Itoa(tt.status), func(t *testing.T) {
			got := isServerError(tt.status)
			if got != tt.expected {
				t.Errorf("isServerError(%v) => %v, expected %v", tt.status, got, tt.expected)
			}
		})
	}
}
<commit_msg>Added copyright comment<commit_after>// Copyright (c) 2016 Danilo Bürger <info@danilobuerger.de>

package oauth2

import (
	"errors"
	"net/http"
	"strconv"
	"testing"
)

func TestErrorText(t *testing.T) {
	tests := []struct {
		status   int
		text     string
		expected string
	}{
		{http.StatusContinue, "foo", "foo"},
		{http.StatusSwitchingProtocols, "foo", "foo"},
		{http.StatusOK, "foo", "foo"},
		{http.StatusCreated, "foo", "foo"},
		{http.StatusAccepted, "foo", "foo"},
		{http.StatusNonAuthoritativeInfo, "foo", "foo"},
		{http.StatusNoContent, "foo", "foo"},
		{http.StatusResetContent, "foo", "foo"},
		{http.StatusPartialContent, "foo", "foo"},
		{http.StatusMultipleChoices, "foo", "foo"},
		{http.StatusMovedPermanently, "foo", "foo"},
		{http.StatusFound, "foo", "foo"},
		{http.StatusSeeOther, "foo", "foo"},
		{http.StatusNotModified, "foo", "foo"},
		{http.StatusUseProxy, "foo", "foo"},
		{http.StatusTemporaryRedirect, "foo", "foo"},
		{http.StatusBadRequest, "foo", "foo"},
		{http.StatusUnauthorized, "foo", "foo"},
		{http.StatusPaymentRequired, "foo", "foo"},
		{http.StatusForbidden, "foo", "foo"},
		{http.StatusNotFound, "foo", "foo"},
		{http.StatusMethodNotAllowed, "foo", "foo"},
		{http.StatusNotAcceptable, "foo", "foo"},
		{http.StatusProxyAuthRequired, "foo", "foo"},
		{http.StatusRequestTimeout, "foo", "foo"},
		{http.StatusConflict, "foo", "foo"},
		{http.StatusGone, "foo", "foo"},
		{http.StatusLengthRequired, "foo", "foo"},
		{http.StatusPreconditionFailed, "foo", "foo"},
		{http.StatusRequestEntityTooLarge, "foo", "foo"},
		{http.StatusRequestURITooLong, "foo", "foo"},
		{http.StatusUnsupportedMediaType, "foo", "foo"},
		{http.StatusRequestedRangeNotSatisfiable, "foo", "foo"},
		{http.StatusExpectationFailed, "foo", "foo"},
		{http.StatusTeapot, "foo", "foo"},
		{http.StatusPreconditionRequired, "foo", "foo"},
		{http.StatusTooManyRequests, "foo", "foo"},
		{http.StatusRequestHeaderFieldsTooLarge, "foo", "foo"},
		{http.StatusUnavailableForLegalReasons, "foo", "foo"},
		{http.StatusInternalServerError, "foo", http.StatusText(http.StatusInternalServerError)},
		{http.StatusNotImplemented, "foo", http.StatusText(http.StatusNotImplemented)},
		{http.StatusBadGateway, "foo", http.StatusText(http.StatusBadGateway)},
		{http.StatusServiceUnavailable, "foo", http.StatusText(http.StatusServiceUnavailable)},
		{http.StatusGatewayTimeout, "foo", http.StatusText(http.StatusGatewayTimeout)},
		{http.StatusHTTPVersionNotSupported, "foo", http.StatusText(http.StatusHTTPVersionNotSupported)},
		{http.StatusNetworkAuthenticationRequired, "foo", http.StatusText(http.StatusNetworkAuthenticationRequired)},
	}

	for _, tt := range tests {
		t.Run(strconv.Itoa(tt.status), func(t *testing.T) {
			got := errorText(tt.status, errors.New(tt.text))
			if got != tt.expected {
				t.Errorf("errorText(%v, %v) => %v, expected %v", tt.status, tt.text, got, tt.expected)
			}
		})
	}
}

func TestIsServerError(t *testing.T) {
	tests := []struct {
		status   int
		expected bool
	}{
		{http.StatusContinue, false},
		{http.StatusSwitchingProtocols, false},
		{http.StatusOK, false},
		{http.StatusCreated, false},
		{http.StatusAccepted, false},
		{http.StatusNonAuthoritativeInfo, false},
		{http.StatusNoContent, false},
		{http.StatusResetContent, false},
		{http.StatusPartialContent, false},
		{http.StatusMultipleChoices, false},
		{http.StatusMovedPermanently, false},
		{http.StatusFound, false},
		{http.StatusSeeOther, false},
		{http.StatusNotModified, false},
		{http.StatusUseProxy, false},
		{http.StatusTemporaryRedirect, false},
		{http.StatusBadRequest, false},
		{http.StatusUnauthorized, false},
		{http.StatusPaymentRequired, false},
		{http.StatusForbidden, false},
		{http.StatusNotFound, false},
		{http.StatusMethodNotAllowed, false},
		{http.StatusNotAcceptable, false},
		{http.StatusProxyAuthRequired, false},
		{http.StatusRequestTimeout, false},
		{http.StatusConflict, false},
		{http.StatusGone, false},
		{http.StatusLengthRequired, false},
		{http.StatusPreconditionFailed, false},
		{http.StatusRequestEntityTooLarge, false},
		{http.StatusRequestURITooLong, false},
		{http.StatusUnsupportedMediaType, false},
		{http.StatusRequestedRangeNotSatisfiable, false},
		{http.StatusExpectationFailed, false},
		{http.StatusTeapot, false},
		{http.StatusPreconditionRequired, false},
		{http.StatusTooManyRequests, false},
		{http.StatusRequestHeaderFieldsTooLarge, false},
		{http.StatusUnavailableForLegalReasons, false},
		{http.StatusInternalServerError, true},
		{http.StatusNotImplemented, true},
		{http.StatusBadGateway, true},
		{http.StatusServiceUnavailable, true},
		{http.StatusGatewayTimeout, true},
		{http.StatusHTTPVersionNotSupported, true},
		{http.StatusNetworkAuthenticationRequired, true},
	}

	for _, tt := range tests {
		t.Run(strconv.Itoa(tt.status), func(t *testing.T) {
			got := isServerError(tt.status)
			if got != tt.expected {
				t.Errorf("isServerError(%v) => %v, expected %v", tt.status, got, tt.expected)
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package upload

import (
	"errors"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

// 创建文件的默认权限，比如Upload.dir若不存在，会使用此权限创建目录。
const defaultMode os.FileMode = 0660

// Upload用于处理文件上传
type Upload struct {
	dir     string   // 上传文件保存的路径根目录
	maxSize int64    // 允许的最大文件大小，以byte为单位
	role    string   // 文件命名方式
	exts    []string // 允许的扩展名
}

// 声明一个Upload对象。
// dir 上传文件的保存目录，若目录不存在，则会尝试创建;
// maxSize 允许上传文件的最大尺寸，单位为byte；
// role 文件命名规则，格式可参考time.Format()参数；
// exts 允许的扩展名，若为空，将不允许任何文件上传。
func New(dir string, maxSize int64, role string, exts ...string) (*Upload, error) {
	// 确保所有的后缀名都是以.作为开始符号的。
	es := make([]string, 0, len(exts))
	for _, ext := range exts {
		if ext[0] != '.' {
			es = append(es, "."+ext)
			continue
		}
		es = append(es, ext)
	}

	// 确保dir最后一个字符为目录分隔符。
	last := dir[len(dir)-1]
	if last != '/' && last != filepath.Separator {
		dir = dir + string(filepath.Separator)
	}

	// 确保dir目录存在，若不存在则会尝试创建。
	stat, err := os.Stat(dir)
	if err != nil && !os.IsExist(err) {
		if !os.IsNotExist(err) {
			return nil, err
		}

		// 尝试创建目录
		if err = os.MkdirAll(dir, 0660); err != nil {
			return nil, err
		}

		// 创建目录成功，重新获取状态
		if stat, err = os.Stat(dir); err != nil {
			return nil, err
		}
	}
	if !stat.IsDir() {
		return nil, errors.New("dir不是一个目录")
	}

	return &Upload{
		dir:     dir,
		maxSize: maxSize,
		role:    role,
		exts:    es,
	}, nil
}

// 判断扩展名是否符合要求。
func (u *Upload) checkExt(ext string) bool {
	if len(ext) == 0 { // 没有扩展名，一律过滤
		return false
	}

	// 是否为允许的扩展名
	for _, e := range u.exts {
		if e == ext {
			return true
		}
	}
	return false
}

// 检测文件大小是否符合要求。
func (u *Upload) checkSize(file multipart.File) (bool, error) {
	var size int64

	switch f := file.(type) {
	case stater:
		stat, err := f.Stat()
		if err != nil {
			return false, err
		}
		size = stat.Size()
	case sizer:
		size = f.Size()
	default:
		return false, errors.New("上传文件时发生未知的错误")
	}

	return size <= u.maxSize, nil
}

// 设置水印，file为水印文件的路径，或是在isText为true时，file为水印的文字。
func (u *Upload) SetWaterMark(file string, isText bool) {
	// TODO
}

// 招行上传的操作。会检测上传文件是否符合要求，只要有一个文件不符合，就会中断上传。
// 返回的是相对于u.dir目录的文件名列表。
func (u *Upload) Do(field string, w *http.ResponseWriter, r *http.Request) ([]string, error) {
	r.ParseMultipartForm(32 << 20)
	heads := r.MultipartForm.File[field]
	ret := make([]string, len(heads))

	for _, head := range heads {
		file, err := head.Open()
		if err != nil {
			return nil, err
		}

		ext := filepath.Ext(head.Filename)
		if !u.checkExt(ext) {
			return nil, errors.New("包含无效的文件类型")
		}

		ok, err := u.checkSize(file)
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, errors.New("超过最大的文件大小")
		}

		path := time.Now().Format(u.role) + ext
		ret = append(ret, path)
		f, err := os.Create(u.dir + path)
		if err != nil {
			return nil, err
		}

		io.Copy(f, file)

		f.Close()
		file.Close() // for的最后关闭file
	}

	return ret, nil
}
<commit_msg>判断io.Copy的返回值<commit_after>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package upload

import (
	"errors"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

// 创建文件的默认权限，比如Upload.dir若不存在，会使用此权限创建目录。
const defaultMode os.FileMode = 0660

// Upload用于处理文件上传
type Upload struct {
	dir     string   // 上传文件保存的路径根目录
	maxSize int64    // 允许的最大文件大小，以byte为单位
	role    string   // 文件命名方式
	exts    []string // 允许的扩展名
}

// 声明一个Upload对象。
// dir 上传文件的保存目录，若目录不存在，则会尝试创建;
// maxSize 允许上传文件的最大尺寸，单位为byte；
// role 文件命名规则，格式可参考time.Format()参数；
// exts 允许的扩展名，若为空，将不允许任何文件上传。
func New(dir string, maxSize int64, role string, exts ...string) (*Upload, error) {
	// 确保所有的后缀名都是以.作为开始符号的。
	es := make([]string, 0, len(exts))
	for _, ext := range exts {
		if ext[0] != '.' {
			es = append(es, "."+ext)
			continue
		}
		es = append(es, ext)
	}

	// 确保dir最后一个字符为目录分隔符。
	last := dir[len(dir)-1]
	if last != '/' && last != filepath.Separator {
		dir = dir + string(filepath.Separator)
	}

	// 确保dir目录存在，若不存在则会尝试创建。
	stat, err := os.Stat(dir)
	if err != nil && !os.IsExist(err) {
		if !os.IsNotExist(err) {
			return nil, err
		}

		// 尝试创建目录
		if err = os.MkdirAll(dir, 0660); err != nil {
			return nil, err
		}

		// 创建目录成功，重新获取状态
		if stat, err = os.Stat(dir); err != nil {
			return nil, err
		}
	}
	if !stat.IsDir() {
		return nil, errors.New("dir不是一个目录")
	}

	return &Upload{
		dir:     dir,
		maxSize: maxSize,
		role:    role,
		exts:    es,
	}, nil
}

// 判断扩展名是否符合要求。
func (u *Upload) checkExt(ext string) bool {
	if len(ext) == 0 { // 没有扩展名，一律过滤
		return false
	}

	// 是否为允许的扩展名
	for _, e := range u.exts {
		if e == ext {
			return true
		}
	}
	return false
}

// 检测文件大小是否符合要求。
func (u *Upload) checkSize(file multipart.File) (bool, error) {
	var size int64

	switch f := file.(type) {
	case stater:
		stat, err := f.Stat()
		if err != nil {
			return false, err
		}
		size = stat.Size()
	case sizer:
		size = f.Size()
	default:
		return false, errors.New("上传文件时发生未知的错误")
	}

	return size <= u.maxSize, nil
}

// 设置水印，file为水印文件的路径，或是在isText为true时，file为水印的文字。
func (u *Upload) SetWaterMark(file string, isText bool) {
	// TODO
}

// 招行上传的操作。会检测上传文件是否符合要求，只要有一个文件不符合，就会中断上传。
// 返回的是相对于u.dir目录的文件名列表。
func (u *Upload) Do(field string, w *http.ResponseWriter, r *http.Request) ([]string, error) {
	r.ParseMultipartForm(32 << 20)
	heads := r.MultipartForm.File[field]
	ret := make([]string, len(heads))

	for _, head := range heads {
		file, err := head.Open()
		if err != nil {
			return nil, err
		}

		ext := filepath.Ext(head.Filename)
		if !u.checkExt(ext) {
			return nil, errors.New("包含无效的文件类型")
		}

		ok, err := u.checkSize(file)
		if err != nil {
			return nil, err
		}
		if !ok {
			return nil, errors.New("超过最大的文件大小")
		}

		path := time.Now().Format(u.role) + ext
		ret = append(ret, path)
		f, err := os.Create(u.dir + path)
		if err != nil {
			return nil, err
		}

		if _, err = io.Copy(f, file); err != nil {
			return nil, err
		}

		// 循环最后关闭所有打开的文件
		f.Close()
		file.Close()
	}

	return ret, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type GreeterHTTPConverter struct {
	srv GreeterServer
}

func NewGreeterHTTPConverter(srv GreeterServer) *GreeterHTTPConverter {
	return &GreeterHTTPConverter{
		srv: srv,
	}
}

func (h *GreeterHTTPConverter) SayHello(cb func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error)) http.HandlerFunc {
	if cb == nil {
		cb = func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error) {
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				p := status.New(codes.Unknown, err.Error()).Proto()
				switch r.Header.Get("Content-Type") {
				case "application/protobuf", "application/x-protobuf":
					buf, err := proto.Marshal(p)
					if err != nil {
						return
					}
					if _, err := io.Copy(w, bytes.NewBuffer(buf)); err != nil {
						return
					}
				case "application/json":
					if err := json.NewEncoder(w).Encode(p); err != nil {
						return
					}
				default:
				}
			}
		}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			cb(ctx, w, r, nil, nil, err)
			return
		}

		arg := &HelloRequest{}

		contentType := r.Header.Get("Content-Type")
		switch contentType {
		case "application/protobuf", "application/x-protobuf":
			if err := proto.Unmarshal(body, arg); err != nil {
				cb(ctx, w, r, nil, nil, err)
				return
			}
		case "application/json":
			if err := jsonpb.Unmarshal(bytes.NewBuffer(body), arg); err != nil {
				cb(ctx, w, r, nil, nil, err)
				return
			}
		default:
			w.WriteHeader(http.StatusUnsupportedMediaType)
			_, err := fmt.Fprintf(w, "Unsupported Content-Type: %s", contentType)
			cb(ctx, w, r, nil, nil, err)
			return
		}

		ret, err := h.srv.SayHello(ctx, arg)
		if err != nil {
			cb(ctx, w, r, arg, nil, err)
			return
		}

		switch contentType {
		case "application/protobuf", "application/x-protobuf":
			buf, err := proto.Marshal(ret)
			if err != nil {
				cb(ctx, w, r, arg, ret, err)
				return
			}
			if _, err := io.Copy(w, bytes.NewBuffer(buf)); err != nil {
				cb(ctx, w, r, arg, ret, err)
				return
			}
		case "application/json":
			if err := json.NewEncoder(w).Encode(ret); err != nil {
				cb(ctx, w, r, arg, ret, err)
				return
			}
		default:
			w.WriteHeader(http.StatusUnsupportedMediaType)
			_, err := fmt.Fprintf(w, "Unsupported Content-Type: %s", contentType)
			cb(ctx, w, r, arg, ret, err)
			return
		}
		cb(ctx, w, r, arg, ret, nil)
	})
}

func (h *GreeterHTTPConverter) SayHelloWithName(cb func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error)) (string, string, http.HandlerFunc) {
	return "Greeter", "SayHello", h.SayHello(cb)
}
<commit_msg>fix examples<commit_after>package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type GreeterHTTPConverter struct {
	srv GreeterServer
}

func NewGreeterHTTPConverter(srv GreeterServer) *GreeterHTTPConverter {
	return &GreeterHTTPConverter{
		srv: srv,
	}
}

func (h *GreeterHTTPConverter) SayHello(cb func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error)) http.HandlerFunc {
	if cb == nil {
		cb = func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error) {
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				p := status.New(codes.Unknown, err.Error()).Proto()
				switch r.Header.Get("Content-Type") {
				case "application/protobuf", "application/x-protobuf":
					buf, err := proto.Marshal(p)
					if err != nil {
						return
					}
					if _, err := io.Copy(w, bytes.NewBuffer(buf)); err != nil {
						return
					}
				case "application/json":
					if err := json.NewEncoder(w).Encode(p); err != nil {
						return
					}
				default:
				}
			}
		}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			cb(ctx, w, r, nil, nil, err)
			return
		}

		arg := &HelloRequest{}

		contentType := r.Header.Get("Content-Type")
		switch contentType {
		case "application/protobuf", "application/x-protobuf":
			if err := proto.Unmarshal(body, arg); err != nil {
				cb(ctx, w, r, nil, nil, err)
				return
			}
		case "application/json":
			if err := jsonpb.Unmarshal(bytes.NewBuffer(body), arg); err != nil {
				cb(ctx, w, r, nil, nil, err)
				return
			}
		default:
			w.WriteHeader(http.StatusUnsupportedMediaType)
			_, err := fmt.Fprintf(w, "Unsupported Content-Type: %s", contentType)
			cb(ctx, w, r, nil, nil, err)
			return
		}

		ret, err := h.srv.SayHello(ctx, arg)
		if err != nil {
			cb(ctx, w, r, arg, nil, err)
			return
		}

		switch contentType {
		case "application/protobuf", "application/x-protobuf":
			buf, err := proto.Marshal(ret)
			if err != nil {
				cb(ctx, w, r, arg, ret, err)
				return
			}
			if _, err := io.Copy(w, bytes.NewBuffer(buf)); err != nil {
				cb(ctx, w, r, arg, ret, err)
				return
			}
		case "application/json":
			m := jsonpb.Marshaler{
				EnumsAsInts:  true,
				EmitDefaults: true,
			}
			if err := m.Marshal(w, ret); err != nil {
				cb(ctx, w, r, arg, ret, err)
				return
			}
		default:
			w.WriteHeader(http.StatusUnsupportedMediaType)
			_, err := fmt.Fprintf(w, "Unsupported Content-Type: %s", contentType)
			cb(ctx, w, r, arg, ret, err)
			return
		}
		cb(ctx, w, r, arg, ret, nil)
	})
}

func (h *GreeterHTTPConverter) SayHelloWithName(cb func(ctx context.Context, w http.ResponseWriter, r *http.Request, arg, ret proto.Message, err error)) (string, string, http.HandlerFunc) {
	return "Greeter", "SayHello", h.SayHello(cb)
}
<|endoftext|>


<commit_before>package box

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"regexp"
	"strings"

	"github.com/mattn/go-runewidth"
)

var AnsiCutter = regexp.MustCompile("\x1B[^a-zA-Z]*[A-Za-z]")

func Print(ctx context.Context, nodes []string, out io.Writer) bool {
	b := New()
	b.Height = 0
	value, _, _ := b.Print(ctx, nodes, 0, out)
	return value
}

func (b *box_t) Print(ctx context.Context,
	nodes []string,
	offset int,
	out io.Writer) (bool, int, int) {

	selected, columns, nlines := b.PrintNoLastLineFeed(ctx, nodes, offset, out)

	// append last linefeed.
	if nlines > 0 {
		fmt.Fprintln(out)
	}
	b.Cache = nil
	return selected, columns, nlines
}

func (b *box_t) PrintNoLastLineFeed(ctx context.Context,
	nodes []string,
	offset int,
	out io.Writer) (bool, int, int) {

	if len(nodes) <= 0 {
		return true, 0, 0
	}

	maxLen := 1
	for _, finfo := range nodes {
		length := runewidth.StringWidth(AnsiCutter.ReplaceAllString(finfo, ""))
		if length > maxLen {
			maxLen = length
		}
	}
	nodePerLine := (b.Width - 1) / (maxLen + 1)
	if nodePerLine <= 0 {
		nodePerLine = 1
	}
	nlines := (len(nodes) + nodePerLine - 1) / nodePerLine

	lines := make([][]byte, nlines)
	row := 0
	for _, finfo := range nodes {
		lines[row] = append(lines[row], finfo...)
		w := runewidth.StringWidth(AnsiCutter.ReplaceAllString(finfo, ""))
		if maxLen < b.Width {
			for i := maxLen + 1; i > w; i-- {
				lines[row] = append(lines[row], ' ')
			}
		}
		row++
		if row >= nlines {
			row = 0
		}
	}
	i_end := len(lines)
	if b.Height > 0 {
		if i_end >= offset+b.Height {
			i_end = offset + b.Height
		}
	}

	if b.Cache == nil {
		b.Cache = make([][]byte, b.Height)
	}
	i := offset
	y := 0
	for {
		if y >= len(b.Cache) {
			b.Cache = append(b.Cache, []byte{})
		}
		// assertion
		if i >= len(lines) {
			panic(fmt.Sprintf("len(lines)==%d i==%d", len(lines), i))
		}
		if bytes.Compare(lines[i], b.Cache[y]) != 0 {
			fmt.Fprint(out, strings.TrimRight(string(lines[i]), " "))
			if len(b.Cache[y]) > 0 {
				fmt.Fprint(out, ERASE_LINE)
			}
			b.Cache[y] = lines[i]
		}
		y++
		if ctx != nil {
			select {
			case <-ctx.Done():
				return false, nodePerLine, nlines
			default:
			}
		}
		i++
		if i >= i_end {
			break
		}
		fmt.Fprintln(out)
	}
	return true, nodePerLine, nlines
}

const (
	CURSOR_OFF = "\x1B[?25l"
	CURSOR_ON  = "\x1B[?25h"
	BOLD_ON    = "\x1B[0;47;30m"
	BOLD_OFF   = "\x1B[0m"
	UP_N       = "\x1B[%dA"
	ERASE_LINE = "\x1B[0K"
)

func truncate(s string, w int) string {
	return runewidth.Truncate(strings.TrimSpace(s), w, "")
}

const (
	NONE  = 0
	LEFT  = 1
	DOWN  = 2
	UP    = 3
	RIGHT = 4
	ENTER = 5
	LEAVE = 6
)

type nodeT struct {
	Index int
	Text  string
}

// Choice returns selected string
func Choice(sources []string, out io.Writer) string {
	n := Choose(sources, out)
	if n < 0 {
		return ""
	}
	return sources[n]
}

// Choice returns the index of selected string
func Choose(sources []string, out io.Writer) int {
	cursor := 0
	nodes := make([]*nodeT, 0, len(sources))
	draws := make([]string, 0, len(sources))
	b := New()
	defer b.Close()
	for i, text := range sources {
		val := truncate(text, b.Width-1)
		if val != "" {
			nodes = append(nodes, &nodeT{Index: i, Text: val})
			draws = append(draws, val)
		}
	}
	io.WriteString(out, CURSOR_OFF)
	defer io.WriteString(out, CURSOR_ON)

	if len(nodes) <= 0 {
		nodes = []*nodeT{&nodeT{-1, ""}}
		draws = []string{""}
	}

	offset := 0
	for {
		draws[cursor] = BOLD_ON + truncate(nodes[cursor].Text, b.Width-2) + BOLD_OFF
		status, _, h := b.PrintNoLastLineFeed(nil, draws, offset, out)
		if !status {
			return -1
		}
		draws[cursor] = truncate(nodes[cursor].Text, b.Width-2)
		last := cursor
		for last == cursor {
			if bw, ok := out.(*bufio.Writer); ok {
				bw.Flush()
			}
			switch b.GetCmd() {
			case LEFT:
				cursor = (cursor + len(nodes) - h) % len(nodes)
			case RIGHT:
				cursor = (cursor + h) % len(nodes)
			case DOWN:
				cursor++
				if cursor >= len(nodes) {
					cursor = 0
				}
			case UP:
				cursor--
				if cursor < 0 {
					cursor = len(nodes) - 1
				}
			case ENTER:
				return nodes[cursor].Index
			case LEAVE:
				return -1
			}

			// x := cursor / h
			y := cursor % h
			if y < offset {
				offset = y
				// offset--
			} else if y >= offset+b.Height {
				offset = y - b.Height + 1
				// offset++
			}
		}
		if h < b.Height {
			fmt.Fprintf(out, UP_N+"\r", h-1)
		} else {
			fmt.Fprintf(out, UP_N+"\r", b.Height-1)
		}
	}
}
<commit_msg>Do not use ESC[0A for https://github.com/zetamatta/nyagos/issues/364<commit_after>package box

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"regexp"
	"strings"

	"github.com/mattn/go-runewidth"
)

var AnsiCutter = regexp.MustCompile("\x1B[^a-zA-Z]*[A-Za-z]")

func Print(ctx context.Context, nodes []string, out io.Writer) bool {
	b := New()
	b.Height = 0
	value, _, _ := b.Print(ctx, nodes, 0, out)
	return value
}

func (b *box_t) Print(ctx context.Context,
	nodes []string,
	offset int,
	out io.Writer) (bool, int, int) {

	selected, columns, nlines := b.PrintNoLastLineFeed(ctx, nodes, offset, out)

	// append last linefeed.
	if nlines > 0 {
		fmt.Fprintln(out)
	}
	b.Cache = nil
	return selected, columns, nlines
}

func (b *box_t) PrintNoLastLineFeed(ctx context.Context,
	nodes []string,
	offset int,
	out io.Writer) (bool, int, int) {

	if len(nodes) <= 0 {
		return true, 0, 0
	}

	maxLen := 1
	for _, finfo := range nodes {
		length := runewidth.StringWidth(AnsiCutter.ReplaceAllString(finfo, ""))
		if length > maxLen {
			maxLen = length
		}
	}
	nodePerLine := (b.Width - 1) / (maxLen + 1)
	if nodePerLine <= 0 {
		nodePerLine = 1
	}
	nlines := (len(nodes) + nodePerLine - 1) / nodePerLine

	lines := make([][]byte, nlines)
	row := 0
	for _, finfo := range nodes {
		lines[row] = append(lines[row], finfo...)
		w := runewidth.StringWidth(AnsiCutter.ReplaceAllString(finfo, ""))
		if maxLen < b.Width {
			for i := maxLen + 1; i > w; i-- {
				lines[row] = append(lines[row], ' ')
			}
		}
		row++
		if row >= nlines {
			row = 0
		}
	}
	i_end := len(lines)
	if b.Height > 0 {
		if i_end >= offset+b.Height {
			i_end = offset + b.Height
		}
	}

	if b.Cache == nil {
		b.Cache = make([][]byte, b.Height)
	}
	i := offset
	y := 0
	for {
		if y >= len(b.Cache) {
			b.Cache = append(b.Cache, []byte{})
		}
		// assertion
		if i >= len(lines) {
			panic(fmt.Sprintf("len(lines)==%d i==%d", len(lines), i))
		}
		if bytes.Compare(lines[i], b.Cache[y]) != 0 {
			fmt.Fprint(out, strings.TrimRight(string(lines[i]), " "))
			if len(b.Cache[y]) > 0 {
				fmt.Fprint(out, ERASE_LINE)
			}
			b.Cache[y] = lines[i]
		}
		y++
		if ctx != nil {
			select {
			case <-ctx.Done():
				return false, nodePerLine, nlines
			default:
			}
		}
		i++
		if i >= i_end {
			break
		}
		fmt.Fprintln(out)
	}
	return true, nodePerLine, nlines
}

const (
	CURSOR_OFF = "\x1B[?25l"
	CURSOR_ON  = "\x1B[?25h"
	BOLD_ON    = "\x1B[0;47;30m"
	BOLD_OFF   = "\x1B[0m"
	UP_N       = "\x1B[%dA"
	ERASE_LINE = "\x1B[0K"
)

func truncate(s string, w int) string {
	return runewidth.Truncate(strings.TrimSpace(s), w, "")
}

const (
	NONE  = 0
	LEFT  = 1
	DOWN  = 2
	UP    = 3
	RIGHT = 4
	ENTER = 5
	LEAVE = 6
)

type nodeT struct {
	Index int
	Text  string
}

// Choice returns selected string
func Choice(sources []string, out io.Writer) string {
	n := Choose(sources, out)
	if n < 0 {
		return ""
	}
	return sources[n]
}

// Choice returns the index of selected string
func Choose(sources []string, out io.Writer) int {
	cursor := 0
	nodes := make([]*nodeT, 0, len(sources))
	draws := make([]string, 0, len(sources))
	b := New()
	defer b.Close()
	for i, text := range sources {
		val := truncate(text, b.Width-1)
		if val != "" {
			nodes = append(nodes, &nodeT{Index: i, Text: val})
			draws = append(draws, val)
		}
	}
	io.WriteString(out, CURSOR_OFF)
	defer io.WriteString(out, CURSOR_ON)

	if len(nodes) <= 0 {
		nodes = []*nodeT{&nodeT{-1, ""}}
		draws = []string{""}
	}

	offset := 0
	for {
		draws[cursor] = BOLD_ON + truncate(nodes[cursor].Text, b.Width-2) + BOLD_OFF
		status, _, h := b.PrintNoLastLineFeed(nil, draws, offset, out)
		if !status {
			return -1
		}
		draws[cursor] = truncate(nodes[cursor].Text, b.Width-2)
		last := cursor
		for last == cursor {
			if bw, ok := out.(*bufio.Writer); ok {
				bw.Flush()
			}
			switch b.GetCmd() {
			case LEFT:
				cursor = (cursor + len(nodes) - h) % len(nodes)
			case RIGHT:
				cursor = (cursor + h) % len(nodes)
			case DOWN:
				cursor++
				if cursor >= len(nodes) {
					cursor = 0
				}
			case UP:
				cursor--
				if cursor < 0 {
					cursor = len(nodes) - 1
				}
			case ENTER:
				return nodes[cursor].Index
			case LEAVE:
				return -1
			}

			// x := cursor / h
			y := cursor % h
			if y < offset {
				offset = y
				// offset--
			} else if y >= offset+b.Height {
				offset = y - b.Height + 1
				// offset++
			}
		}
		if h < b.Height {
			if h > 1 {
				fmt.Fprintf(out, UP_N, h-1)
			}
		} else {
			if b.Height > 1 {
				fmt.Fprintf(out, UP_N, b.Height-1)
			}
		}
		fmt.Fprint(out, "\r")
	}
}
<|endoftext|>


<commit_before>package persistent

type vectorNode []interface{}

func newVectorNode() vectorNode {
	return vectorNode(make([]interface{}, nodeCap))
}

func (n vectorNode) clone() vectorNode {
	m := newVectorNode()
	copy(m, n)
	return m
}

// Vector is an implementation of persistent vector. &Vector{} is a valid empty
// vector.
type Vector struct {
	count uint
	// height of the tree structure, defined to be 0 when root is a leaf.
	height uint
	root   vectorNode
	tail   []interface{}
}

// Count returns the number of elements in a Vector.
func (v *Vector) Count() uint {
	return v.count
}

// tailoff returns the number of elements not stored in tail.
func (v *Vector) tailoff() uint {
	if v.count < tailMaxLen {
		return 0
	}
	return ((v.count - 1) >> bitChunk) << bitChunk
}

// sliceFor returns the slice where the i-th element is stored.
func (v *Vector) sliceFor(i uint) []interface{} {
	if i < 0 || i >= v.count {
		return nil
	}
	if i >= v.tailoff() {
		return v.tail
	}
	n := v.root
	for shift := v.height * bitChunk; shift > 0; shift -= bitChunk {
		n = n[(i>>shift)&mask].(vectorNode)
	}
	return n
}

// Nth returns the i-th element.
func (v *Vector) Nth(i uint) interface{} {
	return v.sliceFor(i)[i&mask]
}

// AssocN returns a new Vector with the i-th element replaced by val.
func (v *Vector) AssocN(i uint, val interface{}) *Vector {
	if i < 0 || i > v.count {
		return nil
	} else if i == v.count {
		return v.Cons(val)
	}
	if i >= v.tailoff() {
		newTail := make([]interface{}, len(v.tail))
		copy(newTail, v.tail)
		newTail[i&mask] = val
		return &Vector{v.count, v.height, v.root, newTail}
	}
	return &Vector{v.count, v.height, doAssoc(v.height, v.root, i, val), v.tail}
}

// doAssoc returns a new tree with the i-th element replaced by val.
func doAssoc(height uint, n vectorNode, i uint, val interface{}) vectorNode {
	m := n.clone()
	if height == 0 {
		m[i&mask] = val
	} else {
		sub := (i >> (height * bitChunk)) & mask
		m[sub] = doAssoc(height-1, m[sub].(vectorNode), i, val)
	}
	return m
}

// Cons returns a new Vector with val appended to the end.
func (v *Vector) Cons(val interface{}) *Vector {
	// Room in tail?
	if v.count-v.tailoff() < tailMaxLen {
		newTail := make([]interface{}, len(v.tail)+1)
		copy(newTail, v.tail)
		newTail[len(v.tail)] = val
		return &Vector{v.count + 1, v.height, v.root, newTail}
	}
	// Full tail; push into tree.
	tailNode := vectorNode(v.tail)
	newHeight := v.height
	var newRoot vectorNode
	// Overflow root?
	if (v.count >> bitChunk) > (1 << (v.height * bitChunk)) {
		newRoot = newVectorNode()
		newRoot[0] = v.root
		newRoot[1] = newPath(v.height, tailNode)
		newHeight++
	} else {
		newRoot = v.pushTail(v.height, v.root, tailNode)
	}
	return &Vector{v.count + 1, newHeight, newRoot, []interface{}{val}}
}

// pushTail returns a tree with tail appended.
func (v *Vector) pushTail(height uint, n vectorNode, tail vectorNode) vectorNode {
	if height == 0 {
		return tail
	}
	idx := ((v.count - 1) >> (height * 5)) & mask
	m := n.clone()
	child := n[idx]
	if child == nil {
		m[idx] = newPath(height-1, tail)
	} else {
		m[idx] = v.pushTail(height-1, child.(vectorNode), tail)
	}
	return m
}

// newPath creates a left-branching tree of specified height and leaf.
func newPath(height uint, leaf vectorNode) vectorNode {
	if height == 0 {
		return leaf
	}
	ret := newVectorNode()
	ret[0] = newPath(height-1, leaf)
	return ret
}
<commit_msg>Eliminate a magic number<commit_after>package persistent

type vectorNode []interface{}

func newVectorNode() vectorNode {
	return vectorNode(make([]interface{}, nodeCap))
}

func (n vectorNode) clone() vectorNode {
	m := newVectorNode()
	copy(m, n)
	return m
}

// Vector is an implementation of persistent vector. &Vector{} is a valid empty
// vector.
type Vector struct {
	count uint
	// height of the tree structure, defined to be 0 when root is a leaf.
	height uint
	root   vectorNode
	tail   []interface{}
}

// Count returns the number of elements in a Vector.
func (v *Vector) Count() uint {
	return v.count
}

// tailoff returns the number of elements not stored in tail.
func (v *Vector) tailoff() uint {
	if v.count < tailMaxLen {
		return 0
	}
	return ((v.count - 1) >> bitChunk) << bitChunk
}

// sliceFor returns the slice where the i-th element is stored.
func (v *Vector) sliceFor(i uint) []interface{} {
	if i < 0 || i >= v.count {
		return nil
	}
	if i >= v.tailoff() {
		return v.tail
	}
	n := v.root
	for shift := v.height * bitChunk; shift > 0; shift -= bitChunk {
		n = n[(i>>shift)&mask].(vectorNode)
	}
	return n
}

// Nth returns the i-th element.
func (v *Vector) Nth(i uint) interface{} {
	return v.sliceFor(i)[i&mask]
}

// AssocN returns a new Vector with the i-th element replaced by val.
func (v *Vector) AssocN(i uint, val interface{}) *Vector {
	if i < 0 || i > v.count {
		return nil
	} else if i == v.count {
		return v.Cons(val)
	}
	if i >= v.tailoff() {
		newTail := make([]interface{}, len(v.tail))
		copy(newTail, v.tail)
		newTail[i&mask] = val
		return &Vector{v.count, v.height, v.root, newTail}
	}
	return &Vector{v.count, v.height, doAssoc(v.height, v.root, i, val), v.tail}
}

// doAssoc returns a new tree with the i-th element replaced by val.
func doAssoc(height uint, n vectorNode, i uint, val interface{}) vectorNode {
	m := n.clone()
	if height == 0 {
		m[i&mask] = val
	} else {
		sub := (i >> (height * bitChunk)) & mask
		m[sub] = doAssoc(height-1, m[sub].(vectorNode), i, val)
	}
	return m
}

// Cons returns a new Vector with val appended to the end.
func (v *Vector) Cons(val interface{}) *Vector {
	// Room in tail?
	if v.count-v.tailoff() < tailMaxLen {
		newTail := make([]interface{}, len(v.tail)+1)
		copy(newTail, v.tail)
		newTail[len(v.tail)] = val
		return &Vector{v.count + 1, v.height, v.root, newTail}
	}
	// Full tail; push into tree.
	tailNode := vectorNode(v.tail)
	newHeight := v.height
	var newRoot vectorNode
	// Overflow root?
	if (v.count >> bitChunk) > (1 << (v.height * bitChunk)) {
		newRoot = newVectorNode()
		newRoot[0] = v.root
		newRoot[1] = newPath(v.height, tailNode)
		newHeight++
	} else {
		newRoot = v.pushTail(v.height, v.root, tailNode)
	}
	return &Vector{v.count + 1, newHeight, newRoot, []interface{}{val}}
}

// pushTail returns a tree with tail appended.
func (v *Vector) pushTail(height uint, n vectorNode, tail vectorNode) vectorNode {
	if height == 0 {
		return tail
	}
	idx := ((v.count - 1) >> (height * bitChunk)) & mask
	m := n.clone()
	child := n[idx]
	if child == nil {
		m[idx] = newPath(height-1, tail)
	} else {
		m[idx] = v.pushTail(height-1, child.(vectorNode), tail)
	}
	return m
}

// newPath creates a left-branching tree of specified height and leaf.
func newPath(height uint, leaf vectorNode) vectorNode {
	if height == 0 {
		return leaf
	}
	ret := newVectorNode()
	ret[0] = newPath(height-1, leaf)
	return ret
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "git.lukas.moe/sn0w/Karen/cache"
    "git.lukas.moe/sn0w/Karen/helpers"
    Logger "git.lukas.moe/sn0w/Karen/logger"
    "git.lukas.moe/sn0w/Karen/metrics"
    "git.lukas.moe/sn0w/Karen/modules"
    "git.lukas.moe/sn0w/Karen/ratelimits"
    "github.com/bwmarrin/discordgo"
    "github.com/getsentry/raven-go"
    "regexp"
    "strings"
    "time"
)

// BotOnReady gets called after the gateway connected
func BotOnReady(session *discordgo.Session, event *discordgo.Ready) {
    Logger.INFO.L("bot", "Connected to discord!")
    Logger.VERBOSE.L("bot", "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)

    // 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,
                "",
            )
        }
    }()
}

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

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

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

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

    // We only do things in guilds.
    // Get a friend already and stop chatting with bots
    if channel.IsPrivate {
        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.Trim(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.Split(
                    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
        }
    }

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

    // Split the message into parts
    parts := strings.Split(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.Replace(message.Content, prefix+cmd, "", -1)

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

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

    // Else exit
    return
}

func sendHelp(message *discordgo.MessageCreate) {
    cache.GetSession().ChannelMessageSend(
        message.ChannelID,
        helpers.GetTextF("bot.help", message.Author.ID),
    )
}

// Changes the game interval every 10 seconds after called
func changeGameInterval(session *discordgo.Session) {
    for {
        err := session.UpdateStatus(0, helpers.GetText("games"))
        if err != nil {
            raven.CaptureError(err, map[string]string{})
        }

        time.Sleep(10 * time.Second)
    }
}
<commit_msg>Removed useless return statement<commit_after>package main

import (
    "fmt"
    "git.lukas.moe/sn0w/Karen/cache"
    "git.lukas.moe/sn0w/Karen/helpers"
    Logger "git.lukas.moe/sn0w/Karen/logger"
    "git.lukas.moe/sn0w/Karen/metrics"
    "git.lukas.moe/sn0w/Karen/modules"
    "git.lukas.moe/sn0w/Karen/ratelimits"
    "github.com/bwmarrin/discordgo"
    "github.com/getsentry/raven-go"
    "regexp"
    "strings"
    "time"
)

// BotOnReady gets called after the gateway connected
func BotOnReady(session *discordgo.Session, event *discordgo.Ready) {
    Logger.INFO.L("bot", "Connected to discord!")
    Logger.VERBOSE.L("bot", "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)

    // 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,
                "",
            )
        }
    }()
}

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

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

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

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

    // We only do things in guilds.
    // Get a friend already and stop chatting with bots
    if channel.IsPrivate {
        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.Trim(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.Split(
                    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
        }
    }

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

    // Split the message into parts
    parts := strings.Split(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.Replace(message.Content, prefix+cmd, "", -1)

    // 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 sendHelp(message *discordgo.MessageCreate) {
    cache.GetSession().ChannelMessageSend(
        message.ChannelID,
        helpers.GetTextF("bot.help", message.Author.ID),
    )
}

// Changes the game interval every 10 seconds after called
func changeGameInterval(session *discordgo.Session) {
    for {
        err := session.UpdateStatus(0, helpers.GetText("games"))
        if err != nil {
            raven.CaptureError(err, map[string]string{})
        }

        time.Sleep(10 * time.Second)
    }
}
<|endoftext|>


<commit_before>package main

import(
        "flag"
        "github.com/thoj/go-ircevent"
        "log"
        "strings"
        "regexp"
)

//var url_re = regexp.MustCompile(`\b(?:ht|f)tps?://[a-zA-Z0-9%_\+-]+\?[a-zA-Z0-9%_\+-=&]+\b`)
var url_re = regexp.MustCompile(`\bhttps?://[^\s]+\b`)

func logprivmsgs(event *irc.Event){
        log.Print(event.Nick + ": ", event.Arguments)
}
func writeurltitle(event *irc.Event){
        //var url string
        log.Print(url_re.FindAllString(event.Arguments[1], 5))
}

func FindURLs(input string) []string {
        return url_re.FindAllString(input, -1)
}

func main(){
        var myircbot *irc.Connection
        var botname *string
        var serveraddress *string
        var rawchannellist *string
        var channellist []string
        var channelname string
        var err error

        botname = flag.String("botname", "justanotherbot", "Name of the bot")
        serveraddress = flag.String("server-address", "irc.freenode.org:6667", "Server Address")
        rawchannellist = flag.String("channels", "#ancient-solutions", "List of channels")
        flag.Parse()

        channellist = strings.Split(*rawchannellist, ",")

        myircbot = irc.IRC(*botname, *botname)
        if err = myircbot.Connect(*serveraddress); err != nil {
                log.Fatal("Error connecting to server: ", err)
        }

        //Join all channels.
        for _, channelname = range channellist {
                myircbot.Join(channelname)
        }

        //Event handling
        myircbot.AddCallback("PRIVMSG", logprivmsgs)
        myircbot.AddCallback("PRIVMSG", writeurltitle)

        myircbot.Loop()
}
<commit_msg>validate found urls<commit_after>package main

import (
	"flag"
	"github.com/thoj/go-ircevent"
	"log"
	"net/url"
	"regexp"
	"strings"
)

var url_re = regexp.MustCompile(`\bhttps?://[^\s]+\b`)

func logprivmsgs(event *irc.Event) {
	log.Print(event.Nick+": ", event.Arguments)
}

func writeurltitle(event *irc.Event) {
	var urls []string = FindURLs(event.Arguments[1])
        var err error

        // URL valid?
        for _, oneurl := range urls {
                _, err = url.Parse(oneurl)
                if err != nil {
                        continue
                }
                log.Print(oneurl)
        }
}

func FindURLs(input string) []string {
	return url_re.FindAllString(input, 5)
}

func main() {
	var myircbot *irc.Connection
	var botname *string
	var serveraddress *string
	var rawchannellist *string
	var channellist []string
	var channelname string
	var err error

	botname = flag.String("botname", "justanotherbot", "Name of the bot")
	serveraddress = flag.String("server-address", "irc.freenode.org:6667", "Server Address")
	rawchannellist = flag.String("channels", "#ancient-solutions", "List of channels")
	flag.Parse()

	channellist = strings.Split(*rawchannellist, ",")

	myircbot = irc.IRC(*botname, *botname)
	if err = myircbot.Connect(*serveraddress); err != nil {
		log.Fatal("Error connecting to server: ", err)
	}

	//Join all channels.
	for _, channelname = range channellist {
		myircbot.Join(channelname)
	}

	//Event handling
	myircbot.AddCallback("PRIVMSG", logprivmsgs)
	myircbot.AddCallback("PRIVMSG", writeurltitle)

	myircbot.Loop()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/url"
	"os"

	"github.com/ajm188/slack"
	"github.com/ajm188/slack/plugins/github"
)

const (
	version = "0.4.1"
)

func setRealNameFields(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	channel := event["channel"].(string)
	if channel != bot.Channels["general"] {
		return nil, slack.Continue
	}
	userID := event["user"].(string)
	dmChan := make(chan string)
	userChan := make(chan interface{})
	go func() {
		dm, _ := bot.OpenDirectMessage(userID)
		dmChan <- dm
	}()
	go func() {
		payload, _ := bot.Call("users.info", url.Values{"user": []string{userID}})
		userChan <- payload
	}()
	payload := (<-userChan).(map[string]interface{})
	success := payload["ok"].(bool)
	if !success {
		fmt.Println(payload)
		return nil, slack.Continue
	}
	user := payload["user"].(map[string]interface{})
	nick := user["name"].(string)
	text := "Please set your real name fields. https://hacsoc.slack.com/team/%s."
	text += " Then click \"Edit\"."
	text = fmt.Sprintf(text, nick)
	dm := <-dmChan
	return slack.NewMessage(text, dm), slack.Continue
}

func getSlackId(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	user := event["user"].(string)
	return bot.Mention(user, "Your slack id is `" + user + "`.",
		event["channel"].(string)), slack.Continue
}

func sendDM(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	user := event["user"].(string)
	return bot.DirectMessage(user, "hi"), slack.Continue
}

func troll(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	user, ok := event["user"]
	if !ok || user.(string) != bot.Users["catofnostalgia"].ID {
		return nil, slack.Continue
	}

	return bot.Mention(user.(string), "where is the third lambda?",
		event["channel"].(string)), slack.Continue
}

func configureGithubPlugin(id, secret, token string) {
	github.ClientID = id
	github.ClientSecret = secret
	github.AccessToken = token

	github.SharedClient = github.DefaultClient()
}

func getEnvvar(name string) (envvar string) {
	envvar = os.Getenv(name)
	if envvar == "" {
		fmt.Println("Missing environment variable %s", name)
		os.Exit(1)
	}
	return
}

func main() {
	token := getEnvvar("SLACKSOC_TOKEN")

	ghClientID := getEnvvar("GH_CLIENT_ID")
	ghClientSecret := getEnvvar("GH_CLIENT_SECRET")
	ghAccessToken := getEnvvar("GH_ACCESS_TOKEN")

	bot := slack.NewBot(token)
	bot.Respond("hi\\z", slack.Respond("hi there!"))
	bot.Respond("pm me", sendDM)
	bot.Respond("((what's)|(tell me) your)? ?version??",
		slack.Respond(fmt.Sprintf("My version is %s. My lib version is %s", version, slack.Version)))
	bot.Respond("((what's)|(tell me))? ?my id??", getSlackId)
	bot.Listen("gentoo", slack.React("funroll-loops"))
	bot.Listen(".+\\bslacksoc\\b", slack.React("raisedeyebrow"))
	bot.Listen("GNU/Linux", slack.React("stallman"))
	bot.OnEvent("message", troll)
	bot.OnEventWithSubtype("message", "channel_join", setRealNameFields)

	configureGithubPlugin(ghClientID, ghClientSecret, ghAccessToken)
	github.OpenIssue(bot, nil)

	fmt.Println("Starting bot")
	if err := bot.Start(); err != nil {
		fmt.Println(err)
	}
}
<commit_msg>Plz stop the trollz<commit_after>package main

import (
	"fmt"
	"net/url"
	"os"

	"github.com/ajm188/slack"
	"github.com/ajm188/slack/plugins/github"
)

const (
	version = "0.4.1"
)

func setRealNameFields(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	channel := event["channel"].(string)
	if channel != bot.Channels["general"] {
		return nil, slack.Continue
	}
	userID := event["user"].(string)
	dmChan := make(chan string)
	userChan := make(chan interface{})
	go func() {
		dm, _ := bot.OpenDirectMessage(userID)
		dmChan <- dm
	}()
	go func() {
		payload, _ := bot.Call("users.info", url.Values{"user": []string{userID}})
		userChan <- payload
	}()
	payload := (<-userChan).(map[string]interface{})
	success := payload["ok"].(bool)
	if !success {
		fmt.Println(payload)
		return nil, slack.Continue
	}
	user := payload["user"].(map[string]interface{})
	nick := user["name"].(string)
	text := "Please set your real name fields. https://hacsoc.slack.com/team/%s."
	text += " Then click \"Edit\"."
	text = fmt.Sprintf(text, nick)
	dm := <-dmChan
	return slack.NewMessage(text, dm), slack.Continue
}

func getSlackId(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	user := event["user"].(string)
	return bot.Mention(user, "Your slack id is `" + user + "`.",
		event["channel"].(string)), slack.Continue
}

func sendDM(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	user := event["user"].(string)
	return bot.DirectMessage(user, "hi"), slack.Continue
}

func configureGithubPlugin(id, secret, token string) {
	github.ClientID = id
	github.ClientSecret = secret
	github.AccessToken = token

	github.SharedClient = github.DefaultClient()
}

func getEnvvar(name string) (envvar string) {
	envvar = os.Getenv(name)
	if envvar == "" {
		fmt.Println("Missing environment variable %s", name)
		os.Exit(1)
	}
	return
}

func main() {
	token := getEnvvar("SLACKSOC_TOKEN")

	ghClientID := getEnvvar("GH_CLIENT_ID")
	ghClientSecret := getEnvvar("GH_CLIENT_SECRET")
	ghAccessToken := getEnvvar("GH_ACCESS_TOKEN")

	bot := slack.NewBot(token)
	bot.Respond("hi\\z", slack.Respond("hi there!"))
	bot.Respond("pm me", sendDM)
	bot.Respond("((what's)|(tell me) your)? ?version??",
		slack.Respond(fmt.Sprintf("My version is %s. My lib version is %s", version, slack.Version)))
	bot.Respond("((what's)|(tell me))? ?my id??", getSlackId)
	bot.Listen("gentoo", slack.React("funroll-loops"))
	bot.Listen(".+\\bslacksoc\\b", slack.React("raisedeyebrow"))
	bot.Listen("GNU/Linux", slack.React("stallman"))
	bot.OnEvent("message", troll)
	bot.OnEventWithSubtype("message", "channel_join", setRealNameFields)

	configureGithubPlugin(ghClientID, ghClientSecret, ghAccessToken)
	github.OpenIssue(bot, nil)

	fmt.Println("Starting bot")
	if err := bot.Start(); err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
    "github.com/Seklfreak/Robyul2/cache"
    "github.com/Seklfreak/Robyul2/helpers"
    Logger "github.com/Seklfreak/Robyul2/logger"
    "github.com/Seklfreak/Robyul2/metrics"
    "github.com/Seklfreak/Robyul2/modules"
    "github.com/Seklfreak/Robyul2/ratelimits"
    "github.com/getsentry/raven-go"
    "github.com/bwmarrin/discordgo"
    "regexp"
    "strings"
    "time"
    "github.com/Seklfreak/Robyul2/emojis"
    "fmt"
    "os/user"
)

// BotOnReady gets called after the gateway connected
func BotOnReady(session *discordgo.Session, event *discordgo.Ready) {
    Logger.INFO.L("bot", "Connected to discord!")
    Logger.VERBOSE.L("bot", "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)

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

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

    // We only do things in guilds.
    // Get a friend already and stop chatting with bots
    if channel.IsPrivate {
        // 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))

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

    // Check if a trigger matches
    modules.CallTriggerPlugin(cmd, content, message.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 := session.State.Channel(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) {
    cache.GetSession().ChannelMessageSend(
        message.ChannelID,
        helpers.GetTextF("bot.help", message.Author.ID),
    )
}

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

        for _, guild := range guilds {
            lastAfterMemberId := ""
            for {
                members, err := session.GuildMembers(guild.ID, lastAfterMemberId, 1000)
                if len(members) <= 0 {
                    break
                }
                lastAfterMemberId = members[len(members)-1].User.ID
                helpers.Relax(err)
                for _, u := range members {
                    users[u.User.ID] = u.User.Username
                }
            }
        }

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

        time.Sleep(1 * time.Hour)
    }
}
<commit_msg>fix imports<commit_after>package main

import (
    "github.com/Seklfreak/Robyul2/cache"
    "github.com/Seklfreak/Robyul2/helpers"
    Logger "github.com/Seklfreak/Robyul2/logger"
    "github.com/Seklfreak/Robyul2/metrics"
    "github.com/Seklfreak/Robyul2/modules"
    "github.com/Seklfreak/Robyul2/ratelimits"
    "github.com/getsentry/raven-go"
    "github.com/bwmarrin/discordgo"
    "regexp"
    "strings"
    "time"
    "github.com/Seklfreak/Robyul2/emojis"
    "fmt"
)

// BotOnReady gets called after the gateway connected
func BotOnReady(session *discordgo.Session, event *discordgo.Ready) {
    Logger.INFO.L("bot", "Connected to discord!")
    Logger.VERBOSE.L("bot", "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)

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

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

    // We only do things in guilds.
    // Get a friend already and stop chatting with bots
    if channel.IsPrivate {
        // 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))

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

    // Check if a trigger matches
    modules.CallTriggerPlugin(cmd, content, message.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 := session.State.Channel(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) {
    cache.GetSession().ChannelMessageSend(
        message.ChannelID,
        helpers.GetTextF("bot.help", message.Author.ID),
    )
}

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

        for _, guild := range guilds {
            lastAfterMemberId := ""
            for {
                members, err := session.GuildMembers(guild.ID, lastAfterMemberId, 1000)
                if len(members) <= 0 {
                    break
                }
                lastAfterMemberId = members[len(members)-1].User.ID
                helpers.Relax(err)
                for _, u := range members {
                    users[u.User.ID] = u.User.Username
                }
            }
        }

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

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


<commit_before>package main

import (
	"fmt"
	"net/url"
	"os"

	"github.com/ajm188/slack"
)

const (
	tokenVar     = "SLACKSOC_TOKEN"
	noTokenError = "You must have the SLACKSOC_TOKEN variable to run the" +
		" slacksoc bot"
	version = "0.2.2"
)

func setRealNameFields(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	channel := event["channel"].(string)
	if channel != bot.Channels["general"] {
		return nil, slack.Continue
	}
	userID := event["user"].(string)
	dmChan := make(chan string)
	userChan := make(chan interface{})
	go func() {
		dm, _ := bot.OpenDirectMessage(userID)
		dmChan <- dm
	}()
	go func() {
		payload, _ := bot.Call("users.info", url.Values{"user": []string{userID}})
		userChan <- payload
	}()
	payload := (<-userChan).(map[string]interface{})
	success := payload["ok"].(bool)
	if !success {
		fmt.Println(payload)
		return nil, slack.Continue
	}
	user := payload["user"].(map[string]interface{})
	nick := user["name"].(string)
	text := "Please set your real name fields. https://hacsoc.slack.com/team/%s."
	text += " Then click \"Edit\"."
	text = fmt.Sprintf(text, nick)
	dm := <-dmChan
	return slack.NewMessage(text, dm), slack.Continue
}

func sendDM(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	user := event["user"].(string)
	return bot.DirectMessage(user, "hi"), slack.Continue
}

func main() {
	token := os.Getenv(tokenVar)
	if token == "" {
		fmt.Println(noTokenError)
		os.Exit(1)
	}

	bot := slack.NewBot(token)
	bot.Respond("hi", slack.Respond("hi there!"))
	bot.Respond("pm me", sendDM)
	bot.Respond("((what's)|(tell me) your)? ?version??",
		slack.Respond(fmt.Sprintf("My version is %s. My lib version is %s", version, slack.Version)))
	bot.Listen("gentoo", slack.React("funroll-loops"))
	bot.Listen(".+\\bslacksoc\\b", slack.React("raisedeyebrow"))
	bot.OnEventWithSubtype("message", "channel_join", setRealNameFields)
	fmt.Println("Starting bot")
	if err := bot.Start(); err != nil {
		fmt.Println(err)
	}
}
<commit_msg>Add stallman reaction.<commit_after>package main

import (
	"fmt"
	"net/url"
	"os"

	"github.com/ajm188/slack"
)

const (
	tokenVar     = "SLACKSOC_TOKEN"
	noTokenError = "You must have the SLACKSOC_TOKEN variable to run the" +
		" slacksoc bot"
	version = "0.2.2"
)

func setRealNameFields(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	channel := event["channel"].(string)
	if channel != bot.Channels["general"] {
		return nil, slack.Continue
	}
	userID := event["user"].(string)
	dmChan := make(chan string)
	userChan := make(chan interface{})
	go func() {
		dm, _ := bot.OpenDirectMessage(userID)
		dmChan <- dm
	}()
	go func() {
		payload, _ := bot.Call("users.info", url.Values{"user": []string{userID}})
		userChan <- payload
	}()
	payload := (<-userChan).(map[string]interface{})
	success := payload["ok"].(bool)
	if !success {
		fmt.Println(payload)
		return nil, slack.Continue
	}
	user := payload["user"].(map[string]interface{})
	nick := user["name"].(string)
	text := "Please set your real name fields. https://hacsoc.slack.com/team/%s."
	text += " Then click \"Edit\"."
	text = fmt.Sprintf(text, nick)
	dm := <-dmChan
	return slack.NewMessage(text, dm), slack.Continue
}

func sendDM(bot *slack.Bot, event map[string]interface{}) (*slack.Message, slack.Status) {
	user := event["user"].(string)
	return bot.DirectMessage(user, "hi"), slack.Continue
}

func main() {
	token := os.Getenv(tokenVar)
	if token == "" {
		fmt.Println(noTokenError)
		os.Exit(1)
	}

	bot := slack.NewBot(token)
	bot.Respond("hi", slack.Respond("hi there!"))
	bot.Respond("pm me", sendDM)
	bot.Respond("((what's)|(tell me) your)? ?version??",
		slack.Respond(fmt.Sprintf("My version is %s. My lib version is %s", version, slack.Version)))
	bot.Listen("gentoo", slack.React("funroll-loops"))
	bot.Listen(".+\\bslacksoc\\b", slack.React("raisedeyebrow"))
	bot.Listen("GNU/Linux", slack.React("stallman"))
	bot.OnEventWithSubtype("message", "channel_join", setRealNameFields)
	fmt.Println("Starting bot")
	if err := bot.Start(); err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>