[分享]web recaptcha code 示例

本文转载自小猫的世界MUD,原文链接:https://catworld.muds.me/forum/viewtopic.php?f=23&t=738

通过网页验证码防止机器人的示例,类似北大侠客行中fullme一样。

// 2017 05 07 by Natsu, using Google recaptcha to identify bots
// 流程說明
// mob的init觸發第一個web連線 把資料傳給web端
// web端紀錄用戶是否通過驗證
// mob定期從web取得全部資料 確認玩家是否通過驗證

#include <login.h>
#include <ansi.h>
#include <net/socket.h>
#include <socket_err.h>

inherit NPC;
// test_time 是驗證的時間,超過時間沒完成驗證就抓去關
int test_time = 300;
// 提供給玩家的基本url,後面會加上一次性token
string url = "http://wf.twkang.net/robot/";

void create()
{
    set_name("冥府判官",({"hell judge", "judge"}));
    set("invis",1);
    set("gender", "男性" );
    set("age", 4321);
    setup();
}

void init()
{
    object me=this_player();
    ::init();
    if(!query_heart_beat(this_object()))
        set_heart_beat(1);
    this_player()->start_busy(1);
    // 呼叫judge(),進行第一次web連線傳送玩家資料過去
    call_out("judge", 1, me);
    // 下面是開始檢查的部分
    if(!this_object()->query_temp("callout")){
        this_object()->set_temp("callout",1);
        call_out("check", 10);
    }
}
// 用來建立一次性token的function
string rand_string(int length)
{
    string output = "", words = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    int i, tmp;
    for (i=0; i<length; i++) {
        tmp = random(strlen(words));
        output += words[tmp..tmp];
    }
    return output;
}
// 建立連線最主要的function
// read_callback: 處理socket response
// write_callback: 處理socket request
// sendData: 要在request時送過去web的資料
int do_request(string read_callback, string write_callback, string sendData) {
    int err, m_socket;
    // 建立socket並將resource id 存在m_socket
    m_socket = socket_create(STREAM, read_callback,"socket_close");
    if ( m_socket < 0 ){
        log_file("www",sprintf("[%s]無法初始化 Socket .\n", change_time_scale(time()) ));
        return ;
    }
    // 將資料放在obj的tempdata之後才能取用
    this_object()->set_temp("d_"+m_socket, sendData);
    // 實際建立連線的部分
    // m_socket會被傳到read_callback,write_callback
    err = socket_connect(m_socket,"127.0.0.1 80",read_callback,write_callback );
    if( err!=EESUCCESS ) {
        CHANNEL_D->do_channel(this_object(),"sys",sprintf("[%s]啟動 Socket 失敗.\n",
        change_time_scale(time()) ));
    }
}
// 告訴玩家他還有多久可以進行驗證
void tell_url(object user, int remain_time)
{
    string output = "\n" + HIR + "請開啟以下連結,並完成驗證。\n";
    output += "剩餘驗證時間:";
    if(remain_time>=60) {
        output += chinese_number(remain_time/60) + "分";
    }
    if(remain_time % 60 !=0 || remain_time == 0) {
        output += chinese_number(remain_time%60) + "秒";
    } else if(remain_time>=60) {
        output += "鐘";
    }
    output += "。\n\n" + HIW + url + user->query_temp("answer/token") + NOR + "\n";
    tell_object(user, output);
}
// 定期從web取回資料
void check()
{
    object *inv;
    int a;
    do_request("check_response", "check_request", "");
    remove_call_out("check");
    // 只要環境內還有user就每10秒執行一次
    inv = all_inventory(environment(this_object()));
    for (a = 0; a < sizeof(inv); a++) {
        if (userp(inv[a])) {
            call_out("check", 10);
            return;
        }
    }
    this_object()->delete_temp("callout");
}
// 開始呼叫的部分
// 傳過去web的格式是 "token,id,time,ip,passed(0 or 1),\n";
void judge(object player)
{
    int answer_time;
    string token, id, sendData;

    answer_time = time();
    id = player->query("id");
    token = rand_string(12);

    player->set_temp("answer/token", token);
    player->set_temp("answer/time", answer_time);
    sendData = token + "," + id + "," + answer_time + "," + query_ip_number(player) + ",0,\n";
    do_request("start_response", "start_request", sendData);
    tell_url(player, test_time);
}

