aldaviva / microblog-favorites Goto Github PK
View Code? Open in Web Editor NEW⭐ 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
⭐ 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
Like #1 but for Mastodon instead of Bluesky.
API requests use an access token that is returned in the HTML source of the webapp.
script#initial-state
meta.access_token
Authorization: Bearer <access token>
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
}]
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.
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
.
This could necessitate renaming the entire project, but getting favorites from other microblogging social networks is probably a good idea.
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>"
}
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.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.
InvalidToken
error responseHTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{"error":"InvalidToken","message":"Token could not be verified"}
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).
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"
}
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.
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-']
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.