utils.go (28537B)
1 package utils 2 3 import ( 4 "bytes" 5 "crypto/aes" 6 "crypto/cipher" 7 "crypto/ed25519" 8 "crypto/md5" 9 cryptoRand "crypto/rand" 10 "crypto/rsa" 11 "crypto/sha1" 12 "crypto/sha256" 13 "crypto/sha512" 14 "crypto/x509" 15 "dkforest/pkg/hashset" 16 "dkforest/pkg/utils/crypto" 17 "encoding/base32" 18 "encoding/binary" 19 "encoding/hex" 20 "encoding/json" 21 "encoding/pem" 22 "errors" 23 "fmt" 24 "github.com/ProtonMail/go-crypto/openpgp" 25 "github.com/ProtonMail/go-crypto/openpgp/armor" 26 "github.com/ProtonMail/go-crypto/openpgp/clearsign" 27 "github.com/ProtonMail/go-crypto/openpgp/packet" 28 "github.com/asaskevich/govalidator" 29 "hash/crc32" 30 "image" 31 "image/jpeg" 32 "image/png" 33 "io" 34 "log" 35 "math/rand" 36 "net" 37 "net/http" 38 "os" 39 "os/user" 40 "path/filepath" 41 "regexp" 42 "runtime/debug" 43 "strconv" 44 "strings" 45 "sync" 46 "sync/atomic" 47 "time" 48 "unicode" 49 50 "dkforest/pkg/clockwork" 51 "dkforest/pkg/config" 52 humanize "github.com/dustin/go-humanize" 53 "github.com/microcosm-cc/bluemonday" 54 "github.com/nicksnyder/go-i18n/v2/i18n" 55 "github.com/sirupsen/logrus" 56 tgbotapi "gopkg.in/telegram-bot-api.v4" 57 ) 58 59 // Constants ... 60 const ( 61 OneMinuteSecs = 60 62 OneHourSecs = OneMinuteSecs * 60 63 OneDaySecs = OneHourSecs * 24 64 OneMonthSecs = OneDaySecs * 30 65 ) 66 67 // H is a hashmap 68 type H map[string]any 69 70 // SGo stands for Safe Go or Shit Go depending how you feel about goroutine panic handling 71 // Basically just a wrapper around the built-in keyword "go" with crash recovery 72 func SGo(fn func()) { 73 go func() { 74 defer func() { 75 if r := recover(); r != nil { 76 logrus.Error("unexpected crash", r) 77 debug.PrintStack() 78 } 79 }() 80 fn() 81 }() 82 } 83 84 // EnsureRange ensure min is smaller or equal to max 85 func EnsureRange(min, max int64) (int64, int64) { 86 if max < min { 87 min, max = max, min 88 } 89 return min, max 90 } 91 92 // EnsureRangeDur ... 93 func EnsureRangeDur(min, max time.Duration) (time.Duration, time.Duration) { 94 if max < min { 95 min, max = max, min 96 } 97 return min, max 98 } 99 100 func castStr(v any) string { 101 if s, ok := v.(string); ok { 102 return s 103 } 104 return "" 105 } 106 107 func doParseInt(s string) (out int) { 108 out, _ = strconv.Atoi(s) 109 return 110 } 111 112 // ParseInt64 shortcut for strconv.ParseInt base 10 64bit integer 113 func ParseInt64(v string) (int64, error) { 114 return strconv.ParseInt(v, 10, 64) 115 } 116 117 // ParseInt shortcut for strconv.Atoi 118 func ParseInt(v string) (int, error) { 119 return strconv.Atoi(v) 120 } 121 122 // ParseInt64OrDefault ... 123 func ParseInt64OrDefault(v string, d int64) (out int64) { 124 var err error 125 out, err = ParseInt64(v) 126 if err != nil { 127 out = d 128 } 129 return 130 } 131 132 // DoParseInt64 same as ParseInt64 but ignore errors 133 func DoParseInt64(v string) (out int64) { 134 out, _ = ParseInt64(v) 135 return 136 } 137 138 // DoParseInt same as ParseInt but ignore errors 139 func DoParseInt(v string) (out int) { 140 out, _ = ParseInt(v) 141 return 142 } 143 144 func ParseUint64(v string) (uint64, error) { 145 p, err := strconv.ParseInt(v, 10, 64) 146 return uint64(p), err 147 } 148 149 func DoParseUint64(v string) (out uint64) { 150 out, _ = ParseUint64(v) 151 return 152 } 153 154 // ParseF64 ... 155 func ParseF64(v string) (float64, error) { 156 return strconv.ParseFloat(v, 64) 157 } 158 159 // DoParseF64 same as ParseF64 but ignore errors 160 func DoParseF64(v string) (out float64) { 161 out, _ = ParseF64(v) 162 return 163 } 164 165 // DoParsePInt64 ... 166 func DoParsePInt64(v string) (out *int64) { 167 tmp, err := ParseInt64(v) 168 if err != nil { 169 return nil 170 } 171 return &tmp 172 } 173 174 // ParseBool ... 175 func ParseBool(v string) (bool, error) { 176 return strconv.ParseBool(v) 177 } 178 179 // DoParseBool ... 180 func DoParseBool(v string) (out bool) { 181 out, _ = ParseBool(v) 182 return 183 } 184 185 // ParseMs parse string to milliseconds 186 func ParseMs(v string) (time.Duration, error) { 187 tmp, err := ParseInt64(v) 188 if err != nil { 189 return 0, err 190 } 191 return time.Duration(tmp) * time.Millisecond, nil 192 } 193 194 // DoParseMs ... 195 func DoParseMs(v string) (out time.Duration) { 196 out, _ = ParseMs(v) 197 return 198 } 199 200 func refStr(s string) *string { 201 return &s 202 } 203 204 // Sha1 returns sha1 hex sum as a string 205 func Sha1(in []byte) string { 206 h := sha1.New() 207 h.Write(in) 208 return hex.EncodeToString(h.Sum(nil)) 209 } 210 211 // Sha256 returns sha256 hex sum as a string 212 func Sha256(in []byte) string { 213 h := sha256.New() 214 h.Write(in) 215 return hex.EncodeToString(h.Sum(nil)) 216 } 217 218 // Sha512 returns sha512 hex sum as a string 219 func Sha512(in []byte) string { 220 h := sha512.New() 221 h.Write(in) 222 return hex.EncodeToString(h.Sum(nil)) 223 } 224 225 // MD5 returns md5 hex sum as a string 226 func MD5(in []byte) string { 227 h := md5.New() 228 h.Write(in) 229 return hex.EncodeToString(h.Sum(nil)) 230 } 231 232 func Crc32(in []byte) uint32 { 233 h := crc32.NewIEEE() 234 _, _ = h.Write(in) 235 return h.Sum32() 236 } 237 238 // ShortDisplayID generate a short display id 239 func ShortDisplayID(size int64) string { 240 if size <= 4 || size > 20 { 241 return "" 242 } 243 b := make([]byte, size) 244 _, _ = cryptoRand.Read(b) 245 return hex.EncodeToString(b)[0:size] 246 } 247 248 // GenerateToken32 generate a random 32 bytes hex token 249 // fe3aa9e2a3362ed6fb19295e76dca9b74c9edb415affe1a9b3d8be23b8608e23 250 func GenerateToken32() string { 251 return GenerateTokenN(32) 252 } 253 254 // GenerateToken16 ... 255 func GenerateToken16() string { 256 return GenerateTokenN(16) 257 } 258 259 // GenerateToken10 ... 260 // 0144387f11c617517a41 261 func GenerateToken10() string { 262 return GenerateTokenN(10) 263 } 264 265 func GenerateToken3() string { 266 return GenerateTokenN(3) 267 } 268 269 func GenerateTokenN(n int) string { 270 b := make([]byte, n) 271 _, _ = rand.Read(b) 272 return hex.EncodeToString(b) 273 } 274 275 func TruncStr2(s string, maxLen int, suffix string) (out string) { 276 if len(s) <= maxLen { 277 return s 278 } 279 out = s[0:maxLen] 280 idx := strings.LastIndex(out, " ") 281 if idx > -1 { 282 if idx < maxLen-20 { 283 out = s[0:maxLen] 284 } else { 285 out = out[0:idx] 286 } 287 } 288 if suffix != "" { 289 out += suffix 290 } 291 return 292 } 293 294 func TruncStr(s string, maxLen int, suffix string) (out string) { 295 if len(s) <= maxLen { 296 return s 297 } 298 out = s[0:maxLen] 299 if suffix != "" { 300 out += suffix 301 } 302 return 303 } 304 305 // FormatInt64 shortcut for strconv.FormatInt base 10 306 func FormatInt64(v int64) string { 307 return strconv.FormatInt(v, 10) 308 } 309 310 // IP2Long ... 311 func IP2Long(ip string) uint32 { 312 var long uint32 313 _ = binary.Read(bytes.NewBuffer(net.ParseIP(ip).To4()), binary.BigEndian, &long) 314 return long 315 } 316 317 // BacktoIP4 ... 318 func BacktoIP4(ipInt int64) string { 319 // need to do two bit shifting and “0xff” masking 320 b0 := FormatInt64((ipInt >> 24) & 0xff) 321 b1 := FormatInt64((ipInt >> 16) & 0xff) 322 b2 := FormatInt64((ipInt >> 8) & 0xff) 323 b3 := FormatInt64(ipInt & 0xff) 324 return b0 + "." + b1 + "." + b2 + "." + b3 325 } 326 327 // ShortDur ... 328 func ShortDur(v any) string { 329 if d, ok := v.(time.Duration); ok { 330 d = d.Round(time.Second) 331 s := d.String() 332 if strings.HasSuffix(s, "m0s") { 333 s = s[:len(s)-2] 334 } 335 if strings.HasSuffix(s, "h0m") { 336 s = s[:len(s)-2] 337 } 338 return s 339 } else if d, ok := v.(time.Time); ok { 340 return ShortDur(time.Until(d)) 341 } else if d, ok := v.(uint64); ok { 342 return ShortDur(time.Duration(d) * time.Second) 343 } else if d, ok := v.(int64); ok { 344 return ShortDur(time.Duration(d) * time.Second) 345 } else if d, ok := v.(int32); ok { 346 return ShortDur(time.Duration(d) * time.Second) 347 } else if d, ok := v.(int64); ok { 348 return ShortDur(time.Duration(d) * time.Second) 349 } else if d, ok := v.(float32); ok { 350 return ShortDur(time.Duration(d) * time.Second) 351 } else if d, ok := v.(float64); ok { 352 return ShortDur(time.Duration(d) * time.Second) 353 } 354 return "n/a" 355 } 356 357 // Sanitize ... 358 func Sanitize(txt string) string { 359 p := bluemonday.UGCPolicy() 360 return p.Sanitize(txt) 361 } 362 363 // N2br ... 364 func N2br(txt string) string { 365 return strings.Replace(txt, "\n", "<br />", -1) 366 } 367 368 // Dot ... 369 func Dot(in int64) string { 370 return strings.Replace(humanize.Comma(in), ",", ".", -1) 371 } 372 373 // EncryptAES ... 374 func EncryptAES(plaintext []byte, key []byte) ([]byte, error) { 375 gcm, ns, err := getGCMKeyBytes(key) 376 if err != nil { 377 return nil, err 378 } 379 nonce := make([]byte, ns) 380 if _, err = io.ReadFull(cryptoRand.Reader, nonce); err != nil { 381 return nil, err 382 } 383 return gcm.Seal(nonce, nonce, plaintext, nil), nil 384 } 385 386 // DecryptAES ... 387 func DecryptAES(ciphertext []byte, key []byte) ([]byte, error) { 388 if len(ciphertext) == 0 { 389 return []byte(""), nil 390 } 391 392 gcm, nonceSize, err := getGCMKeyBytes(key) 393 if err != nil { 394 return nil, err 395 } 396 if len(ciphertext) < nonceSize { 397 return nil, errors.New("ciphertext too short") 398 } 399 400 nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] 401 return gcm.Open(nil, nonce, ciphertext, nil) 402 } 403 404 // EncryptAESMaster same as EncryptAES but use the default master key 405 func EncryptAESMaster(plaintext []byte) ([]byte, error) { 406 return EncryptAES(plaintext, []byte(config.Global.MasterKey.Get())) 407 } 408 409 // DecryptAESMaster same as DecryptAES but use the default master key 410 func DecryptAESMaster(ciphertext []byte) ([]byte, error) { 411 return DecryptAES(ciphertext, []byte(config.Global.MasterKey.Get())) 412 } 413 414 func EncryptStream(password []byte, src io.Reader) (*crypto.StreamEncrypter, error) { 415 return crypto.NewStreamEncrypter(password, nil, src) 416 } 417 418 func DecryptStream(password, iv []byte, src io.Reader) (*crypto.StreamDecrypter, error) { 419 decrypter, err := crypto.NewStreamDecrypter(password, nil, crypto.StreamMeta{IV: iv}, src) 420 if err != nil { 421 return nil, err 422 } 423 return decrypter, nil 424 } 425 426 func GetGCM(key string) (cipher.AEAD, int, error) { 427 keyBytes, err := hex.DecodeString(key) 428 if err != nil { 429 return nil, 0, err 430 } 431 gcm, nonceSize, err := getGCMKeyBytes(keyBytes) 432 if err != nil { 433 return nil, 0, err 434 } 435 return gcm, nonceSize, nil 436 } 437 438 func getGCMKeyBytes(keyBytes []byte) (cipher.AEAD, int, error) { 439 block, err := aes.NewCipher(keyBytes) 440 if err != nil { 441 return nil, 0, err 442 } 443 gcm, err := cipher.NewGCM(block) 444 if err != nil { 445 return nil, 0, err 446 } 447 nonceSize := gcm.NonceSize() 448 return gcm, nonceSize, nil 449 } 450 451 func GetKeyExpiredTime(pkey string) (*time.Time, bool) { 452 e := GetEntityFromPKey(pkey) 453 if e == nil { 454 return nil, false 455 } 456 i := e.PrimaryIdentity() 457 sig := i.SelfSignature 458 if sig.KeyLifetimeSecs == nil || *sig.KeyLifetimeSecs == 0 { 459 return nil, false 460 } 461 expiredTime := e.PrimaryKey.CreationTime.Add(time.Duration(*sig.KeyLifetimeSecs) * time.Second) 462 expired := e.PrimaryKey.KeyExpired(sig, time.Now()) 463 return &expiredTime, expired 464 } 465 466 func GetKeyFingerprint(pkey string) string { 467 if e := GetEntityFromPKey(pkey); e != nil { 468 return FormatPgPFingerprint(e.PrimaryKey.Fingerprint) 469 } 470 return "" 471 } 472 473 func FormatPgPFingerprint(fpBytes []byte) string { 474 fp := strings.ToUpper(hex.EncodeToString(fpBytes)) 475 return fmt.Sprintf("%s %s %s %s %s %s %s %s %s %s", 476 fp[0:4], fp[4:8], fp[8:12], fp[12:16], fp[16:20], 477 fp[20:24], fp[24:28], fp[28:32], fp[32:36], fp[36:40]) 478 } 479 480 func PgpCheckClearSignMessage(pkey, msg string) bool { 481 keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(pkey)) 482 if err != nil { 483 return false 484 } 485 b, _ := clearsign.Decode([]byte(msg)) 486 if b == nil { 487 return false 488 } 489 if _, err = b.VerifySignature(keyring, nil); err != nil { 490 return false 491 } 492 return true 493 } 494 495 func GetEntityFromPKey(pkey string) *openpgp.Entity { 496 reader := bytes.NewReader([]byte(pkey)) 497 if block, err := armor.Decode(reader); err == nil { 498 r := packet.NewReader(block.Body) 499 if e, err := openpgp.ReadEntity(r); err == nil { 500 return e 501 } 502 } 503 return nil 504 } 505 506 func PgpCheckSignMessage(pkey, msg, signature string) bool { 507 keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(pkey)) 508 if err != nil { 509 return false 510 } 511 verify := func(msg string) bool { 512 _, err := openpgp.CheckArmoredDetachedSignature(keyring, strings.NewReader(msg), strings.NewReader(signature), nil) 513 return err == nil 514 } 515 // Text editors often add an extra line break, so let's check with and without it. 516 return verify(msg) || verify(msg+"\n") 517 } 518 519 func PgpDecryptMessage(secretKey, msg string) (string, error) { 520 readerMsg := bytes.NewReader([]byte(msg)) 521 block, err := armor.Decode(readerMsg) 522 if err != nil { 523 logrus.Error(err) 524 return "", errors.New("invalid msg") 525 } 526 //if block.Type != "MESSAGE" { 527 // return "", errors.New("invalid message type") 528 //} 529 reader := bytes.NewReader([]byte(secretKey)) 530 e, err := openpgp.ReadArmoredKeyRing(reader) 531 if err != nil { 532 logrus.Error(err) 533 return "", errors.New("invalid secret key") 534 } 535 md, err := openpgp.ReadMessage(block.Body, e, nil, nil) 536 if err != nil { 537 logrus.Error(err) 538 return "", errors.New("unable to read message") 539 } 540 by, err := io.ReadAll(md.UnverifiedBody) 541 if err != nil { 542 return "", err 543 } 544 decStr := string(by) 545 return decStr, nil 546 } 547 548 func GeneratePgpEncryptedMessage(pkey, msg string) (string, error) { 549 e := GetEntityFromPKey(pkey) 550 if e == nil { 551 return "", errors.New("invalid public key") 552 } 553 buffer := &bytes.Buffer{} 554 armoredWriter, _ := armor.Encode(buffer, "PGP MESSAGE", nil) 555 w, err := openpgp.Encrypt(armoredWriter, []*openpgp.Entity{e}, nil, nil, nil) 556 if err != nil { 557 if e.PrimaryKey.KeyExpired(e.PrimaryIdentity().SelfSignature, time.Now()) { // Primary key has expired 558 return "", errors.New("PGP key has expired") 559 } 560 // openpgp: invalid argument: cannot encrypt a message to key id xxx because it has no encryption keys 561 // Likely your key is expired or had expired subkeys. (https://github.com/keybase/keybase-issues/issues/2072#issuecomment-183702559) 562 logrus.Error(err) 563 return "", err 564 } 565 _, err = w.Write([]byte(msg)) 566 if err != nil { 567 logrus.Error(err) 568 return "", err 569 } 570 err = w.Close() 571 if err != nil { 572 logrus.Error(err) 573 return "", err 574 } 575 _ = armoredWriter.Close() 576 577 return buffer.String(), nil 578 } 579 580 // LoadLocals ... 581 func LoadLocals(bundle *i18n.Bundle) error { 582 err := filepath.Walk(config.Global.ProjectLocalsPath.Get(), func(path string, info os.FileInfo, err error) error { 583 if !info.IsDir() { 584 if strings.HasSuffix(info.Name(), ".yaml") { 585 if _, err = bundle.LoadMessageFile(path); err != nil { 586 return err 587 } 588 } 589 } 590 return nil 591 }) 592 return err 593 } 594 595 // MustGetDefaultProjectPath ... 596 func MustGetDefaultProjectPath() string { 597 osUser, err := user.Current() 598 if err != nil { 599 log.Fatal("Unable to find os user") 600 } 601 return filepath.Join(osUser.HomeDir, config.AppDirName) 602 } 603 604 // MinInt returns the minimum int64 value 605 func MinInt[T Ints](vals ...T) T { 606 minV := vals[0] 607 for _, num := range vals { 608 if num < minV { 609 minV = num 610 } 611 } 612 return minV 613 } 614 615 // MaxInt returns the minimum int64 value 616 func MaxInt[T Ints](vals ...T) T { 617 maxV := vals[0] 618 for _, num := range vals { 619 if num > maxV { 620 maxV = num 621 } 622 } 623 return maxV 624 } 625 626 func Shuffle[T any](s []T) { 627 rand.Shuffle(len(s), func(i, j int) { s[i], s[j] = s[j], s[i] }) 628 } 629 630 func Shuffle1[T any](r *rand.Rand, s []T) { 631 r.Shuffle(len(s), func(i, j int) { s[i], s[j] = s[j], s[i] }) 632 } 633 634 type Ints interface { 635 int | int64 | ~uint64 636 } 637 638 // Clamp ensure the value is within a range 639 func Clamp[T Ints](val, min, max T) T { 640 val = MinInt(val, max) 641 val = MaxInt(val, min) 642 return val 643 } 644 645 // RandChoice returns a random element from an array 646 func RandChoice[T any](arr []T) T { 647 if len(arr) == 0 { 648 panic("empty array") 649 } 650 return arr[rand.Intn(len(arr))] 651 } 652 653 func RandBool() bool { 654 return RandInt(0, 1) == 1 655 } 656 657 // DiceRoll receive an int 0-100, returns true "pct" of the time 658 func DiceRoll(pct int) bool { 659 if pct < 0 || pct > 100 { 660 panic("invalid dice roll value") 661 } 662 return RandInt(0, 100) <= pct 663 } 664 665 // RandI64 generates a number between min and max inclusively 666 func RandI64(min, max int64) int64 { 667 if min == max { 668 return min 669 } 670 if max < min { 671 min, max = max, min 672 } 673 return rand.Int63n(max-min+1) + min 674 } 675 676 func RandInt(min, max int) int { 677 if min == max { 678 return min 679 } 680 if max < min { 681 min, max = max, min 682 } 683 return rand.Intn(max-min+1) + min 684 } 685 686 func RandFloat(min, max float64) float64 { 687 if min == max { 688 return min 689 } 690 if max < min { 691 min, max = max, min 692 } 693 return rand.Float64()*(max-min) + min 694 } 695 696 // RandMs generates random duration in milliseconds 697 func RandMs(min, max int64) time.Duration { 698 return randDur(min, max, time.Millisecond) 699 } 700 701 // RandSec generates random duration in seconds 702 func RandSec(min, max int64) time.Duration { 703 return randDur(min, max, time.Second) 704 } 705 706 // RandMin generates random duration in minutes 707 func RandMin(min, max int64) time.Duration { 708 return randDur(min, max, time.Minute) 709 } 710 711 // RandHour generates random duration in hours 712 func RandHour(min, max int64) time.Duration { 713 return randDur(min, max, time.Hour) 714 } 715 716 func randDur(min, max int64, dur time.Duration) time.Duration { 717 return RandDuration(time.Duration(min)*dur, time.Duration(max)*dur) 718 } 719 720 // RandDuration generates random duration 721 func RandDuration(min, max time.Duration) time.Duration { 722 n := RandI64(min.Nanoseconds(), max.Nanoseconds()) 723 return time.Duration(n) * time.Nanosecond 724 } 725 726 // SendTelegram sends a telegram message to a chatID 727 func SendTelegram(telegramBotToken string, chatID int64, msg string) error { 728 if chatID == 0 { 729 return errors.New("invalid chat ID 0") 730 } 731 tgbot, err := tgbotapi.NewBotAPI(telegramBotToken) 732 if err != nil { 733 return err 734 } 735 if _, err := tgbot.Send(tgbotapi.NewMessage(chatID, msg)); err != nil { 736 return err 737 } 738 return nil 739 } 740 741 func SendDiscord(webhookURL string, msg string) error { 742 type Payload struct { 743 Content string `json:"content"` 744 } 745 data := Payload{ 746 Content: msg, 747 } 748 payloadBytes, err := json.Marshal(data) 749 if err != nil { 750 return err 751 } 752 body := bytes.NewReader(payloadBytes) 753 req, err := http.NewRequest("POST", webhookURL, body) 754 if err != nil { 755 return err 756 } 757 req.Header.Set("Content-Type", "application/json") 758 resp, err := http.DefaultClient.Do(req) 759 if err != nil { 760 return err 761 } 762 defer resp.Body.Close() 763 return nil 764 } 765 766 // Last4 display last 4 digits of a CC 767 func Last4(cc string) string { 768 return strings.Repeat("*", 12) + cc[len(cc)-4:] 769 } 770 771 // Once ... 772 type Once struct { 773 m sync.Mutex 774 done uint32 775 } 776 777 func (o *Once) Now() <-chan time.Time { 778 return o.After(0) 779 } 780 781 // After if and only if After is being called for the first time for this instance of Once. 782 func (o *Once) After(duration time.Duration) <-chan time.Time { 783 if atomic.LoadUint32(&o.done) == 1 { 784 return nil 785 } 786 // Slow-path. 787 o.m.Lock() 788 defer o.m.Unlock() 789 if o.done == 0 { 790 defer atomic.StoreUint32(&o.done, 1) 791 return time.After(duration) 792 } 793 return nil 794 } 795 796 // Today returns today's date 797 func Today() time.Time { 798 return TodayWithClock(clockwork.NewRealClock()) 799 } 800 801 // TodayWithClock returns today's date using a clock 802 func TodayWithClock(clock clockwork.Clock) time.Time { 803 year, month, day := clock.Now().Date() 804 today := time.Date(year, month, day, 0, 0, 0, 0, clock.Now().Location()) 805 return today 806 } 807 808 // FileExists https://stackoverflow.com/a/12518877/4196220 809 func FileExists(filePath string) bool { 810 if _, err := os.Stat(filePath); os.IsNotExist(err) { 811 return false 812 } 813 return true 814 } 815 816 // GenerateRSAKeyPair need to initialize rand.Seed 817 func GenerateRSAKeyPair() (pubKey, privKey []byte, err error) { 818 privateKey, err := rsa.GenerateKey(cryptoRand.Reader, 4096) 819 if err != nil { 820 return nil, nil, err 821 } 822 PubASN1, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) 823 if err != nil { 824 return nil, nil, err 825 } 826 encodedPrivateKey := x509.MarshalPKCS1PrivateKey(privateKey) 827 privKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: encodedPrivateKey}) 828 pubKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PUBLIC KEY", Bytes: PubASN1}) 829 return pubKey, privKey, nil 830 } 831 832 func GetRandomChatColor() string { 833 colors := []string{ 834 "#F5F5DC", 835 "#8A2BE2", 836 "#A52A2A", 837 "#00FFFF", 838 "#00BFFF", 839 "#FFD700", 840 "#808080", 841 "#008000", 842 "#FF69B4", 843 "#ADD8E6", 844 "#90EE90", 845 "#32CD32", 846 "#FF00FF", 847 "#808000", 848 "#FFA500", 849 "#FF4500", 850 "#FF0000", 851 "#4169E1", 852 "#2E8B57", 853 "#A0522D", 854 "#C0C0C0", 855 "#D2B48C", 856 "#008080", 857 "#EE82EE", 858 "#FFFFFF", 859 "#FFFF00", 860 "#9ACD32", 861 } 862 return colors[rand.Intn(len(colors))] 863 } 864 865 type Font struct { 866 Display string 867 Value int64 868 Style string 869 } 870 871 func GetFonts() []Font { 872 return []Font{ 873 {"* Room Default *", 0, ""}, 874 {"Arial", 2, "Arial,Helvetica,sans-serif;"}, 875 {"Book Antiqua", 4, "'Book Antiqua','MS Gothic',serif;"}, 876 {"Comic", 5, "'Comic Sans MS',Papyrus,sans-serif;"}, 877 {"Courier", 1, "'Courier New',Courier,monospace;"}, 878 {"Cursive", 6, "Cursive,Papyrus,sans-serif;"}, 879 {"Garamond", 8, "Fantasy,Futura,Papyrus,sans;"}, 880 {"Georgia", 3, "Georgia,'Times New Roman',Times,serif;"}, 881 {"Serif", 9, "'MS Serif','New York',serif;"}, 882 {"System", 10, "System,Chicago,sans-serif;"}, 883 {"Times New Roman", 11, "Times New Roman',Times,serif;"}, 884 {"Verdana", 12, "Verdana,Geneva,Arial,Helvetica,sans-serif;"}, 885 } 886 } 887 888 func GetSound(id int64) string { 889 switch id { 890 case 1: 891 return "/public/mp3/sound1.mp3" 892 case 2: 893 return "/public/mp3/sound1.mp3" 894 case 3: 895 return "/public/mp3/sound1.mp3" 896 case 4: 897 return "/public/mp3/sound4.mp3" 898 } 899 return "/public/mp3/sound1.mp3" 900 } 901 902 func WordCount(str string) (int, map[string]int) { 903 wordList := strings.Fields(str) 904 counts := make(map[string]int) 905 for _, word := range wordList { 906 _, ok := counts[word] 907 if ok { 908 counts[word] += 1 909 } else { 910 counts[word] = 1 911 } 912 } 913 return len(wordList), counts 914 } 915 916 var hourMinSecRgx = regexp.MustCompile(`^\d{2}:\d{2}:\d{2}$`) 917 var monthDayHourMinSecRgx = regexp.MustCompile(`^\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`) 918 919 // ParseNextDatetimeAt given a string in this format 00:00:00 returns the next Time object at that time. 920 func ParseNextDatetimeAt(hourMinSec string, clock clockwork.Clock) (time.Time, error) { 921 if !hourMinSecRgx.MatchString(hourMinSec) { 922 return time.Time{}, errors.New("invalid format (should be 00:00:00)") 923 } 924 var hour, minute, sec int64 925 if n, err := fmt.Sscanf(hourMinSec, "%d:%d:%d", &hour, &minute, &sec); err != nil || n != 3 { 926 return time.Time{}, errors.New("invalid format (should be 00:00:00)") 927 } 928 return GetNextDatetimeAt(hour, minute, sec, clock) 929 } 930 931 // GetNextDatetimeAt given a hour, minute, second, returns the next Time object at that time. 932 func GetNextDatetimeAt(hour, min, sec int64, clock clockwork.Clock) (time.Time, error) { 933 if hour < 0 || hour > 23 { 934 return time.Time{}, errors.New("hour must be between [0, 23]") 935 } 936 if min < 0 || min > 59 { 937 return time.Time{}, errors.New("min must be between [0, 59]") 938 } 939 if sec < 0 || sec > 59 { 940 return time.Time{}, errors.New("sec must be between [0, 59]") 941 } 942 secondsOfDay := func(datetime time.Time) int { 943 return (datetime.Hour() * 60 * 60) + (datetime.Minute() * 60) + datetime.Second() 944 } 945 hourMinSec := fmt.Sprintf("%02d:%02d:%02d", hour, min, sec) 946 timeNow := clock.Now() 947 nowInSeconds := secondsOfDay(timeNow) 948 parsedDate, _ := time.ParseInLocation("2 Jan 2006 15:04:05", timeNow.Format("2 Jan 2006")+" "+hourMinSec, timeNow.Location()) 949 if nowInSeconds >= secondsOfDay(parsedDate) { 950 parsedDate = parsedDate.Add(24 * time.Hour) 951 } 952 return parsedDate, nil 953 } 954 955 // ParsePrevDatetimeAt given a string in this format HH:MM:SS returns the previous Time object at that time. 956 func ParsePrevDatetimeAt(hourMinSec string, clock clockwork.Clock) (time.Time, error) { 957 if !hourMinSecRgx.MatchString(hourMinSec) { 958 return time.Time{}, errors.New("invalid format (should be HH:MM:SS)") 959 } 960 var hour, minute, sec int64 961 if n, err := fmt.Sscanf(hourMinSec, "%d:%d:%d", &hour, &minute, &sec); err != nil || n != 3 { 962 return time.Time{}, errors.New("invalid format (should be HH:MM:SS)") 963 } 964 return GetPrevDatetimeAt(hour, minute, sec, clock) 965 } 966 967 // ParsePrevDatetimeAt2 given a string in this format "mm-dd HH:MM:SS" returns the previous Time object at that time. 968 func ParsePrevDatetimeAt2(monthDayHourMinSec string, clock clockwork.Clock) (time.Time, error) { 969 if !monthDayHourMinSecRgx.MatchString(monthDayHourMinSec) { 970 return time.Time{}, errors.New("invalid format (should be mm-dd HH:MM:SS)") 971 } 972 var month, day, hour, minute, sec int64 973 if n, err := fmt.Sscanf(monthDayHourMinSec, "%d-%d %d:%d:%d", &month, &day, &hour, &minute, &sec); err != nil || n != 5 { 974 return time.Time{}, errors.New("invalid format (should be mm-dd HH:MM:SS)") 975 } 976 return GetPrevDatetimeAt2(month, day, hour, minute, sec, clock) 977 } 978 979 // GetPrevDatetimeAt given a hour, minute, second, returns the previous Time object at that time. 980 func GetPrevDatetimeAt(hour, min, sec int64, clock clockwork.Clock) (time.Time, error) { 981 if hour < 0 || hour > 23 { 982 return time.Time{}, errors.New("hour must be between [0, 23]") 983 } 984 if min < 0 || min > 59 { 985 return time.Time{}, errors.New("min must be between [0, 59]") 986 } 987 if sec < 0 || sec > 59 { 988 return time.Time{}, errors.New("sec must be between [0, 59]") 989 } 990 secondsOfDay := func(datetime time.Time) int { 991 return (datetime.Hour() * 60 * 60) + (datetime.Minute() * 60) + datetime.Second() 992 } 993 hourMinSec := fmt.Sprintf("%02d:%02d:%02d", hour, min, sec) 994 timeNow := clock.Now() 995 nowInSeconds := secondsOfDay(timeNow) 996 parsedDate, _ := time.ParseInLocation("2 Jan 2006 15:04:05", timeNow.Format("2 Jan 2006")+" "+hourMinSec, timeNow.Location()) 997 if nowInSeconds < secondsOfDay(parsedDate) { 998 parsedDate = parsedDate.Add(-24 * time.Hour) 999 } 1000 return parsedDate, nil 1001 } 1002 1003 // GetPrevDatetimeAt2 given a month, day, hour, minute, second, returns the previous Time object at that time. 1004 func GetPrevDatetimeAt2(month, day, hour, min, sec int64, clock clockwork.Clock) (time.Time, error) { 1005 if month < 1 || month > 12 { 1006 return time.Time{}, errors.New("month must be between [1, 12]") 1007 } 1008 if day < 1 || day > 31 { 1009 return time.Time{}, errors.New("day must be between [1, 31]") 1010 } 1011 if hour < 0 || hour > 23 { 1012 return time.Time{}, errors.New("hour must be between [0, 23]") 1013 } 1014 if min < 0 || min > 59 { 1015 return time.Time{}, errors.New("min must be between [0, 59]") 1016 } 1017 if sec < 0 || sec > 59 { 1018 return time.Time{}, errors.New("sec must be between [0, 59]") 1019 } 1020 monthDayHourMinSec := fmt.Sprintf("%02d-%02d %02d:%02d:%02d", month, day, hour, min, sec) 1021 timeNow := clock.Now() 1022 parsedDate, _ := time.ParseInLocation("2006 01-02 15:04:05", timeNow.Format("2006")+" "+monthDayHourMinSec, timeNow.Location()) 1023 return parsedDate, nil 1024 } 1025 1026 // ReencodePng to remove metadata 1027 func ReencodePng(in []byte) (out []byte, err error) { 1028 var buf bytes.Buffer 1029 img, _, err := image.Decode(bytes.NewReader(in)) 1030 if err != nil { 1031 return nil, err 1032 } 1033 if err := png.Encode(&buf, img); err != nil { 1034 return nil, err 1035 } 1036 return buf.Bytes(), nil 1037 } 1038 1039 // ReencodeJpg to remove metadata 1040 func ReencodeJpg(in []byte) (out []byte, err error) { 1041 var buf bytes.Buffer 1042 img, _, err := image.Decode(bytes.NewReader(in)) 1043 if err != nil { 1044 return nil, err 1045 } 1046 if err := jpeg.Encode(&buf, img, nil); err != nil { 1047 return nil, err 1048 } 1049 return buf.Bytes(), nil 1050 } 1051 1052 func Must[T any](v T, err error) T { 1053 if err != nil { 1054 panic(err) 1055 } 1056 return v 1057 } 1058 1059 func Must1(err error) { 1060 if err != nil { 1061 panic(err) 1062 } 1063 } 1064 1065 func ValidateRuneLength(str string, min, max int) bool { 1066 return govalidator.RuneLength(str, strconv.Itoa(min), strconv.Itoa(max)) 1067 } 1068 1069 func Ternary[T any](predicate bool, a, b T) T { 1070 if predicate { 1071 return a 1072 } 1073 return b 1074 } 1075 1076 func TernaryOrZero[T any](predicate bool, a T) T { 1077 var zero T 1078 return Ternary(predicate, a, zero) 1079 } 1080 1081 func InArr[T comparable](needle T, haystack []T) bool { 1082 for _, el := range haystack { 1083 if el == needle { 1084 return true 1085 } 1086 } 1087 return false 1088 } 1089 1090 // CountBools given booleans, returns how many are set to true 1091 func CountBools(vals ...bool) (count int64) { 1092 for _, v := range vals { 1093 if v { 1094 count++ 1095 } 1096 } 1097 return count 1098 } 1099 1100 func CountUppercase(s string) (count, total int64) { 1101 for _, r := range s { 1102 if unicode.IsLetter(r) { 1103 total++ 1104 if unicode.IsUpper(r) { 1105 count++ 1106 } 1107 } 1108 } 1109 return 1110 } 1111 1112 func VerifyTorSign(onionAddr, msg, pemSig string) bool { 1113 block, _ := pem.Decode([]byte(pemSig)) 1114 if block == nil { 1115 return false 1116 } 1117 sig := block.Bytes 1118 pub := identityKeyFromAddress(onionAddr) 1119 return ed25519.Verify(pub, []byte(msg), sig) 1120 } 1121 1122 func identityKeyFromAddress(onionAddr string) ed25519.PublicKey { 1123 trimmedAddr := strings.TrimSuffix(onionAddr, ".onion") 1124 upperAddr := strings.ToUpper(trimmedAddr) 1125 decodedAddr, _ := base32.StdEncoding.DecodeString(upperAddr) 1126 return decodedAddr[:32] 1127 } 1128 1129 func Slice2Set[T any, U comparable](s []T, f func(T) U) *hashset.HashSet[U] { 1130 h := hashset.New[U]() 1131 for _, e := range s { 1132 h.Set(f(e)) 1133 } 1134 return h 1135 } 1136 1137 type CryptoRandSource struct{} 1138 1139 func NewCryptoRandSource() CryptoRandSource { 1140 return CryptoRandSource{} 1141 } 1142 1143 func (_ CryptoRandSource) Int63() int64 { 1144 var b [8]byte 1145 _, _ = cryptoRand.Read(b[:]) 1146 // mask off sign bit to ensure positive number 1147 return int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1)) 1148 } 1149 1150 func (_ CryptoRandSource) Seed(_ int64) {} 1151 1152 func False() bool { return false } 1153 func True() bool { return true } 1154 1155 func LogErr(err error) { 1156 if err != nil { 1157 logrus.Error(err) 1158 } 1159 } 1160 1161 // LogErr2 ... 1162 func LogErr2[T any](_ T, err error) { 1163 LogErr(err) 1164 } 1165 1166 func Second[T any, U any](_ T, v U) U { return v }