2020-02-01 06:18:01 +00:00
|
|
|
/* Copyright (C) 2020 C. McEnroe <june@causal.agency>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2020-06-08 21:48:07 +00:00
|
|
|
*
|
|
|
|
* Additional permission under GNU GPL version 3 section 7:
|
|
|
|
*
|
|
|
|
* If you modify this Program, or any covered work, by linking or
|
2020-08-04 16:19:14 +00:00
|
|
|
* combining it with OpenSSL (or a modified version of that library),
|
2020-06-08 21:48:07 +00:00
|
|
|
* containing parts covered by the terms of the OpenSSL License and the
|
|
|
|
* original SSLeay license, the licensors of this Program grant you
|
|
|
|
* additional permission to convey the resulting work. Corresponding
|
|
|
|
* Source for a non-source form of such a combination shall include the
|
2020-08-04 16:19:14 +00:00
|
|
|
* source code for the parts of OpenSSL used as well as that of the
|
2020-06-08 21:48:07 +00:00
|
|
|
* covered work.
|
2020-02-01 06:18:01 +00:00
|
|
|
*/
|
|
|
|
|
2020-02-17 00:12:19 +00:00
|
|
|
#include <assert.h>
|
2020-09-01 22:05:17 +00:00
|
|
|
#include <ctype.h>
|
2020-02-01 07:33:17 +00:00
|
|
|
#include <err.h>
|
2020-02-06 03:49:56 +00:00
|
|
|
#include <getopt.h>
|
2020-02-17 00:12:19 +00:00
|
|
|
#include <stdarg.h>
|
2020-02-01 06:18:01 +00:00
|
|
|
#include <stdbool.h>
|
2020-02-02 07:31:20 +00:00
|
|
|
#include <stdint.h>
|
2020-02-17 00:12:19 +00:00
|
|
|
#include <stdio.h>
|
2020-02-01 07:33:17 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <sysexits.h>
|
|
|
|
#include <time.h>
|
2020-02-05 05:20:39 +00:00
|
|
|
#include <wchar.h>
|
2020-02-01 06:18:01 +00:00
|
|
|
|
|
|
|
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
|
|
|
#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
|
|
|
|
|
2020-02-16 03:19:55 +00:00
|
|
|
typedef unsigned uint;
|
2020-02-01 06:18:01 +00:00
|
|
|
typedef unsigned char byte;
|
|
|
|
|
2020-07-30 18:37:46 +00:00
|
|
|
struct Cat {
|
|
|
|
char *buf;
|
|
|
|
size_t cap;
|
|
|
|
size_t len;
|
|
|
|
};
|
|
|
|
static inline void __attribute__((format(printf, 2, 3)))
|
|
|
|
catf(struct Cat *cat, const char *format, ...) {
|
2020-02-17 00:12:19 +00:00
|
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
2020-07-30 18:37:46 +00:00
|
|
|
int len = vsnprintf(&cat->buf[cat->len], cat->cap - cat->len, format, ap);
|
|
|
|
assert(len >= 0);
|
2020-02-17 00:12:19 +00:00
|
|
|
va_end(ap);
|
2020-07-30 18:37:46 +00:00
|
|
|
cat->len += len;
|
|
|
|
if (cat->len >= cat->cap) cat->len = cat->cap - 1;
|
2020-02-17 00:12:19 +00:00
|
|
|
}
|
|
|
|
|
2020-09-01 22:05:17 +00:00
|
|
|
enum Attr {
|
|
|
|
BIT(Bold),
|
|
|
|
BIT(Reverse),
|
|
|
|
BIT(Italic),
|
|
|
|
BIT(Underline),
|
|
|
|
};
|
2020-02-02 08:27:50 +00:00
|
|
|
enum Color {
|
|
|
|
White, Black, Blue, Green, Red, Brown, Magenta, Orange,
|
|
|
|
Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray,
|
|
|
|
Default = 99,
|
2020-02-11 22:43:36 +00:00
|
|
|
ColorCap,
|
2020-02-02 08:27:50 +00:00
|
|
|
};
|
2020-09-01 22:05:17 +00:00
|
|
|
struct Style {
|
|
|
|
enum Attr attr;
|
|
|
|
enum Color fg, bg;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct Style StyleDefault = { 0, Default, Default };
|
|
|
|
enum { B = '\2', C = '\3', O = '\17', R = '\26', I = '\35', U = '\37' };
|
|
|
|
|
|
|
|
static inline size_t styleParse(struct Style *style, const char **str) {
|
|
|
|
switch (**str) {
|
|
|
|
break; case B: (*str)++; style->attr ^= Bold;
|
|
|
|
break; case O: (*str)++; *style = StyleDefault;
|
|
|
|
break; case R: (*str)++; style->attr ^= Reverse;
|
|
|
|
break; case I: (*str)++; style->attr ^= Italic;
|
|
|
|
break; case U: (*str)++; style->attr ^= Underline;
|
|
|
|
break; case C: {
|
|
|
|
(*str)++;
|
|
|
|
if (!isdigit(**str)) {
|
|
|
|
style->fg = Default;
|
|
|
|
style->bg = Default;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
style->fg = *(*str)++ - '0';
|
|
|
|
if (isdigit(**str)) style->fg = style->fg * 10 + *(*str)++ - '0';
|
|
|
|
if ((*str)[0] != ',' || !isdigit((*str)[1])) break;
|
|
|
|
(*str)++;
|
|
|
|
style->bg = *(*str)++ - '0';
|
|
|
|
if (isdigit(**str)) style->bg = style->bg * 10 + *(*str)++ - '0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return strcspn(*str, (const char[]) { B, C, O, R, I, U, '\0' });
|
|
|
|
}
|
2020-02-02 08:27:50 +00:00
|
|
|
|
2020-10-09 22:45:52 +00:00
|
|
|
static inline void styleStrip(struct Cat *cat, const char *str) {
|
|
|
|
struct Style style = StyleDefault;
|
|
|
|
while (*str) {
|
|
|
|
size_t len = styleParse(&style, &str);
|
|
|
|
catf(cat, "%.*s", (int)len, str);
|
|
|
|
str += len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-01 07:33:17 +00:00
|
|
|
enum { None, Debug, Network, IDCap = 256 };
|
|
|
|
extern char *idNames[IDCap];
|
2020-02-02 08:27:50 +00:00
|
|
|
extern enum Color idColors[IDCap];
|
2020-02-16 03:19:55 +00:00
|
|
|
extern uint idNext;
|
2020-02-01 07:33:17 +00:00
|
|
|
|
2020-02-16 03:19:55 +00:00
|
|
|
static inline uint idFind(const char *name) {
|
|
|
|
for (uint id = 0; id < idNext; ++id) {
|
2020-02-01 07:33:17 +00:00
|
|
|
if (!strcmp(idNames[id], name)) return id;
|
|
|
|
}
|
|
|
|
return None;
|
|
|
|
}
|
2020-02-02 08:27:50 +00:00
|
|
|
|
2020-02-16 03:19:55 +00:00
|
|
|
static inline uint idFor(const char *name) {
|
|
|
|
uint id = idFind(name);
|
2020-02-01 07:33:17 +00:00
|
|
|
if (id) return id;
|
2020-02-08 06:14:41 +00:00
|
|
|
if (idNext == IDCap) return Network;
|
2020-02-01 07:33:17 +00:00
|
|
|
idNames[idNext] = strdup(name);
|
2020-02-06 09:19:56 +00:00
|
|
|
idColors[idNext] = Default;
|
2020-02-17 04:05:43 +00:00
|
|
|
if (!idNames[idNext]) err(EX_OSERR, "strdup");
|
2020-02-01 07:33:17 +00:00
|
|
|
return idNext++;
|
|
|
|
}
|
|
|
|
|
2020-02-11 22:41:06 +00:00
|
|
|
extern uint32_t hashInit;
|
2021-01-09 22:58:29 +00:00
|
|
|
extern uint32_t hashBound;
|
2020-02-11 22:41:06 +00:00
|
|
|
static inline enum Color hash(const char *str) {
|
|
|
|
if (*str == '~') str++;
|
|
|
|
uint32_t hash = hashInit;
|
|
|
|
for (; *str; ++str) {
|
|
|
|
hash = (hash << 5) | (hash >> 27);
|
|
|
|
hash ^= *str;
|
|
|
|
hash *= 0x27220A95;
|
|
|
|
}
|
2021-01-09 22:58:29 +00:00
|
|
|
return Blue + hash % (hashBound + 1 - Blue);
|
2020-02-11 22:41:06 +00:00
|
|
|
}
|
|
|
|
|
2020-02-17 04:05:43 +00:00
|
|
|
extern struct Network {
|
|
|
|
char *name;
|
2020-03-23 17:13:43 +00:00
|
|
|
uint userLen;
|
|
|
|
uint hostLen;
|
2020-02-17 04:05:43 +00:00
|
|
|
char *chanTypes;
|
|
|
|
char *prefixes;
|
|
|
|
char *prefixModes;
|
|
|
|
char *listModes;
|
|
|
|
char *paramModes;
|
|
|
|
char *setParamModes;
|
|
|
|
char *channelModes;
|
|
|
|
char excepts;
|
|
|
|
char invex;
|
|
|
|
} network;
|
|
|
|
|
2020-02-01 06:18:01 +00:00
|
|
|
#define ENUM_CAP \
|
2020-02-29 06:03:46 +00:00
|
|
|
X("causal.agency/consumer", CapConsumer) \
|
2020-03-22 18:32:20 +00:00
|
|
|
X("chghost", CapChghost) \
|
2020-02-09 05:53:55 +00:00
|
|
|
X("extended-join", CapExtendedJoin) \
|
2020-02-15 02:36:58 +00:00
|
|
|
X("invite-notify", CapInviteNotify) \
|
2020-02-15 09:59:50 +00:00
|
|
|
X("multi-prefix", CapMultiPrefix) \
|
2020-02-01 06:18:01 +00:00
|
|
|
X("sasl", CapSASL) \
|
|
|
|
X("server-time", CapServerTime) \
|
2020-12-30 04:49:16 +00:00
|
|
|
X("setname", CapSetname) \
|
2020-02-01 06:18:01 +00:00
|
|
|
X("userhost-in-names", CapUserhostInNames)
|
|
|
|
|
|
|
|
enum Cap {
|
|
|
|
#define X(name, id) BIT(id),
|
|
|
|
ENUM_CAP
|
|
|
|
#undef X
|
|
|
|
};
|
|
|
|
|
|
|
|
extern struct Self {
|
2020-02-01 07:26:35 +00:00
|
|
|
bool debug;
|
2020-02-12 03:39:29 +00:00
|
|
|
bool restricted;
|
2020-02-29 06:03:46 +00:00
|
|
|
size_t pos;
|
2020-02-01 06:18:01 +00:00
|
|
|
enum Cap caps;
|
2020-02-17 04:05:43 +00:00
|
|
|
char *plain;
|
|
|
|
char *join;
|
2020-02-01 06:18:01 +00:00
|
|
|
char *nick;
|
2020-02-05 05:40:24 +00:00
|
|
|
char *user;
|
2020-03-22 18:32:20 +00:00
|
|
|
char *host;
|
2020-02-05 01:23:55 +00:00
|
|
|
enum Color color;
|
2020-02-06 02:57:23 +00:00
|
|
|
char *quit;
|
2020-02-01 06:18:01 +00:00
|
|
|
} self;
|
|
|
|
|
2020-02-02 22:37:36 +00:00
|
|
|
static inline void set(char **field, const char *value) {
|
|
|
|
free(*field);
|
|
|
|
*field = strdup(value);
|
|
|
|
if (!*field) err(EX_OSERR, "strdup");
|
|
|
|
}
|
|
|
|
|
2020-02-01 06:18:01 +00:00
|
|
|
#define ENUM_TAG \
|
2020-02-29 06:03:46 +00:00
|
|
|
X("causal.agency/pos", TagPos) \
|
2020-02-01 06:18:01 +00:00
|
|
|
X("time", TagTime)
|
|
|
|
|
|
|
|
enum Tag {
|
|
|
|
#define X(name, id) id,
|
|
|
|
ENUM_TAG
|
|
|
|
#undef X
|
|
|
|
TagCap,
|
|
|
|
};
|
|
|
|
|
2020-06-24 17:36:24 +00:00
|
|
|
enum { ParamCap = 254 };
|
2020-02-01 06:18:01 +00:00
|
|
|
struct Message {
|
|
|
|
char *tags[TagCap];
|
|
|
|
char *nick;
|
|
|
|
char *user;
|
|
|
|
char *host;
|
|
|
|
char *cmd;
|
|
|
|
char *params[ParamCap];
|
|
|
|
};
|
|
|
|
|
2020-08-20 18:56:13 +00:00
|
|
|
void ircConfig(bool insecure, const char *cert, const char *priv);
|
2020-02-12 01:02:37 +00:00
|
|
|
int ircConnect(const char *bind, const char *host, const char *port);
|
2020-02-01 06:18:01 +00:00
|
|
|
void ircRecv(void);
|
|
|
|
void ircSend(const char *ptr, size_t len);
|
|
|
|
void ircFormat(const char *format, ...)
|
|
|
|
__attribute__((format(printf, 1, 2)));
|
2020-02-13 06:01:23 +00:00
|
|
|
void ircClose(void);
|
2020-02-01 06:18:01 +00:00
|
|
|
|
2020-05-07 23:47:40 +00:00
|
|
|
extern uint execID;
|
|
|
|
extern int execPipe[2];
|
|
|
|
extern int utilPipe[2];
|
2020-02-14 02:57:55 +00:00
|
|
|
|
2020-02-13 01:12:34 +00:00
|
|
|
enum { UtilCap = 16 };
|
|
|
|
struct Util {
|
2020-02-16 03:19:55 +00:00
|
|
|
uint argc;
|
2020-02-13 01:12:34 +00:00
|
|
|
const char *argv[UtilCap];
|
|
|
|
};
|
|
|
|
|
|
|
|
static inline void utilPush(struct Util *util, const char *arg) {
|
|
|
|
if (1 + util->argc < UtilCap) {
|
|
|
|
util->argv[util->argc++] = arg;
|
|
|
|
} else {
|
|
|
|
errx(EX_CONFIG, "too many utility arguments");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-30 21:49:55 +00:00
|
|
|
enum Reply {
|
|
|
|
ReplyAway = 1,
|
|
|
|
ReplyBan,
|
|
|
|
ReplyExcepts,
|
|
|
|
ReplyHelp,
|
|
|
|
ReplyInvex,
|
|
|
|
ReplyJoin,
|
|
|
|
ReplyList,
|
|
|
|
ReplyMode,
|
|
|
|
ReplyNames,
|
|
|
|
ReplyTopic,
|
|
|
|
ReplyWho,
|
|
|
|
ReplyWhois,
|
|
|
|
ReplyWhowas,
|
|
|
|
ReplyCap,
|
|
|
|
};
|
|
|
|
|
|
|
|
extern uint replies[ReplyCap];
|
2020-02-17 04:05:43 +00:00
|
|
|
|
2020-06-24 17:36:24 +00:00
|
|
|
void handle(struct Message *msg);
|
2020-02-17 04:05:43 +00:00
|
|
|
void command(uint id, char *input);
|
|
|
|
const char *commandIsPrivmsg(uint id, const char *input);
|
|
|
|
const char *commandIsNotice(uint id, const char *input);
|
|
|
|
const char *commandIsAction(uint id, const char *input);
|
2020-03-30 18:56:26 +00:00
|
|
|
void commandCompleteAdd(void);
|
2020-02-17 04:05:43 +00:00
|
|
|
|
2020-03-31 18:30:42 +00:00
|
|
|
enum Heat { Ice, Cold, Warm, Hot };
|
2020-02-13 01:12:34 +00:00
|
|
|
extern struct Util uiNotifyUtil;
|
2020-10-12 23:25:08 +00:00
|
|
|
void uiInitEarly(void);
|
|
|
|
void uiInitLate(void);
|
2020-02-03 04:20:19 +00:00
|
|
|
void uiShow(void);
|
|
|
|
void uiHide(void);
|
2020-02-02 00:37:48 +00:00
|
|
|
void uiDraw(void);
|
2020-02-16 03:19:55 +00:00
|
|
|
void uiShowID(uint id);
|
|
|
|
void uiShowNum(uint num);
|
|
|
|
void uiMoveID(uint id, uint num);
|
|
|
|
void uiCloseID(uint id);
|
|
|
|
void uiCloseNum(uint id);
|
2020-02-04 08:58:56 +00:00
|
|
|
void uiRead(void);
|
2020-02-16 03:19:55 +00:00
|
|
|
void uiWrite(uint id, enum Heat heat, const time_t *time, const char *str);
|
2020-02-02 00:37:48 +00:00
|
|
|
void uiFormat(
|
2020-02-16 03:19:55 +00:00
|
|
|
uint id, enum Heat heat, const time_t *time, const char *format, ...
|
2020-02-02 00:37:48 +00:00
|
|
|
) __attribute__((format(printf, 4, 5)));
|
2020-02-11 00:40:13 +00:00
|
|
|
void uiLoad(const char *name);
|
|
|
|
int uiSave(const char *name);
|
2020-02-02 00:37:48 +00:00
|
|
|
|
2020-09-02 00:35:17 +00:00
|
|
|
enum { BufferCap = 1024 };
|
|
|
|
struct Buffer;
|
|
|
|
struct Line {
|
|
|
|
enum Heat heat;
|
|
|
|
time_t time;
|
|
|
|
char *str;
|
|
|
|
};
|
|
|
|
struct Buffer *bufferAlloc(void);
|
|
|
|
void bufferFree(struct Buffer *buffer);
|
|
|
|
const struct Line *bufferSoft(const struct Buffer *buffer, size_t i);
|
|
|
|
const struct Line *bufferHard(const struct Buffer *buffer, size_t i);
|
2020-09-02 02:54:26 +00:00
|
|
|
int bufferPush(
|
2020-09-02 22:51:07 +00:00
|
|
|
struct Buffer *buffer, int cols, bool ignore,
|
2020-09-02 00:35:17 +00:00
|
|
|
enum Heat heat, time_t time, const char *str
|
|
|
|
);
|
2020-09-03 01:29:03 +00:00
|
|
|
int bufferReflow(struct Buffer *buffer, int cols, bool ignore, size_t tail);
|
2020-09-02 00:35:17 +00:00
|
|
|
|
2020-02-05 05:20:39 +00:00
|
|
|
enum Edit {
|
2020-02-09 08:56:18 +00:00
|
|
|
EditHead,
|
|
|
|
EditTail,
|
|
|
|
EditPrev,
|
|
|
|
EditNext,
|
2020-02-09 09:20:07 +00:00
|
|
|
EditPrevWord,
|
|
|
|
EditNextWord,
|
2020-02-09 09:22:41 +00:00
|
|
|
EditDeleteHead,
|
|
|
|
EditDeleteTail,
|
2020-02-09 08:56:18 +00:00
|
|
|
EditDeletePrev,
|
|
|
|
EditDeleteNext,
|
2020-02-09 09:32:32 +00:00
|
|
|
EditDeletePrevWord,
|
|
|
|
EditDeleteNextWord,
|
2020-02-09 12:09:51 +00:00
|
|
|
EditPaste,
|
2020-02-12 06:16:40 +00:00
|
|
|
EditTranspose,
|
2020-04-07 14:48:44 +00:00
|
|
|
EditCollapse,
|
2020-02-05 05:20:39 +00:00
|
|
|
EditInsert,
|
2020-02-08 02:30:25 +00:00
|
|
|
EditComplete,
|
2020-03-30 18:56:26 +00:00
|
|
|
EditExpand,
|
2020-02-05 05:20:39 +00:00
|
|
|
EditEnter,
|
|
|
|
};
|
2020-02-16 03:19:55 +00:00
|
|
|
void edit(uint id, enum Edit op, wchar_t ch);
|
2020-02-09 06:28:24 +00:00
|
|
|
char *editBuffer(size_t *pos);
|
2020-03-30 18:56:26 +00:00
|
|
|
void editCompleteAdd(void);
|
2020-02-05 01:23:55 +00:00
|
|
|
|
2020-02-16 03:19:55 +00:00
|
|
|
const char *complete(uint id, const char *prefix);
|
2020-02-08 02:30:25 +00:00
|
|
|
void completeAccept(void);
|
|
|
|
void completeReject(void);
|
2020-02-16 03:19:55 +00:00
|
|
|
void completeAdd(uint id, const char *str, enum Color color);
|
|
|
|
void completeTouch(uint id, const char *str, enum Color color);
|
|
|
|
void completeReplace(uint id, const char *old, const char *new);
|
|
|
|
void completeRemove(uint id, const char *str);
|
|
|
|
void completeClear(uint id);
|
|
|
|
uint completeID(const char *str);
|
|
|
|
enum Color completeColor(uint id, const char *str);
|
2020-02-08 02:30:25 +00:00
|
|
|
|
2020-02-12 05:48:43 +00:00
|
|
|
extern struct Util urlOpenUtil;
|
|
|
|
extern struct Util urlCopyUtil;
|
2020-02-16 03:19:55 +00:00
|
|
|
void urlScan(uint id, const char *nick, const char *mesg);
|
|
|
|
void urlOpenCount(uint id, uint count);
|
|
|
|
void urlOpenMatch(uint id, const char *str);
|
|
|
|
void urlCopyMatch(uint id, const char *str);
|
2020-02-08 23:29:01 +00:00
|
|
|
|
2021-01-02 01:09:10 +00:00
|
|
|
enum { IgnoreCap = 64 };
|
2020-03-31 18:30:42 +00:00
|
|
|
extern struct Ignore {
|
2021-01-02 01:09:10 +00:00
|
|
|
char *mask;
|
|
|
|
char *cmd;
|
|
|
|
char *chan;
|
|
|
|
char *mesg;
|
|
|
|
} ignores[IgnoreCap];
|
|
|
|
struct Ignore ignoreParse(char *pattern);
|
|
|
|
struct Ignore ignoreAdd(const char *pattern);
|
|
|
|
bool ignoreRemove(struct Ignore ignore);
|
2020-04-07 17:18:42 +00:00
|
|
|
enum Heat ignoreCheck(enum Heat heat, uint id, const struct Message *msg);
|
2020-03-31 18:30:42 +00:00
|
|
|
|
2020-03-25 22:56:09 +00:00
|
|
|
extern bool logEnable;
|
|
|
|
void logFormat(uint id, const time_t *time, const char *format, ...)
|
|
|
|
__attribute__((format(printf, 3, 4)));
|
|
|
|
void logClose(void);
|
|
|
|
|
2020-08-21 20:47:54 +00:00
|
|
|
const char *configPath(const char **dirs, const char *path);
|
|
|
|
const char *dataPath(const char **dirs, const char *path);
|
2020-02-06 03:49:56 +00:00
|
|
|
FILE *configOpen(const char *path, const char *mode);
|
2020-02-11 00:57:10 +00:00
|
|
|
FILE *dataOpen(const char *path, const char *mode);
|
2020-03-25 22:56:09 +00:00
|
|
|
void dataMkdir(const char *path);
|
2020-02-11 00:57:10 +00:00
|
|
|
|
2020-02-06 03:49:56 +00:00
|
|
|
int getopt_config(
|
|
|
|
int argc, char *const *argv,
|
|
|
|
const char *optstring, const struct option *longopts, int *longindex
|
|
|
|
);
|