Compare commits
No commits in common. "main" and "v1.2.0" have entirely different histories.
8 changed files with 9196 additions and 9138 deletions
|
@ -2,5 +2,4 @@ MASTODON_INSTANCE="https://mastodon.instance"
|
|||
MASTODON_USER="username"
|
||||
BLUESKY_ENDPOINT="https://bsky.social"
|
||||
BLUESKY_HANDLE="USERNAME.bsky.social"
|
||||
BLUESKY_PASSWORD="PASSWORD"
|
||||
INTERVAL_MINUTES=5
|
||||
BLUESKY_PASSWORD="PASSWORD"
|
83
.github/workflows/main.yml
vendored
Normal file
83
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build docker image
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Read .nvmrc
|
||||
run: echo "NODE_VERSION=$(awk -F. '{print $1}' .nvmrc)" >> $GITHUB_OUTPUT
|
||||
id: nvm
|
||||
|
||||
- name: Login to container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker image metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=schedule
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=sha,prefix={{branch}}-,format=short
|
||||
type=sha,prefix=,format=short
|
||||
{{branch}}
|
||||
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
NODE_VERSION=${{ steps.nvm.outputs.NODE_VERSION }}
|
||||
|
||||
release:
|
||||
name: Release
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read .nvmrc
|
||||
run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_OUTPUT
|
||||
id: nvm
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.nvm.outputs.NODE_VERSION }}
|
||||
|
||||
- name: Install
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_SEMANTIC_RELEASE }}
|
||||
run: npx semantic-release
|
44
.github/workflows/release.yml
vendored
Normal file
44
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
name: Release
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
uses: battila7/get-version-action@v2
|
||||
|
||||
- name: Add Semantic Version tag to Docker Image
|
||||
uses: shrink/actions-docker-registry-tag@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
repository: ${{ env.IMAGE_NAME }}
|
||||
target: 'main'
|
||||
tags: |
|
||||
${{ steps.version.outputs.version-without-v }}
|
||||
${{ steps.version.outputs.major }}.${{ steps.version.outputs.minor }}
|
||||
latest
|
59
README.md
59
README.md
|
@ -1,56 +1,3 @@
|
|||
# Mastodon to Bluesky
|
||||
This is forked from https://github.com/mauricerenck/mastodon-to-bluesky. So far I have only added the following:
|
||||
|
||||
- Upgraded packages due to issues with conversion to Bluesky.
|
||||
- Updated docker-compose to build the Docker image.
|
||||
|
||||
#### Crosspost from Mastodon to Bluesky
|
||||
|
||||
 
