标签 mastodon 下的文章

前言

使用 neodb.social 的API算是多一种选择.
豆瓣数据的获取还是不太方便

获取neodb的token

使用mastodon账号登录 https://neodb.social/
在右上角头像点击- 设置 - 找到更多设置
2024-12-26T00:09:57.png
点击 查看已授权的应用程序

2024-12-26T00:11:01.png
生成一个token即可

获取neodb API

在此使用项目
https://github.com/Lyunvy/neodb-shelf-api

可以部署在vercel上,过程就不赘述了

调用

在本主题的基础上修改

JS

class NeoDB {
    constructor(config) {
        this.container = config.container;
        this.types = config.types ?? ["book", "movie", "tv", "music", "game", "podcast"];
        this.baseAPI = config.baseAPI;
        this.type = "movie";
        this.status = "complete";
        this.finished = false;
        this.paged = 1;
        this.subjects = [];
        this._create();
    }

    on(event, element, callback) {
        const nodeList = document.querySelectorAll(element);
        nodeList.forEach((item) => {
            item.addEventListener(event, callback);
        });
    }

    _handleTypeClick() {
        this.on("click", ".neodb-navItem", (t) => {
            const self = t.currentTarget;
            if (self.classList.contains("current")) return;
            this.type = self.dataset.type;
            document.querySelector(".neodb-list").innerHTML = "";
            document.querySelector(".lds-ripple").classList.remove("u-hide");
            document.querySelector(".neodb-navItem.current").classList.remove("current");
            self.classList.add("current");
            this.paged = 1;
            this.finished = false;
            this.subjects = [];
            this._fetchData();
        });
    }

    _renderTypes() {
        document.querySelector(".neodb-nav").innerHTML = this.types
            .map((item) => {
                return `<span class="neodb-navItem${
                    this.type == item ? " current" : ""
                }" data-type="${item}">${item}</span>`;
            })
            .join("");
        this._handleTypeClick();
    }

    _fetchData() {
        const params = new URLSearchParams({
            type: "complete",
            category: this.type,
            page: this.paged.toString(),
        });
    
        return fetch(this.baseAPI + "?" + params.toString())
            .then((response) => response.json())
            .then((data) => {
                if (data.length) {
                    // 过滤重复项
                    data = data.filter(item => !this.subjects.some(existing => existing.item.id === item.item.id));
                    
                    if (data.length) {
                        this.subjects = [...this.subjects, ...data];
                        this._renderListTemplate();
                    }
    
                    document.querySelector(".lds-ripple").classList.add("u-hide");
                } else {
                    this.finished = true; // 没有更多数据
                    document.querySelector(".lds-ripple").classList.add("u-hide");
                }
            });
    }

