1package photocopy
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/araddon/dateparse"
9 "github.com/bluesky-social/indigo/api/bsky"
10 "github.com/bluesky-social/indigo/atproto/syntax"
11)
12
13func (p *Photocopy) handleCreate(ctx context.Context, recb []byte, indexedAt, rev, did, collection, rkey, cid string) error {
14 _, err := dateparse.ParseAny(indexedAt)
15 if err != nil {
16 return err
17 }
18
19 switch collection {
20 }
21
22 return nil
23}
24
25// func (p *Photocopy) handleCreatePost(ctx context.Context, rev string, recb []byte, uri, did, collection, rkey, cid string, indexedAt time.Time) error {
26// var rec bsky.FeedPost
27// if err := rec.UnmarshalCBOR(bytes.NewReader(recb)); err != nil {
28// return err
29// }
30//
31// jb, err := json.Marshal(rec)
32// if err != nil {
33// return err
34// }
35//
36// cat, err := parseTimeFromRecord(rec, rkey)
37// if err != nil {
38// return err
39// }
40//
41// bqrec := &BigQueryRecord{
42// Uri: uri,
43// AuthorDid: did,
44// Collection: collection,
45// Rkey: rkey,
46// CreatedAt: *cat,
47// IndexedAt: indexedAt,
48// Raw: recb,
49// Json: string(jb),
50// Cid: cid,
51// Rev: rev,
52// }
53//
54// if rec.Reply != nil {
55// if rec.Reply.Parent != nil {
56// bqrec.ReplyToUri = rec.Reply.Parent.Uri
57// }
58// if rec.Reply.Root != nil {
59// bqrec.InThreadUri = rec.Reply.Root.Uri
60// }
61// }
62//
63// if err := p.inserters.genericInserter.Insert(ctx, bqrec); err != nil {
64// return err
65// }
66//
67// return nil
68// }
69
70// func (p *Photocopy) handleCreateFollow(ctx context.Context, recb []byte, uri, did, rkey string, indexedAt time.Time) error {
71// var rec bsky.GraphFollow
72// if err := rec.UnmarshalCBOR(bytes.NewReader(recb)); err != nil {
73// return err
74// }
75//
76// cat, err := parseTimeFromRecord(rec, rkey)
77// if err != nil {
78// return err
79// }
80//
81// bqrec := &BigQueryFollow{
82// Uri: uri,
83// AuthorDid: did,
84// Rkey: rkey,
85// CreatedAt: *cat,
86// IndexedAt: indexedAt,
87// SubjectDid: rec.Subject,
88// }
89//
90// if err := p.inserters.followsInserter.Insert(ctx, bqrec); err != nil {
91// return err
92// }
93//
94// return nil
95// }
96//
97// func (p *Photocopy) handleCreateInteraction(ctx context.Context, recb []byte, uri, did, collection, rkey string, indexedAt time.Time) error {
98// colPts := strings.Split(collection, ".")
99// if len(colPts) < 4 {
100// return fmt.Errorf("invalid collection type %s", collection)
101// }
102//
103// bqi := &BigQueryInteraction{
104// Uri: uri,
105// Kind: colPts[3],
106// AuthorDid: did,
107// Rkey: rkey,
108// IndexedAt: indexedAt,
109// }
110//
111// switch collection {
112// case "app.bsky.feed.like":
113// var rec bsky.FeedLike
114// if err := rec.UnmarshalCBOR(bytes.NewReader(recb)); err != nil {
115// return err
116// }
117//
118// cat, err := parseTimeFromRecord(rec, rkey)
119// if err != nil {
120// return err
121// }
122//
123// if rec.Subject == nil {
124// return fmt.Errorf("invalid subject in like")
125// }
126//
127// bqi.SubjectUri = rec.Subject.Uri
128// bqi.CreatedAt = *cat
129// case "app.bsky.feed.repost":
130// var rec bsky.FeedRepost
131// if err := rec.UnmarshalCBOR(bytes.NewReader(recb)); err != nil {
132// return err
133// }
134//
135// cat, err := parseTimeFromRecord(rec, rkey)
136// if err != nil {
137// return err
138// }
139//
140// if rec.Subject == nil {
141// return fmt.Errorf("invalid subject in repost")
142// }
143//
144// bqi.SubjectUri = rec.Subject.Uri
145// bqi.CreatedAt = *cat
146// }
147//
148// if err := p.inserters.interactionsInserter.Insert(ctx, bqi); err != nil {
149// return err
150// }
151//
152// return nil
153// }
154
155func parseTimeFromRecord(rec any, rkey string) (*time.Time, error) {
156 var rkeyTime time.Time
157 if rkey != "self" {
158 rt, err := syntax.ParseTID(rkey)
159 if err == nil {
160 rkeyTime = rt.Time()
161 }
162 }
163 switch rec := rec.(type) {
164 case *bsky.FeedPost:
165 t, err := dateparse.ParseAny(rec.CreatedAt)
166 if err != nil {
167 return nil, err
168 }
169
170 if inRange(t) {
171 return &t, nil
172 }
173
174 if rkeyTime.IsZero() || !inRange(rkeyTime) {
175 return timePtr(time.Now()), nil
176 }
177
178 return &rkeyTime, nil
179 case *bsky.FeedRepost:
180 t, err := dateparse.ParseAny(rec.CreatedAt)
181 if err != nil {
182 return nil, err
183 }
184
185 if inRange(t) {
186 return timePtr(t), nil
187 }
188
189 if rkeyTime.IsZero() {
190 return nil, fmt.Errorf("failed to get a useful timestamp from record")
191 }
192
193 return &rkeyTime, nil
194 case *bsky.FeedLike:
195 t, err := dateparse.ParseAny(rec.CreatedAt)
196 if err != nil {
197 return nil, err
198 }
199
200 if inRange(t) {
201 return timePtr(t), nil
202 }
203
204 if rkeyTime.IsZero() {
205 return nil, fmt.Errorf("failed to get a useful timestamp from record")
206 }
207
208 return &rkeyTime, nil
209 case *bsky.ActorProfile:
210 // We can't really trust the createdat in the profile record anyway, and its very possible its missing. just use iat for this one
211 return timePtr(time.Now()), nil
212 case *bsky.FeedGenerator:
213 if !rkeyTime.IsZero() && inRange(rkeyTime) {
214 return &rkeyTime, nil
215 }
216 return timePtr(time.Now()), nil
217 default:
218 if !rkeyTime.IsZero() && inRange(rkeyTime) {
219 return &rkeyTime, nil
220 }
221 return timePtr(time.Now()), nil
222 }
223}
224
225func inRange(t time.Time) bool {
226 now := time.Now()
227 if t.Before(now) {
228 return now.Sub(t) <= time.Hour*24*365*5
229 }
230 return t.Sub(now) <= time.Hour*24*200
231}
232
233func timePtr(t time.Time) *time.Time {
234 return &t
235}