Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,30 @@ nanobot [-Tadhst] ID1 ID2 ...

</details>

<details>
<summary>抽老婆</summary>

`import _ "github.com/FloatTech/NanoBot-Plugin/plugin/wife"`

- [x] 抽老婆

</details>

<details>
<summary>猜单词</summary>

`import _ "github.com/FloatTech/NanoBot-Plugin/plugin/wordle"`

- [x] 个人猜单词

- [x] 团队猜单词

- [x] 团队六阶猜单词

- [x] 团队七阶猜单词

</details>


## 特别感谢

Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
_ "github.com/FloatTech/NanoBot-Plugin/plugin/score"
_ "github.com/FloatTech/NanoBot-Plugin/plugin/status"
_ "github.com/FloatTech/NanoBot-Plugin/plugin/tarot"
_ "github.com/FloatTech/NanoBot-Plugin/plugin/wife"
_ "github.com/FloatTech/NanoBot-Plugin/plugin/wordle"

// -----------------------以下为内置依赖,勿动------------------------ //
nano "github.com/fumiama/NanoBot"
Expand Down
65 changes: 65 additions & 0 deletions plugin/wife/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Package wife 抽老婆
package wife

import (
"encoding/base64"
"encoding/json"
"os"
"strconv"
"strings"

"github.com/FloatTech/NanoBot-Plugin/utils/ctxext"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
nano "github.com/fumiama/NanoBot"
"github.com/sirupsen/logrus"
)

func init() {
engine := nano.Register("wife", &ctrl.Options[*nano.Ctx]{
DisableOnDefault: false,
Help: "- 抽老婆",
Brief: "从老婆库抽每日老婆",
PublicDataFolder: "Wife",
}).ApplySingle(ctxext.DefaultSingle)
_ = os.MkdirAll(engine.DataFolder()+"wives", 0755)
cards := []string{}
engine.OnMessageFullMatch("抽老婆", fcext.DoOnceOnSuccess(
func(ctx *nano.Ctx) bool {
data, err := engine.GetLazyData("wife.json", true)
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
return false
}
err = json.Unmarshal(data, &cards)
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
return false
}
logrus.Infof("[wife]加载%d个老婆", len(cards))
return true
},
)).SetBlock(true).
Handle(func(ctx *nano.Ctx) {
uid := ctx.Message.Author.ID
if uid == "" {
_, _ = ctx.SendPlainMessage(false, "ERROR: 未获取到用户")
return
}
uidint, _ := strconv.ParseInt(uid, 10, 64)
card := cards[fcext.RandSenderPerDayN(uidint, len(cards))]
data, err := engine.GetLazyData("wives/"+card, true)
card, _, _ = strings.Cut(card, ".")
if err != nil {
_, err = ctx.SendPlainMessage(false, "<@", uid, ">今天的二次元老婆是~【", card, "】哒\n【图片下载失败: ", err, "】")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
}
return
}
_, err = ctx.SendImage("base64://"+base64.StdEncoding.EncodeToString(data), false, "<@", uid, ">今天的二次元老婆是~【", card, "】哒")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
}
})
}
268 changes: 268 additions & 0 deletions plugin/wordle/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
// Package wordle 猜单词
package wordle

import (
"errors"
"fmt"
"image/color"
"math/rand"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/FloatTech/AnimeAPI/tl"
"github.com/FloatTech/imgfactory"

"github.com/FloatTech/NanoBot-Plugin/utils/ctxext"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/gg"
ctrl "github.com/FloatTech/zbpctrl"
nano "github.com/fumiama/NanoBot"
)

var (
errLengthNotEnough = errors.New("length not enough")
errUnknownWord = errors.New("unknown word")
errTimesRunOut = errors.New("times run out")
)

const (
match = iota
exist
notexist
undone
)

var colors = [...]color.RGBA{
{125, 166, 108, 255},
{199, 183, 96, 255},
{123, 123, 123, 255},
{219, 219, 219, 255},
}

var classdict = map[string]int{
"": 5,
"五阶": 5,
"六阶": 6,
"七阶": 7,
}

type dictionary map[int]struct {
dict []string
cet4 []string
}

var words = make(dictionary)

