Compare commits

...

7 Commits

Author SHA1 Message Date
Bubblegumdrop
856d8132b5 Strings... 2021-03-11 20:48:32 -05:00
Bubblegumdrop
90f6bdbb7a IRC functionality in place: .start .stop spam and .quit 2021-03-11 17:23:43 -05:00
Bubblegumdrop
542e8395ef Bugs 2021-03-11 13:25:53 -05:00
Bubblegumdrop
823330dde9 Insert, update, run script, run single query.
Most of the basic db functionality is in place now.

Changing print_col out for a user-supplied callback function might be a
cool idea.
2021-03-11 12:48:15 -05:00
Bubblegumdrop
eb77e1ac0e Moving things around. 2021-03-09 01:39:19 -05:00
Bubblegumdrop
61ba8a692f Winding down for the day. 2021-03-08 23:16:14 -05:00
Bubblegumdrop
60dbee40e3 Initial code commit.
It doesn't do anything useful.
2021-03-08 16:59:27 -05:00
22 changed files with 1099 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
spammer
*.o
sqlite3*
*.sqlite3

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

23
README.md Normal file
View File

@ -0,0 +1,23 @@
# The (un)Official Lain's Diction Book
Quote / Definition / Pastebin bot
it takes text from irc and stores it in a database
13:56 <@kashire> We need to make our own dictionary.
13:56 <@kashire> The (un)Official Lain's Diction Book.
# TODO
- Perhaps a database backend and a client frontend that's separate.
- SQL schema
- SQL interface
- Option arguments w/ getopt
- Exit codes
# Extras
You can unzip the SQLite amalgamation right into here to use it,
otherwise, provide INLINE_SQLITE=0:
make INLINE_SQLITE=1

36
data.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef DATA_H
#define DATA_H
/*
* We store data in IRC session context.
*/
struct irc_ctx_t {
unsigned short port;
char *channel;
char *nick;
char *server;
};
/*
* Params that we give to our threads.
*/
struct spam_params_t {
irc_session_t *session;
const char *phrase;
const char *channel;
int timer;
int unused;
};
/*
* Eventually I'd like to be able to do insert_struct with filled out info or similar.
*/
struct quote_t {
int rowid;
const char *added_by;
const char *channel;
const char *subject;
const char *words;
};
#endif /* DATA_H */

188
db.c Normal file
View File

