스티밋 보팅 챗봇 만들기 — (3) 서버에서 Steem API 구성하기

in kr •  3 months ago

이전 포스트에서는 Dialogflow를 활용하여 문장을 학습하고 이에 대한 의도와 성분 분석을 수행하였습니다.

이번 포스트에서는 Dialogflow의 웹훅을 통해 연동이 될 서버에서 액션을 처리하기 위해 Steem API를 구성해보겠습니다.

우선, Steem API를 가지고 무엇을 할 수 있는지 확인해보도록 하죠.

  • 스팀 api 소개 및 구성

https://developers.steem.io/ 에 가보면, 스팀에서 공식적으로 지원하는 API 목록과 다양한 개발 언어로 활용할 수 있는 방법이 잘 나와있습니다.

스크린샷 2018-07-10 오후 12.12.59.png

우리는 이 프로젝트에서 사용할 몇가지 API들만 살펴보도록 할게요.
또한, 자바스크립트를 사용하여 호출하려고 합니다. 이에 대한 공식 라이브러리는 다음에 링크되어 있지요. https://github.com/steemit/steem-js/

const query = {"tag": "kr", "limit": 2};
steem.api.getDiscussionsByCreated(query, function(err, result) {
   console.log(err, result);
});

steem.api.getDiscussionsByHot(query, function(err, result) {
  console.log(err, result);
});

이는 현재 스팀에 올라와 있는 포스트를 생성순(ByCreated)/핫한순(ByHot) 으로 가져오는 API 입니다. query는 오브젝트 형태로 tag는 말 그대로 해당 태그를 가지고 있는 포스트, limit은 가져오는 포스트의 갯수입니다. 이를 실행하여 가져오는 반환값은 JSON-Array 형태로서, 필요한 값을 추출하여 사용할 수 있습니다.

steem.broadcast.comment(wif, parentAuthor, parentPermlink, author, permlink, title, body, jsonMetadata, function(err, result) {
  console.log(err, result);
});

이는 스팀잇 특정 포스트에 코멘트를 달 수 있는 액션을 수행하는 API 입니다. 이는 포스팅키(wif)가 필요하며, 이밖에 해당 포스팅 저자 id, 링크, 코멘트 저자 id, 링크, 내용 등이 파라미터로 세팅됩니다. 자세한 내용은 추후에 다시 설명해볼게요.

steem.broadcast.vote(wif, voter, author, permlink, weight, function(err, result) {
  console.log(err, result);
});

이 API는 특정 포스트에 보팅을 실행하는 API입니다. 무척 간단하죠~? 포스팅키(wif)와 보팅하는 저자의 아이디, 링크, 보팅 비율(weight)을 세팅합니다. 스팀 파워가 적거나, weight가 임계치를 벗어나는 경우에 오류가 생길 수 있습니다.

  • 서버 구성

기본적인 node.js 사용법을 미리 알고 있다고 가정하고 이야기를 진행해볼게요. 또한, Mac/Linux 상의 CLI 툴을 가지고 테스트를 진행했습니다.

새로운 프로젝트 디렉터리를 생성하고, 디렉터리 안에 package.json을 만들어, node.js에 사용할 라이브러리를 다음과 같이 작성합니다.

$ mkdir steemit-bot && cd steemit-bot

(package.json)
{
    “name”: “steemit-bot”,
    “version”: “1.0.0”,
    “main”: “app.js”.
    “dependencies”: {
       "body-parser": "^1.18.2",
        "express": "^4.16.3",
        "steem": "^0.7.1"
      }
}

서버 구성에 필요한 express 및 앞서 소개한 steem api의 node.js 용 wrapper 라이브러리를 추가합니다.

직접적인 연동은 다음 포스트에 이어서 하도록 하고, 여기서는 이 steem API를 테스트해보고 Promise 형태의 API를 구성해볼게요.

먼저, .key 라는 파일을 만들어, 여기에 steem API에 사용할 postKey와 creator를 넣어볼게요. 이 파일은 개인 로컬 상에 안전하게 등록되어야 하며, github과 같은 형상관리 시스템에 올릴때, 꼭 제외하고 올려둬야 합니다.

