javascript2017.12.24 18:21


http://www.drbitcoin.net/

비트코인 가격이 한국거래소만 유달리 높게 나타나는 현상을, 속칭 '김치프리미엄' 줄여서 '김프'라고 하지요. (Kimchi Premium = Korea Premium)

가상화폐/암호화폐/비트코인을 거래할때에, 초단타거래시에는 김치프리미엄이 꽤 중요한 값인데, 해외사이트들 가격까지 한눈에 파악하기는 조금 힘들어요. (실시간 환율까지 계산해야함).

그래서, 실시간으로 김치프리미엄을 계산해서 그래프로 보여주는 사이트를 만들었습니다. JPY로 표시되는 BitFlyer와 USD 표시 BitFinex 자료를 기초로하여, 실시간 환율자료를 이용하여 한국 빗썸(Bithumb) 업비트(UpBit)의 가격 거품(?)을 매분단위로 실시간, 10분/1시간단위 누적 데이터를 그래프로 표시해 줍니다.

아시다시피 해석 및 투자결정은 본인 책임이니... 참고용으로 쓰세요.

* 김치프리미엄이 쫘악 빠지는데 해외사이트가격은 유지되고 있다면 (한국만 하락중) -> 매수신호 (?)
* 김치프리미엄이 급속이 늘어나는데 해외사이트가격이 유지될때 (한국만 상승중) -> 매도신호 (추격매수금지)



Posted by Jadumate
javascript2017.11.29 11:21

서버, 파이썬, node.js 없이 브라우저만으로 돌아가는, 

간단한 반응형 슬랙봇 만들기 (자바스크립트)

Simple reactive Slackbot in Javascript without Python, node.js, REACT and a server. 

All you need is a browser.



커뮤니티툴로 슬랙이 인기다. 외부 확장이 쉽도록 API와 BOT인터페이스들을 잘 제공하고 있는데, 카카오톡의 폐쇄성에 답답하던 사람들에게 해방구가 아닐까 싶다. 


간단히 슬랙으로 메세지를 보내려면 단순히 URL+파라미터 형태만으로도 가능하다. curl같은 프로그램으로 리눅스 쉘에서 바로 보낼 수도 있다. 


문제는 반응형인데 (슬랙 유저가 채널에 작성한 메세지에 반응하는 형태), 슬랙에서 이런 반응형 봇을 만들수 있게 제공해주는 기반은 RTM (RealTime Messaging) 혹은 Event 방식이다. 


구글링으로 찾아본, 이걸 구현한 라이브러리/샘플들은 대부분 파이썬, node.js 등으로 만들어져 있다. 파이썬에 익숙한 사람들은 좋겠지만 나처럼 극악으로 싫어하는 사람들은 다른 샘플이 필요했다. 


php로 된 샘플도 찾아내긴 했는데, RTM을 구현하려면 소켓연결라이브러리 REACT가 필요하고, 이것을 설치하려면 어쩌고 저쩌고....  아 이산이 아닌갑다. 하고 포기하게 된다. 


이쯤이면 직접 만들각이다.  

성격급한 사람들을 위한 소스 다운로드 : https://github.com/jadumate/jadubot



간단한 메세지전송  (Simple Posting)


반응형이 필요 없는 경우 - 예를 들면 매일아침 주가지수를 알려주는 봇 -는 굉장히 쉽게 구현이 가능하다.  슬랙API페이지에서 안내하듯이 curl 같은걸 써서 특별한 코딩없이 바로 가능한데, 일단 Js로 구현해 보았다. 



