Coder Social home page Coder Social logo

aldaviva / microblog-favorites Goto Github PK

View Code? Open in Web Editor NEW
1.0 3.0 0.0 96 KB

⭐ Save screenshots of your favorite tweets with metadata, so that you can upload them to a digital photo frame and easily find them later.

License: Apache License 2.0

Java 97.91% CSS 2.09%
twitter twitter-backup twitter-like photo-frame nixplay twitter-favorite bluesky mastodon

microblog-favorites's People

Contributors

aldaviva avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar

microblog-favorites's Issues

Add ability to capture favorites from Mastodon

Like #1 but for Mastodon instead of Bluesky.

Authentication

OAuth with consumer

Scraping from webapp

API requests use an access token that is returned in the HTML source of the webapp.

  1. Load the Mastodon webapp in a browser that is logged in to that instance
  2. Query the element selected by script#initial-state
  3. Parse the element's inner text as a JSON object
  4. Get the JSON string at the path meta.access_token
  5. Pass this access token in the header Authorization: Bearer <access token>

List Favorites API method

GET https://social.vivaldi.net/api/v1/favourites HTTP/1.1
Authorization: Bearer <access token>

This returns a JSON array with zero or more post objects.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[{
    "id": "111195262944327028",
    "created_at": "2023-10-07T18:57:40.000Z",
    "in_reply_to_id": null,
    "in_reply_to_account_id": null,
    "sensitive": false,
    "spoiler_text": "",
    "visibility": "public",
    "language": "en",
    "uri": "https://tooters.org/users/Ashley/statuses/111195262836512667",
    "url": "https://tooters.org/@Ashley/111195262836512667",
    "replies_count": 37,
    "reblogs_count": 163,
    "favourites_count": 3,
    "edited_at": null,
    "favourited": true,
    "reblogged": false,
    "muted": false,
    "bookmarked": false,
    "content": "\u003cp\u003e💯 this would happen to me\u003c/p\u003e",
    "filtered": [],
    "reblog": null,
    "account": {
        "id": "110610741587879546",
        "username": "Ashley",
        "acct": "[email protected]",
        "display_name": "Graveyard smAsh :witches_town:",
        "locked": false,
        "bot": false,
        "discoverable": true,
        "group": false,
        "created_at": "2023-02-03T00:00:00.000Z",
        "note": "\u003cp\u003eShe/her\u003c/p\u003e\u003cp\u003eAlt avatar: white female with light brown hair, blue eyes. But now there’s a pirate filter (eye patch) and red lip\u003c/p\u003e\u003cp\u003eAlt header: Cartoon grey stripe and white cat with mouth open and walking sassy. Text says 'haters gonna hate'.\u003c/p\u003e",
        "url": "https://tooters.org/@Ashley",
        "uri": "https://tooters.org/users/Ashley",
        "avatar": "https://social-cdn.vivaldi.net/system/cache/accounts/avatars/110/610/741/587/879/546/original/69f5b55b5c076d2a.jpeg",
        "avatar_static": "https://social-cdn.vivaldi.net/system/cache/accounts/avatars/110/610/741/587/879/546/original/69f5b55b5c076d2a.jpeg",
        "header": "https://social-cdn.vivaldi.net/system/cache/accounts/headers/110/610/741/587/879/546/original/2c16bc864f193108.jpg",
        "header_static": "https://social-cdn.vivaldi.net/system/cache/accounts/headers/110/610/741/587/879/546/original/2c16bc864f193108.jpg",
        "followers_count": 1038,
        "following_count": 248,
        "statuses_count": 6241,
        "last_status_at": "2023-10-16",
        "emojis": [
            {
                "shortcode": "witches_town",
                "url": "https://social-cdn.vivaldi.net/system/cache/custom_emojis/images/000/275/349/original/6afbe336032b562e.png",
                "static_url": "https://social-cdn.vivaldi.net/system/cache/custom_emojis/images/000/275/349/static/6afbe336032b562e.png",
                "visible_in_picker": true
            }
        ], "fields": [
            {
                "name": "Here For",
                "value": "Attention but not that much. Stopppppp 😏",
                "verified_at": null
            },
            {
                "name": "Feuding with",
                "value": "Pumpkin spice",
                "verified_at": null
            },
            {
                "name": "Mood",
                "value": "The German word for a manic sloth",
                "verified_at": null
            },
            {
                "name": "A verified bitch 💁‍♀️",
                "value": "\u003ca href=\"https://ashley.tooters.org\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" translate=\"no\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003eashley.tooters.org\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e",
                "verified_at": "2023-10-15T03:10:40.148+00:00"
            }
        ]
    },
    "media_attachments": [
        {
            "id": "111195262888539473",
            "type": "image",
            "url": "https://social-cdn.vivaldi.net/system/cache/media_attachments/files/111/195/262/888/539/473/original/f4daf7e7dcb5b544.jpeg",
            "preview_url": "https://social-cdn.vivaldi.net/system/cache/media_attachments/files/111/195/262/888/539/473/small/f4daf7e7dcb5b544.jpeg",
            "remote_url": "https://cdn.masto.host/tootersorg/media_attachments/files/111/195/262/456/475/145/original/14f34d4c1b677756.jpeg",
            "preview_remote_url": null,
            "text_url": null,
            "meta": {
                "original": {
                    "width": 1179,
                    "height": 1340,
                    "size": "1179x1340",
                    "aspect": 0.8798507462686567
                },
                "small": {
                    "width": 450,
                    "height": 511,
                    "size": "450x511",
                    "aspect": 0.8806262230919765
                }
            },
            "description": "'Missing' woman\nmystery solved\nA group of tourists spent\nhours Saturday night looking for\na missing woman near Iceland's\nEldgja canyon, only to find her\namong the search party.\nThe group was travelling\nthrough Iceland on a tour bus\nand stopped near a volcanic\ncanyon. Soon, there was word\nof a missing passenger. The\nwoman, who had changed\nclothes, didn't recognize the\ndescription of herself, and\njoined in the search.\nBut the search was called\noff at about 3 a.m., when it\nbecame clear the missing\nwoman was, in fact, accounted\nfor and searching for herself.",
            "blurhash": "UDFiMq9E019F-;s,o#n$kWt7t7oL%1X9xZW="
        }
    ], "mentions": [],
    "tags": [],
    "emojis": [],
    "card": null,
    "poll": null
}]

