[개발] Node & Steem - 7편 리스팀 댓글 알림 봇 #3 시범오픈과 구현 마무으리!

in #kr-dev7 years ago (edited)

node_door.png

두가지 파트로 포스팅을 작성합니다. 하나는 리스팀 알림봇의 시범운영 런칭 안내와 두번째는 지난시간에 이은 리스팀 알림 댓글을 달기 위한 작업입니다. 시범단계라 여러가지 예상치 못한 오류가 있을 수 있습니다. ^^ 그럼 시작합니다.

1. 시범오픈 진행 안내

리스팀 알림 봇이 베타테스트를 진행할 정도의 완성 단계입니다. 어떤 포스팅에서든 단 1회만 댓글로 @리스팀 켬의 명령어를 통해 리스팀 알림 서비스에 등록하실 수 있으며 @리스팀 끔의 명령어를 통해 해제하지 않는 이상 반영구적으로 댓글로 알림을 받으실 수 있습니다.

자매품 이녀석과 무한 댓글놀이도 가능해여~ ㅋㅋ 모니터링 해보면서 차차 개선해나가도록 하겠습니다.

등록 이후 리스팀 되었을 시의 알람 :

여러분의 서비스 등록과 리스팀은 제 서비스 모니터링에 큰 도움이 됩니다. 감사합니다. 최근 api.steemit.com의 노드 문제로 작동이 중지되는 현상을 겪긴 했습니다;; 계속 추가적으로 모니터링할 예정입니다. 그리고 조만간 멘션 댓글 알림도 추가하지 않을까 싶습니다. 멘션 놓치시는 분들 없기를 바라며...


2. 봇의 댓글 구현 편

여기서부터는 봇을 처음 만들거나 예비개발자분들을 위한 실제 구현내용입니다. ^^

지난 시간에 리스팀 명령어를 인식해서 데이터베이스에 적재하는 단계까지 진행하였습니다. 이미 '리스팀'시의 이벤트를 찍는건 되었으니 남은 건 저 부분에서 댓글을 달아주는 봇의 작업만이 필요합니다. 차근차근 하나씩 진행해보도록 하죠. 저도 작성하면서 개발하고 작성하면서 작업하고 있습니다.

여기는 계정을 만들고 테이블에 적재하고 적절한 스케줄링으로 댓글을 다는 작업이 꽤나 길어질 것 같습니다. 하지만 한단계씩 차근차근 진행하면 어려운 부분은 없을 것 같으니 진행해봅시다.

2-1.봇 계정을 하나 만들어봅시다.

서... 설마 사용중인 계정으로 하려는 생각을 하고 계시진 않을 듯 합니다. 저는 @steem.apps 라는 계정을 하나 만들어보도록 하겠습니다. 자... 자랑처럼 보일 수 있지만 과거에 만들어놓은 계정 생성 사이트를 이용해서 진행하겠습니다. 예전에 steem.apps라는 페이지를 하나 개설한 적이 있습니다. 여기에 asbear님이나 segyepark님의 사이트 그리고 기타 스팀잇을 이용하면서 필요한 사이트들을 나름 모아놨었습니다.

steem.apps 사이트

자랑을 하는게 아니고... 그냥 여기에 만들면 1분도 안걸립니다. 단지 계정에 3 steem 정도만 있으면 됩니다. account creator 메뉴로 들어가셔서 아이디와 액티브 키 그리고 만들 계정만 입력하시고 create account 버튼을 누르시면 뙇! 하고 계정이 만들어집니다.

만들어진 다음에 반드시 오너키를 잘 백업해주시기 바랍니다.

그 다음엔 만들어진 계정으로 로그인을 해봅시다. 로그인 시에 무조건 비밀번호를 바꾸게 되어있으니까 다시 한번 백업을 할 준비를 합시다.

그리고 로그인 키를 넣고 로그인을 누르면 비밀번호를 변경하라고 나오는데 변경해줍시다.