void start_response(int fd, mixed message)
{
    // no need to get status
}

void start_request(int fd)
{
    string sendData = this_object()->query_temp("d_"+fd);
    socket_write(fd,
        sprintf("GET /robotStart?data=%s HTTP/1.1\nHOST: wf.twkang.net\n\n", sendData));
    this_object()->delete_temp("d_"+fd);
    return ;
}
// 取得清單確認玩家是否通過驗證
void check_response(int fd, mixed message)
{
    string data, reading, *tmp, ot;
    int a, remain_time, line_num;
    object *inv;

    inv = all_inventory(environment(this_object()));
    for (a = 0; a < sizeof(inv); a++) {
        // 是玩家而且還在進行驗證中
        if (userp(inv[a]) && inv[a]->query_temp("answer/token")) {
            // 確認剩餘時間
            remain_time = test_time - (time() - inv[a]->query_temp("answer/time"));
            if (remain_time >= 0) {
                // do check
                // 取回資料是csv格式
                // 欄位與送過去的相同
                // 這邊先拆每筆資料
                    foreach(reading in explode(message,"\n"))
                    {
                        if(sscanf(reading, "data: %s", data)==1){
                            if (strlen(data)<3) {
                                continue;
                            }
                            // 再取得每個欄位
                            tmp = explode(data, ",");
                            // 如果token相同且已經通過驗證
                            if(tmp[0] == inv[a]->query_temp("answer/token") && tmp[4] != "0") {
                                if(tmp[4]=="1"){
                                    // same ip
                                    inv[a]->set_temp("answer/right", 1);
                                    log_file("ROBOT", sprintf(HIW"[%s] %s(%s) passed with same ip\n"NOR,
                                 change_time_scale(time()), inv[a]->name(1),inv[a]->query("id")));
                                } else if (tmp[4]=="2"){
                                    // different ip
                                    inv[a]->set_temp("answer/right", 1);
                                    log_file("ROBOT", sprintf(HIY"[%s] %s(%s) passed with different ip\n"NOR,
                                 change_time_scale(time()), inv[a]->name(1),inv[a]->query("id")));
                                }
                                command("say 「"HIC+ inv[a]->name() +HIY"」通過了驗證,請按<free>離開\n"NOR);
                                inv[a]->delete_temp("answer/token");
                                inv[a]->delete_temp("answer/time");
                            }
                        }
                    }
                // 如果時間還沒到而且沒通過驗證
                // 就繼續告知剩餘時間
                if(inv[a]->query_temp("answer/token")) {
                    tell_url(inv[a], remain_time);
                }
            // 超過驗證時間沒回應 掰掰
            } else {
                // bye~~
                command("say 「"HIM+ inv[a]->name() +HIY"」,你反應太慢了喔!進牢房蹲吧!!\n"NOR);
                inv[a]->delete_temp("accuse");
                inv[a]->delete_temp("answer/token");
                inv[a]->delete_temp("answer/time");
                inv[a]->receive_damage("kee", 1, this_object());
                inv[a]->move("/d/auso/room2");
                inv[a]->set("startroom","/d/auso/room2");
                ot = (string)inv[a]->query("title");
                inv[a]->set("old_title",ot);
                inv[a]->set("title",HIW BLK"死囚"NOR);
                inv[a]->set("bot_jail",1);
                inv[a]->save();
                tell_object(users(), HIR"該死的"+inv[a]->name(1)+"因掛機器人經審判查證屬實被關入死牢囉!\n"NOR);
                log_file("ROBOT", sprintf(HIM"[%s] %s(%s) jailed because no answer\n"NOR,
                         change_time_scale(time()), inv[a]->name(1),inv[a]->query("id")));
            }
        }
    }
}

void check_request(int fd)
{
    socket_write(fd,
        sprintf("GET /robotCheck HTTP/1.1\nHOST: wf.twkang.net\n\n"));
    return ;
}
京ICP备13031296号-4