postKey는 스팀에 새 글 및 코멘트나 보팅 등의 액션을 수행하기 위해 필요한 키값인데, 이는 스티밋 사이트에서 (지갑 > 권한 탭)에서 포스팅 키(STM…..)를 확인할 수 있습니다.

creator는 본인의 스팀 아이디를 넣습니다. 저는 willpark이 되겠네요. 이것이 제대로 입력이 되지 않은 경우에는 API 사용 시에, 여러 인증 에러가 발생하게 됩니다.

이제 이 키 값을 불러오고 스팀 라이브러리를 불러와 테스트 코드를 작성해보겠습니다. (test.js)

const fs = require('fs');
const key = JSON.parse(fs.readFileSync('./.key', 'utf8'));

const steem = require('steem');
const query = {"tag": "kr", "limit": 2};
steem.api.getDiscussionsByCreated(query, function(err, result) {
    if(err){
        console.log("err:" + err)        
    }else{        
        console.log(result)
    }
});

앞서, 살펴보았던 생성순 포스트를 가져오는 API를 호출하는 코드입니다.

$ node test.js

를 실행하면, 이에 대한 포스트 정보를 가져오는 것을 확인할 수 있습니다.

이제, Promise 형태의 API 라이브러리를 만들어볼게요. steemapi.js 라는 파일을 생성하고, 앞서 생성순/핫한순의 포스트 가져오는 API는 다음과 같이 만들 수 있습니다.

module.exports = function(app, key) {
    const steem = require(‘steem’);

    app.getDiscussionsByCreated = (query) => {
        return new Promise((resolve, reject) => {
            steem.api.getDiscussionsByCreated(query, function(err, response) {
                if(err){
                    reject(err);
                }
                
                resolve(response);
            });
        });
    }

    app.getDiscussionsByHot = (query) => {
        return new Promise((resolve, reject) => {
            steem.api.getDiscussionsByHot(query, function(err, response) {
                if(err){
                    reject(err);
                }

                resolve(response);
            });
        });
    }
}

그리고, 코멘트 및 보팅을 위해 특정 태그에 따른 최신 포스트 중 하나를 가져오는 함수를 구현해볼게요.

app.getDiscussionsByTag = (tag) => {
return new Promise((resolve, reject) => {
const query = { tag, limit: 1};
        steem.api.getDiscussionsByCreated(query, (err, res) => {
if (err) {
                reject(err);
            } else {
                res.forEach(post => {
                  const voters = post.active_votes.map(vote => vote.voter);
                  const isVoted = voters.includes(key.creator);
                  
                  if (!isVoted) {
                    resolve(post);                  
                  } 
                });

                reject("notFound");
            }
        });
    });
}

이와 같이 최신 포스트를 가져와서 해당 포스트의 active_votes 에 접근하게 되는데, 이는 현 포스트에 보팅한 유저의 정보 등이 나옵니다. 만약, 자신이 이미 보팅을 하였다면, 재보팅을 하는 것이므로, 이것이 아닐 때만 해당 post를 반환합니다.

코멘트를 남기는 것에 대한 API는 다음과 같습니다.

app.comment = (tag, post) => {
        return new Promise((resolve, reject) => {           
            const title = "";
            const body = `안녕하세요. \`${tag}\` 태그에 대한 업보팅 하고 갑니다.`;            
            const metadata = { tags: [tag] }
            steem.broadcast.comment(
                key.postKey,
                post.author,
                post.permlink,
                key.creator,
                steem.formatter.commentPermlink(post.author, post.permlink),
                title,
                body,
                metadata,
                (err, result) => {
                  console.log(err, result);
                  if(err) {
                    reject(err);
                  } else {
                    resolve(post);
                  }
                }
            );
        });
}

steem.broadcast.comment에 들어갈 파라미터로 차례로, 포스팅키, 포스팅 저자 id, 포스트 permlink (호스트 url을 제외한 path), 본인의 id, 현 코멘트의 permlink, 타이틀, 내용, 메타데이터 를 세팅합니다. 여기서 코멘트는 제목이 달리 없기에 blank로 두었습니다.