현재 비밀번호를 누르고 새 비밀번호 생성을 누르면 새로운 비밀번호가 나오는데 잘 백업하시고 재 입력 후 새 비밀번호 적용을 누르면 됩니다!


그리고 로그인을 하면 뙇! 이로서 저의 피같은 3 steem을 들여 새로운 계정을 만들어보았습니다. ㅋㅋㅋ


2-2.봇 계정 정보와 작업 내역을 저장하는 테이블을 만듭니다.

현재 제 계정인 @nhj12311이 리스팀 서비스 신청이 되어있는 상태니까 제 계정으로 테스트를 해보면서 진행하면 됩니다. 댓글을 달아야 하는 봇 계정 정보는 데이터베이스에 넣도록 하겠습니다. 여기에는 두가지 이유정도가 있는데 ...

첫번째. github 소스에 포스팅 키를 넣을 순 없는 노릇이고, 두번째로는 댓글을 달아야할 계정이 여러개가 될 가능성을 배제할 수 없기 때문입니다.

두번째 사유에 대해 조금 더 자세히 말하자면 20초의 댓글 한계가 있고 이보다 더 많은 댓글을 달 필요가 있다면 그만큼 여러개의 댓글 봇 계정이 필요합니다. 현재 스팀잇 전체의 리스팀 양을 추산해보면 분당 10개에서 20개 정도입니다. 그렇다면 리스팀 알림 기능을 댓글로서 수행하기 위해서만도 약 6개~10개의 댓글 봇 계정이 있어야 원활하게 수행할 수 있다는 결론이 나옵니다. kr의 리스팀 수준은 그정도는 아닌것으로 파악되니 일단 1개의 계정으로 시행 후에 많이 부족한 경우 봇 계정을 늘리는 식으로 가야할 것 같습니다. @steem.apps2, @steem.apps3, @steem.apps4 식으로 가겠죠?

이제 닥설하고 봇 계정관리 테이블을 만듭니다.

명칭은 BOT_ACCT_MNG (봇 계정 관리) 입니다.

그리고 생각하다보면 아시겠지만 댓글달아야 할 내용을 테이블에 적재해야합니다. 프로그램은 두개의 작업이 같이 도는 형태로 개발되어야 한다는 것이죠.

첫번째로 스팀블록을 읽어서 작업해야할 내용, 즉 댓글과 보팅을 테이블에 적재하는 파트 하나와 두번째로 적재된 테이블을 읽어와 실제로 댓글과 보팅을 수행할 봇이 움직이는 파트로요. 해서 작업대상 리스트 테이블을 하나 만들어봅시다.

명칭은 BOT_WRK_LIST (봇 작업 리스트) 입니다. 여기에는 봇이 댓글을 달아야할 내용이 적재해야됩니다. 댓글을 달게 하는 author와 perm_link(포스팅 주소), 작업을 했는지의 상태코드, 작업을 한 시각정도 표현되면 될것 같습니다. 나중에는 댓글 뿐만이 아닌 보팅해야할 작업도 넣게 될겁니다. 때로는 krguidedog 처럼 일정 steem이나 sbd를 이체할수도 있겠죠. 항상 테이블을 만들 때는 확장성을 고려해서 만들어봅니다.

그런다고 안고칠리는 없겠지만... 나중에 고치려고 할수록 귀찮고 공수가 많이 들어갑니다.

BOT_WRK_LIST


2-3. 이제 리스팀 발견 시 작업 내역에 저장해보자.

작업내역을 저장하기 위한 조건은 신청내역을 보고서 진행해야합니다. 이렇게 하면 되겠죠?

1. 리스팀을 발견한다.

  • 이미 발견하고 있습니다. ㅎㅎ

2. 리스팀 된 글의 저자가 신청 리스트에 있는지 확인한다.

  • DB에 select 문으로 확인하면 되겠죠?

3. 신청이 된 저자의 경우 작업대상 테이블에 저장해준다.

  • 작업대상 테이블에 insert 하면 됩니다.

바로 소스에 작업해봅시다. 먼저 리스팀 on, off에 대해 등록 여부를 알려주는 댓글 먼저 달아봅시다.

