24 дня индивеба: Импорт

Я недавно обнаружил, что пишу в твиттер десять лет с перерывами.

Подумал и решил их переложить на свой сайт, а там уже разобраться, что оставлять, что удалять.

Для этого пришлось выкачать архив Твиттера, открыть в нём tweet.js и исправить этот файл на JSON (удалив начало). В итоге в нём получается массив из твитов, с которыми можно делать что угодно.

Вот так выглядит один твит:

{
  "retweeted" : false,
  "source" : "<a href=\"https://tapbots.com/software/tweetbot/mac\" rel=\"nofollow\">Tweetbot for Mac</a>",
  "entities" : {
    "hashtags" : [ ],
    "symbols" : [ ],
    "user_mentions" : [ ],
    "urls" : [ ]
  },
  "display_text_range" : [ "0", "140" ],
  "favorite_count" : "11",
  "id_str" : "924968941260242944",
  "truncated" : false,
  "retweet_count" : "1",
  "id" : "924968941260242944",
  "created_at" : "Mon Oct 30 11:59:15 +0000 2017",
  "favorited" : false,
  "full_text" : "Подходит математик к трём логикам:\n— Что, все пойдём бухать?\n— Не знаю, — говорит первый\n— Не знаю, — говорит второй\n— Да! — говорит третий.",
  "lang" : "ru"
}

Я решил сконвертировать каждый твит в заметку, оставив ссылку на твиттер, но не конвертируя лайки/ретвиты. Получился вот такой несложный скрипт на джаваскрипте:

// всякие зависимости
const exec = require('child_process').execSync
const mkdirp = (path) => { exec('mkdir -p ' + path) }
const fs = require('fs')

// достаю интересующие меня части твита
function tweet2note(tweet) {
  let date = new Date(tweet.created_at)

  // путь к файлу: 2019/12/17/id_твита/index.md
  let path = date.toISOString()
    .split('T')[0] // 2019-12-17
    .replace(/-/g, '/') +
    '/' + tweet.id_str

  let note = {
    text: tweet.full_text,
    meta: {
      lang: tweet.lang,
      publishing_software: tweet.source,
      date: date.toISOString(),
      syndicated: {
        twitter: `https://twitter.com/marinintim/status/${tweet.id_str}`
      },
    },
    path: path,
  }

  if (tweet.entities && tweet.entities.media) {
    for (let m of tweet.entities.media) {
      if (m.type === 'photo') {
        // пока что просто вставляю <img> с src твиттера
        note.text += `
  <img src="${m.media_url_https}" />`
      }
    }
  }

  if (tweet.in_reply_to_status_id_str) {
    // добавляю ссылку на твит, на который отвечаю
  note.meta.in_reply_to = `https://twitter.com/_/status/${tweet.in_reply_to_status_id_str}`
    note.meta.in_reply_to_text = 'твит @' + tweet.in_reply_to_screen_name
  }

  return note
}

// преобразую в текст файла index.md, который 11ty превратит в HTML.
function note2file(note) {
	const meta = note.meta
	return `---
layout: note.njk
tags:
- note
- twitter_pesos
lang: ${meta.lang}
author: Тим
date: ${meta.date}
syndicated:
  twitter: ${meta.syndicated.twitter}
publishing_software: '${meta.publishing_software}'
${
  meta.in_reply_to
  ? `in_reply_to: "${meta.in_reply_to}"
in_reply_to_text: "${meta.in_reply_to_text}"`
  : ''}
---
${note.text}
`
}

console.log(`Total tweets: ${tweets.length}`)

let i = 0;
for (let tweet of tweets) {
  let note = tweet2note(tweet)
  mkdirp(note.path)
  // делаю синхронно, потому что иначе нода умирала,
  // а разбираться почему было лень
  fs.writeFileSync(note.path + '/index.md', note2file(note))
  console.log(`[${++i} / ${tweets.length}] ${note.path + '/index.md'}`)
}

В итоге оказалось, что твитов в том архиве было больше восьми тысяч, и 11ty начал несколько захлёбываться, пытаясь их засунуть на одну страницу и сделать авто-линковку.

Oh well. Пока что добавил большую часть из них в .eleventyignore, но потом непременно включу в общий сайт. На /notes пока отрисовываются 100 последних заметок, в будущем я добавлю архивы по месяцам.

В результате работы скрипта я получил восемь тысяч файлов, вот пример одного из них:

---
layout: note.njk
tags:
- note
- twitter_pesos
lang: en
author: Тим
date: 2018-01-10T17:59:56.000Z
syndicated:
  twitter: https://twitter.com/marinintim/status/951151635073519616
publishing_software: '<a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>'

---
[AT THE THINGS-NAMING FACILITY BACK IN THE DAY]
*looks at the paper with an article about man biting the dog*
- Is it about something new? Let's call it news. *stamp* Next!

*looks at the strip of photos w/ jumping horse*
- Pictures are moving? Let's call it movies. *stamp* Next!

И вот пример вёрстки, которая получается в итоге:

<article class="h-entry">
  <span class="u-author h-card">
    <img class="avatar u-photo" src="https://marinintim.fra1.digitaloceanspaces.com/tim_and_green.jpg" alt="">
    <a class="u-url h-card" href="https://marinintim.com">Тим</a>
  </span>
  ·

  <a href="/notes/2018/01/10/951151635073519616/" class="u-url">
    <time datetime="2018-01-10T17:59:56.000Z">10 января</time>
  </a>
  <div class="e-content p-name">
    <p>[AT THE THINGS-NAMING FACILITY BACK IN THE DAY]
    <em>looks at the paper with an article about man biting the dog</em></p>
    <ul>
      <li>Is it about something new? Let's call it news. <em>stamp</em> Next!</li>
    </ul>
    <p><em>looks at the strip of photos w/ jumping horse</em></p>
    <ul>
      <li>Pictures are moving? Let's call it movies. <em>stamp</em> Next!</li>
    </ul>
  </div>
  Эта же заметка <a class="u-syndication" href="https://twitter.com/marinintim/status/951151635073519616">в твиттере</a>
</article>

В архиве также есть папка tweet_media, где лежат все загруженные картинки, и их можно сопоставить с твитами по айдишнику.

Примерно по этому же принципу можно сохранить свои посты из любой соцсети:

  1. Раздобыть архив или сохранить через API все посты
  2. Написать скрипт, который переделает архив в контент сайта
  3. Шага три нет. Хотя нет, можно переписать скрипт из шага 2 ещё раз.

На днях я собираюсь утащить свои посты из ВК, о чём напишу на индивеб-вики/VK.

Если всё ещё кажется, что «мне нечего писать на своём сайте», то попробуй импортировать свой твиттер или инстаграм и обнаружишь, что тебе есть, что сказать или показать миру: так почему бы не со своего сайта?


Пока я не собираюсь удалять свой твиттер-аккаунт, но бэкапы — это всегда хорошо, а когда они ещё и доступны для просмотра — ещё лучше.

День 16: ActivityPub · День 18: Два фида

Тим опубликовал

Если понравился этот пост, то подпишись на мой Патреон