|
||||
|
||||
---
|
||||
|
||||
This scripts listens to your Mastodon account and crossposts your toots to your Bluesky account. It uses the Mastodon API and the Bluesky API to achieve this. The script is written in Node.js and can be run on your local machine or on a server.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
You can run the script directly using Node.js or you can use the Docker image.
|
||||
|
||||
### Node.js
|
||||
|
||||
Clone this repository and install the dependencies:
|
||||
|
||||
```bash
|
||||
git clone https://code.alexhyett.com/alexhyett/mastodon-to-bluesky.git
|
||||
cd mastodon-to-bluesky
|
||||
npm install
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Create a `.env` file in the root directory of the project and add the following variables:
|
||||
|
||||
```bash
|
||||
MASTODON_INSTANCE: 'https://mastodon.instance'
|
||||
MASTODON_USER: 'username'
|
||||
BLUESKY_ENDPOINT: 'https://bsky.social'
|
||||
BLUESKY_HANDLE: 'USERNAME.bsky.social'
|
||||
BLUESKY_PASSWORD: 'PASSWORD'
|
||||
INTERVAL_MINUTES: 5
|
||||
```
|
||||
|
||||
You can also set the same variables as environment variables in the `docker-compose.yml` file.
|
||||
|
||||
## Usage
|
||||
|
||||
To run the script, execute the following command:
|
||||
|
||||
```bash
|
||||
node main.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
For more details see: https://maurice-renck.de/hub/tooling/crosspost-from-mastodon-to-bluesky
|
||||
# mastodon-to-bluesky
|
||||
A Node.js script for crossposting from mastodon to bluesky
|
||||
For more details see: https://maurice-renck.de/hub/tooling/crosspost-from-mastodon-to-bluesky
|
|
@ -1,12 +1,19 @@
|
|||
version: '3'
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
image: host/mastodon-to-bluesky:latest
|
||||
container_name: mastodon-to-bluesky
|
||||
env_file: '.env'
|
||||
environment:
|
||||
MASTODON_INSTANCE: 'https://mastodon.instance'
|
||||
MASTODON_USER: 'username'
|
||||
BLUESKY_ENDPOINT: 'https://bsky.social'
|
||||
BLUESKY_HANDLE: 'USERNAME.bsky.social'
|
||||
BLUESKY_PASSWORD: 'PASSWORD'
|
||||
INTERVAL_MINUTES: 5
|
||||
volumes:
|
||||
- mastodon-to-bluesky:/usr/src/app/data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
mastodon-to-bluesky:
|
||||
mastodon-to-bluesky:
|
||||
external: true
|
17
main.js
17
main.js
|
@ -1,9 +1,8 @@
|
|||
require("dotenv").config();
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { RichText, AtpAgent } = require("@atproto/api");
|
||||
const { RichText, BskyAgent } = require("@atproto/api");
|
||||
const axios = require("axios");
|
||||
const he = require('he');
|
||||
|
||||
// Mastodon credentials
|
||||
const mastodonInstance = process.env.MASTODON_INSTANCE;
|
||||
|
@ -11,7 +10,7 @@ const mastodonUser = process.env.MASTODON_USER;
|
|||
|
||||
async function main() {
|
||||
// Bluesky agent
|
||||
const agent = new AtpAgent({ service: process.env.BLUESKY_ENDPOINT });
|
||||
const agent = new BskyAgent({ service: process.env.BLUESKY_ENDPOINT });
|
||||
const loginResponse = await agent.login({
|
||||
identifier: process.env.BLUESKY_HANDLE,
|
||||
password: process.env.BLUESKY_PASSWORD,
|
||||
|
@ -58,8 +57,7 @@ async function main() {
|
|||
}
|
||||
|
||||
async function postToBluesky(textParts) {
|
||||
const blueskyMessage = await createBlueskyMessage(textParts[0]);
|
||||
const rootMessageResponse = await agent.post(blueskyMessage);
|
||||
const rootMessageResponse = await agent.post(await createBlueskyMessage(textParts[0]));
|
||||
|
||||
if (textParts.length === 1) return;
|
||||
|
||||
|
@ -75,11 +73,8 @@ async function main() {
|
|||
}
|
||||
}
|
||||
|
||||
function sanitizeHtml(input) {
|
||||
const withoutHtml = input.replace(/<[^>]*>/g, "");
|
||||
const decodeQuotes = he.decode(withoutHtml);
|
||||
const addSpace = decodeQuotes.replace(/(https?:\/\/)/g, ' $1');
|
||||
return addSpace;
|
||||
function removeHtmlTags(input) {
|
||||
return input.replace(/<[^>]*>/g, "");
|
||||
}
|
||||
|
||||
function splitText(text, maxLength) {
|
||||
|
@ -132,7 +127,7 @@ async function main() {
|
|||
if (currentTimestampId > lastProcessedPostId && lastProcessedPostId != 0) {
|
||||
try {
|
||||
console.log('📧 posting to BlueSky', currentTimestampId)
|
||||
const textParts = splitText(sanitizeHtml(item.object.content), 300);
|
||||
const textParts = splitText(removeHtmlTags(item.object.content), 300);
|
||||
postToBluesky(textParts);
|
||||
} catch (error) {
|
||||
console.error('🔥 can\'t post to Bluesky', currentTimestampId, error)
|
||||
|
|
18080
package-lock.json
generated
18080
package-lock.json
generated
File diff suppressed because it is too large
Load diff
35
package.json
35
package.json
|
@ -1,20 +1,19 @@
|
|||
{
|
||||
"name": "mastodon-to-bluesky",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Maurice Renck <hello@maurice-renck.de>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.13.15",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"axios": "^1.7.7",
|
||||
"dotenv": "^16.4.5",
|
||||
"he": "^1.2.0",
|
||||
"mastodon-api": "^1.3.0"
|
||||
}
|
||||
"name": "mastodon-to-bluesky",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Maurice Renck <hello@maurice-renck.de>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.12.2",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"axios": "^1.6.8",
|
||||
"dotenv": "^16.4.5",
|
||||
"mastodon-api": "^1.3.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue