or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

blog-content.mdclient-configuration.mdcontent-discovery.mdhttp-requests.mdindex.mdpost-management.mdsocial-interactions.mduser-management.md
tile.json

social-interactions.mddocs/

Social Interactions

Like, unlike, follow, and unfollow functionality for social engagement on the Tumblr platform.

Capabilities

Like Post

Like a specific post as the authenticated user.

/**
 * Like a post as the authenticating user
 * @param postId - ID of the post to like
 * @param reblogKey - Reblog key of the post to like
 * @returns Promise resolving to like confirmation
 */
likePost(postId: string, reblogKey: string): Promise<any>;

Usage Examples:

// Like a post (requires post ID and reblog key from post data)
await client.likePost('12345', 'abc123def456');
console.log('Post liked successfully');

// Get post data first, then like it
const posts = await client.blogPosts('some-blog');
const firstPost = posts.posts[0];
await client.likePost(firstPost.id, firstPost.reblog_key);

Unlike Post

Remove a like from a post as the authenticated user.

/**
 * Unlike a post as the authenticating user
 * @param postId - ID of the post to unlike
 * @param reblogKey - Reblog key of the post to unlike
 * @returns Promise resolving to unlike confirmation
 */
unlikePost(postId: string, reblogKey: string): Promise<any>;

Usage Examples:

// Unlike a previously liked post
await client.unlikePost('12345', 'abc123def456');
console.log('Post unliked successfully');

// Unlike posts from your likes list
const likes = await client.userLikes({ limit: 5 });
for (const post of likes.liked_posts) {
  await client.unlikePost(post.id, post.reblog_key);
  console.log(`Unliked post ${post.id}`);
}

Follow Blog

Follow a blog as the authenticated user.

/**
 * Follow a blog as the authenticating user
 * @param params - Blog identification parameters
 * @returns Promise resolving to follow confirmation
 */
followBlog(params: { url: string } | { email: string }): Promise<any>;

Usage Examples:

// Follow a blog by URL
await client.followBlog({ url: 'cool-blog.tumblr.com' });
console.log('Blog followed successfully');

// Follow a blog by full URL
await client.followBlog({ url: 'https://cool-blog.tumblr.com/' });

// Follow a blog by email (if supported)
await client.followBlog({ email: 'blogger@example.com' });

// Follow multiple blogs
const blogsToFollow = [
  'photography-blog.tumblr.com',
  'art-blog.tumblr.com',
  'music-blog.tumblr.com'
];

for (const blogUrl of blogsToFollow) {
  try {
    await client.followBlog({ url: blogUrl });
    console.log(`Followed ${blogUrl}`);
  } catch (error) {
    console.error(`Failed to follow ${blogUrl}:`, error.message);
  }
}

Unfollow Blog

Unfollow a blog as the authenticated user.

/**
 * Unfollow a blog as the authenticating user
 * @param params - Blog URL to unfollow
 * @returns Promise resolving to unfollow confirmation
 */
unfollowBlog(params: { url: string }): Promise<any>;

Usage Examples:

// Unfollow a blog by URL
await client.unfollowBlog({ url: 'some-blog.tumblr.com' });
console.log('Blog unfollowed successfully');

// Unfollow blogs from your following list
const following = await client.userFollowing({ limit: 10 });
for (const blog of following.blogs) {
  if (blog.name.includes('spam')) {
    await client.unfollowBlog({ url: blog.url });
    console.log(`Unfollowed ${blog.name}`);
  }
}

Parameter Types

Follow Blog Parameters

type FollowBlogParams = { url: string } | { email: string };

interface FollowByUrl {
  /** Blog URL to follow (can be short form or full URL) */
  url: string;
}

interface FollowByEmail {
  /** Email address associated with the blog */
  email: string;
}

Unfollow Blog Parameters

interface UnfollowBlogParams {
  /** Blog URL to unfollow */
  url: string;
}

Getting Required Information

Obtaining Post ID and Reblog Key

// Get post information from blog posts
const posts = await client.blogPosts('target-blog');
const targetPost = posts.posts[0];

console.log('Post ID:', targetPost.id);
console.log('Reblog Key:', targetPost.reblog_key);

// Now you can like/unlike the post
await client.likePost(targetPost.id, targetPost.reblog_key);

Getting Post Information from Dashboard

// Get posts from dashboard with like/reblog capabilities
const dashboard = await client.userDashboard({
  reblog_info: true,
  notes_info: true
});

dashboard.posts.forEach(async (post) => {
  if (post.can_like && !post.liked) {
    console.log(`Can like post ${post.id} by ${post.blog_name}`);
    // await client.likePost(post.id, post.reblog_key);
  }
});

Finding Blogs to Follow

// Discover blogs through tagged posts
const taggedPosts = await client.taggedPosts('photography');
const uniqueBlogs = [...new Set(taggedPosts.response.map(post => post.blog_name))];

console.log('Blogs posting about photography:');
uniqueBlogs.forEach(blogName => {
  console.log(`- ${blogName}.tumblr.com`);
});