@ -0,0 +1,188 @@
/* <https://gist.github.com/enile8/2424514> */
#define _POSIX_C_SOURCE 200809L /* strtok_r, strndup */
#include <string.h>
#include <assert.h> /* assert */
#include <sqlite3.h>
#include <stdio.h> /* size_t */
#include <stdlib.h> /* free */
#include "io.h"
#include "strings.h"
#include "unused.h"
struct params {
int type;
void *arg;
};
static int print_col(sqlite3_stmt * pTableInfo, int col) {
int n, rc;
static int ct = 0;
char outfile[50];
size_t size;
printf("%s: ", sqlite3_column_name(pTableInfo, col));
switch (sqlite3_column_type(pTableInfo, col)) {
case SQLITE_INTEGER:
printf("%d ", sqlite3_column_int(pTableInfo, col));
break;
case SQLITE_FLOAT:
printf("%f ", sqlite3_column_double(pTableInfo, col));
break;
case SQLITE_TEXT:
printf("%s ", sqlite3_column_text(pTableInfo, col));
break;
case SQLITE_BLOB: //printf("%s",sqlite3_column_blob(pTableInfo, col));
/* fprintf(stderr, "IN BLOB bytes %d\n",
sqlite3_column_bytes(pTableInfo, col)); */
snprintf(outfile, 20, "outdata.%d.png", ct++);
n = sqlite3_column_bytes(pTableInfo, col);
if (n > 0)
size = (size_t)n;
else
break;
if ((rc = (int)file_write(outfile, sqlite3_column_blob(pTableInfo, col), size))) {
assert(rc == (int)size);
}
break;
case SQLITE_NULL:
printf("(null)\n");
break;
default:
printf(" *Cannot determine SQLITE TYPE* col=%d\n", col);
}
return 0;
}
static int run_script_callback(void *NotUsed, int argc, char **argv, char **azColName) {
int i;
UNUSED(NotUsed);
for (i = 0; i < argc; i++) {
printf(" => %s = %s\n", azColName[i], (argv[i] ? argv[i] : "NULL"));
}
return 0;
}
int run_script(sqlite3 * db, const char *script_filename) {
int rc;
char *statements, *zErrMsg;
zErrMsg = 0;
if (!(statements = file_read(script_filename, NULL))) {
fprintf(stderr, string_errmsg_readfile, script_filename);
return -1;
}
rc = sqlite3_exec(db, statements, run_script_callback, 0, &zErrMsg);
if (SQLITE_OK != rc) {
fprintf(stderr, " !! SQL error: %s\n", sqlite3_errmsg(db));
sqlite3_free(zErrMsg);
}
#if 0
for (j = 1, str1 = statements;; j++, str1 = NULL) {
token = strtok_r(str1, "\n", &saveptr1);
if (token == NULL)
break;
if (strlen(token) > 2 && token[0] == '-' && token[1] == '-')
continue; /* Comment line; TODO multiline comment */
printf("%d: %s\n", j, token);
rc = sqlite3_exec(db, token, run_script_callback, 0, &zErrMsg);
if (SQLITE_OK != rc) {
fprintf(stderr, " !! SQL error: %s\n", sqlite3_errmsg(db));
sqlite3_free(zErrMsg);
break;
}
}
#endif
free(statements);
fprintf(stderr, "Bye!\n");
return SQLITE_OK;
}
int run_one(sqlite3 * db, const char *query) {
int i, n, rc;
sqlite3_stmt *stmt = NULL;
printf("> %s\n", query);
if (SQLITE_OK != (rc = sqlite3_prepare_v2(db, query, -1, &stmt, 0))) {
fprintf(stderr, string_errmsg_prepare, sqlite3_errmsg(db));
return SQLITE_ERROR;
}
while (SQLITE_ROW == (rc = sqlite3_step(stmt))) {
n = sqlite3_column_count(stmt);
for (i = 0; i < n; ++i) {
printf(" => ");
print_col(stmt, i);
puts("");
}
}
if (SQLITE_DONE != rc) {
fprintf(stderr, string_errmsg_update, sqlite3_errmsg(db));
}
if (SQLITE_OK != (rc = sqlite3_reset(stmt))) {
fprintf(stderr, string_errmsg_reset, sqlite3_errmsg(db));
}
if (SQLITE_OK != (rc = sqlite3_clear_bindings(stmt))) {
fprintf(stderr, string_errmsg_clear, sqlite3_errmsg(db));
}
return sqlite3_finalize(stmt);
}
int update_one(sqlite3 * db, int rowid, const char *param1, const char *param2) {
const char insert_stmt[] = "update quotedb set ? = ? where quote_id = ?";
int n, rc;
size_t maxlen;
sqlite3_stmt *stmt = NULL;
maxlen = 4096;
n = 0;
if (SQLITE_OK != (rc = sqlite3_prepare_v2(db, insert_stmt, -1, &stmt, 0))) {
fprintf(stderr, string_errmsg_prepare, sqlite3_errmsg(db));
return SQLITE_ERROR;
}
#define S(x) if (SQLITE_OK != (rc = sqlite3_bind_text(stmt, ++n, x, (int)strnlen(x, maxlen), SQLITE_STATIC))) { \
fprintf(stderr, string_errmsg_bind, sqlite3_errmsg(db)); \
return SQLITE_ERROR; \
}
S(param1);
S(param2);
#undef S
sqlite3_bind_int(stmt, 3, rowid);
if (SQLITE_DONE != (rc = sqlite3_step(stmt))) {
fprintf(stderr, string_errmsg_update, sqlite3_errmsg(db));
}
if (SQLITE_OK != (rc = sqlite3_reset(stmt))) {
fprintf(stderr, string_errmsg_reset, sqlite3_errmsg(db));
}
return sqlite3_finalize(stmt);
}
int insert_row(sqlite3 * db, const char *channel, const char *added_by, const char *subject, const char *words) {
const char insert_stmt[] = "insert into quotedb (date_added, channel, added_by, subject, words) values (datetime('now'), ?, ?, ?, ?)";
int n, rc;
size_t maxlen;
sqlite3_stmt *stmt = NULL;
maxlen = 4096;
n = 0;
if (SQLITE_OK != (rc = sqlite3_prepare_v2(db, insert_stmt, -1, &stmt, 0))) {
fprintf(stderr, string_errmsg_prepare, sqlite3_errmsg(db));
return SQLITE_ERROR;
}
#define S(x) if (SQLITE_OK != (rc = sqlite3_bind_text(stmt, ++n, x, (int)strnlen(x, maxlen), SQLITE_STATIC))) { \
fprintf(stderr, string_errmsg_bind, sqlite3_errmsg(db)); \
return SQLITE_ERROR; \
}
S(channel);
S(added_by);
S(subject);
S(words);
#undef S
if (SQLITE_DONE != (rc = sqlite3_step(stmt))) {
fprintf(stderr, string_errmsg_insert, sqlite3_errmsg(db));
}
if (SQLITE_OK != (rc = sqlite3_reset(stmt))) {
fprintf(stderr, string_errmsg_reset, sqlite3_errmsg(db));
}
return sqlite3_finalize(stmt);
}
int sqlite_version(sqlite3 * db) {
int rc;
sqlite3_stmt *stmt = NULL;
if (SQLITE_OK != (rc = sqlite3_prepare_v2(db, "SELECT SQLITE_VERSION()", -1, &stmt, 0))) {
fprintf(stderr, string_errmsg_prepare, sqlite3_errmsg(db));
return 1;
}
if (SQLITE_ROW == (rc = sqlite3_step(stmt))) {
printf("SQLite version: %s\n", sqlite3_column_text(stmt, 0));
}
return sqlite3_finalize(stmt);
}

