github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/wallet/parser.go (about) 1 package wallet 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 "github.com/keybase/client/go/chat/utils" 9 ) 10 11 var txPattern = regexp.MustCompile( 12 utils.ServiceDecorationPrefix + 13 // Have a + in front 14 `\+` + 15 // Stellar decimal amount 16 `(\d+\.?\d*|\d*\.?\d+)` + 17 // Currency code 18 `([A-Za-z]{2,6})` + 19 // At sign and username, optional for direct messages 20 `(?:@((?:[a-zA-Z0-9]+_?)+))?` + 21 // Sentinel group for advancing the scan 22 `()` + 23 // Must be followed by a nice character or the end of the string. 24 `(?:[\s)\]}:;.,!?"']|\z)`, 25 ) 26 27 var maxAmountLength = 100 28 var maxUsernameLength = 16 29 var maxTxsPerMessage = 3000 30 31 type ChatTxCandidate struct { 32 Amount string 33 CurrencyCode string 34 Username *string 35 Full string 36 Position []int 37 } 38 39 func FindChatTxCandidates(xs string) []ChatTxCandidate { 40 // A string that does not appear in the candidate regex so we don't get 41 // false positives from concatenations. 42 replaced := utils.ReplaceQuotedSubstrings(xs, false) 43 44 buf := replaced // buf is a suffix of replaced 45 bufOffset := 0 // buf[0] is replaced[bufOffset] 46 47 // Munch matches off the front of buf. 48 // Can't use FindAllStringSubmatchIndex (emphasis on All) because 49 // adjacent matches of txPattern can overlap. 50 // For example: "+1xlm +2xlm" -> "+1xlm ", " +2xlm" 51 var matches []ChatTxCandidate 52 for i := 0; i < maxTxsPerMessage; i++ { 53 rawIndices := txPattern.FindStringSubmatchIndex(buf) 54 if rawIndices == nil { 55 break 56 } 57 group := func(n int) (s string, startIndex, endIndex int) { 58 startIndex, endIndex = rawIndices[2*n], rawIndices[2*n+1] 59 if startIndex >= 0 { 60 return buf[startIndex:endIndex], startIndex, endIndex 61 } 62 return "", startIndex, endIndex 63 } 64 amount, amountStart, _ := group(1) 65 _, _, nextIndex := group(4) 66 if amount != "0" { 67 currencyCode, _, ccEnd := group(2) 68 currencyCode = strings.ToUpper(currencyCode) 69 var atSign string 70 endIndex := ccEnd 71 username, _, usernameEndIndex := group(3) 72 if len(username) > 0 { 73 atSign = "@" 74 endIndex = usernameEndIndex 75 } 76 full := fmt.Sprintf("+%s%s%s%s", amount, currencyCode, atSign, username) 77 if len(amount) <= maxAmountLength && len(username) <= maxUsernameLength { 78 var txUsername *string 79 if username == "" { 80 txUsername = nil 81 } else { 82 txUsername = &username 83 } 84 matches = append(matches, ChatTxCandidate{ 85 Full: full, 86 Amount: amount, 87 CurrencyCode: currencyCode, 88 Username: txUsername, 89 Position: []int{amountStart - 1 + bufOffset, endIndex + bufOffset}, 90 }) 91 } 92 } 93 if nextIndex == -1 || nextIndex > len(buf) { 94 // should never happen 95 return nil 96 } 97 buf = buf[nextIndex:] 98 bufOffset += nextIndex 99 } 100 return matches 101 }