├── package.json ├── README.md └── import.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "import-tweets-to-mastodon", 3 | "version": "2.0.0", 4 | "description": "Imports Twitter tweets to Mastodon CLI", 5 | "main": "import.js", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/FGRibreau/import-tweets-to-mastodon.git" 10 | }, 11 | "author": "Francois-Guillaume Ribreau (http://fgribreau.com/)", 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/FGRibreau/import-tweets-to-mastodon/issues" 15 | }, 16 | "homepage": "https://github.com/FGRibreau/import-tweets-to-mastodon#readme", 17 | "dependencies": { 18 | "axios": "^0.19.0", 19 | "common-env": "^6.3.0", 20 | "progressbar": "^1.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Importing tweets for a Mastodon instance :bird::arrow_right::elephant: 2 | Thats a simples script to import yours tweets from any Twitter account to a Mastodon account, in any instance. 3 | 4 | ## Steps 5 | For this tutorial, you'll need to install **Node.js** and **Git**. 6 | - Node.js: [Download | Node.js (nodejs.org)](https://nodejs.org/en/download/) 7 | - Git: [Git - Downloads (git-scm.com)](https://git-scm.com/downloads) 8 | ### #1 - Request your Twitter data in link below; 9 | Go [here](https://twitter.com/settings/your_twitter_data) for request your Twitter data. You'll recive a link to download a zip file. There will be tweets, images, information etc. It'll be in this configuration: 10 | - twitter 11 | - assets (folder) 12 | - data (folder) 13 | - Your archive (html file) 14 | 15 | ### #2 - Extract archive and look for `tweets.js` 16 | Extract the file in your Desktop folder. Will be more easy for manipulate. Find the **tweets.js** file. It'll be, probably inside "data" folder and move it to Desktop. 17 | 18 | ### #3 - Clone this repo 19 | Clone this repo in your Desktop folder with the command: 20 | ``` 21 | git clone https://github.com/FGRibreau/import-tweets-to-mastodon.git 22 | ``` 23 | Or download the zip and extract in Desktop. 24 | ### #4 - Set an API key from Mastodon 25 | To request an API key from Mastodon, click [here](https://mastodon.cloud/settings/applications). Create an new application in "New Application". Set an Application Name (e. g.: api-mastodon-twitter) and, at the end of page, click in "Submit". 26 | ### #5 - Set the environment variables 27 | #### Windows 28 | 1. Copy your "Your access token" from developer's page in Mastodon; 29 | 2. Press "WIN + R" and write "cmd". Click ENTER; 30 | 3. Go to your Desktop folder; 31 | 4. Write 32 | ``` 33 | set MASTODON_API_BASEPATH=https://[YOUR-MASTODON-INSTANCE-DOMAIN] 34 | ``` 35 | ``` 36 | set MASTODON_API_KEY=YOUR_ACCESS_TOKEN_FROM_MASTODON 37 | ``` 38 | ``` 39 | set TWITTER_TWEETJS_FILEPATH=tweets.js 40 | ``` 41 | #### Linux and MacOS 42 | 1. Same thing as Windows; 43 | 2. Open Terminal; 44 | 3. Got to your Desktop folder; 45 | 4. Write 46 | ``` 47 | export MASTODON_API_BASEPATH=https://[YOUR-MASTODON-INSTANCE-DOMAIN] 48 | ``` 49 | ``` 50 | export MASTODON_API_KEY=YOUR_ACCESS_TOKEN_FROM_MASTODON 51 | ``` 52 | ``` 53 | export TWITTER_TWEETJS_FILEPATH=../tweets.js 54 | ``` 55 | 56 | ### #6 - Setting the script 57 | In the terminal (or CMD), enter in "import-tweets-mastodon" folder, located in Desktop. Write 58 | ``` 59 | npm install 60 | 61 | ``` 62 | After this 63 | ``` 64 | node import.js 65 | 66 | ``` 67 | 68 | #### Need help? 69 | 70 | - This project is open-sourced. Feel free to contribute; 71 | - If you really want support please consider [sponsoring me](https://github.com/sponsors/FGRibreau) :+1: 72 | -------------------------------------------------------------------------------- /import.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('import'); 2 | const axios = require('axios'); 3 | const env = require('common-env/withLogger')(console); 4 | const config = env.getOrElseAll({ 5 | mastodon: { 6 | api: { 7 | key: { 8 | $type: env.types.String, 9 | }, 10 | basePath: { 11 | $type: env.types.String, 12 | } 13 | } 14 | }, 15 | twitter: { 16 | excludeReplies: true, 17 | year: new Date().getFullYear(), 18 | tweetjs: { 19 | filepath: { 20 | $type: env.types.String 21 | }, 22 | } 23 | } 24 | }); 25 | function getTweets() { 26 | const vm = require('vm'); 27 | const fs = require('fs'); 28 | const _global = { 29 | window: { 30 | YTD: { 31 | tweets: { 32 | part0: {} 33 | } 34 | } 35 | } 36 | }; 37 | debug('Loading tweets...') 38 | const script = new vm.Script(fs.readFileSync(config.twitter.tweetjs.filepath, 'utf-8')); 39 | const context = vm.createContext(_global); 40 | script.runInContext(context); 41 | 42 | const tweets = Object.keys(_global.window.YTD.tweets.part0).reduce((m, key, i, obj) => { 43 | return m.concat(_global.window.YTD.tweets.part0[key].tweet); 44 | }, []).filter(_keepTweet) 45 | 46 | debug('Loading %s tweets...', tweets.length); 47 | 48 | function _keepTweet(tweet) { 49 | if (!tweet.created_at.endsWith(config.twitter.year)) { 50 | return false; 51 | } 52 | 53 | if (!config.twitter.excludeReplies) { 54 | return true; 55 | } 56 | 57 | return !tweet.full_text.startsWith('@'); 58 | } 59 | 60 | return tweets; 61 | } 62 | function importTweets(tweets) { 63 | const progress = require('progressbar').create().step('Importing tweets'); 64 | const max = tweets.length; 65 | 66 | progress.setTotal(max); 67 | 68 | function next() { 69 | const tweet = tweets.pop(); 70 | let current = 0; 71 | if (!tweet) { 72 | debug('Tweets import completed'); 73 | return; 74 | } 75 | createMastodonPost({ 76 | apiToken: config.mastodon.api.key, 77 | baseURL: config.mastodon.api.basePath 78 | },{ 79 | status: replaceTwitterUrls(tweet.full_text,tweet.entities.urls), 80 | language: tweet.lang 81 | }).then((mastodonPost) => { 82 | debug('%s/%i Created post %s', current, max, mastodonPost.url); 83 | progress.addTick(); 84 | next(); 85 | }).catch(err => { 86 | console.log(err); 87 | }); 88 | } 89 | } 90 | function replaceTwitterUrls(full_text, urls) { 91 | urls.forEach(url => { 92 | full_text=full_text.replace(url.url,url.expanded_url); 93 | }); 94 | return full_text; 95 | } 96 | function createMastodonPost({ 97 | apiToken, 98 | baseURL 99 | }, { 100 | status, 101 | language 102 | }) { 103 | return axios({ 104 | url: '/api/v1/statuses', 105 | baseURL: baseURL, 106 | method: 'POST', 107 | headers: { 108 | 'Authorization': 'Bearer ' + apiToken 109 | }, 110 | data: { 111 | status, 112 | language, 113 | visibility: "public" 114 | } 115 | }).then(function(response) { 116 | return response.data 117 | }); 118 | } 119 | importTweets(getTweets()); 120 | --------------------------------------------------------------------------------