12
db.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef DB_H
#define DB_H
#include <sqlite3.h>
int insert_row(sqlite3 *, const char *, const char *, const char *, const char *);
int run_one(sqlite3 *, const char *);
int run_script(sqlite3 *, const char *);
int sqlite_version(sqlite3 *);
int update_one(sqlite3 *, int, const char *, const char *);
#endif /* DB_H */

8
dictionary.sql Normal file
View File

@ -0,0 +1,8 @@
BEGIN TRANSACTION;
create table if not exists dictionarydb (date_added text not null, added_by text not null, channel text not null, word text not null, definition text not null)
COMMIT;
--delete from quotedb
--drop table quotedb

235
events.c Normal file
View File

@ -0,0 +1,235 @@
#include <string.h>
#include <assert.h> /* assert */
#include <errno.h>
#include <stdio.h> /* size_t */
#include "libircclient.h"
#include "data.h"
#include "events.h"
#include "strings.h"
#include "threads.h"
#ifndef UNUSED
#define UNUSED(x) (void)(x)
#endif
char F_SPAM_THREADS = STOPPED;
char say_hi = 1;
THREAD_FUNCTION(gen_spam) {
struct spam_params_t *sp = (struct spam_params_t *)arg;
while (RUNNING == F_SPAM_THREADS) {
if (irc_cmd_msg(sp->session, sp->channel, sp->phrase))
break;
if (sp->timer > 0)
sleep((unsigned int)sp->timer);
}
return 0;
}
EVENT_SIGNATURE(start_spam) {
struct irc_ctx_t *ctx;
static struct spam_params_t spam1;
static struct spam_params_t spam2;
static struct spam_params_t spam3;
thread_id_t tid;
UNUSED(count);
UNUSED(event);
UNUSED(origin);
UNUSED(params);
ctx = (struct irc_ctx_t *)irc_get_ctx(session);
spam1.session = spam2.session = spam3.session = session;
spam1.channel = spam2.channel = spam3.channel = ctx->channel;
spam1.phrase = "HEHE";
spam2.phrase = "HAHA";
spam3.phrase = "HUHU";
spam1.timer = 2;
spam2.timer = 3;
spam3.timer = 4;
F_SPAM_THREADS = RUNNING;
if (CREATE_THREAD(&tid, gen_spam, &spam1)
|| CREATE_THREAD(&tid, gen_spam, &spam2)
|| CREATE_THREAD(&tid, gen_spam, &spam3))
printf("CREATE_THREAD failed: %s\n", strerror(errno));
else
printf(string_spam_success);
}
void dump_event(irc_session_t * session, const char *event, const char *origin, const char **params, unsigned int count) {
char buf[1024];
unsigned int cnt;
UNUSED(session);
buf[0] = '\0';
memset(buf, 0, sizeof buf);
for (cnt = 0; cnt < count; cnt++) {
if (cnt)
strcat(buf, "|");
strcat(buf, params[cnt]);
}
printf("[%s] %s : [%d] %s\n", event, origin ? origin : "(null)", cnt, buf);
}
EVENT_SIGNATURE(event_connect) {
struct irc_ctx_t *ctx;
UNUSED(event);
UNUSED(origin);
UNUSED(params);
UNUSED(count);
ctx = (struct irc_ctx_t *)irc_get_ctx(session);
printf(string_connect_success, ctx->server, ctx->port, ctx->nick, ctx->channel);
irc_cmd_join(session, ctx->channel, 0);
}
EVENT_NUMERIC_SIGNATURE(event_numeric) {
char buf[24];
snprintf(buf, sizeof buf, "%d", event);
dump_event(session, buf, origin, params, count);
if (event > 400) {
printf("ERROR %d: %s: %s %s %s %s\n", event, origin ? origin : "unknown", params[0], count > 1 ? params[1] : "", count > 2 ? params[2] : "", count > 3 ? params[3] : "");
}
}
int IsAdmin(const char *name) {
return !strcmp(name, "Bubblegumdrop");
}
EVENT_SIGNATURE(event_channel) {
struct irc_ctx_t *ctx;
char nickbuf[128];
UNUSED(event);
UNUSED(params);
UNUSED(count);
dump_event(session, event, origin, params, count);
if (NULL == origin || count < 2)
return;
irc_target_get_nick(origin, nickbuf, sizeof(nickbuf));
if (!IsAdmin(nickbuf)) {
return;
}
ctx = (struct irc_ctx_t *)irc_get_ctx(session);
UNUSED(ctx);
if (!strcmp(params[1], ".stop")) {
irc_cmd_msg(session, params[0], ":x");
F_SPAM_THREADS = STOPPED;
}
if (!strcmp(params[1], ".start")) {
irc_cmd_msg(session, params[0], ":v");
F_SPAM_THREADS = RUNNING;
start_spam(session, ctx->nick, origin, params, count);
}
if (!strcmp(params[1], ".quit")) {
F_MAIN_THREAD = STOPPED;
F_IRC_THREAD = STOPPED;
F_MAIN_THREAD = STOPPED;
irc_cmd_quit(session, "Bye!");
}
#if 0
if (ctx->insolents.find(nickbuf) == ctx->insolents.end())
ctx->insolents[nickbuf]
= 0;
ctx->insolents[nickbuf]++;
printf("'%s' swears in the channel '%s' %d times\n", nickbuf, params[1], ctx->insolents[nickbuf]);
switch (ctx->insolents[nickbuf]) {
case 1:
// Send a private message
sprintf(text, "%s, please do not swear in this channel.", nickbuf);
irc_cmd_msg(session, nickbuf, text);
break;
case 2:
// Send a channel message
sprintf(text, "%s, do not swear in this channel, or you'll leave it.", nickbuf);
irc_cmd_msg(session, params[0], text);
break;
default:
// Send a channel notice, and kick the insolent
sprintf(text, "kicked %s from %s for swearing.", nickbuf, params[0]);
irc_cmd_me(session, params[0], text);
irc_cmd_kick(session, nickbuf, params[0], "swearing");
break;
}
#endif
}
EVENT_SIGNATURE(event_join) {
char buf[128];
struct irc_ctx_t *ctx;
UNUSED(count);
UNUSED(event);
if (!origin)
return;
ctx = (struct irc_ctx_t *)irc_get_ctx(session);
/*
* We need to know whether WE are joining the channel, or someone else.
* To do this, we compare the origin with our nick.
* Note that we have set LIBIRC_OPTION_STRIPNICKS to obtain 'parsed' nicks.
*/
if (!strcmp(origin, ctx->nick)) {
if (say_hi)
irc_cmd_msg(session, params[0], "Hi");
/*
printf("We just joined the channel %s; starting the spam threads\n", params[0]);
start_spam (session, ctx->nick, origin, params, count);
*/
} else if (say_hi) {
memset(&buf, 0, sizeof buf);
snprintf(buf, (sizeof buf) - 1, "Hey, %s, hi!", origin);
irc_cmd_msg(session, params[0], buf);
}
}
EVENT_SIGNATURE(event_nick) {
char nickbuf[128];
struct irc_ctx_t *ctx;
UNUSED(event);
UNUSED(params);
if (!origin || count != 1)
return;
irc_target_get_nick(origin, nickbuf, sizeof(nickbuf));
ctx = (struct irc_ctx_t *)irc_get_ctx(session);
UNUSED(ctx);
#if 0
if (ctx->insolents.find(nickbuf) != ctx->insolents.end()) {
printf("%s has changed its nick to %s to prevent penalties - no way!\n", nickbuf, params[0]);
ctx->insolents[params[0]] = ctx->insolents[nickbuf];
ctx->insolents.erase(nickbuf);
}
#endif
}

