Subskrybuj newsletter o cyfrowej humanistyce i innowacjach w sektorze kultury

Budujemy muzealnego bota na Bluesky 🤖

Okładka lekcji: przypominająca robota w fabryce samochodów maszyna, powielająca wizerunki kociej mordki / Źródło: AI

Wprowadzenie

Czyż nie powinniśmy wnieść własnego wkładu w potwierdzenie prawdziwości teorii martwego internetu 😉? Teoria ta zakłada, że większość interakcji online to efekt działania botów - automatycznie działających programów, które publikują treści w mediach społecznościowych, komentarze na forach i artykuły na portalach.

Nasz bot ma jednak wprost informować, że jest botem, i automatycznie (co jakiś czas) publikować na Bluesky wizerunki, opisy i odnośniki do zbiorów polskich muzeów. Na początek wykorzystajmy bazę ponad 300 obiektów z fascynujących zbiorów Muzeum Narodowego w Krakowie. Dane na temat zbiorów dostępne są już na GitHubie.

Jak zostały zebrane? Żeby się tego dowiedzieć, zorganizuj ze mną w swojej instytucji warsztaty z maszynowego korzystania ze zbiorów kultury, możesz też zapoznać się z lekcją poświęconą web scrapingowi.

Cele lekcji

Celem lekcji jest przygotowanie bota, który na platformie Bluesky automatycznie publikował będzie informacje o wybranych obiektach ze zbiorów MNK. Takiego bota przygotować też można dla platformy X (dawniej Twittera), jednak skoncentrujemy się dziś na Bluesky, ponieważ umiemy już łączyć się z API tej platformy. W listopadzie 2024 roku z Bluesky korzystało 20 mln użytkowników, platforma ta obiecuje bardziej podmiotowe traktowanie użytkowników i pełną otwartość danych.

Efekty

Efektem naszej pracy będzie kod, uruchamiający działającego na Bluesky bota, który korzystać będzie z danych zapisanych w pliku CSV. Nasz program napiszemy w języku R, co nie jest na pewno pierwszym wyborem jeśli chodzi o przygotowywanie oprogramowania dla botów (lepszym rozwiązaniem jest stosowanie JavaScript, który może działać na każdym serwerze z Node.js). Zależy nam jednak na poznaniu samej metody przygotowywania i uruchamiania bota, a nie na jego oficjalnym uruchomieniu na Bluesky.

Wymagania

Konieczne jest konto na Bluesky i na platformie Posit.cloud lub środowisko R i program RStudio na własnym komputerze. Będziemy pracować w języku R, używając metod biblioteki bskyr do publikowania oraz cronR do ustawienia automatycznego wykonywania kodu bota.

Część merytoryczna

Przed rozpoczęciem pracy w R zastanówmy się, jakie korzyści dla instytucji kultury i dziedzictwa może dać uruchomienie bota na Bluesky (czy na innej dowolnej platformie społecznościowej). Następnie założymy nowy projekt w Posit.cloud, zainstalujemy bibliotekę bskyr i napiszemy kod, pozwalający uruchomić bota.

Boty instytucji kultury w mediach społecznościowych

Korzystanie z mediów społecznościowych to nieodpłatna praca.

Jak wszystkie firmy prowadzące media społecznościowe, Facebook zarabia na podstawowych aktywnościach życiowych: naszej potrzebie utrzymywania przyjaźni i więzi rodzinnych, śledzenia wiadomości, kształtowania przekonań o świecie i dzielenia się tymi przekonaniami. „To, co na pierwszy rzut oka wydaje się darmową aktywnością komunikacyjną” – napisali badacze z Uniwersytetu w Lüneburgu i Uniwersytetu w Essex w niedawnym artykule – „w rzeczywistości jest formą ‘darmowej pracy’”.

Taka darmowa praca jest też udziałem pracowników i pracowniczek instytucji kultury, szczególnie tych, którzy/które nie mogą korzystać z wsparcia wewnętrznych działów PR. W ramach swoich obowiązków merytorycznych muszą oni dodatkowo udostępniać w mediach społecznościowych określone treści i zarządzać społecznością, która wokół nich powinna się integrować.