사전작업 (https://api.slack.com/)
1. App을 생성하고 셋팅. 'Incoming Webooks'를 활성화시켜준다. 

2. 채널별로 Webhook URL을 생성한다. 




이제 이 URL만 있으면 슬랙채널에 메세지를 날릴 수 있다.



function
sendWebhook(url, msg)

{

$.ajax({
data: 'payload=' + JSON.stringify({ "text": msg }),
dataType: 'json',
processData: false,
type: 'POST',
url: url
});
}


이런 형태로 사용한다. 

sendWebhook('https://hooks.slack.com/services/.....', 'Hello Slack');  




반응형 메세지 전송 (Reactive Messaging)


반응형이란, 슬랙채널 누군가가 보낸 메세지를 분석해서 그에 따라 반응하는 형태를 말한다. 예를 들면 '점심 메뉴를 골라줘' 라는 요청에 대해 답을 찾아주는 것.


사전작업

1. 슬랙 API페이지에서 BOT User를 생성한다. 

2. Bot User OAuth Access Token을 복사한다. 

3. 적당한 권한을 설정한다. 








이제 코딩


function onevent(event) {
var json = JSON.parse(event.data);
if( json.type == 'message'){
processMessage(json);
}
console.log('RAW: ' + event.data);
};
function openSocket(url) {
console.log('WS:url : ' + url);
window.ws = new WebSocket(url);
window.ws.onmessage = onevent;
window.ws.onopen = function() {
console.log('WS:opened');
};
}
function run(){
$.post(
'https://slack.com/api/rtm.start',
{ token : config.apiToken, type : 'hello' },
function(data){
if(data && data.ok) {
metadata = data;
openSocket(data.url);
}
}
);
return false;
}


브라우저의 웹소켓 (websocket)을 이용한다.  


1. https://slack.com/api/rtm.start 에 접속하여 metadata를 얻는다. 이 데이터안에 연결해야할 websocket 주소가 있다.

2. 웹소켓을 연결하고 이벤트핸들러를 설정한다. 

3. 핸들러 루틴을 작성한다. 




function processMessage(json)
{
if(! json.text) return;
if(json.bot_id !== undefined) return;
var userName = getUsername(json.user);
dump( userName + ' : ' + json.text + '\n');
if(json.text.indexOf('Hello') >= 0){
postMessage(json.channel, 'Hello ' + userName);
}
}


누군가 'Hello' 라고 입력하면, 그사람 이름붙여서 같이 인사해주는 코드다.

슬랙에 메세지를 보내는 방법은 여러가지가 있는데, 이상하게도 websocket으로 보내는 방식은 꾸미기가 불가능하게 되어 있다 (간단한 텍스트만 전송).   그래서 chat.postmessage라는 또다른 api를 사용해 보자.


function postMessageSimple(channel, msg)
{
var text = {
as_user: false,
username: config.botName,
channel : channel,
type : 'message',
text : msg
};
window.ws.send( JSON.stringify(text) );
}

function
postMessage(channel, msg)
{
var params = {
token : config.apiToken,
encoding: 'utf8',
channel : channel,
as_user: false,
username: config.botName,
text: msg
};
$.post(
'https://slack.com/api/chat.postMessage',
params,
function(status){
console.log(status);
}
);
console.log('#slack send : ' + msg);
}



스크립트는 완성. 이제 콘솔이 될 html 페이지를 대충 만들어 보자.



<body onload="return run();">
<form action='' methos='POST'>
<div style='width:100%;'>Jadubot V1.0 by jadumate@gmail.com</div>
<textarea id='dump' style='width:100%; height:80%;'>
</textarea>
</form>
</body>




function
dump(msg)
{
$("#dump").append(msg);
}




이렇게 해서 기본구현은 끝. processMessage에서 적당히 구문분석하고 다른 일들을 해주면 되겠다.


소스파일을 돌려보려면 두 파일을 다운받아 html파일을 브라우저에서 열면 된다. 미리 config.js 안에 자기의 슬랙에 맞는 webhookurl, apitoken값을 지정해주어야 한다.  실제 통신이 잘 이루어지는지 알고 싶으면 F12를 눌러 콘솔 덤프를 확인해 보자. 




Posted by Jadumate
linux2016.01.12 19:20

sqlite3에서 database disk image is malformed 에러가 나올때... 

sql로 덤프한후 새롭게 DB구성



$ sqlite3  [망가진.db]


sqlite3> .mode insert

sqlite3> .output a.sql

sqlite3> .dump



$sqlite3 [new.db]


sqlite3> .read a.sql

Posted by Jadumate
분류없음2014.12.08 16:43

Mozilla Thunderbird 이메일 프로그램에서 PC운영체제를 새로 설치하거나 했을때 예전 디스크의 이메일을 불러오는 기능이 있을까? 기본적으로는 없는것 같다.


1. AddOn 설치하기

https://addons.mozilla.org/en-us/thunderbird/addon/importexporttools/


2. ImportExportTool을 이용하여 예전 프로필 불러오기

한참을 찾아다녔는데, 예전 이메일들이 있는 위치는... 

\Users\<username>\AppData\Roaming\Thunderbird\Profiles\nj5n9fi8.default

이런 형태로 저장되어 있다. 








Posted by Jadumate
linux2014.08.04 18:52

Linux에서 Window Network Drive 연결

# mount -t cifs //192.168.0.234/share /mnt/share -o user=username,pasword=password


smbmount 는 없어졌다. 잊어버리자.

mount -t smbfs 도 없어졌다. 잊어버리자. cifs를 사용








Posted by Jadumate
sqlite32013.12.30 12:14

sqlite> pragma table_info(delivery);

0|key|varchar(65)|0||1

1|nick_src|varchar(65)|0||0

2|nick_tgt|varchar(65)|0||0

3|date_send|varchar(33)|0||0

4|date_recv|varchar(33)|0||0

5|item|varchar(33)|0||0

6|msg|varchar(81)|0||0



delivery.date_send 는  "20131201.1230" 형태의 문자열로 저장되어 있는 컬럼이다. 이것을 비교하여 쿼리하기 위해서는 CAST함수를 이용한다. 


sqlite> delete from delivery where CAST(date_send as integer) < 20131201;
sqlite>


date_send(보낸 날짜)가 12월01일 이전인 레코드를 삭제하려는 목적인데, 이렇게 하면 잘 동작한다.
※ 처리속도는 매우 느리므로, 유지관리작업중에나 사용 가능한 점에 유의. 


Posted by Jadumate
sqlite32013.12.30 12:10

delete from [table] 로 레코드를 엄청나게 줄였음에도, 물리적 파일 사이즈는 줄어들지 않는다. 디스크 공간을 재사용하기 위해서인데, 이 경우 vacuum 명령어로 실제 파일 사이즈를 줄일수 있다. 


sqlite> vacuum;

sqlite> .quit


[biscuit@*****]$ ls -al sogon.db*

-rw-rw-rw- 1 biscuit users 92,228,608 12월 30 12:05 sogon.db

-rw-r--r-- 1 biscuit users 18,917,376 12월 30 12:05 sogon.db.test


필요없는 레코드들을 지우고나서도 92메가 정도였는데, vacuum 명령어 실행후 확 줄어들었다. 


Posted by Jadumate
c++2013.07.04 11:41

C++표준 string (basic_string)을 printf 형식으로 사용하는 편한 방법이 없을까?  인터넷 찾아보면 여러가지 방법들이 나오긴하는데, static 변수를 이용하여 편하고/간결하고/오버헤드 없이 구현해 보았다. 


void strFormat(string &strResult, const char *pFormat, ... )

{

 static char sprintf_buffer[8192];


va_list va = NULL;

va_start(va, pFormat);

vsprintf(sprintf_buffer, pFormat, va);

strResult = sprintf_buffer;


va_end(va);

}


void strReplace(string& subject, const string& search, const string& replace)

{

size_t pos = 0;


while((pos = subject.find(search, pos)) != string::npos) {

subject.replace(pos, search.length(), replace);

pos += replace.length();

}

}


덤으로, strReplace는, string.find 와 string.replace의 조합을 이용하여 find&replace하는 함수인데 메모리할당을 하지 않고 비교적 빠른속도로 처리한다.


Posted by Jadumate
c++2013.07.03 21:46

개발환경 : C++/STL/Linux/gcc

프로그램을 짜다 보면 잘 못 짜서  어디선가 무한루프에 빠져서 헤어나질 못하는 경우가 종종 있는데, 이럴때 SIGALRM을 이용하여 이 녀석이 대체 무슨 짓을 하고 있었는지 자세하게 확인할 수 있다.

// signal handler

static void SigAlarm(int signo)

{

        ...

        closing_routine();

        ....

abort(); // exit with core_dump

}


int main(){

// 핸들러 설정

if(signal(SIGALRM, SigAlarm) == SIG_ERR) ERROR("signal ALARM error");

...

// main loop
while(1){

alarm(5); // 알람설정 5초

...

do_something();

...

alarm(0); // 알람 해제

}

}


SIGALRM에 대한 핸들러를 설정해두고, alarm(n)함수를 호출하면, n초후에 핸들러가 호출된다. 문제가 없을 경우 알람을 해제하고, 전체적으로 이 과정을 반복하도록 하였다. 

위 소스에서는 알람이 발생되면, 메인루프의 한 턴을 5초이상 쓰고 있다는 것인데, 이런 경우는 비정상적인 상황이라고 판단하여 코어 덤프하고 종료하도록 하였다. 나중에 gdb 를 이용하면 무슨짓을 하고 있었는지 상세하게 확인할 수 있다. 

앞서 포스트의 Call stack, Back trace글을 참고.


Posted by Jadumate
c++2013.07.03 21:37

누가 이 함수를 호출하였을까?


개발환경 : C++/STL/Linux/gcc

디버깅을 하다보면, 대체 어디서 이 함수를 호출했을까 궁금해질때가 있다.  이럴때 호출한 함수들을 역순으로 알아낼 수 있는 함수 루틴. 

#include <execinfo.h>

void call_stack_dump() {

void *   array[50];

char **  messages;

int      size, i;


size     = backtrace(array, 50);

messages = backtrace_symbols(array, size);


// skip first stack frame (points here)

for (i = 0; i < size; ++i){

fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);

}


free(messages);

}


