Skip to content

Commit

Permalink
add proper bsky handling + local kv worker storage + debug mode
Browse files Browse the repository at this point in the history
  • Loading branch information
jerdog committed Dec 5, 2024
1 parent d31ecf9 commit 58b3c3e
Show file tree
Hide file tree
Showing 5 changed files with 487 additions and 205 deletions.
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,58 @@ A serverless bot that generates and posts content using Markov chain text genera
- `MASTODON_SOURCE_ACCOUNTS` - Mastodon accounts to source content from
- `BLUESKY_SOURCE_ACCOUNTS` - Bluesky accounts to source content from
- `EXCLUDED_WORDS` - Words to exclude from generated content and replies
- `DEBUG_MODE` - Enable detailed logging (true/false)
- `DEBUG_MODE` - When set to 'true', prevents actual posting and enables detailed logging
- `DEBUG_LEVEL` - Debug log level (verbose/info/error)
- `MARKOV_STATE_SIZE` - Markov chain state size (default: 2)
- `MARKOV_MIN_CHARS` - Minimum characters in generated post (default: 100)
- `MARKOV_MAX_CHARS` - Maximum characters in generated post (default: 280)
- `MARKOV_MAX_TRIES` - Maximum attempts to generate valid post (default: 100)

## Debug Mode

The bot includes a comprehensive debug mode that allows you to test functionality without actually posting to social media platforms.

### Enabling Debug Mode

You can enable debug mode in one of three ways:

1. In `.dev.vars` for local development:
```ini
DEBUG_MODE=true
```

2. In `wrangler.toml` for development and testing:
```toml
[vars]
DEBUG_MODE = "true"
```

3. In Cloudflare Dashboard for production:
- Go to Workers & Pages > Your Worker > Settings > Variables
- Add `DEBUG_MODE` with value `true`

### What Debug Mode Does

When `DEBUG_MODE` is set to 'true':
- No actual posts will be made to any platform
- Detailed logs show what would have been posted
- Reply tracking still works to prevent duplicate debug logs
- All other functionality (notifications, reply generation) works normally

This is useful for:
- Testing reply generation
- Verifying post content before going live
- Debugging notification processing
- Testing rate limit handling

### Debug Logs

With debug mode enabled, you'll see detailed logs like:
```
Debug mode: Would post to Bluesky: [post content]
Debug mode: Would reply to Mastodon post: [reply content]
```

## Local Development