Przy dużej liczbie platform, na których należy być aktywnym, boty mogą być namiastką aktywności ludzkiego pracownika. Nie będzie to oczywiście pełne zaangażowanie, ale może pozwolić - bez dodatkowej pracy - obsłużyć kolejną ścieżkę komunikacji i potencjalne źródło ruchu na stronę instytucji. Warto patrzeć na boty w mediach społecznościowych jak na pewną grę z platformą, która wymaga od nas zaangażowania i danych. Za pomocą bota możemy dać jedno i drugie i zrobić to właściwie bez osobistego zaangażowania i codziennej pracy.

Bot potrzebuje danych

Pracą, którą bezwzględnie musimy wykonać, aby móc uruchomić bota, jest przygotowanie danych, jakie będzie udostępniał na Bluesky. Prostym i dobrym pomysłem jest użyć go do publikowania odnośników do wybranych obiektów z kolekcji cyfrowej - naszym celem będzie bezkosztowe i automatyczne generowanie ruchu na naszej stronie domowej (muzeum cyfrowego).

Skorzystajmy z przygotowanych wcześniej danych, dostępnych na GitHubie. Dane te zawierają informacje o 368 obiektach ze zbiorów Muzeum Narodowego w Krakowie, w tym kolumny:

  • object_id: porządkowy identyfikator obiektu, nie mający żadnych związków z id obiektu w repozytorium cyfrowym,
  • url: adres URL obiektu na stronie zbiory.mnk.pl,
  • desc: krótki opis obiektu,
  • img_src: adres URL reprodukcji cyfrowej danego obiektu w mniejszym rozmiarze,
  • full_img_src: adres URL dobrej jakości reprodukcji cyfrowej danego obiektu,
  • is_sent: informacja, czy obiekt został udostępniony już na Bluesky (1) lub nie został jeszcze udostępniony (0),

Takie dane wystarczą do automatycznego publikowania wpisów na Bluesky. Jeśli takie wpisy zostaną zauważone, a umieszczone w nich odnośniki kliknięte, możemy cieszyć się ze skuteczności bota. Uruchomione raz narzędzie będzie działać na korzyść naszej instytucji, już nawet bez naszego zaangażowania. Baza informacji o 368 obiektach, przygotowana w 15 min., wystarczy na cały rok publikowania na Bluesky (przy założeniu, że publikujemy raz dziennie).

Publikowanie na Bluesky w R

Zacznijmy pracę w R. Wchodzimy na Posit.cloud lub do RStudio na własnym komputerze. Pobieramy i wczytujemy do środowiska bibliotekę bskyr:

install.packages('bskyr')
library(bskyr)

Musimy autoryzować się w Bluesky - opis tego, jak to zrobić, dostępny jest w lekcji Czytamy posty muzeów na Bluesky z wykorzystaniem R. Poniżej kod, do którego wrzucamy nazwę użytkownika i hasło aplikacji:

# nazwa użytkownika - wpisz tu swoją nazwę użytkownika
# (nie nazwę aplikacji)
set_bluesky_user('XXXXXXXXX.bsky.social')
# hasło - podmień na hasło założonej przed chwilą aplikacji
set_bluesky_pass('XXXX-XXXX-XXXX-XXXX')

Pamiętajmy, że ta metoda autoryzacji działa wyłącznie w jednej sesji. Po zamknięciu i ponownym uruchomieniu, będziemy musieli autoryzować się ponownie.

Podstawową metodą biblioteki bskyr, którą wykorzystamy do automatycznego publikowania, będzie bs_post:

bs_post(
  text,
  images,
  images_alt,
  video,
  video_alt,
  langs,
  reply,
  quote,
  emoji = TRUE,
  max_tries,
  user = get_bluesky_user(),
  pass = get_bluesky_pass(),
  auth = bs_auth(user, pass),
  clean = TRUE
)