func init() {
en := nano.Register("wordle", &ctrl.Options[*nano.Ctx]{
DisableOnDefault: false,
Brief: "猜单词",
Help: "- 个人猜单词\n" +
"- 团队猜单词\n" +
"- 团队六阶猜单词\n" +
"- 团队七阶猜单词",
PublicDataFolder: "Wordle",
}).ApplySingle(nano.NewSingle(
nano.WithKeyFn(func(ctx *nano.Ctx) int64 {
gid, _ := strconv.ParseUint(ctx.Message.ChannelID, 10, 64)
return int64(gid)
}),
nano.WithPostFn[int64](func(ctx *nano.Ctx) {
_, _ = ctx.SendPlainMessage(true, "已经有正在进行的游戏了")
}),
))

en.OnMessageRegex(`^(个人|团队)(五阶|六阶|七阶)?猜单词$`, fcext.DoOnceOnSuccess(
func(ctx *nano.Ctx) bool {
var errcnt uint32
var wg sync.WaitGroup
var mu sync.Mutex
for i := 5; i <= 7; i++ {
wg.Add(2)
go func(i int) {
defer wg.Done()
dc, err := en.GetLazyData(fmt.Sprintf("cet-4_%d.txt", i), true)
if err != nil {
atomic.AddUint32(&errcnt, 1)
return
}
c := strings.Split(nano.BytesToString(dc), "\n")
sort.Strings(c)
mu.Lock()
tmp := words[i]
tmp.cet4 = c
words[i] = tmp
mu.Unlock()
}(i)
go func(i int) {
defer wg.Done()
dd, err := en.GetLazyData(fmt.Sprintf("dict_%d.txt", i), true)
if err != nil {
atomic.AddUint32(&errcnt, 1)
return
}
d := strings.Split(nano.BytesToString(dd), "\n")
sort.Strings(d)
mu.Lock()
tmp := words[i]
tmp.dict = d
words[i] = tmp
mu.Unlock()
}(i)
}
wg.Wait()
if errcnt > 0 {
_, _ = ctx.SendPlainMessage(false, "ERROR: 下载字典时发生", errcnt, "个错误")
return false
}
return true
},
)).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *nano.Ctx) {
class := classdict[ctx.State["regex_matched"].([]string)[2]]
target := words[class].cet4[rand.Intn(len(words[class].cet4))]
tt, err := tl.Translate(target)
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
return
}
game := newWordleGame(target)
_, img, _ := game("")
_, err = ctx.SendImage("base64://"+nano.BytesToString(img), true, "你有", class+1, "次机会猜出单词,单词长度为", class, ",请发送单词")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
return
}
var next *nano.FutureEvent
if ctx.State["regex_matched"].([]string)[1] == "团队" && !nano.OnlyPrivate(ctx) {
next = nano.NewFutureEvent("Message", 999, false, nano.RegexRule(fmt.Sprintf(`^([A-Z]|[a-z]){%d}$`, class)),
nano.OnlyChannel, nano.CheckChannel(ctx.Message.ChannelID))
} else {
next = nano.NewFutureEvent("Message", 999, false, nano.RegexRule(fmt.Sprintf(`^([A-Z]|[a-z]){%d}$`, class)),
ctx.CheckSession())
}
var win bool
recv, cancel := next.Repeat()
defer cancel()
tick := time.NewTimer(105 * time.Second)
after := time.NewTimer(120 * time.Second)
for {
select {
case <-tick.C:
_, err := ctx.SendPlainMessage(true, "猜单词, 你还有15s作答时间")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
return
}
case <-after.C:
_, err := ctx.SendPlainMessage(true, "猜单词超时,游戏结束...答案是: ", target, "(", tt, ")")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
}
return
case c := <-recv:
tick.Reset(105 * time.Second)
after.Reset(120 * time.Second)
win, img, err = game(c.Message.Content)
switch {
case win:
tick.Stop()
after.Stop()
_, err := ctx.SendImage("base64://"+nano.BytesToString(img), true, "太棒了,你猜出来了!答案是: ", target, "(", tt, ")")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
}
return
case err == errTimesRunOut:
tick.Stop()
after.Stop()
_, err := ctx.SendImage("base64://"+nano.BytesToString(img), true, "游戏结束...答案是: ", target, "(", tt, ")")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
}
return
case err == errLengthNotEnough:
_, err := ctx.SendPlainMessage(true, "单词长度错误")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
return
}
case err == errUnknownWord:
_, err := ctx.SendPlainMessage(true, "你确定存在这样的单词吗?")
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
return
}
default:
_, err := ctx.SendImage("base64://"+nano.BytesToString(img), true)
if err != nil {
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
return
}
}
}
}
})
}

func newWordleGame(target string) func(string) (bool, []byte, error) {
var class = len(target)
record := make([]string, 0, len(target)+1)
return func(s string) (win bool, data []byte, err error) {
if s != "" {
s = strings.ToLower(s)
if target == s {
win = true
} else {
if len(s) != len(target) {
err = errLengthNotEnough
return
}
i := sort.SearchStrings(words[class].dict, s)
if i >= len(words[class].dict) || words[class].dict[i] != s {
err = errUnknownWord
return
}
}
record = append(record, s)
if len(record) >= cap(record) {
err = errTimesRunOut
return
}
}
var side = 20
var space = 10
ctx := gg.NewContext((side+4)*class+space*2-4, (side+4)*(class+1)+space*2-4)
ctx.SetColor(color.RGBA{255, 255, 255, 255})
ctx.Clear()
for i := 0; i < class+1; i++ {
for j := 0; j < class; j++ {
if len(record) > i {
ctx.DrawRectangle(float64(space+j*(side+4)), float64(space+i*(side+4)), float64(side), float64(side))
switch {
case record[i][j] == target[j]:
ctx.SetColor(colors[match])
case strings.IndexByte(target, record[i][j]) != -1:
ctx.SetColor(colors[exist])
default:
ctx.SetColor(colors[notexist])
}
ctx.Fill()
ctx.SetColor(color.RGBA{255, 255, 255, 255})
ctx.DrawString(strings.ToUpper(string(record[i][j])), float64(10+j*(side+4)+7), float64(10+i*(side+4)+15))
} else {
ctx.DrawRectangle(float64(10+j*(side+4)+1), float64(10+i*(side+4)+1), float64(side-2), float64(side-2))
ctx.SetLineWidth(1)
ctx.SetColor(colors[undone])
ctx.Stroke()
}
}
}
data, err = imgfactory.ToBase64(ctx.Image())
return
}
}