or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

content-discovery.mddocs/

0

# Content Discovery

1

2

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

3

4

## Capabilities

5

6

### Tagged Posts

7

8

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

9

10

```javascript { .api }

11

/**

12

* Get posts tagged with the specified tag

13

* @param tag - The tag on the posts you'd like to retrieve

14

* @param params - Additional filtering and pagination parameters

15

* @returns Promise resolving to tagged posts from across Tumblr

16

*/

17

taggedPosts(tag: string, params?: TaggedPostsParams): Promise<any>;

18

```

19

20

**Usage Examples:**

21

22

```javascript

23

// Get recent posts with a specific tag

24

const catPosts = await client.taggedPosts('cats');

25

console.log(`Found ${catPosts.response.length} cat posts`);

26

27

// Get tagged posts with additional parameters

28

const photoPost = await client.taggedPosts('photography', {

29

before: 1461979830, // Unix timestamp

30

limit: 20,

31

filter: 'text'

32

});

33

34

// Discover trending content

35

const trendingPosts = await client.taggedPosts('tumblr', {

36

limit: 50

37

});

38

39

trendingPosts.response.forEach(post => {

40

console.log(`${post.blog_name}: ${post.summary}`);

41

console.log(`Tags: ${post.tags.join(', ')}`);

42

console.log(`Notes: ${post.note_count}`);

43

});

44

```

45

46

### Blog Likes

47

48

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

49

50

```javascript { .api }

51

/**

52

* Get the likes for a blog

53

* @param blogIdentifier - Blog name or URL

54

* @param params - Pagination parameters for blog likes

55

* @returns Promise resolving to posts liked by the blog

56

*/

57

blogLikes(blogIdentifier: string, params?: BlogLikesParams): Promise<any>;

58

```

59

60

**Usage Examples:**

61

62

```javascript

63

// Get posts liked by a blog

64

const blogLikes = await client.blogLikes('staff');

65

console.log(`Staff blog has liked ${blogLikes.liked_posts.length} posts`);

66

67

// Paginate through blog likes

68

const moreLikes = await client.blogLikes('staff', {

69

limit: 20,

70

offset: 20

71

});

72

73

// Get likes before/after specific time

74

const recentLikes = await client.blogLikes('staff', {

75

after: 1461979830,

76

limit: 10

77

});

78

79

// Discover content through curator blogs

80

const curatorLikes = await client.blogLikes('museumsblog');

81

curatorLikes.liked_posts.forEach(post => {

82

console.log(`Curated post from ${post.blog_name}: ${post.summary}`);

83

});

84

```

85

86

### Blog Followers

87

88

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

89

90

```javascript { .api }

91

/**

92

* Get the followers for a blog

93

* @param blogIdentifier - Blog name or URL

94

* @param params - Pagination parameters for followers list

95

* @returns Promise resolving to blogs following the specified blog

96

*/

97

blogFollowers(blogIdentifier: string, params?: FollowersParams): Promise<any>;

98

```

99

100

**Usage Examples:**

101

102

```javascript

103

// Get followers of a blog

104

const followers = await client.blogFollowers('popular-blog');

105

console.log(`Found ${followers.users.length} followers`);

106

107

followers.users.forEach(follower => {

108

console.log(`${follower.name}: ${follower.url}`);

109

if (follower.updated) {

110

console.log(` Last active: ${new Date(follower.updated * 1000)}`);

111

}

112

});

113

114

// Paginate through followers

115

const moreFollowers = await client.blogFollowers('popular-blog', {

116

limit: 20,

117

offset: 20

118

});

119

120

// Find potential blogs to follow based on who follows interesting blogs

121

const photoFollowers = await client.blogFollowers('photography');

122

const activeFollowers = photoFollowers.users.filter(user => {

123

const weekAgo = Date.now() / 1000 - (7 * 24 * 60 * 60);

124

return user.updated > weekAgo;

125

});

126

127

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

128

```

129

130

## Parameter Types

131

132

### Tagged Posts Parameters

133

134

```javascript { .api }

135

interface TaggedPostsParams {

136

/** Return posts before this timestamp */

137

before?: number;

138

/** Number of posts to return (1-20) */

139

limit?: number;

140

/** Post format filter */

141

filter?: PostFormatFilter;

142

}

143

144

type PostFormatFilter = 'text' | 'raw';

145

```

146

147