27
events.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef EVENTS_H
#define EVENTS_H
#define EVENT_SIGNATURE(event_name) \
void event_name \
(irc_session_t * session, \
const char *event, \
const char *origin, \
const char **params, \
unsigned int count)
#define EVENT_NUMERIC_SIGNATURE(event_name) \
void event_name \
(irc_session_t * session, \
unsigned int event, \
const char *origin, \
const char **params, \
unsigned int count)
EVENT_NUMERIC_SIGNATURE(event_numeric);
EVENT_SIGNATURE(event_channel);
EVENT_SIGNATURE(event_connect);
EVENT_SIGNATURE(event_join);
EVENT_SIGNATURE(event_nick);
#endif /* EVENTS_H */

79
io.c Normal file
View File

@ -0,0 +1,79 @@
#include <assert.h> /* assert */
#include <stdio.h> /* FILE* */
#include <stdlib.h> /* malloc, free */
#include <unistd.h> /* unlink */
#include "io.h"
/* <https://rosettacode.org/wiki/Read_entire_file#C> */
char *file_read(const char *filename, size_t *readSize) {
char *buffer;
FILE *fh;
long size;
size_t nread;
assert(filename != NULL);
buffer = NULL;
fh = fopen(filename, "rb");
if (fh != NULL) {
fseek(fh, 0L, SEEK_END);
size = ftell(fh);
rewind(fh);
if (size > 0)
buffer = (char *)malloc((size_t)size);
if (buffer != NULL) {
assert(buffer != NULL);
nread = fread(buffer, 1, (size_t)size, fh);
fclose(fh);
fh = NULL;
if (size != (long)nread) {
free(buffer);
buffer = NULL;
}
assert(size == (long)nread);
if (NULL != readSize) {
*readSize = nread;
}
}
if (fh != NULL)
fclose(fh);
}
return buffer;
}
/* <https://www.rosettacode.org/wiki/Write_entire_file#C> */
size_t file_write(const char *fileName, const void *data, const size_t size) {
size_t numberBytesWritten = 0;
FILE *file;
assert(fileName != NULL);
assert(data != NULL);
assert(size > 0);
if (fileName != NULL && *fileName != '\0') {
file = fopen(fileName, "wb");
if (file != NULL) {
if (data != NULL) {
numberBytesWritten = fwrite(data, 1, size, file);
}
fclose(file);
}
}
return numberBytesWritten;
}
int copy_file(const char *srcname, const char *dstname) {
int rc;
char *buf;
size_t nread, nwrite;
rc = -1;
buf = file_read(srcname, &nread);
if (buf) {
nwrite = file_write(dstname, buf, nread);
if (nread != nwrite) {
fprintf(stderr, "Incorrect write size (%ld != %ld)\n", nread, nwrite);
unlink(dstname);
}
assert(nread == nwrite);
free(buf);
rc = 0;
}
return rc;
}