Zwróćmy uwagę, że funkcja ta przyjmuje wiele parametrów, kształtujących treść publikowanego automatycznie posta oraz jego relacje z innymi (np. quote i replay). Opisy wszystkich tych parametrów dostępne są w dokumentacji metody, my skorzystamy tylko z podstawowych.

Spróbujmy najpierw ręcznie wysłać post na nasze konto w Bluesky:

download.file("https://cdn-zbiory.mnk.pl/upload/cache/multimedia_detail/e7/90/e790c7242e5bf12295c9d58f6750d0f0.jpg", "scan.jpg")

image_blob <- bs_upload_blob("scan.jpg", clean = FALSE)

bs_post(
  text = "Portret aktora Nakamura Nakazo obok ołtarza. Deszcz, Katsukawa, Shunsho (1726-1792), https://zbiory.mnk.pl/pl/katalog/14759",
  images = image_blob,
  images_alt = "Portret aktora Nakamura Nakazo obok ołtarza. Deszcz, Katsukawa, Shunsho (1726-1792)"
)

Kod wydaje się skomplikowany, ale uważna lektura pozwoli nam go zrozumieć.

Zacznijmy od pobrania pliku z cyfrową reprodukcją interesującego nas obiektu. Funkcja download.file przyjmuje dwa parametry - adres URL pliku oraz jego nazwę. Adres URL pliku mamy w naszych danych źródłowych, w kolumnie img_src. Dlaczego nie korzystamy z dobrej jakości, dużych plików, których adresy mamy w kolumnie full_img_src? API Bluesky standardowo przyjmuje pliki o wadze do 1MB, pliki cięższe należy wysłać za pomocą kodowania Base64. Automatyczne wysyłanie postu z takim plikiem jest więc nieco bardziej skomplikowane i na razie je pominiemy.

Kiedy mamy już w środowisku plik scan.jpg, musimy wysłać go do Bluesky. Nie będziemy jeszcze publikować postu, tylko udostępnimy plik do otwartego repozytorium danych Bluesky. Jeśli nie odwołamy się do tego udostępnienia w ciągu kilku sekund, plik zostanie automatycznie skasowany.

Deponujemy plik za pomocą funkcji bs_upload_blob. Czym jest blob?

str(image_blob)
# List of 1
# $ :List of 1
#  ..$ blob:List of 4
#  .. ..$ $type   : chr "blob"
#  .. ..$ ref     :List of 1
#  .. .. ..$ $link: chr "bafkreicqpm5iddb6tyvde2l6c6e7j3l2q5ezgx4aorqhcywwmizk4qbcam"
#  .. ..$ mimeType: chr "image/jpeg"
# .. ..$ size    : int 123077

Blob to skrót od Binary Large Object i odnosi się do dużych danych binarnych, które są przechowywane jako pojedynczy obiekt w bazie danych lub jakimś systemie plików czy repozytorium. Blob może zawierać dane, które nie są w prosty sposób przechowywane jako tekst, takie jak obrazy, filmy, pliki audio czy inne pliki binarne. Jak widać na powyższym kodzie, pokazującym strukturę bloba, wygenerowanego z pliku scan.jpg, znajduje się w nim identyfikator pliku oraz jego metadane (np. wielkość czy typ).

bs_post(
  text = "Portret aktora Nakamura Nakazo obok ołtarza. Deszcz, Katsukawa, Shunsho (1726-1792), https://zbiory.mnk.pl/pl/katalog/14759",
  images = image_blob,
  images_alt = "Portret aktora Nakamura Nakazo obok ołtarza. Deszcz, Katsukawa, Shunsho (1726-1792)"
)

Mając już udostępniony blob w systemie danych Bluesky, możemy się do niego odwołać i przygotować cały wpis, co pokazuje kod umieszczony powyżej. Parametr text jest oczywisty - umieszczamy tu treść wpisu, możemy dodać też adres URL, który zostanie automatycznie podlinkowany. W parametrze images umieszczamy blob (rerferencję do już opublikowanego pliku). Parametr images_alt doda alternatywny podpis pod umieszczaną przez nas grafikę - to ważne dla osób korzystających z platformy za pomocą czytników ekranowych.

