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

content-discovery.mddocs/

Content Discovery

Search and discover content through tags, public feeds, and explore blog social connections like likes and followers.

Capabilities

Tagged Posts

Get posts from across Tumblr that are tagged with specific tags.

/**
 * Get posts tagged with the specified tag
 * @param tag - The tag on the posts you'd like to retrieve
 * @param params - Additional filtering and pagination parameters
 * @returns Promise resolving to tagged posts from across Tumblr
 */
taggedPosts(tag: string, params?: TaggedPostsParams): Promise<any>;

Usage Examples:

// Get recent posts with a specific tag
const catPosts = await client.taggedPosts('cats');
console.log(`Found ${catPosts.response.length} cat posts`);

// Get tagged posts with additional parameters
const photoPost = await client.taggedPosts('photography', {
  before: 1461979830,  // Unix timestamp
  limit: 20,
  filter: 'text'
});

// Discover trending content
const trendingPosts = await client.taggedPosts('tumblr', {
  limit: 50
});

trendingPosts.response.forEach(post => {
  console.log(`${post.blog_name}: ${post.summary}`);
  console.log(`Tags: ${post.tags.join(', ')}`);
  console.log(`Notes: ${post.note_count}`);
});

Blog Likes

Get the posts that a specific blog has liked (if publicly visible).

/**
 * Get the likes for a blog
 * @param blogIdentifier - Blog name or URL
 * @param params - Pagination parameters for blog likes
 * @returns Promise resolving to posts liked by the blog
 */
blogLikes(blogIdentifier: string, params?: BlogLikesParams): Promise<any>;

Usage Examples:

// Get posts liked by a blog
const blogLikes = await client.blogLikes('staff');
console.log(`Staff blog has liked ${blogLikes.liked_posts.length} posts`);

// Paginate through blog likes
const moreLikes = await client.blogLikes('staff', {
  limit: 20,
  offset: 20
});

// Get likes before/after specific time
const recentLikes = await client.blogLikes('staff', {
  after: 1461979830,
  limit: 10
});

// Discover content through curator blogs
const curatorLikes = await client.blogLikes('museumsblog');
curatorLikes.liked_posts.forEach(post => {
  console.log(`Curated post from ${post.blog_name}: ${post.summary}`);
});

Blog Followers

Get the list of blogs following a specific blog (if publicly visible).

/**
 * Get the followers for a blog
 * @param blogIdentifier - Blog name or URL
 * @param params - Pagination parameters for followers list
 * @returns Promise resolving to blogs following the specified blog
 */
blogFollowers(blogIdentifier: string, params?: FollowersParams): Promise<any>;

Usage Examples:

// Get followers of a blog
const followers = await client.blogFollowers('popular-blog');
console.log(`Found ${followers.users.length} followers`);

followers.users.forEach(follower => {
  console.log(`${follower.name}: ${follower.url}`);
  if (follower.updated) {
    console.log(`  Last active: ${new Date(follower.updated * 1000)}`);
  }
});

// Paginate through followers
const moreFollowers = await client.blogFollowers('popular-blog', {
  limit: 20,
  offset: 20
});

// Find potential blogs to follow based on who follows interesting blogs
const photoFollowers = await client.blogFollowers('photography');
const activeFollowers = photoFollowers.users.filter(user => {
  const weekAgo = Date.now() / 1000 - (7 * 24 * 60 * 60);
  return user.updated > weekAgo;
});

console.log(`${activeFollowers.length} active followers of photography blog`);

Parameter Types

Tagged Posts Parameters

interface TaggedPostsParams {
  /** Return posts before this timestamp */
  before?: number;
  /** Number of posts to return (1-20) */
  limit?: number;
  /** Post format filter */
  filter?: PostFormatFilter;
}

type PostFormatFilter = 'text' | 'raw';

Blog Likes Parameters

interface BlogLikesParams {
  /** Number of results to return (1-20) */
  limit?: number;
  /** Liked post number to start at */
  offset?: number;
  /** Return posts liked before this timestamp */
  before?: number;
  /** Return posts liked after this timestamp */
  after?: number;
}

Blog Followers Parameters

interface FollowersParams {
  /** Number of results to return (1-20) */
  limit?: number;
  /** Follower number to start at */
  offset?: number;
}

Response Data Examples

Tagged Posts Response

{
  response: [
    {
      id: "12345",
      type: "photo",
      blog_name: "photography-blog",
      timestamp: 1461979830,
      date: "2016-04-29 22:23:50 GMT",
      tags: ["photography", "nature", "landscape"],
      summary: "Beautiful sunset over the mountains",
      note_count: 156,
      photos: [
        {
          caption: "",
          original_size: {
            url: "https://example.com/photo.jpg",
            width: 1280,
            height: 720
          }
        }
      ]
    }
    // ... more posts
  ]
}

Blog Likes Response

{
  liked_posts: [
    {
      id: "67890",
      type: "text",
      blog_name: "writer-blog",
      timestamp: 1461979830,
      liked_timestamp: 1461980000,
      title: "On Writing",
      body: "Some thoughts about the creative process...",
      tags: ["writing", "creativity", "inspiration"],
      note_count: 89
    }
    // ... more liked posts
  ],
  liked_count: 1234
}

Blog Followers Response

{
  users: [
    {
      name: "follower-blog",
      url: "https://follower-blog.tumblr.com/",
      updated: 1461979830
    },
    {
      name: "another-follower",
      url: "https://another-follower.tumblr.com/",
      updated: 1461979700
    }
    // ... more followers
  ],
  total_users: 5678
}

Content Discovery Strategies

Explore by Tag