이제 보팅하는 코드를 살펴보지요.

app.vote = (post, rate) => {
        return new Promise((resolve, reject) => {
            steem.broadcast.vote(
                key.postKey,
                key.creator,
                post.author,
                post.permlink,
                rate,
                (err, res) => {
                    if(err) {
                        reject(err);
                    } else {
                        resolve(post);
                    }
                }
            );
        });
}

steem.broadcast.vote의 파라미터로 차례로, 포스팅키, 본인의 아이디, 포스트의 아이디, 포스트 permlink, 보팅율(rate)가 들어가게 됩니다. 이 보팅율은 최대 10,000까지 가능하며, 가지고 있는 스팀 파워에 따라 제약이 발생할 수도 있습니다.

이제 한번, 테스트를 진행해볼까요?

메인 서버 파일인 app.js를 열고, 다음과 같이 작성합니다.

const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const app = express();
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(bodyParser.json());

const key = JSON.parse(fs.readFileSync('./.key', 'utf8'));

require('./steemapi')(app, key);

const tag = "kr-dev";
app.getDiscussionsByTag(tag).then((post) => {
    console.log(JSON.stringify(post));
    app.comment(tag, post).then((post) => {     
        app.vote(post, 10).then((post) => {
            let msg = `${tag}의 자동 보팅이 완료되었습니다! `;
            const url = "https://steemit.com/"+post.url;
            msg += post.title +"\n URL: "+url;                              
   console.log(msg);    
        }).catch((err) => {
            console.error(err);
        });
    }).catch((err) => {
        console.error(err);
    });
}).catch((err) => {
    console.error(err);
});             

#kr-dev의 최신 태그의 글을 가져와 코멘트 및 보팅을 해주는 로직이지요.

$ node app.js

를 실행하면,

스크린샷 2018-07-10 오후 12.09.14.png

이렇게 코멘트와 보팅이 된 것을 확인할 수 있습니다.

간혹, 보팅 프로세스 중에 ‘Voting weight is too small, please accumulate more voting power or steem power.’ 와 같은 에러가 나타나는 경우가 있는데, 이는 말 그대로 보팅 파워 weight 가 적거나 스팀파워의 부족으로 인한 현상으로 보이구요. weight 비율을 키우거나 스팀파워를 보다 충전하면 됩니다.

스팀으로 가서 작성한 댓글 및 해당 전체 글을 확인하면, 댓글과 보팅이 잘 이루어진 것을 확인할 수 있습니다.

스크린샷 2018-07-10 오후 12.10.48.png

스크린샷 2018-07-10 오후 12.12.00.png

이번 포스트에서 Steem API를 활용하여 테스트까지 완료해보았습니다. 이제, 앞서 포스트에서 학습한 Dialogflow와 연동하는 방법을 다음 포스트에서 알아보도록 하겠습니다.

  1. 스티밋 보팅 챗봇 만들기 - 개요
  2. 스티밋 보팅 챗봇 만들기 - DialogFlow 로 학습하기
  3. 스티밋 보팅 챗봇 만들기 - 서버에서 Steem API 구성하기
  4. 스티밋 보팅 챗봇 만들기 - 서버에서 DialogFlow 연동하기
  5. 스티밋 보팅 챗봇 만들기 - 슬랙으로 통합하기

http://bit.ly/2tQKV9J : 누구나 쉽게 배우는 챗봇 서비스

보다 관심있는 분들은 이 책에서 자세한 내용을 확인할 수 있습니다~.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

steemjs에서

promise 로 wrap 할 필요 없이 메소드명 뒤에 Async 만 붙여주면 promise 개체가 반환됩니다 ^^

ex)
callback : steem.broadcast.comment
promise : steem.broadcast.commentAsync

멋진 개발 응원 합니다 ^^

·

넵 맞습니다. async,await를 사용하면 보다 코드가 간결해질 수 있지요. 좋은 팁 감사드려요~