Skip to content

Commit 0db32f7

Browse files
committed
docs: update Discord bot invitation instructions
- Replace dead discordjs.guide link with comprehensive self-contained instructions - Add step-by-step guide for using Discord's OAuth2 URL Generator - Include quick invite URL template with pre-calculated permissions (18432) - Permissions: Send Messages (2048) + Embed Links (16384)
1 parent 22e6af4 commit 0db32f7

File tree

7 files changed

+74
-22
lines changed

7 files changed

+74
-22
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,20 @@ Originally developed for [@dutchtide](https://twitter.com/dutchtide)'s [𝕄𝕚
9696

9797
**Discord Setup:**
9898
1. [Create a Discord application](https://discord.com/developers/applications)
99-
2. Create a bot with permissions: `Send Messages` and `Embed Links`
100-
3. [Add bot to your server](https://discordjs.guide/preparations/adding-your-bot-to-servers.html#bot-invite-links)
99+
2. Go to the **Bot** tab and click "Add Bot"
100+
3. Under **Privileged Gateway Intents**, enable any intents you need (none required for this bot)
101101
4. Copy the bot token to `DISCORD_TOKEN`
102+
5. **Invite bot to your server:**
103+
- Go to **OAuth2****URL Generator**
104+
- Under **Scopes**, select `bot`
105+
- Under **Bot Permissions**, select `Send Messages` and `Embed Links`
106+
- Copy the generated URL and open it in your browser
107+
- Select your server and authorize
108+
109+
**Quick Invite URL** (replace `YOUR_CLIENT_ID` with your application's Client ID from the OAuth2 page):
110+
```
111+
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=18432&scope=bot
112+
```
102113

103114
**DISCORD_EVENTS Format:**
104115
- Single channel: `CHANNEL_ID=event1,event2`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opensea-activity-bot",
3-
"version": "3.5.0",
3+
"version": "3.5.1",
44
"description": "A bot that shares new OpenSea events for a collection to Discord and Twitter.",
55
"author": "Ryan Ghods <ryan@ryanio.com>",
66
"license": "MIT",

src/index.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ import {
77
import { messageEvents } from "./platforms/discord";
88
import { tweetEvents } from "./platforms/twitter";
99
import type { OpenSeaAssetEvent } from "./types";
10+
import { MS_PER_SECOND } from "./utils/constants";
1011
import { getDefaultEventGroupConfig } from "./utils/event-grouping";
1112
import { logger } from "./utils/logger";
12-
import { botInterval, chain, fullTokenAddr, minOfferETH } from "./utils/utils";
13-
14-
const MILLISECONDS_PER_SECOND = 1000;
13+
import {
14+
botInterval,
15+
chain,
16+
formatReadableDate,
17+
formatTimeAgo,
18+
fullTokenAddr,
19+
minOfferETH,
20+
} from "./utils/utils";
1521

1622
const logPlatformConfig = (
1723
twitterEnabled: boolean,
@@ -36,9 +42,7 @@ const logPlatformConfig = (
3642
process.env.TWITTER_PREPEND_TWEET || process.env.TWITTER_APPEND_TWEET;
3743
logger.info(`│ ${hasPrependOrAppend ? "├─" : "└─"} Grouping`);
3844
logger.info(`│ ├─ Min Group Size: ${config.minGroupSize} items`);
39-
logger.info(
40-
`│ └─ Settle Time: ${config.settleMs / MILLISECONDS_PER_SECOND}s`
41-
);
45+
logger.info(`│ └─ Settle Time: ${config.settleMs / MS_PER_SECOND}s`);
4246
}
4347
logger.info("│");
4448
logger.info(
@@ -50,9 +54,7 @@ const logPlatformConfig = (
5054
const config = getDefaultEventGroupConfig("DISCORD");
5155
logger.info("│ └─ Grouping");
5256
logger.info(`│ ├─ Min Group Size: ${config.minGroupSize} items`);
53-
logger.info(
54-
`│ └─ Settle Time: ${config.settleMs / MILLISECONDS_PER_SECOND}s`
55-
);
57+
logger.info(`│ └─ Settle Time: ${config.settleMs / MS_PER_SECOND}s`);
5658
}
5759
logger.info("│");
5860
};
@@ -117,7 +119,10 @@ const logStartupConfiguration = async () => {
117119
logger.info(`│ ⛓️ Chain: ${chain}`);
118120
logger.info(`│ ⏱️ Poll Interval: ${botInterval}s`);
119121
if (eventTimestampInfo) {
120-
logger.info(`│ 🕐 Event Timestamp: ${eventTimestampInfo.timestamp}`);
122+
const ts = eventTimestampInfo.timestamp;
123+
logger.info(
124+
`│ 🕐 Last Event: ${formatReadableDate(ts)} (${formatTimeAgo(ts)})`
125+
);
121126
logger.info(
122127
`│ └─ Source: ${formatTimestampSource(eventTimestampInfo.source)}`
123128
);
@@ -152,7 +157,6 @@ async function main() {
152157
await logStartupConfiguration();
153158
run();
154159

155-
const MS_PER_SECOND = 1000;
156160
const interval = setInterval(run.bind(this), botInterval * MS_PER_SECOND);
157161

158162
process.on("SIGINT", () => {

src/platforms/twitter.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TwitterApi } from "twitter-api-v2";
22
import { EventType, getCollectionSlug, opensea, username } from "../opensea";
33
import type { BotEvent, OpenSeaAssetEvent, OpenSeaPayment } from "../types";
44
import { txHashFor } from "../utils/aggregator";
5+
import { MS_PER_SECOND, SECONDS_PER_MINUTE } from "../utils/constants";
56
import {
67
calculateTotalSpent,
78
EventGroupManager,
@@ -41,11 +42,9 @@ const tweetedEventsCache = new LRUCache<string, boolean>(
4142
// Queue + backoff config
4243
const DEFAULT_TWEET_DELAY_MS = 3000;
4344
const DEFAULT_BACKOFF_BASE_MS = 15_000;
44-
const MINUTES = 60;
45-
const SECONDS_PER_MINUTE = 60;
46-
const MS_PER_SECOND = 1000;
4745
const BACKOFF_MAX_MINUTES = 15;
48-
const DEFAULT_BACKOFF_MAX_MS = BACKOFF_MAX_MINUTES * MINUTES * MS_PER_SECOND;
46+
const DEFAULT_BACKOFF_MAX_MS =
47+
BACKOFF_MAX_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
4948
const DEFAULT_PROCESSING_TIMEOUT_MINUTES = 2;
5049
const DEFAULT_PROCESSING_TIMEOUT_MS =
5150
DEFAULT_PROCESSING_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;

src/utils/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export const NULL_ONE_ADDRESS = "0x0000000000000000000000000000000000000001";
1212
export const MIN_GROUP_SIZE = 2;
1313
export const DEFAULT_SETTLE_MS = 60_000;
1414

15-
// Milliseconds per second (for utility conversions)
15+
// Time constants (for utility conversions)
1616
export const MS_PER_SECOND = 1000;
17+
export const SECONDS_PER_MINUTE = 60;

src/utils/event-state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { promises as fs } from "node:fs";
22
import { dirname, join } from "node:path";
3+
import { SECONDS_PER_MINUTE } from "./constants";
34
import { logger } from "./logger";
45

56
const DEFAULT_DEDUPE_WINDOW_MINUTES = 60;
6-
const SECONDS_PER_MINUTE = 60;
77

88
export type EventCursor = {
99
source: string;

src/utils/utils.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { NFTLike } from "./aggregator";
44
import {
55
DEAD_ADDRESS,
66
GLYPHBOTS_CONTRACT_ADDRESS,
7+
MS_PER_SECOND,
78
NULL_ADDRESS,
89
NULL_ONE_ADDRESS,
910
} from "./constants";
@@ -13,9 +14,45 @@ export function timeout(ms: number) {
1314
return new Promise((resolve) => setTimeout(resolve, ms));
1415
}
1516

16-
const SECONDS_PER_MS = 1000;
1717
export const unixTimestamp = (date: Date) =>
18-
Math.floor(date.getTime() / SECONDS_PER_MS);
18+
Math.floor(date.getTime() / MS_PER_SECOND);
19+
20+
/**
21+
* Formats a Unix timestamp as a relative time string (e.g., "2 hours ago")
22+
*/
23+
export const formatTimeAgo = (timestamp: number): string => {
24+
const now = Date.now();
25+
const diffMs = now - timestamp * MS_PER_SECOND;
26+
const diffSeconds = Math.floor(diffMs / MS_PER_SECOND);
27+
const diffMinutes = Math.floor(diffSeconds / 60);
28+
const diffHours = Math.floor(diffMinutes / 60);
29+
const diffDays = Math.floor(diffHours / 24);
30+
31+
if (diffDays > 0) {
32+
return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
33+
}
34+
if (diffHours > 0) {
35+
return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`;
36+
}
37+
if (diffMinutes > 0) {
38+
return `${diffMinutes} minute${diffMinutes === 1 ? "" : "s"} ago`;
39+
}
40+
return "just now";
41+
};
42+
43+
/**
44+
* Formats a Unix timestamp as a human-readable date (e.g., "Dec 4, 2025, 10:30 AM")
45+
*/
46+
export const formatReadableDate = (timestamp: number): string => {
47+
const date = new Date(timestamp * MS_PER_SECOND);
48+
return date.toLocaleDateString("en-US", {
49+
month: "short",
50+
day: "numeric",
51+
year: "numeric",
52+
hour: "numeric",
53+
minute: "2-digit",
54+
});
55+
};
1956

2057
/**
2158
* Returns a shortened version of a full ethereum address

0 commit comments

Comments
 (0)