From edd8b3e0f762fd3da6c0fe9f2456220251d16454 Mon Sep 17 00:00:00 2001 From: Mark Suckerberg Date: Thu, 3 Aug 2023 19:41:33 -0500 Subject: [PATCH] Adds like, dislike, and notes endpoints --- src/functions/AccessTumblrApi.ts | 2 +- src/functions/Like.ts | 29 +++++++++++++++++++++++++ src/functions/Post.ts | 18 +++++++++++++++- src/functions/index.ts | 1 + src/interfaces/TumblrPost.ts | 12 +++++++++++ tests/Posts.test.ts | 37 ++++++++++++++++++++++++++------ 6 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 src/functions/Like.ts diff --git a/src/functions/AccessTumblrApi.ts b/src/functions/AccessTumblrApi.ts index 444a35b..a79a682 100644 --- a/src/functions/AccessTumblrApi.ts +++ b/src/functions/AccessTumblrApi.ts @@ -40,7 +40,7 @@ export async function accessTumblrAPI( throw new Error( `${response.meta.status}: ${response.meta.msg} - ${JSON.stringify( response.errors - )} (Query URL: ${url})` + )} (Query URL: ${method} ${url} ${method === "GET" ? "" : JSON.stringify(params)})` ); } diff --git a/src/functions/Like.ts b/src/functions/Like.ts new file mode 100644 index 0000000..493c199 --- /dev/null +++ b/src/functions/Like.ts @@ -0,0 +1,29 @@ +import { accessTumblrAPI } from "./AccessTumblrApi"; + +export async function LikePost(token: string, id: number | string, reblogKey: string) { + return ( + ( + await accessTumblrAPI( + token, + "user/like", + { id: id.toString(), reblog_key: reblogKey }, + "POST", + "https://www.tumblr.com/api/v2/" + ) + ).meta.status === 200 + ); +} + +export async function UnlikePost(token: string, id: number | string, reblogKey: string) { + return ( + ( + await accessTumblrAPI( + token, + "user/unlike", + { id: id.toString(), reblog_key: reblogKey }, + "POST", + "https://www.tumblr.com/api/v2/" + ) + ).meta.status === 200 + ); +} diff --git a/src/functions/Post.ts b/src/functions/Post.ts index 0f2b8f6..0c89820 100644 --- a/src/functions/Post.ts +++ b/src/functions/Post.ts @@ -1,4 +1,4 @@ -import { NewPostDetails, TumblrPost } from "../interfaces"; +import { NewPostDetails, TumblrNoteResponse, TumblrPost } from "../interfaces"; import { accessTumblrAPI } from "./AccessTumblrApi"; /** @@ -196,3 +196,19 @@ export async function FetchPosts( ) ).response.posts as PostType[]; } + +export async function GetNotes( + token: string, + blogIdentifier: string, + id: number | string, + beforeTimestamp = Number.MAX_VALUE, + mode: "all" | "likes" | "conversation" | "rollup" | "reblogs_with_tags" = "all" +) { + return ( + await accessTumblrAPI(token, `blog/${blogIdentifier}/notes`, { + id: id.toString(), + mode, + before_timestamp: beforeTimestamp.toString(), + }) + ).response as TumblrNoteResponse; +} diff --git a/src/functions/index.ts b/src/functions/index.ts index e3f0ba4..24fb077 100644 --- a/src/functions/index.ts +++ b/src/functions/index.ts @@ -2,5 +2,6 @@ export * from "./AccessTumblrApi"; export * from "./Block"; export * from "./BlogInfo"; export * from "./Follow"; +export * from "./Like"; export * from "./Post"; export * from "./UserInfo"; diff --git a/src/interfaces/TumblrPost.ts b/src/interfaces/TumblrPost.ts index 3343b6c..dd64c63 100644 --- a/src/interfaces/TumblrPost.ts +++ b/src/interfaces/TumblrPost.ts @@ -1,3 +1,4 @@ +import { TumblrLink } from "./TumblrAPIResponse"; import { TumblrFollowerBlog } from "./TumblrBlog"; import { TumblrBlocksPost } from "./TumblrNeuePost"; @@ -151,6 +152,15 @@ interface TumblrDialogue { phrase: string; } +export interface TumblrNoteResponse { + notes: TumblrNote[]; + rollup_notes?: TumblrNote[]; + total_notes: number; + total_likes?: number; + total_reblogs?: number; + _links: TumblrLink; +} + interface TumblrNote { type: "like" | "reblog"; timestamp: number; @@ -163,4 +173,6 @@ interface TumblrNote { "64": string; "128": string; }; + tags?: string[]; + reblog_parent_blog_name?: string; } diff --git a/tests/Posts.test.ts b/tests/Posts.test.ts index 3fc6894..5cd5279 100644 --- a/tests/Posts.test.ts +++ b/tests/Posts.test.ts @@ -5,17 +5,22 @@ import { FetchPost, FetchPostNeue, FetchPosts, + GetNotes, + LikePost, NewPostDetails, TumblrBlocksPost, + UnlikePost, } from "../src"; const token = process.env.TUMBLR_TOKEN; const consumerID = process.env.CONSUMER_ID; +const testBlog = "typeble-bot"; if (!token) throw new Error("No token provided"); if (!consumerID) throw new Error("No consumer ID provided"); let postID: string | undefined; +let post: TumblrBlocksPost | undefined; it("should post a text post", async () => { const postDetails: NewPostDetails = { @@ -26,7 +31,7 @@ it("should post a text post", async () => { tags: "test,typeble", }; - postID = await CreatePost(token, "typeble-bot", postDetails); + postID = await CreatePost(token, testBlog, postDetails); expect(postID).toBeDefined(); }); @@ -40,13 +45,13 @@ it("should edit the post", async () => { }; if (!postID) throw new Error("No post ID provided"); - const returnedID = await EditPost(token, "typeble-bot", postID, postDetails); + const returnedID = await EditPost(token, testBlog, postID, postDetails); expect(returnedID).toBe(postID); }); it("should fetch the post", async () => { if (!postID) throw new Error("No post ID provided"); - const post = (await FetchPostNeue(token, "typeble-bot", postID)) as TumblrBlocksPost; + post = (await FetchPostNeue(token, testBlog, postID)) as TumblrBlocksPost; expect(post.content).toEqual([ { type: "text", text: "Hello, world!", subtype: "heading1" }, { type: "text", text: "This is a test post. It has been edited!" }, @@ -59,7 +64,7 @@ it("should fetch the post with a consumer ID only", async () => { if (!postID) throw new Error("No post ID provided"); const post = await FetchPost( consumerID, - "typeble-bot", + testBlog, postID, undefined, undefined, @@ -76,13 +81,31 @@ it("should fetch the post with a consumer ID only", async () => { }); it("should fetch the blog's posts", async () => { - const posts = await FetchPosts(token, "typeble-bot"); + const posts = await FetchPosts(token, testBlog); const post = posts.find(post => post.id_string === postID); expect(post).toBeDefined(); }); +it("should like the post", async () => { + if (!post) throw new Error("No post object retrieved"); + expect(await LikePost(token, post.id_string, post.reblog_key)).toBe(true); +}); + +it("should retrieve the notes count", async () => { + if (!post) throw new Error("No post object retrieved"); + const response = await GetNotes(token, testBlog, post.id_string); + expect(response).toBeDefined(); + expect(response.total_notes).toBeGreaterThan(0); + expect(response.notes.find(note => note.blog_name == testBlog)).toBeDefined(); +}); + +it("should unlike the post", async () => { + if (!post) throw new Error("No post object retrieved"); + expect(await UnlikePost(token, post.id_string, post.reblog_key)).toBe(true); +}); + it("should delete the post", async () => { if (!postID) throw new Error("No post ID provided"); - expect(await DeletePost(token, "typeble-bot", postID)).toBe(true); - await expect(FetchPost(token, "typeble-bot", postID)).rejects.toThrow(); + expect(await DeletePost(token, testBlog, postID)).toBe(true); + await expect(FetchPost(token, testBlog, postID)).rejects.toThrow(); });