|
| 1 | +package nearsocial |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "encoding/base64" |
| 6 | + "encoding/json" |
| 7 | + "fmt" |
| 8 | + "strings" |
| 9 | + |
| 10 | + "github.com/rss3-network/node/config" |
| 11 | + "github.com/rss3-network/node/internal/engine" |
| 12 | + source "github.com/rss3-network/node/internal/engine/source/near" |
| 13 | + "github.com/rss3-network/node/provider/near" |
| 14 | + workerx "github.com/rss3-network/node/schema/worker/decentralized" |
| 15 | + "github.com/rss3-network/protocol-go/schema" |
| 16 | + activityx "github.com/rss3-network/protocol-go/schema/activity" |
| 17 | + "github.com/rss3-network/protocol-go/schema/metadata" |
| 18 | + "github.com/rss3-network/protocol-go/schema/network" |
| 19 | + "github.com/rss3-network/protocol-go/schema/tag" |
| 20 | + "github.com/rss3-network/protocol-go/schema/typex" |
| 21 | +) |
| 22 | + |
| 23 | +var _ engine.Worker = (*worker)(nil) |
| 24 | + |
| 25 | +type worker struct { |
| 26 | + config *config.Module |
| 27 | +} |
| 28 | + |
| 29 | +func (w *worker) Name() string { |
| 30 | + return workerx.NearSocial.String() |
| 31 | +} |
| 32 | + |
| 33 | +func (w *worker) Platform() string { |
| 34 | + return workerx.PlatformNearSocial.String() |
| 35 | +} |
| 36 | + |
| 37 | +func (w *worker) Network() []network.Network { |
| 38 | + return []network.Network{ |
| 39 | + network.Near, |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +func (w *worker) Tags() []tag.Tag { |
| 44 | + return []tag.Tag{ |
| 45 | + tag.Social, |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +func (w *worker) Types() []schema.Type { |
| 50 | + return []schema.Type{ |
| 51 | + typex.SocialPost, |
| 52 | + typex.SocialComment, |
| 53 | + typex.SocialShare, |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +const ( |
| 58 | + socialNearReceiverID = "social.near" |
| 59 | +) |
| 60 | + |
| 61 | +func (w *worker) Filter() engine.DataSourceFilter { |
| 62 | + return &source.Filter{ |
| 63 | + ReceiverIDs: []string{ |
| 64 | + socialNearReceiverID, |
| 65 | + }, |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +// Transform processes a Near task and returns an activity. |
| 70 | +// It is the main entry point for processing Near Social transactions. |
| 71 | +func (w *worker) Transform(ctx context.Context, task engine.Task) (*activityx.Activity, error) { |
| 72 | + // Cast the task to a Near task. |
| 73 | + nearTask, ok := task.(*source.Task) |
| 74 | + if !ok { |
| 75 | + return nil, fmt.Errorf("invalid task type: %T", task) |
| 76 | + } |
| 77 | + |
| 78 | + // Build the activity with the platform information. |
| 79 | + activity, err := task.BuildActivity(activityx.WithActivityPlatform(w.Platform())) |
| 80 | + if err != nil { |
| 81 | + return nil, fmt.Errorf("build activity: %w", err) |
| 82 | + } |
| 83 | + |
| 84 | + // Process the Near Social action in the transaction. |
| 85 | + action, err := w.handleNearSocialAction(ctx, nearTask, activity.Timestamp) |
| 86 | + if err != nil { |
| 87 | + return nil, fmt.Errorf("handle near social action: %w", err) |
| 88 | + } |
| 89 | + |
| 90 | + if action != nil { |
| 91 | + activity.Type = action.Type |
| 92 | + activity.Actions = append(activity.Actions, action) |
| 93 | + } else { |
| 94 | + return nil, fmt.Errorf("no action found in transaction") |
| 95 | + } |
| 96 | + |
| 97 | + return activity, nil |
| 98 | +} |
| 99 | + |
| 100 | +// handleNearSocialAction processes the action in the Near Social transaction and returns an activityx.Action. |
| 101 | +// This function is responsible for identifying and processing the 'set' function call in the transaction. |
| 102 | +func (w *worker) handleNearSocialAction(_ context.Context, task *source.Task, timestamp uint64) (*activityx.Action, error) { |
| 103 | + for _, action := range task.Transaction.Transaction.Actions { |
| 104 | + if action.FunctionCall != nil && action.FunctionCall.MethodName == "set" { |
| 105 | + return w.processSetFunction(task.Transaction.Transaction.SignerID, action.FunctionCall, timestamp) |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + return nil, nil |
| 110 | +} |
| 111 | + |
| 112 | +// processSetFunction handles the "set" function call and returns an activityx.Action. |
| 113 | +// This function decodes and processes the arguments of the 'set' function call to build a social action. |
| 114 | +func (w *worker) processSetFunction(signerID string, functionCall *near.FunctionCallAction, timestamp uint64) (*activityx.Action, error) { |
| 115 | + // Decode base64 args |
| 116 | + decodedArgs, err := base64.StdEncoding.DecodeString(functionCall.Args) |
| 117 | + if err != nil { |
| 118 | + return nil, fmt.Errorf("decode base64 args: %w", err) |
| 119 | + } |
| 120 | + |
| 121 | + // Unmarshal decoded args into FunctionCallArgs struct |
| 122 | + var args FunctionCallArgs |
| 123 | + if err := json.Unmarshal(decodedArgs, &args); err != nil { |
| 124 | + return nil, fmt.Errorf("unmarshal function call args: %w", err) |
| 125 | + } |
| 126 | + |
| 127 | + for _, userContent := range args.Data { |
| 128 | + if userContent.Post != nil { |
| 129 | + for path, postDataJSON := range userContent.Post { |
| 130 | + var postData PostData |
| 131 | + if err := json.Unmarshal([]byte(postDataJSON), &postData); err != nil { |
| 132 | + return nil, fmt.Errorf("unmarshal post data: %w", err) |
| 133 | + } |
| 134 | + |
| 135 | + return w.buildSocialAction(signerID, path, postData, args, timestamp) |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + if userContent.Index != nil { |
| 140 | + return w.buildSocialAction(signerID, "", PostData{}, args, timestamp) |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + return nil, nil |
| 145 | +} |
| 146 | + |
| 147 | +// buildSocialAction constructs an activityx.Action based on the provided social action data. |
| 148 | +// This function is crucial for creating the appropriate action type (post, comment, or share) and populating its metadata. |
| 149 | +func (w *worker) buildSocialAction(signerID, path string, postData PostData, args FunctionCallArgs, timestamp uint64) (*activityx.Action, error) { |
| 150 | + action := &activityx.Action{ |
| 151 | + Type: typex.SocialPost, |
| 152 | + Platform: w.Platform(), |
| 153 | + From: signerID, |
| 154 | + To: signerID, |
| 155 | + Metadata: metadata.SocialPost{ |
| 156 | + Handle: signerID, |
| 157 | + Body: postData.Text, |
| 158 | + Timestamp: timestamp, |
| 159 | + }, |
| 160 | + } |
| 161 | + |
| 162 | + if path == "comment" { |
| 163 | + action = w.handleComment(action, postData) |
| 164 | + } |
| 165 | + |
| 166 | + if userContent, ok := args.Data[signerID]; ok { |
| 167 | + action = w.processUserContent(action, userContent, signerID, timestamp) |
| 168 | + } |
| 169 | + |
| 170 | + return action, nil |
| 171 | +} |
| 172 | + |
| 173 | +func (w *worker) handleComment(action *activityx.Action, postData PostData) *activityx.Action { |
| 174 | + action.Type = typex.SocialComment |
| 175 | + |
| 176 | + if postData.Item != nil { |
| 177 | + target := &metadata.SocialPost{ |
| 178 | + Handle: strings.Split(postData.Item.Path, "/")[0], |
| 179 | + PublicationID: fmt.Sprintf("%s-%d", postData.Item.Path, postData.Item.BlockHeight), |
| 180 | + } |
| 181 | + |
| 182 | + if socialPost, ok := action.Metadata.(metadata.SocialPost); ok { |
| 183 | + socialPost.Target = target |
| 184 | + action.Metadata = socialPost |
| 185 | + } |
| 186 | + |
| 187 | + action.To = target.Handle |
| 188 | + } |
| 189 | + |
| 190 | + return action |
| 191 | +} |
| 192 | + |
| 193 | +func (w *worker) processUserContent(action *activityx.Action, userContent UserContent, signerID string, timestamp uint64) *activityx.Action { |
| 194 | + action = w.processHashtags(action, userContent) |
| 195 | + action = w.processCommentIndex(action, userContent) |
| 196 | + action = w.processReposts(action, userContent, signerID, timestamp) |
| 197 | + |
| 198 | + return action |
| 199 | +} |
| 200 | + |
| 201 | +func (w *worker) processHashtags(action *activityx.Action, userContent UserContent) *activityx.Action { |
| 202 | + if hashtagJSON, ok := userContent.Index["hashtag"]; ok { |
| 203 | + var hashtags []HashtagData |
| 204 | + |
| 205 | + if err := json.Unmarshal([]byte(hashtagJSON), &hashtags); err == nil { |
| 206 | + tags := make([]string, 0, len(hashtags)) |
| 207 | + |
| 208 | + for _, hashtag := range hashtags { |
| 209 | + tags = append(tags, hashtag.Key) |
| 210 | + } |
| 211 | + |
| 212 | + if socialPost, ok := action.Metadata.(metadata.SocialPost); ok { |
| 213 | + socialPost.Tags = tags |
| 214 | + action.Metadata = socialPost |
| 215 | + } |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + return action |
| 220 | +} |
| 221 | + |
| 222 | +func (w *worker) processCommentIndex(action *activityx.Action, userContent UserContent) *activityx.Action { |
| 223 | + if indexJSON, ok := userContent.Index["comment"]; ok { |
| 224 | + var indexData IndexData |
| 225 | + if err := json.Unmarshal([]byte(indexJSON), &indexData); err == nil { |
| 226 | + if socialPost, ok := action.Metadata.(metadata.SocialPost); ok { |
| 227 | + socialPost.PublicationID = fmt.Sprintf("%s-%d", indexData.Key.Path, indexData.Key.BlockHeight) |
| 228 | + action.Metadata = socialPost |
| 229 | + } |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + return action |
| 234 | +} |
| 235 | + |
| 236 | +func (w *worker) processReposts(action *activityx.Action, userContent UserContent, signerID string, timestamp uint64) *activityx.Action { |
| 237 | + if repostJSON, ok := userContent.Index["repost"]; ok { |
| 238 | + var repostData RepostIndexData |
| 239 | + if err := json.Unmarshal([]byte(repostJSON), &repostData); err == nil { |
| 240 | + if len(repostData) > 0 { |
| 241 | + action.Type = typex.SocialShare |
| 242 | + |
| 243 | + if repostData[0].Value.Item != nil { |
| 244 | + pathParts := strings.Split(repostData[0].Value.Item.Path, "/") |
| 245 | + if len(pathParts) >= 2 { |
| 246 | + target := &metadata.SocialPost{ |
| 247 | + Handle: pathParts[0], |
| 248 | + PublicationID: fmt.Sprintf("%s-%d", repostData[0].Value.Item.Path, repostData[0].Value.Item.BlockHeight), |
| 249 | + } |
| 250 | + |
| 251 | + action.Metadata = metadata.SocialPost{ |
| 252 | + Handle: signerID, |
| 253 | + Timestamp: timestamp, |
| 254 | + Target: target, |
| 255 | + } |
| 256 | + action.To = target.Handle |
| 257 | + } |
| 258 | + } |
| 259 | + } |
| 260 | + } |
| 261 | + } |
| 262 | + |
| 263 | + return action |
| 264 | +} |
| 265 | + |
| 266 | +// NewWorker returns a new near social worker. |
| 267 | +func NewWorker(config *config.Module) (engine.Worker, error) { |
| 268 | + var instance = worker{ |
| 269 | + config: config, |
| 270 | + } |
| 271 | + |
| 272 | + return &instance, nil |
| 273 | +} |
0 commit comments