컴파일 할때에 -g -rdynamic 옵션을 주어야 한다. 이 방법 말고도 프로그램을 바로 죽여도 좋다면 더 쉬운 방법이 있다.


abort()를 이용하여 코어덤프하고 종료시키자

 #include <stdlib.h>

 void abort(void);


이렇게 하면 core 파일이 생기는데,  gdb를 이용하여 back trace하면 자세히 확인할 수 있다. 



core 파일을 이용하여 GDB로 Backtrace 하기


$ gdb chat4 core.2838

Core was generated by `/work/chat4 -D'.

Program terminated with signal 6, Aborted.

#0  0xffffe410 in __kernel_vsyscall ()

(gdb) bt

#0  0xffffe410 in __kernel_vsyscall ()

#1  0x00943df0 in raise () from /lib/libc.so.6

#2  0x00945701 in abort () from /lib/libc.so.6

#3  0x080579ed in SigAlarm (signo=14) at chat4.cc:3799

#4  <signal handler called>

#5  0x00982bbb in _int_free () from /lib/libc.so.6

#6  0x00983329 in free () from /lib/libc.so.6

#7  0xf7f50fe1 in operator delete(void*) () from /usr/lib/libstdc++.so.6

#8  0xf7f2dbad in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_M_destroy(std::allocator<char> const&) () from /usr/lib/libstdc++.so.6

#9  0xf7f30757 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() () from /usr/lib/libstdc++.so.6

#10 0x08065515 in __gnu_cxx::new_allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >::destroy (this=0xfff3c417, __p=0x9e23750)

...


불특정한 순간에 무한루프에 빠지는 증상을 찾기 위해, SIGALRM을 설정하여, 알람이 발생한 경우 abort()를 호출하여 core dump 하도록 하였다. 이 경우는 string 을 free 하다가 미궁(?)에 빠진 것인데... 아직 정확한 원인은 못 찾고 있다. ㅠㅠ


Posted by Jadumate

티스토리 툴바