diff options
author | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2009-10-22 19:54:59 +0800 |
---|---|---|
committer | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2009-10-22 19:54:59 +0800 |
commit | 83b3da32a80eb87eddae6c26c3e709f78e4a99eb (patch) | |
tree | 90414e465d50c4750f93963fa23e264c76eb91be | |
parent | 1c91369d42e063133443b0cafccc7d6fdb4b66bb (diff) | |
download | pttbbs-83b3da32a80eb87eddae6c26c3e709f78e4a99eb.tar pttbbs-83b3da32a80eb87eddae6c26c3e709f78e4a99eb.tar.gz pttbbs-83b3da32a80eb87eddae6c26c3e709f78e4a99eb.tar.bz2 pttbbs-83b3da32a80eb87eddae6c26c3e709f78e4a99eb.tar.lz pttbbs-83b3da32a80eb87eddae6c26c3e709f78e4a99eb.tar.xz pttbbs-83b3da32a80eb87eddae6c26c3e709f78e4a99eb.tar.zst pttbbs-83b3da32a80eb87eddae6c26c3e709f78e4a99eb.zip |
* add new Common Chat Window framework
git-svn-id: http://opensvn.csie.org/pttbbs/trunk@4952 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
-rw-r--r-- | pttbbs/include/proto.h | 7 | ||||
-rw-r--r-- | pttbbs/include/pttstruct.h | 33 | ||||
-rw-r--r-- | pttbbs/include/vtuikit.h | 2 | ||||
-rw-r--r-- | pttbbs/mbbsd/Makefile | 2 | ||||
-rw-r--r-- | pttbbs/mbbsd/ccw.c | 1071 | ||||
-rw-r--r-- | pttbbs/mbbsd/chat.c | 393 |
6 files changed, 1179 insertions, 329 deletions
diff --git a/pttbbs/include/proto.h b/pttbbs/include/proto.h index cd386d72..afa35995 100644 --- a/pttbbs/include/proto.h +++ b/pttbbs/include/proto.h @@ -142,10 +142,13 @@ int g_card_jack(void); int g_ten_helf(void); int card_99(void); +/* ccw (common chat window) */ +// int ccw_process(CCW_CTX *ctx); +int ccw_talk(int fd, int destuid); // common chat window: private talk +int ccw_chat(int fd); // common chat window: chatroom + /* chat */ int t_chat(void); -int ccw_talk(int fd, int destuid); // common chat window: private talk -int ccw_chat(); // common chat window: chatroom /* chc */ void chc(int s, ChessGameMode mode); diff --git a/pttbbs/include/pttstruct.h b/pttbbs/include/pttstruct.h index 636076fc..7d8664a6 100644 --- a/pttbbs/include/pttstruct.h +++ b/pttbbs/include/pttstruct.h @@ -437,6 +437,39 @@ typedef struct menu_t { char mtitle[STRLEN]; } menu_t; +// Common Chat Window (ccw) +typedef struct CCW_CTX { + int line; // position of next line to append data + int abort; // indicate session complete + int abort_vget;// temporary abort from input + int reset_scr; // need to redraw everything + int local_echo;// should we echo local input? + FILE *log; // log file handle + char *log_fpath;// path of log file + void *arg; // private argument + char *sep_help_msg; // quick help message on separators + + int fd; // remote connection + char *remote_id;// remote user id + char *local_id; // local user id + + const int max_input_len; + + // layout renderers + void (*header) (struct CCW_CTX *); + void (*prompt) (struct CCW_CTX *); + void (*footer) (struct CCW_CTX *); + void (*separators) (struct CCW_CTX *); // render header and footer separate lines + + // content processors + int (*peek_key) (struct CCW_CTX *, int key); + int (*peek_cmd) (struct CCW_CTX *, const char *buf, int local); + void (*print_line) (struct CCW_CTX *, const char *buf, int local); // print on screen + void (*log_line) (struct CCW_CTX *, const char *buf, int local); // log to file + void (*post_input) (struct CCW_CTX *, const char *buf, int local); // after valid input +} CCW_CTX; + + /* Used to pass commands to the readmenu. * direct mapping, indexed by ascii code. */ #define onekey_size ((int) 'z') diff --git a/pttbbs/include/vtuikit.h b/pttbbs/include/vtuikit.h index 4866db4f..faf8dcba 100644 --- a/pttbbs/include/vtuikit.h +++ b/pttbbs/include/vtuikit.h @@ -96,7 +96,7 @@ typedef struct { } VGET_RUNTIME; typedef int (*VGET_FCALLBACK)(int key, VGET_RUNTIME *prt, void *instance); -typedef struct { +typedef struct VGET_CALLBACKS { VGET_FCALLBACK peek; // called immediately after key hit VGET_FCALLBACK data; // called before inserting character data VGET_FCALLBACK change; // called after buffer changed (the key may be an editing key) diff --git a/pttbbs/mbbsd/Makefile b/pttbbs/mbbsd/Makefile index 8de2cff3..e302de42 100644 --- a/pttbbs/mbbsd/Makefile +++ b/pttbbs/mbbsd/Makefile @@ -11,7 +11,7 @@ PROG= mbbsd COREOBJS = bbs.o announce.o read.o board.o cache.o cal.o brc.o mail.o record.o fav.o ACCOBJS = user.o register.o passwd.o emaildb.o NETOBJS = mbbsd.o io.o term.o telnet.o -TALKOBJS = talk.o chat.o friend.o +TALKOBJS = friend.o talk.o chat.o ccw.o UTILOBJS = stuff.o kaede.o convert.o name.o syspost.o PAGEROBJS= more.o pmore.o PLUGOBJS = calendar.o ordersong.o gamble.o vice.o angel.o diff --git a/pttbbs/mbbsd/ccw.c b/pttbbs/mbbsd/ccw.c new file mode 100644 index 00000000..dedadaf8 --- /dev/null +++ b/pttbbs/mbbsd/ccw.c @@ -0,0 +1,1071 @@ +/* $Id$ */ +#include "bbs.h" + +// Common Chat Window (CCW) +// piaip's new implementation for chatroom and private talk. +// Author: Hung-Te Lin (piaip) +// -------------------------------------------------------------------------- +// Copyright (c) 2009 Hung-Te Lin <piaip@csie.ntu.edu.tw> +// All rights reserved. +// Distributed under BSD license (GPL compatible). +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// -------------------------------------------------------------------------- +// Common Chat Window layout: +// [header] +// [header separate line] +// [ccw] user: message\n [CCW_INIT_LINE] +// [ccw] ... +// [ccw] ->\n [CCW_STOP_LINE] +// [footer separate line][sep_help_msg] +// prompt -> [input here] +// [footer] +// -------------------------------------------------------------------------- + +#define CCW_MAX_INPUT_LEN (STRLEN) +#define CCW_INIT_LINE (2) +#define CCW_STOP_LINE (t_lines-3) + +// input style +#define CCW_REMOTE (0) +#define CCW_LOCAL (1) +#define CCW_LOCAL_MSG (2) + +/////////////////////////////////////////////////////////////////////////// +// CCW helpers + +// utilities declaration +void ccw_prepare_line(CCW_CTX *ctx); + +// default callback handlers +static void +ccw_header(CCW_CTX *ctx) +{ + move(0, 0); + SOLVE_ANSI_CACHE(); + clrtoeol(); + + if (ctx->header) + { + ctx->header(ctx); + return; + } + + outs("Common Chat Window"); +} + +static void +ccw_footer(CCW_CTX *ctx) +{ + move(b_lines, 0); + SOLVE_ANSI_CACHE(); + clrtoeol(); + + if (ctx->footer) + { + ctx->footer(ctx); + return; + } + + vs_footer(" CCW ", " (/b) 離開 "); +} + +static void +ccw_separators(CCW_CTX *ctx) +{ + int i; + + if (ctx->separators) + { + ctx->separators(ctx); + return; + } + + move(CCW_INIT_LINE-1, 0); + vpad(t_columns-2, "─"); + outc('\n'); + + i = ctx->sep_help_msg ? strlen(ctx->sep_help_msg) : 0; + move(CCW_STOP_LINE, 0); + vpad(t_columns-2-i, "─"); + if (i) outs(ctx->sep_help_msg); + outc('\n'); +} + +static void +ccw_prompt(CCW_CTX *ctx) +{ + move(b_lines-1, 0); + SOLVE_ANSI_CACHE(); + clrtoeol(); + + if (ctx->prompt) + { + ctx->prompt(ctx); + return; + } + + prints("%s: ", ctx->local_id); +} + +static int +ccw_peek_cmd(struct CCW_CTX *ctx, const char *buf, int local) +{ + if (ctx->peek_cmd) + return ctx->peek_cmd(ctx, buf, local); + + // sample command: /b + if (strcasecmp(buf, "/b") == 0) + { + ctx->abort = 1; + return 1; + } + return 0; +} + +static void +ccw_print_line (struct CCW_CTX *ctx, const char *buf, int local) +{ + ccw_prepare_line(ctx); + if (ctx->print_line) + { + ctx->print_line(ctx, buf, local); + return; + } + + if (local <= CCW_LOCAL) // local or remote, but not message + { + if (local) { + outs(ctx->local_id); + } else { + outs(ANSI_COLOR(1)); + outs(ctx->remote_id); + } + outs(": "); + } + + outs(buf); + outs(ANSI_RESET "\n→"); +} + +static void +ccw_log_line (struct CCW_CTX *ctx, const char *buf, int local) +{ + if (!ctx->log) + return; + + if (ctx->log_line) + { + ctx->log_line(ctx, buf, local); + return; + } + + if (local <= CCW_LOCAL) // local or remote, but not message + { + fprintf(ctx->log, "%s%s: ", + local ? "" : ANSI_COLOR(1), + local ? ctx->local_id : ctx->remote_id); + } + fprintf(ctx->log, "%s%s\n", + buf, local ? "" : ANSI_RESET); +} + +// CCW utilities + +// clear content and redraw all widgets +void +ccw_reset_scr(CCW_CTX *ctx) +{ + clear(); + ctx->line = CCW_INIT_LINE; + ctx->reset_scr = NA; + ccw_header(ctx); + ccw_separators(ctx); + // following will be called again for each prompts + ccw_footer(ctx); + ccw_prompt(ctx); +} + +// prepare screen buffer to display one line of text +void +ccw_prepare_line(CCW_CTX *ctx) +{ + move(ctx->line, 0); + if ( ctx->line+1 < CCW_STOP_LINE) + { + // simply append + ctx->line ++; + } + else + { + // scroll screen buffer + region_scroll_up(CCW_INIT_LINE, CCW_STOP_LINE - CCW_INIT_LINE); + move(ctx->line-1, 0); + // TODO after resize, we may need to scroll more than once. + } + clrtoeol(); +} + +// verify if buf (separated by space) matches partial of cmd (case insensitive) +int +ccw_partial_match(const char *buf, const char *cmd) +{ + size_t szbuf = strcspn(buf, str_space); + + assert(*cmd); + if (!szbuf) + return 0; + + return strncasecmp(buf, cmd, szbuf) == 0; +} + +// print and log one line of text. +void +ccw_add_line (struct CCW_CTX *ctx, const char *buf, int local) +{ + // only print/log local when local_echo is enabled. + if (local != CCW_LOCAL || ctx->local_echo) + { + ccw_print_line(ctx, buf, local); + if (ctx->log) + ccw_log_line(ctx, buf, local); + } + + if (ctx->post_input) + ctx->post_input(ctx, buf, local); +} + +// VGET callback adaptors + +static int +ccw_vgetcb_peek(int key, VGET_RUNTIME *prt GCC_UNUSED, void *instance) +{ + CCW_CTX *ctx = (CCW_CTX*) instance; + assert(ctx); + if (ctx->peek_key && + ctx->peek_key(ctx, key)) + { + return (ctx->abort_vget || ctx->abort) ? VGETCB_ABORT : VGETCB_NEXT; + } + + return VGETCB_NONE; + +} + +// main processor + +int +ccw_process(CCW_CTX *ctx) +{ + char inbuf[STRLEN]; + VGET_CALLBACKS vgetcb = { ccw_vgetcb_peek }; + + assert( ctx->max_input_len > 2 && + ctx->max_input_len <= CCW_MAX_INPUT_LEN); + ccw_reset_scr(ctx); + +#ifdef DEBUG + ccw_print_line(ctx, ANSI_COLOR(1;30) + "◆ Powered by piaip's Common Chat Window" ANSI_RESET, + CCW_LOCAL_MSG); +#endif + + while (!ctx->abort) + { + // reset screen or print prompts (may be destroyed by short message) + if (ctx->reset_scr) + ccw_reset_scr(ctx); + else { + ccw_footer(ctx); + ccw_prompt(ctx); + // reset again if prompt request to redraw. + if (ctx->reset_scr) + ccw_reset_scr(ctx); + } + + // get input + ctx->abort_vget = 0; + vgetstring(inbuf, ctx->max_input_len, VGET_TRANSPARENT, "", + &vgetcb, ctx); + + // quick check for end flag or exit command. + if (ctx->abort) + break; + + // quick continue for empty input + trim(inbuf); + if (!*inbuf) + continue; + + // process commands + if (ccw_peek_cmd(ctx, inbuf, CCW_LOCAL)) + continue; + + // accept this data + ccw_add_line(ctx, inbuf, CCW_LOCAL); + } + + if (ctx->log) + fflush(ctx->log); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////// +// Talk / Chat Adaptors + +int +ccw_talkchat_close_log(CCW_CTX *ctx, int force_decide, int is_chat) +{ + char ans[3]; + const char *fpath = ctx->log_fpath; + + if (!ctx->log) + return 0; + assert(*fpath); + + // flush + fclose(ctx->log); + ctx->log = NULL; + more(fpath, NA); + + + // prompt user to decide how to deal with the log + while (1) { + char c; + getdata(b_lines - 1, 0, + force_decide ? "清除(C) 移至備忘錄(M)? [c/m]: " : + "清除(C) 移至備忘錄(M)? [C/m]", + ans, sizeof(ans), LCECHO); + + // decide default answer + c = *ans; + if (c != 'c' && c != 'm') + { + if (!force_decide) + c = 'c'; + } + + if (c == 'c') + break; + if (c == 'm') + { + char subj[STRLEN]; + if (is_chat) + snprintf(subj, sizeof(subj), "會議記錄"); + else + snprintf(subj, sizeof(subj), "對話記錄 (%s)", ctx->remote_id); + + if (mail_log2id(cuser.userid, subj, fpath, "[備.忘.錄]", 0, 1) < 0) + vmsg("錯誤: 備忘錄儲存失敗。"); + break; + } + + // force user to input correct answer... + move(b_lines-2, 0); clrtobot(); + prints(ANSI_COLOR(0;1;3%d) "請正確輸入 c 或 m 的指令。" ANSI_RESET, + (int)((now % 7)+1)); + if (c == 0) outs("為避免誤按所以只按 ENTER 是不行的。"); + } + unlink(fpath); + return 1; +} + +///////////////////////////////////////////////////////////////////////////// +// Talk + +#define CCW_TALK_CMD_PREFIX '/' +#define CCW_TALK_CMD_PREFIX_STR "/" +#define CCW_TALK_CMD_BYE "bye" +#define CCW_TALK_CMD_CLEAR "clear" +#define CCW_TALK_CMD_CLS "cls" +#define CCW_TALK_CMD_HELP "help" + +static ssize_t +ccw_talk_send(CCW_CTX *ctx, const char *msg) +{ + // protocol: [len][msg] + char len = strlen(msg); + assert(len >= 0 && (int)len == strlen(msg)); + if (len < 1) return 0; + + if (towrite(ctx->fd, &len, 1) != 1) + return -1; + if (towrite(ctx->fd, msg, len)!= len) + return -1; + return len; +} + +static ssize_t +ccw_talk_recv(CCW_CTX *ctx, char *buf, size_t szbuf) +{ + char len = 0; + buf[0] = 0; + if (toread(ctx->fd, &len, 1) != 1) + return -1; + if (toread(ctx->fd, buf, len)!= len) + return -1; + assert(len >= 0 && len < szbuf); + buf[(size_t)len] = 0; + return len; +} + +static ssize_t +ccw_talk_send_bye(CCW_CTX *ctx) +{ + return ccw_talk_send(ctx, + CCW_TALK_CMD_PREFIX_STR CCW_TALK_CMD_BYE); +} + +static void +ccw_talk_header(CCW_CTX *ctx) +{ + prints(ANSI_COLOR(1;37;46) " 【聊天】 " + ANSI_COLOR(45) " %-*s" ANSI_RESET "\n", + t_columns - 12, ctx->remote_id); +} + +static void +ccw_talk_footer(CCW_CTX *ctx) +{ + vs_footer("【聊天】", " (" + CCW_TALK_CMD_PREFIX_STR CCW_TALK_CMD_BYE ")結束聊天 (" + CCW_TALK_CMD_PREFIX_STR CCW_TALK_CMD_CLEAR ")清除畫面" + "\t(Ctrl-C)離開 "); +} + +static int +ccw_talk_peek_cmd(CCW_CTX *ctx, const char *buf, int local) +{ + if (buf[0] != CCW_TALK_CMD_PREFIX) + return 0; + buf++; + + // process commands + if (ccw_partial_match(buf, CCW_TALK_CMD_HELP)) + { + ccw_print_line(ctx, "[說明]: 可輸入 " + CCW_TALK_CMD_PREFIX_STR CCW_TALK_CMD_CLEAR " 清除畫面或 " + CCW_TALK_CMD_PREFIX_STR CCW_TALK_CMD_BYE " 離開。", + CCW_LOCAL_MSG); + return 1; + } + if (ccw_partial_match(buf, CCW_TALK_CMD_BYE)) + { + ccw_talk_send_bye(ctx); // notify dest user if he's still online + ctx->abort = 1; + return 1; + } + if (ccw_partial_match(buf, CCW_TALK_CMD_CLEAR) || + ccw_partial_match(buf, CCW_TALK_CMD_CLS)) + { + ctx->reset_scr = YEA; + return 1; + } + return 0; +} + +static void +ccw_talk_post_input(CCW_CTX *ctx, const char *buf, int local) +{ + if (local != CCW_LOCAL) + return; + + // send message to server if possible. + ctx->abort = (ccw_talk_send(ctx, buf) <= 0); +} + +static int +ccw_talk_peek_key(CCW_CTX *ctx, int key) +{ + switch (key) { + case I_OTHERDATA: // incoming + { + char buf[STRLEN]; + if (ccw_talk_recv(ctx, buf, sizeof(buf)) < 1) + { + ctx->abort = YEA; + return 1; + } + // process commands + if (strcasecmp(buf, CCW_TALK_CMD_BYE) == 0) + { + ctx->abort = YEA; + return 1; + } + // received something, let's print it. + ccw_add_line(ctx, buf, CCW_REMOTE); + return 1; + } + + case Ctrl('C'): + { + VREFSCR scr = vscr_save(); + add_io(0, 0); + if (vans("確定要中止聊天嗎? [y/N]: ") == 'y') + ctx->abort = YEA; + vscr_restore(scr); + add_io(ctx->fd, 0); + ccw_footer(ctx); + } + // notify remote user + if (ctx->abort) + ccw_talk_send(ctx, CCW_TALK_CMD_BYE); + return 1; + } + return 0; +} + +int +ccw_talk(int fd, int destuid) +{ + char fpath[PATHLEN]; + char remote_id[IDLEN+1], local_id[IDLEN+1]; + + CCW_CTX ctx = { + .fd = fd, + .abort = NA, + + .log = NULL, + .log_fpath = fpath, + .local_echo = YEA, + .max_input_len = STRLEN - IDLEN - 5, // 5 for ": " and more + .remote_id = remote_id, + .local_id = local_id, + .sep_help_msg = " /b 離開 /c 清除畫面 ", + + .header = ccw_talk_header, + .footer = ccw_talk_footer, + + .peek_key = ccw_talk_peek_key, + .peek_cmd = ccw_talk_peek_cmd, + .post_input = ccw_talk_post_input, + }; + + STATINC(STAT_DOTALK); + setutmpmode(TALK); + + // get dest user id + assert(getuserid(destuid)); + strlcpy(remote_id, getuserid(destuid), sizeof(remote_id)); + strlcpy(local_id, cuser.userid, sizeof(local_id)); + assert(ctx.remote_id[0]); + assert(ctx.local_id[0]); + + // create log file + setuserfile(fpath, "talk_XXXXXX"); + ctx.log = fdopen(mkstemp(fpath), "w"); + assert(ctx.log); + fprintf(ctx.log, "[%s] 與 %s 聊天:\n", Cdatelite(&now), ctx.remote_id); + + // entering event loop... + add_io(fd, 0); + ccw_process(&ctx); + + // clean network handle + add_io(0, 0); + close(fd); + + ccw_talkchat_close_log(&ctx, YEA, NA); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////// +// Chat + +#define CHAT_ID_LEN (8) +#define CHAT_ROOM_LEN (IDLEN) +#define CHAT_TOPIC_LEN (48) // must < t_columns-CHAT_ROOM_LEN + +typedef struct ccw_chat_ext { + // misc + int newmail; + int old_rows, old_cols; // for terminal resize auto detection + // buffer + int bufstart; + char buf[128]; + // topic + char topic[CHAT_TOPIC_LEN+1]; +} ccw_chat_ext; + +static ccw_chat_ext * +ccw_chat_get_ext(CCW_CTX *ctx) +{ + assert(ctx->arg); + return (ccw_chat_ext*)ctx->arg; +} + +static void +ccw_chat_check_newmail(CCW_CTX *ctx) +{ + ccw_chat_ext *ext = ccw_chat_get_ext(ctx); + if (ISNEWMAIL(currutmp)) + { + if (!ext->newmail) + { + // no need to log this...? + ccw_print_line(ctx, "◆ 您有未讀的新信件。", CCW_LOCAL_MSG); + ext->newmail = 1; + } + } + else if (ext->newmail) + ext->newmail = 0; +} + +static void +ccw_chat_check_term_resize(CCW_CTX *ctx) +{ + ccw_chat_ext *ext = ccw_chat_get_ext(ctx); + + // detect terminal resize + if (ext->old_rows != t_lines || + ext->old_cols != t_columns) + { + ext->old_rows = t_lines; + ext->old_cols = t_columns; + ctx->reset_scr = YEA; + } +} + +static int +ccw_chat_send(CCW_CTX *ctx, const char *buf) +{ + int len; + char genbuf[200]; + + len = snprintf(genbuf, sizeof(genbuf), "%s\n", buf); + return (send(ctx->fd, genbuf, len, 0) == len); +} + +static int +ccw_chat_send_bye(CCW_CTX *ctx) +{ + return ccw_chat_send(ctx, "/b"); // protocol: bye +} + +static void +ccw_chat_header(CCW_CTX *ctx) +{ + + prints(ANSI_COLOR(1;37;46) " 談天室 [%-*s] " + ANSI_COLOR(45) " 話題: %-*s" ANSI_RESET, + CHAT_ROOM_LEN, ctx->remote_id, + t_columns - CHAT_ROOM_LEN - 20, + ccw_chat_get_ext(ctx)->topic); +} + +static void +ccw_chat_footer(CCW_CTX *ctx) +{ + ccw_chat_check_term_resize(ctx); + + // a little weird, but this looks like the best place to do ZA here... + if (ZA_Waiting()) + { + // process ZA + VREFSCR scr = vscr_save(); + add_io(0, 0); + ZA_Enter(); + add_io(ctx->fd, 0); + vscr_restore(scr); + } + + // draw real footer + vs_footer("【談天室】", + " (PgUp/PgDn)回顧聊天記錄 (Ctrl-Z)快速切換\t(Ctrl-C)離開聊天室"); +} + +static void +ccw_chat_prompt(CCW_CTX *ctx) +{ + prints("%-*s》", CHAT_ID_LEN, ctx->local_id); +} + +static void +ccw_chat_print_line (struct CCW_CTX *ctx, const char *buf, int local) +{ + assert(local != CCW_LOCAL); + outs(buf); + outs(ANSI_RESET "\n→"); +} + +static void +ccw_chat_log_line (struct CCW_CTX *ctx, const char *buf, int local) +{ + assert(local != CCW_LOCAL); + fprintf(ctx->log, "%s\n", buf); +} + +static int +ccw_chat_recv(CCW_CTX *ctx) +{ + int c, len; + char *bptr; + ccw_chat_ext *cb = ccw_chat_get_ext(ctx); + + len = sizeof(cb->buf) - cb->bufstart - 1; + if ((c = recv(ctx->fd, cb->buf + cb->bufstart, len, 0)) <= 0) + return -1; + c += cb->bufstart; + + bptr = cb->buf; + while (c > 0) { + len = strlen(bptr) + 1; + if (len > c && (unsigned)len < (sizeof(cb->buf)/ 2) ) + break; + + if (*bptr == '/') { + switch (bptr[1]) { + case 'c': + ccw_reset_scr(ctx); + break; + case 'n': + strlcpy(ctx->local_id, bptr+2, CHAT_ID_LEN+1); + DBCS_safe_trim(ctx->local_id); + ccw_prompt(ctx); + break; + case 'r': + strlcpy(ctx->remote_id, bptr+2, CHAT_ROOM_LEN+1); + DBCS_safe_trim(ctx->remote_id); + ccw_header(ctx); + break; + case 't': + { + ccw_chat_ext *ext = ccw_chat_get_ext(ctx); + strlcpy(ext->topic, bptr+2, CHAT_TOPIC_LEN+1); + DBCS_safe_trim(ext->topic); + ccw_header(ctx); + } + + } + } else { + // received something, let's print it. + if (*bptr != '>' || PERM_HIDE(currutmp)) + ccw_add_line(ctx, bptr, CCW_REMOTE); + } + + c -= len; + bptr += len; + } + + if (c > 0) { + memmove(cb->buf, bptr, sizeof(cb->buf)-(bptr-cb->buf)); + cb->bufstart = len - 1; + } else + cb->bufstart = 0; + return 0; +} + +static int +ccw_chat_peek_cmd(CCW_CTX *ctx, const char *buf, int local) +{ +#ifdef EXP_ANTIFLOOD + { + // prevent flooding */ + static time4_t lasttime = 0; + static int flood = 0; + + syncnow(); + if (now - lasttime < 3 ) + { + // 3 秒內洗半面是不行的 ((25-5)/2) + if( ++flood > 10 ) + { + // flush all input! + unsigned char garbage[STRLEN]; + drop_input(); + while (wait_input(1, 0)) + { + if (num_in_buf()) + drop_input(); + else + tty_read(garbage, sizeof(garbage)); + } + drop_input(); + vmsg("請勿大量剪貼或造成洗板面的效果。"); + // log? + sleep(2); + return 1; + } + } else { + lasttime = now; + flood = 0; + } + } +#endif // anti-flood + + ccw_chat_check_newmail(ctx); + + if (buf[0] != '/') return 0; + buf ++; + + if (ccw_partial_match(buf, "help")) + { + static const char *hlp_op[] = { + "[/f]lag [+-][ls]", "設定鎖定、秘密狀態", + "[/i]nvite <id>", "邀請 <id> 加入談天室", + "[/k]ick <id>", "將 <id> 踢出談天室", + "[/o]p <id>", "將 Op 的權力轉移給 <id>", + "[/t]opic <text>", "換個話題", + "[/w]all", "廣播 (站長專用)", + " /ban <userid>", "拒絕 <userid> 再次進入此聊天室 (加入黑名單)", + " /unban <userid>", "把 <userid> 移出黑名單", + NULL, + }, *hlp[] = { + " /help op", "談天室管理員專用指令", + "[//]help", "MUD-like 社交動詞", + "[/a]ct <msg>", "做一個動作", + "[/b]ye [msg]", "道別", + "[/c]lear", "清除螢幕", + "[/j]oin <room>", "建立或加入談天室", + "[/l]ist [room]", "列出談天室使用者", + "[/m]sg <id> <msg>", "跟 <id> 說悄悄話", + "[/n]ick <id>", "將談天代號換成 <id>", + "[/p]ager", "切換呼叫器", + "[/r]oom ", "列出一般談天室", + "[/w]ho", "列出本談天室使用者", + " /whoin <room>", "列出談天室<room> 的使用者", + " /ignore <userid>", "忽略指定使用者的訊息", + " /unignore <userid>", "停止忽略指定使用者的訊息", + NULL, + }; + + const char **p = hlp; + char msg[STRLEN]; + + if (strcasestr(buf, " op")) + { + p = hlp_op; + ccw_print_line(ctx, "談天室管理員專用指令", CCW_LOCAL_MSG); + } + while (*p) + { + snprintf(msg, sizeof(msg), " %-20s- %s", p[0], p[1]); + ccw_print_line(ctx, msg, CCW_LOCAL_MSG); + p += 2; + } + return 1; + } + if (ccw_partial_match(buf, "clear") || + ccw_partial_match(buf, "cls")) + { + ccw_reset_scr(ctx); + return 1; + } + if (ccw_partial_match(buf, "date")) + { + char genbuf[STRLEN]; + syncnow(); + snprintf(genbuf, sizeof(genbuf), + "◆ " BBSNAME "標準時間: %s", Cdate(&now)); + ccw_add_line(ctx, genbuf, CCW_LOCAL_MSG); + return 1; + } + if (ccw_partial_match(buf, "pager")) + { + char genbuf[STRLEN]; + char *msgs[PAGER_MODES] = { + /* Ref: please match PAGER* in modes.h */ + "關閉", "打開", "拔掉", "防水", "好友" + }; + snprintf(genbuf, sizeof(genbuf), "◆ 您的呼叫器:[%s]", + msgs[currutmp->pager = (currutmp->pager + 1) % PAGER_MODES]); + ccw_add_line(ctx, genbuf, CCW_LOCAL_MSG); + return 1; + } + if (ccw_partial_match(buf, "bye")) + { + ccw_chat_send_bye(ctx); + ctx->abort = 1; + return 1; + } + return 0; +} + +static void +ccw_chat_post_input(CCW_CTX *ctx, const char *buf, int local) +{ + if (local != CCW_LOCAL) + return; + + // send message to server if possible. + ctx->abort = (ccw_chat_send(ctx, buf) <= 0); +} + +static int +ccw_chat_peek_key(CCW_CTX *ctx, int key) +{ + switch (key) { + case I_OTHERDATA: // incoming + { + if (ccw_chat_recv(ctx) == -1) + { + ccw_chat_send_bye(ctx); + ctx->abort = YEA; + return 1; + } + ccw_chat_check_newmail(ctx); + return 1; + } + + case Ctrl('C'): + { + VREFSCR scr = vscr_save(); + add_io(0, 0); + if (vans("確定要中止聊天嗎? [y/N]: ") == 'y') + ctx->abort = YEA; + vscr_restore(scr); + add_io(ctx->fd, 0); + ccw_footer(ctx); + } + // notify remote user + if (ctx->abort) + ccw_chat_send_bye(ctx); + return 1; + + case Ctrl('I'): + { + VREFSCR scr = vscr_save(); + add_io(0, 0); + t_idle(); + vscr_restore(scr); + add_io(ctx->fd, 0); + } + return 1; + + case KEY_PGUP: + case KEY_PGDN: + if (ctx->log && ctx->log_fpath) + { + VREFSCR scr = vscr_save(); + add_io(0, 0); + + fflush(ctx->log); + more(ctx->log_fpath, YEA); + + vscr_restore(scr); + add_io(ctx->fd, 0); +#ifndef USE_PFTERM + // XXX screen is buggy when dealing with scroll... + ccw_reset_scr(ctx); +#endif + } + return 1; + + // Support ZA because chat is mostly independent and secure. + case Ctrl('Z'): + { + int za = 0; + VREFCUR cur = vcur_save(); + add_io(0, 0); + za = ZA_Select(); + ccw_footer(ctx); + add_io(ctx->fd, 0); + vcur_restore(cur); + if (za) + ctx->abort_vget = 1; + return 1; + } + } + return 0; +} + +int +ccw_chat(int fd) +{ + char chatid[CHAT_ID_LEN+1] = "myid", + roomid[CHAT_ROOM_LEN+1] = "room"; + char fpath[PATHLEN]; + ccw_chat_ext ext = { + .old_rows = t_lines, + .old_cols = t_columns, + }; + CCW_CTX ctx = { + .fd = fd, + .abort = NA, + + .log = NULL, + .log_fpath = fpath, + .local_echo = NA, + .max_input_len = STRLEN - CHAT_ID_LEN - 3, // 3 for ": " + .remote_id = roomid, + .local_id = chatid, + .arg = &ext, + .sep_help_msg = " /h 查詢指令 /b 離開 ", + + .header = ccw_chat_header, + .footer = ccw_chat_footer, + .prompt = ccw_chat_prompt, + .print_line = ccw_chat_print_line, + .log_line = ccw_chat_log_line, + + .peek_key = ccw_chat_peek_key, + .peek_cmd = ccw_chat_peek_cmd, + .post_input = ccw_chat_post_input, + }; + + // initialize nick + while (1) { + const char *err = "無法使用此代號"; + char cmd[200]; + + getdata(b_lines - 1, 0, "請輸入想使用的聊天暱稱:", chatid, sizeof(chatid), DOECHO); + if(!chatid[0]) + strlcpy(chatid, cuser.userid, sizeof(chatid)); + + // safe truncate + DBCS_safe_trim(chatid); + + // login format: /! UserID ChatID password + snprintf(cmd, sizeof(cmd), "/! %s %s %s", cuser.userid, chatid, cuser.passwd); + ccw_chat_send(&ctx, cmd); + if (recv(ctx.fd, cmd, 3, 0) != 3) { + close(ctx.fd); + vmsg("系統錯誤。"); + return 0; + } + + if (strcmp(cmd, CHAT_LOGIN_OK) == 0) + break; + else if (!strcmp(cmd, CHAT_LOGIN_EXISTS)) + err = "這個代號已經有人用了"; + else if (!strcmp(cmd, CHAT_LOGIN_INVALID)) + err = "這個代號是錯誤的"; + else if (!strcmp(cmd, CHAT_LOGIN_BOGUS)) + err = "請勿派遣分身進入聊天室 !!"; + + move(b_lines - 2, 0); + outs(err); + clrtoeol(); + bell(); + } + + add_io(fd, 0); + setutmpmode(CHATING); + currutmp->in_chat = YEA; + strlcpy(currutmp->chatid, chatid, sizeof(currutmp->chatid)); + + // generate log file + setuserfile(fpath, "chat_XXXXXX"); + ctx.log = fdopen(mkstemp(fpath), "w"); + assert(ctx.log); + fprintf(ctx.log, "[%s] 進入聊天室:\n", Cdatelite(&now)); + + ccw_process(&ctx); + + close(fd); + add_io(0, 0); + currutmp->in_chat = currutmp->chatid[0] = 0; + + ccw_talkchat_close_log(&ctx, NA, YEA); + + return 0; +} diff --git a/pttbbs/mbbsd/chat.c b/pttbbs/mbbsd/chat.c index ddfe83e7..98356513 100644 --- a/pttbbs/mbbsd/chat.c +++ b/pttbbs/mbbsd/chat.c @@ -1,30 +1,19 @@ /* $Id$ */ #include "bbs.h" -// shared by chat and talk. -// Common Chat Window (CCW) layout: -// [header] -// [header separate line] -// [ccw] user: message\n [CCW_INIT_LINE] -// [ccw] ... -// [ccw] ->\n [CCW_STOP_LINE] -// [footer separate line][qhelp] -// prompt -> [input here] -// [footer] - -#define CCW_STOP_LINE (t_lines-3) -#define CCW_INIT_LINE (2) +#define CHAT_STOP_LINE (t_lines-3) +#define CHAT_INIT_LINE (2) /////////////////////////////////////////////////////////////////////////// -// CCW helpers +// CHAT helpers static void -ccw_prepare_newline(int *p_line) +chat_prepare_newline(int *p_line) { assert(p_line); move(*p_line, 0); - if (*p_line < CCW_STOP_LINE - 1) + if (*p_line < CHAT_STOP_LINE - 1) { // simply append (*p_line)++; @@ -33,32 +22,32 @@ ccw_prepare_newline(int *p_line) { // TODO after resize, we may need to scroll more than once. // scroll screen buffer - region_scroll_up(CCW_INIT_LINE, CCW_STOP_LINE - CCW_INIT_LINE); + region_scroll_up(CHAT_INIT_LINE, CHAT_STOP_LINE - CHAT_INIT_LINE); move(*p_line-1, 0); } clrtoeol(); } static void -ccw_clear_main_window(int *p_line) +chat_clear_main_window(int *p_line) { int i; - for (i = CCW_INIT_LINE; i < CCW_STOP_LINE; i++) + for (i = CHAT_INIT_LINE; i < CHAT_STOP_LINE; i++) { move(i, 0); SOLVE_ANSI_CACHE(); clrtoeol(); } if (p_line) - *p_line = CCW_INIT_LINE; + *p_line = CHAT_INIT_LINE; } static void -ccw_draw_separate_lines() +chat_draw_separate_lines() { - move(CCW_INIT_LINE-1, 0); + move(CHAT_INIT_LINE-1, 0); vpad(t_columns-1, "─"); - move(CCW_STOP_LINE, 0); + move(CHAT_STOP_LINE, 0); vpad(t_columns-1, "─"); } @@ -74,7 +63,7 @@ chat_print_line(const char *str) if (*str == '>' && !PERM_HIDE(currutmp)) return; - ccw_prepare_newline(&chatline); + chat_prepare_newline(&chatline); outs(str); outs("\n→"); @@ -85,7 +74,7 @@ chat_print_line(const char *str) static void chat_clear(char *unused GCC_UNUSED) { - ccw_clear_main_window(&chatline); + chat_clear_main_window(&chatline); // render new prompt move(chatline = 2, 0); @@ -151,12 +140,12 @@ chat_recv(struct ChatBuf *cb, int fd, char *chatroom, char *chatid, size_t chati chat_prompt_footer(chatid); break; case 'r': - strlcpy(chatroom, bptr + 2, sizeof(chatroom)); + strlcpy(chatroom, bptr + 2, IDLEN+1); break; case 't': move(0, 0); clrtoeol(); - prints(ANSI_COLOR(1;37;46) " 談天室 [%-12s] " ANSI_COLOR(45) " 話題:%-48s" ANSI_RESET, + prints(ANSI_COLOR(1;37;46) " 談天室 [%-12s] " ANSI_COLOR(45) " 話題: %-48s" ANSI_RESET, chatroom, bptr + 2); } } else @@ -207,7 +196,6 @@ chat_help(char *arg) chathelp("[/m]sg <id> <msg>", "跟 <id> 說悄悄話"); chathelp("[/n]ick <id>", "將談天代號換成 <id>"); chathelp("[/p]ager", "切換呼叫器"); - chathelp("[/q]uery <id>", "查詢網友"); chathelp("[/r]oom ", "列出一般談天室"); chathelp("[/w]ho", "列出本談天室使用者"); chathelp(" /whoin <room>", "列出談天室<room> 的使用者"); @@ -395,61 +383,19 @@ _vgetcb_peek(int key, VGET_RUNTIME *prt GCC_UNUSED, void *instance) } int -ccw_chat(void) +do_chat(int cfd) { - static time4_t lastEnter = 0; - - char chatroom[IDLEN+1] = "";/* Chat-Room Name */ - char inbuf[80], chatid[20] = "", *ptr = ""; - char hasnewmail = 0; - char fpath[PATHLEN]; - int cfd; - int chatting = YEA; - const int chatid_len = 10; + char chatroom[IDLEN+1] = "";/* Chat-Room Name */ + char inbuf[80], chatid[20] = "", *ptr = ""; + char hasnewmail = 0; + char fpath[PATHLEN]; + int chatting = YEA; + const int chatid_len = 10; struct ChatBuf chatbuf; ChatCbParam vgetparam = {0}; - if(HasUserPerm(PERM_VIOLATELAW)) - { - vmsg("請先繳罰單才能使用聊天室!"); - return -1; - } - - if (!HasUserPerm(PERM_CHAT)) - return -1; - - syncnow(); - -#ifdef CHAT_GAPMINS - if ((now - lastEnter)/60 < CHAT_GAPMINS) - { - vmsg("您才剛離開聊天室,裡面正在整理中。請稍後再試。"); - return 0; - } -#endif - -#ifdef CHAT_REGDAYS - if ((now - cuser.firstlogin)/DAY_SECONDS < CHAT_REGDAYS) - { - int i = CHAT_REGDAYS - (now-cuser.firstlogin)/DAY_SECONDS +1; - vmsgf("您還不夠資深喔 (再等 %d 天吧)", i); - return 0; - } -#endif - memset(&chatbuf, 0, sizeof(chatbuf)); - outs(" 驅車前往 請梢候........ "); - - cfd = toconnect(XCHATD_ADDR); - if (cfd < 0) { - outs("\n " - "哇! 沒人在那邊耶...要有那地方的人先去開門啦!..."); - system("bin/xchatd"); - pressanykey(); - return -1; - } - while (1) { getdata(b_lines - 1, 0, "請輸入想使用的聊天暱稱:", chatid, 9, DOECHO); if(!chatid[0]) @@ -480,8 +426,6 @@ ccw_chat(void) clrtoeol(); bell(); } - syncnow(); - lastEnter = now; add_io(cfd, 0); @@ -490,13 +434,13 @@ ccw_chat(void) strlcpy(currutmp->chatid, chatid, sizeof(currutmp->chatid)); clear(); - ccw_draw_separate_lines(); + chat_draw_separate_lines(); #define QHELP_STR " /h 查詢指令 /b 離開 " - move(CCW_STOP_LINE, (t_columns - sizeof(QHELP_STR))/2*2 ); + move(CHAT_STOP_LINE, (t_columns - sizeof(QHELP_STR))/2*2 ); outs(QHELP_STR); chat_prompt_footer(chatid); memset(inbuf, 0, sizeof(inbuf)); - chatline = CCW_INIT_LINE; + chatline = CHAT_INIT_LINE; setuserfile(fpath, "chat_XXXXXX"); flog = fdopen(mkstemp(fpath), "w"); @@ -618,258 +562,57 @@ ccw_chat(void) int t_chat(void) { - return ccw_chat(); -} - -/////////////////////////////////////////////////////////////////////////// -// TALK specific -// private talk for only 2 user (not using chat server) - -typedef struct { - int fd; // remote socket - int next_line; // CCW location - int abort; - FILE *log; // log file - char dest_userid[IDLEN+1]; -} TalkCtx; - -static void -talk_footer() -{ - vs_footer("【聊天】", " (/b)結束聊天 (/c)清除畫面\t(Ctrl-C)離開 "); -} - -static ssize_t -talk_send(int fd, const char *msg) -{ - // protocol: [len][msg] - char len = strlen(msg); - - if (!len) return 0; - assert(len >= 0); - - if (towrite(fd, &len, 1) != 1) - return -1; - if (towrite(fd, msg, len)!= len) - return -1; - return len; -} + static time4_t lastEnter = 0; + int fd; -static ssize_t -talk_recv(int fd, char *buf, size_t szbuf) -{ - char len = 0; - buf[0] = 0; - if (toread(fd, &len, 1) != 1) - return -1; - if (toread(fd, buf, len)!= len) + if (!HasUserPerm(PERM_CHAT)) return -1; - assert(len >= 0 && len < szbuf); - buf[(size_t)len] = 0; - return len; -} - -static void -talk_print_line(int *p_next_line, const char *uid, const char *buf) -{ - ccw_prepare_newline(p_next_line); - if (strcmp(uid, cuser.userid) != 0) - outs(ANSI_COLOR(1)); - prints("%s: %s" ANSI_RESET "\n", uid, buf); - // add prompt - outs("→"); -} - -static void -talk_add_line(TalkCtx *ctx, const char* uid, const char *buf) -{ - int is_self = (strcmp(uid, cuser.userid) == 0); - - talk_print_line(&ctx->next_line, uid, buf); - if (!ctx->log) return; - - fprintf(ctx->log, "%s%s: %s%s\n", - is_self ? "" : ANSI_COLOR(1), - uid, - buf, - is_self ? "" : ANSI_RESET); -} - -static void -talk_clear(TalkCtx *ctx) -{ - ccw_clear_main_window(&ctx->next_line); - - clear(); - - // print header - prints(ANSI_COLOR(1;37;46) " 【聊天】 " ANSI_COLOR(45) " %-*s" ANSI_RESET, - t_columns - 12, ctx->dest_userid); - // snprintf(genbuf, sizeof(genbuf), "與 %s 聊天", ctx.dest_userid); - // vs_hdr(genbuf); - - ccw_draw_separate_lines(); - talk_footer(); - // render new prompt - move(ctx->next_line, 0); - outs("→"); -} - -static int -_talk_vgetcb_peek(int key, VGET_RUNTIME *prt GCC_UNUSED, void *instance) -{ - TalkCtx *p = (TalkCtx*) instance; - assert(p); - - switch (key) { - case I_OTHERDATA: // incoming - { - char buf[STRLEN]; - if (talk_recv(p->fd, buf, sizeof(buf)) < 1) - { - // talk_send(p->fd, "/b"); // try to close it if still OK... - p->abort = YEA; - return VGETCB_ABORT; - } - // process commands - if (strcasecmp(buf, "/b") == 0) - { - p->abort = YEA; - return VGETCB_ABORT; - } - // received something, let's print it. - talk_add_line(p, p->dest_userid, buf); - return VGETCB_NEXT; - } - - case Ctrl('C'): - { - VREFSCR scr = vscr_save(); - add_io(0, 0); - if (vans("確定要中止聊天嗎? [y/N]: ") == 'y') - p->abort = YEA; - vscr_restore(scr); - add_io(p->fd, 0); - talk_footer(); - } - // notify dest user - if (p->abort) - talk_send(p->fd, "/b"); - return VGETCB_ABORT; + if(HasUserPerm(PERM_VIOLATELAW)) + { + vmsg("請先繳罰單才能使用聊天室!"); + return -1; } - return VGETCB_NONE; -} -int -ccw_talk(int fd, int destuid) -{ - char inbuf[STRLEN]="", genbuf[PATHLEN], fpath[PATHLEN]; - const int INBUF_MAXLEN = STRLEN - IDLEN - 5; - const char *chatid = cuser.userid; - VGET_CALLBACKS vge = { _talk_vgetcb_peek }; - TalkCtx ctx = { - .fd = fd, - .abort = 0, - .next_line = CCW_INIT_LINE, - }; - - STATINC(STAT_DOTALK); - setutmpmode(TALK); - - // get dest user id - assert(getuserid(destuid)); - strlcpy(ctx.dest_userid, getuserid(destuid), sizeof(ctx.dest_userid)); - assert(ctx.dest_userid[0]); - - // create log file - setuserfile(fpath, "talk_XXXXXX"); - ctx.log = fdopen(mkstemp(fpath), "w"); - assert(ctx.log); - fprintf(ctx.log, "[%s] 與 %s 聊天:\n", Cdate_mdHM(&now), ctx.dest_userid); - - // initialize screen - talk_clear(&ctx); - - // entering event loop... - add_io(fd, 0); - - while (!ctx.abort) +#ifdef CHAT_GAPMINS + if ((now - lastEnter)/60 < CHAT_GAPMINS) { - // print prompt - talk_footer(); // may be destroyed by short message / resize - move(b_lines-1, 0); clrtoeol(); - prints("%s: ", chatid); // the extra stuff must fit INBUF_MAXLEN - vgetstring(inbuf, INBUF_MAXLEN, VGET_TRANSPARENT, "", &vge, &ctx); - - // quick check for end flag or exit command. - if (ctx.abort) - break; - - // quick continue for empty input - trim(inbuf); - if (!*inbuf) - continue; - - // process commands - if (strcasecmp(inbuf, "/h") == 0) - { - talk_print_line(&ctx.next_line, "[說明]", "可輸入 /c 清除畫面或 /b 離開。"); - continue; - } - if (strcasecmp(inbuf, "/b") == 0) - { - talk_send(ctx.fd, "/b"); // notify dest user if he's still online - ctx.abort = 1; - break; - } - if (strcasecmp(inbuf, "/c") == 0) - { - talk_clear(&ctx); - continue; - } - - // accept this data - talk_add_line(&ctx, cuser.userid, inbuf); - - // send message to server if possible. - ctx.abort = (talk_send(fd, inbuf) <= 0); + vmsg("您才剛離開聊天室,裡面正在整理中。請稍後再試。"); + return 0; } +#endif - // clean network handle - add_io(0, 0); - close(fd); - - // process log - if (ctx.log) { - char ans[3]; - - // flush - fclose(ctx.log); - ctx.log = NULL; - - more(fpath, NA); - - // force user decide how to deal with the log - while (1) { - getdata(b_lines - 1, 0, "清除(C) 移至備忘錄(M)? [c/m]: ", - ans, sizeof(ans), LCECHO); - if (ans[0] == 'c' || ans[0] == 'm') - break; - move(b_lines-2, 0); - prints(ANSI_COLOR(0;1;3%d) "請正確輸入 c 或 m 的指令。" ANSI_RESET, - (int)((now % 7)+1)); - if (ans[0] == 0) outs("為避免誤按所以只 ENTER 是不行的。"); - } +#ifdef CHAT_REGDAYS + if ((now - cuser.firstlogin)/DAY_SECONDS < CHAT_REGDAYS) + { + int i = CHAT_REGDAYS - (now-cuser.firstlogin)/DAY_SECONDS +1; + vmsgf("您還不夠資深喔 (再等 %d 天吧)", i); + return 0; + } +#endif - if (*ans == 'm') { - snprintf(genbuf, sizeof(genbuf), "對話記錄 (%s)", ctx.dest_userid); - if (mail_log2id(cuser.userid, genbuf, fpath, "[備.忘.錄]", 0, 1) < 0) - vmsg("儲存失敗。"); - } - unlink(fpath); + // start to create connection. + syncnow(); + move(b_lines, 0); clrtoeol(); + outs(" 正在與聊天室連線... "); + refresh(); + fd = toconnect(XCHATD_ADDR); + move(b_lines-1, 0); clrtobot(); + if (fd < 0) { + outs(" 聊天室正在整理中,請稍候再試。"); + system("bin/xchatd"); + pressanykey(); + return -1; } - // restore screen and session - return 0; + // mark for entering + syncnow(); + lastEnter = now; + +#ifdef EXP_CCW_CHAT + return ccw_chat(fd); +#else + return do_chat(fd); +#endif } + |