### Blog Likes Parameters

148

149

```javascript { .api }

150

interface BlogLikesParams {

151

/** Number of results to return (1-20) */

152

limit?: number;

153

/** Liked post number to start at */

154

offset?: number;

155

/** Return posts liked before this timestamp */

156

before?: number;

157

/** Return posts liked after this timestamp */

158

after?: number;

159

}

160

```

161

162

### Blog Followers Parameters

163

164

```javascript { .api }

165

interface FollowersParams {

166

/** Number of results to return (1-20) */

167

limit?: number;

168

/** Follower number to start at */

169

offset?: number;

170

}

171

```

172

173

## Response Data Examples

174

175

### Tagged Posts Response

176

177

```javascript

178

{

179

response: [

180

{

181

id: "12345",

182

type: "photo",

183

blog_name: "photography-blog",

184

timestamp: 1461979830,

185

date: "2016-04-29 22:23:50 GMT",

186

tags: ["photography", "nature", "landscape"],

187

summary: "Beautiful sunset over the mountains",

188

note_count: 156,

189

photos: [

190

{

191

caption: "",

192

original_size: {

193

url: "https://example.com/photo.jpg",

194

width: 1280,

195

height: 720

196

}

197

}

198

]

199

}

200

// ... more posts

201

]

202

}

203

```

204

205

### Blog Likes Response

206

207

```javascript

208

{

209

liked_posts: [

210

{

211

id: "67890",

212

type: "text",

213

blog_name: "writer-blog",

214

timestamp: 1461979830,

215

liked_timestamp: 1461980000,

216

title: "On Writing",

217

body: "Some thoughts about the creative process...",

218

tags: ["writing", "creativity", "inspiration"],

219

note_count: 89

220

}

221

// ... more liked posts

222

],

223

liked_count: 1234

224

}

225

```

226

227

### Blog Followers Response

228

229

```javascript

230

{

231

users: [

232

{

233

name: "follower-blog",

234

url: "https://follower-blog.tumblr.com/",

235

updated: 1461979830

236

},

237

{

238

name: "another-follower",

239

url: "https://another-follower.tumblr.com/",

240

updated: 1461979700

241

}

242

// ... more followers

243

],

244

total_users: 5678

245

}

246

```

247

248

## Content Discovery Strategies

249

250

### Explore by Tag

251

252

```javascript

253

async function exploreTag(tag, depth = 3) {

254

console.log(`Exploring tag: ${tag}`);

255

256

const posts = await client.taggedPosts(tag, { limit: 20 });

257

258

// Analyze posting patterns

259

const blogFrequency = {};

260

const relatedTags = new Set();

261

262

posts.response.forEach(post => {

263

// Track which blogs post about this tag

264

blogFrequency[post.blog_name] = (blogFrequency[post.blog_name] || 0) + 1;

265

266

// Collect related tags

267

post.tags.forEach(t => {

268

if (t !== tag) relatedTags.add(t);

269

});

270

});

271

272

// Find most active blogs for this tag

273

const topBlogs = Object.entries(blogFrequency)

274

.sort(([,a], [,b]) => b - a)

275

.slice(0, 5);

276

277

console.log('Top blogs posting about', tag, ':', topBlogs);

278

console.log('Related tags:', Array.from(relatedTags).slice(0, 10));

279

280

return {

281

posts: posts.response,

282

topBlogs,

283

relatedTags: Array.from(relatedTags)

284

};

285

}

286

```

287

288

### Find Similar Blogs

289

290

```javascript

291

async function findSimilarBlogs(targetBlog, maxSuggestions = 10) {

292

try {

293

// Get what the target blog likes

294

const likes = await client.blogLikes(targetBlog, { limit: 20 });

295

296

// Find blogs that appear frequently in their likes

297

const likedBlogs = {};

298

likes.liked_posts.forEach(post => {

299

likedBlogs[post.blog_name] = (likedBlogs[post.blog_name] || 0) + 1;

300

});

301

302

// Get followers of the target blog

303

const followers = await client.blogFollowers(targetBlog, { limit: 20 });

304

305

const suggestions = Object.entries(likedBlogs)

306

.sort(([,a], [,b]) => b - a)

307

.slice(0, maxSuggestions)

308

.map(([blogName, count]) => ({

309

name: blogName,

310

reason: `Liked ${count} times by ${targetBlog}`,

311

url: `https://${blogName}.tumblr.com/`