    _renderListTemplate() {
        document.querySelector(".neodb-list").innerHTML = this.subjects
            .map((item) => {
                const coverImage = item.item.cover_image_url;
                const title = item.item.title;
                const rating = item.item.rating;
                const link = item.item.id;

                return `<div class="neodb-item">
                    <img src="${coverImage}" referrerpolicy="no-referrer" class="neodb-image">
                    <div class="neodb-score">
                        ${rating ? `<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M12 20.1l5.82 3.682c1.066.675 2.37-.322 2.09-1.584l-1.543-6.926 5.146-4.667c.94-.85.435-2.465-.799-2.567l-6.773-.602L13.29.89a1.38 1.38 0 0 0-2.581 0l-2.65 6.53-6.774.602C.052 8.126-.453 9.74.486 10.59l5.147 4.666-1.542 6.926c-.28 1.262 1.023 2.26 2.09 1.585L12 20.099z"></path></svg>${rating}` : ""}
                    </div>
                    <div class="neodb-title">
                        <a href="${link}" target="_blank">${title}</a>
                    </div>
                    
                </div>`;
            })
            .join("");
    }

    _handleScroll() {
        let isLoading = false; // 标志位,表示是否正在加载数据
        let lastScrollTop = 0; // 上一次的滚动位置
    
        window.addEventListener("scroll", () => {
            const scrollY = window.scrollY || window.pageYOffset;
            const moreElement = document.querySelector(".block-more");
    
            // 检查滚动到底部的条件
            if (
                moreElement.offsetTop + moreElement.clientHeight <= scrollY + window.innerHeight &&
                document.querySelector(".lds-ripple").classList.contains("u-hide") &&
                !this.finished &&
                !isLoading // 确保没有正在加载数据
            ) {
                isLoading = true; // 设置标志位为 true,表示正在加载数据
                document.querySelector(".lds-ripple").classList.remove("u-hide");
                this.paged++;
                this._fetchData().finally(() => {
                    isLoading = false; // 数据加载完成后,重置标志位
                });
            }
    
            // 更新上一次的滚动位置
            lastScrollTop = scrollY;
        });
    }

    _create() {
        if (document.querySelector(".neodb-container")) {
            const container = document.querySelector(this.container);
            if (!container) return;
            container.innerHTML = `
                <nav class="neodb-nav"></nav>
                <div class="neodb-list"></div>
                <div class="block-more block-more__centered">
                    <div class="lds-ripple"></div>
                </div>
            `;
            this._renderTypes();
            this._fetchData();
            this._handleScroll();
        }
    }
}

CSS

.neodb-container{--db-item-width:150px;--db-item-height:180px;--db-music-width:150px;--db-music-height:150px;--db-primary-color:var(--farallon-hover-color);--db-background-white:var(--farallon-background-white);--db-background-gray:var(--farallon-background-gray);--db-border-color:var(--farallon-border-color);--db-text-light:var(--farallon-text-light);}.neodb-nav{padding:30px 0 20px;display:flex;align-items:center;flex-wrap:wrap;}.neodb-navItem{font-size:20px;cursor:pointer;border-bottom:1px solid rgba(0,0,0,0);transition:0.5s border-color;display:flex;align-items:center;text-transform:capitalize;}.neodb-navItem.current,.neodb-navItem:hover{border-color:inherit;}.neodb-navItem{margin-right:20px;}.neodb-score svg{fill:#f5c518;margin-right:5px;}.neodb-list{display:flex;align-items:flex-start;flex-wrap:wrap;}.neodb-image{width:var(--db-item-width);height:var(--db-item-height);object-fit:cover;border-radius:4px;}.neodb-image:hover{box-shadow:0 0 10px var(--db-border-color);}.neodb-title{margin-top:2px;font-size:14px;line-height:1.4;}.neodb-title a:hover{color:var(--db-primary-color);text-decoration:underline;}.neodb-genreItem{background:var(--db-background-gray);font-size:12px;padding:5px 12px;border-radius:4px;margin-right:6px;margin-bottom:10px;line-height:1.4;cursor:pointer;}.neodb-genreItem.is-active,.neodb-genreItem:hover{background-color:var(--db-primary-color);color:var(--db-background-white);}.neodb-genres{padding-bottom:15px;display:flex;flex-wrap:wrap;}.neodb-genres.u-hide + .neodb-list{padding-top:10px;}.neodb-score{display:flex;align-items:center;font-size:14px;color:var(--db-text-light);}.neodb-item{width:var(--db-item-width);margin-right:20px;margin-bottom:20px;position:relative;}.neodb-item__music img{width:var(--db-music-width);height:var(--db-music-height);object-fit:cover;}.neodb-date{position:relative;font-size:20px;color:var(--farallon-text-light);font-weight:900;line-height:1;}.neodb-date::before{content:"";position:absolute;top:0.5em;bottom:-2px;left:-10px;width:3.4em;z-index:-1;background:var(--farallon-hover-color);opacity:0.3;transform:skew(-35deg);transition:opacity 0.2s ease;border-radius:3px 8px 10px 6px;}.neodb-date{margin-top:30px;margin-bottom:10px;}.neodb-dateList{padding-left:15px;padding-top:5px;padding-right:15px;}.neodb-card__list{display:flex;align-items:center;padding:15px 0;border-bottom:1px dotted var(--farallon-border-color);font-size:14px;color:rgba(0,0,0,0.55);}.neodb-card__list:last-child{border-bottom:0;}.neodb-card__list .title{font-size:18px;margin-bottom:5px;}.neodb-card__list .rating{margin:0 0 0px;font-size:14px;line-height:1;display:flex;align-items:center;}.neodb-card__list .rating .allstardark{position:relative;color:#f99b01;height:16px;width:80px;background-repeat:repeat;background-image:url("../images/star.svg");background-size:auto 100%;margin-right:5px;}.neodb-card__list .rating .allstarlight{position:absolute;left:0;color:#f99b01;height:16px;overflow:hidden;background-repeat:repeat;background-image:url("../images/star-fill.svg");background-size:auto 100%;}.neodb-card__list img{width:80px;border-radius:4px;height:80px;object-fit:cover;flex:0 0 auto;margin-right:15px;}.neodb-titleDate{display:flex;flex-direction:column;line-height:1.1;margin-bottom:10px;flex:0 0 auto;margin-right:15px;align-items:center;}.neodb-titleDate__day{font-weight:900;font-size:44px;}.neodb-titleDate__month{font-size:14px;color:var(--farallon-text-light);font-weight:900;}.neodb-list__card{display:block;}.neodb-dateList__card{display:flex;flex-wrap:wrap;align-items:flex-start;}.neodb-listBydate{display:flex;align-items:flex-start;margin-top:15px;}@media (max-width:600px){.neodb-listBydate{flex-direction:column;}}

HTML

    <div class="neodb-container"></div>
<script>
const neodb = new NeoDB({
    container: ".neodb-container",
    baseAPI: "https://neodb.imsun.org/api",
    types: ["book", "movie", "tv", "music", "game"],
});    
</script>

其中https://neodb.imsun.org/为 部署在 vercel 的绑定域名,可自行更改


[article id="1643"]

根据 Cloudflare Workers 获取API 拉取的时候有些慢,所以使用php 获取到json 数据并保存在本地,通过计划任务定时生成.

<?php
$userId = '110711427149362311'; //改为自己的
$instance = 'jiong.us'; //改为自己的
$baseUrl = 'https://' . $instance . '/api/v1/accounts/' . $userId . '/statuses';
$limit = 20; // Maximum limit per page
$toots = [];

for ($i = 0; $i < 25; $i++) { // 25 pages * 40 toots per page = 1000 toots
    $ch = curl_init();
    $url = $baseUrl . '?limit=' . $limit;

    if (isset($lastId)) {
        $url .= '&max_id=' . $lastId;
    }

    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    //curl_setopt($ch, CURLOPT_HTTPHEADER, [
    //    'Authorization: Bearer ' . $accessToken
    //]);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);

    if (empty($data)) {
        break;
    }

    foreach ($data as $toot) {
        if (!isset($toot['reblog']) && !isset($toot['in_reply_to_id'])) {
            $toots[] = $toot;
        }
    }

    $lastId = end($data)['id'];
}

// Now $toots contains up to 1000 toots
$jsonData = json_encode($toots, JSON_PRETTY_PRINT);
file_put_contents('toot.json', $jsonData);
?>

保存为toot.php, 访问 toot.php 则会在同级目录生成 toot.json.

创建一个定时任务定时访问toot.php


Mastodon 是什么?

是自己也是全世界.

它可以私有化部署,所以说他是自己.

它可以连通世界,与世界各地的实例进行交互.所以也可以说是全世界.

它之所以被称之为联邦宇宙,自然与联邦政府类似,各自为政,但是相互连接.

相关文章

[article id="1663"]
[article id="1537"]
[article id="1469"]

如何部署

直接使用docker compose部署是不可行的,需要按照步骤进行

创建目录

mkdir -p /home/mastodon/mastodon

进入目录

cd /home/mastodon/mastodon

拉取镜像

docker pull ghcr.io/mastodon/mastodon

修改docker compose配置文件

wget https://raw.githubusercontent.com/mastodon/mastodon/main/docker-compose.yml

修改docker compose文件中的版本号

初始化PostgreSQL

  • 重要!!!!!
docker run --name postgres14 -v /home/mastodon/mastodon/postgres14:/var/lib/postgresql/data -e   POSTGRES_PASSWORD=设置数据库管理员密码 --rm -d postgres:14-alpine

进入数据库

docker exec -it postgres14 psql -U postgres

创建用户名mastodon的密码

CREATE USER mastodon WITH PASSWORD '数据库密码(最好和数据库管理员密码不一样)' CREATEDB;

停止docker

docker stop postgres14

配置Mastodon

/home/mastodon/mastodon根文件夹中创建空白.env.production文件

cd /home/mastodon/mastodon
touch .env.production

运行引导

docker-compose run --rm web bundle exec rake mastodon:setup

按照提示进行操作

Below is your configuration, save it to an .env.production file outside Docker:之后会出现配置文件的数据,复制下来

写入.env.production

启动Mastodon

docker-compose down
docker-compose up -d

文件夹赋权

chown 991:991 -R ./public
chown -R 70:70 ./postgres14
docker-compose down
docker-compose up -d

创建管理员

docker exec mastodon-web-1 tootctl accounts create USERNAME --email EMAIL --confirmed --role Owner

至此完成


前言

为了更加方便的同步内容,可以作备份之用.

声明

代码和功能实现来自于@大大的蜗牛 原理是使用webhook. 在发布内容时,触发脚本运行.

步骤

同步脚本

脚本中API_HOST为memos的API AUTHORIZATION为memos中Token CONTENT_URL111363033003475492为mastodon的用户的ID 获得用户ID的方法可以参见

[article id="1469"]

#!/bin/sh

# API 和 Token
API_HOST="https://memos.ee/api/v1/memo"
AUTHORIZATION="Bearer eyJhbGciOiJIUzI1NiIsImtpZCI6InYxIiwidHlwIjoiSldUIn0.eyJuYW1lIjoiamtqb3kiLCJpc3MiOiJtZW1vcyIsInN1YiI6IjEiLCJhdWQiOlsidXNlci5hY2Nlc3MtdG9rZW4iXSwiaWF0IjoxNjk3ODc0NTk2fQ.jNGMDE1YVX4Qj6hNhmrxb63WlRM5kGX10k_qRXH6ID4"

# 原始内容
CONTENT_URL="https://09j.cn/api/v1/accounts/111363033003475492/statuses?limit=1"
CONTENT=$(curl --connect-timeout 60 -s $CONTENT_URL | jq -r &#039;.[0]&#039;)
# mastodon
MASTODON_URL=$(echo $CONTENT | grep -oP &#039;https:\/\/09j\.cn\/@[^\/]+\/\d+&#039;)
DUDU_CONTENT="[自动转发自我的Mastodon]($MASTODON_URL)"

MENTIONS=$(echo $CONTENT | jq -r &#039;.mentions[]&#039;)
if [ ! -z "$MENTIONS" ]; then
  echo "Skipping status mention! $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")"
  echo ======================================================
  exit 0
fi

MEDIA=$(echo $CONTENT | jq -r &#039;.media_attachments&#039;)
# 判断 Media 的内容
if [ "$MEDIA" != "null" ]; then
  MEDIAS=$(echo $CONTENT | jq -r &#039;.media_attachments[] | select(.type=="image") | .url&#039;)
  # 拼接图片
  images=""
  for url in $MEDIAS; do
    images="$images![image]($url)\n"
  done
  TEXT=$(echo "$CONTENT" | jq -r &#039;.content&#039; | sed &#039;s/ +/ /g&#039; | lynx -dump -stdin -nonumbers -nolist | tr -d &#039;\n&#039; | sed &#039;/^$/N;s/\n\n/\n/g&#039; | sed &#039;s/^[[:space:]]*//;s/[[:space:]]*$//&#039;)
  TEXT="$TEXT\n$DUDU_CONTENT"
  TEXT="$TEXT\n$images"

else
   # 普通内容
  TEXT=$(echo "$CONTENT" | jq -r &#039;.content&#039; | sed &#039;s/ +/ /g&#039; | lynx -dump -stdin -nonumbers -nolist | tr -d &#039;\n&#039; | sed &#039;/^$/N;s/\n\n/\n/g&#039; | sed &#039;s/^[[:space:]]*//;s/[[:space:]]*$//&#039;)
  TEXT="${TEXT}\n$DUDU_CONTENT"
fi

curl -X POST \
  -H "Accept: application/json" \
  -H "Authorization: $AUTHORIZATION" \
  -d "{ \"content\": \"$TEXT\" }" \
  $API_HOST

echo Sync Mastodon to Memos Successful! $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")
echo ======================================================

我稍微做了一点修改.在发布到memos的同时贴上原本mastodon的原文链接. 由于我不会写规则,就随意写了匹配规则 我的实例为09j.cn 不需要则删除以下

MASTODON_URL=$(echo $CONTENT | grep -oP &#039;https:\/\/09j\.cn\/@[^\/]+\/\d+&#039;)
DUDU_CONTENT="[自动转发自我的Mastodon]($MASTODON_URL)" 
TEXT="$TEXT\n$DUDU_CONTENT"

即可

部署webhook

Docker镜像是根据官方dockerfile增加了中文支持,

推荐使用docker-compose部署 编辑docker-compose.yaml内容为

services:
  webhook:
    image: jkjoy/webhook
    container_name: webhook
    command: -verbose -hooks=hooks.yml -hotreload
    environment:
      - TZ=Asia/Chongqing #中国时区
      - LANG=C.UTF-8  #中文支持
    volumes:
      - ./config:/config:ro
    ports:
      - 9000:9000
    restart: always

在根目录下创建config目录,并在config下创建hooks.yml文件并编辑内容为

- id: memos
  execute-command: "/config/memos.sh"
  command-working-directory: "/"

把脚本内容保存为memos.sh保存在config目录下

然后在docker-compose.yaml所在的根目录下 运行docker compose up -d即可

使用Webhook

hooks.yaml为webhook的配置

其中的execute-command为可执行脚本

webhook的访问地址格式为

服务器 ip:端口/hooks/ID

以127.0.0.1为例 访问http://127.0.0.1:9000/hooks/memos

设置mastodon

在管理员后台中管理-webhooks-新增对端 对端URL填入http://127.0.0.1:9000/hooks/memos 已启用事件选择status.created 点击新增即可在发布新的嘟嘟时同步内容到memos了.

其他

同理也可以使用webhook在发布memos时候同步到其他拥有API的服务中了.