diff --git a/README.md b/README.md
index ac456c37..a06fda0b 100644
--- a/README.md
+++ b/README.md
@@ -707,6 +707,14 @@ print("run[CQ:image,file="+j["img"]+"]")
- 本地歌曲命名规则为:\n歌名 - 歌手 - 其他(歌曲出处之类)
+
+
+ 黑丝
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/heisi"`
+
+ - [x] 来点黑丝/白丝/jk/巨乳/足控/网红
+
炉石
diff --git a/data b/data
index aa8f7eb3..c066abeb 160000
--- a/data
+++ b/data
@@ -1 +1 @@
-Subproject commit aa8f7eb30a360babcbd7d658d4bebc4643d478df
+Subproject commit c066abebe7e230ee96ac90e408d9ac555211bdcc
diff --git a/main.go b/main.go
index 50cc6c06..93ca3578 100644
--- a/main.go
+++ b/main.go
@@ -86,6 +86,7 @@ import (
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/gif" // 制图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/github" // 搜索GitHub仓库
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic" // 猜歌
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/heisi" // 黑丝
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hs" // 炉石
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hyaku" // 百人一首
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/image_finder" // 关键字搜图
diff --git a/plugin/heisi/heisi.go b/plugin/heisi/heisi.go
new file mode 100644
index 00000000..b3170265
--- /dev/null
+++ b/plugin/heisi/heisi.go
@@ -0,0 +1,88 @@
+// Package heisi 黑丝
+package heisi
+
+import (
+ "math/rand"
+ "strconv"
+ "unsafe"
+
+ fbctxext "github.com/FloatTech/floatbox/ctxext"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+var (
+ heisiPic []item
+ baisiPic []item
+ jkPic []item
+ jurPic []item
+ zukPic []item
+ mcnPic []item
+ fileList = [...]string{"heisi.bin", "baisi.bin", "jk.bin", "jur.bin", "zuk.bin", "mcn.bin"}
+)
+
+func init() { // 插件主体
+ engine := control.Register("heisi", &ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Help: "黑丝\n" +
+ "- 来点黑丝\n- 来点白丝\n- 来点jk\n- 来点巨乳\n- 来点足控\n- 来点网红",
+ PublicDataFolder: "Heisi",
+ })
+
+ engine.OnFullMatchGroup([]string{"来点黑丝", "来点白丝", "来点jk", "来点巨乳", "来点足控", "来点网红"}, zero.OnlyGroup, fbctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
+ for i, filePath := range fileList {
+ data, err := engine.GetLazyData(filePath, true)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return false
+ }
+ if len(data)%10 != 0 {
+ ctx.SendChain(message.Text("ERROR: invalid data " + strconv.Itoa(i)))
+ return false
+ }
+ s := (*slice)(unsafe.Pointer(&data))
+ s.len /= 10
+ s.cap /= 10
+ switch i {
+ case 0:
+ heisiPic = *(*[]item)(unsafe.Pointer(s))
+ case 1:
+ baisiPic = *(*[]item)(unsafe.Pointer(s))
+ case 2:
+ jkPic = *(*[]item)(unsafe.Pointer(s))
+ case 3:
+ jurPic = *(*[]item)(unsafe.Pointer(s))
+ case 4:
+ zukPic = *(*[]item)(unsafe.Pointer(s))
+ case 5:
+ mcnPic = *(*[]item)(unsafe.Pointer(s))
+ }
+ }
+ return true
+ })).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ matched := ctx.State["matched"].(string)
+ var pic item
+ switch matched {
+ case "来点黑丝":
+ pic = heisiPic[rand.Intn(len(heisiPic))]
+ case "来点白丝":
+ pic = baisiPic[rand.Intn(len(baisiPic))]
+ case "来点jk":
+ pic = jkPic[rand.Intn(len(jkPic))]
+ case "来点巨乳":
+ pic = jurPic[rand.Intn(len(jurPic))]
+ case "来点足控":
+ pic = zukPic[rand.Intn(len(zukPic))]
+ case "来点网红":
+ pic = mcnPic[rand.Intn(len(mcnPic))]
+ }
+ m := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Image(pic.String()))}
+ if id := ctx.Send(m).ID(); id == 0 {
+ ctx.SendChain(message.Text("ERROR: 可能被风控或下载图片用时过长,请耐心等待"))
+ }
+ })
+}
diff --git a/plugin/heisi/packer.go b/plugin/heisi/packer.go
new file mode 100644
index 00000000..f6024a47
--- /dev/null
+++ b/plugin/heisi/packer.go
@@ -0,0 +1,48 @@
+package heisi
+
+import (
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "math/bits"
+)
+
+const (
+ template2021 = "http://hs.heisiwu.com/wp-content/uploads/%4d/%02d/%4d%02d16%06d-611a3%8s.jpg"
+ templategeneral = "http://hs.heisiwu.com/wp-content/uploads/%4d/%02d/%015x"
+)
+
+type item [10]byte
+
+// String item to url
+func (it item) String() string {
+ year, month := int((it[0]>>4)&0x0f), int(it[0]&0x0f)
+ year += 2021
+ if year == 2021 {
+ num := binary.BigEndian.Uint32(it[1:5])
+ dstr := hex.EncodeToString(it[5:9])
+ return fmt.Sprintf(template2021, year, month, year, month, num, dstr)
+ }
+ d := binary.BigEndian.Uint64(it[1:9])
+ isscaled := it[9]&0x80 > 0
+ num := int(it[9] & 0x7f)
+ trestore := fmt.Sprintf(templategeneral, year, month, d&0x0fffffff_ffffffff)
+ if num > 0 {
+ trestore += fmt.Sprintf("-%d", num)
+ }
+ if isscaled {
+ trestore += "-scaled"
+ }
+ d = bits.RotateLeft64(d, 4) & 0x0f
+ switch d {
+ case 0:
+ trestore += ".jpg"
+ case 1:
+ trestore += ".png"
+ case 2:
+ trestore += ".webp"
+ default:
+ return "invalid ext"
+ }
+ return trestore
+}
diff --git a/plugin/heisi/slice.go b/plugin/heisi/slice.go
new file mode 100644
index 00000000..8aafbdfc
--- /dev/null
+++ b/plugin/heisi/slice.go
@@ -0,0 +1,15 @@
+package heisi
+
+import "unsafe"
+
+// slice is the runtime representation of a slice.
+// It cannot be used safely or portably and its representation may
+// change in a later release.
+//
+// Unlike reflect.SliceHeader, its Data field is sufficient to guarantee the
+// data it references will not be garbage collected.
+type slice struct {
+ data unsafe.Pointer
+ len int
+ cap int
+}