312

}));

313

314

return suggestions;

315

316

} catch (error) {

317

console.error(`Cannot analyze ${targetBlog}:`, error.message);

318

return [];

319

}

320

}

321

322

// Usage

323

const similar = await findSimilarBlogs('photography');

324

similar.forEach(blog => {

325

console.log(`${blog.name}: ${blog.reason}`);

326

});

327

```

328

329

### Discover Trending Content

330

331

```javascript

332

async function findTrendingContent(tags = ['tumblr', 'viral', 'trending']) {

333

const trendingPosts = [];

334

335

for (const tag of tags) {

336

const posts = await client.taggedPosts(tag, { limit: 10 });

337

338

// Filter for posts with high engagement

339

const highEngagement = posts.response.filter(post =>

340

post.note_count > 100

341

);

342

343

trendingPosts.push(...highEngagement);

344

}

345

346

// Sort by engagement and recency

347

trendingPosts.sort((a, b) => {

348

const scoreA = a.note_count * (a.timestamp / 1000000);

349

const scoreB = b.note_count * (b.timestamp / 1000000);

350

return scoreB - scoreA;

351

});

352

353

return trendingPosts.slice(0, 20);

354

}

355

356

// Usage

357

const trending = await findTrendingContent();

358

trending.forEach(post => {

359

console.log(`${post.blog_name}: ${post.summary} (${post.note_count} notes)`);

360

});

361

```

362

363

### Build Content Feed

364

365

```javascript

366

async function buildCustomFeed(interests = []) {

367

const feedPosts = [];

368

369

// Get posts for each interest

370

for (const interest of interests) {

371

try {

372

const posts = await client.taggedPosts(interest, { limit: 5 });

373

374

// Add interest category to posts

375

posts.response.forEach(post => {

376

post.category = interest;

377

});

378

379

feedPosts.push(...posts.response);

380

381

// Add delay to avoid rate limiting

382

await new Promise(resolve => setTimeout(resolve, 500));

383

} catch (error) {

384

console.error(`Failed to get posts for ${interest}:`, error.message);

385

}

386

}

387

388

// Sort by timestamp (most recent first)

389

feedPosts.sort((a, b) => b.timestamp - a.timestamp);

390

391

return feedPosts;

392

}

393

394

// Usage

395

const customFeed = await buildCustomFeed([

396

'photography', 'art', 'technology', 'cats'

397

]);

398

399

customFeed.forEach(post => {

400

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

401

});

402

```

403

404

## Privacy and Access Considerations

405

406

### Public vs Private Content

407

408

```javascript

409

// Tagged posts are always public

410

const publicPosts = await client.taggedPosts('photography');

411

412

// Blog likes may be private

413

try {

414

const likes = await client.blogLikes('private-blog');

415

} catch (error) {

416

if (error.message.includes('404')) {

417

console.log('Blog likes are private or blog does not exist');

418

}

419

}

420

421

// Blog followers may be private

422

try {

423

const followers = await client.blogFollowers('private-blog');

424

} catch (error) {

425

if (error.message.includes('403')) {

426

console.log('Followers list is private');

427

}

428

}

429

```

430

431

### Rate Limiting for Discovery

432

433

```javascript

434

async function respectfulContentDiscovery(tags, delayMs = 1000) {

435

const results = [];

436

437

for (const tag of tags) {

438

try {

439

console.log(`Discovering content for: ${tag}`);

440

const posts = await client.taggedPosts(tag, { limit: 10 });

441

results.push({ tag, posts: posts.response });

442

443

// Wait between requests

444

await new Promise(resolve => setTimeout(resolve, delayMs));

445

} catch (error) {

446

if (error.message.includes('429')) {

447

console.log('Rate limit hit, waiting longer...');

448

await new Promise(resolve => setTimeout(resolve, 30000));

449

} else {

450

console.error(`Error discovering ${tag}:`, error.message);

451

}

452

}

453

}

454

455

return results;

456

}

457

```

458

459

## Authentication Requirements

460

461

### Public Discovery (No Auth Required)

462

- `taggedPosts()` - Public tagged content across Tumblr

463

464

### API Key Recommended

465

- `blogLikes()` - Enhanced rate limits for blog likes

466

- `blogFollowers()` - Enhanced rate limits for followers

467

468

### OAuth Not Required

469

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