Skip to content

Commit 1919385

Browse files
author
Saveliy Yudin
committed
feat: add ContactAttachmentPayload.Phone() to extract phone from vCard
1 parent f67db56 commit 1919385

3 files changed

Lines changed: 89 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [v0.3.0] - 2026-02-23
4+
5+
### Added
6+
- `ContactAttachmentPayload.Phone()` — extracts phone number from VCFInfo (vCard TEL field)
7+
38
## [v0.2.2] - 2026-02-15
49

510
### Added

types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package maxigo
33
import (
44
"encoding/json"
55
"fmt"
6+
"strings"
67
)
78

89
// ChatType represents the type of chat.
@@ -479,6 +480,23 @@ type ContactAttachmentPayload struct {
479480
MaxInfo *User `json:"max_info,omitempty"`
480481
}
481482

483+
// Phone extracts the phone number from VCFInfo (vCard TEL field).
484+
// Returns an empty string if VCFInfo is nil or contains no TEL field.
485+
func (p *ContactAttachmentPayload) Phone() string {
486+
if p.VCFInfo == nil {
487+
return ""
488+
}
489+
for _, line := range strings.Split(*p.VCFInfo, "\n") {
490+
line = strings.TrimRight(line, "\r")
491+
if strings.HasPrefix(strings.ToUpper(line), "TEL") {
492+
if idx := strings.LastIndex(line, ":"); idx >= 0 {
493+
return strings.TrimSpace(line[idx+1:])
494+
}
495+
}
496+
}
497+
return ""
498+
}
499+
482500
// ContactAttachment represents a contact attachment in a message.
483501
type ContactAttachment struct {
484502
AttachmentType

types_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,3 +547,69 @@ func TestParseAttachments(t *testing.T) {
547547
}
548548
})
549549
}
550+
551+
func TestContactAttachmentPayload_Phone(t *testing.T) {
552+
tests := []struct {
553+
name string
554+
payload ContactAttachmentPayload
555+
want string
556+
}{
557+
{
558+
name: "nil VCFInfo",
559+
payload: ContactAttachmentPayload{},
560+
want: "",
561+
},
562+
{
563+
name: "standard vCard with TEL;TYPE=cell",
564+
payload: ContactAttachmentPayload{
565+
VCFInfo: strPtr("BEGIN:VCARD\nVERSION:3.0\nTEL;TYPE=cell:79001234567\nFN:John\nEND:VCARD"),
566+
},
567+
want: "79001234567",
568+
},
569+
{
570+
name: "plain TEL field",
571+
payload: ContactAttachmentPayload{
572+
VCFInfo: strPtr("BEGIN:VCARD\nTEL:+79001234567\nEND:VCARD"),
573+
},
574+
want: "+79001234567",
575+
},
576+
{
577+
name: "TEL with multiple type params",
578+
payload: ContactAttachmentPayload{
579+
VCFInfo: strPtr("BEGIN:VCARD\nTEL;TYPE=work;TYPE=voice:+74951234567\nEND:VCARD"),
580+
},
581+
want: "+74951234567",
582+
},
583+
{
584+
name: "no TEL field",
585+
payload: ContactAttachmentPayload{
586+
VCFInfo: strPtr("BEGIN:VCARD\nFN:John\nEND:VCARD"),
587+
},
588+
want: "",
589+
},
590+
{
591+
name: "vCard with \\r\\n line endings",
592+
payload: ContactAttachmentPayload{
593+
VCFInfo: strPtr("BEGIN:VCARD\r\nTEL;TYPE=cell:79991234567\r\nEND:VCARD"),
594+
},
595+
want: "79991234567",
596+
},
597+
{
598+
name: "lowercase tel field",
599+
payload: ContactAttachmentPayload{
600+
VCFInfo: strPtr("BEGIN:VCARD\ntel;type=cell:79991234567\nEND:VCARD"),
601+
},
602+
want: "79991234567",
603+
},
604+
}
605+
606+
for _, tt := range tests {
607+
t.Run(tt.name, func(t *testing.T) {
608+
got := tt.payload.Phone()
609+
if got != tt.want {
610+
t.Errorf("Phone() = %q, want %q", got, tt.want)
611+
}
612+
})
613+
}
614+
615+
}

0 commit comments

Comments
 (0)