Added cj & jyutping lookup
This commit is contained in:
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[submodule "datasources/cjlookup/rime-cangjie"]
|
||||||
|
path = datasources/cjlookup/rime-cangjie
|
||||||
|
url = https://github.com/rime/rime-cangjie.git
|
||||||
|
[submodule "datasources/cjlookup/rime-cantonese"]
|
||||||
|
path = datasources/cjlookup/rime-cantonese
|
||||||
|
url = https://github.com/rime/rime-cantonese.git
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.19-alpine AS build
|
FROM golang:1.25-alpine AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ FROM scratch
|
|||||||
|
|
||||||
COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=build /golifehkbot /
|
COPY --from=build /golifehkbot /
|
||||||
|
COPY "datasources/cjlookup/maps/*" /
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
CMD [ "/golifehkbot" ]
|
CMD [ "/golifehkbot" ]
|
||||||
|
|||||||
45
datasources/cjlookup/CChar.go
Normal file
45
datasources/cjlookup/CChar.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
query "github.com/tgckpg/golifehk/query"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CChar struct {
|
||||||
|
Face string
|
||||||
|
CangJie string
|
||||||
|
JyutPing string
|
||||||
|
TungJamZi *[]*CChar
|
||||||
|
YiDukJam *[]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CJyutPing struct {
|
||||||
|
Ref *CChar
|
||||||
|
SKey string
|
||||||
|
query.Searchable
|
||||||
|
}
|
||||||
|
|
||||||
|
type CFace struct {
|
||||||
|
Ref *CChar
|
||||||
|
query.Searchable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CJyutPing) Test(val string) bool {
|
||||||
|
_, err := strconv.Atoi(val)
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CFace) Test(val string) bool {
|
||||||
|
_, err := strconv.Atoi(val)
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CChar) String() string {
|
||||||
|
return m.Face
|
||||||
|
}
|
||||||
132
datasources/cjlookup/QueryResult.go
Normal file
132
datasources/cjlookup/QueryResult.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
query "github.com/tgckpg/golifehk/query"
|
||||||
|
utils "github.com/tgckpg/golifehk/utils"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryResult struct {
|
||||||
|
Lang string
|
||||||
|
Error error
|
||||||
|
Query *query.QueryObject
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCCharInfo(sb *strings.Builder, cc *CChar) {
|
||||||
|
sb.WriteString(getCUHARTSUrlForChar(cc.Face))
|
||||||
|
sb.WriteString(" ")
|
||||||
|
utils.WriteMDv2Text(sb, cc.JyutPing)
|
||||||
|
if cc.YiDukJam != nil {
|
||||||
|
sb.WriteString("\n")
|
||||||
|
utils.WriteMDv2Text(sb, "異讀: ")
|
||||||
|
utils.WriteMDv2Text(sb, strings.Join(*cc.YiDukJam, " "))
|
||||||
|
}
|
||||||
|
sb.WriteString("\n")
|
||||||
|
utils.WriteMDv2Text(sb, "倉: ")
|
||||||
|
utils.WriteMDv2Text(sb, cc.CangJie)
|
||||||
|
sb.WriteString("\n")
|
||||||
|
utils.WriteMDv2Text(sb, "同音字:")
|
||||||
|
|
||||||
|
slices.SortFunc(*cc.TungJamZi, func(a, b *CChar) int {
|
||||||
|
switch {
|
||||||
|
case a.Face < b.Face:
|
||||||
|
return -1
|
||||||
|
case a.Face > b.Face:
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for i, cchar := range *cc.TungJamZi {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
utils.WriteMDv2Text(sb, cchar.Face)
|
||||||
|
utils.WriteMDv2Text(sb, strconv.Itoa(i+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this QueryResult) Message() (string, error) {
|
||||||
|
|
||||||
|
if this.Error != nil {
|
||||||
|
return "", this.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.Query == nil {
|
||||||
|
panic("Query is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
sb := strings.Builder{}
|
||||||
|
|
||||||
|
q := *this.Query
|
||||||
|
|
||||||
|
argv := *q.SearchTerms
|
||||||
|
args := len(argv)
|
||||||
|
|
||||||
|
if len(*q.Results) == 0 {
|
||||||
|
terms := make([]string, args, len(*q.SearchTerms))
|
||||||
|
for i, term := range *q.SearchTerms {
|
||||||
|
terms[i] = term.Org
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("Not Found: \"%s\"", strings.Join(terms, "\", \""))
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(*q.Results, func(a, b query.ISearchable) int {
|
||||||
|
aa := a.(*CJyutPing)
|
||||||
|
bb := b.(*CJyutPing)
|
||||||
|
switch {
|
||||||
|
case aa.Ref.Face < bb.Ref.Face:
|
||||||
|
return -1
|
||||||
|
case aa.Ref.Face > bb.Ref.Face:
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if utils.IsASCIIAlnum(argv[0].Value) {
|
||||||
|
if args == 1 {
|
||||||
|
for i, entry := range *q.Results {
|
||||||
|
cjp := any(entry).(*CJyutPing)
|
||||||
|
sb.WriteString(" ")
|
||||||
|
utils.WriteMDv2Text(&sb, cjp.Ref.Face)
|
||||||
|
utils.WriteMDv2Text(&sb, strconv.Itoa(i+1))
|
||||||
|
}
|
||||||
|
} else if args == 2 {
|
||||||
|
var err error
|
||||||
|
x, err := strconv.Atoi(argv[1].Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Invalid index: %s", argv[1].Org)
|
||||||
|
}
|
||||||
|
if 0 < x {
|
||||||
|
for i, entry := range *q.Results {
|
||||||
|
cjp := any(entry).(*CJyutPing)
|
||||||
|
if (i + 1) == x {
|
||||||
|
writeCCharInfo(&sb, cjp.Ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cjp := any((*q.Results)[0]).(*CFace)
|
||||||
|
if args == 1 {
|
||||||
|
writeCCharInfo(&sb, cjp.Ref)
|
||||||
|
} else if args == 2 {
|
||||||
|
var err error
|
||||||
|
x, err := strconv.Atoi(argv[1].Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Invalid index: %s", argv[1].Org)
|
||||||
|
}
|
||||||
|
tjz := (*(*cjp.Ref).TungJamZi)[x-1]
|
||||||
|
|
||||||
|
if tjz != nil {
|
||||||
|
writeCCharInfo(&sb, tjz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
4
datasources/cjlookup/copy-maps.sh
Executable file
4
datasources/cjlookup/copy-maps.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cp ./rime-cangjie/cangjie5.dict.yaml ./maps/
|
||||||
|
cp ./rime-cantonese/jyut6ping3.chars.dict.yaml ./maps/
|
||||||
32
datasources/cjlookup/humanumarts.go
Normal file
32
datasources/cjlookup/humanumarts.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding/traditionalchinese"
|
||||||
|
)
|
||||||
|
|
||||||
|
var CUHARTS_FACE = "[%s](https://humanum.arts.cuhk.edu.hk/Lexis/lexi-can/search.php?q=%s)"
|
||||||
|
var ZDIC_FACE = "[%s](https://www.zdic.net/hans/%s)"
|
||||||
|
|
||||||
|
func getCUHARTSUrlForChar(c string) string {
|
||||||
|
b, err := Big5UrlParam(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to encode:", c, err)
|
||||||
|
return fmt.Sprintf(ZDIC_FACE, c, c)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(CUHARTS_FACE, c, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Big5UrlParam(input string) (string, error) {
|
||||||
|
enc := traditionalchinese.Big5.NewEncoder()
|
||||||
|
encodedBytes, err := enc.Bytes([]byte(input))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
urlEncoded := url.PathEscape(string(encodedBytes))
|
||||||
|
return urlEncoded, nil
|
||||||
|
}
|
||||||
11
datasources/cjlookup/humanumarts_test.go
Normal file
11
datasources/cjlookup/humanumarts_test.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCUHARTLinks(t *testing.T) {
|
||||||
|
if Big5UrlParam("呀") != "%A7r" {
|
||||||
|
t.Errorf("Faild to encode Big5 string: %A7r", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
172
datasources/cjlookup/maps.go
Normal file
172
datasources/cjlookup/maps.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/csv"
|
||||||
|
utils "github.com/tgckpg/golifehk/utils"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TXT_CANGJIE_KEYS string = filepath.Join(utils.WORKDIR, "cangjie.keys.txt")
|
||||||
|
var YAML_CANGJIE5_DICT string = filepath.Join(utils.WORKDIR, "cangjie5.dict.yaml")
|
||||||
|
var YAML_JYUTPING_CHARS string = filepath.Join(utils.WORKDIR, "jyut6ping3.chars.dict.yaml")
|
||||||
|
|
||||||
|
func ReadCangJieKeys() (*strings.Replacer, error) {
|
||||||
|
f, err := os.Open(TXT_CANGJIE_KEYS)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
r := csv.NewReader(f)
|
||||||
|
r.Comma = '\t'
|
||||||
|
r.FieldsPerRecord = -1
|
||||||
|
|
||||||
|
cjKeymap := []string{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
record, err := r.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cjKeymap = append(cjKeymap, record[0])
|
||||||
|
cjKeymap = append(cjKeymap, record[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.NewReplacer(cjKeymap...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadCangJieTable(charMap map[string]*CChar, repl *strings.Replacer) error {
|
||||||
|
|
||||||
|
f, err := os.Open(YAML_CANGJIE5_DICT)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
br := bufio.NewReader(f)
|
||||||
|
|
||||||
|
// Skip until YAML document end
|
||||||
|
for {
|
||||||
|
line, err := br.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(line) == "..." {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := csv.NewReader(br)
|
||||||
|
r.Comma = '\t'
|
||||||
|
r.FieldsPerRecord = -1
|
||||||
|
|
||||||
|
for {
|
||||||
|
record, err := r.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
face := record[0]
|
||||||
|
if c, ok := charMap[face]; ok {
|
||||||
|
c.Face = face
|
||||||
|
c.CangJie = repl.Replace(record[1])
|
||||||
|
} else {
|
||||||
|
c := CChar{}
|
||||||
|
c.Face = face
|
||||||
|
c.CangJie = repl.Replace(record[1])
|
||||||
|
charMap[face] = &c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadJyutPingTable(charMap map[string]*CChar) (map[string]*[]*CChar, error) {
|
||||||
|
f, err := os.Open(YAML_JYUTPING_CHARS)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
br := bufio.NewReader(f)
|
||||||
|
|
||||||
|
// Skip until YAML document end
|
||||||
|
for {
|
||||||
|
line, err := br.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(line) == "..." {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := csv.NewReader(br)
|
||||||
|
r.Comma = '\t'
|
||||||
|
r.FieldsPerRecord = -1
|
||||||
|
|
||||||
|
JyutPingMap := map[string]*[]*CChar{}
|
||||||
|
for {
|
||||||
|
record, err := r.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
face := record[0]
|
||||||
|
jyutping := record[1]
|
||||||
|
|
||||||
|
var c *CChar
|
||||||
|
c, ok := charMap[face]
|
||||||
|
if ok {
|
||||||
|
if len(record) == 2 {
|
||||||
|
c.JyutPing = record[1]
|
||||||
|
} else if len(record) == 3 {
|
||||||
|
s := record[2]
|
||||||
|
if strings.HasSuffix(s, "%") {
|
||||||
|
_, err := strconv.Atoi(s[:len(s)-1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yList := c.YiDukJam
|
||||||
|
if yList == nil {
|
||||||
|
yList = &[]string{}
|
||||||
|
}
|
||||||
|
*yList = append(*yList, record[1])
|
||||||
|
c.YiDukJam = yList
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c = &CChar{}
|
||||||
|
c.Face = face
|
||||||
|
c.JyutPing = jyutping
|
||||||
|
charMap[face] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
pListPtr, ok := JyutPingMap[jyutping]
|
||||||
|
if !ok {
|
||||||
|
pList := []*CChar{}
|
||||||
|
pListPtr = &pList
|
||||||
|
JyutPingMap[jyutping] = pListPtr
|
||||||
|
}
|
||||||
|
*pListPtr = append(*pListPtr, c)
|
||||||
|
c.TungJamZi = pListPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
return JyutPingMap, nil
|
||||||
|
}
|
||||||
26
datasources/cjlookup/maps/cangjie.keys.txt
Normal file
26
datasources/cjlookup/maps/cangjie.keys.txt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
a 日
|
||||||
|
b 月
|
||||||
|
c 金
|
||||||
|
d 木
|
||||||
|
e 水
|
||||||
|
f 火
|
||||||
|
g 土
|
||||||
|
h 竹
|
||||||
|
i 戈
|
||||||
|
j 十
|
||||||
|
k 大
|
||||||
|
l 中
|
||||||
|
m 一
|
||||||
|
n 弓
|
||||||
|
o 人
|
||||||
|
p 心
|
||||||
|
q 手
|
||||||
|
r 口
|
||||||
|
s 尸
|
||||||
|
t 廿
|
||||||
|
u 山
|
||||||
|
v 女
|
||||||
|
w 田
|
||||||
|
x 難
|
||||||
|
y 卜
|
||||||
|
z 重
|
||||||
79316
datasources/cjlookup/maps/cangjie5.dict.yaml
Normal file
79316
datasources/cjlookup/maps/cangjie5.dict.yaml
Normal file
File diff suppressed because it is too large
Load Diff
34370
datasources/cjlookup/maps/jyut6ping3.chars.dict.yaml
Normal file
34370
datasources/cjlookup/maps/jyut6ping3.chars.dict.yaml
Normal file
File diff suppressed because it is too large
Load Diff
27
datasources/cjlookup/objects_test.go
Normal file
27
datasources/cjlookup/objects_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDataRead(t *testing.T) {
|
||||||
|
chars := map[string]*CChar{}
|
||||||
|
cjRepl, err := ReadCangJieKeys()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
ReadCangJieTable(chars, cjRepl)
|
||||||
|
JyutPingMap, err := ReadJyutPingTable(chars)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
j := *JyutPingMap["sing4"]
|
||||||
|
if j[0].TungJamZi != j[1].TungJamZi {
|
||||||
|
t.Errorf("%s & %s does not point to the same reference.", j[0].Face, j[1].Face)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
for i, c := range j {
|
||||||
|
t.Log(i, c.CangJie)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
45
datasources/cjlookup/query.go
Normal file
45
datasources/cjlookup/query.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tgckpg/golifehk/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Query(lang string, message string) query.IQueryResult {
|
||||||
|
var qResults *query.QueryObject
|
||||||
|
var err error
|
||||||
|
var searchables *[]query.ISearchable
|
||||||
|
|
||||||
|
log.Printf("HNHNHNHNHNHNHNHNHNH")
|
||||||
|
qr := QueryResult{Lang: lang}
|
||||||
|
messageU := strings.ToUpper(message)
|
||||||
|
|
||||||
|
// Only look up jyut ping
|
||||||
|
if !strings.HasPrefix(messageU, "JP ") {
|
||||||
|
err = fmt.Errorf("Invalid query")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
qr.Error = err
|
||||||
|
goto qrReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
messageU = messageU[3:]
|
||||||
|
searchables, err = getSearchables()
|
||||||
|
|
||||||
|
qResults, err = query.Parse(messageU, searchables)
|
||||||
|
|
||||||
|
qr.Query = qResults
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
qr.Error = err
|
||||||
|
goto qrReturn
|
||||||
|
}
|
||||||
|
qrReturn:
|
||||||
|
var iqr query.IQueryResult
|
||||||
|
iqr = &qr
|
||||||
|
return iqr
|
||||||
|
}
|
||||||
45
datasources/cjlookup/query_test.go
Normal file
45
datasources/cjlookup/query_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQuery(t *testing.T) {
|
||||||
|
qo := Query("zh-Hant", "jp jyut6")
|
||||||
|
mesg, err := qo.Message()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected Error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(mesg, "粵42") {
|
||||||
|
t.Errorf("Expected 粵42 in response, got \"%s\" instead", mesg)
|
||||||
|
}
|
||||||
|
|
||||||
|
qo = Query("zh-Hant", "jp jyut6")
|
||||||
|
mesg, err = qo.Message()
|
||||||
|
if !strings.Contains(mesg, "粵42") {
|
||||||
|
t.Errorf("Expected 粵42 in response, got \"%s\" instead", mesg)
|
||||||
|
}
|
||||||
|
|
||||||
|
qo = Query("zh-Hant", "NaN")
|
||||||
|
mesg, err = qo.Message()
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expecting error, got \"%s\" instead", mesg)
|
||||||
|
}
|
||||||
|
|
||||||
|
qo = Query("zh-Hant", "jp 粵")
|
||||||
|
mesg, err = qo.Message()
|
||||||
|
if !strings.Contains(mesg, "閲62") {
|
||||||
|
t.Errorf("Expected 閲62 in response, got \"%s\" instead", mesg)
|
||||||
|
}
|
||||||
|
|
||||||
|
qo = Query("zh-Hant", "jp 粵 62")
|
||||||
|
mesg, err = qo.Message()
|
||||||
|
if !strings.Contains(mesg, "閲62") {
|
||||||
|
t.Errorf("Expected 閲62 in response, got \"%s\" instead", mesg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(mesg)
|
||||||
|
}
|
||||||
1
datasources/cjlookup/rime-cangjie
Submodule
1
datasources/cjlookup/rime-cangjie
Submodule
Submodule datasources/cjlookup/rime-cangjie added at 0ac8452eeb
1
datasources/cjlookup/rime-cantonese
Submodule
1
datasources/cjlookup/rime-cantonese
Submodule
Submodule datasources/cjlookup/rime-cantonese added at 77776c0aad
42
datasources/cjlookup/searchables.go
Normal file
42
datasources/cjlookup/searchables.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package cjlookup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
query "github.com/tgckpg/golifehk/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getSearchables() (*[]query.ISearchable, error) {
|
||||||
|
searchables := []query.ISearchable{}
|
||||||
|
|
||||||
|
cjRepl, err := ReadCangJieKeys()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
chars := map[string]*CChar{}
|
||||||
|
ReadCangJieTable(chars, cjRepl)
|
||||||
|
jpMap, err := ReadJyutPingTable(chars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for jyutping, chars := range jpMap {
|
||||||
|
for _, c := range *chars {
|
||||||
|
cjp := CJyutPing{}
|
||||||
|
cjp.Ref = c
|
||||||
|
cjp.SKey = strings.ToUpper(jyutping)
|
||||||
|
cjp.Key = &cjp.SKey
|
||||||
|
searchables = append(searchables, &cjp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range chars {
|
||||||
|
ccj := CFace{}
|
||||||
|
ccj.Ref = c
|
||||||
|
ccj.Key = &c.Face
|
||||||
|
searchables = append(searchables, &ccj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &searchables, nil
|
||||||
|
}
|
||||||
8
go.mod
8
go.mod
@@ -1,5 +1,9 @@
|
|||||||
module github.com/tgckpg/golifehk
|
module github.com/tgckpg/golifehk
|
||||||
|
|
||||||
go 1.19
|
go 1.25.0
|
||||||
|
|
||||||
require github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
|
require (
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
|
||||||
|
golang.org/x/text v0.34.0
|
||||||
|
)
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,2 +1,6 @@
|
|||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||||
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||||
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
|||||||
117
main.go
117
main.go
@@ -1,78 +1,81 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
utils "github.com/tgckpg/golifehk/utils"
|
|
||||||
mtrbus "github.com/tgckpg/golifehk/datasources/mtr/bus"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
kmb "github.com/tgckpg/golifehk/datasources/kmb"
|
cjlookup "github.com/tgckpg/golifehk/datasources/cjlookup"
|
||||||
query "github.com/tgckpg/golifehk/query"
|
kmb "github.com/tgckpg/golifehk/datasources/kmb"
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
mtrbus "github.com/tgckpg/golifehk/datasources/mtr/bus"
|
||||||
|
query "github.com/tgckpg/golifehk/query"
|
||||||
|
utils "github.com/tgckpg/golifehk/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func botsend( bot *tgbotapi.BotAPI, update *tgbotapi.Update, mesg *string ) {
|
func botsend(bot *tgbotapi.BotAPI, update *tgbotapi.Update, mesg *string) {
|
||||||
var msg tgbotapi.MessageConfig
|
var msg tgbotapi.MessageConfig
|
||||||
msg = tgbotapi.NewMessage( update.Message.Chat.ID, *mesg )
|
msg = tgbotapi.NewMessage(update.Message.Chat.ID, *mesg)
|
||||||
msg.ParseMode = "MarkdownV2"
|
msg.ParseMode = "MarkdownV2"
|
||||||
|
|
||||||
msg.ReplyToMessageID = update.Message.MessageID
|
msg.ReplyToMessageID = update.Message.MessageID
|
||||||
bot.Send( msg )
|
bot.Send(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bot, err := tgbotapi.NewBotAPI( os.Getenv( "TELEGRAM_API_TOKEN" ) )
|
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic( err )
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.Debug = true
|
bot.Debug = true
|
||||||
|
|
||||||
log.Printf("Authorized on account %s", bot.Self.UserName)
|
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||||
|
|
||||||
u := tgbotapi.NewUpdate(0)
|
u := tgbotapi.NewUpdate(0)
|
||||||
u.Timeout = 60
|
u.Timeout = 60
|
||||||
|
|
||||||
updates := bot.GetUpdatesChan(u)
|
updates := bot.GetUpdatesChan(u)
|
||||||
|
|
||||||
for update := range updates {
|
for update := range updates {
|
||||||
if update.Message == nil {
|
if update.Message == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf( "[%s] %s", update.Message.From.UserName, update.Message.Text )
|
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
|
||||||
isGroup := ( update.Message.Chat.ID < 0 )
|
isGroup := (update.Message.Chat.ID < 0)
|
||||||
|
|
||||||
mesg, processed := utils.SystemControl( update.Message )
|
mesg, processed := utils.SystemControl(update.Message)
|
||||||
if processed {
|
if processed {
|
||||||
if mesg != "" {
|
if mesg != "" {
|
||||||
botsend( bot, &update, &mesg )
|
botsend(bot, &update, &mesg)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
f_queries := []func( string, string ) query.IQueryResult{
|
f_queries := []func(string, string) query.IQueryResult{
|
||||||
mtrbus.Query,
|
cjlookup.Query,
|
||||||
kmb.Query,
|
mtrbus.Query,
|
||||||
}
|
kmb.Query,
|
||||||
|
}
|
||||||
|
|
||||||
var f_sent bool = false
|
var f_sent bool = false
|
||||||
var f_err error = nil
|
var f_err error = nil
|
||||||
for _, Query := range f_queries {
|
for _, Query := range f_queries {
|
||||||
var err error
|
var err error
|
||||||
mesg, err = Query( "zh-Hant", update.Message.Text ).Message()
|
mesg, err = Query("zh-Hant", update.Message.Text).Message()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f_sent = true
|
f_sent = true
|
||||||
botsend( bot, &update, &mesg )
|
botsend(bot, &update, &mesg)
|
||||||
} else if f_err == nil {
|
} else if f_err == nil {
|
||||||
f_err = err
|
f_err = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isGroup && !f_sent && f_err != nil {
|
if !isGroup && !f_sent && f_err != nil {
|
||||||
mesg := utils.MDv2Text( fmt.Sprintf( "%s", f_err ) )
|
mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
|
||||||
botsend( bot, &update, &mesg )
|
botsend(bot, &update, &mesg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,95 +1,119 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var MARKDOWN_ESC []string = []string{"_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"}
|
var MARKDOWN_ESC []string = []string{"_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"}
|
||||||
|
|
||||||
func WriteMDv2Text( sb *strings.Builder, t string ) {
|
func Contains[T comparable](slice []T, target T) bool {
|
||||||
for _, c := range MARKDOWN_ESC {
|
for _, v := range slice {
|
||||||
t = strings.Replace( t, c, "\\" + c, -1 )
|
if v == target {
|
||||||
}
|
return true
|
||||||
|
}
|
||||||
sb.WriteString( t )
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func MDv2Text( t string ) string {
|
func WriteMDv2Text(sb *strings.Builder, t string) {
|
||||||
sb := strings.Builder{}
|
for _, c := range MARKDOWN_ESC {
|
||||||
|
t = strings.Replace(t, c, "\\"+c, -1)
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range MARKDOWN_ESC {
|
sb.WriteString(t)
|
||||||
t = strings.Replace( t, c, "\\" + c, -1 )
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.WriteString( t )
|
|
||||||
return sb.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToPower( t string ) string {
|
func MDv2Text(t string) string {
|
||||||
for s, r := range POWER_NUMBERS {
|
sb := strings.Builder{}
|
||||||
t = strings.ReplaceAll( t, s, r )
|
|
||||||
}
|
for _, c := range MARKDOWN_ESC {
|
||||||
return t
|
t = strings.Replace(t, c, "\\"+c, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(t)
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadBOM( buff *bytes.Buffer ) {
|
func IsASCIIAlnum(s string) bool {
|
||||||
b, _ := buff.ReadByte()
|
if s == "" {
|
||||||
if b != 0xef {
|
return false
|
||||||
buff.UnreadByte()
|
}
|
||||||
return
|
for i := 0; i < len(s); i++ {
|
||||||
}
|
c := s[i]
|
||||||
b, _ = buff.ReadByte()
|
if !(c >= '0' && c <= '9' ||
|
||||||
if b != 0xbb {
|
c >= 'A' && c <= 'Z' ||
|
||||||
buff.UnreadByte()
|
c >= 'a' && c <= 'z') {
|
||||||
buff.UnreadByte()
|
return false
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
b, _ = buff.ReadByte()
|
return true
|
||||||
if b != 0xbf {
|
|
||||||
buff.UnreadByte()
|
|
||||||
buff.UnreadByte()
|
|
||||||
buff.UnreadByte()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TryGetEnv[T any]( name string, fallback T ) T {
|
func ToPower(t string) string {
|
||||||
v := os.Getenv( name )
|
for s, r := range POWER_NUMBERS {
|
||||||
|
t = strings.ReplaceAll(t, s, r)
|
||||||
if v != "" {
|
}
|
||||||
switch any( fallback ).(type) {
|
return t
|
||||||
case uint64:
|
}
|
||||||
p, err := strconv.ParseUint( v, 10, 64 )
|
|
||||||
if err == nil {
|
func ReadBOM(buff *bytes.Buffer) {
|
||||||
return any( uint64( p ) ).(T)
|
b, _ := buff.ReadByte()
|
||||||
}
|
if b != 0xef {
|
||||||
case uint32:
|
buff.UnreadByte()
|
||||||
p, err := strconv.ParseUint( v, 10, 32 )
|
return
|
||||||
if err == nil {
|
}
|
||||||
return any( uint32( p ) ).(T)
|
b, _ = buff.ReadByte()
|
||||||
}
|
if b != 0xbb {
|
||||||
case int:
|
buff.UnreadByte()
|
||||||
p, err := strconv.ParseInt( v, 10, 32 )
|
buff.UnreadByte()
|
||||||
if err == nil {
|
return
|
||||||
return any( int( p ) ).(T)
|
}
|
||||||
}
|
b, _ = buff.ReadByte()
|
||||||
case float64:
|
if b != 0xbf {
|
||||||
p, err := strconv.ParseFloat( v, 64 )
|
buff.UnreadByte()
|
||||||
if err == nil {
|
buff.UnreadByte()
|
||||||
return any( float64( p ) ).(T)
|
buff.UnreadByte()
|
||||||
}
|
}
|
||||||
case float32:
|
}
|
||||||
p, err := strconv.ParseFloat( v, 32 )
|
|
||||||
if err == nil {
|
func TryGetEnv[T any](name string, fallback T) T {
|
||||||
return any( float32( p ) ).(T)
|
v := os.Getenv(name)
|
||||||
}
|
|
||||||
default:
|
if v != "" {
|
||||||
return any( v ).(T)
|
switch any(fallback).(type) {
|
||||||
}
|
case uint64:
|
||||||
|
p, err := strconv.ParseUint(v, 10, 64)
|
||||||
}
|
if err == nil {
|
||||||
|
return any(uint64(p)).(T)
|
||||||
return fallback
|
}
|
||||||
|
case uint32:
|
||||||
|
p, err := strconv.ParseUint(v, 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
return any(uint32(p)).(T)
|
||||||
|
}
|
||||||
|
case int:
|
||||||
|
p, err := strconv.ParseInt(v, 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
return any(int(p)).(T)
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
p, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err == nil {
|
||||||
|
return any(float64(p)).(T)
|
||||||
|
}
|
||||||
|
case float32:
|
||||||
|
p, err := strconv.ParseFloat(v, 32)
|
||||||
|
if err == nil {
|
||||||
|
return any(float32(p)).(T)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return any(v).(T)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user