async function exploreTag(tag, depth = 3) {
  console.log(`Exploring tag: ${tag}`);
  
  const posts = await client.taggedPosts(tag, { limit: 20 });
  
  // Analyze posting patterns
  const blogFrequency = {};
  const relatedTags = new Set();
  
  posts.response.forEach(post => {
    // Track which blogs post about this tag
    blogFrequency[post.blog_name] = (blogFrequency[post.blog_name] || 0) + 1;
    
    // Collect related tags
    post.tags.forEach(t => {
      if (t !== tag) relatedTags.add(t);
    });
  });
  
  // Find most active blogs for this tag
  const topBlogs = Object.entries(blogFrequency)
    .sort(([,a], [,b]) => b - a)
    .slice(0, 5);
    
  console.log('Top blogs posting about', tag, ':', topBlogs);
  console.log('Related tags:', Array.from(relatedTags).slice(0, 10));
  
  return {
    posts: posts.response,
    topBlogs,
    relatedTags: Array.from(relatedTags)
  };
}

Find Similar Blogs

async function findSimilarBlogs(targetBlog, maxSuggestions = 10) {
  try {
    // Get what the target blog likes
    const likes = await client.blogLikes(targetBlog, { limit: 20 });
    
    // Find blogs that appear frequently in their likes
    const likedBlogs = {};
    likes.liked_posts.forEach(post => {
      likedBlogs[post.blog_name] = (likedBlogs[post.blog_name] || 0) + 1;
    });
    
    // Get followers of the target blog
    const followers = await client.blogFollowers(targetBlog, { limit: 20 });
    
    const suggestions = Object.entries(likedBlogs)
      .sort(([,a], [,b]) => b - a)
      .slice(0, maxSuggestions)
      .map(([blogName, count]) => ({
        name: blogName,
        reason: `Liked ${count} times by ${targetBlog}`,
        url: `https://${blogName}.tumblr.com/`
      }));
      
    return suggestions;
    
  } catch (error) {
    console.error(`Cannot analyze ${targetBlog}:`, error.message);
    return [];
  }
}

// Usage
const similar = await findSimilarBlogs('photography');
similar.forEach(blog => {
  console.log(`${blog.name}: ${blog.reason}`);
});

Discover Trending Content

async function findTrendingContent(tags = ['tumblr', 'viral', 'trending']) {
  const trendingPosts = [];
  
  for (const tag of tags) {
    const posts = await client.taggedPosts(tag, { limit: 10 });
    
    // Filter for posts with high engagement
    const highEngagement = posts.response.filter(post => 
      post.note_count > 100
    );
    
    trendingPosts.push(...highEngagement);
  }
  
  // Sort by engagement and recency
  trendingPosts.sort((a, b) => {
    const scoreA = a.note_count * (a.timestamp / 1000000);
    const scoreB = b.note_count * (b.timestamp / 1000000);
    return scoreB - scoreA;
  });
  
  return trendingPosts.slice(0, 20);
}

// Usage
const trending = await findTrendingContent();
trending.forEach(post => {
  console.log(`${post.blog_name}: ${post.summary} (${post.note_count} notes)`);
});

Build Content Feed

async function buildCustomFeed(interests = []) {
  const feedPosts = [];
  
  // Get posts for each interest
  for (const interest of interests) {
    try {
      const posts = await client.taggedPosts(interest, { limit: 5 });
      
      // Add interest category to posts
      posts.response.forEach(post => {
        post.category = interest;
      });
      
      feedPosts.push(...posts.response);
      
      // Add delay to avoid rate limiting
      await new Promise(resolve => setTimeout(resolve, 500));
    } catch (error) {
      console.error(`Failed to get posts for ${interest}:`, error.message);
    }
  }
  
  // Sort by timestamp (most recent first)
  feedPosts.sort((a, b) => b.timestamp - a.timestamp);
  
  return feedPosts;
}

// Usage
const customFeed = await buildCustomFeed([
  'photography', 'art', 'technology', 'cats'
]);

customFeed.forEach(post => {
  console.log(`[${post.category}] ${post.blog_name}: ${post.summary}`);
});

Privacy and Access Considerations

Public vs Private Content

// Tagged posts are always public
const publicPosts = await client.taggedPosts('photography');

// Blog likes may be private
try {
  const likes = await client.blogLikes('private-blog');
} catch (error) {
  if (error.message.includes('404')) {
    console.log('Blog likes are private or blog does not exist');
  }
}

// Blog followers may be private
try {
  const followers = await client.blogFollowers('private-blog');
} catch (error) {
  if (error.message.includes('403')) {
    console.log('Followers list is private');
  }
}

Rate Limiting for Discovery

async function respectfulContentDiscovery(tags, delayMs = 1000) {
  const results = [];
  
  for (const tag of tags) {
    try {
      console.log(`Discovering content for: ${tag}`);
      const posts = await client.taggedPosts(tag, { limit: 10 });
      results.push({ tag, posts: posts.response });
      
      // Wait between requests
      await new Promise(resolve => setTimeout(resolve, delayMs));
    } catch (error) {
      if (error.message.includes('429')) {
        console.log('Rate limit hit, waiting longer...');
        await new Promise(resolve => setTimeout(resolve, 30000));
      } else {
        console.error(`Error discovering ${tag}:`, error.message);
      }
    }
  }
  
  return results;
}

Authentication Requirements

Public Discovery (No Auth Required)

  • taggedPosts() - Public tagged content across Tumblr

API Key Recommended

  • blogLikes() - Enhanced rate limits for blog likes
  • blogFollowers() - Enhanced rate limits for followers

OAuth Not Required

Unlike social interactions, content discovery methods work without user authentication, making them ideal for public content exploration and research.