9
io.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef IO_H
#define IO_H
#include <stdio.h>
char *file_read(const char *filename, size_t *readSize);
size_t file_write(const char *fileName, const void *data, const size_t size);
#endif /* IO_H */

46
levenshtein.c Normal file
View File

@ -0,0 +1,46 @@
#define _POSIX_C_SOURCE 200809L /* strtok_r, strndup */
#include <string.h>
#include <stdlib.h> /* malloc */
#include "levenshtein.h"
/* <https://rosettacode.org/wiki/Levenshtein_distance#C>
* TODO: use this for quote duplicate removal */
static int levenshtein_dist(const char *s, const char *t, int *d, int ls, int lt, int i, int j) {
int x, y;
int *n = d + i * ls + j;
if (*n >= 0)
return *n;
if (i == ls)
x = lt - j;
else if (j == lt)
x = ls - i;
else if (s[i] == t[j])
x = levenshtein_dist(s, t, d, ls, lt, i + 1, j + 1);
else {
x = levenshtein_dist(s, t, d, ls, lt, i + 1, j + 1);
if ((y = levenshtein_dist(s, t, d, ls, lt, i, j + 1)) < x)
x = y;
if ((y = levenshtein_dist(s, t, d, ls, lt, i + 1, j)) < x)
x = y;
x++;
}
return *n = x;
}
int levenshtein(const char *s, const char *t) {
int i, j, n;
int ls = (int)strnlen(s, 128), lt = (int)strnlen(t, 128);
int *d;
d = (int*)malloc(sizeof(int) * (unsigned long)((ls + 1) * (lt + 1)));
for (i = 0; i <= ls; i++)
for (j = 0; j <= lt; j++)
*(d + i * ls + j) = -1;
n = levenshtein_dist(s, t, d, ls, lt, 0, 0);
free(d);
d = NULL;
return n;
}

6
levenshtein.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef LEVENSHTEIN_H
#define LEVENSHTEIN_H
int levenshtein(const char *s, const char *t);
#endif /* LEVENSHTEIN_H */

217
main.c Normal file
View File

