forked from Elara6331/itd
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01bf493c77 | |||
| b6e9ad6160 | |||
| c56c0ae198 | |||
| 6b94030b83 | |||
| 2bbd722ecd | |||
| 73f16fcfef | |||
| 9df6531023 | |||
| 1db2ca3395 | |||
| 419b2f5a79 | |||
| 44607ba9e2 | |||
| f4d2f4e6eb | |||
| 0721b7f9d4 | |||
| b7bd385c43 | |||
| cbcefb149e | |||
| cb8d207249 | |||
| 7786ea1d58 | |||
| 6e16aa7a7a | |||
| 91f7132d5e | |||
| b186f77bea | |||
| adb297c6dd | |||
| b4992cb393 | |||
| a5490b8364 | |||
| 44d1f5552b | |||
| 5e34f656b3 | |||
| 560d19860e | |||
| ea1a7fa9f4 | |||
| 81fe634ed8 | |||
| 281e1dcbac |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/itctl
|
/itctl
|
||||||
/itd
|
/itd
|
||||||
|
/itgui
|
||||||
14
Makefile
14
Makefile
@@ -7,14 +7,20 @@ all:
|
|||||||
go build $(GOFLAGS)
|
go build $(GOFLAGS)
|
||||||
go build ./cmd/itctl $(GOFLAGS)
|
go build ./cmd/itctl $(GOFLAGS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f itctl
|
||||||
|
rm -f itd
|
||||||
|
|
||||||
install:
|
install:
|
||||||
install -Dm755 ./itd $(BIN_PREFIX)/itd
|
install -Dm755 ./itd $(BIN_PREFIX)/itd
|
||||||
install -Dm755 ./itctl $(BIN_PREFIX)/itctl
|
install -Dm755 ./itctl $(BIN_PREFIX)/itctl
|
||||||
install -Dm644 ./itd.service $(SERVICE_PREFIX)/itd.service
|
install -Dm644 ./itd.service $(SERVICE_PREFIX)/itd.service
|
||||||
install -Dm644 ./itd.toml $(CFG_PREFIX)/itd.toml
|
install -Dm644 ./itd.toml $(CFG_PREFIX)/itd.toml
|
||||||
|
|
||||||
clean:
|
uninstall:
|
||||||
rm -f itctl
|
rm $(BIN_PREFIX)/itd
|
||||||
rm -f itd
|
rm $(BIN_PREFIX)/itctl
|
||||||
|
rm $(SERVICE_PREFIX)/itd.service
|
||||||
|
rm $(CFG_PREFIX)/itd.toml
|
||||||
|
|
||||||
.PHONY: all install clean
|
.PHONY: all clean install uninstall
|
||||||
83
README.md
83
README.md
@@ -3,11 +3,16 @@
|
|||||||
|
|
||||||
`itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io).
|
`itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io).
|
||||||
|
|
||||||
|
[](https://ci.appveyor.com/project/moussaelianarsen/itd)
|
||||||
|
[](https://minio.arsenm.dev/minio/itd/)
|
||||||
|
[](https://aur.archlinux.org/packages/itd-git/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- Notification relay
|
- Notification relay
|
||||||
|
- Notificstion transliteration
|
||||||
- Music control
|
- Music control
|
||||||
- Get info from watch (HRM, Battery level, Firmware version)
|
- Get info from watch (HRM, Battery level, Firmware version)
|
||||||
- Set current time
|
- Set current time
|
||||||
@@ -32,6 +37,42 @@ The various request types and their data requirements can be seen in `internal/t
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Transliteration
|
||||||
|
|
||||||
|
Since the PineTime does not have enough space to store all unicode glyphs, it only stores the ASCII space and Cyrillic. Therefore, this daemon can transliterate unsupported characters into supported ones. Since some languages have different transliterations, the transliterators to be used must be specified in the config. Here are the available transliterators:
|
||||||
|
|
||||||
|
- eASCII
|
||||||
|
- Scandinavian
|
||||||
|
- German
|
||||||
|
- Hebrew
|
||||||
|
- Greek
|
||||||
|
- Russian
|
||||||
|
- Ukranian
|
||||||
|
- Arabic
|
||||||
|
- Farsi
|
||||||
|
- Polish
|
||||||
|
- Lithuanian
|
||||||
|
- Estonian
|
||||||
|
- Icelandic
|
||||||
|
- Czeck
|
||||||
|
- French
|
||||||
|
- Armenian
|
||||||
|
- Korean
|
||||||
|
- Chinese
|
||||||
|
- Emoji
|
||||||
|
|
||||||
|
Place the desired map names in an array as `notifs.translit.use`. They will be evaluated in order. You can also put custom transliterations in `notifs.translit.custom`. These take priority over any other maps. The `notifs.translit` config section should look like this:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[notifs.translit]
|
||||||
|
use = ["eASCII", "Russian", "Emoji"]
|
||||||
|
custom = [
|
||||||
|
"test", "replaced"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `itctl`
|
### `itctl`
|
||||||
|
|
||||||
This daemon comes with a binary called `itctl` which uses the socket to control the daemon from the command line. As such, it can be scripted using bash.
|
This daemon comes with a binary called `itctl` which uses the socket to control the daemon from the command line. As such, it can be scripted using bash.
|
||||||
@@ -56,6 +97,48 @@ Flags:
|
|||||||
|
|
||||||
Use "itctl [command] --help" for more information about a command.
|
Use "itctl [command] --help" for more information about a command.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `itgui`
|
||||||
|
|
||||||
|
In `cmd/itgui`, there is a gui frontend to the socket of `itd`. It uses the [fyne library](https://fyne.io/) for Go. It can be compiled by running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go build ./cmd/itgui
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Interactive mode
|
||||||
|
|
||||||
|
Running `itctl` by itself will open interactive mode. It's essentially a shell where you can enter commands. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ itctl
|
||||||
|
itctl> fw ver
|
||||||
|
1.3.0
|
||||||
|
itctl> get batt
|
||||||
|
81%
|
||||||
|
itctl> get heart
|
||||||
|
92 BPM
|
||||||
|
itctl> set time 2021-08-22T00:06:18-07:00
|
||||||
|
itctl> set time now
|
||||||
|
itctl> exit
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/itd/internal/types"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ var addressCmd = &cobra.Command{
|
|||||||
Short: "Get InfiniTime's bluetooth address",
|
Short: "Get InfiniTime's bluetooth address",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Connect to itd UNIX socket
|
// Connect to itd UNIX socket
|
||||||
conn, err := net.Dial("unix", SockPath)
|
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ var addressCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Encode request into connection
|
// Encode request into connection
|
||||||
err = json.NewEncoder(conn).Encode(types.Request{
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
Type: ReqTypeBtAddress,
|
Type: types.ReqTypeBtAddress,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error making request")
|
log.Fatal().Err(err).Msg("Error making request")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/itd/internal/types"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ var batteryCmd = &cobra.Command{
|
|||||||
Short: "Get battery level from InfiniTime",
|
Short: "Get battery level from InfiniTime",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Connect to itd UNIX socket
|
// Connect to itd UNIX socket
|
||||||
conn, err := net.Dial("unix", SockPath)
|
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ var batteryCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Encode request into connection
|
// Encode request into connection
|
||||||
err = json.NewEncoder(conn).Encode(types.Request{
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
Type: ReqTypeBattLevel,
|
Type: types.ReqTypeBattLevel,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error making request")
|
log.Fatal().Err(err).Msg("Error making request")
|
||||||
|
|||||||
@@ -18,23 +18,6 @@
|
|||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
const SockPath = "/tmp/itd/socket"
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReqTypeHeartRate = "hrt"
|
|
||||||
ReqTypeBattLevel = "battlvl"
|
|
||||||
ReqTypeFwVersion = "fwver"
|
|
||||||
ReqTypeFwUpgrade = "fwupg"
|
|
||||||
ReqTypeBtAddress = "btaddr"
|
|
||||||
ReqTypeNotify = "notify"
|
|
||||||
ReqTypeSetTime = "settime"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
UpgradeTypeArchive = iota
|
|
||||||
UpgradeTypeFiles
|
|
||||||
)
|
|
||||||
|
|
||||||
type DFUProgress struct {
|
type DFUProgress struct {
|
||||||
Received int64 `mapstructure:"recvd"`
|
Received int64 `mapstructure:"recvd"`
|
||||||
Total int64 `mapstructure:"total"`
|
Total int64 `mapstructure:"total"`
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/itd/internal/types"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ var heartCmd = &cobra.Command{
|
|||||||
Short: "Get heart rate from InfiniTime",
|
Short: "Get heart rate from InfiniTime",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Connect to itd UNIX socket
|
// Connect to itd UNIX socket
|
||||||
conn, err := net.Dial("unix", SockPath)
|
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||||
}
|
}
|
||||||
@@ -43,7 +44,7 @@ var heartCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Encode request into connection
|
// Encode request into connection
|
||||||
err = json.NewEncoder(conn).Encode(types.Request{
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
Type: ReqTypeHeartRate,
|
Type: types.ReqTypeHeartRate,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error making request")
|
log.Fatal().Err(err).Msg("Error making request")
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/itd/internal/types"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ var notifyCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect to itd UNIX socket
|
// Connect to itd UNIX socket
|
||||||
conn, err := net.Dial("unix", SockPath)
|
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ var notifyCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Encode request into connection
|
// Encode request into connection
|
||||||
err = json.NewEncoder(conn).Encode(types.Request{
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
Type: ReqTypeNotify,
|
Type: types.ReqTypeNotify,
|
||||||
Data: types.ReqDataNotify{
|
Data: types.ReqDataNotify{
|
||||||
Title: args[0],
|
Title: args[0],
|
||||||
Body: args[1],
|
Body: args[1],
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"github.com/abiosoft/ishell"
|
"github.com/abiosoft/ishell"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands
|
||||||
@@ -60,5 +61,18 @@ var rootCmd = &cobra.Command{
|
|||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
func Execute() {
|
func Execute() {
|
||||||
|
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||||
cobra.CheckErr(rootCmd.Execute())
|
cobra.CheckErr(rootCmd.Execute())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Register flag for socket path
|
||||||
|
rootCmd.Flags().StringP("socket-path", "s", "", "Path to itd socket")
|
||||||
|
|
||||||
|
// Bind flag and environment variable to viper key
|
||||||
|
viper.BindPFlag("sockPath", rootCmd.Flags().Lookup("socket-path"))
|
||||||
|
viper.BindEnv("sockPath", "ITCTL_SOCKET_PATH")
|
||||||
|
|
||||||
|
// Set default value for socket path
|
||||||
|
viper.SetDefault("sockPath", "/tmp/itd/socket")
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/itd/internal/types"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,9 +40,9 @@ var timeCmd = &cobra.Command{
|
|||||||
log.Warn().Msg("Command time requires one argument")
|
log.Warn().Msg("Command time requires one argument")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to itd UNIX socket
|
// Connect to itd UNIX socket
|
||||||
conn, err := net.Dial("unix", SockPath)
|
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||||
}
|
}
|
||||||
@@ -49,7 +50,7 @@ var timeCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Encode request into connection
|
// Encode request into connection
|
||||||
err = json.NewEncoder(conn).Encode(types.Request{
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
Type: ReqTypeSetTime,
|
Type: types.ReqTypeSetTime,
|
||||||
Data: args[0],
|
Data: args[0],
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ var upgradeCmd = &cobra.Command{
|
|||||||
Aliases: []string{"upg"},
|
Aliases: []string{"upg"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Connect to itd UNIX socket
|
// Connect to itd UNIX socket
|
||||||
conn, err := net.Dial("unix", SockPath)
|
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||||
}
|
}
|
||||||
@@ -49,13 +49,13 @@ var upgradeCmd = &cobra.Command{
|
|||||||
if viper.GetString("archive") != "" {
|
if viper.GetString("archive") != "" {
|
||||||
// Get archive data struct
|
// Get archive data struct
|
||||||
data = types.ReqDataFwUpgrade{
|
data = types.ReqDataFwUpgrade{
|
||||||
Type: UpgradeTypeArchive,
|
Type: types.UpgradeTypeArchive,
|
||||||
Files: []string{viper.GetString("archive")},
|
Files: []string{viper.GetString("archive")},
|
||||||
}
|
}
|
||||||
} else if viper.GetString("initPkt") != "" && viper.GetString("firmware") != "" {
|
} else if viper.GetString("initPkt") != "" && viper.GetString("firmware") != "" {
|
||||||
// Get files data struct
|
// Get files data struct
|
||||||
data = types.ReqDataFwUpgrade{
|
data = types.ReqDataFwUpgrade{
|
||||||
Type: UpgradeTypeFiles,
|
Type: types.UpgradeTypeFiles,
|
||||||
Files: []string{viper.GetString("initPkt"), viper.GetString("firmware")},
|
Files: []string{viper.GetString("initPkt"), viper.GetString("firmware")},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -66,7 +66,7 @@ var upgradeCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Encode response into connection
|
// Encode response into connection
|
||||||
err = json.NewEncoder(conn).Encode(types.Request{
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
Type: ReqTypeFwUpgrade,
|
Type: types.ReqTypeFwUpgrade,
|
||||||
Data: data,
|
Data: data,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -95,14 +95,14 @@ var upgradeCmd = &cobra.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error decoding response data")
|
log.Fatal().Err(err).Msg("Error decoding response data")
|
||||||
}
|
}
|
||||||
// If transfer finished, break
|
|
||||||
if event.Received == event.Total {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Set total bytes in progress bar
|
// Set total bytes in progress bar
|
||||||
bar.SetTotal(event.Total)
|
bar.SetTotal(event.Total)
|
||||||
// Set amount of bytes received in progress bar
|
// Set amount of bytes received in progress bar
|
||||||
bar.SetCurrent(event.Received)
|
bar.SetCurrent(event.Received)
|
||||||
|
// If transfer finished, break
|
||||||
|
if event.Received == event.Total {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Finish progress bar
|
// Finish progress bar
|
||||||
bar.Finish()
|
bar.Finish()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/itd/internal/types"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ var versionCmd = &cobra.Command{
|
|||||||
Short: "Get firmware version of InfiniTime",
|
Short: "Get firmware version of InfiniTime",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Connect to itd UNIX socket
|
// Connect to itd UNIX socket
|
||||||
conn, err := net.Dial("unix", SockPath)
|
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ var versionCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Encode request into connection
|
// Encode request into connection
|
||||||
err = json.NewEncoder(conn).Encode(types.Request{
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
Type: ReqTypeFwVersion,
|
Type: types.ReqTypeFwVersion,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error making request")
|
log.Fatal().Err(err).Msg("Error making request")
|
||||||
|
|||||||
53
cmd/itgui/error.go
Normal file
53
cmd/itgui/error.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
)
|
||||||
|
|
||||||
|
func guiErr(err error, msg string, fatal bool, parent fyne.Window) {
|
||||||
|
// Create new label containing message
|
||||||
|
msgLbl := widget.NewLabel(msg)
|
||||||
|
// Text formatting settings
|
||||||
|
msgLbl.Wrapping = fyne.TextWrapWord
|
||||||
|
msgLbl.Alignment = fyne.TextAlignCenter
|
||||||
|
// Create new rectangle to set the size of the dialog
|
||||||
|
rect := canvas.NewRectangle(color.Transparent)
|
||||||
|
// Set minimum size of rectangle to 350x0
|
||||||
|
rect.SetMinSize(fyne.NewSize(350, 0))
|
||||||
|
// Create new container containing message and rectangle
|
||||||
|
content := container.NewVBox(
|
||||||
|
msgLbl,
|
||||||
|
rect,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
// Create new label containing error text
|
||||||
|
errLbl := widget.NewLabel(err.Error())
|
||||||
|
// Create new dropdown containing error label
|
||||||
|
content.Add(widget.NewAccordion(
|
||||||
|
widget.NewAccordionItem("More Details", errLbl),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if fatal {
|
||||||
|
// Create new error dialog
|
||||||
|
errDlg := dialog.NewCustom("Error", "Close", content, parent)
|
||||||
|
// On close, exit with code 1
|
||||||
|
errDlg.SetOnClosed(func() {
|
||||||
|
os.Exit(1)
|
||||||
|
})
|
||||||
|
// Show dialog
|
||||||
|
errDlg.Show()
|
||||||
|
// Run app prematurely to stop further execution
|
||||||
|
parent.ShowAndRun()
|
||||||
|
} else {
|
||||||
|
// Show error dialog
|
||||||
|
dialog.NewCustom("Error", "Ok", content, parent).Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
149
cmd/itgui/info.go
Normal file
149
cmd/itgui/info.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func infoTab(parent fyne.Window) *fyne.Container {
|
||||||
|
infoLayout := container.NewVBox(
|
||||||
|
// Add rectangle for a bit of padding
|
||||||
|
canvas.NewRectangle(color.Transparent),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create label for heart rate
|
||||||
|
heartRateLbl := newText("0 BPM", 24)
|
||||||
|
// Creae container to store heart rate section
|
||||||
|
heartRate := container.NewVBox(
|
||||||
|
newText("Heart Rate", 12),
|
||||||
|
heartRateLbl,
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
infoLayout.Add(heartRate)
|
||||||
|
|
||||||
|
// Watch for heart rate updates
|
||||||
|
go watch(types.ReqTypeWatchHeartRate, func(data interface{}) {
|
||||||
|
// Change text of heart rate label
|
||||||
|
heartRateLbl.Text = fmt.Sprintf("%d BPM", int(data.(float64)))
|
||||||
|
// Refresh label
|
||||||
|
heartRateLbl.Refresh()
|
||||||
|
}, parent)
|
||||||
|
|
||||||
|
// Create label for battery level
|
||||||
|
battLevelLbl := newText("0%", 24)
|
||||||
|
// Create container to store battery level section
|
||||||
|
battLevel := container.NewVBox(
|
||||||
|
newText("Battery Level", 12),
|
||||||
|
battLevelLbl,
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
infoLayout.Add(battLevel)
|
||||||
|
|
||||||
|
// Watch for changes in battery level
|
||||||
|
go watch(types.ReqTypeWatchBattLevel, func(data interface{}) {
|
||||||
|
battLevelLbl.Text = fmt.Sprintf("%d%%", int(data.(float64)))
|
||||||
|
battLevelLbl.Refresh()
|
||||||
|
}, parent)
|
||||||
|
|
||||||
|
fwVerString, err := get(types.ReqTypeFwVersion)
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error getting firmware string", true, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fwVer := container.NewVBox(
|
||||||
|
newText("Firmware Version", 12),
|
||||||
|
newText(fwVerString.(string), 24),
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
infoLayout.Add(fwVer)
|
||||||
|
|
||||||
|
btAddrString, err := get(types.ReqTypeBtAddress)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
btAddr := container.NewVBox(
|
||||||
|
newText("Bluetooth Address", 12),
|
||||||
|
newText(btAddrString.(string), 24),
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
infoLayout.Add(btAddr)
|
||||||
|
|
||||||
|
return infoLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
func watch(req int, onRecv func(data interface{}), parent fyne.Window) error {
|
||||||
|
conn, err := net.Dial("unix", SockPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
|
Type: req,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(conn)
|
||||||
|
for scanner.Scan() {
|
||||||
|
res, err := getResp(scanner.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error getting response from connection", false, parent)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
onRecv(res.Value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(req int) (interface{}, error) {
|
||||||
|
conn, err := net.Dial("unix", SockPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
|
Type: req,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := getResp(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResp(line []byte) (*types.Response, error) {
|
||||||
|
var res types.Response
|
||||||
|
err := json.Unmarshal(line, &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.Error {
|
||||||
|
return nil, errors.New(res.Message)
|
||||||
|
}
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newText(t string, size float32) *canvas.Text {
|
||||||
|
text := canvas.NewText(t, theme.ForegroundColor())
|
||||||
|
text.TextSize = size
|
||||||
|
return text
|
||||||
|
}
|
||||||
35
cmd/itgui/main.go
Normal file
35
cmd/itgui/main.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2/app"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SockPath = "/tmp/itd/socket"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create new app
|
||||||
|
a := app.New()
|
||||||
|
// Create new window with title "itgui"
|
||||||
|
window := a.NewWindow("itgui")
|
||||||
|
|
||||||
|
_, err := net.Dial("unix", SockPath)
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error dialing itd socket", true, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new app tabs container
|
||||||
|
tabs := container.NewAppTabs(
|
||||||
|
container.NewTabItem("Info", infoTab(window)),
|
||||||
|
container.NewTabItem("Notify", notifyTab(window)),
|
||||||
|
container.NewTabItem("Set Time", timeTab(window)),
|
||||||
|
container.NewTabItem("Upgrade", upgradeTab(window)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set tabs as window content
|
||||||
|
window.SetContent(tabs)
|
||||||
|
// Show window and run app
|
||||||
|
window.ShowAndRun()
|
||||||
|
}
|
||||||
49
cmd/itgui/notify.go
Normal file
49
cmd/itgui/notify.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func notifyTab(parent fyne.Window) *fyne.Container {
|
||||||
|
// Create new entry for notification title
|
||||||
|
titleEntry := widget.NewEntry()
|
||||||
|
titleEntry.SetPlaceHolder("Title")
|
||||||
|
|
||||||
|
// Create multiline entry for notification body
|
||||||
|
bodyEntry := widget.NewMultiLineEntry()
|
||||||
|
bodyEntry.SetPlaceHolder("Body")
|
||||||
|
|
||||||
|
// Create new button to send notification
|
||||||
|
sendBtn := widget.NewButton("Send", func() {
|
||||||
|
// Dial itd UNIX socket
|
||||||
|
conn, err := net.Dial("unix", SockPath)
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error dialing socket", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Encode notify request on connection
|
||||||
|
json.NewEncoder(conn).Encode(types.Request{
|
||||||
|
Type: types.ReqTypeNotify,
|
||||||
|
Data: types.ReqDataNotify{
|
||||||
|
Title: titleEntry.Text,
|
||||||
|
Body: bodyEntry.Text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return new container containing all elements
|
||||||
|
return container.NewVBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
titleEntry,
|
||||||
|
bodyEntry,
|
||||||
|
sendBtn,
|
||||||
|
layout.NewSpacer(),
|
||||||
|
)
|
||||||
|
}
|
||||||
79
cmd/itgui/time.go
Normal file
79
cmd/itgui/time.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func timeTab(parent fyne.Window) *fyne.Container {
|
||||||
|
// Create new entry for time string
|
||||||
|
timeEntry := widget.NewEntry()
|
||||||
|
// Set text to current time formatter properly
|
||||||
|
timeEntry.SetText(time.Now().Format(time.RFC1123))
|
||||||
|
|
||||||
|
// Create button to set current time
|
||||||
|
currentBtn := widget.NewButton("Set Current", func() {
|
||||||
|
timeEntry.SetText(time.Now().Format(time.RFC1123))
|
||||||
|
setTime(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create button to set time inside entry
|
||||||
|
timeBtn := widget.NewButton("Set", func() {
|
||||||
|
// Parse time as RFC1123 string
|
||||||
|
parsedTime, err := time.Parse(time.RFC1123, timeEntry.Text)
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error parsing time string", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Set time to parsed time
|
||||||
|
setTime(false, parsedTime)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return new container with all elements centered
|
||||||
|
return container.NewVBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
timeEntry,
|
||||||
|
currentBtn,
|
||||||
|
timeBtn,
|
||||||
|
layout.NewSpacer(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTime sets the first element in the variadic parameter
|
||||||
|
// if current is false, otherwise, it sets the current time.
|
||||||
|
func setTime(current bool, t ...time.Time) error {
|
||||||
|
// Dial UNIX socket
|
||||||
|
conn, err := net.Dial("unix", SockPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
var data string
|
||||||
|
// If current is true, use the string "now"
|
||||||
|
// otherwise, use the formatted time from the
|
||||||
|
// first element in the variadic parameter.
|
||||||
|
// "now" is more accurate than formatting
|
||||||
|
// current time as only seconds are preserved
|
||||||
|
// in that case.
|
||||||
|
if current {
|
||||||
|
data = "now"
|
||||||
|
} else {
|
||||||
|
data = t[0].Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
// Encode SetTime request with above data
|
||||||
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
|
Type: types.ReqTypeSetTime,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
191
cmd/itgui/upgrade.go
Normal file
191
cmd/itgui/upgrade.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/storage"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func upgradeTab(parent fyne.Window) *fyne.Container {
|
||||||
|
var (
|
||||||
|
archivePath string
|
||||||
|
fiwmarePath string
|
||||||
|
initPktPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create archive selection dialog
|
||||||
|
archiveDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
|
||||||
|
if e != nil || uc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uc.Close()
|
||||||
|
archivePath = uc.URI().Path()
|
||||||
|
}, parent)
|
||||||
|
// Limit dialog to .zip files
|
||||||
|
archiveDialog.SetFilter(storage.NewExtensionFileFilter([]string{".zip"}))
|
||||||
|
// Create button to show dialog
|
||||||
|
archiveBtn := widget.NewButton("Select archive (.zip)", archiveDialog.Show)
|
||||||
|
|
||||||
|
// Create firmware selection dialog
|
||||||
|
firmwareDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
|
||||||
|
if e != nil || uc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uc.Close()
|
||||||
|
fiwmarePath = uc.URI().Path()
|
||||||
|
}, parent)
|
||||||
|
// Limit dialog to .bin files
|
||||||
|
firmwareDialog.SetFilter(storage.NewExtensionFileFilter([]string{".bin"}))
|
||||||
|
// Create button to show dialog
|
||||||
|
firmwareBtn := widget.NewButton("Select init packet (.bin)", firmwareDialog.Show)
|
||||||
|
|
||||||
|
// Create init packet selection dialog
|
||||||
|
initPktDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
|
||||||
|
if e != nil || uc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uc.Close()
|
||||||
|
initPktPath = uc.URI().Path()
|
||||||
|
}, parent)
|
||||||
|
// Limit dialog to .dat files
|
||||||
|
initPktDialog.SetFilter(storage.NewExtensionFileFilter([]string{".dat"}))
|
||||||
|
// Create button to show dialog
|
||||||
|
initPktBtn := widget.NewButton("Select init packet (.dat)", initPktDialog.Show)
|
||||||
|
|
||||||
|
// Hide init packet and firmware buttons
|
||||||
|
initPktBtn.Hide()
|
||||||
|
firmwareBtn.Hide()
|
||||||
|
|
||||||
|
// Create dropdown to select upgrade type
|
||||||
|
upgradeTypeSelect := widget.NewSelect([]string{
|
||||||
|
"Archive",
|
||||||
|
"Files",
|
||||||
|
}, func(s string) {
|
||||||
|
// Hide all buttons
|
||||||
|
archiveBtn.Hide()
|
||||||
|
initPktBtn.Hide()
|
||||||
|
firmwareBtn.Hide()
|
||||||
|
// Unhide appropriate button(s)
|
||||||
|
switch s {
|
||||||
|
case "Archive":
|
||||||
|
archiveBtn.Show()
|
||||||
|
case "Files":
|
||||||
|
initPktBtn.Show()
|
||||||
|
firmwareBtn.Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Select first elemetn
|
||||||
|
upgradeTypeSelect.SetSelectedIndex(0)
|
||||||
|
|
||||||
|
// Create new button to start DFU
|
||||||
|
startBtn := widget.NewButton("Start", func() {
|
||||||
|
// If archive path does not exist and both init packet and firmware paths
|
||||||
|
// also do not exist, return error
|
||||||
|
if archivePath == "" && (initPktPath == "" && fiwmarePath == "") {
|
||||||
|
guiErr(nil, "Upgrade requires archive or files selected", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new label for byte progress
|
||||||
|
progressLbl := widget.NewLabelWithStyle("0 / 0 B", fyne.TextAlignCenter, fyne.TextStyle{})
|
||||||
|
// Create new progress bar
|
||||||
|
progressBar := widget.NewProgressBar()
|
||||||
|
// Create modal dialog containing label and progress bar
|
||||||
|
progressDlg := widget.NewModalPopUp(container.NewVBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
progressLbl,
|
||||||
|
progressBar,
|
||||||
|
layout.NewSpacer(),
|
||||||
|
), parent.Canvas())
|
||||||
|
// Resize modal to 300x100
|
||||||
|
progressDlg.Resize(fyne.NewSize(300, 100))
|
||||||
|
|
||||||
|
var fwUpgType int
|
||||||
|
var files []string
|
||||||
|
// Get appropriate upgrade type and file paths
|
||||||
|
switch upgradeTypeSelect.Selected {
|
||||||
|
case "Archive":
|
||||||
|
fwUpgType = types.UpgradeTypeArchive
|
||||||
|
files = append(files, archivePath)
|
||||||
|
case "Files":
|
||||||
|
fwUpgType = types.UpgradeTypeFiles
|
||||||
|
files = append(files, initPktPath, fiwmarePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial itd UNIX socket
|
||||||
|
conn, err := net.Dial("unix", SockPath)
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error dialing socket", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// Encode firmware upgrade request to connection
|
||||||
|
json.NewEncoder(conn).Encode(types.Request{
|
||||||
|
Type: types.ReqTypeFwUpgrade,
|
||||||
|
Data: types.ReqDataFwUpgrade{
|
||||||
|
Type: fwUpgType,
|
||||||
|
Files: files,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show progress dialog
|
||||||
|
progressDlg.Show()
|
||||||
|
// Hide progress dialog after completion
|
||||||
|
defer progressDlg.Hide()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(conn)
|
||||||
|
for scanner.Scan() {
|
||||||
|
var res types.Response
|
||||||
|
// Decode scanned line into response struct
|
||||||
|
err = json.Unmarshal(scanner.Bytes(), &res)
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error decoding response", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.Error {
|
||||||
|
guiErr(err, "Error returned in response", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var event types.DFUProgress
|
||||||
|
// Decode response data into progress struct
|
||||||
|
err = mapstructure.Decode(res.Value, &event)
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error decoding response value", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Set label text to received / total B
|
||||||
|
progressLbl.SetText(fmt.Sprintf("%d / %d B", event.Received, event.Total))
|
||||||
|
// Set progress bar values
|
||||||
|
progressBar.Max = float64(event.Total)
|
||||||
|
progressBar.Value = float64(event.Received)
|
||||||
|
// Refresh progress bar
|
||||||
|
progressBar.Refresh()
|
||||||
|
// If transfer finished, break
|
||||||
|
if event.Received == event.Total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return container containing all elements
|
||||||
|
return container.NewVBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
upgradeTypeSelect,
|
||||||
|
archiveBtn,
|
||||||
|
firmwareBtn,
|
||||||
|
initPktBtn,
|
||||||
|
startBtn,
|
||||||
|
layout.NewSpacer(),
|
||||||
|
)
|
||||||
|
}
|
||||||
47
config.go
Normal file
47
config.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Set up logger
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
|
// Set config settings
|
||||||
|
setCfgDefaults()
|
||||||
|
viper.AddConfigPath("$HOME/.config")
|
||||||
|
viper.AddConfigPath("/etc")
|
||||||
|
viper.SetConfigName("itd")
|
||||||
|
viper.SetConfigType("toml")
|
||||||
|
viper.WatchConfig()
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
viper.SetEnvPrefix("itd")
|
||||||
|
// Ignore error because defaults set
|
||||||
|
viper.ReadInConfig()
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCfgDefaults() {
|
||||||
|
viper.SetDefault("cfg.version", 2)
|
||||||
|
|
||||||
|
viper.SetDefault("socket.path", "/tmp/itd/socket")
|
||||||
|
|
||||||
|
viper.SetDefault("conn.reconnect", true)
|
||||||
|
|
||||||
|
viper.SetDefault("on.connect.notify", true)
|
||||||
|
|
||||||
|
viper.SetDefault("on.reconnect.notify", true)
|
||||||
|
viper.SetDefault("on.reconnect.setTime", true)
|
||||||
|
|
||||||
|
viper.SetDefault("notifs.ignore.sender", []string{})
|
||||||
|
viper.SetDefault("notifs.ignore.summary", []string{"InfiniTime"})
|
||||||
|
viper.SetDefault("notifs.ignore.body", []string{})
|
||||||
|
|
||||||
|
viper.SetDefault("music.vol.interval", 5)
|
||||||
|
}
|
||||||
8
go.mod
8
go.mod
@@ -3,6 +3,7 @@ module go.arsenm.dev/itd
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
fyne.io/fyne/v2 v2.0.4
|
||||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||||
@@ -14,12 +15,13 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.1
|
github.com/mitchellh/mapstructure v1.4.1
|
||||||
|
github.com/mozillazg/go-pinyin v0.18.0
|
||||||
github.com/rs/zerolog v1.23.0
|
github.com/rs/zerolog v1.23.0
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
github.com/spf13/cobra v1.2.1
|
github.com/spf13/cobra v1.2.1
|
||||||
github.com/spf13/viper v1.8.1
|
github.com/spf13/viper v1.8.1
|
||||||
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b
|
go.arsenm.dev/infinitime v0.0.0-20210825051734-745b4bd37cf4
|
||||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 // indirect
|
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7
|
||||||
)
|
)
|
||||||
|
|||||||
42
go.sum
42
go.sum
@@ -37,8 +37,11 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
fyne.io/fyne/v2 v2.0.4 h1:eDGaPGzeR4qNqWuAp9Li1kY4eVIHldCkf42KMakKIK4=
|
||||||
|
fyne.io/fyne/v2 v2.0.4/go.mod h1:nNpgL7sZkDVLraGtQII2ArNRnnl6kHup/KfQRxIhbvs=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||||
@@ -46,6 +49,7 @@ github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma
|
|||||||
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
|
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
@@ -85,17 +89,28 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
|||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
||||||
|
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
|
||||||
|
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.5.0 h1:NO5hkcB+srp1x6QmwvNZLeaOgbM8cmBTN32THzjvu2k=
|
github.com/fsnotify/fsnotify v1.5.0 h1:NO5hkcB+srp1x6QmwvNZLeaOgbM8cmBTN32THzjvu2k=
|
||||||
github.com/fsnotify/fsnotify v1.5.0/go.mod h1:BX0DCEr5pT4jm2CnQdVP1lFV521fcCNcyEeNp4DQQDk=
|
github.com/fsnotify/fsnotify v1.5.0/go.mod h1:BX0DCEr5pT4jm2CnQdVP1lFV521fcCNcyEeNp4DQQDk=
|
||||||
|
github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a h1:3TAJhl8vXyli0tooKB0vd6gLCyBdWL4QEYbDoJpHEZk=
|
||||||
|
github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
||||||
|
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||||
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@@ -186,6 +201,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
|||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||||
|
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
@@ -199,6 +216,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
|
||||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
@@ -224,14 +242,18 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mozillazg/go-pinyin v0.18.0 h1:hQompXO23/0ohH8YNjvfsAITnCQImCiR/Fny8EhIeW0=
|
||||||
|
github.com/mozillazg/go-pinyin v0.18.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
|
||||||
github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d h1:EG/xyWjHT19rkUpwsWSkyiCCmyqNwFovr9m10rhyOxU=
|
github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d h1:EG/xyWjHT19rkUpwsWSkyiCCmyqNwFovr9m10rhyOxU=
|
||||||
github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
|
github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
|
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
|
||||||
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
|
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
|
||||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
@@ -258,6 +280,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
|
|||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
||||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
@@ -267,10 +290,15 @@ github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
|
|||||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
|
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
|
||||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
@@ -287,8 +315,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b h1:Wwj7F0gqYHUx+9H8fCCIy5JZTlCusJRpPuzeSFM0EoU=
|
go.arsenm.dev/infinitime v0.0.0-20210825051734-745b4bd37cf4 h1:XZyynxrvGxP0mwyhdiuMrvj5SkiK6N+MDiC6DiGzgWU=
|
||||||
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b/go.mod h1:gaepaueUz4J5FfxuV19B4w5pi+V3mD0LTef50ryxr/Q=
|
go.arsenm.dev/infinitime v0.0.0-20210825051734-745b4bd37cf4/go.mod h1:gaepaueUz4J5FfxuV19B4w5pi+V3mD0LTef50ryxr/Q=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||||
@@ -321,6 +349,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
|||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||||
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -379,6 +409,7 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -433,6 +464,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -449,8 +481,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 h1:rw6UNGRMfarCepjI8qOepea/SXwIBVfTKjztZ5gBbq4=
|
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
|
||||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -477,6 +509,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
|||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -498,6 +531,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
|
|||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReqTypeHeartRate = iota
|
||||||
|
ReqTypeBattLevel
|
||||||
|
ReqTypeFwVersion
|
||||||
|
ReqTypeFwUpgrade
|
||||||
|
ReqTypeBtAddress
|
||||||
|
ReqTypeNotify
|
||||||
|
ReqTypeSetTime
|
||||||
|
ReqTypeWatchHeartRate
|
||||||
|
ReqTypeWatchBattLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UpgradeTypeArchive = iota
|
||||||
|
UpgradeTypeFiles
|
||||||
|
)
|
||||||
|
|
||||||
type ReqDataFwUpgrade struct {
|
type ReqDataFwUpgrade struct {
|
||||||
Type int
|
Type int
|
||||||
Files []string
|
Files []string
|
||||||
@@ -12,11 +29,16 @@ type Response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Type string `json:"type"`
|
Type int `json:"type"`
|
||||||
Data interface{} `json:"data,omitempty"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReqDataNotify struct {
|
type ReqDataNotify struct {
|
||||||
Title string
|
Title string
|
||||||
Body string
|
Body string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DFUProgress struct {
|
||||||
|
Received int64 `mapstructure:"recvd"`
|
||||||
|
Total int64 `mapstructure:"total"`
|
||||||
|
}
|
||||||
|
|||||||
24
itd.toml
24
itd.toml
@@ -1,14 +1,28 @@
|
|||||||
|
# This is temporary, it is to show a notice
|
||||||
|
# to people still using the old config
|
||||||
|
cfg.version = 2
|
||||||
|
|
||||||
|
[socket]
|
||||||
|
path = "/tmp/itd/socket"
|
||||||
|
|
||||||
[conn]
|
[conn]
|
||||||
reconnect = true
|
reconnect = true
|
||||||
|
|
||||||
[notify]
|
[on.connect]
|
||||||
onConnect = true
|
notify = true
|
||||||
onReconnect = true
|
|
||||||
|
|
||||||
[notifications.ignore]
|
[on.reconnect]
|
||||||
|
notify = true
|
||||||
|
setTime = true
|
||||||
|
|
||||||
|
[notifs.translit]
|
||||||
|
use = ["eASCII", "Russian", "Emoji"]
|
||||||
|
|
||||||
|
[notifs.ignore]
|
||||||
sender = []
|
sender = []
|
||||||
summary = ["InfiniTime"]
|
summary = ["InfiniTime"]
|
||||||
body = []
|
body = []
|
||||||
|
|
||||||
[music]
|
[music]
|
||||||
volInterval = 5
|
vol.interval = 5
|
||||||
|
|
||||||
|
|||||||
39
main.go
39
main.go
@@ -19,11 +19,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/infinitime"
|
"go.arsenm.dev/infinitime"
|
||||||
@@ -31,25 +28,11 @@ import (
|
|||||||
|
|
||||||
var firmwareUpdating = false
|
var firmwareUpdating = false
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Set up logger
|
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
|
||||||
|
|
||||||
// Set config settings
|
|
||||||
viper.AddConfigPath("$HOME/.config")
|
|
||||||
viper.AddConfigPath("/etc")
|
|
||||||
viper.SetConfigName("itd")
|
|
||||||
viper.SetConfigType("toml")
|
|
||||||
viper.WatchConfig()
|
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
||||||
viper.SetEnvPrefix("itd")
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
|
||||||
log.Warn().Err(err).Msg("Could not read in config")
|
|
||||||
}
|
|
||||||
viper.AutomaticEnv()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if viper.GetInt("cfg.version") != 2 {
|
||||||
|
log.Fatal().Msg("Please update your config to the newest format, only v2 configs supported.")
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanly exit after function
|
// Cleanly exit after function
|
||||||
defer infinitime.Exit()
|
defer infinitime.Exit()
|
||||||
|
|
||||||
@@ -63,14 +46,16 @@ func main() {
|
|||||||
|
|
||||||
// When InfiniTime reconnects
|
// When InfiniTime reconnects
|
||||||
dev.OnReconnect(func() {
|
dev.OnReconnect(func() {
|
||||||
// Set time to current time
|
if viper.GetBool("on.reconnect.setTime") {
|
||||||
err = dev.SetTime(time.Now())
|
// Set time to current time
|
||||||
if err != nil {
|
err = dev.SetTime(time.Now())
|
||||||
log.Error().Err(err).Msg("Error setting current time on connected InfiniTime")
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error setting current time on connected InfiniTime")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If config specifies to notify on reconnect
|
// If config specifies to notify on reconnect
|
||||||
if viper.GetBool("notify.onReconnect") {
|
if viper.GetBool("on.reconnect.notify") {
|
||||||
// Send notification to InfiniTime
|
// Send notification to InfiniTime
|
||||||
err = dev.Notify("itd", "Successfully reconnected")
|
err = dev.Notify("itd", "Successfully reconnected")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -89,7 +74,7 @@ func main() {
|
|||||||
log.Info().Str("version", ver).Msg("Connected to InfiniTime")
|
log.Info().Str("version", ver).Msg("Connected to InfiniTime")
|
||||||
|
|
||||||
// If config specifies to notify on connect
|
// If config specifies to notify on connect
|
||||||
if viper.GetBool("notify.onConnect") {
|
if viper.GetBool("on.connect.notify") {
|
||||||
// Send notification to InfiniTime
|
// Send notification to InfiniTime
|
||||||
err = dev.Notify("itd", "Successfully connected")
|
err = dev.Notify("itd", "Successfully connected")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
8
music.go
8
music.go
@@ -19,10 +19,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go.arsenm.dev/infinitime"
|
|
||||||
"go.arsenm.dev/infinitime/pkg/player"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/infinitime"
|
||||||
|
"go.arsenm.dev/infinitime/pkg/player"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initMusicCtrl(dev *infinitime.Device) error {
|
func initMusicCtrl(dev *infinitime.Device) error {
|
||||||
@@ -85,9 +85,9 @@ func initMusicCtrl(dev *infinitime.Device) error {
|
|||||||
case infinitime.MusicEventPrev:
|
case infinitime.MusicEventPrev:
|
||||||
player.Prev()
|
player.Prev()
|
||||||
case infinitime.MusicEventVolUp:
|
case infinitime.MusicEventVolUp:
|
||||||
player.VolUp(viper.GetUint("music.volInterval"))
|
player.VolUp(viper.GetUint("music.vol.interval"))
|
||||||
case infinitime.MusicEventVolDown:
|
case infinitime.MusicEventVolDown:
|
||||||
player.VolDown(viper.GetUint("music.volInterval"))
|
player.VolDown(viper.GetUint("music.vol.interval"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
13
notifs.go
13
notifs.go
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/infinitime"
|
"go.arsenm.dev/infinitime"
|
||||||
|
"go.arsenm.dev/itd/translit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initNotifRelay(dev *infinitime.Device) error {
|
func initNotifRelay(dev *infinitime.Device) error {
|
||||||
@@ -71,6 +72,12 @@ func initNotifRelay(dev *infinitime.Device) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maps := viper.GetStringSlice("notifs.translit.use")
|
||||||
|
translit.Transliterators["custom"] = translit.Map(viper.GetStringSlice("notifs.translit.custom"))
|
||||||
|
sender = translit.Transliterate(sender, maps...)
|
||||||
|
summary = translit.Transliterate(summary, maps...)
|
||||||
|
body = translit.Transliterate(body, maps...)
|
||||||
|
|
||||||
var msg string
|
var msg string
|
||||||
// If summary does not exist, set message to body.
|
// If summary does not exist, set message to body.
|
||||||
// If it does, set message to summary, two newlines, and then body
|
// If it does, set message to summary, two newlines, and then body
|
||||||
@@ -90,9 +97,9 @@ func initNotifRelay(dev *infinitime.Device) error {
|
|||||||
|
|
||||||
// ignored checks whether any fields were ignored in the config
|
// ignored checks whether any fields were ignored in the config
|
||||||
func ignored(sender, summary, body string) bool {
|
func ignored(sender, summary, body string) bool {
|
||||||
ignoreSender := viper.GetStringSlice("notifications.ignore.sender")
|
ignoreSender := viper.GetStringSlice("notifs.ignore.sender")
|
||||||
ignoreSummary := viper.GetStringSlice("notifications.ignore.summary")
|
ignoreSummary := viper.GetStringSlice("notifs.ignore.summary")
|
||||||
ignoreBody := viper.GetStringSlice("notifications.ignore.body")
|
ignoreBody := viper.GetStringSlice("notifs.ignore.body")
|
||||||
return strSlcContains(ignoreSender, sender) ||
|
return strSlcContains(ignoreSender, sender) ||
|
||||||
strSlcContains(ignoreSummary, summary) ||
|
strSlcContains(ignoreSummary, summary) ||
|
||||||
strSlcContains(ignoreBody, body)
|
strSlcContains(ignoreBody, body)
|
||||||
|
|||||||
98
socket.go
98
socket.go
@@ -29,42 +29,27 @@ import (
|
|||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/infinitime"
|
"go.arsenm.dev/infinitime"
|
||||||
"go.arsenm.dev/itd/internal/types"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
)
|
"go.arsenm.dev/itd/translit"
|
||||||
|
|
||||||
const SockPath = "/tmp/itd/socket"
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReqTypeHeartRate = "hrt"
|
|
||||||
ReqTypeBattLevel = "battlvl"
|
|
||||||
ReqTypeFwVersion = "fwver"
|
|
||||||
ReqTypeFwUpgrade = "fwupg"
|
|
||||||
ReqTypeBtAddress = "btaddr"
|
|
||||||
ReqTypeNotify = "notify"
|
|
||||||
ReqTypeSetTime = "settime"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
UpgradeTypeArchive = iota
|
|
||||||
UpgradeTypeFiles
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startSocket(dev *infinitime.Device) error {
|
func startSocket(dev *infinitime.Device) error {
|
||||||
// Make socket directory if non existant
|
// Make socket directory if non-existent
|
||||||
err := os.MkdirAll(filepath.Dir(SockPath), 0755)
|
err := os.MkdirAll(filepath.Dir(viper.GetString("socket.path")), 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old socket if it exists
|
// Remove old socket if it exists
|
||||||
err = os.RemoveAll(SockPath)
|
err = os.RemoveAll(viper.GetString("socket.path"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen on socket path
|
// Listen on socket path
|
||||||
ln, err := net.Listen("unix", SockPath)
|
ln, err := net.Listen("unix", viper.GetString("socket.path"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -83,7 +68,7 @@ func startSocket(dev *infinitime.Device) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Log socket start
|
// Log socket start
|
||||||
log.Info().Str("path", SockPath).Msg("Started control socket")
|
log.Info().Str("path", viper.GetString("socket.path")).Msg("Started control socket")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -108,7 +93,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch req.Type {
|
switch req.Type {
|
||||||
case ReqTypeHeartRate:
|
case types.ReqTypeHeartRate:
|
||||||
// Get heart rate from watch
|
// Get heart rate from watch
|
||||||
heartRate, err := dev.HeartRate()
|
heartRate, err := dev.HeartRate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -119,7 +104,33 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
|||||||
json.NewEncoder(conn).Encode(types.Response{
|
json.NewEncoder(conn).Encode(types.Response{
|
||||||
Value: heartRate,
|
Value: heartRate,
|
||||||
})
|
})
|
||||||
case ReqTypeBattLevel:
|
case types.ReqTypeWatchHeartRate:
|
||||||
|
heartRateCh, err := dev.WatchHeartRate()
|
||||||
|
if err != nil {
|
||||||
|
connErr(conn, err, "Error getting heart rate channel")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for heartRate := range heartRateCh {
|
||||||
|
json.NewEncoder(conn).Encode(types.Response{
|
||||||
|
Value: heartRate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case types.ReqTypeWatchBattLevel:
|
||||||
|
battLevelCh, err := dev.WatchBatteryLevel()
|
||||||
|
if err != nil {
|
||||||
|
connErr(conn, err, "Error getting heart rate channel")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for battLevel := range battLevelCh {
|
||||||
|
json.NewEncoder(conn).Encode(types.Response{
|
||||||
|
Value: battLevel,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case types.ReqTypeBattLevel:
|
||||||
// Get battery level from watch
|
// Get battery level from watch
|
||||||
battLevel, err := dev.BatteryLevel()
|
battLevel, err := dev.BatteryLevel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -130,7 +141,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
|||||||
json.NewEncoder(conn).Encode(types.Response{
|
json.NewEncoder(conn).Encode(types.Response{
|
||||||
Value: battLevel,
|
Value: battLevel,
|
||||||
})
|
})
|
||||||
case ReqTypeFwVersion:
|
case types.ReqTypeFwVersion:
|
||||||
// Get firmware version from watch
|
// Get firmware version from watch
|
||||||
version, err := dev.Version()
|
version, err := dev.Version()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -141,42 +152,46 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
|||||||
json.NewEncoder(conn).Encode(types.Response{
|
json.NewEncoder(conn).Encode(types.Response{
|
||||||
Value: version,
|
Value: version,
|
||||||
})
|
})
|
||||||
case ReqTypeBtAddress:
|
case types.ReqTypeBtAddress:
|
||||||
// Encode bluetooth address to connection
|
// Encode bluetooth address to connection
|
||||||
json.NewEncoder(conn).Encode(types.Response{
|
json.NewEncoder(conn).Encode(types.Response{
|
||||||
Value: dev.Address(),
|
Value: dev.Address(),
|
||||||
})
|
})
|
||||||
case ReqTypeNotify:
|
case types.ReqTypeNotify:
|
||||||
// If no data, return error
|
// If no data, return error
|
||||||
if req.Data == nil {
|
if req.Data == nil {
|
||||||
connErr(conn, nil, "Data required for notify types.Request")
|
connErr(conn, nil, "Data required for notify request")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
var reqData types.ReqDataNotify
|
var reqData types.ReqDataNotify
|
||||||
// Decode data map to notify types.Request data
|
// Decode data map to notify request data
|
||||||
err = mapstructure.Decode(req.Data, &reqData)
|
err = mapstructure.Decode(req.Data, &reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
connErr(conn, err, "Error decoding types.Request data")
|
connErr(conn, err, "Error decoding request data")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
maps := viper.GetStringSlice("notifs.translit.use")
|
||||||
|
translit.Transliterators["custom"] = translit.Map(viper.GetStringSlice("notifs.translit.custom"))
|
||||||
|
title := translit.Transliterate(reqData.Title, maps...)
|
||||||
|
body := translit.Transliterate(reqData.Body, maps...)
|
||||||
// Send notification to watch
|
// Send notification to watch
|
||||||
err = dev.Notify(reqData.Title, reqData.Body)
|
err = dev.Notify(title, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
connErr(conn, err, "Error sending notification")
|
connErr(conn, err, "Error sending notification")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Encode empty types.Response to connection
|
// Encode empty types.Response to connection
|
||||||
json.NewEncoder(conn).Encode(types.Response{})
|
json.NewEncoder(conn).Encode(types.Response{})
|
||||||
case ReqTypeSetTime:
|
case types.ReqTypeSetTime:
|
||||||
// If no data, return error
|
// If no data, return error
|
||||||
if req.Data == nil {
|
if req.Data == nil {
|
||||||
connErr(conn, nil, "Data required for settime types.Request")
|
connErr(conn, nil, "Data required for settime request")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Get string from data or return error
|
// Get string from data or return error
|
||||||
reqTimeStr, ok := req.Data.(string)
|
reqTimeStr, ok := req.Data.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
connErr(conn, nil, "Data for settime types.Request must be RFC3339 formatted time string")
|
connErr(conn, nil, "Data for settime request must be RFC3339 formatted time string")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +199,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
|||||||
if reqTimeStr == "now" {
|
if reqTimeStr == "now" {
|
||||||
reqTime = time.Now()
|
reqTime = time.Now()
|
||||||
} else {
|
} else {
|
||||||
// Parse time as RFC3339/ISO9601
|
// Parse time as RFC3339/ISO8601
|
||||||
reqTime, err = time.Parse(time.RFC3339, reqTimeStr)
|
reqTime, err = time.Parse(time.RFC3339, reqTimeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
connErr(conn, err, "Invalid time format. Time string must be formatted as ISO8601 or the word `now`")
|
connErr(conn, err, "Invalid time format. Time string must be formatted as ISO8601 or the word `now`")
|
||||||
@@ -199,21 +214,21 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
|||||||
}
|
}
|
||||||
// Encode empty types.Response to connection
|
// Encode empty types.Response to connection
|
||||||
json.NewEncoder(conn).Encode(types.Response{})
|
json.NewEncoder(conn).Encode(types.Response{})
|
||||||
case ReqTypeFwUpgrade:
|
case types.ReqTypeFwUpgrade:
|
||||||
// If no data, return error
|
// If no data, return error
|
||||||
if req.Data == nil {
|
if req.Data == nil {
|
||||||
connErr(conn, nil, "Data required for firmware upgrade types.Request")
|
connErr(conn, nil, "Data required for firmware upgrade request")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
var reqData types.ReqDataFwUpgrade
|
var reqData types.ReqDataFwUpgrade
|
||||||
// Decode data map to firmware upgrade types.Request data
|
// Decode data map to firmware upgrade request data
|
||||||
err = mapstructure.Decode(req.Data, &reqData)
|
err = mapstructure.Decode(req.Data, &reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
connErr(conn, err, "Error decoding types.Request data")
|
connErr(conn, err, "Error decoding request data")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch reqData.Type {
|
switch reqData.Type {
|
||||||
case UpgradeTypeArchive:
|
case types.UpgradeTypeArchive:
|
||||||
// If less than one file, return error
|
// If less than one file, return error
|
||||||
if len(reqData.Files) < 1 {
|
if len(reqData.Files) < 1 {
|
||||||
connErr(conn, nil, "Archive upgrade requires one file with .zip extension")
|
connErr(conn, nil, "Archive upgrade requires one file with .zip extension")
|
||||||
@@ -230,7 +245,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
|||||||
connErr(conn, err, "Error loading archive file")
|
connErr(conn, err, "Error loading archive file")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case UpgradeTypeFiles:
|
case types.UpgradeTypeFiles:
|
||||||
// If less than two files, return error
|
// If less than two files, return error
|
||||||
if len(reqData.Files) < 2 {
|
if len(reqData.Files) < 2 {
|
||||||
connErr(conn, nil, "Files upgrade requires two files. First with .dat and second with .bin extension.")
|
connErr(conn, nil, "Files upgrade requires two files. First with .dat and second with .bin extension.")
|
||||||
@@ -272,6 +287,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
|||||||
err = dev.DFU.Start()
|
err = dev.DFU.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
connErr(conn, err, "Error performing upgrade")
|
connErr(conn, err, "Error performing upgrade")
|
||||||
|
firmwareUpdating = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
firmwareUpdating = false
|
firmwareUpdating = false
|
||||||
|
|||||||
138
translit/armenian.go
Normal file
138
translit/armenian.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package translit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArmenianTranslit struct{}
|
||||||
|
|
||||||
|
var armenianMap = []string{
|
||||||
|
"աու", "au",
|
||||||
|
"բու", "bu",
|
||||||
|
"գու", "gu",
|
||||||
|
"դու", "du",
|
||||||
|
"եու", "eu",
|
||||||
|
"զու", "zu",
|
||||||
|
"էու", "eu",
|
||||||
|
"ըու", "yu",
|
||||||
|
"թու", "tu",
|
||||||
|
"ժու", "ju",
|
||||||
|
"իու", "iu",
|
||||||
|
"լու", "lu",
|
||||||
|
"խու", "xu",
|
||||||
|
"ծու", "cu",
|
||||||
|
"կու", "ku",
|
||||||
|
"հու", "hu",
|
||||||
|
"ձու", "dzu",
|
||||||
|
"ղու", "xu",
|
||||||
|
"ճու", "cu",
|
||||||
|
"մու", "mu",
|
||||||
|
"յու", "yu",
|
||||||
|
"նու", "nu",
|
||||||
|
"շու", "shu",
|
||||||
|
"չու", "chu",
|
||||||
|
"պու", "pu",
|
||||||
|
"ջու", "ju",
|
||||||
|
"ռու", "ru",
|
||||||
|
"սու", "su",
|
||||||
|
"վու", "vu",
|
||||||
|
"տու", "tu",
|
||||||
|
"րու", "ru",
|
||||||
|
"ցու", "cu",
|
||||||
|
"փու", "pu",
|
||||||
|
"քու", "qu",
|
||||||
|
"օու", "ou",
|
||||||
|
"ևու", "eu",
|
||||||
|
"ֆու", "fu",
|
||||||
|
"ոու", "vou",
|
||||||
|
"ու", "u",
|
||||||
|
"բո", "bo",
|
||||||
|
"գո", "go",
|
||||||
|
"դո", "do",
|
||||||
|
"զո", "zo",
|
||||||
|
"թո", "to",
|
||||||
|
"ժո", "jo",
|
||||||
|
"լո", "lo",
|
||||||
|
"խո", "xo",
|
||||||
|
"ծո", "co",
|
||||||
|
"կո", "ko",
|
||||||
|
"հո", "ho",
|
||||||
|
"ձո", "dzo",
|
||||||
|
"ղո", "xo",
|
||||||
|
"ճո", "co",
|
||||||
|
"մո", "mo",
|
||||||
|
"յո", "yo",
|
||||||
|
"նո", "no",
|
||||||
|
"շո", "so",
|
||||||
|
"չո", "co",
|
||||||
|
"պո", "po",
|
||||||
|
"ջո", "jo",
|
||||||
|
"ռո", "ro",
|
||||||
|
"սո", "so",
|
||||||
|
"վո", "vo",
|
||||||
|
"տո", "to",
|
||||||
|
"րո", "ro",
|
||||||
|
"ցո", "co",
|
||||||
|
"փո", "po",
|
||||||
|
"քո", "qo",
|
||||||
|
"ևո", "eo",
|
||||||
|
"ֆո", "fo",
|
||||||
|
"ո", "vo",
|
||||||
|
"եւ", "ev",
|
||||||
|
"եվ", "ev",
|
||||||
|
"ա", "a",
|
||||||
|
"բ", "b",
|
||||||
|
"գ", "g",
|
||||||
|
"դ", "d",
|
||||||
|
"ե", "e",
|
||||||
|
"զ", "z",
|
||||||
|
"է", "e",
|
||||||
|
"ը", "y",
|
||||||
|
"թ", "t",
|
||||||
|
"ժ", "j",
|
||||||
|
"ի", "i",
|
||||||
|
"լ", "l",
|
||||||
|
"խ", "x",
|
||||||
|
"ծ", "c",
|
||||||
|
"կ", "k",
|
||||||
|
"հ", "h",
|
||||||
|
"ձ", "dz",
|
||||||
|
"ղ", "x",
|
||||||
|
"ճ", "c",
|
||||||
|
"մ", "m",
|
||||||
|
"յ", "y",
|
||||||
|
"ն", "n",
|
||||||
|
"շ", "sh",
|
||||||
|
"չ", "ch",
|
||||||
|
"պ", "p",
|
||||||
|
"ջ", "j",
|
||||||
|
"ռ", "r",
|
||||||
|
"ս", "s",
|
||||||
|
"վ", "v",
|
||||||
|
"տ", "t",
|
||||||
|
"ր", "r",
|
||||||
|
"ց", "c",
|
||||||
|
"փ", "p",
|
||||||
|
"ք", "q",
|
||||||
|
"օ", "o",
|
||||||
|
"և", "ev",
|
||||||
|
"ֆ", "f",
|
||||||
|
"ւ", "",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
lower := armenianMap
|
||||||
|
for i, val := range lower {
|
||||||
|
if i%2 == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
capital := strings.Title(val)
|
||||||
|
if capital != val {
|
||||||
|
armenianMap = append(armenianMap, capital, strings.Title(armenianMap[i+1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (at *ArmenianTranslit) Transliterate(s string) string {
|
||||||
|
return strings.NewReplacer(armenianMap...).Replace(s)
|
||||||
|
}
|
||||||
42
translit/chinese.go
Normal file
42
translit/chinese.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package translit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/mozillazg/go-pinyin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChineseTranslit implements Transliterator using a pinyin
|
||||||
|
// conversion library.
|
||||||
|
type ChineseTranslit struct{}
|
||||||
|
|
||||||
|
func (ct *ChineseTranslit) Transliterate(s string) string {
|
||||||
|
// Create buffer for final output
|
||||||
|
outBuf := &bytes.Buffer{}
|
||||||
|
// Create buffer to temporarily store chinese characters
|
||||||
|
tmpBuf := &bytes.Buffer{}
|
||||||
|
// For every character in string
|
||||||
|
for _, char := range s {
|
||||||
|
// If character in Han range
|
||||||
|
if unicode.Is(unicode.Han, char) {
|
||||||
|
// Write character to temporary buffer
|
||||||
|
tmpBuf.WriteRune(char)
|
||||||
|
} else {
|
||||||
|
// If buffer contains characters
|
||||||
|
if tmpBuf.Len() > 0 {
|
||||||
|
// Convert to pinyin (without tones)
|
||||||
|
out := pinyin.LazyConvert(tmpBuf.String(), nil)
|
||||||
|
// Write space-separated string to output
|
||||||
|
outBuf.WriteString(strings.Join(out, " "))
|
||||||
|
// Reset temporary buffer
|
||||||
|
tmpBuf.Reset()
|
||||||
|
}
|
||||||
|
// Write character to output
|
||||||
|
outBuf.WriteRune(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return output string
|
||||||
|
return outBuf.String()
|
||||||
|
}
|
||||||
470
translit/korean.go
Normal file
470
translit/korean.go
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
package translit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Hangul_Jamo_%28Unicode_block%29
|
||||||
|
var jamoBlock = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{{
|
||||||
|
Lo: 0x1100,
|
||||||
|
Hi: 0x11FF,
|
||||||
|
Stride: 1,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Hangul_Syllables
|
||||||
|
var syllablesBlock = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{{
|
||||||
|
Lo: 0xAC00,
|
||||||
|
Hi: 0xD7A3,
|
||||||
|
Stride: 1,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Hangul_Compatibility_Jamo
|
||||||
|
var compatJamoBlock = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{{
|
||||||
|
Lo: 0x3131,
|
||||||
|
Hi: 0x318E,
|
||||||
|
Stride: 1,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// KoreanTranslit implements transliteration for Korean.
|
||||||
|
//
|
||||||
|
// This was translated to Go from the code in https://codeberg.org/Freeyourgadget/Gadgetbridge
|
||||||
|
type KoreanTranslit struct{}
|
||||||
|
|
||||||
|
// User input consisting of isolated jamo is usually mapped to the KS X 1001 compatibility
|
||||||
|
// block, but jamo resulting from decomposed syllables are mapped to the modern one. This
|
||||||
|
// function maps compat jamo to modern ones where possible and returns all other characters
|
||||||
|
// unmodified.
|
||||||
|
//
|
||||||
|
// https://en.wikipedia.org/wiki/Hangul_Compatibility_Jamo
|
||||||
|
// https://en.wikipedia.org/wiki/Hangul_Jamo_%28Unicode_block%29
|
||||||
|
func decompatJamo(jamo rune) rune {
|
||||||
|
// KS X 1001 Hangul filler, not used in modern Unicode. A useful landmark in the
|
||||||
|
// compatibility jamo block.
|
||||||
|
// https://en.wikipedia.org/wiki/KS_X_1001#Hangul_Filler
|
||||||
|
var hangulFiller rune = 0x3164
|
||||||
|
|
||||||
|
// Ignore characters outside compatibility jamo block
|
||||||
|
if !unicode.In(jamo, compatJamoBlock) {
|
||||||
|
return jamo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vowels are contiguous, in the same order, and unambiguous so it's a simple offset.
|
||||||
|
if jamo >= 0x314F && jamo < hangulFiller {
|
||||||
|
return jamo - 0x1FEE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consonants are organized differently. No clean way to do this.
|
||||||
|
// The compatibility jamo block doesn't distinguish between Choseong (leading) and Jongseong
|
||||||
|
// (final) positions, but the modern block does. We map to Choseong here.
|
||||||
|
switch jamo {
|
||||||
|
case 0x3131:
|
||||||
|
return 0x1100 // ㄱ
|
||||||
|
case 0x3132:
|
||||||
|
return 0x1101 // ㄲ
|
||||||
|
case 0x3134:
|
||||||
|
return 0x1102 // ㄴ
|
||||||
|
case 0x3137:
|
||||||
|
return 0x1103 // ㄷ
|
||||||
|
case 0x3138:
|
||||||
|
return 0x1104 // ㄸ
|
||||||
|
case 0x3139:
|
||||||
|
return 0x1105 // ㄹ
|
||||||
|
case 0x3141:
|
||||||
|
return 0x1106 // ㅁ
|
||||||
|
case 0x3142:
|
||||||
|
return 0x1107 // ㅂ
|
||||||
|
case 0x3143:
|
||||||
|
return 0x1108 // ㅃ
|
||||||
|
case 0x3145:
|
||||||
|
return 0x1109 // ㅅ
|
||||||
|
case 0x3146:
|
||||||
|
return 0x110A // ㅆ
|
||||||
|
case 0x3147:
|
||||||
|
return 0x110B // ㅇ
|
||||||
|
case 0x3148:
|
||||||
|
return 0x110C // ㅈ
|
||||||
|
case 0x3149:
|
||||||
|
return 0x110D // ㅉ
|
||||||
|
case 0x314A:
|
||||||
|
return 0x110E // ㅊ
|
||||||
|
case 0x314B:
|
||||||
|
return 0x110F // ㅋ
|
||||||
|
case 0x314C:
|
||||||
|
return 0x1110 // ㅌ
|
||||||
|
case 0x314D:
|
||||||
|
return 0x1111 // ㅍ
|
||||||
|
case 0x314E:
|
||||||
|
return 0x1112 // ㅎ
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest of the compatibility block consists of archaic compounds that are
|
||||||
|
// unlikely to be encountered in modern systems. Just leave them alone.
|
||||||
|
return jamo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transliterates one jamo at a time.
|
||||||
|
// Does nothing if it isn't in the modern jamo block.
|
||||||
|
func translitSingleJamo(jamo rune) string {
|
||||||
|
jamo = decompatJamo(jamo)
|
||||||
|
|
||||||
|
switch jamo {
|
||||||
|
// Choseong (leading position consonants)
|
||||||
|
case 0x1100:
|
||||||
|
return "g" // ㄱ
|
||||||
|
case 0x1101:
|
||||||
|
return "kk" // ㄲ
|
||||||
|
case 0x1102:
|
||||||
|
return "n" // ㄴ
|
||||||
|
case 0x1103:
|
||||||
|
return "d" // ㄷ
|
||||||
|
case 0x1104:
|
||||||
|
return "tt" // ㄸ
|
||||||
|
case 0x1105:
|
||||||
|
return "r" // ㄹ
|
||||||
|
case 0x1106:
|
||||||
|
return "m" // ㅁ
|
||||||
|
case 0x1107:
|
||||||
|
return "b" // ㅂ
|
||||||
|
case 0x1108:
|
||||||
|
return "pp" // ㅃ
|
||||||
|
case 0x1109:
|
||||||
|
return "s" // ㅅ
|
||||||
|
case 0x110A:
|
||||||
|
return "ss" // ㅆ
|
||||||
|
case 0x110B:
|
||||||
|
return "" // ㅇ
|
||||||
|
case 0x110C:
|
||||||
|
return "j" // ㅈ
|
||||||
|
case 0x110D:
|
||||||
|
return "jj" // ㅉ
|
||||||
|
case 0x110E:
|
||||||
|
return "ch" // ㅊ
|
||||||
|
case 0x110F:
|
||||||
|
return "k" // ㅋ
|
||||||
|
case 0x1110:
|
||||||
|
return "t" // ㅌ
|
||||||
|
case 0x1111:
|
||||||
|
return "p" // ㅍ
|
||||||
|
case 0x1112:
|
||||||
|
return "h" // ㅎ
|
||||||
|
// Jungseong (vowels)
|
||||||
|
case 0x1161:
|
||||||
|
return "a" // ㅏ
|
||||||
|
case 0x1162:
|
||||||
|
return "ae" // ㅐ
|
||||||
|
case 0x1163:
|
||||||
|
return "ya" // ㅑ
|
||||||
|
case 0x1164:
|
||||||
|
return "yae" // ㅒ
|
||||||
|
case 0x1165:
|
||||||
|
return "eo" // ㅓ
|
||||||
|
case 0x1166:
|
||||||
|
return "e" // ㅔ
|
||||||
|
case 0x1167:
|
||||||
|
return "yeo" // ㅕ
|
||||||
|
case 0x1168:
|
||||||
|
return "ye" // ㅖ
|
||||||
|
case 0x1169:
|
||||||
|
return "o" // ㅗ
|
||||||
|
case 0x116A:
|
||||||
|
return "wa" // ㅘ
|
||||||
|
case 0x116B:
|
||||||
|
return "wae" // ㅙ
|
||||||
|
case 0x116C:
|
||||||
|
return "oe" // ㅚ
|
||||||
|
case 0x116D:
|
||||||
|
return "yo" // ㅛ
|
||||||
|
case 0x116E:
|
||||||
|
return "u" // ㅜ
|
||||||
|
case 0x116F:
|
||||||
|
return "wo" // ㅝ
|
||||||
|
case 0x1170:
|
||||||
|
return "we" // ㅞ
|
||||||
|
case 0x1171:
|
||||||
|
return "wi" // ㅟ
|
||||||
|
case 0x1172:
|
||||||
|
return "yu" // ㅠ
|
||||||
|
case 0x1173:
|
||||||
|
return "eu" // ㅡ
|
||||||
|
case 0x1174:
|
||||||
|
return "ui" // ㅢ
|
||||||
|
case 0x1175:
|
||||||
|
return "i" // ㅣ
|
||||||
|
// Jongseong (final position consonants)
|
||||||
|
case 0x11A8:
|
||||||
|
return "k" // ㄱ
|
||||||
|
case 0x11A9:
|
||||||
|
return "k" // ㄲ
|
||||||
|
case 0x11AB:
|
||||||
|
return "n" // ㄴ
|
||||||
|
case 0x11AE:
|
||||||
|
return "t" // ㄷ
|
||||||
|
case 0x11AF:
|
||||||
|
return "l" // ㄹ
|
||||||
|
case 0x11B7:
|
||||||
|
return "m" // ㅁ
|
||||||
|
case 0x11B8:
|
||||||
|
return "p" // ㅂ
|
||||||
|
case 0x11BA:
|
||||||
|
return "t" // ㅅ
|
||||||
|
case 0x11BB:
|
||||||
|
return "t" // ㅆ
|
||||||
|
case 0x11BC:
|
||||||
|
return "ng" // ㅇ
|
||||||
|
case 0x11BD:
|
||||||
|
return "t" // ㅈ
|
||||||
|
case 0x11BE:
|
||||||
|
return "t" // ㅊ
|
||||||
|
case 0x11BF:
|
||||||
|
return "k" // ㅋ
|
||||||
|
case 0x11C0:
|
||||||
|
return "t" // ㅌ
|
||||||
|
case 0x11C1:
|
||||||
|
return "p" // ㅍ
|
||||||
|
case 0x11C2:
|
||||||
|
return "t" // ㅎ
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(jamo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some combinations of ending jamo in one syllable and initial jamo in the next are romanized
|
||||||
|
// irregularly. These exceptions are called "special provisions". In cases where multiple
|
||||||
|
// romanizations are permitted, we use the one that's least commonly used elsewhere.
|
||||||
|
//
|
||||||
|
// Returns empty strring and false if either character is not in the modern jamo block,
|
||||||
|
// or if there is no special provision for that pair of jamo.
|
||||||
|
func translitSpecialProvisions(previousEnding rune, nextInitial rune) (string, bool) {
|
||||||
|
// Return false if previousEnding not in modern jamo block
|
||||||
|
if !unicode.In(previousEnding, jamoBlock) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
// Return false if nextInitial not in modern jamo block
|
||||||
|
if !unicode.In(nextInitial, jamoBlock) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jongseong (final position) ㅎ has a number of special provisions.
|
||||||
|
if previousEnding == 0x11C2 {
|
||||||
|
switch nextInitial {
|
||||||
|
case 0x110B:
|
||||||
|
return "h", true // ㅇ
|
||||||
|
case 0x1100:
|
||||||
|
return "k", true // ㄱ
|
||||||
|
case 0x1102:
|
||||||
|
return "nn", true // ㄴ
|
||||||
|
case 0x1103:
|
||||||
|
return "t", true // ㄷ
|
||||||
|
case 0x1105:
|
||||||
|
return "nn", true // ㄹ
|
||||||
|
case 0x1106:
|
||||||
|
return "nm", true // ㅁ
|
||||||
|
case 0x1107:
|
||||||
|
return "p", true // ㅂ
|
||||||
|
case 0x1109:
|
||||||
|
return "hs", true // ㅅ
|
||||||
|
case 0x110C:
|
||||||
|
return "ch", true // ㅈ
|
||||||
|
case 0x1112:
|
||||||
|
return "t", true // ㅎ
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, special provisions are denser when grouped by the second jamo.
|
||||||
|
switch nextInitial {
|
||||||
|
case 0x1100: // ㄱ
|
||||||
|
switch previousEnding {
|
||||||
|
case 0x11AB:
|
||||||
|
return "n-g", true // ㄴ
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
case 0x1102: // ㄴ
|
||||||
|
switch previousEnding {
|
||||||
|
case 0x11A8:
|
||||||
|
return "ngn", true // ㄱ
|
||||||
|
case 0x11AE:
|
||||||
|
fallthrough // ㄷ
|
||||||
|
case 0x11BA:
|
||||||
|
fallthrough // ㅅ
|
||||||
|
case 0x11BD:
|
||||||
|
fallthrough // ㅈ
|
||||||
|
case 0x11BE:
|
||||||
|
fallthrough // ㅊ
|
||||||
|
case 0x11C0: // ㅌ
|
||||||
|
return "nn", true
|
||||||
|
case 0x11AF:
|
||||||
|
return "ll", true // ㄹ
|
||||||
|
case 0x11B8:
|
||||||
|
return "mn", true // ㅂ
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
case 0x1105: // ㄹ
|
||||||
|
switch previousEnding {
|
||||||
|
case 0x11A8:
|
||||||
|
fallthrough // ㄱ
|
||||||
|
case 0x11AB:
|
||||||
|
fallthrough // ㄴ
|
||||||
|
case 0x11AF: // ㄹ
|
||||||
|
return "ll", true
|
||||||
|
case 0x11AE:
|
||||||
|
fallthrough // ㄷ
|
||||||
|
case 0x11BA:
|
||||||
|
fallthrough // ㅅ
|
||||||
|
case 0x11BD:
|
||||||
|
fallthrough // ㅈ
|
||||||
|
case 0x11BE:
|
||||||
|
fallthrough // ㅊ
|
||||||
|
case 0x11C0: // ㅌ
|
||||||
|
return "nn", true
|
||||||
|
case 0x11B7:
|
||||||
|
fallthrough // ㅁ
|
||||||
|
case 0x11B8: // ㅂ
|
||||||
|
return "mn", true
|
||||||
|
case 0x11BC:
|
||||||
|
return "ngn", true // ㅇ
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
case 0x1106: // ㅁ
|
||||||
|
switch previousEnding {
|
||||||
|
case 0x11A8:
|
||||||
|
return "ngm", true // ㄱ
|
||||||
|
case 0x11AE:
|
||||||
|
fallthrough // ㄷ
|
||||||
|
case 0x11BA:
|
||||||
|
fallthrough // ㅅ
|
||||||
|
case 0x11BD:
|
||||||
|
fallthrough // ㅈ
|
||||||
|
case 0x11BE:
|
||||||
|
fallthrough // ㅊ
|
||||||
|
case 0x11C0: // ㅌ
|
||||||
|
return "nm", true
|
||||||
|
case 0x11B8:
|
||||||
|
return "mm", true // ㅂ
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
case 0x110B: // ㅇ
|
||||||
|
switch previousEnding {
|
||||||
|
case 0x11A8:
|
||||||
|
return "g", true // ㄱ
|
||||||
|
case 0x11AE:
|
||||||
|
return "d", true // ㄷ
|
||||||
|
case 0x11AF:
|
||||||
|
return "r", true // ㄹ
|
||||||
|
case 0x11B8:
|
||||||
|
return "b", true // ㅂ
|
||||||
|
case 0x11BA:
|
||||||
|
return "s", true // ㅅ
|
||||||
|
case 0x11BC:
|
||||||
|
return "ng-", true // ㅇ
|
||||||
|
case 0x11BD:
|
||||||
|
return "j", true // ㅈ
|
||||||
|
case 0x11BE:
|
||||||
|
return "ch", true // ㅊ
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
case 0x110F: // ㅋ
|
||||||
|
switch previousEnding {
|
||||||
|
case 0x11A8:
|
||||||
|
return "k-k", true // ㄱ
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
case 0x1110: // ㅌ
|
||||||
|
switch previousEnding {
|
||||||
|
case 0x11AE:
|
||||||
|
fallthrough // ㄷ
|
||||||
|
case 0x11BA:
|
||||||
|
fallthrough // ㅅ
|
||||||
|
case 0x11BD:
|
||||||
|
fallthrough // ㅈ
|
||||||
|
case 0x11BE:
|
||||||
|
fallthrough // ㅊ
|
||||||
|
case 0x11C0: // ㅌ
|
||||||
|
return "t-t", true
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
case 0x1111: // ㅍ
|
||||||
|
switch previousEnding {
|
||||||
|
case 0x11B8:
|
||||||
|
return "p-p", true // ㅂ
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompose a syllable into several jamo. Does nothing if that isn't possible.
|
||||||
|
func decompose(syllable rune) string {
|
||||||
|
return norm.NFD.String(string(syllable))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transliterate any Hangul in the given string.
|
||||||
|
// Leaves any non-Hangul characters unmodified.
|
||||||
|
func (kt *KoreanTranslit) Transliterate(s string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := &strings.Builder{}
|
||||||
|
|
||||||
|
nextInitialJamoConsumed := false
|
||||||
|
|
||||||
|
for i, syllable := range s {
|
||||||
|
// If character not in blocks, leave it unmodified
|
||||||
|
if !unicode.In(syllable, jamoBlock, syllablesBlock, compatJamoBlock) {
|
||||||
|
builder.WriteRune(syllable)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
jamo := decompose(syllable)
|
||||||
|
for j, char := range jamo {
|
||||||
|
// If we already transliterated the first jamo of this syllable as part of a special
|
||||||
|
// provision, skip it. Otherwise, handle it in the unconditional else branch.
|
||||||
|
if j == 0 && nextInitialJamoConsumed {
|
||||||
|
nextInitialJamoConsumed = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the last jamo of this syllable and not the last syllable of the
|
||||||
|
// string, check for special provisions. If the next char is whitespace or not
|
||||||
|
// Hangul, run translitSpecialProvisions() should return no value.
|
||||||
|
if j == len(jamo)-1 && i < len(s)-1 {
|
||||||
|
nextSyllable := s[i+1]
|
||||||
|
nextJamo := decompose(rune(nextSyllable))[0]
|
||||||
|
|
||||||
|
// Attempt to handle special provision
|
||||||
|
specialProvision, ok := translitSpecialProvisions(char, rune(nextJamo))
|
||||||
|
if ok {
|
||||||
|
builder.WriteString(specialProvision)
|
||||||
|
nextInitialJamoConsumed = true
|
||||||
|
} else {
|
||||||
|
// Not a special provision, transliterate normally
|
||||||
|
builder.WriteString(translitSingleJamo(char))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Transliterate normally
|
||||||
|
builder.WriteString(translitSingleJamo(char))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
357
translit/translit.go
Normal file
357
translit/translit.go
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
package translit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transliterate runs the given maps on s and returns the result
|
||||||
|
func Transliterate(s string, useMaps ...string) string {
|
||||||
|
// Create variable to store modified string
|
||||||
|
out := s
|
||||||
|
// If custom map exists
|
||||||
|
if customMap, ok := Transliterators["custom"]; ok {
|
||||||
|
// Perform transliteration with it
|
||||||
|
out = customMap.Transliterate(out)
|
||||||
|
}
|
||||||
|
// For every map to use
|
||||||
|
for _, useMap := range useMaps {
|
||||||
|
// If custom, skip
|
||||||
|
if useMap == "custom" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Get requested map
|
||||||
|
translitMap, ok := Transliterators[useMap]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Perform transliteration
|
||||||
|
out = translitMap.Transliterate(out)
|
||||||
|
}
|
||||||
|
// Return result
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transliterator is implemented by anything with a
|
||||||
|
// Transliterate method, which performs transliteration
|
||||||
|
// and returns the resulting string.
|
||||||
|
type Transliterator interface {
|
||||||
|
Transliterate(string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map implements Transliterator using a slice where
|
||||||
|
// every odd element is a key and every even one is a value
|
||||||
|
// which replaces the key.
|
||||||
|
type Map []string
|
||||||
|
|
||||||
|
func (mt Map) Transliterate(s string) string {
|
||||||
|
return strings.NewReplacer(mt...).Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transliterators stores transliterator implementations for each supported language.
|
||||||
|
// Some of these were sourced from https://codeberg.org/Freeyourgadget/Gadgetbridge
|
||||||
|
var Transliterators = map[string]Transliterator{
|
||||||
|
"eASCII": Map{
|
||||||
|
"œ", "oe",
|
||||||
|
"ª", "a",
|
||||||
|
"°", "o",
|
||||||
|
"«", `"`,
|
||||||
|
"»", `"`,
|
||||||
|
},
|
||||||
|
"Scandinavian": Map{
|
||||||
|
"Æ", "Ae",
|
||||||
|
"æ", "ae",
|
||||||
|
"Ø", "Oe",
|
||||||
|
"ø", "oe",
|
||||||
|
"Å", "Aa",
|
||||||
|
"å", "aa",
|
||||||
|
},
|
||||||
|
"German": Map{
|
||||||
|
"ä", "ae",
|
||||||
|
"ö", "oe",
|
||||||
|
"ü", "ue",
|
||||||
|
"Ä", "Ae",
|
||||||
|
"Ö", "Oe",
|
||||||
|
"Ü", "Ue",
|
||||||
|
"ß", "ss",
|
||||||
|
"ẞ", "SS",
|
||||||
|
},
|
||||||
|
"Hebrew": Map{
|
||||||
|
"א", "a",
|
||||||
|
"ב", "b",
|
||||||
|
"ג", "g",
|
||||||
|
"ד", "d",
|
||||||
|
"ה", "h",
|
||||||
|
"ו", "u",
|
||||||
|
"ז", "z",
|
||||||
|
"ח", "kh",
|
||||||
|
"ט", "t",
|
||||||
|
"י", "y",
|
||||||
|
"כ", "c",
|
||||||
|
"ל", "l",
|
||||||
|
"מ", "m",
|
||||||
|
"נ", "n",
|
||||||
|
"ס", "s",
|
||||||
|
"ע", "'",
|
||||||
|
"פ", "p",
|
||||||
|
"צ", "ts",
|
||||||
|
"ק", "k",
|
||||||
|
"ר", "r",
|
||||||
|
"ש", "sh",
|
||||||
|
"ת", "th",
|
||||||
|
"ף", "f",
|
||||||
|
"ץ", "ts",
|
||||||
|
"ך", "ch",
|
||||||
|
"ם", "m",
|
||||||
|
"ן", "n",
|
||||||
|
},
|
||||||
|
"Greek": Map{
|
||||||
|
"α", "a",
|
||||||
|
"ά", "a",
|
||||||
|
"β", "v",
|
||||||
|
"γ", "g",
|
||||||
|
"δ", "d",
|
||||||
|
"ε", "e",
|
||||||
|
"έ", "e",
|
||||||
|
"ζ", "z",
|
||||||
|
"η", "i",
|
||||||
|
"ή", "i",
|
||||||
|
"θ", "th",
|
||||||
|
"ι", "i",
|
||||||
|
"ί", "i",
|
||||||
|
"ϊ", "i",
|
||||||
|
"ΐ", "i",
|
||||||
|
"κ", "k",
|
||||||
|
"λ", "l",
|
||||||
|
"μ", "m",
|
||||||
|
"ν", "n",
|
||||||
|
"ξ", "ks",
|
||||||
|
"ο", "o",
|
||||||
|
"ό", "o",
|
||||||
|
"π", "p",
|
||||||
|
"ρ", "r",
|
||||||
|
"σ", "s",
|
||||||
|
"ς", "s",
|
||||||
|
"τ", "t",
|
||||||
|
"υ", "y",
|
||||||
|
"ύ", "y",
|
||||||
|
"ϋ", "y",
|
||||||
|
"ΰ", "y",
|
||||||
|
"φ", "f",
|
||||||
|
"χ", "ch",
|
||||||
|
"ψ", "ps",
|
||||||
|
"ω", "o",
|
||||||
|
"ώ", "o",
|
||||||
|
"Α", "A",
|
||||||
|
"Ά", "A",
|
||||||
|
"Β", "B",
|
||||||
|
"Γ", "G",
|
||||||
|
"Δ", "D",
|
||||||
|
"Ε", "E",
|
||||||
|
"Έ", "E",
|
||||||
|
"Ζ", "Z",
|
||||||
|
"Η", "I",
|
||||||
|
"Ή", "I",
|
||||||
|
"Θ", "TH",
|
||||||
|
"Ι", "I",
|
||||||
|
"Ί", "I",
|
||||||
|
"Ϊ", "I",
|
||||||
|
"Κ", "K",
|
||||||
|
"Λ", "L",
|
||||||
|
"Μ", "M",
|
||||||
|
"Ν", "N",
|
||||||
|
"Ξ", "KS",
|
||||||
|
"Ο", "O",
|
||||||
|
"Ό", "O",
|
||||||
|
"Π", "P",
|
||||||
|
"Ρ", "R",
|
||||||
|
"Σ", "S",
|
||||||
|
"Τ", "T",
|
||||||
|
"Υ", "Y",
|
||||||
|
"Ύ", "Y",
|
||||||
|
"Ϋ", "Y",
|
||||||
|
"Φ", "F",
|
||||||
|
"Χ", "CH",
|
||||||
|
"Ψ", "PS",
|
||||||
|
"Ω", "O",
|
||||||
|
"Ώ", "O",
|
||||||
|
},
|
||||||
|
"Russian": Map{
|
||||||
|
"Ё", "Йo",
|
||||||
|
"ё", "йo",
|
||||||
|
},
|
||||||
|
"Ukranian": Map{
|
||||||
|
"ґ", "gh",
|
||||||
|
"є", "je",
|
||||||
|
"і", "i",
|
||||||
|
"ї", "ji",
|
||||||
|
"Ґ", "GH",
|
||||||
|
"Є", "JE",
|
||||||
|
"І", "I",
|
||||||
|
"Ї", "JI",
|
||||||
|
},
|
||||||
|
"Arabic": Map{
|
||||||
|
"ا", "a",
|
||||||
|
"ب", "b",
|
||||||
|
"ت", "t",
|
||||||
|
"ث", "th",
|
||||||
|
"ج", "j",
|
||||||
|
"ح", "7",
|
||||||
|
"خ", "5",
|
||||||
|
"د", "d",
|
||||||
|
"ذ", "th",
|
||||||
|
"ر", "r",
|
||||||
|
"ز", "z",
|
||||||
|
"س", "s",
|
||||||
|
"ش", "sh",
|
||||||
|
"ص", "9",
|
||||||
|
"ض", "9'",
|
||||||
|
"ط", "6",
|
||||||
|
"ظ", "6'",
|
||||||
|
"ع", "3",
|
||||||
|
"غ", "3'",
|
||||||
|
"ف", "f",
|
||||||
|
"ق", "q",
|
||||||
|
"ك", "k",
|
||||||
|
"ل", "l",
|
||||||
|
"م", "m",
|
||||||
|
"ن", "n",
|
||||||
|
"ه", "h",
|
||||||
|
"و", "w",
|
||||||
|
"ي", "y",
|
||||||
|
"ى", "a",
|
||||||
|
"ﺓ", "",
|
||||||
|
"آ", "2",
|
||||||
|
"ئ", "2",
|
||||||
|
"إ", "2",
|
||||||
|
"ؤ", "2",
|
||||||
|
"أ", "2",
|
||||||
|
"ء", "2",
|
||||||
|
"٠", "0",
|
||||||
|
"١", "1",
|
||||||
|
"٢", "2",
|
||||||
|
"٣", "3",
|
||||||
|
"٤", "4",
|
||||||
|
"٥", "5",
|
||||||
|
"٦", "6",
|
||||||
|
"٧", "7",
|
||||||
|
"٨", "8",
|
||||||
|
"٩", "9",
|
||||||
|
},
|
||||||
|
"Farsi": Map{
|
||||||
|
"پ", "p",
|
||||||
|
"چ", "ch",
|
||||||
|
"ژ", "zh",
|
||||||
|
"ک", "k",
|
||||||
|
"گ", "g",
|
||||||
|
"ی", "y",
|
||||||
|
"\u200c", " ",
|
||||||
|
"؟", "?",
|
||||||
|
"٪", "%",
|
||||||
|
"؛", ";",
|
||||||
|
"،", ":",
|
||||||
|
"۱", "1",
|
||||||
|
"۲", "2",
|
||||||
|
"۳", "3",
|
||||||
|
"۴", "4",
|
||||||
|
"۵", "5",
|
||||||
|
"۶", "6",
|
||||||
|
"۷", "7",
|
||||||
|
"۸", "8",
|
||||||
|
"۹", "9",
|
||||||
|
"۰", "0",
|
||||||
|
"»", "<",
|
||||||
|
"«", ">",
|
||||||
|
"ِ", "e",
|
||||||
|
"َ", "a",
|
||||||
|
"ُ", "o",
|
||||||
|
"ّ", "",
|
||||||
|
},
|
||||||
|
"Polish": Map{
|
||||||
|
"Ł", "L",
|
||||||
|
"ł", "l",
|
||||||
|
},
|
||||||
|
"Lithuanian": Map{
|
||||||
|
"ą", "a",
|
||||||
|
"č", "c",
|
||||||
|
"ę", "e",
|
||||||
|
"ė", "e",
|
||||||
|
"į", "i",
|
||||||
|
"š", "s",
|
||||||
|
"ų", "u",
|
||||||
|
"ū", "u",
|
||||||
|
"ž", "z",
|
||||||
|
},
|
||||||
|
"Estonian": Map{
|
||||||
|
"ä", "a",
|
||||||
|
"Ä", "A",
|
||||||
|
"ö", "o",
|
||||||
|
"õ", "o",
|
||||||
|
"Ö", "O",
|
||||||
|
"Õ", "O",
|
||||||
|
"ü", "u",
|
||||||
|
"Ü", "U",
|
||||||
|
},
|
||||||
|
"Icelandic": Map{
|
||||||
|
"Þ", "Th",
|
||||||
|
"þ", "th",
|
||||||
|
"Ð", "D",
|
||||||
|
"ð", "d",
|
||||||
|
},
|
||||||
|
"Czeck": Map{
|
||||||
|
"ř", "r",
|
||||||
|
"ě", "e",
|
||||||
|
"ý", "y",
|
||||||
|
"á", "a",
|
||||||
|
"í", "i",
|
||||||
|
"é", "e",
|
||||||
|
"ó", "o",
|
||||||
|
"ú", "u",
|
||||||
|
"ů", "u",
|
||||||
|
"ď", "d",
|
||||||
|
"ť", "t",
|
||||||
|
"ň", "n",
|
||||||
|
},
|
||||||
|
"French": Map{
|
||||||
|
"à", "a",
|
||||||
|
"â", "a",
|
||||||
|
"é", "e",
|
||||||
|
"è", "e",
|
||||||
|
"ê", "e",
|
||||||
|
"ë", "e",
|
||||||
|
"ù", "u",
|
||||||
|
"ü", "u",
|
||||||
|
"ÿ", "y",
|
||||||
|
"ç", "c",
|
||||||
|
},
|
||||||
|
"Emoji": Map{
|
||||||
|
"😂", ":')",
|
||||||
|
"😊", ":)",
|
||||||
|
"😃", ":)",
|
||||||
|
"😩", "-_-",
|
||||||
|
"😏", ":‑J",
|
||||||
|
"💜", "<3",
|
||||||
|
"💖", "<3",
|
||||||
|
"💗", "<3",
|
||||||
|
"❤️", "<3",
|
||||||
|
"💕", "<3",
|
||||||
|
"💞", "<3",
|
||||||
|
"💘", "<3",
|
||||||
|
"💓", "<3",
|
||||||
|
"💚", "<3",
|
||||||
|
"💙", "<3",
|
||||||
|
"💔", "</3",
|
||||||
|
"😱", "D:",
|
||||||
|
"😮", ":O",
|
||||||
|
"😝", ":P",
|
||||||
|
"😍", ":x",
|
||||||
|
"😢", ":(",
|
||||||
|
"💯", ":100:",
|
||||||
|
"🔥", ":fire:",
|
||||||
|
"😉", ";)",
|
||||||
|
"😴", ":zzz:",
|
||||||
|
"💤", ":zzz:",
|
||||||
|
},
|
||||||
|
"Korean": &KoreanTranslit{},
|
||||||
|
"Chinese": &ChineseTranslit{},
|
||||||
|
"Armenian": &ArmenianTranslit{},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user