Rendering favorite

URL

The web page URL for a post is of the form

https://social.vivaldi.net/<userid>/<postid>

The userid is the fully-qualified username from account.acct with an @ prepended, e.g. @[email protected]. The postid is the value of the id key.

DOM

If you navigate to the post URL in a browser without a session on the Mastodon server (anonymous access), it will redirect to the post on the author's server, e.g. https://tooters.org/@Ashley/111195262836512667.

If you navigate to the post URL in a browser with a session on the Mastodon server, it will render the post itself, which makes the DOM uniform instead of being at the mercy of whatever server the author uses (does not have to be Mastodon at all). Therefore, being logged in is better for finding the correct element in the page.

The post is rendered in an element selected by div.detailed-status.

Add ability to capture favorites from Bluesky

This could necessitate renaming the entire project, but getting favorites from other microblogging social networks is probably a good idea.

Authentication

  1. Create an app password in your account (Settings > Advanced > App Passwords).
  2. Create an access token using the normal authentication API method:
    POST https://bsky.social/xrpc/com.atproto.server.createSession HTTP/1.1
    Content-Type: application/json
    
    { "identifier": "ben.aldaviva.com", "password": "<app password>" }
    HTTP/1.1 200 OK
    Content-Type: application/json; charset=utf-8
    
    {
        "did": "did:plc:gjvi5iv6wugjwpencavy5jge",
        "handle": "ben.aldaviva.com",
        "email": "<email address>",
        "emailConfirmed": true,
        "accessJwt": "<access token>",
        "refreshJwt": "<refresh token>"
    }
  3. The accessJwt is used for API requests. The refreshJwt is presumably used to make a new accessJwt when the old one expires, but the expiration duration is not returned and I don't know how to exchange the refresh token for a new access token.

Refreshing an existing access token

Do this when a request returns an ExpiredToken error in the response JSON object's error string value.

POST https://bsky.social/xrpc/com.atproto.server.refreshSession HTTP/1.1
Authorization: Bearer <refresh token>

If the refresh response is the ExpiredToken or InvalidToken error, then fail. Otherwise, if the response contains new session data, use that.

Example InvalidToken error response

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8

{"error":"InvalidToken","message":"Token could not be verified"}

Logging out

The webapp does not seem to send any request to the server when logging out, except a tracking request. This means the access token may still be valid on the server.

You can log out by sending a com.atproto.server.deleteSession POST request with no arguments (besides the access token in the Authorization header).

List Favorites API method