@ -0,0 +1,217 @@
/*
* Dictionary bot.
*
* Code adapted from:
*
* <https://gist.github.com/enile8/2424514>
* <https://www.rosettacode.org/wiki/Write_entire_file#C>
* <https://rosettacode.org/wiki/Read_entire_file#C>
* libircclient-1.10/examples/spammer.c
*
* And surely others.
*
* Copyright (C) 2004-2009 Georgy Yunaev gyunaev@ulduzsoft.com
* Copyright (C) 2021 Bubblegumdrop <Bubblegumdrop@lain.church>
*
* This example is free, and not covered by LGPL license. There is no
* restriction applied to their modification, redistribution, using and so on.
* You can study them, modify them, use them in your own program - either
* completely or partially. By using it you may give me some credits in your
* program, but you don't have to.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
* Copyright (C) 2021 Bubblegumdrop <Bubblegumdrop@lain.church>
*
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*/
#define _POSIX_C_SOURCE 200809L /* strtok_r, strndup */
#include <string.h>
#include <assert.h> /* assert */
#include <errno.h>
#include <getopt.h> /* getopt */
#include <stdio.h> /* size_t */
#include <unistd.h> /* getopt */
#include "libircclient.h"
#include "data.h"
#include "db.h"
#include "events.h"
#include "levenshtein.h"
#include "strings.h"
#include "threads.h"
#ifndef UNUSED
#define UNUSED(x) (void)(x)
#endif
extern void install_signal_handler(void);
char F_IRC_THREAD = STOPPED;
char F_MAIN_THREAD = STOPPED;
irc_session_t *irc_session = NULL;
const char create_db_query[] = "create table if not exists quotedb (date_added text not null, added_by text not null, channel text not null, subject text not null, words text not null)";
THREAD_FUNCTION(IrcSessionThread) {
const char *server;
struct irc_ctx_t *ctx;
irc_callbacks_t callbacks;
ctx = (struct irc_ctx_t *)arg;
assert(NULL != ctx);
server = ctx->server;
assert(NULL != server);
assert(NULL != ctx->nick);
assert(NULL != ctx->channel);
while (RUNNING == F_IRC_THREAD) {
/* Initialize the callbacks */
memset(&callbacks, 0, sizeof(callbacks));
/* Set up the callbacks we will use */
callbacks.event_channel = event_channel;
callbacks.event_connect = event_connect;
callbacks.event_join = event_join;
callbacks.event_nick = event_nick;
callbacks.event_numeric = event_numeric;
/* And create the IRC session; 0 means error */
irc_session = irc_create_session(&callbacks);
if (!irc_session) {
fprintf(stderr, string_connect_failure, irc_strerror(irc_errno(irc_session)));
F_IRC_THREAD = STOPPED;
}
irc_set_ctx(irc_session, ctx);
irc_option_set(irc_session, LIBIRC_OPTION_STRIPNICKS);
/* If the port number is specified in the server string, use the port 0 so it gets parsed */
if (strchr(server, ':') != 0)
ctx->port = 0;
/*
* To handle the "SSL certificate verify failed" from command line we allow passing ## in front
* of the server name, and in this case tell libircclient not to verify the cert
*/
if (server[0] == '#' && server[0] == '#') {
/* Skip the first character as libircclient needs only one # for SSL support, i.e. #irc.freenode.net */
server++;
irc_option_set(irc_session, LIBIRC_OPTION_SSL_NO_VERIFY);
}
/* Initiate the IRC server connection */
if (irc_connect(irc_session, server, ctx->port, 0, ctx->nick, 0, 0)) {
fprintf(stderr, string_connect_failure, irc_strerror(irc_errno(irc_session)));
F_IRC_THREAD = STOPPED;
}
/* and run into forever loop, generating events */
if (irc_run(irc_session)) {
fprintf(stderr, string_connect_failure, irc_strerror(irc_errno(irc_session)));
F_IRC_THREAD = STOPPED;
}
irc_destroy_session(irc_session);
irc_session = NULL;
}
F_MAIN_THREAD = STOPPED;
#ifdef _WIN32
return 0;
#else
return NULL;
#endif
}
int main(int argc, char *argv[]) {
int rc;
int opt;
const char *db_name;
const char *query_str;
const char *filename;
sqlite3 *db;
struct irc_ctx_t ctx;
thread_id_t tid;
#if 0
const char *s1 = "rosettacode";
const char *s2 = "raisethysword";
printf("distance between `%s' and `%s': %d\n", s1, s2, levenshtein(s1, s2));
#endif
install_signal_handler();
db_name = "familyGuy.sqlite3";
query_str = NULL;
filename = NULL;
rc = -1;
db = NULL;
memset(&ctx, 0, sizeof(struct irc_ctx_t));
ctx.port = 6667;
while ((opt = getopt(argc, argv, "c:d:f:hn:p:q:s:")) != -1) {
switch (opt) {
case 'c':
ctx.channel = optarg;
break;
case 'd':
db_name = optarg;
break;
case 'f':
filename = optarg;
break;
case 'n':
ctx.nick = optarg;
break;
case 'p':
ctx.port = atoi(optarg);
break;
case 'q':
query_str = optarg;
break;
case 's':
ctx.server = optarg;
break;
case 'h':
default:
fprintf(stderr, string_usage_fmt, argv[0]);
return -1;
break;
}
}
if (NULL != db_name) {
if (SQLITE_OK == (rc = sqlite3_open(db_name, &db))) {
printf(string_opendb_success);
sqlite_version(db);
}
} else {
fprintf(stderr, string_opendb_failure, sqlite3_errmsg(db));
return -1;
}
/*run_one(db, create_db_query);*/
if (NULL != query_str) {
rc = run_one(db, query_str);
} else if (NULL != filename) {
rc = run_script(db, filename);
} else if (argc == 4) {
if (argc > 1)
ctx.server = argv[1];
if (argc > 2)
ctx.nick = argv[2];
if (argc > 3)
ctx.channel = argv[3];
if (!ctx.server || !ctx.nick || !ctx.channel) {
fprintf(stderr, string_usage_fmt, argv[0]);
return -1;
}
F_IRC_THREAD = RUNNING;
F_MAIN_THREAD = RUNNING;
if (CREATE_THREAD(&tid, IrcSessionThread, &ctx)) {
printf(string_ircthread_failure, strerror(errno));
} else {
printf(string_ircthread_success);
}
while (RUNNING == F_MAIN_THREAD) {
sleep(1);
}
F_IRC_THREAD = STOPPED;
F_SPAM_THREADS = STOPPED;
rc = 0;
} else {
fprintf(stderr, string_usage_fmt, argv[0]);
return -1;
}
sqlite3_close(db);
return rc;
}

