├── images ├── ext01.jpg ├── ext02.jpg ├── invoke.png ├── sig01.png ├── sig02.png ├── plugin01.jpg ├── plugin02.jpg ├── plugin03.jpg ├── plugin04.jpg ├── plugin05.jpg └── plugin06.jpg ├── .gitignore ├── utils ├── error.go ├── data.go └── snowflake.go ├── plugins ├── games │ ├── farm │ │ ├── emoji.go │ │ ├── pet.go │ │ ├── crop.go │ │ ├── mapper.go │ │ └── farm.go │ └── circle │ │ └── circle.go ├── sys │ ├── ignore │ │ └── ignore.go │ ├── menu │ │ └── menu.go │ └── log │ │ └── log.go ├── plugins.go ├── tools │ └── gm │ │ └── gm.go └── query │ └── imglab │ └── imglab.go ├── config ├── device.go └── config.go ├── database ├── mongo │ └── mongo.go └── redis │ ├── redis.go │ └── lock.go ├── README.md ├── main.go ├── go.mod ├── login └── login.go └── go.sum /images/ext01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/ext01.jpg -------------------------------------------------------------------------------- /images/ext02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/ext02.jpg -------------------------------------------------------------------------------- /images/invoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/invoke.png -------------------------------------------------------------------------------- /images/sig01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/sig01.png -------------------------------------------------------------------------------- /images/sig02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/sig02.png -------------------------------------------------------------------------------- /images/plugin01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/plugin01.jpg -------------------------------------------------------------------------------- /images/plugin02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/plugin02.jpg -------------------------------------------------------------------------------- /images/plugin03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/plugin03.jpg -------------------------------------------------------------------------------- /images/plugin04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/plugin04.jpg -------------------------------------------------------------------------------- /images/plugin05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/plugin05.jpg -------------------------------------------------------------------------------- /images/plugin06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuhuan/mirai-bot/HEAD/images/plugin06.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEA 2 | .idea/ 3 | *.iml 4 | # MIRAI 5 | mirai.yml 6 | device.json 7 | session.token 8 | 9 | -------------------------------------------------------------------------------- /utils/error.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func PanicNotNil(err interface{}) { 4 | if err != nil { 5 | panic(err) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /plugins/games/farm/emoji.go: -------------------------------------------------------------------------------- 1 | package farm 2 | 3 | const ( 4 | emojiSun = "🔆" 5 | emojiStock = "🏕" 6 | emojiExp = "📒" 7 | emojiLevel = "🔰" 8 | emojiVoucher = "🎟" 9 | emojiField = "📜" 10 | emojiWater = "💦" 11 | emojiRain = "🌧️" 12 | emojiDog = "🐕" 13 | ) 14 | -------------------------------------------------------------------------------- /config/device.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/Mrs4s/MiraiGo/client" 5 | "io/ioutil" 6 | "os" 7 | ) 8 | 9 | const deviceFilePath = "device.json" 10 | 11 | func InitDeviceInfo() { 12 | _, err := os.Stat(deviceFilePath) 13 | if err == nil { 14 | buff, _ := ioutil.ReadFile(deviceFilePath) 15 | client.SystemDeviceInfo.ReadJson(buff) 16 | } else { 17 | client.GenRandomDevice() 18 | ioutil.WriteFile(deviceFilePath, client.SystemDeviceInfo.ToJson(), os.FileMode(0644)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /utils/data.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func ContainsInt64(items []int64, item int64) bool { 4 | for _, eachItem := range items { 5 | if eachItem == item { 6 | return true 7 | } 8 | } 9 | return false 10 | } 11 | 12 | func ContainsInt(items []int, item int) bool { 13 | for _, eachItem := range items { 14 | if eachItem == item { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | 22 | func SumInts(items []int) (sum int) { 23 | for _, item := range items { 24 | sum += item 25 | } 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /plugins/games/farm/pet.go: -------------------------------------------------------------------------------- 1 | package farm 2 | 3 | import "encoding/json" 4 | 5 | var ( 6 | petList []Pet 7 | petMap = map[int]Pet{} 8 | ) 9 | 10 | func init() { 11 | json.Unmarshal([]byte(petJson), &petList) 12 | for index := 0; index < len(petList); index++ { 13 | pet := petList[index] 14 | petMap[pet.Level] = pet 15 | } 16 | } 17 | 18 | type Pet struct { 19 | Level int `json:"level"` 20 | Name string `json:"name"` 21 | Price int `json:"price"` 22 | } 23 | 24 | const petJson = ` 25 | [ 26 | { 27 | "level": 1, 28 | "name": "斗牛犬", 29 | "price": 10000 30 | } 31 | ] 32 | ` 33 | -------------------------------------------------------------------------------- /plugins/sys/ignore/ignore.go: -------------------------------------------------------------------------------- 1 | package ignore 2 | 3 | import ( 4 | "github.com/niuhuan/mirai-bot/utils" 5 | "github.com/niuhuan/mirai-framework" 6 | ) 7 | 8 | var ignoreUidArray = []int64{ 9 | 2854196310, // Q群管家 10 | 2854196312, // 表情包老铁 11 | 2854196306, // 微软小冰 12 | } 13 | 14 | func NewPluginInstance() *mirai.Plugin { 15 | return &mirai.Plugin{ 16 | Id: func() string { 17 | return "IGNORE" 18 | }, 19 | Name: func() string { 20 | return "忽略" 21 | }, 22 | OnMessage: func(client *mirai.Client, messageInterface interface{}) bool { 23 | if utils.ContainsInt64(ignoreUidArray, client.MessageSenderUin(messageInterface)) { 24 | return true 25 | } 26 | return false 27 | }, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugins/sys/menu/menu.go: -------------------------------------------------------------------------------- 1 | package menu 2 | 3 | import ( 4 | "fmt" 5 | "github.com/niuhuan/mirai-framework" 6 | "strings" 7 | ) 8 | 9 | func NewPluginInstance(customerPlugins []*mirai.Plugin) *mirai.Plugin { 10 | return &mirai.Plugin{ 11 | Id: func() string { 12 | return "MENU" 13 | }, 14 | Name: func() string { 15 | return "菜单" 16 | }, 17 | OnMessage: func(client *mirai.Client, messageInterface interface{}) bool { 18 | content := client.MessageContent(messageInterface) 19 | if strings.EqualFold("菜单", content) { 20 | builder := strings.Builder{} 21 | builder.WriteString("菜单 : ") 22 | for i := 0; i < len(customerPlugins); i++ { 23 | builder.WriteString(fmt.Sprintf("\n♦️ %s", (*customerPlugins[i]).Name())) 24 | } 25 | client.ReplyText(messageInterface, builder.String()) 26 | return true 27 | } 28 | return false 29 | }, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/mongo/mongo.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/niuhuan/mirai-bot/config" 7 | "github.com/niuhuan/mirai-bot/utils" 8 | "go.mongodb.org/mongo-driver/mongo" 9 | "go.mongodb.org/mongo-driver/mongo/options" 10 | "time" 11 | ) 12 | 13 | var ( 14 | mongoClient *mongo.Client 15 | dbName string 16 | ) 17 | 18 | func InitMongo() { 19 | uri := fmt.Sprintf( 20 | "mongodb://%s:%d/%s?w=majority", 21 | config.Config.Database.Mongo.Hostname, 22 | config.Config.Database.Mongo.Port, 23 | config.Config.Database.Mongo.Database, 24 | ) 25 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 26 | defer cancel() 27 | client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri)) 28 | utils.PanicNotNil(err) 29 | mongoClient = client 30 | dbName = config.Config.Database.Mongo.Database 31 | } 32 | 33 | func Test() { 34 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 35 | defer cancel() 36 | utils.PanicNotNil(mongoClient.Ping(ctx, nil)) 37 | } 38 | 39 | func Collection(collectionName string) *mongo.Collection { 40 | return mongoClient.Database(dbName).Collection(collectionName) 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mirai-bot 2 | ===== 3 | 一个基于MiraiFramework的QQ机器人 4 | 5 | PS: 已经跟随MiraiGo升级到 go1.18 6 | 7 | 8 | # 设计思路 9 | 10 | [MiraiFramework](https://github.com/niuhuan/mirai-framework) 11 | 12 | 所有的功能都是由插件完成, 事件发生时, 调度器对插件循环调用, 插件响应是否处理该事件, 直至有插件响应事件, 插件发生异常, 或插件轮训结束, 最后日志结果被记录, 事件响应周期结束。 13 | 14 | ![img.png](images/invoke.png) 15 | 16 | # 运行须知 17 | 18 | 1. 第一次运行 会生成 mirai.yml 和 device.json, 将机器人的账号和密码的MD5填入 mirai.yml 中. 19 | 2. 本bot使用了redis和mongo, mongo和redis官网下载, 解压后可直接使用, 如果您没有条件下载, 可以删除database包以及需要数据库的plugins之后启动. 20 | 21 | - (OPTIONAL) 第一次登录 您可以安装安卓软件DeviceInfo, 参照内容修改device.json, 并将protocol改为2(安卓手表)/1(安卓手机)将绕过设备锁 22 | 23 | 依赖关系 24 | - 圈子插件 : mongo / redis 25 | - 农场插件 : mongo / redis 26 | 27 | # 功能展示 28 | 29 | ## 模版功能 30 | 31 | ### 菜单 32 | 33 | ![](images/plugin01.jpg) 34 | 35 | #### 农场 36 | 37 | ![](images/plugin02.jpg) 38 | ![](images/plugin03.jpg) 39 | ![](images/plugin04.jpg) 40 | 41 | ### 图库 42 | 43 | ![](images/plugin05.jpg) 44 | 45 | ### 群管 46 | 47 | ![](images/plugin06.jpg) 48 | 49 | ### 圈子 50 | 51 | 签到, 打劫, 或者兑换成其他游戏的货币 52 | 53 | ![](images/sig01.png) 54 | 55 | ![](images/sig02.png) 56 | 57 | ## 其他利用此模版实现的功能 58 | 59 | ### 视频互动冒险 60 | 61 | ![](images/ext01.jpg) 62 | 63 | ### 定时通知或2019新冠疫情播报 64 | 65 | ![](images/ext02.jpg) 66 | -------------------------------------------------------------------------------- /plugins/plugins.go: -------------------------------------------------------------------------------- 1 | package plugins 2 | 3 | import ( 4 | "github.com/niuhuan/mirai-bot/plugins/games/circle" 5 | "github.com/niuhuan/mirai-bot/plugins/games/farm" 6 | "github.com/niuhuan/mirai-bot/plugins/query/imglab" 7 | "github.com/niuhuan/mirai-bot/plugins/sys/ignore" 8 | "github.com/niuhuan/mirai-bot/plugins/sys/log" 9 | "github.com/niuhuan/mirai-bot/plugins/sys/menu" 10 | "github.com/niuhuan/mirai-bot/plugins/tools/gm" 11 | "github.com/niuhuan/mirai-framework" 12 | ) 13 | 14 | func Register(c *mirai.Client) { 15 | // 事件监听器 16 | actionsListeners := []*mirai.ActionListener{ 17 | log.NewListenerInstance(), 18 | } 19 | c.SetActionListeners(actionsListeners) 20 | // 自定义组件 21 | cPlugins := []*mirai.Plugin{ 22 | gm.NewPluginInstance(), 23 | circle.NewPluginInstance(), 24 | imglab.NewPluginInstance(), 25 | farm.NewPluginInstance(), 26 | // 最后可以添加拦截所有私聊并做出回复的插件, 做一个连天系统 27 | } 28 | // 系统组件 29 | sPlugins := []*mirai.Plugin{ 30 | log.NewPluginInstance(), 31 | // 忽略指定用户的发言 所以放在菜单的钱main 32 | ignore.NewPluginInstance(), 33 | menu.NewPluginInstance(cPlugins), 34 | } 35 | c.SetPlugins( 36 | append(sPlugins, cPlugins...), 37 | ) 38 | // 插件过滤器 true为阻止该插件 39 | c.SetPluginBlocker(func(plugin *mirai.Plugin, contactType int, contactNumber int64) bool { 40 | return false 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/niuhuan/mirai-bot/config" 5 | "github.com/niuhuan/mirai-bot/database/mongo" 6 | "github.com/niuhuan/mirai-bot/database/redis" 7 | "github.com/niuhuan/mirai-bot/login" 8 | "github.com/niuhuan/mirai-bot/plugins" 9 | "github.com/niuhuan/mirai-framework" 10 | logger "github.com/sirupsen/logrus" 11 | "io/ioutil" 12 | "os" 13 | "os/signal" 14 | ) 15 | 16 | func main() { 17 | // 检查DeviceInfo, Config 初始化配置 18 | config.InitDeviceInfo() 19 | config.InitConfig() 20 | mongo.InitMongo() 21 | redis.InitRedis() 22 | // 校验数据库链接是否正常 23 | mongo.Test() 24 | redis.Test() 25 | // 新建客户端 26 | // client := mirai.NewClientMd5(config.Config.Bot.Account.Uin, config.Config.Bot.Account.PasswordBytes) 27 | client := mirai.NewClient(0, "") 28 | // 注册插件 29 | plugins.Register(client) 30 | // 登录 31 | buff, err := ioutil.ReadFile("session.token") 32 | if err == nil { 33 | err = client.TokenLogin(buff) 34 | } 35 | if err != nil { 36 | err = login.QrcodeLogin(client) 37 | } 38 | // login.CmdLogin(client) 39 | if err == nil{ 40 | ioutil.WriteFile("session.token", client.GenToken(), os.FileMode(0600)) 41 | logger.Info("登录成功, 加载通讯录...") 42 | client.ReloadFriendList() 43 | client.ReloadGroupList() 44 | logger.Info("加载完成") 45 | login.Login = true 46 | // 等待退出信号 47 | ch := make(chan os.Signal) 48 | signal.Notify(ch, os.Interrupt, os.Kill) 49 | <-ch 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /utils/snowflake.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | var ( 8 | machineID int64 // 机器 id 占10位, 十进制范围是 [ 0, 1023 ] 9 | sn int64 // 序列号占 12 位,十进制范围是 [ 0, 4095 ] 10 | lastTimeStamp int64 // 上次的时间戳(毫秒级), 1秒=1000毫秒, 1毫秒=1000微秒,1微秒=1000纳秒 11 | ) 12 | 13 | func init() { 14 | lastTimeStamp = time.Now().UnixNano() / 1000000 15 | SetMachineId(192168100101) 16 | } 17 | 18 | func SetMachineId(mid int64) { 19 | // 把机器 id 左移 12 位,让出 12 位空间给序列号使用 20 | machineID = mid << 12 21 | } 22 | 23 | func GetSnowflakeId() int64 { 24 | curTimeStamp := time.Now().UnixNano() / 1000000 25 | // 同一毫秒 26 | if curTimeStamp == lastTimeStamp { 27 | sn++ 28 | // 序列号占 12 位,十进制范围是 [ 0, 4095 ] 29 | if sn > 4095 { 30 | time.Sleep(time.Millisecond) 31 | curTimeStamp = time.Now().UnixNano() / 1000000 32 | lastTimeStamp = curTimeStamp 33 | sn = 0 34 | } 35 | 36 | // 取 64 位的二进制数 0000000000 0000000000 0000000000 0001111111111 1111111111 1111111111 1 ( 这里共 41 个 1 )和时间戳进行并操作 37 | // 并结果( 右数 )第 42 位必然是 0, 低 41 位也就是时间戳的低 41 位 38 | rightBinValue := curTimeStamp & 0x1FFFFFFFFFF 39 | // 机器 id 占用10位空间,序列号占用12位空间,所以左移 22 位; 经过上面的并操作,左移后的第 1 位,必然是 0 40 | rightBinValue <<= 22 41 | id := rightBinValue | machineID | sn 42 | return id 43 | } 44 | if curTimeStamp > lastTimeStamp { 45 | sn = 0 46 | lastTimeStamp = curTimeStamp 47 | // 取 64 位的二进制数 0000000000 0000000000 0000000000 0001111111111 1111111111 1111111111 1 ( 这里共 41 个 1 )和时间戳进行并操作 48 | // 并结果( 右数 )第 42 位必然是 0, 低 41 位也就是时间戳的低 41 位 49 | rightBinValue := curTimeStamp & 0x1FFFFFFFFFF 50 | // 机器 id 占用10位空间,序列号占用12位空间,所以左移 22 位; 经过上面的并操作,左移后的第 1 位,必然是 0 51 | rightBinValue <<= 22 52 | id := rightBinValue | machineID | sn 53 | return id 54 | } 55 | if curTimeStamp < lastTimeStamp { 56 | return 0 57 | } 58 | return 0 59 | } 60 | 61 | func GetSnowflakeIdString() string { 62 | return strconv.FormatInt(GetSnowflakeId(),10) 63 | } 64 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "github.com/niuhuan/mirai-bot/utils" 7 | "gopkg.in/yaml.v2" 8 | "io/ioutil" 9 | "os" 10 | "regexp" 11 | ) 12 | 13 | const configFilename = "mirai.yml" 14 | 15 | const defaultContent = ` 16 | database: 17 | mongo: 18 | hostname: localhost 19 | port: 27017 20 | database: bot 21 | redis: 22 | hostname: localhost 23 | port: 6379 24 | 25 | bot: 26 | account: 27 | uin: 123456789 28 | password: 5f4dcc3b5aa765d61d8327deb882cf99 29 | ` 30 | const message = ` 31 | 32 | 请修改mirai.yml 33 | 配置mongo和redis两种数据库 34 | 机器人的账号和密码(你可以使用 " echo -n password|md5 " 生成md5) 35 | 36 | 如果登录需要验证码, 您可以按照账号之前登录过的安卓手机修改device.json(可选项) 37 | 38 | ` 39 | 40 | var Config struct { 41 | Database struct { 42 | Mongo struct { 43 | Hostname string 44 | Port int 45 | Database string 46 | } 47 | Redis struct { 48 | Hostname string 49 | Port int 50 | } 51 | } 52 | Bot struct { 53 | Account struct { 54 | Uin int64 55 | Password string 56 | PasswordBytes [16]byte 57 | } 58 | } 59 | } 60 | 61 | func InitConfig() { 62 | _, err := os.Stat(configFilename) 63 | if err != nil { 64 | err = ioutil.WriteFile(configFilename, []byte(defaultContent), os.FileMode(0644)) 65 | utils.PanicNotNil(err) 66 | } 67 | _, err = os.Stat(configFilename) 68 | utils.PanicNotNil(err) 69 | content, _ := ioutil.ReadFile(configFilename) 70 | err = yaml.Unmarshal(content, &Config) 71 | utils.PanicNotNil(err) 72 | if Config.Bot.Account.Uin == 123456789 { 73 | fmt.Print(message) 74 | os.Exit(0) 75 | } 76 | regex, _ := regexp.Compile("^[0-9a-fA-F]{32}$") 77 | if !regex.MatchString(Config.Bot.Account.Password) { 78 | panic("password must be md5 32") 79 | } 80 | temp, _ := hex.DecodeString(Config.Bot.Account.Password) 81 | for i := 0; i < 16; i++ { 82 | Config.Bot.Account.PasswordBytes[i] = temp[i] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/niuhuan/mirai-bot 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f 7 | github.com/Mrs4s/MiraiGo v0.0.0-20220630160133-a39b3fdd962f 8 | github.com/gomodule/redigo v1.8.8 9 | github.com/niuhuan/mirai-framework v0.0.0-20220706065221-8078f2c4b4fa 10 | github.com/sirupsen/logrus v1.8.1 11 | github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d 12 | go.mongodb.org/mongo-driver v1.9.1 13 | gopkg.in/yaml.v2 v2.4.0 14 | ) 15 | 16 | require ( 17 | github.com/RomiChan/protobuf v0.1.1-0.20220624030127-3310cba9dbc0 // indirect 18 | github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c // indirect 19 | github.com/fumiama/imgsz v0.0.2 // indirect 20 | github.com/go-stack/stack v1.8.1 // indirect 21 | github.com/golang/snappy v0.0.4 // indirect 22 | github.com/google/uuid v1.3.0 // indirect 23 | github.com/klauspost/compress v1.15.7 // indirect 24 | github.com/maruel/rs v1.1.0 // indirect 25 | github.com/mattn/go-colorable v0.1.12 // indirect 26 | github.com/mattn/go-isatty v0.0.14 // indirect 27 | github.com/pierrec/lz4/v4 v4.1.15 // indirect 28 | github.com/pkg/errors v0.9.1 // indirect 29 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect 30 | github.com/tidwall/gjson v1.14.1 // indirect 31 | github.com/tidwall/match v1.1.1 // indirect 32 | github.com/tidwall/pretty v1.2.0 // indirect 33 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 34 | github.com/xdg-go/scram v1.1.1 // indirect 35 | github.com/xdg-go/stringprep v1.0.3 // indirect 36 | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect 37 | go.uber.org/atomic v1.9.0 // indirect 38 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect 39 | golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect 40 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect 41 | golang.org/x/text v0.3.7 // indirect 42 | rsc.io/qr v0.2.0 // indirect 43 | ) 44 | 45 | replace github.com/willf/bitset v1.2.0 => github.com/bits-and-blooms/bitset v1.2.0 46 | -------------------------------------------------------------------------------- /database/redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gomodule/redigo/redis" 6 | "github.com/niuhuan/mirai-bot/config" 7 | "github.com/niuhuan/mirai-bot/utils" 8 | logger "github.com/sirupsen/logrus" 9 | "time" 10 | ) 11 | 12 | const ( 13 | setIfNotExist = "NX" // 不存在则执行 14 | setWithExpireTime = "PX" // 过期时间(秒) PX 毫秒 15 | ) 16 | 17 | var ( 18 | RdPool *redis.Pool 19 | Nil = redis.ErrNil 20 | ) 21 | 22 | func InitRedis() { 23 | uri := fmt.Sprintf( 24 | "%s:%d", 25 | config.Config.Database.Redis.Hostname, 26 | config.Config.Database.Redis.Port, 27 | ) 28 | RdPool = &redis.Pool{ 29 | Dial: func() (conn redis.Conn, e error) { 30 | return redis.Dial("tcp", uri) 31 | }, 32 | MaxIdle: 10, 33 | MaxActive: 20, 34 | IdleTimeout: 1000, 35 | } 36 | } 37 | 38 | func Test() { 39 | conn := RdPool.Get() 40 | defer conn.Close() 41 | _, err := conn.Do("PING") 42 | utils.PanicNotNil(err) 43 | } 44 | 45 | func DelKey(key string) { 46 | conn := RdPool.Get() 47 | defer conn.Close() 48 | conn.Do("DEL", key) 49 | } 50 | 51 | func SetString(key string, value string, duration time.Duration) bool { 52 | conn := RdPool.Get() 53 | defer conn.Close() 54 | _, err := conn.Do("SET", key, value, setWithExpireTime, duration.Milliseconds()) 55 | if err != nil { 56 | logger.Info(err) 57 | } 58 | return err == nil 59 | } 60 | 61 | func GetString(key string) string { 62 | conn := RdPool.Get() 63 | defer conn.Close() 64 | value, err := redis.String(conn.Do("GET", key)) 65 | if err != nil { 66 | logger.Info(err) 67 | return "" 68 | } 69 | return value 70 | } 71 | 72 | func GetStringError(key string) (string, error) { 73 | conn := RdPool.Get() 74 | defer conn.Close() 75 | return redis.String(conn.Do("GET", key)) 76 | } 77 | 78 | func SetByteArray(key string, value []byte, duration time.Duration) error { 79 | conn := RdPool.Get() 80 | defer conn.Close() 81 | _, err := conn.Do("SET", key, value, setWithExpireTime, duration.Milliseconds()) 82 | return err 83 | } 84 | 85 | func GetByteArray(key string) []byte { 86 | conn := RdPool.Get() 87 | defer conn.Close() 88 | value, err := redis.Bytes(conn.Do("GET", key)) 89 | if err != nil { 90 | logger.Info(err) 91 | return nil 92 | } 93 | return value 94 | } 95 | 96 | func GetByteArrayError(key string) ([]byte, error) { 97 | conn := RdPool.Get() 98 | defer conn.Close() 99 | return redis.Bytes(conn.Do("GET", key)) 100 | } 101 | 102 | func SetInt(key string, value int, duration time.Duration) bool { 103 | conn := RdPool.Get() 104 | defer conn.Close() 105 | _, err := conn.Do("SET", key, value, setWithExpireTime, duration.Milliseconds()) 106 | if err != nil { 107 | logger.Info(err) 108 | } 109 | return err == nil 110 | } 111 | 112 | func GetInt(key string) (int, error) { 113 | conn := RdPool.Get() 114 | defer conn.Close() 115 | return redis.Int(conn.Do("GET", key)) 116 | } 117 | 118 | func SetBool(key string, value bool, duration time.Duration) error { 119 | conn := RdPool.Get() 120 | defer conn.Close() 121 | _, err := conn.Do("SET", key, value, setWithExpireTime, duration.Milliseconds()) 122 | return err 123 | } 124 | 125 | func GetBoolErr(key string) (bool, error) { 126 | conn := RdPool.Get() 127 | defer conn.Close() 128 | return redis.Bool(conn.Do("GET", key)) 129 | } 130 | -------------------------------------------------------------------------------- /plugins/tools/gm/gm.go: -------------------------------------------------------------------------------- 1 | package gm 2 | 3 | import ( 4 | client2 "github.com/Mrs4s/MiraiGo/client" 5 | "github.com/Mrs4s/MiraiGo/message" 6 | "github.com/niuhuan/mirai-framework" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | const id = "GROUP_MANAGER" 13 | const name = "群管" 14 | 15 | var banRegexp, _ = regexp.Compile("^(\\s+)?b(\\s+)?([0-9]{1,5})(\\s+)?([smhd]?)(\\s+)?$") 16 | 17 | func mana(qqClient *mirai.Client, groupMessage *message.GroupMessage) bool { 18 | groupInfo := qqClient.FindGroup(groupMessage.GroupCode) 19 | if groupInfo != nil { 20 | senderInfo := groupInfo.FindMember(groupMessage.Sender.Uin) 21 | botInfo := groupInfo.FindMember(qqClient.Uin) 22 | if senderInfo != nil && botInfo != nil { 23 | if senderInfo.Permission == client2.Member { 24 | qqClient.ReplyText(groupMessage, "您必须是管理员才能使用管理指令") 25 | } else if botInfo.Permission == client2.Member { 26 | qqClient.ReplyText(groupMessage, "机器人必须是管理员才能使用管理指令") 27 | } else { 28 | return true 29 | } 30 | } 31 | } 32 | return false 33 | } 34 | 35 | func NewPluginInstance() *mirai.Plugin { 36 | return &mirai.Plugin{ 37 | Id: func() string { 38 | return id 39 | }, 40 | Name: func() string { 41 | return name 42 | }, 43 | OnPrivateMessage: func(client *mirai.Client, privateMessage *message.PrivateMessage) bool { 44 | if client.MessageContent(privateMessage) == name { 45 | client.ReplyText(privateMessage, "群管功能只能在群中使用") 46 | return true 47 | } 48 | return false 49 | }, 50 | OnGroupMessage: func(client *mirai.Client, groupMessage *message.GroupMessage) bool { 51 | elements := groupMessage.Elements 52 | if text, ok := (elements[0]).(*message.TextElement); ok { 53 | if banRegexp.MatchString(text.Content) { 54 | if mana(client, groupMessage) { 55 | matches := banRegexp.FindStringSubmatch(text.Content) 56 | source, _ := strconv.Atoi(matches[3]) 57 | switch matches[5] { 58 | case "m": 59 | source *= 60 60 | case "h": 61 | source *= 60 * 60 62 | case "d": 63 | source *= 60 * 60 * 24 64 | } 65 | if source > 60*60*24*29 { 66 | client.ReplyText(groupMessage, "最多禁言29天") 67 | } else { 68 | mLen := 0 69 | groupInfo := client.FindGroup(groupMessage.GroupCode) 70 | if groupInfo != nil { 71 | for _, element := range elements { 72 | if at, ok := element.(*message.AtElement); ok { 73 | target := groupInfo.FindMember(at.Target) 74 | if target != nil && target.Manageable() { 75 | target.Mute(uint32(source)) 76 | mLen++ 77 | } 78 | } 79 | } 80 | } 81 | if mLen > 0 { 82 | client.ReplyText(groupMessage, "操作成功") 83 | } 84 | } 85 | } 86 | return true 87 | } 88 | } 89 | return false 90 | }, 91 | OnMessage: func(client *mirai.Client, messageInterface interface{}) bool { 92 | content := client.MessageContent(messageInterface) 93 | if strings.EqualFold(name, content) { 94 | client.ReplyText(messageInterface, 95 | "群管理功能\n"+ 96 | "当机器人是管理员, 且发送者为管理员时可生效\n\n"+ 97 | " 批量禁言", 98 | ) 99 | return true 100 | } 101 | if strings.EqualFold("批量禁言", content) { 102 | client.ReplyText(messageInterface, 103 | "b+禁言时间 @一个或多个人\n\n"+ 104 | "比如禁言张三12小时 : b12h @张三 \n\n"+ 105 | "比如禁言张三李四12天 : b12h @张三 @李四 \n\n"+ 106 | " s 秒, m 分, h 小时, d 天\n\n"+ 107 | "b0 则解除禁言", 108 | ) 109 | return true 110 | } 111 | return false 112 | }, 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /database/redis/lock.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | 4 | import ( 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "github.com/gomodule/redigo/redis" 9 | "github.com/niuhuan/mirai-bot/utils" 10 | "runtime" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | var ( 16 | ErrTimeout = errors.New("lock: obtain timeout") 17 | BootId = utils.GetSnowflakeIdString() 18 | ) 19 | 20 | func GoID() uint64 { 21 | b := make([]byte, 64) 22 | b = b[:runtime.Stack(b, false)] 23 | b = bytes.TrimPrefix(b, []byte("goroutine ")) 24 | b = b[:bytes.IndexByte(b, ' ')] 25 | n, _ := strconv.ParseUint(string(b), 10, 64) 26 | return n 27 | } 28 | 29 | func ContextId() string { 30 | return fmt.Sprintf("%s::%d", BootId, GoID()) 31 | } 32 | 33 | type Lock struct { 34 | Key string 35 | Expire time.Duration 36 | RdPool *redis.Pool 37 | } 38 | 39 | const lockScript = ` 40 | if (redis.call('exists', KEYS[1]) == 0) then 41 | redis.call('hincrby', KEYS[1], ARGV[2], 1); 42 | redis.call('pexpire', KEYS[1], ARGV[1]); 43 | return nil; 44 | end; 45 | if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 46 | redis.call('hincrby', KEYS[1], ARGV[2], 1); 47 | redis.call('pexpire', KEYS[1], ARGV[1]); 48 | return nil; 49 | end; 50 | return redis.call('pttl', KEYS[1]); 51 | ` 52 | 53 | const unlockScript = ` 54 | if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then 55 | return nil; 56 | end; 57 | local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); 58 | if (counter > 0) then 59 | redis.call('pexpire', KEYS[1], ARGV[1]); 60 | return 0; 61 | else 62 | redis.call('del', KEYS[1]); 63 | return 1; 64 | end; 65 | return nil; 66 | ` 67 | 68 | var lockScriptRedis = redis.NewScript(1, lockScript) 69 | var unlockScriptRedis = redis.NewScript(1, unlockScript) 70 | 71 | func (lock *Lock) Unlock() (bool, error) { 72 | // connect 73 | conn := lock.RdPool.Get() 74 | defer conn.Close() 75 | // 76 | bool, err := redis.Bool(unlockScriptRedis.Do(conn, lock.Key, lock.Expire.Milliseconds(), ContextId())) 77 | // already lease when if err == redis.ErrNil 78 | // reentry counter > 0 when bool is false 79 | if err == nil && bool { 80 | conn.Do("PUBLISH", lock.Key, lock.Key) 81 | } 82 | return bool, err 83 | } 84 | 85 | func TryLock(key string, wait time.Duration, lease time.Duration) (*Lock, error) { 86 | current := time.Now() 87 | conn := RdPool.Get() 88 | defer conn.Close() 89 | // loop 90 | for true { 91 | // lock or reentry 92 | ttl, err := redis.Uint64(lockScriptRedis.Do(conn, key, lease.Milliseconds(), ContextId())) 93 | if err == redis.ErrNil { 94 | return &Lock{ 95 | Key: key, 96 | Expire: lease, 97 | RdPool: RdPool, 98 | }, nil 99 | } else if err != nil { 100 | return nil, err 101 | } 102 | // timeout 103 | currentOff := time.Now() 104 | if currentOff.Sub(current) >= wait { 105 | return nil, ErrTimeout 106 | } 107 | // subscribe 108 | func() { 109 | waitDuration := current.Add(wait).Sub(currentOff) 110 | ttlDuration := time.Millisecond * time.Duration(ttl) 111 | if ttlDuration < waitDuration { 112 | waitDuration = ttlDuration 113 | } 114 | subConn := redis.PubSubConn{Conn: RdPool.Get()} 115 | defer subConn.Close() 116 | subConn.Subscribe(key) 117 | for true { 118 | switch subConn.ReceiveWithTimeout(waitDuration).(type) { 119 | case redis.Message: // message 120 | case error: // timeout (or redis error) 121 | return 122 | case redis.Subscription: // continue for 123 | default: // else continue 124 | continue 125 | } 126 | } 127 | }() 128 | } 129 | return nil, ErrTimeout 130 | } 131 | -------------------------------------------------------------------------------- /plugins/query/imglab/imglab.go: -------------------------------------------------------------------------------- 1 | package imglab 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/niuhuan/mirai-framework" 6 | logger "github.com/sirupsen/logrus" 7 | "io/ioutil" 8 | "net/http" 9 | "regexp" 10 | ) 11 | 12 | var httpClient = &http.Client{Transport: &http.Transport{ 13 | TLSClientConfig: &tls.Config{ 14 | InsecureSkipVerify: true, 15 | }, 16 | }} 17 | 18 | const id = "IMG_LIB" 19 | const name = "图库" 20 | 21 | func NewPluginInstance() *mirai.Plugin { 22 | return &mirai.Plugin{ 23 | Id: func() string { 24 | return id 25 | }, 26 | Name: func() string { 27 | return name 28 | }, 29 | OnMessage: func(client *mirai.Client, messageInterface interface{}) bool { 30 | content := client.MessageContent(messageInterface) 31 | if content == "图库" { 32 | client.ReplyText(messageInterface, 33 | "发送以下内容 \n\n 动漫壁纸 \n 美女壁纸 \n 风景壁纸 \n 手机动漫壁纸 \n 手机美女壁纸 \n 手机风景壁纸 \n 随机插画 \n 随机老婆") 34 | return true 35 | } 36 | if content == "动漫壁纸" { 37 | reply(client, messageInterface, dmRequest) 38 | return true 39 | } 40 | if content == "美女壁纸" { 41 | reply(client, messageInterface, meiziRequest) 42 | return true 43 | } 44 | if content == "风景壁纸" { 45 | reply(client, messageInterface, fengjingRequest) 46 | return true 47 | } 48 | if content == "手机动漫壁纸" { 49 | reply(client, messageInterface, dmSjRequest) 50 | return true 51 | } 52 | if content == "手机美女壁纸" { 53 | reply(client, messageInterface, meiziSjRequest) 54 | return true 55 | } 56 | if content == "手机风景壁纸" { 57 | reply(client, messageInterface, fengjingSjRequest) 58 | return true 59 | } 60 | if content == "随机插画" { 61 | reply(client, messageInterface, illustrationRequest) 62 | return true 63 | } 64 | if content == "随机老婆" { 65 | reg, _ := regexp.Compile("src=\"//(.+\\.jpg)\"") 66 | do, err := httpClient.Do(wfRequest) 67 | if err == nil { 68 | defer do.Body.Close() 69 | bo, err := ioutil.ReadAll(do.Body) 70 | if err == nil { 71 | str := string(bo) 72 | find := reg.FindStringSubmatch(str) 73 | if find != nil { 74 | req, _ := http.NewRequest("GET", "https://"+find[1], nil) 75 | reply(client, messageInterface, req) 76 | } 77 | } 78 | } 79 | return true 80 | } 81 | return false 82 | }, 83 | } 84 | } 85 | 86 | 87 | var dmRequest, _ = http.NewRequest("GET", "http://api.molure.cn/sjbz/api.php?lx=dongman", nil) 88 | var dmSjRequest, _ = http.NewRequest("GET", "http://api.molure.cn/sjbz/api.php?method=mobile&lx=dongman", nil) 89 | var meiziRequest, _ = http.NewRequest("GET", "http://api.molure.cn/sjbz/api.php?lx=meizi", nil) 90 | var meiziSjRequest, _ = http.NewRequest("GET", "http://api.molure.cn/sjbz/api.php?method=mobile&lx=meizi", nil) 91 | var fengjingRequest, _ = http.NewRequest("GET", "http://api.molure.cn/sjbz/api.php?lx=fengjing", nil) 92 | var fengjingSjRequest, _ = http.NewRequest("GET", "http://api.molure.cn/sjbz/api.php?method=mobile&lx=fengjing", nil) 93 | 94 | var illustrationRequest, _ = http.NewRequest("GET", "https://api.mz-moe.cn/img.php", nil) 95 | var wfRequest, _ = http.NewRequest("GET", "https://img.xjh.me/random_img.php", nil) 96 | 97 | func reply(client *mirai.Client, messageInterface interface{}, request *http.Request) { 98 | do, err := httpClient.Do(request) 99 | if err == nil { 100 | defer do.Body.Close() 101 | bo, err := ioutil.ReadAll(do.Body) 102 | if err == nil { 103 | img, err := client.UploadReplyImage(messageInterface, bo) 104 | if err == nil { 105 | client.ReplyRawMessage(messageInterface, client.MakeReplySendingMessage(messageInterface).Append(img)) 106 | } else { 107 | logger.Error("IMG UPLOAD ERROR : {}", err.Error()) 108 | } 109 | } else { 110 | logger.Error("IMG READ ERROR : {}", err.Error()) 111 | } 112 | } else { 113 | logger.Error("IMG REQUEST ERROR : {}", err.Error()) 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /plugins/sys/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "github.com/Mrs4s/MiraiGo/message" 6 | "github.com/niuhuan/mirai-bot/database/mongo" 7 | "github.com/niuhuan/mirai-framework" 8 | "go.mongodb.org/mongo-driver/bson" 9 | "time" 10 | ) 11 | 12 | const ( 13 | DirectionSending = "SENDING" 14 | DirectionReceiving = "RECEIVING" 15 | TypePrivate = "PRIVATE" 16 | TypeGroup = "GROUP" 17 | TypeTemp = "TEMP" 18 | ) 19 | 20 | func NewPluginInstance() *mirai.Plugin { 21 | return &mirai.Plugin{ 22 | Id: func() string { 23 | return "LOG" 24 | }, 25 | Name: func() string { 26 | return "日志" 27 | }, 28 | OnMessage: func(client *mirai.Client, messageInterface interface{}) bool { 29 | var m *bson.M 30 | 31 | if privateMessage, b := (messageInterface).(*message.PrivateMessage); b { 32 | buff, err := client.FormatMessageElements(privateMessage.Elements) 33 | if err == nil { 34 | m = &bson.M{ 35 | "Direction": DirectionReceiving, 36 | "Type": TypePrivate, 37 | "GroupCode": 0, 38 | "Uin": privateMessage.Sender.Uin, 39 | "Time": privateMessage.Time, 40 | "InternalId": privateMessage.InternalId, 41 | "MsgId": privateMessage.Id, 42 | "Content": string(buff), 43 | } 44 | } 45 | } else if groupMessage, b := (messageInterface).(*message.GroupMessage); b { 46 | buff, err := client.FormatMessageElements(groupMessage.Elements) 47 | if err == nil { 48 | m = &bson.M{ 49 | "Direction": DirectionReceiving, 50 | "Type": TypeGroup, 51 | "GroupCode": groupMessage.GroupCode, 52 | "Uin": groupMessage.Sender.Uin, 53 | "Time": groupMessage.Time, 54 | "InternalId": groupMessage.InternalId, 55 | "MsgId": groupMessage.Id, 56 | "Content": string(buff), 57 | } 58 | } 59 | } else if tempMessage, b := (messageInterface).(*message.TempMessage); b { 60 | buff, err := client.FormatMessageElements(tempMessage.Elements) 61 | if err == nil { 62 | m = &bson.M{ 63 | "Direction": DirectionReceiving, 64 | "Type": TypeTemp, 65 | "GroupCode": tempMessage.GroupCode, 66 | "Uin": tempMessage.Sender.Uin, 67 | "Time": time.Now().Unix(), 68 | "InternalId": tempMessage.Id, 69 | "MsgId": tempMessage.Id, 70 | "Content": string(buff), 71 | } 72 | } 73 | } 74 | if m != nil { 75 | save(m) 76 | } 77 | return false 78 | }, 79 | } 80 | } 81 | 82 | func NewListenerInstance() *mirai.ActionListener { 83 | return &mirai.ActionListener{ 84 | Id: func() string { 85 | return "LOG" 86 | }, 87 | Name: func() string { 88 | return "日志" 89 | }, 90 | OnSendPrivateMessage: func(c *mirai.Client, message *message.PrivateMessage) bool { 91 | buff, err := c.FormatMessageElements(message.Elements) 92 | if err == nil { 93 | save(&bson.M{ 94 | "Direction": DirectionSending, 95 | "Type": TypePrivate, 96 | "GroupCode": 0, 97 | "Uin": message.Target, 98 | "Time": message.Time, 99 | "InternalId": message.InternalId, 100 | "MsgId": message.Id, 101 | "Content": string(buff), 102 | }) 103 | } 104 | return false 105 | }, 106 | OnSendGroupMessage: func(c *mirai.Client, message *message.GroupMessage) bool { 107 | buff, err := c.FormatMessageElements(message.Elements) 108 | if err == nil { 109 | save(&bson.M{ 110 | "Direction": DirectionSending, 111 | "Type": TypeGroup, 112 | "GroupCode": message.GroupCode, 113 | "Uin": 0, 114 | "Time": message.Time, 115 | "InternalId": message.InternalId, 116 | "MsgId": message.Id, 117 | "Content": string(buff), 118 | }) 119 | } 120 | return false 121 | }, 122 | OnSendTempMessage: func(c *mirai.Client, message *message.TempMessage, target int64) bool { 123 | buff, err := c.FormatMessageElements(message.Elements) 124 | if err == nil { 125 | save(&bson.M{ 126 | "Direction": DirectionSending, 127 | "Type": TypeTemp, 128 | "GroupCode": message.GroupCode, 129 | "Uin": target, 130 | "Time": time.Now().Unix(), 131 | "InternalId": message.Id, 132 | "MsgId": message.Id, 133 | "Content": string(buff), 134 | }) 135 | } 136 | return false 137 | }, 138 | } 139 | } 140 | 141 | func save(m *bson.M) { 142 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 143 | defer cancel() 144 | mongo.Collection("log.message").InsertOne(ctx, &m) 145 | } 146 | -------------------------------------------------------------------------------- /login/login.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | qrcodeTerminal "github.com/Baozisoftware/qrcode-terminal-go" 8 | "github.com/Mrs4s/MiraiGo/client" 9 | "github.com/niuhuan/mirai-bot/utils" 10 | "github.com/niuhuan/mirai-framework" 11 | logger "github.com/sirupsen/logrus" 12 | "github.com/tuotoo/qrcode" 13 | "io/ioutil" 14 | "net/http" 15 | "os" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | var Login = false 21 | 22 | func CmdLogin(c *mirai.Client) error { 23 | resp, err := c.Login() 24 | if err != nil { 25 | return err 26 | } 27 | return loginResult(c, resp) 28 | } 29 | 30 | func QrcodeLogin(c *mirai.Client) error { 31 | rsp, err := c.FetchQRCode() 32 | if err != nil { 33 | return err 34 | } 35 | fi, err := qrcode.Decode(bytes.NewReader(rsp.ImageData)) 36 | if err != nil { 37 | return err 38 | } 39 | _ = os.WriteFile("qrcode.png", rsp.ImageData, 0o644) 40 | defer func() { _ = os.Remove("qrcode.png") }() 41 | logger.Infof("请使用手机QQ扫描二维码 (qrcode.png) : ") 42 | time.Sleep(time.Second) 43 | qrcodeTerminal.New().Get(fi.Content).Print() 44 | s, err := c.QueryQRCodeStatus(rsp.Sig) 45 | if err != nil { 46 | return err 47 | } 48 | prevState := s.State 49 | for { 50 | time.Sleep(time.Second) 51 | s, _ = c.QueryQRCodeStatus(rsp.Sig) 52 | if s == nil { 53 | continue 54 | } 55 | if prevState == s.State { 56 | continue 57 | } 58 | prevState = s.State 59 | switch s.State { 60 | case client.QRCodeCanceled: 61 | logger.Fatalf("扫码被用户取消.") 62 | case client.QRCodeTimeout: 63 | logger.Fatalf("二维码过期") 64 | case client.QRCodeWaitingForConfirm: 65 | logger.Infof("扫码成功, 请在手机端确认登录.") 66 | case client.QRCodeConfirmed: 67 | res, err := c.QRCodeLogin(s.LoginInfo) 68 | if err != nil { 69 | return err 70 | } 71 | return loginResult(c, res) 72 | case client.QRCodeImageFetch, client.QRCodeWaitingForScan: 73 | // ignore 74 | } 75 | } 76 | } 77 | 78 | func loginResult(c *mirai.Client, resp *client.LoginResponse) error { 79 | var err error 80 | console := bufio.NewReader(os.Stdin) 81 | for { 82 | if err != nil { 83 | logger.WithError(err).Fatal("无法登录") 84 | return err 85 | } 86 | var text string 87 | if !resp.Success { 88 | switch resp.Error { 89 | case client.SliderNeededError: 90 | logger.Info("请参考 https://github.com/mzdluo123/TxCaptchaHelper 获取并输入ticker") 91 | logger.Info("Slider url : ", resp.VerifyUrl) 92 | f := strings.Replace(resp.VerifyUrl, "ssl.captcha.qq.com", "txhelper.glitch.me", -1) 93 | logger.Info("Slider url : ", f) 94 | var a string 95 | func() { 96 | rsp, err := http.DefaultClient.Get(f) 97 | if err != nil { 98 | panic(err) 99 | } 100 | defer rsp.Body.Close() 101 | buff, err := ioutil.ReadAll(rsp.Body) 102 | a = string(buff) 103 | if err != nil { 104 | panic(err) 105 | } 106 | }() 107 | println(a) 108 | console.ReadString('\n') 109 | func() { 110 | rsp, err := http.DefaultClient.Get(f) 111 | if err != nil { 112 | panic(err) 113 | } 114 | defer rsp.Body.Close() 115 | buff, err := ioutil.ReadAll(rsp.Body) 116 | a = string(buff) 117 | if err != nil { 118 | panic(err) 119 | } 120 | }() 121 | println(a) 122 | resp, err = c.SubmitTicket(a) 123 | continue 124 | case client.NeedCaptcha: 125 | var file *os.File 126 | file, err = ioutil.TempFile("mirai", utils.GetSnowflakeIdString()+".jpeg") 127 | func() { 128 | utils.GetSnowflakeIdString() 129 | utils.PanicNotNil(err) 130 | defer file.Close() 131 | file.Write(resp.CaptchaImage) 132 | }() 133 | fmt.Print("请输入验证码 : 图片位置 : " + file.Name()) 134 | text, _ := console.ReadString('\n') 135 | resp, err = c.SubmitCaptcha(strings.ReplaceAll(text, "\n", ""), resp.CaptchaSign) 136 | continue 137 | case client.SMSNeededError: 138 | fmt.Println("QQ开启了设备锁, 需要发送短信, 输入YES进行发送") 139 | fmt.Printf("短信发送到 %s ? (yes)", resp.SMSPhone) 140 | t, _ := console.ReadString('\n') 141 | t = strings.TrimSpace(t) 142 | if t != "yes" { 143 | os.Exit(2) 144 | } 145 | if !c.RequestSMS() { 146 | logger.Warnf("无法获取短信验证码") 147 | os.Exit(2) 148 | } 149 | logger.Warn("请输入短信验证码: ") 150 | text, _ = console.ReadString('\n') 151 | resp, err = c.SubmitSMS(strings.ReplaceAll(strings.ReplaceAll(text, "\n", ""), "\r", "")) 152 | continue 153 | case client.SMSOrVerifyNeededError: 154 | fmt.Println("开启了设备锁:") 155 | fmt.Println("1. 发送短信到 ", resp.SMSPhone) 156 | fmt.Println("2. 扫描二维码") 157 | fmt.Print("输入 (1,2):") 158 | text, _ = console.ReadString('\n') 159 | text = strings.TrimSpace(text) 160 | switch text { 161 | case "1": 162 | if !c.RequestSMS() { 163 | logger.Warnf("无法获取短信验证码") 164 | os.Exit(2) 165 | } 166 | logger.Warn("请输入短信验证码: ") 167 | text, _ = console.ReadString('\n') 168 | resp, err = c.SubmitSMS(strings.ReplaceAll(strings.ReplaceAll(text, "\n", ""), "\r", "")) 169 | continue 170 | case "2": 171 | fmt.Printf("设备锁 -> %v\n", resp.VerifyUrl) 172 | os.Exit(2) 173 | default: 174 | fmt.Println("不正确的输入") 175 | os.Exit(2) 176 | } 177 | case client.UnsafeDeviceError: 178 | fmt.Printf("设备锁 -> %v\n", resp.VerifyUrl) 179 | os.Exit(2) 180 | case client.OtherLoginError, client.UnknownLoginError: 181 | logger.Fatalf("登录失败: %v", resp.ErrorMessage) 182 | os.Exit(3) 183 | } 184 | } 185 | break 186 | } 187 | return nil 188 | } 189 | -------------------------------------------------------------------------------- /plugins/games/farm/crop.go: -------------------------------------------------------------------------------- 1 | package farm 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | var ( 8 | cropList []Crop 9 | cropMap = map[int]Crop{} 10 | ) 11 | 12 | func init() { 13 | json.Unmarshal([]byte(cropJson), &cropList) 14 | for index := 0; index < len(cropList); index++ { 15 | crop := cropList[index] 16 | cropMap[crop.Level] = crop 17 | } 18 | } 19 | 20 | type Crop struct { 21 | Level int 22 | Name string 23 | SeedPrice int 24 | FruitsMin int 25 | FruitsMax int 26 | FruitPrice int 27 | FruitExp int 28 | StepHours []int 29 | StepEmojis []string 30 | FruitEmoji string 31 | } 32 | 33 | const cropJson = ` 34 | [ 35 | { 36 | "level": 1, 37 | "name": "土豆", 38 | "seedPrice": 10, 39 | "fruitsMin": 8, 40 | "fruitsMax": 12, 41 | "fruitPrice": 4, 42 | "fruitExp": 4, 43 | "stepHours": [ 44 | 1, 45 | 2, 46 | 3 47 | ], 48 | "stepEmojis": [ 49 | "\uD83C\uDF31", 50 | "\uD83C\uDF31", 51 | "\uD83C\uDF8D" 52 | ], 53 | "fruitEmoji": "\uD83E\uDD54" 54 | }, 55 | { 56 | "level": 2, 57 | "name": "萝卜", 58 | "seedPrice": 20, 59 | "fruitsMin": 10, 60 | "fruitsMax": 15, 61 | "fruitPrice": 8, 62 | "fruitExp": 4, 63 | "stepHours": [ 64 | 1, 65 | 2, 66 | 3 67 | ], 68 | "stepEmojis": [ 69 | "\uD83C\uDF31", 70 | "\uD83C\uDF8D", 71 | "\uD83C\uDF8D" 72 | ], 73 | "fruitEmoji": "\uD83E\uDD55" 74 | }, 75 | { 76 | "level": 3, 77 | "name": "花生", 78 | "seedPrice": 30, 79 | "fruitsMin": 15, 80 | "fruitsMax": 17, 81 | "fruitPrice": 8, 82 | "fruitExp": 4, 83 | "stepHours": [ 84 | 1, 85 | 3, 86 | 4 87 | ], 88 | "stepEmojis": [ 89 | "\uD83C\uDF31", 90 | "\uD83C\uDF8D", 91 | "\uD83C\uDF3F" 92 | ], 93 | "fruitEmoji": "\uD83E\uDD5C" 94 | }, 95 | { 96 | "level": 4, 97 | "name": "番茄", 98 | "seedPrice": 40, 99 | "fruitsMin": 10, 100 | "fruitsMax": 15, 101 | "fruitPrice": 20, 102 | "fruitExp": 9, 103 | "stepHours": [ 104 | 1, 105 | 3, 106 | 4 107 | ], 108 | "stepEmojis": [ 109 | "\uD83C\uDF31", 110 | "\uD83C\uDF8D", 111 | "\uD83C\uDF3F" 112 | ], 113 | "fruitEmoji": "\uD83C\uDF45" 114 | }, 115 | { 116 | "level": 5, 117 | "name": "茄子", 118 | "seedPrice": 50, 119 | "fruitsMin": 10, 120 | "fruitsMax": 15, 121 | "fruitPrice": 25, 122 | "fruitExp": 12, 123 | "stepHours": [ 124 | 2, 125 | 4, 126 | 5 127 | ], 128 | "stepEmojis": [ 129 | "\uD83C\uDF31", 130 | "\uD83C\uDF8D", 131 | "\uD83C\uDF3F" 132 | ], 133 | "fruitEmoji": "\uD83C\uDF46" 134 | }, 135 | { 136 | "level": 6, 137 | "name": "辣椒", 138 | "seedPrice": 120, 139 | "fruitsMin": 20, 140 | "fruitsMax": 25, 141 | "fruitPrice": 25, 142 | "fruitExp": 12, 143 | "stepHours": [ 144 | 2, 145 | 4, 146 | 5 147 | ], 148 | "stepEmojis": [ 149 | "\uD83C\uDF31", 150 | "\uD83C\uDF8D", 151 | "\uD83C\uDF3E" 152 | ], 153 | "fruitEmoji": "\uD83C\uDF36" 154 | }, 155 | { 156 | "level": 7, 157 | "name": "蘑菇", 158 | "seedPrice": 140, 159 | "fruitsMin": 25, 160 | "fruitsMax": 30, 161 | "fruitPrice": 25, 162 | "fruitExp": 12, 163 | "stepHours": [ 164 | 2, 165 | 4, 166 | 6 167 | ], 168 | "stepEmojis": [ 169 | "\uD83C\uDF31", 170 | "\uD83C\uDF8D", 171 | "\uD83C\uDF3E" 172 | ], 173 | "fruitEmoji": "\uD83C\uDF44" 174 | }, 175 | { 176 | "level": 8, 177 | "name": "玉米", 178 | "seedPrice": 160, 179 | "fruitsMin": 30, 180 | "fruitsMax": 35, 181 | "fruitPrice": 50, 182 | "fruitExp": 20, 183 | "stepHours": [ 184 | 2, 185 | 4, 186 | 6 187 | ], 188 | "stepEmojis": [ 189 | "\uD83C\uDF31", 190 | "\uD83C\uDF8D", 191 | "\uD83C\uDF3E" 192 | ], 193 | "fruitEmoji": "\uD83C\uDF3D" 194 | }, 195 | { 196 | "level": 11, 197 | "name": "苹果", 198 | "seedPrice": 220, 199 | "fruitsMin": 30, 200 | "fruitsMax": 35, 201 | "fruitPrice": 60, 202 | "fruitExp": 30, 203 | "stepHours": [ 204 | 3, 205 | 6, 206 | 8 207 | ], 208 | "stepEmojis": [ 209 | "\uD83C\uDF31", 210 | "\uD83C\uDF8D", 211 | "\uD83C\uDF33" 212 | ], 213 | "fruitEmoji": "\uD83C\uDF4E" 214 | }, 215 | { 216 | "level": 13, 217 | "name": "雪梨", 218 | "seedPrice": 260, 219 | "fruitsMin": 30, 220 | "fruitsMax": 35, 221 | "fruitPrice": 70, 222 | "fruitExp": 30, 223 | "stepHours": [ 224 | 3, 225 | 6, 226 | 8 227 | ], 228 | "stepEmojis": [ 229 | "\uD83C\uDF31", 230 | "\uD83C\uDF8D", 231 | "\uD83C\uDF33" 232 | ], 233 | "fruitEmoji": "\uD83C\uDF50" 234 | }, 235 | { 236 | "level": 15, 237 | "name": "桃子", 238 | "seedPrice": 300, 239 | "fruitsMin": 30, 240 | "fruitsMax": 35, 241 | "fruitPrice": 100, 242 | "fruitExp": 70, 243 | "stepHours": [ 244 | 3, 245 | 6, 246 | 8 247 | ], 248 | "stepEmojis": [ 249 | "\uD83C\uDF31", 250 | "\uD83C\uDF8D", 251 | "\uD83C\uDF33" 252 | ], 253 | "fruitEmoji": "\uD83C\uDF51" 254 | }, 255 | { 256 | "level": 17, 257 | "name": "橙子", 258 | "seedPrice": 510, 259 | "fruitsMin": 30, 260 | "fruitsMax": 35, 261 | "fruitPrice": 150, 262 | "fruitExp": 100, 263 | "stepHours": [ 264 | 3, 265 | 6, 266 | 8 267 | ], 268 | "stepEmojis": [ 269 | "\uD83C\uDF31", 270 | "\uD83C\uDF8D", 271 | "\uD83C\uDF33" 272 | ], 273 | "fruitEmoji": "\uD83C\uDF4A" 274 | }, 275 | { 276 | "level": 19, 277 | "name": "柠檬", 278 | "seedPrice": 999, 279 | "fruitsMin": 30, 280 | "fruitsMax": 35, 281 | "fruitPrice": 200, 282 | "fruitExp": 150, 283 | "stepHours": [ 284 | 3, 285 | 6, 286 | 8 287 | ], 288 | "stepEmojis": [ 289 | "\uD83C\uDF31", 290 | "\uD83C\uDF8D", 291 | "\uD83C\uDF33" 292 | ], 293 | "fruitEmoji": "\uD83C\uDF4B" 294 | } 295 | ] 296 | ` 297 | -------------------------------------------------------------------------------- /plugins/games/circle/circle.go: -------------------------------------------------------------------------------- 1 | package circle 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/Mrs4s/MiraiGo/message" 7 | "github.com/niuhuan/mirai-bot/database/mongo" 8 | "github.com/niuhuan/mirai-bot/database/redis" 9 | "github.com/niuhuan/mirai-framework" 10 | "go.mongodb.org/mongo-driver/bson" 11 | "go.mongodb.org/mongo-driver/mongo/options" 12 | "math/rand" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | const id = "CIRCLE" 18 | const name = "圈子" 19 | 20 | func NewPluginInstance() *mirai.Plugin { 21 | return &mirai.Plugin{ 22 | Id: func() string { 23 | return id 24 | }, 25 | Name: func() string { 26 | return name 27 | }, 28 | OnMessage: func(client *mirai.Client, messageInterface interface{}) bool { 29 | content := client.MessageContent(messageInterface) 30 | if content == name { 31 | client.ReplyText(messageInterface, 32 | "在群中发送以下内容 \n\n 签到 \n 积分 \n 打劫@一个人 \n ") 33 | return true 34 | } 35 | return false 36 | }, 37 | OnGroupMessage: func(client *mirai.Client, groupMessage *message.GroupMessage) bool { 38 | content := client.MessageContent(groupMessage) 39 | if "签到" == content { 40 | sign(client, groupMessage) 41 | return true 42 | } 43 | if "积分" == content { 44 | points(client, groupMessage) 45 | return true 46 | } 47 | if strings.HasPrefix(content, "打劫") { 48 | rob(client, groupMessage) 49 | return true 50 | } 51 | return false 52 | }, 53 | } 54 | } 55 | 56 | func points(client *mirai.Client, groupMessage *message.GroupMessage) { 57 | points := loadPoint(groupMessage.GroupCode, groupMessage.Sender.Uin) 58 | client.ReplyText(groupMessage, fmt.Sprintf("积分合计 : %v", points.Point)) 59 | } 60 | 61 | func sign(client *mirai.Client, groupMessage *message.GroupMessage) { 62 | day := time.Now() 63 | pre := day.Add(-time.Hour * 24) 64 | dayStr := day.Format("2006-01-02") 65 | preStr := pre.Format("2006-01-02") 66 | lock, err := redis.TryLock(fmt.Sprintf("CIRCEL::LOCK::%v", groupMessage.GroupCode), time.Second*5, time.Second*15) 67 | if err != nil { 68 | return 69 | } 70 | defer lock.Unlock() 71 | last := lastSignTime(groupMessage.GroupCode, groupMessage.Sender.Uin) 72 | if last != nil && dayStr == last.LastDay { 73 | client.ReplyText(groupMessage, "您今天已经签到过") 74 | return 75 | } 76 | var series int 77 | if last != nil && last.LastDay == preStr { 78 | series = last.SignSeries + 1 79 | } else { 80 | series = 1 81 | } 82 | up := rand.Int()%15 + 15 // 基础积分15, 随机积分15 83 | up += series / 2 // 每连续签到2天多获得1积分 84 | saveLastSignTime(groupMessage.GroupCode, groupMessage.Sender.Uin, dayStr, series) 85 | points := loadPoint(groupMessage.GroupCode, groupMessage.Sender.Uin) 86 | incPoint(groupMessage.GroupCode, groupMessage.Sender.Uin, up) 87 | client.ReplyText( 88 | groupMessage, 89 | fmt.Sprintf( 90 | "签到成功 : \n"+ 91 | " 连续签到 : %v 天\n"+ 92 | " 获得积分 : %v \n"+ 93 | " 积分合计 : %v \n\n" + 94 | "再接再厉, 连续签到会让获得的积分变多喔", 95 | series, up, points.Point+up, 96 | ), 97 | ) 98 | } 99 | 100 | func rob(client *mirai.Client, groupMessage *message.GroupMessage) { 101 | at := client.MessageFirstAt(groupMessage) 102 | if at == 0 { 103 | client.ReplyText(groupMessage, "您需要发送 打劫并@一个人 才能打劫他人积分") 104 | return 105 | } 106 | lock, err := redis.TryLock(fmt.Sprintf("CIRCEL::LOCK::%v", groupMessage.GroupCode), time.Second*5, time.Second*15) 107 | if err != nil { 108 | return 109 | } 110 | defer lock.Unlock() 111 | day := time.Now() 112 | dayStr := day.Format("2006-01-02") 113 | timeKey := fmt.Sprintf("CIRCEL::ROB::%v::%v::%v", dayStr, groupMessage.GroupCode, groupMessage.Sender.Uin) 114 | _, err = redis.GetStringError(timeKey) 115 | if err == nil { 116 | client.ReplyText(groupMessage, "每天只能打劫一次") 117 | return 118 | } 119 | if err == redis.Nil { 120 | srcPoints := loadPoint(groupMessage.GroupCode, groupMessage.Sender.Uin) 121 | dstPoints := loadPoint(groupMessage.GroupCode, at) 122 | if 30 >= dstPoints.Point { 123 | client.ReplyText(groupMessage, "他已经没有钱可以被打劫了") 124 | return 125 | } 126 | if rand.Int()%100 < 10 { 127 | if redis.SetString(timeKey, "1", time.Hour*24) { 128 | incPoint(groupMessage.GroupCode, groupMessage.Sender.Uin, 100) 129 | client.ReplyText( 130 | groupMessage, 131 | fmt.Sprintf("打劫时被狗咬, 丢失 %v 积分, 积分合计 : %v", 100, srcPoints.Point-100), 132 | ) 133 | } 134 | return 135 | } 136 | if redis.SetString(timeKey, "1", time.Hour*24) { 137 | inc := rand.Int() % 25 138 | incPoint(groupMessage.GroupCode, at, -inc) 139 | incPoint(groupMessage.GroupCode, groupMessage.Sender.Uin, inc) 140 | client.ReplyText( 141 | groupMessage, 142 | fmt.Sprintf("打劫到 %v 积分, 积分合计 : %v", inc, srcPoints.Point+inc), 143 | ) 144 | } 145 | } 146 | } 147 | 148 | func lastSignTime(groupCode int64, uin int64) *SignTime { 149 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 150 | defer cancel() 151 | coll := mongo.Collection("game.circle.lastSign") 152 | cur, err := coll.Find(ctx, bson.M{"groupCode": groupCode, "uin": uin}) 153 | if err != nil { 154 | panic(err) 155 | } 156 | defer cur.Close(ctx) 157 | if cur.Next(ctx) { 158 | var s SignTime 159 | err = cur.Decode(&s) 160 | if err != nil { 161 | panic(err) 162 | } 163 | return &s 164 | } else { 165 | return nil 166 | } 167 | } 168 | 169 | func saveLastSignTime(code int64, uin int64, dayStr string, series int) { 170 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 171 | defer cancel() 172 | coll := mongo.Collection("game.circle.lastSign") 173 | _, err := coll.UpdateOne( 174 | ctx, 175 | bson.M{"uin": uin, "groupCode": code}, 176 | bson.M{"$set": bson.M{"signSeries": series, "lastDay": dayStr}}, 177 | options.Update().SetUpsert(true), 178 | ) 179 | if err != nil { 180 | panic(err) 181 | } 182 | } 183 | 184 | func loadPoint(groupCode int64, uin int64) Points { 185 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 186 | defer cancel() 187 | coll := mongo.Collection("game.circle.points") 188 | cur, err := coll.Find(ctx, bson.M{"groupCode": groupCode, "uin": uin}) 189 | if err != nil { 190 | panic(err) 191 | } 192 | defer cur.Close(ctx) 193 | if cur.Next(ctx) { 194 | var s Points 195 | err = cur.Decode(&s) 196 | if err != nil { 197 | panic(err) 198 | } 199 | return s 200 | } else { 201 | return Points{ 202 | GroupCode: groupCode, 203 | Uin: uin, 204 | Point: 0, 205 | } 206 | } 207 | } 208 | 209 | func incPoint(groupCode int64, uin int64, inc int) { 210 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 211 | defer cancel() 212 | coll := mongo.Collection("game.circle.points") 213 | _, err := coll.UpdateOne( 214 | ctx, 215 | bson.M{"uin": uin, "groupCode": groupCode}, 216 | bson.M{"$inc": bson.M{"point": inc}}, 217 | options.Update().SetUpsert(true), 218 | ) 219 | if err != nil { 220 | panic(err) 221 | } 222 | } 223 | 224 | type SignTime struct { 225 | GroupCode int64 226 | Uin int64 227 | SignSeries int 228 | LastDay string 229 | } 230 | 231 | type Points struct { 232 | GroupCode int64 233 | Uin int64 234 | Point int 235 | } 236 | -------------------------------------------------------------------------------- /plugins/games/farm/mapper.go: -------------------------------------------------------------------------------- 1 | package farm 2 | 3 | import ( 4 | "context" 5 | "github.com/niuhuan/mirai-bot/database/mongo" 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/mongo/options" 8 | "math" 9 | "time" 10 | ) 11 | 12 | // 用户库存 13 | 14 | func stock(groupCode int64, uin int64) Stock { 15 | var stock Stock 16 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 17 | defer cancel() 18 | stockCollection := mongo.Collection("game.farm.stock") 19 | cur, _ := stockCollection.Find(ctx, bson.M{"groupCode": groupCode, "uin": uin}) 20 | defer cur.Close(ctx) 21 | if cur.Next(ctx) { 22 | cur.Decode(&stock) 23 | } else { 24 | stock.GroupCode = groupCode 25 | stock.Uin = uin 26 | stock.CropCount = map[string]int{} 27 | stockUpdate(stock) 28 | } 29 | return stock 30 | } 31 | 32 | func stockUpdate(stock Stock) { 33 | cropCount := bson.M{} 34 | for k, v := range stock.CropCount { 35 | cropCount[k] = v 36 | } 37 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 38 | defer cancel() 39 | user := bson.M{"uin": stock.Uin, "groupCode": stock.GroupCode} 40 | update := bson.M{"$set": bson.M{"cropCount": cropCount}} 41 | stockCollection := mongo.Collection("game.farm.stock") 42 | stockCollection.UpdateOne(ctx, user, update, options.Update().SetUpsert(true)) 43 | } 44 | 45 | type Stock struct { 46 | Uin int64 47 | GroupCode int64 48 | CropCount map[string]int 49 | } 50 | 51 | // 用户资产 52 | 53 | func assets(groupCode int64, uin int64) Assets { 54 | var assets Assets 55 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 56 | defer cancel() 57 | assetsCollection := mongo.Collection("game.farm.assets") 58 | cur, _ := assetsCollection.Find(ctx, bson.M{"groupCode": groupCode, "uin": uin}) 59 | defer cur.Close(ctx) 60 | if cur.Next(ctx) { 61 | cur.Decode(&assets) 62 | } else { 63 | assets.GroupCode = groupCode 64 | assets.Uin = uin 65 | assets.Exp = 0 66 | assets.Coins = 3000 67 | assets.Fields = 1 68 | assets.Ponds = 1 69 | assetsCollection.InsertOne(ctx, bson.M{ 70 | "uin": assets.Uin, 71 | "groupCode": assets.GroupCode, 72 | "exp": assets.Exp, 73 | "coins": assets.Coins, 74 | "fields": assets.Fields, 75 | "ponds": assets.Ponds, 76 | }) 77 | } 78 | return assets 79 | } 80 | 81 | func assetsCoinsInc(code int64, uin int64, inc int64) { 82 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 83 | defer cancel() 84 | assetsCollection := mongo.Collection("game.farm.assets") 85 | assetsCollection.UpdateOne(ctx, bson.M{"uin": uin, "groupCode": code}, bson.M{"$inc": bson.M{"coins": inc}}) 86 | } 87 | 88 | func assetsFieldInc(code int64, uin int64, inc int) { 89 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 90 | defer cancel() 91 | assetsCollection := mongo.Collection("game.farm.assets") 92 | assetsCollection.UpdateOne(ctx, bson.M{"uin": uin, "groupCode": code}, bson.M{"$inc": bson.M{"fields": inc}}) 93 | } 94 | 95 | func assetsExpInc(code int64, uin int64, up int64) { 96 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 97 | defer cancel() 98 | assetsCollection := mongo.Collection("game.farm.assets") 99 | assetsCollection.UpdateOne(ctx, bson.M{"uin": uin, "groupCode": code}, bson.M{"$inc": bson.M{"exp": up}}) 100 | } 101 | 102 | type Assets struct { 103 | GroupCode int64 104 | Uin int64 105 | Exp int64 106 | Coins int64 107 | Fields int 108 | Ponds int 109 | } 110 | 111 | // 场地 112 | 113 | func land(groupCode int64, uin int64) Land { 114 | var land Land 115 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 116 | defer cancel() 117 | landCollection := mongo.Collection("game.farm.land") 118 | cur, _ := landCollection.Find(ctx, bson.M{"groupCode": groupCode, "uin": uin}) 119 | defer cur.Close(ctx) 120 | if cur.Next(ctx) { 121 | cur.Decode(&land) 122 | } else { 123 | land.GroupCode = groupCode 124 | land.Uin = uin 125 | land.Fields = map[string]Field{} 126 | landUpdate(land) 127 | } 128 | return land 129 | } 130 | 131 | func landUpdate(land Land) { 132 | fields := bson.M{} 133 | for k, v := range land.Fields { 134 | watered := bson.M{} 135 | for state, uin := range v.Watered { 136 | watered[state] = uin 137 | } 138 | fields[k] = bson.M{ 139 | "level": v.Level, 140 | "plantTime": v.PlantTime, 141 | "watered": watered, 142 | "stealer": v.Stealer, 143 | "alerted": v.Alerted, 144 | } 145 | } 146 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 147 | defer cancel() 148 | user := bson.M{"groupCode": land.GroupCode, "uin": land.Uin} 149 | update := bson.M{"$set": bson.M{"fields": fields}} 150 | landCollection := mongo.Collection("game.farm.land") 151 | landCollection.UpdateOne(ctx, user, update, options.Update().SetUpsert(true)) 152 | } 153 | 154 | type Field struct { 155 | Level int // 种的什么果实 156 | PlantTime int64 // 种植的时间 157 | Watered map[string]int64 // 浇水 158 | Stealer []int64 // 偷菜的人 159 | Alerted []int64 // 被狗咬的人 160 | } 161 | 162 | type Land struct { 163 | GroupCode int64 164 | Uin int64 165 | Fields map[string]Field 166 | } 167 | 168 | // pets 169 | 170 | func pets(groupCode int64, uin int64) Pets { 171 | var pets Pets 172 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 173 | defer cancel() 174 | landCollection := mongo.Collection("game.farm.pets") 175 | cur, _ := landCollection.Find(ctx, bson.M{"groupCode": groupCode, "uin": uin}) 176 | defer cur.Close(ctx) 177 | if cur.Next(ctx) { 178 | cur.Decode(&pets) 179 | } else { 180 | pets.GroupCode = groupCode 181 | pets.Uin = uin 182 | pets.Pets = []int{} 183 | petsUpdate(pets) 184 | } 185 | return pets 186 | } 187 | 188 | func petsUpdate(pets Pets) { 189 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 190 | defer cancel() 191 | user := bson.M{"groupCode": pets.GroupCode, "uin": pets.Uin} 192 | update := bson.M{"$set": bson.M{"pets": pets.Pets}} 193 | landCollection := mongo.Collection("game.farm.pets") 194 | landCollection.UpdateOne(ctx, user, update, options.Update().SetUpsert(true)) 195 | } 196 | 197 | type Pets struct { 198 | GroupCode int64 199 | Uin int64 200 | Pets []int 201 | } 202 | 203 | // 等级/金额计算公式 204 | 205 | func level(exp int64) int { 206 | for i := 21; i > 0; i-- { 207 | if exp >= (int64(math.Pow(float64(i), float64(4)))-1)/5 { 208 | return i 209 | } 210 | } 211 | return 0 212 | } 213 | 214 | func fieldPrice(currentFieldCount int) int64 { 215 | baseNumber := float64(currentFieldCount) 216 | return int64(math.Pow(2.5, .75*baseNumber)*baseNumber) * 1000 217 | } 218 | 219 | func cropState(crop Crop, plantTime int64, now int64) (state int, emoji string) { 220 | width := now - plantTime 221 | for i := 0; i < len(crop.StepHours); i++ { 222 | state = i 223 | emoji = crop.StepEmojis[i] 224 | band := 3600 * int64(crop.StepHours[i]) 225 | if width > band { 226 | if i == len(crop.StepHours)-1 { 227 | state = MATURE 228 | emoji = crop.FruitEmoji 229 | break 230 | } else { 231 | width -= band 232 | continue 233 | } 234 | } else { 235 | break 236 | } 237 | } 238 | return 239 | } 240 | 241 | func now() int64 { 242 | return time.Now().Unix() 243 | } 244 | 245 | const MATURE = -1 246 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII= 2 | github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= 3 | github.com/Mrs4s/MiraiGo v0.0.0-20220630160133-a39b3fdd962f h1:PHOwN3/cEL/zoBpcJJXwx1mJ1NL901zrt2mvlQUO5BA= 4 | github.com/Mrs4s/MiraiGo v0.0.0-20220630160133-a39b3fdd962f/go.mod h1:Ow7nlaVS5FztyjrTlTRSG7HLpTNmgINMluHRCgKunDI= 5 | github.com/RomiChan/protobuf v0.1.1-0.20220624030127-3310cba9dbc0 h1:+UGPBYVjssFsdahLJIiNPwpmmwgl/OaVdv1oc5NonC0= 6 | github.com/RomiChan/protobuf v0.1.1-0.20220624030127-3310cba9dbc0/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA= 7 | github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c h1:cNPOdTNiVwxLpROLjXCgbIPvdkE+BwvxDvgmdYmWx6Q= 8 | github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c/go.mod h1:KqZzu7slNKROh3TSYEH/IUMG6f4M+1qubZ5e52QypsE= 9 | github.com/bits-and-blooms/bitset v1.2.1 h1:M+/hrU9xlMp7t4TyTDQW97d3tRPVuKFC6zBEK16QnXY= 10 | github.com/bits-and-blooms/bitset v1.2.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak= 15 | github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4= 16 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 17 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= 18 | github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= 19 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 20 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 21 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 22 | github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= 23 | github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= 24 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 25 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 26 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 27 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 28 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 29 | github.com/klauspost/compress v1.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok= 30 | github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 31 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 32 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 33 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 34 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 35 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 36 | github.com/maruel/rs v1.1.0 h1:dh4OceAF5yD06EASOrb+DS358LI4g0B90YApSdjCP6U= 37 | github.com/maruel/rs v1.1.0/go.mod h1:vzwMjzSJJxLIXmU62qHj6O5QRn5kvCKxFrfaFCxBcUY= 38 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 39 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 40 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 41 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 42 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 43 | github.com/niuhuan/mirai-framework v0.0.0-20220706065221-8078f2c4b4fa h1:L1EFDp435mzVais04ivc+WHPzXfSHq1PWkO3egpnYz4= 44 | github.com/niuhuan/mirai-framework v0.0.0-20220706065221-8078f2c4b4fa/go.mod h1:14VRTYS8TOICnhTs4xIIGxmYMguO1FFtvKO28eNAYVo= 45 | github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= 46 | github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 47 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 48 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 49 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 52 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 53 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= 54 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= 55 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 56 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 57 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 58 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 59 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 60 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 61 | github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= 62 | github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 63 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 64 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 65 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 66 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 67 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 68 | github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d h1:4x1FeGJRB00cvxnKXnRJDT89fvG/Lzm2ecm0vlr/qDs= 69 | github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d/go.mod h1:uSELzeIcTceNCgzbKdJuJa0ouCqqtkyzL+6bnA3rM+M= 70 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 71 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 72 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 73 | github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= 74 | github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= 75 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 76 | github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= 77 | github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= 78 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 79 | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= 80 | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= 81 | go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c= 82 | go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= 83 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 84 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 85 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 86 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 87 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 88 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 89 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 90 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 91 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 92 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 93 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= 95 | golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 96 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 97 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 100 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo= 102 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 104 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 105 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 106 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 107 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 108 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 109 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 110 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 111 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 112 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 113 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 114 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 115 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 116 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 117 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 118 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 119 | rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= 120 | rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= 121 | -------------------------------------------------------------------------------- /plugins/games/farm/farm.go: -------------------------------------------------------------------------------- 1 | package farm 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Mrs4s/MiraiGo/message" 6 | "github.com/niuhuan/mirai-bot/database/redis" 7 | "github.com/niuhuan/mirai-bot/utils" 8 | "github.com/niuhuan/mirai-framework" 9 | "math" 10 | "math/rand" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | const name = "农场" 18 | 19 | func NewPluginInstance() *mirai.Plugin { 20 | return &mirai.Plugin{ 21 | Id: func() string { 22 | return "FARM" 23 | }, 24 | Name: func() string { 25 | return name 26 | }, 27 | OnPrivateMessage: func(client *mirai.Client, privateMessage *message.PrivateMessage) bool { 28 | if client.MessageContent(privateMessage) == name { 29 | client.ReplyText(privateMessage, "农场功能只能在群中使用") 30 | return true 31 | } 32 | return false 33 | }, 34 | OnGroupMessage: func(client *mirai.Client, groupMessage *message.GroupMessage) bool { 35 | content := client.MessageContent(groupMessage) 36 | if strings.EqualFold(content, "农场") { 37 | printMenu(client, groupMessage) 38 | return true 39 | } 40 | if strings.EqualFold(content, "农场帮助") { 41 | printHelp(client, groupMessage) 42 | return true 43 | } 44 | if strings.EqualFold(content, "农场商店") { 45 | printCrops(client, groupMessage) 46 | return true 47 | } 48 | if strings.EqualFold(content, "守卫商店") { 49 | printPets(client, groupMessage) 50 | return true 51 | } 52 | if strings.EqualFold(content, "购买种子") { 53 | printHelpBuy(client, groupMessage) 54 | return true 55 | } 56 | if strings.EqualFold(content, "查询种子") { 57 | printHelpSearch(client, groupMessage) 58 | return true 59 | } 60 | if strings.EqualFold(content, "购买守卫") { 61 | printHelpBuy(client, groupMessage) 62 | return true 63 | } 64 | if strings.EqualFold(content, "查询守卫") { 65 | printHelpSearch(client, groupMessage) 66 | return true 67 | } 68 | if strings.EqualFold(content, "种植") { 69 | printHelpPlant(client, groupMessage) 70 | return true 71 | } 72 | if strings.EqualFold(content, "偷菜") { 73 | printHelpSteal(client, groupMessage) 74 | return true 75 | } 76 | if strings.EqualFold(content, "我的农场") { 77 | printSelf(client, groupMessage) 78 | return true 79 | } 80 | if strings.EqualFold(content, "农场等级") { 81 | printLevels(client, groupMessage) 82 | return true 83 | } 84 | if strings.EqualFold(content, "购买土地") { 85 | buyField(client, groupMessage) 86 | return true 87 | } 88 | searchRegex, _ := regexp.Compile("^查询([\\s]+)?(\\p{Han}+)([\\s]+)?$") 89 | if searchRegex.MatchString(content) { 90 | sub := searchRegex.FindStringSubmatch(content) 91 | name := sub[2] 92 | return search(client, groupMessage, name) 93 | } 94 | buyRegex, _ := regexp.Compile("^购?买([\\s]+)?(\\p{Han}+)([\\s]+)?(\\d{1,5})?([\\s]+)?$") 95 | if buyRegex.MatchString(content) { 96 | sub := buyRegex.FindStringSubmatch(content) 97 | name := sub[2] 98 | var number int 99 | if len(sub[4]) > 0 { 100 | number, _ = strconv.Atoi(sub[4]) 101 | } else { 102 | number = 1 103 | } 104 | return buy(client, groupMessage, name, number) 105 | } 106 | plantRegex, _ := regexp.Compile("^播?种植?([\\s]+)?(\\p{Han}+)([\\s]+)?([\\s]+)?$") 107 | if plantRegex.MatchString(content) { 108 | sub := plantRegex.FindStringSubmatch(content) 109 | name := sub[2] 110 | return plant(client, groupMessage, name) 111 | } 112 | if strings.EqualFold(content, "收菜") { 113 | collect(client, groupMessage) 114 | return true 115 | } 116 | if strings.Index(content, "偷菜") == 0 { 117 | steal(client, groupMessage) 118 | return true 119 | } 120 | if strings.Index(content, "浇水") == 0 { 121 | water(client, groupMessage) 122 | return true 123 | } 124 | return false 125 | }, 126 | } 127 | } 128 | 129 | func printMenu(client *mirai.Client, groupMessage *message.GroupMessage) { 130 | client.ReplyText(groupMessage, 131 | " === 农场菜单 === \n\n"+ 132 | "农场帮助\n"+ 133 | "农场商店 守卫商店\n"+ 134 | "购买种子 查询种子\n"+ 135 | "购买守卫 查询守卫\n"+ 136 | "种植 收菜 偷菜 浇水\n"+ 137 | "我的农场 农场等级\n"+ 138 | "购买土地 ") 139 | } 140 | 141 | func printHelp(client *mirai.Client, groupMessage *message.GroupMessage) { 142 | client.ReplyText(groupMessage, 143 | "  农场: 机器人主人无聊开发的小游戏\n\n"+ 144 | "货币系统: "+emojiSun+"(阳光)是农场中的基本货币\n\n"+ 145 | "升级系统: "+emojiExp+"(经验值)可以提高农场等级\n\n"+ 146 | "  作物: 种植种子, 经过一段时间可以 收获"+emojiSun+"(阳光)和"+emojiExp+"(经验值)\n\n"+ 147 | "  土地: 土地越多, 可以同时种的种子个数\n\n"+ 148 | "  偷菜: 赚点小外快?\n\n"+ 149 | "  查询: 查询种子或者其他物品的功能 例如'查询土豆'\n\n"+ 150 | " 守卫: 特效宠物, 防止被偷, 打盹时触发减半\n"+ 151 | " 浇水: 获得经验值和金币, 并且增加产量, 一株植物在成熟之前每个阶段可以浇水一次") 152 | } 153 | 154 | func printHelpBuy(client *mirai.Client, groupMessage *message.GroupMessage) { 155 | client.ReplyText(groupMessage, 156 | "发送 \"购买+种子名称\" 购买相应种子, 例如 \"购买土豆\".\n\n"+ 157 | "发送 \"购买+种子名称+数量\" 购买多个种子, 例如 \"购买土豆15\".\n\n"+ 158 | "发送 \"购买+守卫名称\" 购买相应守卫, 例如 \"购买"+petList[0].Name+"\".\n\n"+ 159 | "使用\"农场商店\"或者\"守卫商店\"查看列表") 160 | } 161 | 162 | func printHelpSearch(client *mirai.Client, groupMessage *message.GroupMessage) { 163 | client.ReplyText(groupMessage, 164 | "发送 \"查询+种子名称\" 查询预计收益, 例如 \"查询土豆\".\n\n"+ 165 | "发送 \"查询+守卫名称\" 查询预计收益, 例如 \"查询"+petList[0].Name+"\".") 166 | } 167 | 168 | func printHelpPlant(client *mirai.Client, groupMessage *message.GroupMessage) { 169 | client.ReplyText(groupMessage, 170 | "发送 \"种+种子名称\" 种植作物, 例如 \"种土豆\".") 171 | } 172 | 173 | func printHelpSteal(client *mirai.Client, groupMessage *message.GroupMessage) { 174 | client.ReplyText(groupMessage, 175 | "发送 \"偷菜+@一个人\" 可以偷菜, 例如 \"偷菜@张三\".") 176 | } 177 | 178 | func printSelf(client *mirai.Client, groupMessage *message.GroupMessage) { 179 | assets := assets(sendUser(groupMessage)) 180 | client.ReplyText(groupMessage, fmt.Sprintf( 181 | "阳光 %s %d\n"+ 182 | "土地 %s️ %d\n"+ 183 | "经验 %s %d\n"+ 184 | "等级 %s️ %d\n", 185 | emojiSun, assets.Coins, 186 | emojiField, assets.Fields, 187 | emojiExp, assets.Exp, emojiLevel, level(assets.Exp), 188 | )) 189 | } 190 | 191 | func printLevels(client *mirai.Client, groupMessage *message.GroupMessage) { 192 | assets := assets(sendUser(groupMessage)) 193 | level := level(assets.Exp) 194 | builder := strings.Builder{} 195 | builder.WriteString(fmt.Sprintf("当前农场等级为%d级(%s%d), ", level, emojiExp, assets.Exp)) 196 | if level >= 20 { 197 | builder.WriteString("您已满级.") 198 | } else { 199 | builder.WriteString(fmt.Sprintf("距离升级还需要%s%d", emojiExp, ((int64(math.Pow(float64(level+1), float64(4)))-1)/5)-assets.Exp)) 200 | } 201 | client.ReplyText(groupMessage, builder.String()) 202 | } 203 | 204 | func buyField(client *mirai.Client, groupMessage *message.GroupMessage) { 205 | // 加锁 206 | lock, err := lockUnit(sendUser(groupMessage)) 207 | if err != nil { 208 | panic(err) 209 | } 210 | defer lock.Unlock() 211 | // 212 | assets := assets(sendUser(groupMessage)) 213 | fieldPrice := fieldPrice(assets.Fields) 214 | if assets.Coins >= fieldPrice { 215 | assetsCoinsInc(groupMessage.GroupCode, groupMessage.Sender.Uin, -fieldPrice) 216 | assetsFieldInc(groupMessage.GroupCode, groupMessage.Sender.Uin, 1) 217 | client.ReplyText(groupMessage, "购买成功 土地+1\n"+ 218 | fmt.Sprintf("%s ↓ %d => %d", emojiSun, fieldPrice, assets.Coins-fieldPrice)) 219 | } else { 220 | client.ReplyText(groupMessage, fmt.Sprintf("购买第%d块土地需要%s%d", assets.Fields+1, emojiSun, fieldPrice)) 221 | } 222 | } 223 | 224 | func search(client *mirai.Client, groupMessage *message.GroupMessage, name string) bool { 225 | for _, crop := range cropList { 226 | if strings.EqualFold(crop.Name, name) { 227 | searchCrop(client, groupMessage, crop) 228 | return true 229 | } 230 | } 231 | return false 232 | } 233 | 234 | func searchCrop(client *mirai.Client, groupMessage *message.GroupMessage, crop Crop) { 235 | client.ReplyText(groupMessage, 236 | fmt.Sprintf("%s %s, %d级别作物, 种子售价%s%d, 成熟时间%d小时.", crop.FruitEmoji, crop.Name, crop.Level, emojiSun, crop.SeedPrice, utils.SumInts(crop.StepHours))+ 237 | fmt.Sprintf(" 每株结出果实%d到%d枚, 预计最少收益%s%d+%s%d。", 238 | crop.FruitsMin, crop.FruitsMax, 239 | emojiSun, crop.FruitsMin*crop.FruitPrice, 240 | emojiExp, crop.FruitsMin*crop.FruitExp)) 241 | } 242 | 243 | func printCrops(client *mirai.Client, groupMessage *message.GroupMessage) { 244 | // 取得数据 245 | assets := assets(sendUser(groupMessage)) 246 | level := level(assets.Exp) 247 | stock := stock(sendUser(groupMessage)) 248 | var builder strings.Builder 249 | builder.WriteString(emojiLevel + "       " + emojiSun + "  " + emojiStock + "\n") 250 | for _, crop := range cropList { 251 | builder.WriteString(fmt.Sprintf("%02d %s %s %d ", crop.Level, crop.FruitEmoji, crop.Name, crop.SeedPrice)) 252 | if crop.SeedPrice < 10 { 253 | builder.WriteString(" ") 254 | } else if crop.SeedPrice < 100 { 255 | builder.WriteString(" ") 256 | } else if crop.SeedPrice < 1000 { 257 | builder.WriteString(" ") 258 | } 259 | if count, ok := stock.CropCount[strconv.Itoa(crop.Level)]; ok { 260 | builder.WriteString(fmt.Sprintf("%d", count)) 261 | } else { 262 | builder.WriteString("0") 263 | } 264 | builder.WriteString("\n") 265 | } 266 | builder.WriteString(fmt.Sprintf("\n%s %d   %s %d", emojiLevel, level, emojiSun, assets.Coins)) 267 | client.ReplyText(groupMessage, builder.String()) 268 | } 269 | 270 | func printPets(client *mirai.Client, groupMessage *message.GroupMessage) { 271 | // 取得数据 272 | assets := assets(sendUser(groupMessage)) 273 | level := level(assets.Exp) 274 | pets := pets(sendUser(groupMessage)) 275 | var builder strings.Builder 276 | builder.WriteString(emojiLevel + "        " + emojiSun + " " + emojiStock + "\n") 277 | for _, pet := range petList { 278 | builder.WriteString(fmt.Sprintf("%02d %s %s %d ", pet.Level, emojiDog, pet.Name, pet.Price)) 279 | if utils.ContainsInt(pets.Pets, pet.Level) { 280 | builder.WriteString("🈶️") 281 | } else { 282 | builder.WriteString("🈚️") 283 | } 284 | builder.WriteString("\n") 285 | } 286 | builder.WriteString(fmt.Sprintf("\n%s %d   %s %d", emojiLevel, level, emojiSun, assets.Coins)) 287 | client.ReplyText(groupMessage, builder.String()) 288 | } 289 | 290 | func buy(client *mirai.Client, groupMessage *message.GroupMessage, name string, number int) bool { 291 | // 加锁 292 | lock, err := lockUnit(sendUser(groupMessage)) 293 | if err != nil { 294 | panic(err) 295 | } 296 | defer lock.Unlock() 297 | // 298 | for _, crop := range cropList { 299 | if strings.EqualFold(crop.Name, name) { 300 | buyCrop(client, groupMessage, crop, number) 301 | return true 302 | } 303 | } 304 | for _, pet := range petList { 305 | if strings.EqualFold(pet.Name, name) { 306 | buyPet(client, groupMessage, pet) 307 | return true 308 | } 309 | } 310 | return false 311 | } 312 | 313 | func buyCrop(client *mirai.Client, groupMessage *message.GroupMessage, crop Crop, number int) { 314 | assets := assets(sendUser(groupMessage)) 315 | level := level(assets.Exp) 316 | stock := stock(sendUser(groupMessage)) 317 | if crop.Level > level { 318 | client.ReplyText(groupMessage, fmt.Sprintf("您不能购买超过您自身等级的作物种子, 购买%s需要%d级, 您当前为%d级. ", crop.Name, crop.Level, level)) 319 | return 320 | } 321 | downCoin := int64(crop.SeedPrice * number) 322 | if downCoin > assets.Coins { 323 | client.ReplyText(groupMessage, fmt.Sprintf("您的阳光不足, 购买%d枚%s种子需要%d阳光, 您只有%d阳光. ", number, crop.Name, downCoin, assets.Coins)) 324 | return 325 | } 326 | inStock, _ := stock.CropCount[strconv.Itoa(crop.Level)] 327 | toInStock := inStock + number 328 | if toInStock > 99 { 329 | client.ReplyText(groupMessage, "一种种子持有量不能超过99枚") 330 | return 331 | } 332 | stock.CropCount[strconv.Itoa(crop.Level)] = toInStock 333 | stockUpdate(stock) 334 | assetsCoinsInc(assets.GroupCode, assets.Uin, -downCoin) 335 | client.ReplyText(groupMessage, fmt.Sprintf("购买成功\n\n%s ↑ %d => %d\n%s ↓ %d => %d", crop.FruitEmoji, number, toInStock, emojiSun, downCoin, assets.Coins-downCoin)) 336 | } 337 | 338 | func buyPet(client *mirai.Client, groupMessage *message.GroupMessage, pet Pet) { 339 | assets := assets(sendUser(groupMessage)) 340 | pets := pets(sendUser(groupMessage)) 341 | downCoin := int64(pet.Price) 342 | if downCoin > assets.Coins { 343 | client.ReplyText(groupMessage, fmt.Sprintf("您的阳光不足, 购买%s需要%d阳光, 您只有%d阳光. ", pet.Name, downCoin, assets.Coins)) 344 | return 345 | } 346 | if utils.ContainsInt(pets.Pets, pet.Level) { 347 | client.ReplyText(groupMessage, "您已经有了该守卫") 348 | return 349 | } 350 | pets.Pets = append(pets.Pets, pet.Level) 351 | petsUpdate(pets) 352 | assetsCoinsInc(assets.GroupCode, assets.Uin, -downCoin) 353 | client.ReplyText(groupMessage, fmt.Sprintf("购买成功\n\n%s %s\n%s ↓ %d => %d", emojiDog, pet.Name, emojiSun, downCoin, assets.Coins-downCoin)) 354 | } 355 | 356 | func plant(client *mirai.Client, groupMessage *message.GroupMessage, name string) bool { 357 | // 加锁 358 | lock, err := lockUnit(sendUser(groupMessage)) 359 | if err != nil { 360 | panic(err) 361 | } 362 | defer lock.Unlock() 363 | // 364 | for _, crop := range cropList { 365 | if strings.EqualFold(crop.Name, name) { 366 | plantCrop(client, groupMessage, crop) 367 | return true 368 | } 369 | } 370 | return false 371 | } 372 | 373 | func plantCrop(client *mirai.Client, groupMessage *message.GroupMessage, crop Crop) { 374 | now := now() 375 | builder := strings.Builder{} 376 | assets := assets(sendUser(groupMessage)) 377 | stock := stock(sendUser(groupMessage)) 378 | land := land(sendUser(groupMessage)) 379 | expUp := int64(0) 380 | for i := 0; i < assets.Fields; i++ { 381 | builder.WriteString(fmt.Sprintf("土地(%d) ", i+1)) 382 | field, _ := land.Fields[strconv.Itoa(i)] 383 | if field.Level > 0 { 384 | cropPlanted := cropMap[field.Level] 385 | _, emoji := cropState(cropPlanted, field.PlantTime, now) 386 | builder.WriteString(fmt.Sprintf("%s (%s 已存在)", emoji, cropPlanted.Name)) 387 | } else { 388 | if stock.CropCount[strconv.Itoa(crop.Level)] > 0 { 389 | stock.CropCount[strconv.Itoa(crop.Level)]-- 390 | land.Fields[strconv.Itoa(i)] = Field{ 391 | Level: crop.Level, 392 | PlantTime: now, 393 | Watered: map[string]int64{}, 394 | Stealer: []int64{}, 395 | Alerted: []int64{}, 396 | } 397 | expUp += int64(crop.FruitExp) 398 | builder.WriteString(fmt.Sprintf(" => %s", crop.FruitEmoji)) 399 | } else { 400 | builder.WriteString(fmt.Sprintf("%s种子不足", crop.Name)) 401 | } 402 | } 403 | builder.WriteString("\n") 404 | } 405 | if expUp > 0 { 406 | stockUpdate(stock) 407 | landUpdate(land) 408 | assetsExpInc(assets.GroupCode, assets.Uin, expUp) 409 | builder.WriteString(fmt.Sprintf("\n%s ↑ %d => %d", emojiExp, expUp, assets.Exp+expUp)) 410 | } 411 | client.ReplyText(groupMessage, builder.String()) 412 | } 413 | 414 | func collect(client *mirai.Client, groupMessage *message.GroupMessage) { 415 | // 加锁 416 | lock, err := lockUnit(sendUser(groupMessage)) 417 | if err != nil { 418 | panic(err) 419 | } 420 | defer lock.Unlock() 421 | // 422 | now := now() 423 | builder := strings.Builder{} 424 | assets := assets(sendUser(groupMessage)) 425 | land := land(sendUser(groupMessage)) 426 | // 427 | expUp := int64(0) 428 | coinsUp := int64(0) 429 | var waterSet []int64 430 | var stealerSet []int64 431 | for i := 0; i < assets.Fields; i++ { 432 | builder.WriteString(fmt.Sprintf("土地(%d) ", i+1)) 433 | field, _ := land.Fields[strconv.Itoa(i)] 434 | if field.Level > 0 { 435 | cropPlanted := cropMap[field.Level] 436 | state, emoji := cropState(cropPlanted, field.PlantTime, now) 437 | if state == MATURE { 438 | var fruitNumber int 439 | if cropPlanted.FruitsMax > cropPlanted.FruitsMin { 440 | fruitNumber = (rand.Int() % (1 + cropPlanted.FruitsMax - cropPlanted.FruitsMin)) + cropPlanted.FruitsMin 441 | } else { 442 | fruitNumber = cropPlanted.FruitsMax 443 | } 444 | fruitNumber += len(field.Watered) * 1 445 | for _, water := range field.Watered { 446 | if !utils.ContainsInt64(waterSet, water) && groupMessage.Sender.Uin != water { 447 | waterSet = append(waterSet, water) 448 | } 449 | } 450 | fruitNumber -= len(field.Stealer) * 1 451 | builder.WriteString(fmt.Sprintf("%s (%s %d枚)", emoji, cropPlanted.Name, fruitNumber)) 452 | if len(field.Stealer) > 0 { 453 | builder.WriteString(fmt.Sprintf("(被偷%d枚)", len(field.Stealer)*1)) 454 | for _, stealer := range field.Stealer { 455 | if !utils.ContainsInt64(stealerSet, stealer) { 456 | stealerSet = append(stealerSet, stealer) 457 | } 458 | } 459 | } 460 | expUp += int64(fruitNumber * cropPlanted.FruitExp) 461 | coinsUp += int64(fruitNumber * cropPlanted.FruitPrice) 462 | delete(land.Fields, strconv.Itoa(i)) 463 | } else { 464 | if _, ok := field.Watered[strconv.Itoa(state)]; ok { 465 | builder.WriteString(fmt.Sprintf("%s (%s 未成熟)", emoji+emojiWater, cropPlanted.Name)) 466 | } else { 467 | builder.WriteString(fmt.Sprintf("%s (%s 未成熟)", emoji, cropPlanted.Name)) 468 | } 469 | } 470 | } else { 471 | builder.WriteString(fmt.Sprintf("未种植")) 472 | } 473 | builder.WriteString("\n") 474 | } 475 | if expUp > 0 { 476 | landUpdate(land) 477 | assetsExpInc(assets.GroupCode, assets.Uin, expUp) 478 | assetsCoinsInc(assets.GroupCode, assets.Uin, coinsUp) 479 | if len(waterSet) > 0 { 480 | builder.WriteString("\n") 481 | builder.WriteString("帮你浇水的群友 : \n") 482 | for _, water := range waterSet { 483 | builder.WriteString(" " + client.CardNameInGroup(groupMessage.GroupCode, water) + "\n") 484 | } 485 | } 486 | if len(stealerSet) > 0 { 487 | builder.WriteString("\n") 488 | builder.WriteString("偷你菜的群友 : \n") 489 | for _, stealer := range stealerSet { 490 | builder.WriteString(" " + client.CardNameInGroup(groupMessage.GroupCode, stealer) + "\n") 491 | } 492 | } 493 | builder.WriteString(fmt.Sprintf("\n%s ↑ %d => %d\n%s ↑ %d => %d", emojiExp, expUp, assets.Exp+expUp, emojiSun, coinsUp, assets.Coins+coinsUp)) 494 | } 495 | client.ReplyText(groupMessage, builder.String()) 496 | } 497 | 498 | func steal(client *mirai.Client, groupMessage *message.GroupMessage) { 499 | // 加锁 500 | lock, err := lockUnit(sendUser(groupMessage)) 501 | if err != nil { 502 | panic(err) 503 | } 504 | defer lock.Unlock() 505 | // 506 | firstAt := client.MessageFirstAt(groupMessage) 507 | if firstAt > 0 { 508 | builder := strings.Builder{} 509 | uin := groupMessage.Sender.Uin 510 | targetUin := firstAt 511 | if uin == targetUin { 512 | client.ReplyText(groupMessage, "你不能偷自己的菜") 513 | return 514 | } else { 515 | builder.WriteString("偷偷进入了 " + client.CardNameInGroup(groupMessage.GroupCode, targetUin) + "的农场\n\n") 516 | } 517 | now := now() 518 | targetAssets := assets(groupMessage.GroupCode, targetUin) 519 | 520 | // 判断被偷的人有没有狗 521 | targetPets := pets(groupMessage.GroupCode, targetUin) 522 | if utils.ContainsInt(targetPets.Pets, 1) { 523 | dog, _ := petMap[1] 524 | builder.WriteString(fmt.Sprintf("%s%s ", emojiDog, dog.Name)) 525 | sleepy := (int(math.Abs(float64(rand.Int63()-targetUin))) % 100) < 50 526 | alertPercentage := 20 527 | if sleepy { 528 | builder.WriteString("正在瞌睡 ") 529 | alertPercentage /= 2 530 | } 531 | alert := (int(math.Abs(float64(rand.Int63()-targetUin))) % 100) < alertPercentage 532 | if alert { 533 | assetsCoinsInc(groupMessage.GroupCode, groupMessage.Sender.Uin, -100) 534 | builder.WriteString("把你咬了 损失 " + emojiSun + "100") 535 | client.ReplyText(groupMessage, builder.String()) 536 | return 537 | } 538 | } 539 | 540 | targetLand := land(groupMessage.GroupCode, targetUin) 541 | expUp := int64(0) 542 | coinsUp := int64(0) 543 | for i := 0; i < targetAssets.Fields; i++ { 544 | builder.WriteString(fmt.Sprintf("土地(%d) ", i+1)) 545 | field, _ := targetLand.Fields[strconv.Itoa(i)] 546 | if field.Level > 0 { 547 | cropPlanted := cropMap[field.Level] 548 | state, emoji := cropState(cropPlanted, field.PlantTime, now) 549 | if state != MATURE { 550 | builder.WriteString(fmt.Sprintf("%s (%s 未成熟)", emoji, cropPlanted.Name)) 551 | } else { 552 | if utils.ContainsInt64(field.Stealer, groupMessage.Sender.Uin) { 553 | builder.WriteString(fmt.Sprintf("%s (%s 偷过了)", emoji, cropPlanted.Name)) 554 | } else if len(field.Stealer) >= 2 { 555 | builder.WriteString(fmt.Sprintf("%s (%s 快被偷光了)", emoji, cropPlanted.Name)) 556 | } else { 557 | expUp += int64(1 * cropPlanted.FruitExp) 558 | coinsUp += int64(1 * cropPlanted.FruitPrice) 559 | field.Stealer = append(field.Stealer, uin) 560 | targetLand.Fields[strconv.Itoa(i)] = field 561 | builder.WriteString(fmt.Sprintf("%s (%s %d枚)", emoji, cropPlanted.Name, 1)) 562 | } 563 | } 564 | } else { 565 | builder.WriteString(fmt.Sprintf("未种植")) 566 | } 567 | builder.WriteString("\n") 568 | } 569 | if expUp > 0 { 570 | assets := assets(sendUser(groupMessage)) 571 | landUpdate(targetLand) 572 | assetsExpInc(groupMessage.GroupCode, uin, expUp) 573 | assetsCoinsInc(groupMessage.GroupCode, uin, coinsUp) 574 | builder.WriteString(fmt.Sprintf("\n%s ↑ %d => %d\n%s ↑ %d => %d", emojiExp, expUp, assets.Exp+expUp, emojiSun, coinsUp, assets.Coins+coinsUp)) 575 | } 576 | client.ReplyText(groupMessage, builder.String()) 577 | } else { 578 | printHelpSteal(client, groupMessage) 579 | } 580 | } 581 | 582 | func water(client *mirai.Client, groupMessage *message.GroupMessage) { 583 | // 加锁 584 | lock, err := lockUnit(sendUser(groupMessage)) 585 | if err != nil { 586 | panic(err) 587 | } 588 | defer lock.Unlock() 589 | // 590 | now := now() 591 | uin := groupMessage.Sender.Uin 592 | targetUin := uin 593 | for _, element := range groupMessage.Elements { 594 | if element.Type() == message.At { 595 | if at, ok := element.(*message.AtElement); ok { 596 | targetUin = at.Target 597 | } 598 | break 599 | } 600 | } 601 | targetAssets := assets(groupMessage.GroupCode, targetUin) 602 | targetLand := land(groupMessage.GroupCode, targetUin) 603 | builder := strings.Builder{} 604 | if uin != targetUin { 605 | builder.WriteString(client.CardNameInGroup(groupMessage.GroupCode, targetUin) + "的农场\n\n") 606 | } else { 607 | builder.WriteString("浇水@一个人可以为群友浇水\n\n") 608 | } 609 | expUp := int64(0) 610 | for i := 0; i < targetAssets.Fields; i++ { 611 | builder.WriteString(fmt.Sprintf("土地(%d) ", i+1)) 612 | field, _ := targetLand.Fields[strconv.Itoa(i)] 613 | if field.Level > 0 { 614 | cropPlanted := cropMap[field.Level] 615 | state, emoji := cropState(cropPlanted, field.PlantTime, now) 616 | if state == MATURE { 617 | builder.WriteString(fmt.Sprintf("%s (%s 已成熟)", emoji, cropPlanted.Name)) 618 | } else { 619 | if _, ok := field.Watered[strconv.Itoa(state)]; ok { 620 | builder.WriteString(fmt.Sprintf("%s (%s 无需浇水)", emoji+emojiWater, cropPlanted.Name)) 621 | } else { 622 | targetLand.Fields[strconv.Itoa(i)].Watered[strconv.Itoa(state)] = uin 623 | expUp += int64(cropPlanted.FruitExp) 624 | builder.WriteString(fmt.Sprintf("%s (%s 浇水成功)", emoji+emojiRain, cropPlanted.Name)) 625 | } 626 | } 627 | } else { 628 | builder.WriteString(fmt.Sprintf("未种植")) 629 | } 630 | builder.WriteString("\n") 631 | } 632 | if expUp > 0 { 633 | assets := assets(sendUser(groupMessage)) 634 | landUpdate(targetLand) 635 | assetsExpInc(groupMessage.GroupCode, uin, expUp) 636 | builder.WriteString(fmt.Sprintf("\n%s ↑ %d => %d", emojiExp, expUp, assets.Exp+expUp)) 637 | } 638 | client.ReplyText(groupMessage, builder.String()) 639 | } 640 | 641 | func sendUser(groupMessage *message.GroupMessage) (groupCode int64, uin int64) { 642 | return groupMessage.GroupCode, groupMessage.Sender.Uin 643 | } 644 | 645 | func lockUnit(groupCode int64, uin int64) (*redis.Lock, error) { 646 | return redis.TryLock(fmt.Sprintf("BOT::GAME::FARM::%v::%v::LOCK", groupCode, uin), time.Second*5, time.Minute) 647 | } 648 | --------------------------------------------------------------------------------