-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0e347ce
Showing
13 changed files
with
4,344 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
build | ||
node_modules | ||
*.js | ||
.env.* | ||
!.env.sample |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Mastodon - Spicy Tags | ||
|
||
Monitor a Mastodon instance for interesting tags. | ||
|
||
To get started, create an API key within Mastodon for your account. | ||
|
||
|
||
Next, create a `./env/.env.<suffix>` file using the `.env.sample` file as a template. | ||
|
||
Set the following variables to match your setup: | ||
``` | ||
API_URL=https://mastodon.social/api/v1/ | ||
ACCOUNT_ID=112233445566778899 | ||
SLEEP_MS=120000 | ||
ACCESS_TOKEN=abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG | ||
``` | ||
|
||
|
||
Start polling with: | ||
|
||
``` | ||
npm start --env=<suffix> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
|
||
|
||
# The base URL for the API - for example: https://mastodon.social/api/v1/ | ||
# | ||
API_URL=https://mastodon.social/api/v1/ | ||
|
||
|
||
# The Mastodon account ID | ||
# | ||
ACCOUNT_ID=112233445566778899 | ||
|
||
|
||
# Time to sleep between fetches | ||
# | ||
SLEEP_MS=120000 | ||
|
||
|
||
# An access token for the above Mastodon account | ||
# | ||
ACCESS_TOKEN=abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import sleep from "sleep-promise"; | ||
import Followers from "./lib/followers"; | ||
import Tags from "./lib/tags"; | ||
import spicyTags from "./spicyTags.json"; | ||
import { API_URL, ACCESS_TOKEN, ACCOUNT_ID, SLEEP_MS } from "./lib/env"; | ||
|
||
const Mastodon = require("mastodon-api"); | ||
|
||
const M = new Mastodon({ api_url: API_URL, access_token: ACCESS_TOKEN }); | ||
const tags = new Tags(spicyTags.spicyTags); | ||
|
||
const main = async () => { | ||
|
||
/* | ||
Watch the federated stream for key hashtags. For each post: | ||
IF | ||
- a post contains one of the 'spicy' hashtags | ||
- and the account is not already followed (or previously logged) | ||
THEN | ||
- log the account to console, and show the spicy tags from its post. | ||
ELSE | ||
- Show the current tags | ||
*/ | ||
|
||
const allTagsSeen = new Set(); | ||
const following = new Followers(M, ACCOUNT_ID); | ||
const follows = await following.load(); | ||
console.log(`Loaded ${follows.size} following`); | ||
|
||
do { | ||
const result = await M.get("timelines/public", {}); | ||
const newTags = new Set(); | ||
const batchTags = new Set(); | ||
for (let item of result.data) { | ||
const user = item.account.acct; | ||
|
||
// Skip the user if it is in the follows Set | ||
if (follows.has(user)) { | ||
continue; | ||
} | ||
|
||
// Collect tags from the batch | ||
item.tags.forEach((t: any) => { | ||
if (!allTagsSeen.has(t.name)) { | ||
allTagsSeen.add(t.name); | ||
newTags.add(t.name); | ||
} | ||
batchTags.add(t.name); | ||
}); | ||
|
||
// If the batch has a post with a spicy tag, log the user & spicy tags | ||
const spicy = tags.getSpicyTags(item.tags); | ||
if (spicy && spicy.length !== 0) { | ||
console.log(`\n${user} #${spicy}`); | ||
follows.add(user); | ||
} | ||
} | ||
|
||
// Show timestamped tags for this batch/iteration | ||
console.log(`${new Date().toISOString()} #${Array.from(newTags)}`); | ||
await sleep(SLEEP_MS); | ||
} while (true); | ||
}; | ||
|
||
main().catch((e) => console.error(e)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Load the .env.<suffix> file, where the suffix is given by `npm start --env <suffix>` | ||
if (!process.env.npm_config_env) { | ||
console.log("Missing --env parameter to select the dotenv config by suffix"); | ||
console.log( | ||
"As an example, you can pass `npm start --env=social` to load ./env/.env.social) " | ||
); | ||
process.exit(1); | ||
} | ||
const path = "env/.env." + process.env.npm_config_env; | ||
require("dotenv").config({ path }); | ||
|
||
if (!process.env.API_URL) { | ||
console.log(`Missing ${process.env.npm_config_env}`); | ||
process.exit(2); | ||
} | ||
|
||
// The base URL for the API - for example: https://mastodon.social/api/v1/ | ||
export const API_URL = process.env.API_URL!; | ||
|
||
// The Mastodon account ID | ||
export const ACCOUNT_ID = process.env.ACCOUNT_ID!; | ||
|
||
// An access token for the above Mastodon account | ||
export const ACCESS_TOKEN = process.env.ACCESS_TOKEN; | ||
|
||
// Time to sleep between fetches | ||
export const SLEEP_MS = parseInt(process.env.SLEEP_MS!); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { IncomingMessage } from "http"; | ||
|
||
export default class Following { | ||
private M: any; | ||
private accountId: string; | ||
|
||
constructor(mastodon: any, accountId: string) { | ||
this.M = mastodon; | ||
this.accountId = accountId; | ||
} | ||
|
||
/* | ||
Return the user addresses that the account is following | ||
*/ | ||
load = async () => { | ||
let allFollowers: string[] = []; | ||
let maxId = undefined; | ||
let result; | ||
do { | ||
result = await this.loadFollowingBatch(maxId); | ||
console.log(`Loading followers ${maxId}`); | ||
maxId = result.nextId; | ||
|
||
allFollowers = allFollowers.concat(result.batch); | ||
} while (maxId); | ||
|
||
return new Set(allFollowers); | ||
}; | ||
|
||
private loadFollowingBatch = async (maxId?: number) => { | ||
if (typeof maxId === "undefined") { | ||
maxId = 1000000000; | ||
} | ||
const response = await this.M.get( | ||
`accounts/${this.accountId}/following?max_id=${maxId}` | ||
); | ||
const nextId = this.getNextMaxId(response.resp); | ||
|
||
const batch = response.data.map((f: any) => f.acct); | ||
|
||
return { | ||
batch, | ||
nextId, | ||
}; | ||
}; | ||
|
||
private getNextMaxId = (resp: IncomingMessage) => { | ||
const link = resp.headers["link"] as string; | ||
const matches = link?.match(/max_id=([0-9]+)/); | ||
return matches ? parseInt(matches[1]) : undefined; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
export default class Tags { | ||
private spicyTags: Set<string>; | ||
|
||
constructor(spicyTags: string[]) { | ||
this.spicyTags = new Set(spicyTags); | ||
} | ||
|
||
/* | ||
Get the intersection of the given tags with the spicyTags as an array | ||
*/ | ||
getSpicyTags(tags: NameUrl[]) { | ||
const found = []; | ||
for (let tag of tags) { | ||
let tagName = tag.name.toLowerCase(); | ||
if (this.spicyTags.has(tagName)) { | ||
found.push(tagName); | ||
} | ||
} | ||
return found; | ||
} | ||
} | ||
|
||
interface NameUrl { | ||
name: string; | ||
url: string; | ||
} |
Oops, something went wrong.