43
makefile Normal file
View File

@ -0,0 +1,43 @@
INLINE_SQLITE ?= 0
WARN := -W -Wall -Wextra -pedantic -pedantic-errors
CC ?= gcc
SRC := $(wildcard *.c)
OBJ := $(SRC:.c=.o)
ifeq ($(INLINE_SQLITE),0)
TMP := $(OBJ)
OBJ := $(filter-out sqlite3.o,$(TMP))
LDFLAGS += -lsqlite3
endif
OPT := -ggdb -O0
ifeq ($(O),1)
OPT := -g -O1
endif
ifeq ($(O),2)
OPT := -g0 -O2
endif
ifeq ($(O),s)
OPT := -g0 -Os
endif
CFLAGS += $(OPT) -std=c99 $(WARN)
CFLAGS += -I.
LDFLAGS += -lircclient -lpthread
all: spammer
sqlite3.o: sqlite3.c
$(CC) -c $< -o $@ $(LDFLAGS)
spammer: $(OBJ)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
clean:
$(RM) -rf spammer db a.out *.o *.db *.sqlite3 *.exe *.dll

10
script.sql Normal file
View File

@ -0,0 +1,10 @@
create table myTable (FirstName varchar(30), LastName varchar(30), Age smallint, Hometown varchar(30), Job varchar(30))
insert into myTable (FirstName, LastName, Age, Hometown, Job) values ('Peter', 'Griffin', 41, 'Quahog', 'Brewery')
insert into myTable (FirstName, LastName, Age, Hometown, Job) values ('Lois', 'Griffin', 40, 'Newport', 'Piano Teacher')
insert into myTable (FirstName, LastName, Age, Hometown, Job) values ('Joseph', 'Swanson', 39, 'Quahog', 'Police Officer')
insert into myTable (FirstName, LastName, Age, Hometown, Job) values ('Glenn', 'Quagmire', 41, 'Quahog', 'Pilot')
select * from myTable
delete from myTable
drop table myTable

13
script2.sql Normal file
View File

@ -0,0 +1,13 @@
BEGIN TRANSACTION;
create table if not exists quotedb (date_added text not null, added_by text not null, channel text not null, subject text not null, words text not null)
insert into quotedb (date_added, channel, added_by, subject, words) values (datetime('now'), '#lainchan', 'strike', 'Flisk', "<Flisk> yeah hate when hookers who give free blowjobs won't finger my asshole for free too")
insert into quotedb (date_added, channel, added_by, subject, words) values (datetime('now'), '#lainchan', 'oda', 'nimbius', '* nimbius is no longer thinking about those beans')
select * from quotedb
COMMIT;
--delete from quotedb
--drop table quotedb

42
signal.c Normal file
View File

