diff options
| author | 2025-03-08 22:29:50 +0100 | |
|---|---|---|
| committer | 2025-03-08 22:29:50 +0100 | |
| commit | 398b733b2c0ff4c96f4de9731a864373842e86d6 (patch) | |
| tree | e2a381ee39d7139cf13c953c5ddad8e59bf8163f /activitypub.c | |
| parent | Merge branch 'master' into curl-smtp (diff) | |
| parent | Updated RELEASE_NOTES. (diff) | |
| download | penes-snac2-398b733b2c0ff4c96f4de9731a864373842e86d6.tar.gz penes-snac2-398b733b2c0ff4c96f4de9731a864373842e86d6.tar.xz penes-snac2-398b733b2c0ff4c96f4de9731a864373842e86d6.zip | |
Merge remote-tracking branch 'upstream/master' into curl-smtp
Diffstat (limited to 'activitypub.c')
| -rw-r--r-- | activitypub.c | 220 |
1 files changed, 126 insertions, 94 deletions
diff --git a/activitypub.c b/activitypub.c index a209abd..aa679a0 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -329,6 +329,52 @@ xs_list *get_attachments(const xs_dict *msg) | |||
| 329 | } | 329 | } |
| 330 | 330 | ||
| 331 | 331 | ||
| 332 | int hashtag_in_msg(const xs_list *hashtags, const xs_dict *msg) | ||
| 333 | /* returns 1 if the message contains any of the list of hashtags */ | ||
| 334 | { | ||
| 335 | if (xs_is_list(hashtags) && xs_is_dict(msg)) { | ||
| 336 | const xs_list *tags_in_msg = xs_dict_get(msg, "tag"); | ||
| 337 | |||
| 338 | if (xs_is_list(tags_in_msg)) { | ||
| 339 | const xs_dict *te; | ||
| 340 | |||
| 341 | /* iterate the tags in the message */ | ||
| 342 | xs_list_foreach(tags_in_msg, te) { | ||
| 343 | if (xs_is_dict(te)) { | ||
| 344 | const char *type = xs_dict_get(te, "type"); | ||
| 345 | const char *name = xs_dict_get(te, "name"); | ||
| 346 | |||
| 347 | if (xs_is_string(type) && xs_is_string(name)) { | ||
| 348 | if (strcmp(type, "Hashtag") == 0) { | ||
| 349 | xs *lc_name = xs_utf8_to_lower(name); | ||
| 350 | |||
| 351 | if (xs_list_in(hashtags, lc_name) != -1) | ||
| 352 | return 1; | ||
| 353 | } | ||
| 354 | } | ||
| 355 | } | ||
| 356 | } | ||
| 357 | } | ||
| 358 | } | ||
| 359 | |||
| 360 | return 0; | ||
| 361 | } | ||
| 362 | |||
| 363 | |||
| 364 | int followed_hashtag_check(snac *user, const xs_dict *msg) | ||
| 365 | /* returns 1 if this message contains a hashtag followed by me */ | ||
| 366 | { | ||
| 367 | return hashtag_in_msg(xs_dict_get(user->config, "followed_hashtags"), msg); | ||
| 368 | } | ||
| 369 | |||
| 370 | |||
| 371 | int blocked_hashtag_check(snac *user, const xs_dict *msg) | ||
| 372 | /* returns 1 if this message contains a hashtag blocked by me */ | ||
| 373 | { | ||
| 374 | return hashtag_in_msg(xs_dict_get(user->config, "blocked_hashtags"), msg); | ||
| 375 | } | ||
| 376 | |||
| 377 | |||
| 332 | int timeline_request(snac *snac, const char **id, xs_str **wrk, int level) | 378 | int timeline_request(snac *snac, const char **id, xs_str **wrk, int level) |
| 333 | /* ensures that an entry and its ancestors are in the timeline */ | 379 | /* ensures that an entry and its ancestors are in the timeline */ |
| 334 | { | 380 | { |
| @@ -344,68 +390,71 @@ int timeline_request(snac *snac, const char **id, xs_str **wrk, int level) | |||
| 344 | } | 390 | } |
| 345 | 391 | ||
| 346 | /* is the object already there? */ | 392 | /* is the object already there? */ |
| 347 | if (!valid_status(object_get(*id, &msg))) { | 393 | if (!valid_status((status = object_get(*id, &msg)))) { |
| 348 | /* no; download it */ | 394 | /* no; download it */ |
| 349 | status = activitypub_request(snac, *id, &msg); | 395 | status = activitypub_request(snac, *id, &msg); |
| 396 | } | ||
| 350 | 397 | ||
| 351 | if (valid_status(status)) { | 398 | if (valid_status(status)) { |
| 352 | const xs_dict *object = msg; | 399 | const xs_dict *object = msg; |
| 353 | const char *type = xs_dict_get(object, "type"); | 400 | const char *type = xs_dict_get(object, "type"); |
| 354 | 401 | ||
| 355 | /* get the id again from the object, as it may be different */ | 402 | /* get the id again from the object, as it may be different */ |
| 356 | const char *nid = xs_dict_get(object, "id"); | 403 | const char *nid = xs_dict_get(object, "id"); |
| 357 | 404 | ||
| 358 | if (xs_type(nid) != XSTYPE_STRING) | 405 | if (xs_type(nid) != XSTYPE_STRING) |
| 359 | return 0; | 406 | return 0; |
| 360 | 407 | ||
| 361 | if (wrk && strcmp(nid, *id) != 0) { | 408 | if (wrk && strcmp(nid, *id) != 0) { |
| 362 | snac_debug(snac, 1, | 409 | snac_debug(snac, 1, |
| 363 | xs_fmt("timeline_request canonical id for %s is %s", *id, nid)); | 410 | xs_fmt("timeline_request canonical id for %s is %s", *id, nid)); |
| 364 | 411 | ||
| 365 | *wrk = xs_dup(nid); | 412 | *wrk = xs_dup(nid); |
| 366 | *id = *wrk; | 413 | *id = *wrk; |
| 367 | } | 414 | } |
| 368 | 415 | ||
| 369 | if (xs_is_null(type)) | 416 | if (xs_is_null(type)) |
| 370 | type = "(null)"; | 417 | type = "(null)"; |
| 371 | 418 | ||
| 372 | srv_debug(2, xs_fmt("timeline_request type %s '%s'", nid, type)); | 419 | srv_debug(2, xs_fmt("timeline_request type %s '%s'", nid, type)); |
| 373 | 420 | ||
| 374 | if (strcmp(type, "Create") == 0) { | 421 | if (strcmp(type, "Create") == 0) { |
| 375 | /* some software like lemmy nest Announce + Create + Note */ | 422 | /* some software like lemmy nest Announce + Create + Note */ |
| 376 | if (!xs_is_null(object = xs_dict_get(object, "object"))) { | 423 | if (!xs_is_null(object = xs_dict_get(object, "object"))) { |
| 377 | type = xs_dict_get(object, "type"); | 424 | type = xs_dict_get(object, "type"); |
| 378 | nid = xs_dict_get(object, "id"); | 425 | nid = xs_dict_get(object, "id"); |
| 379 | } | ||
| 380 | else | ||
| 381 | type = "(null)"; | ||
| 382 | } | 426 | } |
| 427 | else | ||
| 428 | type = "(null)"; | ||
| 429 | } | ||
| 383 | 430 | ||
| 384 | if (xs_match(type, POSTLIKE_OBJECT_TYPE)) { | 431 | if (xs_match(type, POSTLIKE_OBJECT_TYPE)) { |
| 385 | if (content_match("filter_reject.txt", object)) | 432 | if (content_match("filter_reject.txt", object)) |
| 386 | snac_log(snac, xs_fmt("timeline_request rejected by content %s", nid)); | 433 | snac_log(snac, xs_fmt("timeline_request rejected by content %s", nid)); |
| 387 | else { | 434 | else |
| 388 | const char *actor = get_atto(object); | 435 | if (blocked_hashtag_check(snac, object)) |
| 389 | 436 | snac_log(snac, xs_fmt("timeline_request rejected by hashtag %s", nid)); | |
| 390 | if (!xs_is_null(actor)) { | 437 | else { |
| 391 | /* request (and drop) the actor for this entry */ | 438 | const char *actor = get_atto(object); |
| 392 | if (!valid_status(actor_request(snac, actor, NULL))) { | ||
| 393 | /* failed? retry later */ | ||
| 394 | enqueue_actor_refresh(snac, actor, 60); | ||
| 395 | } | ||
| 396 | 439 | ||
| 397 | /* does it have an ancestor? */ | 440 | if (!xs_is_null(actor)) { |
| 398 | const char *in_reply_to = get_in_reply_to(object); | 441 | /* request (and drop) the actor for this entry */ |
| 442 | if (!valid_status(actor_request(snac, actor, NULL))) { | ||
| 443 | /* failed? retry later */ | ||
| 444 | enqueue_actor_refresh(snac, actor, 60); | ||
| 445 | } | ||
| 399 | 446 | ||
| 400 | /* store */ | 447 | /* does it have an ancestor? */ |
| 401 | timeline_add(snac, nid, object); | 448 | const char *in_reply_to = get_in_reply_to(object); |
| 402 | 449 | ||
| 403 | /* redistribute to lists for this user */ | 450 | /* store */ |
| 404 | list_distribute(snac, actor, object); | 451 | timeline_add(snac, nid, object); |
| 405 | 452 | ||
| 406 | /* recurse! */ | 453 | /* redistribute to lists for this user */ |
| 407 | timeline_request(snac, &in_reply_to, NULL, level + 1); | 454 | list_distribute(snac, actor, object); |
| 408 | } | 455 | |
| 456 | /* recurse! */ | ||
| 457 | timeline_request(snac, &in_reply_to, NULL, level + 1); | ||
| 409 | } | 458 | } |
| 410 | } | 459 | } |
| 411 | } | 460 | } |
| @@ -587,40 +636,6 @@ int is_msg_from_private_user(const xs_dict *msg) | |||
| 587 | } | 636 | } |
| 588 | 637 | ||
| 589 | 638 | ||
| 590 | int followed_hashtag_check(snac *user, const xs_dict *msg) | ||
| 591 | /* returns true if this message contains a hashtag followed by me */ | ||
| 592 | { | ||
| 593 | const xs_list *fw_tags = xs_dict_get(user->config, "followed_hashtags"); | ||
| 594 | |||
| 595 | if (xs_is_list(fw_tags)) { | ||
| 596 | const xs_list *tags_in_msg = xs_dict_get(msg, "tag"); | ||
| 597 | |||
| 598 | if (xs_is_list(tags_in_msg)) { | ||
| 599 | const xs_dict *te; | ||
| 600 | |||
| 601 | /* iterate the tags in the message */ | ||
| 602 | xs_list_foreach(tags_in_msg, te) { | ||
| 603 | if (xs_is_dict(te)) { | ||
| 604 | const char *type = xs_dict_get(te, "type"); | ||
| 605 | const char *name = xs_dict_get(te, "name"); | ||
| 606 | |||
| 607 | if (xs_is_string(type) && xs_is_string(name)) { | ||
| 608 | if (strcmp(type, "Hashtag") == 0) { | ||
| 609 | xs *lc_name = xs_utf8_to_lower(name); | ||
| 610 | |||
| 611 | if (xs_list_in(fw_tags, lc_name) != -1) | ||
| 612 | return 1; | ||
| 613 | } | ||
| 614 | } | ||
| 615 | } | ||
| 616 | } | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | return 0; | ||
| 621 | } | ||
| 622 | |||
| 623 | |||
| 624 | void followed_hashtag_distribute(const xs_dict *msg) | 639 | void followed_hashtag_distribute(const xs_dict *msg) |
| 625 | /* distribute this post to all users following the included hashtags */ | 640 | /* distribute this post to all users following the included hashtags */ |
| 626 | { | 641 | { |
| @@ -665,31 +680,36 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 665 | 680 | ||
| 666 | if (xs_match(type, "Like|Announce|EmojiReact")) { | 681 | if (xs_match(type, "Like|Announce|EmojiReact")) { |
| 667 | const char *object = xs_dict_get(c_msg, "object"); | 682 | const char *object = xs_dict_get(c_msg, "object"); |
| 683 | xs *obj = NULL; | ||
| 668 | 684 | ||
| 669 | if (xs_is_dict(object)) | 685 | if (xs_is_dict(object)) { |
| 686 | obj = xs_dup(object); | ||
| 670 | object = xs_dict_get(object, "id"); | 687 | object = xs_dict_get(object, "id"); |
| 688 | } | ||
| 671 | 689 | ||
| 672 | /* bad object id? reject */ | 690 | /* bad object id? reject */ |
| 673 | if (!xs_is_string(object)) | 691 | if (!xs_is_string(object)) |
| 674 | return 0; | 692 | return 0; |
| 675 | 693 | ||
| 694 | /* try to get the object */ | ||
| 695 | if (!xs_is_dict(obj)) | ||
| 696 | object_get(object, &obj); | ||
| 697 | |||
| 676 | /* if it's about one of our posts, accept it */ | 698 | /* if it's about one of our posts, accept it */ |
| 677 | if (xs_startswith(object, snac->actor)) | 699 | if (xs_startswith(object, snac->actor)) |
| 678 | return 2; | 700 | return 2; |
| 679 | 701 | ||
| 702 | /* blocked by hashtag? */ | ||
| 703 | if (blocked_hashtag_check(snac, obj)) | ||
| 704 | return 0; | ||
| 705 | |||
| 680 | /* if it's by someone we follow, accept it */ | 706 | /* if it's by someone we follow, accept it */ |
| 681 | if (following_check(snac, actor)) | 707 | if (following_check(snac, actor)) |
| 682 | return 1; | 708 | return 1; |
| 683 | 709 | ||
| 684 | /* do we follow any hashtag? */ | 710 | /* do we follow any hashtag? */ |
| 685 | if (xs_is_list(xs_dict_get(snac->config, "followed_hashtags"))) { | 711 | if (followed_hashtag_check(snac, obj)) |
| 686 | xs *obj = NULL; | 712 | return 7; |
| 687 | |||
| 688 | /* if the admired object contains any followed hashtag, accept it */ | ||
| 689 | if (valid_status(object_get(object, &obj)) && | ||
| 690 | followed_hashtag_check(snac, obj)) | ||
| 691 | return 7; | ||
| 692 | } | ||
| 693 | 713 | ||
| 694 | return 0; | 714 | return 0; |
| 695 | } | 715 | } |
| @@ -721,13 +741,20 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 721 | return 1; | 741 | return 1; |
| 722 | } | 742 | } |
| 723 | 743 | ||
| 744 | const xs_dict *msg = xs_dict_get(c_msg, "object"); | ||
| 745 | |||
| 746 | /* any blocked hashtag? reject */ | ||
| 747 | if (blocked_hashtag_check(snac, msg)) { | ||
| 748 | snac_debug(snac, 1, xs_fmt("blocked by hashtag %s", xs_dict_get(msg, "id"))); | ||
| 749 | return 0; | ||
| 750 | } | ||
| 751 | |||
| 724 | int pub_msg = is_msg_public(c_msg); | 752 | int pub_msg = is_msg_public(c_msg); |
| 725 | 753 | ||
| 726 | /* if this message is public and we follow the actor of this post, allow */ | 754 | /* if this message is public and we follow the actor of this post, allow */ |
| 727 | if (pub_msg && following_check(snac, actor)) | 755 | if (pub_msg && following_check(snac, actor)) |
| 728 | return 1; | 756 | return 1; |
| 729 | 757 | ||
| 730 | const xs_dict *msg = xs_dict_get(c_msg, "object"); | ||
| 731 | xs *rcpts = recipient_list(snac, msg, 0); | 758 | xs *rcpts = recipient_list(snac, msg, 0); |
| 732 | xs_list *p = rcpts; | 759 | xs_list *p = rcpts; |
| 733 | const xs_str *v; | 760 | const xs_str *v; |
| @@ -1531,7 +1558,7 @@ xs_dict *msg_follow(snac *snac, const char *q) | |||
| 1531 | 1558 | ||
| 1532 | xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | 1559 | xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, |
| 1533 | const xs_str *in_reply_to, const xs_list *attach, | 1560 | const xs_str *in_reply_to, const xs_list *attach, |
| 1534 | int scope, const char *lang_str) | 1561 | int scope, const char *lang_str, const char *msg_date) |
| 1535 | /* creates a 'Note' message */ | 1562 | /* creates a 'Note' message */ |
| 1536 | /* scope: 0, public; 1, private (mentioned only); 2, "quiet public"; 3, followers only */ | 1563 | /* scope: 0, public; 1, private (mentioned only); 2, "quiet public"; 3, followers only */ |
| 1537 | { | 1564 | { |
| @@ -1545,7 +1572,12 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1545 | xs *irt = NULL; | 1572 | xs *irt = NULL; |
| 1546 | xs *tag = xs_list_new(); | 1573 | xs *tag = xs_list_new(); |
| 1547 | xs *atls = xs_list_new(); | 1574 | xs *atls = xs_list_new(); |
| 1548 | xs_dict *msg = msg_base(snac, "Note", id, NULL, "@now", NULL); | 1575 | |
| 1576 | /* discard non-parseable dates */ | ||
| 1577 | if (!xs_is_string(msg_date) || xs_parse_iso_date(msg_date, 0) == 0) | ||
| 1578 | msg_date = NULL; | ||
| 1579 | |||
| 1580 | xs_dict *msg = msg_base(snac, "Note", id, NULL, xs_or(msg_date, "@now"), NULL); | ||
| 1549 | xs_list *p; | 1581 | xs_list *p; |
| 1550 | const xs_val *v; | 1582 | const xs_val *v; |
| 1551 | 1583 | ||
| @@ -1758,7 +1790,7 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach, | |||
| 1758 | const xs_list *opts, int multiple, int end_secs) | 1790 | const xs_list *opts, int multiple, int end_secs) |
| 1759 | /* creates a Question message */ | 1791 | /* creates a Question message */ |
| 1760 | { | 1792 | { |
| 1761 | xs_dict *msg = msg_note(user, content, NULL, NULL, attach, 0, NULL); | 1793 | xs_dict *msg = msg_note(user, content, NULL, NULL, attach, 0, NULL, NULL); |
| 1762 | int max = 8; | 1794 | int max = 8; |
| 1763 | xs_set seen; | 1795 | xs_set seen; |
| 1764 | 1796 | ||
| @@ -2336,7 +2368,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2336 | xs *this_relay = xs_fmt("%s/relay", srv_baseurl); | 2368 | xs *this_relay = xs_fmt("%s/relay", srv_baseurl); |
| 2337 | 2369 | ||
| 2338 | if (strcmp(actor, this_relay) != 0) { | 2370 | if (strcmp(actor, this_relay) != 0) { |
| 2339 | if (timeline_admire(snac, object, actor, 0) == HTTP_STATUS_CREATED) | 2371 | if (valid_status(timeline_admire(snac, object, actor, 0))) |
| 2340 | snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); | 2372 | snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); |
| 2341 | else | 2373 | else |
| 2342 | snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", | 2374 | snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", |