From 3af8a0d6243b02ff30cc1340871eef1a99b3ad3d Mon Sep 17 00:00:00 2001 From: Sumner Evans <sumner.evans@automattic.com> Date: Wed, 19 Feb 2025 17:15:30 -0700 Subject: [PATCH] connector: first sync of conversations Signed-off-by: Sumner Evans <sumner.evans@automattic.com> --- go.mod | 18 +-- go.sum | 36 ++--- pkg/connector/backfill.go | 7 + pkg/connector/client.go | 37 +++-- pkg/connector/config.go | 5 +- pkg/connector/example-config.yaml | 2 +- pkg/connector/linkedinfmt/convert.go | 22 ++- pkg/connector/linkedinfmt/html.go | 54 +++---- pkg/connector/linkedinfmt/styletype_string.go | 33 ++++ pkg/connector/linkedinfmt/tags.go | 88 ++--------- pkg/connector/matrixfmt/convert.go | 29 +++- pkg/connector/matrixfmt/html.go | 109 +++----------- pkg/connector/sync.go | 61 +++++++- pkg/linkedingo/attributedtext.go | 47 +++++- pkg/linkedingo/conversations.go | 141 ++++++------------ pkg/linkedingo/messages.go | 2 +- pkg/linkedingo/request.go | 2 +- 17 files changed, 340 insertions(+), 353 deletions(-) create mode 100644 pkg/connector/linkedinfmt/styletype_string.go diff --git a/go.mod b/go.mod index b710b86..c770d5c 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,11 @@ require ( github.com/google/go-querystring v1.1.0 github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.10.0 - go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003 - golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c - golang.org/x/net v0.34.0 + go.mau.fi/util v0.8.5 + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa + golang.org/x/net v0.35.0 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91 + maunium.net/go/mautrix v0.23.1 ) require ( @@ -28,7 +28,7 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.24 // indirect - github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 // indirect + github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect @@ -38,10 +38,10 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/yuin/goldmark v1.7.8 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect diff --git a/go.sum b/go.sum index a33905a..7bc9b97 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 h1:ah1dvbqPMN5+ocrg/ZSgZ6k8bOk+kcZQ7fnyx6UvOm4= -github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a h1:ckxP/kGzsxvxXo8jO6E/0QJ8MMmwI7IRj4Fys9QbAZA= +github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -60,25 +60,25 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003 h1:ye5l+QpYW5CpGVMedb3EHlmflGMQsMtw8mC4K/U8hIw= -go.mau.fi/util v0.8.5-0.20250203220331-1c0d19ea6003/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY= +go.mau.fi/util v0.8.5 h1:PwCAAtcfK0XxZ4sdErJyfBMkTEWoQU33aB7QqDDzQRI= +go.mau.fi/util v0.8.5/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= -golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= @@ -89,5 +89,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91 h1:jbga2dSYVTd3MgAKugiz5+mIYp+qxUOCDokUGZOEWRg= -maunium.net/go/mautrix v0.23.1-0.20250203222456-475c4bf39d91/go.mod h1:q2U2IRLSFpglDhIpSjd8TnCNVzBNrUJBD8pmYCGQiwc= +maunium.net/go/mautrix v0.23.1 h1:xZtX43YZF3WRxkdR+oMUrpiQe+jbjc+LeXLxHuXP5IM= +maunium.net/go/mautrix v0.23.1/go.mod h1:kldoZQDneV/jquIhwG1MmMw5j2A2M/MnQYRSWt863cY= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index d819496..afdee2c 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -2,11 +2,18 @@ package connector import ( "context" + "fmt" "maunium.net/go/mautrix/bridgev2" + + "go.mau.fi/mautrix-linkedin/pkg/linkedingo" ) func (l *LinkedInClient) FetchMessages(ctx context.Context, fetchParams bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) { + if messages, ok := fetchParams.BundledData.([]linkedingo.Message); ok { + fmt.Printf("%+v\n", messages) + } + // variables := queryold.FetchMessagesVariables{ // ConversationURN: linkedingo.NewURN(fetchParams.Portal.ID), // CountBefore: 20, diff --git a/pkg/connector/client.go b/pkg/connector/client.go index ba4fc36..a8e16a0 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -309,22 +309,35 @@ func (l *LinkedInClient) onRealtimeReactionSummaries(ctx context.Context, summar }) } -func (l *LinkedInClient) getAvatar(img *linkedingo.VectorImage) (avatar bridgev2.Avatar) { - avatar.ID = networkid.AvatarID(img.RootURL) - avatar.Remove = img.RootURL == "" - avatar.Get = func(ctx context.Context) ([]byte, error) { - return l.client.DownloadBytes(ctx, img.GetLargestArtifactURL()) +func (l *LinkedInClient) getAvatar(img *linkedingo.VectorImage) (avatar *bridgev2.Avatar) { + if img == nil { + return nil + } + return &bridgev2.Avatar{ + ID: networkid.AvatarID(img.RootURL), + Remove: img.RootURL == "", + Get: func(ctx context.Context) ([]byte, error) { + return l.client.DownloadBytes(ctx, img.GetLargestArtifactURL()) + }, } - return } func (l *LinkedInClient) getMessagingParticipantUserInfo(participant linkedingo.MessagingParticipant) (ui bridgev2.UserInfo) { - ui.Name = ptr.Ptr(l.main.Config.FormatDisplayname(DisplaynameParams{ - FirstName: participant.ParticipantType.Member.FirstName.Text, - LastName: participant.ParticipantType.Member.LastName.Text, - })) - ui.Avatar = ptr.Ptr(l.getAvatar(participant.ParticipantType.Member.ProfilePicture)) - ui.Identifiers = []string{fmt.Sprintf("linkedin:%s", participant.EntityURN.ID())} + switch { + case participant.ParticipantType.Member != nil: + ui.Name = ptr.Ptr(l.main.Config.FormatDisplayname(DisplaynameParams{ + FirstName: participant.ParticipantType.Member.FirstName.Text, + LastName: participant.ParticipantType.Member.LastName.Text, + })) + ui.Avatar = l.getAvatar(participant.ParticipantType.Member.ProfilePicture) + ui.Identifiers = []string{fmt.Sprintf("linkedin:%s", participant.EntityURN.ID())} + case participant.ParticipantType.Organization != nil: + ui.Name = ptr.Ptr(l.main.Config.FormatDisplayname(DisplaynameParams{ + Organization: participant.ParticipantType.Organization.Name.Text, + })) + ui.Avatar = l.getAvatar(participant.ParticipantType.Organization.Logo) + ui.Identifiers = []string{fmt.Sprintf("linkedin:%s", participant.EntityURN.ID())} + } return } diff --git a/pkg/connector/config.go b/pkg/connector/config.go index 05fadca..b79f1ae 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -57,8 +57,9 @@ func (lc *LinkedInConnector) GetConfig() (string, any, up.Upgrader) { } type DisplaynameParams struct { - FirstName string - LastName string + FirstName string + LastName string + Organization string } func (c *Config) FormatDisplayname(params DisplaynameParams) string { diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index 8d999c9..8869912 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -1,4 +1,4 @@ # Displayname template for LinkedIn users. # .FirstName is replaced with the first name # .LastName is replaced with the last name -displayname_template: "{{ .FirstName }} {{ .LastName }} (LinkedIn)" +displayname_template: "{{ with .Organization }}{{ . }}{{ else }}{{ .FirstName }} {{ .LastName }}{{ end }} (LinkedIn)" diff --git a/pkg/connector/linkedinfmt/convert.go b/pkg/connector/linkedinfmt/convert.go index 942a533..a76332b 100644 --- a/pkg/connector/linkedinfmt/convert.go +++ b/pkg/connector/linkedinfmt/convert.go @@ -70,6 +70,8 @@ func Parse(ctx context.Context, message string, attributes []linkedingo.Attribut Length: a.Length, }.TruncateEnd(maxLength) switch { + case a.AttributeKind.Bold != nil: + br.Value = Style{Type: StyleBold} case a.AttributeKind.Entity != nil: urn := a.AttributeKind.Entity.URN var userInfo UserInfo @@ -83,8 +85,26 @@ func Parse(ctx context.Context, message string, attributes []linkedingo.Attribut userInfo.Name = utf16Message[a.Start+1 : a.Start+a.Length].String() mentions[userInfo.MXID] = struct{}{} br.Value = Mention{userInfo, networkid.UserID(urn.ID())} + case a.AttributeKind.Hyperlink != nil: + br.Value = Style{Type: StyleHyperlink, URL: a.AttributeKind.Hyperlink.URL} + case a.AttributeKind.Italic != nil: + br.Value = Style{Type: StyleItalic} + case a.AttributeKind.LineBreak != nil: + br.Value = Style{Type: StyleLineBreak} + case a.AttributeKind.List != nil: + br.Value = Style{Type: StyleList, Ordered: a.AttributeKind.List.Ordered} + case a.AttributeKind.ListItem != nil: + br.Value = Style{Type: StyleListItem} + case a.AttributeKind.Paragraph != nil: + br.Value = Style{Type: StyleParagraph} + case a.AttributeKind.Subscript != nil: + br.Value = Style{Type: StyleSubscript} + case a.AttributeKind.Superscript != nil: + br.Value = Style{Type: StyleSuperscript} + case a.AttributeKind.Underline != nil: + br.Value = Style{Type: StyleUnderline} default: - log.Warn().Msg("Unhandled attribute") + log.Warn().Any("kind", a.AttributeKind).Msg("Unhandled attribute") } lrt.Add(br) } diff --git a/pkg/connector/linkedinfmt/html.go b/pkg/connector/linkedinfmt/html.go index 3501a83..d9e10c8 100644 --- a/pkg/connector/linkedinfmt/html.go +++ b/pkg/connector/linkedinfmt/html.go @@ -32,46 +32,29 @@ func (s Style) Format(message string) string { return fmt.Sprintf("<strong>%s</strong>", message) case StyleItalic: return fmt.Sprintf("<em>%s</em>", message) - case StyleSpoiler: - return fmt.Sprintf("<span data-mx-spoiler>%s</span>", message) - case StyleStrikethrough: - return fmt.Sprintf("<del>%s</del>", message) - case StyleCode: - if strings.ContainsRune(message, '\n') { - // This is somewhat incorrect, as it won't allow inline text before/after a multiline monospace-formatted string. - return fmt.Sprintf("<pre><code>%s</code></pre>", message) - } - return fmt.Sprintf("<code>%s</code>", message) - case StyleUnderline: - return fmt.Sprintf("<u>%s</u>", message) - case StyleBlockquote: - return fmt.Sprintf("<blockquote>%s</blockquote>", message) - case StylePre: - if s.Language != "" { - return fmt.Sprintf("<pre><code class='language-%s'>%s</code></pre>", s.Language, message) + case StyleLineBreak: + return "<br>" + case StyleList: + if s.Ordered { + return fmt.Sprintf("<ol>%s</ol>", message) } else { - return fmt.Sprintf("<pre><code>%s</code></pre>", message) - } - case StyleEmail: - return fmt.Sprintf(`<a href='mailto:%s'>%s</a>`, message, message) - case StyleTextURL: - if strings.HasPrefix(s.URL, "https://matrix.to/#") { - return s.URL + return fmt.Sprintf("<ul>%s</ul>", message) } - return fmt.Sprintf(`<a href='%s'>%s</a>`, s.URL, message) - case StyleURL: + case StyleListItem: + return fmt.Sprintf("<li>%s</li>", message) + case StyleParagraph: + return fmt.Sprintf("<p>%s</p>", message) + case StyleSubscript: + return fmt.Sprintf("<sub>%s</sub>", message) + case StyleSuperscript: + return fmt.Sprintf("<sup>%s</sup>", message) + case StyleHyperlink: if strings.HasPrefix(s.URL, "https://matrix.to/#") { return s.URL } return fmt.Sprintf(`<a href='%s'>%s</a>`, s.URL, message) - case StyleBotCommand: - return fmt.Sprintf("<font color='#3771bb'>%s</font>", message) - case StyleHashtag: - return fmt.Sprintf("<font color='#3771bb'>%s</font>", message) - case StyleCashtag: - return fmt.Sprintf("<font color='#3771bb'>%s</font>", message) - case StylePhone: - return fmt.Sprintf("<font color='#3771bb'>%s</font>", message) + case StyleUnderline: + return fmt.Sprintf("<u>%s</u>", message) default: return message } @@ -96,9 +79,6 @@ func (lrt *LinkedRangeTree) Format(message UTF16String, ctx formatContext) strin inner := message[lrt.Node.Start:lrt.Node.End()] tail := message[lrt.Node.End():] ourCtx := ctx - if lrt.Node.Value.IsCode() { - ourCtx.IsInCodeblock = true - } childMessage := lrt.Child.Format(inner, ourCtx) formattedChildMessage := lrt.Node.Value.Format(childMessage) siblingMessage := lrt.Sibling.Format(tail, ctx) diff --git a/pkg/connector/linkedinfmt/styletype_string.go b/pkg/connector/linkedinfmt/styletype_string.go new file mode 100644 index 0000000..502405f --- /dev/null +++ b/pkg/connector/linkedinfmt/styletype_string.go @@ -0,0 +1,33 @@ +// Code generated by "stringer -type=StyleType"; DO NOT EDIT. + +package linkedinfmt + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[StyleNone-0] + _ = x[StyleBold-1] + _ = x[StyleItalic-2] + _ = x[StyleLineBreak-3] + _ = x[StyleList-4] + _ = x[StyleListItem-5] + _ = x[StyleParagraph-6] + _ = x[StyleSubscript-7] + _ = x[StyleSuperscript-8] + _ = x[StyleHyperlink-9] + _ = x[StyleUnderline-10] +} + +const _StyleType_name = "StyleNoneStyleBoldStyleItalicStyleLineBreakStyleListStyleListItemStyleParagraphStyleSubscriptStyleSuperscriptStyleHyperlinkStyleUnderline" + +var _StyleType_index = [...]uint8{0, 9, 18, 29, 43, 52, 65, 79, 93, 109, 123, 137} + +func (i StyleType) String() string { + if i < 0 || i >= StyleType(len(_StyleType_index)-1) { + return "StyleType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _StyleType_name[_StyleType_index[i]:_StyleType_index[i+1]] +} diff --git a/pkg/connector/linkedinfmt/tags.go b/pkg/connector/linkedinfmt/tags.go index e81afc0..7736c95 100644 --- a/pkg/connector/linkedinfmt/tags.go +++ b/pkg/connector/linkedinfmt/tags.go @@ -20,13 +20,11 @@ import ( "fmt" "maunium.net/go/mautrix/bridgev2/networkid" - "maunium.net/go/mautrix/id" ) type BodyRangeValue interface { String() string Format(message string) string - IsCode() bool } type Mention struct { @@ -40,97 +38,37 @@ func (m Mention) String() string { return fmt.Sprintf("Mention{MXID: id.UserID(%q), Name: %q}", m.MXID, m.Name) } -func (m Mention) IsCode() bool { - return false -} - +//go:generate stringer -type=StyleType type StyleType int -var _ BodyRangeValue = Mention{} - const ( StyleNone StyleType = iota StyleBold StyleItalic + StyleLineBreak + StyleList + StyleListItem + StyleParagraph + StyleSubscript + StyleSuperscript + StyleHyperlink StyleUnderline - StyleStrikethrough - StyleBlockquote - StyleCode - StylePre - StyleEmail - StyleTextURL - StyleURL - StyleBotCommand - StyleHashtag - StyleCashtag - StylePhone - StyleSpoiler - StyleBankCard ) -func (s StyleType) String() string { - switch s { - case StyleNone: - return "StyleNone" - case StyleBold: - return "StyleBold" - case StyleItalic: - return "StyleItalic" - case StyleUnderline: - return "StyleUnderline" - case StyleStrikethrough: - return "StyleStrikethrough" - case StyleBlockquote: - return "StyleBlockquote" - case StyleCode: - return "StyleCode" - case StylePre: - return "StylePre" - case StyleEmail: - return "StyleEmail" - case StyleTextURL: - return "StyleTextURL" - case StyleURL: - return "StyleEntityURL" - case StyleBotCommand: - return "StyleBotCommand" - case StyleHashtag: - return "StyleHashtag" - case StyleCashtag: - return "StyleCashtag" - case StylePhone: - return "StylePhone" - case StyleSpoiler: - return "StyleSpoiler" - case StyleBankCard: - return "StyleBankCard" - default: - return fmt.Sprintf("StyleType(%d)", s) - } -} - // Style represents a style to apply to a range of text. type Style struct { // Type is the type of style. Type StyleType - // Language is the language of the code block, if applicable. - Language string + // Ordered indicates whether the list is ordered. + Ordered bool // URL is the URL to link to, if applicable. URL string - - // Emoji is the emoji to display, if applicable. - Emoji string - - // EmojiURI is the URI to the emoji, if applicable. - EmojiURI id.ContentURIString } -func (s Style) String() string { - return fmt.Sprintf("Style{Type: %s, Language: %s, URL: %s}", s.Type, s.Language, s.URL) -} +var _ BodyRangeValue = Style{} -func (s Style) IsCode() bool { - return s.Type == StyleCode || s.Type == StylePre +func (s Style) String() string { + return fmt.Sprintf("Style{Type: %s, URL: %s}", s.Type, s.URL) } diff --git a/pkg/connector/matrixfmt/convert.go b/pkg/connector/matrixfmt/convert.go index 0c696d5..97162ba 100644 --- a/pkg/connector/matrixfmt/convert.go +++ b/pkg/connector/matrixfmt/convert.go @@ -38,9 +38,36 @@ func toLinkedInAttribute(br linkedinfmt.BodyRange) linkedingo.SendMessageAttribu }, } case linkedinfmt.Style: + // TODO this doesn't seem to work, LinkedIn seems to ignore these + attributeKind := linkedingo.AttributeKind{} switch val.Type { + case linkedinfmt.StyleBold: + attributeKind.Bold = &linkedingo.Bold{} + case linkedinfmt.StyleItalic: + attributeKind.Italic = &linkedingo.Italic{} + case linkedinfmt.StyleLineBreak: + attributeKind.LineBreak = &linkedingo.LineBreak{} + case linkedinfmt.StyleList: + attributeKind.List = &linkedingo.List{Ordered: val.Ordered} + case linkedinfmt.StyleListItem: + attributeKind.ListItem = &linkedingo.ListItem{} + case linkedinfmt.StyleParagraph: + attributeKind.Paragraph = &linkedingo.Paragraph{} + case linkedinfmt.StyleSubscript: + attributeKind.Subscript = &linkedingo.Subscript{} + case linkedinfmt.StyleSuperscript: + attributeKind.Superscript = &linkedingo.Superscript{} + case linkedinfmt.StyleHyperlink: + attributeKind.Hyperlink = &linkedingo.Hyperlink{URL: val.URL} + case linkedinfmt.StyleUnderline: + attributeKind.Underline = &linkedingo.Underline{} default: - panic("unsupported style type") + panic("unsupported style type %s") + } + return linkedingo.SendMessageAttribute{ + Start: br.Start, + Length: br.Length, + AttributeKindUnion: attributeKind, } default: panic("unknown body range value") diff --git a/pkg/connector/matrixfmt/html.go b/pkg/connector/matrixfmt/html.go index 1717d5d..318daa2 100644 --- a/pkg/connector/matrixfmt/html.go +++ b/pkg/connector/matrixfmt/html.go @@ -18,9 +18,7 @@ package matrixfmt import ( "context" - "fmt" "math" - "strconv" "strings" "golang.org/x/exp/slices" @@ -282,53 +280,6 @@ func Digits(num int) int { return int(math.Floor(math.Log10(float64(num))) + 1) } -var listBullets = []string{"â—", "â—‹", "â– ", "‣"} - -func (parser *HTMLParser) listToString(node *html.Node, ctx Context) *EntityString { - ordered := node.Data == "ol" - if !ordered { - ctx = ctx.WithIncrementedListDepth() - } - taggedChildren := parser.nodeToTaggedStrings(node.FirstChild, ctx) - counter := 1 - indentLength := 0 - if ordered { - start := parser.getAttribute(node, "start") - if len(start) > 0 { - counter, _ = strconv.Atoi(start) - } - - longestIndex := (counter - 1) + len(taggedChildren) - indentLength = Digits(longestIndex) - } - indent := strings.Repeat(" ", indentLength+2) - var children []*EntityString - for _, child := range taggedChildren { - if child.tag != "li" { - continue - } - var prefix string - if ordered { - indexPadding := indentLength - Digits(counter) - if indexPadding < 0 { - // This will happen on negative start indexes where longestIndex is usually wrong, otherwise shouldn't happen - indexPadding = 0 - } - prefix = fmt.Sprintf("%d. %s", counter, strings.Repeat(" ", indexPadding)) - } else { - prefix = fmt.Sprintf("%s ", listBullets[(ctx.ListDepth-1)%len(listBullets)]) - } - es := NewEntityString(prefix).Append(child.EntityString) - counter++ - parts := es.Split('\n') - for i, part := range parts[1:] { - parts[i+1] = NewEntityString(indent).Append(part) - } - children = append(children, parts...) - } - return JoinEntityString("\n", children...) -} - func (parser *HTMLParser) basicFormatToString(node *html.Node, ctx Context) *EntityString { str := parser.nodeToTagAwareString(node.FirstChild, ctx) switch node.Data { @@ -336,23 +287,22 @@ func (parser *HTMLParser) basicFormatToString(node *html.Node, ctx Context) *Ent return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleBold}) case "i", "em": return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleItalic}) - case "s", "del", "strike": - return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleStrikethrough}) - case "u", "ins": + case "br": + return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleLineBreak}) + case "ul": + return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleList, Ordered: false}) + case "ol": + return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleList, Ordered: true}) + case "li": + return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleListItem}) + case "p": + return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleParagraph}) + case "sub": + return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleSubscript}) + case "sup": + return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleSuperscript}) + case "u": return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleUnderline}) - case "tt", "code": - return str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleCode}) - } - return str -} - -func (parser *HTMLParser) spanToString(node *html.Node, ctx Context) *EntityString { - str := parser.nodeToTagAwareString(node.FirstChild, ctx) - if node.Data == "span" { - _, isSpoiler := parser.maybeGetAttribute(node, "data-mx-spoiler") - if isSpoiler { - str = str.Format(linkedinfmt.Style{Type: linkedinfmt.StyleSpoiler}) - } } return str } @@ -386,49 +336,26 @@ func (parser *HTMLParser) linkToString(node *html.Node, ctx Context) *EntityStri return NewEntityString("@" + username).Format(linkedinfmt.Mention{UserID: userID}) } } - if str.String.String() == href { - return ent.Format(linkedinfmt.Style{Type: linkedinfmt.StyleURL, URL: href}) - } else { - return ent.Format(linkedinfmt.Style{Type: linkedinfmt.StyleTextURL, URL: href}) - } + return ent.Format(linkedinfmt.Style{Type: linkedinfmt.StyleHyperlink, URL: href}) } func (parser *HTMLParser) tagToString(node *html.Node, ctx Context) *EntityString { ctx = ctx.WithTag(node.Data) switch node.Data { case "blockquote": - return parser. - nodeToTagAwareString(node.FirstChild, ctx). - Format(linkedinfmt.Style{Type: linkedinfmt.StyleBlockquote}) - case "ol", "ul": - return parser.listToString(node, ctx) + return NewEntityString("> ").Append(parser.nodeToString(node.FirstChild, ctx)) case "h1", "h2", "h3", "h4", "h5", "h6": return parser.headerToString(node, ctx) case "br": return NewEntityString("\n") - case "b", "strong", "i", "em", "s", "strike", "del", "u", "ins", "tt", "code": + case "b", "strong", "i", "em", "s", "strike", "del", "u", "ins", "tt", "code", "ol", "ul", "li": return parser.basicFormatToString(node, ctx) - case "span", "font": - return parser.spanToString(node, ctx) case "a": return parser.linkToString(node, ctx) case "p": return parser.nodeToTagAwareString(node.FirstChild, ctx) case "hr": return NewEntityString("---") - case "pre": - var preStr *EntityString - var language string - if node.FirstChild != nil && node.FirstChild.Type == html.ElementNode && node.FirstChild.Data == "code" { - class := parser.getAttribute(node.FirstChild, "class") - if strings.HasPrefix(class, "language-") { - language = class[len("language-"):] - } - preStr = parser.nodeToString(node.FirstChild.FirstChild, ctx.WithWhitespace()) - } else { - preStr = parser.nodeToString(node.FirstChild, ctx.WithWhitespace()) - } - return preStr.Format(linkedinfmt.Style{Type: linkedinfmt.StylePre, Language: language}) default: return parser.nodeToTagAwareString(node.FirstChild, ctx) } diff --git a/pkg/connector/sync.go b/pkg/connector/sync.go index 7152d8f..760ede3 100644 --- a/pkg/connector/sync.go +++ b/pkg/connector/sync.go @@ -3,17 +3,68 @@ package connector import ( "context" "fmt" + "time" "github.com/rs/zerolog" + "go.mau.fi/util/ptr" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/simplevent" ) func (l *LinkedInClient) syncConversations(ctx context.Context) { log := zerolog.Ctx(ctx).With().Str("action", "sync_conversations").Logger() + log.Info().Msg("starting conversation sync") - conversations, err := l.client.GetConversations(ctx) - if err != nil { - log.Error().Err(err).Msg("failed to fetch initial inbox state") - return + lastUsedUpdatedBefore := time.Time{} + updatedBefore := time.Now() + for { + log := log.With(). + Time("updated_before", updatedBefore). + Time("last_used_updated_before", lastUsedUpdatedBefore). + Logger() + + if lastUsedUpdatedBefore.Equal(updatedBefore) { + log.Info().Msg("no more conversations found") + return + } + lastUsedUpdatedBefore = updatedBefore + + log.Info().Msg("fetching conversations") + + conversations, err := l.client.GetConversationsUpdatedBefore(ctx, updatedBefore) + if err != nil { + log.Err(err).Msg("failed to fetch conversations") + return + } + + fmt.Printf("%+v\n", conversations) + + for _, conv := range conversations.Elements { + fmt.Printf("conv=%+v\n", conv) + if conv.LastActivityAt.Before(updatedBefore) { + updatedBefore = conv.LastActivityAt.Time + } + + meta := simplevent.EventMeta{ + LogContext: func(c zerolog.Context) zerolog.Context { + return c.Str("update", "sync") + }, + PortalKey: l.makePortalKey(conv.EntityURN), + CreatePortal: true, + } + + var latestMessageTS time.Time + for _, msg := range conv.Messages.Elements { + if msg.DeliveredAt.After(latestMessageTS) { + latestMessageTS = msg.DeliveredAt.Time + } + } + l.main.Bridge.QueueRemoteEvent(l.userLogin, &simplevent.ChatResync{ + ChatInfo: ptr.Ptr(l.conversationToChatInfo(conv)), + EventMeta: meta.WithType(bridgev2.RemoteEventChatResync), + LatestMessageTS: latestMessageTS, + BundledBackfillData: conv.Messages.Elements, + }) + } } - fmt.Printf("%+v\n", conversations) } diff --git a/pkg/linkedingo/attributedtext.go b/pkg/linkedingo/attributedtext.go index b97fe1e..d413efe 100644 --- a/pkg/linkedingo/attributedtext.go +++ b/pkg/linkedingo/attributedtext.go @@ -15,11 +15,54 @@ type Attribute struct { } type AttributeKind struct { - // Entity is a user mention. - Entity *Entity `json:"entity,omitempty"` + Bold *Bold `json:"bold,omitempty"` + Entity *Entity `json:"entity,omitempty"` // Entity is a user mention. + Hyperlink *Hyperlink `json:"hyperlink,omitempty"` + Italic *Italic `json:"italic,omitempty"` + LineBreak *LineBreak `json:"lineBreak,omitempty"` + List *List `json:"list,omitempty"` + ListItem *ListItem `json:"listItem,omitempty"` + Paragraph *Paragraph `json:"paragraph,omitempty"` + Subscript *Subscript `json:"subscript,omitempty"` + Superscript *Superscript `json:"superscript,omitempty"` + Underline *Underline `json:"underline,omitempty"` } +// Bold represents a com.linkedin.pemberly.text.Bold object. +type Bold struct{} + // Entity represents a com.linkedin.pemberly.text.Entity object. type Entity struct { URN URN `json:"urn,omitempty"` } + +// Hyperlink represents a com.linkedin.pemberly.text.Hyperlink object. +type Hyperlink struct { + URL string `json:"url,omitempty"` +} + +// Italic represents a com.linkedin.pemberly.text.Italic object. +type Italic struct{} + +// LineBreak represents a com.linkedin.pemberly.text.LineBreak object. +type LineBreak struct{} + +// List represents a com.linkedin.pemberly.text.List object. +type List struct { + Ordered bool `json:"ordered,omitempty"` +} + +// ListItem represents a com.linkedin.pemberly.text.ListItem object. +type ListItem struct{} + +// Paragraph represents a com.linkedin.pemberly.text.Paragraph object. +type Paragraph struct{} + +// Subscript represents a com.linkedin.pemberly.text.Subscript object. +type Subscript struct{} + +// Superscript represents a com.linkedin.pemberly.text.Superscript object. +type Superscript struct{} + +// Underline represents a com.linkedin.pemberly.text.Underline object. +type Underline struct{} diff --git a/pkg/linkedingo/conversations.go b/pkg/linkedingo/conversations.go index d6144b6..c674423 100644 --- a/pkg/linkedingo/conversations.go +++ b/pkg/linkedingo/conversations.go @@ -4,51 +4,43 @@ import ( "context" "encoding/json" "net/http" -) - -// https://www.linkedin.com/voyager/api/voyagerMessagingGraphQL/graphql?queryId=messengerConversations.7b27164c5517548167d9adb4ba603e55&variables=(mailboxUrn:urn%3Ali%3Afsd_profile%3AACoAADZsHU0BD7Cr7MwzvkzsAcCoeOii7kl0mPU) - -// https://www.linkedin.com/voyager/api/voyagerMessagingGraphQL/graphql?queryId=messengerConversations.8656fb361a8ad0c178e8d3ff1a84ce26&variables=(query:(predicateUnions:List((conversationCategoryPredicate:(category:PRIMARY_INBOX)))),count:20,mailboxUrn:urn%3Ali%3Afsd_profile%3AACoAADZsHU0BD7Cr7MwzvkzsAcCoeOii7kl0mPU,lastUpdatedBefore:1739209141023) - -// curl 'https://www.linkedin.com/voyager/api/voyagerMessagingGraphQL/graphql?queryId=messengerConversations.277103fa0741e804ec5f21e6f64cb598&variables=(mailboxUrn:urn%3Ali%3Afsd_profile%3AACoAADZsHU0BD7Cr7MwzvkzsAcCoeOii7kl0mPU,syncToken:-trA4KJljszB4KJlLnVybjpsaTpmYWJyaWM6cHJvZC1sb3IxAA%3D%3D)' \ + "net/url" + "strconv" + "time" -// type GetThreadsVariables struct { -// InboxCategory InboxCategory `graphql:"category"` -// Count int64 `graphql:"count"` -// MailboxUrn string `graphql:"mailboxUrn"` -// LastUpdatedBefore int64 `graphql:"lastUpdatedBefore"` -// NextCursor string `graphql:"nextCursor"` -// SyncToken string `graphql:"syncToken"` -// } + "github.com/rs/zerolog" + "go.mau.fi/util/jsontime" +) -type GraphQlResponse[T any] struct { - Data GraphQLData[T] `json:"data,omitempty"` +type GraphQlResponse struct { + Data GraphQLData `json:"data,omitempty"` } -type GraphQLData[T any] struct { - MessengerConversationsBySyncToken *CollectionResponse[T] `json:"messengerConversationsBySyncToken,omitempty"` +type GraphQLData struct { + MessengerConversationsByCategoryQuery *CollectionResponse[ConversationCursorMetadata, Conversation] `json:"messengerConversationsByCategoryQuery,omitempty"` } // CollectionResponse represents a // com.linkedin.restli.common.CollectionResponse object. -type CollectionResponse[T any] struct { - Metadata SyncMetadata `json:"metadata,omitempty"` - Elements []T `json:"elements,omitempty"` +type CollectionResponse[M, T any] struct { + Metadata M `json:"metadata,omitempty"` + Elements []T `json:"elements,omitempty"` } -// SyncMetadata represents a com.linkedin.messenger.SyncMetadata object. -type SyncMetadata struct { - NewSyncToken string `json:"newSyncToken,omitempty"` +// ConversationCursorMetadata represents a com.linkedin.messenger.ConversationCursorMetadata object. +type ConversationCursorMetadata struct { + NextCursor string `json:"nextCursor,omitempty"` } // Conversation represents a com.linkedin.messenger.Conversation object type Conversation struct { - Title string `json:"title,omitempty"` - EntityURN URN `json:"entityUrn,omitempty"` - GroupChat bool `json:"groupChat,omitempty"` - ConversationParticipants []MessagingParticipant `json:"conversationParticipants,omitempty"` - Read bool `json:"read,omitempty"` - // Messages []CollectionResponse[Message] `json:"messages,omitempty"` + Title string `json:"title,omitempty"` + EntityURN URN `json:"entityUrn,omitempty"` + LastActivityAt jsontime.UnixMilli `json:"lastActivityAt,omitempty"` + GroupChat bool `json:"groupChat,omitempty"` + ConversationParticipants []MessagingParticipant `json:"conversationParticipants,omitempty"` + Read bool `json:"read,omitempty"` + Messages CollectionResponse[any, Message] `json:"messages,omitempty"` } // MessagingParticipant represents a @@ -59,7 +51,8 @@ type MessagingParticipant struct { } type ParticipantType struct { - Member MemberParticipantInfo `json:"member,omitempty"` + Member *MemberParticipantInfo `json:"member,omitempty"` + Organization *OrganizationParticipantInfo `json:"organization,omitempty"` } // MemberParticipantInfo represents a @@ -73,22 +66,25 @@ type MemberParticipantInfo struct { Headline AttributedText `json:"headline,omitempty"` } -func (c *Client) GetConversations(ctx context.Context) (*CollectionResponse[Conversation], error) { - variables := map[string]string{ - "mailboxUrn": c.userEntityURN.WithPrefix("urn", "li", "fsd_profile").String(), - } - - // withCursor := variables.LastUpdatedBefore != 0 && variables.NextCursor != "" - - queryId := graphQLQueryIDMessengerConversations - // if withCursor { - // queryId = graphQLQueryIDMessengerConversationsWithCursor - // } else if variables.SyncToken != "" { - // queryId = graphQLQueryIDMessengerConversationsWithSyncToken - // } +// OrganizationParticipantInfo represents a +// com.linkedin.messenger.OrganizationParticipantInfo object. +type OrganizationParticipantInfo struct { + Name AttributedText `json:"name,omitempty"` + Logo *VectorImage `json:"logo,omitempty"` + PageURL string `json:"pageUrl,omitempty"` +} +func (c *Client) GetConversationsUpdatedBefore(ctx context.Context, updatedBefore time.Time) (*CollectionResponse[ConversationCursorMetadata, Conversation], error) { + zerolog.Ctx(ctx).Info(). + Time("updated_before", updatedBefore). + Msg("Getting conversations updated before") resp, err := c.newAuthedRequest(http.MethodGet, linkedInVoyagerMessagingGraphQLURL). - WithGraphQLQuery(queryId, variables). + WithGraphQLQuery(graphQLQueryIDMessengerConversationsWithCursor, map[string]string{ + "mailboxUrn": url.QueryEscape(c.userEntityURN.WithPrefix("urn", "li", "fsd_profile").String()), + "lastUpdatedBefore": strconv.Itoa(int(updatedBefore.UnixMilli())), + "count": "20", + "query": "(predicateUnions:List((conversationCategoryPredicate:(category:PRIMARY_INBOX))))", + }). WithCSRF(). WithXLIHeaders(). WithHeader("accept", contentTypeGraphQL). @@ -97,55 +93,6 @@ func (c *Client) GetConversations(ctx context.Context) (*CollectionResponse[Conv return nil, err } - var response GraphQlResponse[Conversation] - if err = json.NewDecoder(resp.Body).Decode(&response); err != nil { - return nil, err - } - - return response.Data.MessengerConversationsBySyncToken, nil + var response GraphQlResponse + return response.Data.MessengerConversationsByCategoryQuery, json.NewDecoder(resp.Body).Decode(&response) } - -// func (c *Client) GetThreads(variables queryold.GetThreadsVariables) (*responseold.MessengerConversationsResponse, error) { -// if variables.MailboxUrn == "" { -// variables.MailboxUrn = c.PageLoader.CurrentUser.FsdProfileID -// } -// -// withCursor := variables.LastUpdatedBefore != 0 && variables.NextCursor != "" -// var queryId typesold.GraphQLQueryIDs -// if withCursor { -// queryId = typesold.GraphQLQueryIDMessengerConversationsWithCursor -// } else if variables.SyncToken != "" { -// queryId = typesold.GraphQLQueryIDMessengerConversationsWithSyncToken -// } else { -// queryId = typesold.GraphQLQueryIDMessengerConversations -// } -// -// variablesQuery, err := variables.Encode() -// if err != nil { -// return nil, err -// } -// -// threadQuery := queryold.GraphQLQuery{ -// QueryID: queryId, -// Variables: string(variablesQuery), -// } -// -// _, respData, err := c.MakeRoutingRequest(routingold.LinkedInVoyagerMessagingGraphQLURL, nil, &threadQuery) -// if err != nil { -// return nil, err -// } -// fmt.Printf("%s\n", respData) -// -// graphQLResponse, ok := respData.(*responseold.GraphQlResponse) -// if !ok || graphQLResponse == nil { -// return nil, newErrorResponseTypeAssertFailed("*responseold.GraphQlResponse") -// } -// -// graphQLResponseData := graphQLResponse.Data -// fmt.Printf("%+v\n", graphQLResponseData) -// if withCursor { -// return graphQLResponseData.MessengerConversationsByCategory, nil -// } -// -// return graphQLResponseData.MessengerConversationsBySyncToken, nil -// } diff --git a/pkg/linkedingo/messages.go b/pkg/linkedingo/messages.go index a78d7c6..bd41222 100644 --- a/pkg/linkedingo/messages.go +++ b/pkg/linkedingo/messages.go @@ -190,7 +190,7 @@ func (c *Client) RecallMessage(ctx context.Context, messageURN URN) error { return nil } -func (c *Client) FetchMessages(ctx context.Context, variables queryold.FetchMessagesVariables) (*responseold.MessengerMessagesResponse, error) { +func (c *Client) FetchMessages(ctx context.Context, conversationURN URN, variables queryold.FetchMessagesVariables) (*responseold.MessengerMessagesResponse, error) { withCursor := variables.PrevCursor != "" withAnchorTimestamp := !variables.DeliveredAt.IsZero() diff --git a/pkg/linkedingo/request.go b/pkg/linkedingo/request.go index 968083c..c826823 100644 --- a/pkg/linkedingo/request.go +++ b/pkg/linkedingo/request.go @@ -85,7 +85,7 @@ func (a *authedRequest) WithGraphQLQuery(queryID string, variables map[string]st first = false queryStr.WriteString(k) queryStr.WriteByte(':') - queryStr.WriteString(url.QueryEscape(v)) + queryStr.WriteString(v) } queryStr.WriteString(")") a.rawQuery = queryStr.String() -- GitLab