coding-train

Fractal Tree Bot with Mastodon

Mastodon

https://joinmastodon.org/
https://namu.wiki/w/%EB%A7%88%EC%8A%A4%ED%86%A0%EB%8F%88(SNS)

mastodon tree

사용 툴

시나리오

  1. 사용자가 글을 업데이트
  2. node bot이 업데이트 이벤트를 받아서 아래 항을 처리
    1. 댓글이 없는 글만 선택
    2. 글의 숫자를 찾아서 angle 변수에 저장
    3. angle 값을 인수로 sketch.js(p5-node)를 실행
    4. sketch.js 결과인 tree.pngmastodon media로 업로드
    5. mastodon에 이미지와 함께 게시글을 작성

bot.js


require('dotenv').config();
const Mastodon = require('mastodon-api');
const util = require('util');
const fs = require('fs');
const exec = util.promisify(require('child_process').exec);

console.log('Fractal-Tree Bot starting...');

const M = new Mastodon({
    client_key: process.env.CLIENT_KEY,
    client_secret: process.env.CLIENT_SECRET,
    access_token: process.env.ACCESS_TOKEN,
    timeout_ms: 60 * 1000, // optional HTTP request timeout to apply to all requests.
    api_url: 'https://twingyeo.kr/api/v1/' // optional, defaults to https://mastodon.social/api/v1/
});

const cmd = 'node sketch.js';

const stream = M.stream('streaming/user');

stream.on('message', response => {
    if (response.event === 'update' && !response.data.in_reply_to_id) {
        const id = response.data.account.id;
        const acct = response.data.account.acct;
        const content = response.data.content;

        const regex = /\d+/;
        const results = content.match(regex);
        let angle = -1;
        if (results) {
            angle = results[0];
        }

        toot(acct, id, angle)
            .then(response => console.log(response))
            .catch(error => console.error(error));
    }
}); 


async function toot(acct, reply_id, angle) {
    if (angle === -1) {
        const params = {
            status: `@${acct} Please specify an angle in degrees using digits`,
            in_reply_to_id: reply_id
        };
        const response = await M.post('statuses', params);
        return {
            success: true,
            angle: -1
        };
    } else {
        // Step 1
        const response1 = await exec(cmd + ' ' + angle);
        const out = response1.stdout.split('\n');
        const stream = fs.createReadStream('tree.png');

        // Step 2: Upload Media
        const params1 = {
            file: stream,
            description: `A randomly generated fractal tree with ${angle}`
        };
        const response2 = await M.post('media', params1);
        const id = response2.data.id;

        // Step 3
        const params2 = {
            status: `@${acct} Behold my beautiful tree with angle ${angle} degrees`,
            in_reply_to_id: reply_id,
            media_ids: [id]
        };
        const response3 = await M.post('statuses', params2);
        return {
            success: true,
            angle: angle
        };
    }
}

sketch.js


const process = require('process');
const p5 = require('node-p5');

let theta;
let a = 0;

if (process.argv[2]) {
    a = Number(process.argv[2]);
    console.log('a=' + process.argv[2]);
}

function sketch(p) {
    p.setup = () => {
        const canvas = p.createCanvas(640, 360);
        p.background(51);
        p.stroke(255);
        theta = p.radians(a);
        p.translate(p.width / 2, p.height);
        p.line(0, 0, 0, -120);
        p.translate(0, -120);
        branch(p, 120);
        p.saveCanvas(canvas, 'tree', 'png').then(() => {
            console.log('saved canvas as tree.png');
        }).catch(console.error);
        p.noLoop();
    }
}

function branch(p, h) {
    // Each branch will be 2/3rds the size of the previous one
    h *= 0.66;

    // All recursive functions must have an exit condition!!!!
    // Here, ours is when the length of the branch is 2 pixels or less
    if (h > 2) {
        p.push();
        p.rotate(theta);
        p.line(0, 0, 0, -h);
        p.translate(0, -h);
        branch(p, h);
        p.pop();

        p.push();
        p.rotate(-theta);
        p.line(0, 0, 0, -h);
        p.translate(0, -h);
        branch(p, h);
        p.pop();
    }
}

let p5Instance = p5.createSketch(sketch);