The maximum value for limit is 100. Subsequent paginated requests will also have the cursor query parameter with a value returned by the previous response.

GET https://bsky.social/xrpc/app.bsky.feed.getActorLikes?actor=ben.aldaviva.com&limit=30 HTTP/1.1
Authorization: Bearer <access token>

The response feed array can contain zero or more post objects.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
    "feed": [
        {
            "post": {
                "uri": "at://did:plc:dx4w3js5aiqnutix2lxl6xwu/app.bsky.feed.post/3kbql5amsda2c",
                "cid": "bafyreic6iy3tq3bchs5m2bp6ju7eckj6ydextxgyxncaa7iymshaqbfkby",
                "author": {
                    "did": "did:plc:dx4w3js5aiqnutix2lxl6xwu",
                    "handle": "martychan.bsky.social",
                    "displayName": "Marty Chan",
                    "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:dx4w3js5aiqnutix2lxl6xwu/bafkreiggxprmnenazp7aa3jjmzvlgvygxw32swrzepnmjeqojjjqru644a@jpeg",
                    "viewer": {
                        "muted": false,
                        "blockedBy": false
                    },
                    "labels": []
                },
                "record": {
                    "text": "What's a #Caturday without a snuggle from my favourite feline? This is Hugo. He loves to head butt me even when he's in my arms.",
                    "$type": "app.bsky.feed.post",
                    "embed": {
                        "$type": "app.bsky.embed.images",
                        "images": [
                            {
                                "alt": "Chinese man in grey t-shirt and plaid shorts cradling a black and white tuxedo cat like a baby in his arms. The cat is staring at the camera with wide-eyed shock, while the man is smiling.",
                                "image": {
                                    "$type": "blob",
                                    "ref": {
                                        "$link": "bafkreife2tsrmrykealkd4ysldjivqcvshsepapdusj6jaugqz2s7otrb4"
                                    },
                                    "mimeType": "image/jpeg",
                                    "size": 760857
                                },
                                "aspectRatio": {
                                    "width": 1500,
                                    "height": 2000
                                }
                            }
                        ]
                    },
                    "langs": ["en"],
                    "facets": [
                        {
                            "index": {
                                "byteEnd": 18,
                                "byteStart": 9
                            },
                            "features": [
                                {
                                    "tag": "Caturday",
                                    "$type": "app.bsky.richtext.facet#tag"
                                }
                            ]
                        }
                    ], "createdAt": "2023-10-14T21:58:36.796Z"
                },
                "embed": {
                    "$type": "app.bsky.embed.images#view",
                    "images": [
                        {
                            "thumb": "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:dx4w3js5aiqnutix2lxl6xwu/bafkreife2tsrmrykealkd4ysldjivqcvshsepapdusj6jaugqz2s7otrb4@jpeg",
                            "fullsize": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:dx4w3js5aiqnutix2lxl6xwu/bafkreife2tsrmrykealkd4ysldjivqcvshsepapdusj6jaugqz2s7otrb4@jpeg",
                            "alt": "Chinese man in grey t-shirt and plaid shorts cradling a black and white tuxedo cat like a baby in his arms. The cat is staring at the camera with wide-eyed shock, while the man is smiling.",
                            "aspectRatio": {
                                "width": 1500,
                                "height": 2000
                            }
                        }
                    ]
                },
                "replyCount": 0,
                "repostCount": 0,
                "likeCount": 21,
                "indexedAt": "2023-10-14T21:58:36.796Z",
                "viewer": {
                    "like": "at://did:plc:gjvi5iv6wugjwpencavy5jge/app.bsky.feed.like/3kbqogwgu4n2g"
                },
                "labels": []
            }
        }
    ], "cursor": "1696157002171::bafyreiheirpscidqeclkxyxpuqoaybxt3me54hsmsl36p3yhuv6aics5dm"
}

Rendering favorite

URL

The web page URL for a post is of the form

https://bsky.app/profile/<userid>/post/<postid>

The userid can be a handle (like martychan.bsky.social, from author.handle) or DID (did:plc:dx4w3js5aiqnutix2lxl6xwu from author.did). The postid is the last path segment in uri (3kbql5amsda2c).

For the above example, the post URL is https://bsky.app/profile/martychan.bsky.social/post/3kbql5amsda2c.

DOM

When you navigate to the post URL in a browser which is logged into a Bluesky account (no anonymous access), it will render the post in an element selected by

div[data-testid ^= 'postThreadItem-by-']

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.