Po uruchomieniu tej funkcji możemy podejrzeć efekt jej działania:

Opublikowany automatycznie post / Źródło: test-dev-test.bsky.social

Udało się nam maszynowo opublikować wpis na Bluesky!

Bot: podstawowy kod

Pomysł na bota jest taki, żeby - w określonych odstępach czasu - pobierał z danych informacje o losowym wpisie i następnie publikował go automatycznie na Bluesky. Poza kodem, który zarządzał będzie pobieraniem i wysyłaniem danych, potrzebujemy środowiska, w którym nasz kod będzie regularnie uruchamiany. Nie powinniśmy - przynajmniej w wersji produkcyjnej - korzystać z Posit.cloud, bo system ten ma ograniczenia czasu działania skryptów i sesji oraz brak dostępu do oprogramowania zarządzającego automatycznym wykonywaniem skryptów. Spróbujmy teraz w Posit.cloud przygotować i przetestować kod źródłowy bota, następnie uruchomimy go już na własnym komputerze tak, aby wykonywał się co 5 minut.

Musimy przepisać nasz kod wysyłania postów tak, żeby bot losował obiekt, o którym napisze w poście na Bluesky. W tym celu zakładamy nowy dokument R (niech się nazywa bot.R). Nasze dane znajdują się w pliku bot_data.csv, dostępnym już na Githubie. Musimy umieścić ten plik w środowisku - wybieramy opcję Upload w module Files (prawy dolny róg okna edytora Posit.cloud).

# 1. pobranie danych z pliku

library(dplyr)
# wczytanie danych
bot_data <- read.csv("bot_data.csv", row.names = NULL)
# wybór wszystkich niewysłanych obiektów
allowed_rows <- bot_data %>% filter(is_sent == 0) 
# wygenerowanie pseudolosowego wskażnika - numeru wiersza, który będzie przetwarzany
pointer <- sample(allowed_rows$object_id, 1)
# pobranie danych
post_data <- bot_data %>% filter(object_id == pointer)
# ustawienie w danych is_sent na 1 w bot_data
bot_data$is_sent[bot_data$object_id == pointer] <- 1
# nadpisanie pliku danych z aktualizacją kolumny is_sent
write.csv(bot_data, "bot_data.csv", row.names = FALSE)

# 2. Przygotowanie danych
# korzystamy z post_data
# str(post_data)
#'data.frame':	1 obs. of  6 variables:
#  $ object_id   : int 83
# url         : chr "https://zbiory.mnk.pl/pl/katalog/323348"
# desc        : chr "Wieczór nad Sekwaną, Gierymski, Aleksander (1850-1901)"
#$ img_src     : chr "https://cdn-zbiory.mnk.pl/upload/cache/multimedia_detail/89/0a/890ae49e3c83d434468bd7a844b0ab08.jpg"
#$ full_img_src: chr "https://cdn-zbiory.mnk.pl/upload/multimedia/89/0a/890ae49e3c83d434468bd7a844b0ab08.jpg"
#$ is_sent     : int 0

# przygotowanie bloba

library(bskyr)

set_bluesky_user('XXXXXX.social')
set_bluesky_pass('XXX-XXXX-XXXX-XXXX')

download.file(post_data$img_src, "temp.jpg")

# czekamy 15 sekund, żeby nie wysyłać blobu przed pobraniem obrazka
Sys.sleep(15)

image_blob <- bs_upload_blob("temp.jpg", clean = FALSE)

# 3. Wysłanie danych

post_description <- paste(post_data$desc, post_data$url)

bs_post(
  text = post_description,
  images = image_blob,
  images_alt = post_data$desc
)

Uruchomienie całego kodu pozwoli nam automatycznie opublikować informacje o losowym obiekcie. Kod nie jest idealny, nie ma np. obsługi błędów, choćby wynikających z niedostępności pliku graficznego lub braku wolnych obiektów do publikacji. Ale na pewno nadaje się do testów. Spróbujmy ustawić automatyczne uruchamianie bota co 5 minut.