// Follow interesting blogs
for (const blogName of uniqueBlogs.slice(0, 5)) {
  await client.followBlog({ url: `${blogName}.tumblr.com` });
}

Bulk Operations

Bulk Like Posts

async function likeAllPostsFromBlog(blogName, maxPosts = 20) {
  const posts = await client.blogPosts(blogName, { limit: maxPosts });
  
  for (const post of posts.posts) {
    try {
      await client.likePost(post.id, post.reblog_key);
      console.log(`Liked post ${post.id} from ${post.blog_name}`);
      
      // Add delay to avoid rate limiting
      await new Promise(resolve => setTimeout(resolve, 1000));
    } catch (error) {
      console.error(`Failed to like post ${post.id}:`, error.message);
    }
  }
}

Clean Up Following List

async function unfollowInactiveBlogs(daysSinceUpdate = 365) {
  const cutoffTime = Date.now() / 1000 - (daysSinceUpdate * 24 * 60 * 60);
  const following = await client.userFollowing();
  
  for (const blog of following.blogs) {
    if (blog.updated < cutoffTime) {
      try {
        await client.unfollowBlog({ url: blog.url });
        console.log(`Unfollowed inactive blog: ${blog.name}`);
        
        // Add delay to avoid rate limiting
        await new Promise(resolve => setTimeout(resolve, 500));
      } catch (error) {
        console.error(`Failed to unfollow ${blog.name}:`, error.message);
      }
    }
  }
}

Like Posts from Dashboard

async function likeInterestingDashboardPosts(keywords = []) {
  const dashboard = await client.userDashboard({ limit: 20 });
  
  for (const post of dashboard.posts) {
    if (!post.liked && post.can_like) {
      // Check if post contains interesting keywords
      const postText = (post.title || '') + ' ' + (post.body || '') + ' ' + (post.tags || []).join(' ');
      const hasKeyword = keywords.some(keyword => 
        postText.toLowerCase().includes(keyword.toLowerCase())
      );
      
      if (hasKeyword) {
        try {
          await client.likePost(post.id, post.reblog_key);
          console.log(`Liked post about "${keywords.find(k => postText.toLowerCase().includes(k.toLowerCase()))}"`);
        } catch (error) {
          console.error(`Failed to like post ${post.id}:`, error.message);
        }
      }
    }
  }
}

// Usage
await likeInterestingDashboardPosts(['photography', 'art', 'design']);

Authentication Requirements

All social interaction methods require OAuth authentication:

// Must have full OAuth credentials
const client = tumblr.createClient({
  consumer_key: 'your-consumer-key',
  consumer_secret: 'your-consumer-secret',
  token: 'user-oauth-token', 
  token_secret: 'user-oauth-token-secret'
});

// These will fail without authentication
try {
  await client.likePost('12345', 'abc123');
  await client.followBlog({ url: 'some-blog.tumblr.com' });
} catch (error) {
  if (error.message.includes('401')) {
    console.error('OAuth authentication required');
  }
}

Rate Limiting and Best Practices

Respect Rate Limits

async function likePostsWithDelay(posts, delayMs = 1000) {
  for (const post of posts) {
    try {
      await client.likePost(post.id, post.reblog_key);
      console.log(`Liked post ${post.id}`);
      
      // Wait between requests to avoid rate limiting
      await new Promise(resolve => setTimeout(resolve, delayMs));
    } catch (error) {
      if (error.message.includes('429')) {
        console.log('Rate limit hit, waiting 60 seconds...');
        await new Promise(resolve => setTimeout(resolve, 60000));
        // Retry the request
        await client.likePost(post.id, post.reblog_key);
      } else {
        console.error(`Error liking post:`, error.message);
      }
    }
  }
}

Error Handling

async function safelyFollowBlog(blogUrl) {
  try {
    await client.followBlog({ url: blogUrl });
    return { success: true, message: `Followed ${blogUrl}` };
  } catch (error) {
    if (error.message.includes('403')) {
      return { success: false, message: 'Blog does not allow follows or is private' };
    } else if (error.message.includes('404')) {
      return { success: false, message: 'Blog not found' };
    } else if (error.message.includes('429')) {
      return { success: false, message: 'Rate limit exceeded, try again later' };
    } else {
      return { success: false, message: `Unknown error: ${error.message}` };
    }
  }
}

// Usage
const result = await safelyFollowBlog('example-blog.tumblr.com');
console.log(result.message);

Check Permissions Before Acting

async function smartLikePosts(blogName) {
  const posts = await client.blogPosts(blogName, {
    reblog_info: true,
    notes_info: true
  });
  
  for (const post of posts.posts) {
    // Check if we can and should like this post
    if (post.can_like && !post.liked) {
      try {
        await client.likePost(post.id, post.reblog_key);
        console.log(`Liked ${post.type} post: ${post.summary || post.title || post.id}`);
      } catch (error) {
        console.error(`Cannot like post ${post.id}:`, error.message);
      }
    } else if (post.liked) {
      console.log(`Already liked post ${post.id}`);
    } else {
      console.log(`Cannot like post ${post.id} (permissions)`);
    }
  }
}