1. Install dependencies:
Expand All @@ -66,7 +111,7 @@ A serverless bot that generates and posts content using Markov chain text genera
BLUESKY_PASSWORD=your_app_password
MASTODON_SOURCE_ACCOUNTS=@user@instance
BLUESKY_SOURCE_ACCOUNTS[email protected]
DEBUG_MODE=true
DEBUG_MODE=true # Start with debug mode enabled for safety
DEBUG_LEVEL=verbose
OPENAI_API_KEY=your_openai_api_key
```
Expand Down
133 changes: 83 additions & 50 deletions bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,53 +487,46 @@ async function fetchRecentPosts() {

async function getBlueskyAuth() {
try {
// Ensure config is loaded first
if (!CONFIG) {
CONFIG = await loadConfig();
}

if (!CONFIG?.bluesky?.identifier || !CONFIG?.bluesky?.password || !CONFIG?.bluesky?.service) {
throw new Error('Missing Bluesky configuration');
}
debug('Authenticating with Bluesky using:', 'info', process.env.BLUESKY_USERNAME);
debug('Using API URL:', 'info', process.env.BLUESKY_API_URL);

debug(`Authenticating with Bluesky using: ${CONFIG.bluesky.identifier}`, 'verbose');

if (!validateBlueskyUsername(CONFIG.bluesky.identifier)) {
throw new Error('Invalid Bluesky username format. Should be handle.bsky.social or handle.domain.tld');
}

const authData = {
'identifier': CONFIG.bluesky.identifier,
'password': CONFIG.bluesky.password
};

debug('Sending Bluesky auth request...', 'verbose');

const response = await fetch(`${CONFIG.bluesky.service}/xrpc/com.atproto.server.createSession`, {
debug('Sending Bluesky auth request...', 'info');
const response = await fetch(`${process.env.BLUESKY_API_URL}/xrpc/com.atproto.server.createSession`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
'Content-Type': 'application/json'
},
body: JSON.stringify(authData)
body: JSON.stringify({
identifier: process.env.BLUESKY_USERNAME,
password: process.env.BLUESKY_PASSWORD
})
});

debug('Auth response status:', 'info', {
status: response.status,
statusText: response.statusText
});

if (!response.ok) {
const errorData = await response.json();
debug('Bluesky authentication failed', 'error', errorData);
throw new Error(`Bluesky authentication error: ${errorData.message || 'Unknown error'}`);
const errorText = await response.text();
debug('Auth error:', 'error', {
status: response.status,
statusText: response.statusText,
error: errorText
});
throw new Error(`Authentication failed: ${errorText}`);
}

const data = await response.json();
if (!data || !data.accessJwt) {
debug('Bluesky authentication response missing access token', 'error', data);
return null;
}
debug('Successfully authenticated with Bluesky', 'info', {
did: data.did,
hasAccessJwt: !!data.accessJwt,
hasRefreshJwt: !!data.refreshJwt
});

debug('Successfully authenticated with Bluesky', 'verbose');
return data.accessJwt;
return data;
} catch (error) {
debug(`Bluesky authentication error: ${error.message}`, 'error');
debug('Error authenticating with Bluesky:', 'error', error);
return null;
}
}
Expand Down Expand Up @@ -589,22 +582,45 @@ async function generatePost(content) {
// Social Media Integration
async function postToMastodon(content) {
try {
// Check if we're in debug mode
if (process.env.DEBUG_MODE === 'true') {
debug('Debug mode: Would post to Mastodon:', 'info', {
content,
platform: 'mastodon'
});
return true;
}

const response = await fetch(`${process.env.MASTODON_API_URL}/api/v1/statuses`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MASTODON_ACCESS_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ status: content })
body: JSON.stringify({
status: content,
visibility: 'public'
})
});

if (!response.ok) {
throw new Error(`Failed to post to Mastodon: ${response.statusText}`);
const errorData = await response.text();
debug('Failed to post to Mastodon', 'error', {
status: response.status,
statusText: response.statusText,
error: errorData
});
throw new Error(`Failed to post to Mastodon: ${errorData}`);
}

const data = await response.json();
storeRecentPost('mastodon', data.id, content);
debug('Posted to Mastodon successfully');
debug('Post created successfully', 'info', { id: data.id });

// Store the post URL in our cache
const postUrl = data.url;
storeRecentPost('mastodon', postUrl, content);
debug('Post stored in cache', 'info', { url: postUrl });

return true;
} catch (error) {
debug('Error posting to Mastodon:', 'error', error);
Expand All @@ -614,26 +630,34 @@ async function postToMastodon(content) {

async function postToBluesky(content) {
try {
// Check if we're in debug mode
if (process.env.DEBUG_MODE === 'true') {
debug('Debug mode: Would post to Bluesky:', 'info', {
content,
platform: 'bluesky'
});
return true;
}

// Get auth token
const authToken = await getBlueskyAuth();
if (!authToken) {
const auth = await getBlueskyAuth();
if (!auth || !auth.accessJwt || !auth.did) {
throw new Error('Failed to authenticate with Bluesky');
}

// Get user DID
const did = await getBlueskyDid();
if (!did) {
throw new Error('Failed to get Bluesky DID');
}
debug('Posting to Bluesky', 'info', {
authenticated: true,
did: auth.did
});

const response = await fetch(`${CONFIG.bluesky.service}/xrpc/com.atproto.repo.createRecord`, {
const response = await fetch(`${process.env.BLUESKY_API_URL}/xrpc/com.atproto.repo.createRecord`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Authorization': `Bearer ${auth.accessJwt}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
repo: did,
repo: auth.did,
collection: 'app.bsky.feed.post',
record: {
text: content,
Expand All @@ -644,12 +668,21 @@ async function postToBluesky(content) {

if (!response.ok) {
const errorData = await response.text();
debug('Failed to post to Bluesky', 'error', {
status: response.status,
statusText: response.statusText,
error: errorData
});
throw new Error(`Failed to post to Bluesky: ${errorData}`);
}

const data = await response.json();
debug('Post created successfully', 'info', { uri: data.uri });

// Store the post in our cache
storeRecentPost('bluesky', data.uri, content);
debug('Posted to Bluesky successfully');
debug('Post stored in cache', 'info', { uri: data.uri });

return true;
} catch (error) {
debug('Error posting to Bluesky:', 'error', error);
Expand Down
Loading

0 comments on commit 58b3c3e

Please sign in to comment.