var comment = "리스팀 서비스가 " +  ( useYn == "Y" ? "등록되었습니다." : "해제되었습니다." );
var inQry = "insert into bot_wrk_list "
  + "(dvcd, author, perm_link, comment, wrk_status, vote_yn ) "
  + "values( ?, ?, ?, ?, ?, ?) ";
var params = [
    1  // dvcd
    , operation[1].author // author
    , operation[1].permlink // permlink
    , comment // comment
    , "1" // wrk_status 0:complete, 1:ready, 9:error
    , "N" // vote_yn
];
var inRslt = await(conn.query(inQry, params, defer() ));
logger.info(inRslt);

등록하는 경우 리스팀 서비스가 등록되었습니다. 해지하는 경우리스팀 서비스가 해제 되었습니다.`로 댓글을 달 수 있도록 작업 리스트에 등록하는 과정입니다.

이렇게 하고 테스트를 하면? 오류를 뿜어버리는 군요. 이 메세지는 mysql table에 한글을 넣으면서 생기는 오류입니다.

16:13:51 - error:  Error: ER_TRUNCATED_WRONG_VALUE_FOR_FIELD: Incorrect string value: '\xEB\xA6\xAC\xEC\x8A\xA4...' for column 'comment' at row 1
    at Query.Sequence._packetToError (D:\workspace\nodejs\steem_nhj\node_modules\mysql\lib\protocol\sequences\Sequence.js:52:14)
    at Query.ErrorPacket (D:\workspace\nodejs\steem_nhj\node_modules\mysql\lib\protocol\sequences\Query.js:77:18)
    at Protocol._parsePacket (D:\workspace\nodejs\steem_nhj\node_modules\mysql\lib\protocol\Protocol.js:279:23)
    at Parser.write (D:\workspace\nodejs\steem_nhj\node_modules\mysql\lib\protocol\Parser.js:76:12)
    at Protocol.write (D:\workspace\nodejs\steem_nhj\node_modules\mysql\lib\protocol\Protocol.js:39:16)
    at Socket.<anonymous> (D:\workspace\nodejs\steem_nhj\node_modules\mysql\lib\Connection.js:103:28)
    at emitOne (events.js:116:13)
    at Socket.emit (events.js:211:7)
    at addChunk (_stream_readable.js:263:12)
    at readableAddChunk (_stream_readable.js:250:11)
    at Socket.Readable.push (_stream_readable.js:208:10)
    at TCP.onread (net.js:594:20)
    --------------------
    at Protocol._enqueue (D:\workspace\nodejs\steem_nhj\node_modules\mysql\lib\protocol\Protocol.js:145:48)
    at Connection.query (D:\workspace\nodejs\steem_nhj\node_modules\mysql\lib\Connection.js:208:25)
    at D:\workspace\nodejs\steem_nhj\app.js:208:49
    at D:\workspace\nodejs\steem_nhj\node_modules\synchronize\sync.js:281:10

이 경우에 해당 컬럼에 대해서 'utf8mb4' 형태로 지정해주면 해결됩니다. HeidiSQL로 가서 테이블 편집을 통해 조합 부분을 'utf8mb4_general_ci'로 수정하여 저장해주면 됩니다. mb4로 해야 이모티콘같은 4byte를 저장할 수 있으니 한글을 넣는 컬럼은 앞으로 다 저걸로 변경하도록 해봅시다.


테스트를 해보니 등록이 잘 됩니다.

헠헠... 개발 구현 내용을 본문에 담는건 정말 쉽지 않은 일이네요. 독자층이 적은 내용이라 허허... 일단 한번은 하기로 했으니 끝까지 갑니다. 다음턴은 실제 리스팀 안내 멘트를 등록하는 과정입니다.

지난번 로그를 찍어놨던 reblog 단에 다음 소스를 넣으면 됩니다. 주요 내용은 리스팀 안내 서비스 대상자인지 확인 후에 댓글 디비에 등록해주는 내용입니다.


if( "reblog" == custom_json[0] || "resteem" == custom_json[0] ){
    logger.info( custom_json );
    var selQry = "select * from svc_acct_mng where dvcd = 1 and use_yn = 'Y' and acct_nm = '"+custom_json[1].author+"' ";
    logger.info( "selQry : "+selQry );
    var selRslt = await(conn.query(selQry, defer() ));
    if( selRslt.length > 0 ){
      var comment = "@" + custom_json[1].account + "님께서 이 포스팅에 많은 관심을 가지고 있어요. 리스팀을 해주셨군요~! " ;
      var inQry = "insert into bot_wrk_list "
        + "(dvcd, author, perm_link, comment, wrk_status, vote_yn ) "
        + "values( ?, ?, ?, ?, ?, ?) ";
      var params = [
          1  // dvcd
          , custom_json[1].author // author
          , custom_json[1].permlink // permlink
          , comment // comment
          , "1" // wrk_status 0:complete, 1:ready, 9:error
          , "N" // vote_yn
      ];
      var inRslt = await(conn.query(inQry, params, defer() ));
      logger.info(inRslt);
    }
} // if( reblog )

이렇게 프로그램을 처음 구현할 때에는 곳곳에 로그를 남겨놓고 실제로 서비스 운영할 때에나 충분히 안정적으로 운영이 될 때에는 다시 주석처리를 해놓으면 됩니다.

테스트를 해보니 멘트가 잘 등록됩니다. 물론 한방에 성공한것이 아닌 조금씩 고쳐가면서 테스트를 충분히 해본것입니다.


2-4. 이제 저장된 댓글 작업을 읽어들여 멘트를 다는 봇을 작동시키면 끝.

작동 순서를 생각해봅시다. 이 읽어들이는 기능은 1초에 한번씩 작동하면 될것 같습니다.

1. 댓글 리스트를 읽어들입니다.
2. 댓글을 달아줄 봇의 계정 정보를 읽어들입니다.

  • 이 때엔 최종 멘트 일시를 저장했다는 가정하에 코멘트 등록 20초 이후의 계정을 읽어들입니다.
  • 여기서 정보가 없다면 다시 처음으로 돌아갑니다.

3. 댓글을 달 수 있는 봇 계정이 있으면 댓글을 달고 마무리를 짓습니다.

소스가 좀 복잡합니다만... 아톰에 붙여넣고 보면 그리 복잡하지 않습니다. ^^


var Fiber = require('fibers');

function sleep(ms) {
    var fiber = Fiber.current;
    setTimeout(function() {
        fiber.run();
    }, ms);
    Fiber.yield();
}

var sleepTm = 1000;

function wrkBot(){
  fiber(function() {
  try{
  //for(var loopCnt = 0; true ;loopCnt++ ){
      sleep(sleepTm);
      if( conn.state != 'authenticated'){
        return;
      }
      var chk = true;
      logger.info("wrkBot execute." );

      try{
        var selWrkQry = " select * from bot_wrk_list where wrk_status <> 0 order by seq asc ";
        var wrkList = await(conn.query(selWrkQry, [], defer() ));

        if( wrkList == null || wrkList.length <= 0 ){
          return;
        }
        var selBotQry = "select * from bot_acct_mng "
          + " where 1=1 "
          + " and instr(arr_dvcd, ?) > 0 "
          + " and last_comment_dttm < DATE_ADD(now(), INTERVAL -20 second) "
          + " order by last_comment_dttm asc ";

        var botList = await(conn.query(selBotQry, [ 1 ], defer() ));
        if( botList == null && botList.length <= 0 ){
          return;
        }
        for(var i = 0; i < wrkList.length && i < botList.length ;i++){
          logger.info(i +" : "+ JSON.stringify(botList[i]));
          logger.info(i +" : "+ JSON.stringify(wrkList[i]));
          var wif = botList[i].posting_key;
          var author = botList[i].id;
          var parentAuthor = wrkList[i].author;
          var parentPermlink = wrkList[i].perm_link;
          var permlink = steem.formatter.commentPermlink(parentAuthor, parentPermlink);
          var title = "";
          var body = wrkList[i].comment;
          var jsonMetadata = {};

          var commentRslt = steem.broadcast.comment(wif, parentAuthor, parentPermlink, author, permlink, title, body, jsonMetadata,defer() );
          logger.error(commentRslt);

          var botUpQry = "update bot_acct_mng set last_comment_dttm = now() where seq = ? and id = ? " ;
          var botUpRslt = await(conn.query(botUpQry, [ botList[i].seq, botList[i].id ], defer() ));

          var wrkUpQry = "update bot_wrk_list set wrk_status = 0, wrk_dttm = now() where seq = ?" ;
          var wrkUpRslt = await(conn.query(wrkUpQry, [ wrkList[i].seq  ], defer() ));
        }
      }catch(err){
        logger.error("wrkBot error : ", err);
        throw err;
      }
  }catch(err){
    logger.error("wrkBot err : ", err);
    sleep(sleepTm);
  }finally{
    setImmediate(function(){wrkBot()});
  }
  }); // fiber(function() {
};  // wrkBot function end

wrkBot();
logger.info("end.");

이 wrkBot의 핵심은 fiber를 이용한 sleep으로 1초씩 지연 후 작업하며 finally의 setImmediate를 이용해서 다시 wrkBot을 호출하여 무한 재귀 처리를 하는 것입니다. 작업된 소스와 테이블 반영은 지금까지의 포스팅 내에 충분히 설명되어있으므로 별도로 설명하지 않습니다.


이렇게 리스팀 알림 봇의 기능 구현은 어느정도 끝이 났고 소스를 리팩토링과 운영해보며 발생하는 문제에 대해서 예외처리를 추가하는 과정정도가 남아있는 것 같습니다. 그럼 다음 기능으로 다시 만나영. 😄 물론 보완해야할 점이 많이 보여 보완하는 내용도 같이 포스팅해야겠습니다.


node & steem - 지난 회차 살펴보기
1편 - nodejs 개발환경을 구성해보자. 윈도우 개발 + Github 저장소 + 리눅스 운영
2편 - 콜백 지옥을 탈출해보자. - synchronize.js 편
3편 - 로깅 처리와 DB(mysql)설치 및 설정
4편 - DB 설정과 운영서버까지 설정 마무으리!
5편 리스팀 알림 봇을 만들어볼까? #1
6편 리스팀 알림 봇을 만들어볼까? #2 whitelist 데이터 수집


ps. 제게 공식적인 첫 대문이라는 걸 선물해주신 @forhappywomen님과 그려주신 @carrotcake님께 무한한 감사 인사드립니다. 😄😄😄

Sort:  

Cheer Up!

  • from Clean STEEM activity supporter

@steemalls님께서 이 포스팅에 많은 관심을 가지고 있어요. 리스팀을 해주셨군요~!

@gaethug님께서 이 포스팅에 많은 관심을 가지고 있어요. 리스팀을 해주셨군요~!

@millionfist님께서 이 포스팅에 많은 관심을 가지고 있어요. 리스팀을 해주셨군요~!

@stella12님께서 이 포스팅에 많은 관심을 가지고 있어요. 리스팀을 해주셨군요~!

고생 많으셨습니다. 또 막 따라해 보고 싶은 마음이 ㅎㅎㅎ
근데 제 보팅봇을 돌리고 있어서.. 이걸 꺼야 파이썬이고 뭐고 다시 코딩을 할 수 있을 텐데요.. 방법을 모르니 ㅠㅠ

@myhappycircle님께서 이 포스팅에 많은 관심을 가지고 있어요. 리스팀을 해주셨군요~!

^^ 저도 리스팀 :)

@nhj12311 님 행복한 명절 보내세요~

thank you, you have conveyed very good information good luck you

@replayphoto님께서 이 포스팅에 많은 관심을 가지고 있어요. 리스팀을 해주셨군요~!