UNPKG

steemkr

Version:

스팀잇 기반 CLI 툴 : CLI(Command Line Interface) Tool for steemit.

389 lines (337 loc) 12.8 kB
const help = require('./help'); const {getLocalTime} = require('../util/wdate'); const {getMoney} = require('../util/wsteem'); const {to} = require('../util/wutil'); const {question} = require('../util/wutil'); const steem = require('steem'); const dateFormat = require('dateformat'); const ora = require('ora'); const axios = require('axios'); // 기본값 const STEEM_AUTHOR = process.env.STEEM_AUTHOR; let STEEM_KEY_POSTING = process.env.STEEM_KEY_POSTING; STEEM_KEY_POSTING = STEEM_KEY_POSTING?STEEM_KEY_POSTING:process.env[`ENV_AUTHOR_KEY_POSTING_${STEEM_AUTHOR}`]; const AXIOS_CONFIG = { headers: { 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', 'accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7' } }; /* * 파라미터 정보를 초기화 해준다 * @param args 외부로부터 입력받은 파라미터 */ let isAutoAuthor = false; function initParams(args) { // 초기화 args = args?args:[]; // new 처리 하므로 return 처리 해야 됨에 유의 // 1번째 : 작가 if(args.length==0){ if(STEEM_AUTHOR){ args.push(STEEM_AUTHOR); isAutoAuthor = true; } } // 2번째 : 포스팅키 if(args.length==1){ if(STEEM_KEY_POSTING){ args.push(STEEM_KEY_POSTING); } } return args; } /** * 필요 정보를 읽어들인다. * @param account 계정명 * @return 정보가 담긴 object */ let spinner; async function loadingDatas(account){ let err; const FOLLOW_API_URL = `https://steemdb.com/api/accounts?account=${account}`; // steemd 사이트가 가끔 오류날 경우가 있기 때문 let results; spinner = ora().start('loading data'); [err,results] = await to(axios.get(FOLLOW_API_URL, AXIOS_CONFIG)); let loads; if(err){ loads = [ steem.api.getAccountsAsync([account]), steem.api.getCurrentMedianHistoryPriceAsync(), steem.api.getDynamicGlobalPropertiesAsync(), steem.api.getRewardFundAsync('post'), steem.api.getVestingDelegationsAsync(account, null, 1000), ]; }else{ loads = [ steem.api.getAccountsAsync([account]), steem.api.getCurrentMedianHistoryPriceAsync(), steem.api.getDynamicGlobalPropertiesAsync(), steem.api.getRewardFundAsync('post'), steem.api.getVestingDelegationsAsync(account, null, 1000), axios.get(FOLLOW_API_URL, AXIOS_CONFIG), ]; } // FOLLOW_API_URL : Bad Gateway 서버 다운으로 인해서 나올 수 있음에 유의해야 한다 [err, results] = await to(Promise.all(loads)); // 값 분석 if(results[0].length!=1){ err = `account [ ${account} ] is not exist.`; } // 작업 성공 if(!err){ spinner.succeed(); if(results.length==6){ return Promise.resolve({ account:account, acc:results[0][0], price:results[1], global:results[2], fund:results[3], delegatees:results[4], followers:results[5].data[0], }); }else{ // steemd 사이트 오류 return Promise.resolve({ account:account, acc:results[0][0], price:results[1], global:results[2], fund:results[3], delegatees:results[4], }); } } // 오류 처리 if(err){ if(spinner&&spinner.isSpinning){ spinner.fail(); } return Promise.reject(err); } } function analysis(data){ let profile = undefined; try{ profile = JSON.parse(data.acc.json_metadata); profile=profile?profile.profile:undefined; }catch(e){} let total_vesting_shares = getMoney(data.global.total_vesting_shares); let total_vesting_fund_steem = getMoney(data.global.total_vesting_fund_steem); let profile_image = `https://steemitimages.com/u/${data.acc.name}/avatar`; let profile_name = profile?profile.name:undefined; let profile_about = profile?profile.about:undefined; let profile_location = profile?profile.location:undefined; let reputation = steem.formatter.reputation(data.acc.reputation); // reputation let vesting_shares = getMoney(data.acc.vesting_shares); // vest let received_vesting_shares = getMoney(data.acc.received_vesting_shares); // vest let delegated_vesting_shares = getMoney(data.acc.delegated_vesting_shares); // vest vesting_shares = vesting_shares + received_vesting_shares - delegated_vesting_shares; vesting_shares = steem.formatter.vestToSteem(vesting_shares, total_vesting_shares, total_vesting_fund_steem); vesting_shares = Math.round(vesting_shares); received_vesting_shares = steem.formatter.vestToSteem(received_vesting_shares, total_vesting_shares, total_vesting_fund_steem); received_vesting_shares = Math.round(received_vesting_shares); delegated_vesting_shares = steem.formatter.vestToSteem(delegated_vesting_shares, total_vesting_shares, total_vesting_fund_steem); delegated_vesting_shares = Math.round(delegated_vesting_shares); let reward_sp = steem.formatter.vestToSteem(getMoney(data.acc.reward_vesting_balance), total_vesting_shares, total_vesting_fund_steem); reward_sp = reward_sp.toFixed(3); let vp = data.acc.voting_power; //최근 투표일 기준 보팅파워 let tgap = (new Date - new Date(data.acc.last_vote_time + "Z")) / 1000; // 시간흐름 적용 let vpc = Math.min(100,(vp + (10000 * tgap / 432000))/100).toFixed(2); // 현재 기준 보팅파워 let steemPrice = getMoney(data.price.base) / getMoney(data.price.quote); let rewardBalance = getMoney(data.fund.reward_balance); let recentClaims = data.fund.recent_claims; let getVoteRate = (vw)=>parseInt(((vp * vw / 1e4)+ 49)/50)*100; let voteValue = (vw) => vesting_shares / (total_vesting_fund_steem / total_vesting_shares) * getVoteRate(vw) * (rewardBalance / recentClaims) * steemPrice; let delegateeValues = []; for(let de of data.delegatees){ let de_date = dateFormat(getLocalTime(de.min_delegation_time),'yyyy-mm-dd HH:MM:ss'); let de_sp = steem.formatter.vestToSteem(getMoney(de.vesting_shares), total_vesting_shares, total_vesting_fund_steem).toFixed(0); delegateeValues.push({val:`아이디 : ${de.delegatee.padStart(16)}, 날짜 : ${de_date}, 스파 : ${de_sp} SP`, en:'info', kr:'정보'}); } let oprofile = {}; // 팔로워 정보가 오류나는 경우(steemd 사이트 오류) 처리 if(data.followers){ oprofile = { en : 'profile', kr : '프로필', values : [ {val : data.acc.id, en : 'id', kr : '아이디'}, {val : data.acc.name, en : 'author', kr : '이름'}, {val : profile_image, en : 'image', kr : '프로필사진'}, {val : profile_name, en : 'name', kr : '닉네임'}, {val : profile_about, en : 'about', kr : '정보'}, {val : profile_location, en : 'location', kr : '사는곳'}, {val : dateFormat(new Date(data.acc.created), 'yyyy-mm-dd HH:MM:ss'), en : 'created', kr : '계정생성일'}, {val : data.followers.following_count, en : 'following_count', kr : '팔로잉'}, {val : data.followers.followers_count, en : 'followers_count', kr : '팔로워'}, {val : Math.round(data.followers.followers_mvest), en : 'mvest', kr : '팔로워 스파합'}, {info : `\n(팔로잉/팔로워/스파합 정보는 스냅샷 기준으로 제공되어 실시간 정보와 다를 수 있습니다.)`}, ] } }else{ oprofile = { en : 'profile', kr : '프로필', values : [ {val : data.acc.id, en : 'id', kr : '아이디'}, {val : data.acc.name, en : 'author', kr : '이름'}, {val : profile_image, en : 'image', kr : '프로필사진'}, {val : profile_name, en : 'name', kr : '닉네임'}, {val : profile_about, en : 'about', kr : '정보'}, {val : profile_location, en : 'location', kr : '사는곳'}, {val : dateFormat(new Date(data.acc.created), 'yyyy-mm-dd HH:MM:ss'), en : 'created', kr : '계정생성일'}, {val : `https://steemdb.com/api/accounts?account=${data.acc.name} 사이트에서 (followers_mvest) 를 확인하세요`, en : 'followers_mvest', kr : '스파합'}, ] } } return [ oprofile, { en : 'vote', kr : '투표', values : [ {val : reputation, en : 'reputation', kr : '명성'}, {val : vesting_shares, en : 'vesting_shares', kr : '스파', subfix:'SP'}, {val : received_vesting_shares, en : 'received_vesting_shares', kr : '스파(임차)', subfix:'SP'}, {val : delegated_vesting_shares, en : 'delegated_vesting_shares', kr : '스파(임대)', subfix:'SP'}, {val : dateFormat(getLocalTime(data.acc.last_vote_time), 'yyyy-mm-dd HH:MM:ss'), en : 'last_vote_time', kr : '최근투표일'}, {val : vpc, en : 'voting_power', kr : '현재 보팅파워', subfix:'%'}, {val : voteValue(1e4).toFixed(3), en : 'weight 100%', kr : '100% 보팅', subfix:'SBD'}, {val : voteValue(5e3).toFixed(3), en : 'weight 50%', kr : '50% 보팅', subfix:'SBD'}, {val : voteValue(1e3).toFixed(3), en : 'weight 10%', kr : '10% 보팅', subfix:'SBD'}, ] }, { en : 'money', kr : '잔고', values : [ {val : getMoney(data.acc.balance), en : 'balance', kr : '스팀', subfix:'STEEM'}, {val : getMoney(data.acc.sbd_balance), en : 'sbd_balance', kr : '스달', subfix:'SBD'} ] }, { en : 'reward', kr : '보상', values : [ {val : getMoney(data.acc.reward_sbd_balance), en : 'reward_sbd_balance', kr : '스달', subfix:'SBD'}, {val : getMoney(data.acc.reward_steem_balance), en : 'reward_steem_balance', kr : '스팀', subfix:'STEEM'}, {val : getMoney(data.acc.reward_vesting_balance), en : 'reward_vesting_balance', kr : '베스트', subfix:'VEST'}, {val : reward_sp, en : 'reward_vesting_balance', kr : '스파', subfix:'SP'} ] }, { en : 'delegatee', kr : '임대', values : delegateeValues }, ]; } /* * 입력 계정의 설정 값이 환경변수 ID값과 일치 시 보상을 청구한다 * @param data 획득정보 * @param account 계정정보 * @param wif 포스팅키 */ async function claimReward(data, account, wif){ let isClaim = true; if(STEEM_AUTHOR && STEEM_KEY_POSTING){ if(STEEM_AUTHOR!=account){ isClaim=false; } } if(isClaim && account && wif){ let reward_steem_balance = data.acc.reward_steem_balance; let reward_sbd_balance = data.acc.reward_sbd_balance; let reward_vesting_balance = data.acc.reward_vesting_balance; let r1 = getMoney(reward_steem_balance); let r2 = getMoney(reward_sbd_balance); let r3 = getMoney(reward_vesting_balance); if(r1+r2+r3>0){ let claim; spinner = ora().start('claim reward'); [err, claim] = await to(steem.broadcast.claimRewardBalanceAsync(wif, account, reward_steem_balance, reward_sbd_balance, reward_vesting_balance)); if(err){ // 오류처리 spinner.fail(); return Promise.reject(err.toString()); } spinner.succeed(); return Promise.resolve(claim); } } return Promise.resolve(''); } /* * 비동기 작업을 수행한다 * @param account 계정명 * @param wif 포스팅키 */ async function processAsyc(account, wif){ let err; // 처리 방식 : 각종 값 로딩 완료 => 분석 => 기타 작업 // 데이터 로딩 let data; [err, data] = await to(loadingDatas(account)); // 값 분석 if(!err){ let groups = analysis(data); // 화면 출력 console.log(); for(let g of groups){ console.log(`===== ${g.kr} (${g.en}) =====\n`); for(let v of g.values){ if(v.info){ console.log(`${v.info}`); }else{ console.log(`${v.kr}(${v.en}) : ${v.val?v.val:(v.subfix?0:'N/A')} ${v.subfix?v.subfix:''}`); } } console.log(``); } } // 보상청구 let claim; if(!err){ [err, claim] = await to(claimReward(data,account,wif)); } // 작업 성공 if(!err){ return Promise.resolve(data); } // 오류 처리 if(err){ if(spinner && spinner.isSpinning){ spinner.fail(); } return Promise.reject(err.toString()); } } module.exports = (args)=>{ // 파라미터 초기화 args = initParams(args); // 입력 파라미터 유효성 검증 if(args.length<1){ console.error('\n [경고] 파라미터 오류 : 아래 메뉴얼을 참조 바랍니다'); help('accounts'); return; } let account = args[0]; let wif = args[1]; processAsyc(account, wif) .then(data=>{ console.log(`${data.acc.name} 의 계정 분석이 완료 되었습니다.`); }) .catch(e=>{ console.log(`____________________________________________________________`); console.error(e); // console.log(e, e.stack); console.log(`____________________________________________________________`); console.error('오류가 발생 했습니다.위쪽 라인을 참조 바랍니다.'); console.log(`____________________________________________________________`); }); };