summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2009-10-22 19:54:59 +0800
committerpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2009-10-22 19:54:59 +0800
commit83b3da32a80eb87eddae6c26c3e709f78e4a99eb (patch)
tree90414e465d50c4750f93963fa23e264c76eb91be
parent1c91369d42e063133443b0cafccc7d6fdb4b66bb (diff)
downloadpttbbs-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.h7
-rw-r--r--pttbbs/include/pttstruct.h33
-rw-r--r--pttbbs/include/vtuikit.h2
-rw-r--r--pttbbs/mbbsd/Makefile2
-rw-r--r--pttbbs/mbbsd/ccw.c1071
-rw-r--r--pttbbs/mbbsd/chat.c393
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
}
+