@ -0,0 +1,42 @@
#include <signal.h> /* signal */
#include <stdio.h> /* fprintf */
#include <string.h> /* memset */
#include "libircclient.h"
#include "threads.h" /* F_* */
#include "strings.h"
extern irc_session_t *irc_session;
static void SignalHandler(int sig) {
char buf[512];
const char *s = string_signal_unknown_fmt;
#define CASE(x) case x: s = #x; break
switch (sig) {
CASE(SIGTERM);
CASE(SIGINT);
#ifndef _WIN32
CASE(SIGQUIT);
CASE(SIGKILL);
CASE(SIGHUP);
#endif
default:
break;
}
#undef CASE
memset(&buf, 0, sizeof buf);
snprintf(buf, (sizeof buf) - 1, string_signal_fmt, s);
fprintf(stderr, "%s", buf);
F_IRC_THREAD = STOPPED;
F_MAIN_THREAD = STOPPED;
F_SPAM_THREADS = STOPPED;
if (NULL != irc_session)
irc_cmd_quit(irc_session, buf);
/* TODO: clean exit */
}
void install_signal_handler(void) {
signal(SIGTERM, SignalHandler);
signal(SIGINT, SignalHandler);
#ifndef _WIN32
signal(SIGQUIT, SignalHandler);
signal(SIGKILL, SignalHandler);
signal(SIGHUP, SignalHandler);
#endif
}

25
strings.c Normal file
View File

@ -0,0 +1,25 @@
#include "strings.h"
const char string_connect_failure[] = "Could not connect: %s\n";
const char string_connect_success[] = "Connected!\n Server: %s\n Port: %d\n Nick: %s\n Channel: %s\n";
const char string_errmsg_bind[] = "Error binding parameter: %s\n";
const char string_errmsg_clear[] = "Error clearing bindings: %s\n";
const char string_errmsg_insert[] = "Error inserting data: %s\n";
const char string_errmsg_prepare[] = "Error preparing data: %s\n";
const char string_errmsg_readfile[] = "Error reading file: %s\n";
const char string_errmsg_reset[] = "Error resetting statement: %s\n";
const char string_errmsg_update[] = "Error updating data: %s\n";
const char string_ircthread_success[] = "IRC thread was started successfully.\n";
const char string_ircthread_failure[] = "CREATE_THREAD failed: %s\n";
const char string_opendb_failure[] = "Can't open database: %s\n";
const char string_opendb_success[] = "Open database successfully\n";
const char string_signal_fmt[] = "Received %s, exiting.\n";
const char string_signal_unknown_fmt[] = "(Unknown Signal)";
const char string_spam_success[] = "Spammer thread was started successfully.\n";
const char string_usage_fmt[] = "Usage: %s [-q query] [-d sqlite db] [-h] [-p port] [-f script file]\n";

28
strings.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef STRINGS_H
#define STRINGS_H
extern const char string_connect_failure[];
extern const char string_connect_success[];
extern const char string_errmsg_bind[];
extern const char string_errmsg_clear[];
extern const char string_errmsg_insert[];
extern const char string_errmsg_prepare[];
extern const char string_errmsg_readfile[];
extern const char string_errmsg_reset[];
extern const char string_errmsg_update[];
extern const char string_opendb_failure[];
extern const char string_opendb_success[];
extern const char string_signal_fmt[];
extern const char string_signal_unknown_fmt[];
extern const char string_spam_success[];
extern const char string_usage_fmt[];
extern const char string_ircthread_success[];
extern const char string_ircthread_failure[];
#endif /* STRINGS_H */

27
threads.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef THREADS_H
#define THREADS_H
#if defined (_WIN32)
#include <windows.h>
#define CREATE_THREAD(id,func,param) (CreateThread(0, 0, func, param, 0, id) == 0)
#define THREAD_FUNCTION(funcname) static DWORD WINAPI funcname (LPVOID arg)
#define thread_id_t DWORD
#define sleep(a) Sleep (a*1000)
#else
#include <unistd.h>
#include <pthread.h>
#define CREATE_THREAD(id,func,param) (pthread_create (id, 0, func, (void *) param) != 0)
#define THREAD_FUNCTION(funcname) static void * funcname (void * arg)
#define thread_id_t pthread_t
#endif
enum {
RUNNING,
STOPPED,
};
extern char F_IRC_THREAD;
extern char F_MAIN_THREAD;
extern char F_SPAM_THREADS;
#endif /* THREADS_H */

8
unused.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef UNUSED_H
#define UNUSED_H
#ifndef UNUSED
#define UNUSED(x) (void)(x)
#endif
#endif /* UNUSED_H */