Opublikowany automatycznie post / Źródło: test-dev-test.bsky.social

Bot: automatyczne publikowanie z cronR

Każdorazowe uruchomienie skryptu bot.R powinno skutkować opublikowaniem postu na Bluesky. Możemy zautomatyzować ten proces, niestety nasze rozwiązanie nie zadziała na Posit.cloud (możemy je uruchomić na własnym komputerze w RStudio). Użyjmy biblioteki cronR.

Cron to narzędzie do automatyzacji zadań w systemach operacyjnych Unix/Linux (takich jak np. Ubuntu). Cron pozwala na uruchamianie skryptów lub komend w wybranym czasie i z wybraną częstotliwością. Nasz skrypt bot.R ma być uruchamiany co 5 minut - zarządzanie cronem za pomocą R umożliwia nam właśnie pakiet cronR. Osoby korzystające z Windowsa mogą zainteresować się pakietem taskscheduleR.

Oto nasz kod:

install.packages("cronR")
library(cronR)

# Ścieżka do skryptu R
rscript_path <- "bot.R"

# Generowanie polecenia dla cron
cmd <- cron_rscript(rscript_path)

# Dodanie zadania do Crontab
cron_add(
  command = cmd,               # Polecenie do uruchomienia
  frequency = "*/5 * * * *",   # Co 5 minut
  id = "publish_task",         # Identyfikator zadania
  description = "Publikowanie co 5 minut"
)

# Sprawdzenie listy aktywnych zadań
cron_ls()

# stop
# cron_rm(id = "publish_task")

Aby bot działał prawidłowo, należy uzupełnić plik bot.R o następujące dwie linijki, dodane na początku kodu:

# uporzadkowanie odwołań do plików
# ustawienie ścieżki bieżącej
# w której znajduje się plik bot.R i bot_data.csv
setwd("/sciezka/do/katalogu/")
# poprawienie błędu z zależnościami
library(jsonlite)

Działa!

Opublikowane automatycznie posty / Źródło: test-dev-test.bsky.social

Podczas działania bota zapisywany będzie plik bot.log, w którym znajdziemy informacje o ew. błędach działania skryptu.

Aby zatrzymać bota - usunąć zadanie z harmonogramu, wykorzystujemy metodę cron_rm:

cron_rm(id = "publish_task")

Podsumowanie

Dzięki tej lekcji dowiedzieliśmy się, jak przygotować bota pod platformę Bluesky. Poza treściami, które mają być publikowane, trzeba przygotować kod źródłowy, pozwalający na łączenie się z API, oraz środowisko do regularnego, automatycznego wykonywania tego kodu. Kod dostępny jest na GitHubie, wystarczy tylko podmienić swoje dane dostępowe.

Wykorzystanie metod

Obecność botów w naszym codziennym doświadczaniu internetu jest czasem bardzo ukryta: kto z nas, czytając hasła na Wikipedii, zwraca uwagę, że ich współautorami są boty? Boty na Twitterze mają być już tak powszechne, że zdaniem niektórych użytkowników, platforma ta zaczyna przypominać wyludnione miasto (ghost town) i staje się przestrzenią, w której to głównie boty dyskutują z botami. Czy tak też będzie z Bluesky?

Boty mają fundamentalne znaczenie m.in. w archiwistyce Webu (np. w wykrywaniu i gromadzeniu zasobów do archiwizacji), mogą być też kreatywnymi kreacjami lub narzędziami - niestety ostatnie zmiany na X spowodowały, że duża część użytecznych botów dostępnych wcześniej na tej platformie przestała działać.

Pomysł na warsztat

Lekcja jest gotowym elementem warsztatu z R. Warsztat no-code z tej tematyki mógłby obejmować zaprojektowanie (zaplanowanie) bota instytucji kultury. Jakie zadania mógłby wykonywać? Jakie treści publikować? Czy dałoby się użyć go do wsparcia komunikacji z publicznością? Jakie ograniczenia i problemy mogą wyniknąć z jego wdrożenia do modelu komunikowania się instytucji?