All pastes #970500 Raw Edit

Mine

public text v1 · immutable
·published 2008-04-04 02:10 UTC
rendered paste body
/**
 * Modules Main Functions for loading and matching commands. - Source
 *
 ***********
 * Module Name: bs_quotes
 * Author: mikea - mikea@mjpa.co.uk
 * Creation Date: 12/06/2007
 * More info on http://forum.anope.org/index.php
 *
 ***********
 * 
 * This module adds a !quote fantasy command to BotServ. This enables
 * anyone on the CS access list (halfop+) to add/delete quotes, everyone
 * else can view a quote, view a random quote, search a quote or check how
 * many quotes exist.
 *
 * Commands:
 *
 * - !quote add <quote> - Adds a new quote
 * - !quote del [#]id - Deletes the quote with id 'id'. The # is optional
 * - !quote view [#]id - Views the quote with id 'id'. The # is optional
 * - !quote get [#]id - Same as !quote view
 * - !quote random - Views a random quote
 * - !quote search <text> - Returns a list of ids containing 'text'
 * - !quote count - Returns how many quotes are in the db.
 *
 ***********
 * 
 * Config directives:
 *
 * QuoteMySQLHost "sql.hostname" - The hostname for the MySQL server (for TCP connections)
 * QuoteMySQLPort 3306 - The port for the MySQL server (for TCP connections)
 * QuoteMySQLSocket "/tmp/mysql.sock" - The UNIX socket file for the MySQL server
 * QuoteMySQLUser "sql_user" - The username to use
 * QuoteMySQLPassword "pass" - The password for the specified username
 * QuoteMySQLDatabase "database" - The database to use for the quotes
 * QuoteEnableMultiLine - Enable multi-line support, disabled by default
 *
 * NOTE:
 *
 * To use TCP connections, the QuoteMySQLHost and QuoteMySQLPort are required.
 * To use a UNIX socket file, comment out the QuoteMySQLHost and use QuoteMySQLSocket
 * 
 ***********
 *
 * 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 2 of the License, or
 * (at your option) any later version.
 *
 ***********
 *
 * Last Updated: 20/06/2007
 *
 * v1.0.0 - 14/06/2007 - Initial release
 *
 * v1.1.0 - 18/06/2007 - Added multiline support, when adding quotes use !n! to insert a new line
 *                     - Extra config option "QuoteEnableMultiLine" to enable multi-line support
 *                     - Made the MySQL connection permanent to allow a single query method
 * 
 * v1.1.1 - 20/06/2007 - Fix compile problem for MySQL <5.0.13 (MYSQL_OPT_RECONNECT)
 *
 **/

#include "module.h"
#include <time.h>
#include <mysql.h>

#define AUTHOR               "mikea - mikea@mjpa.co.uk"
#define VERSION              "$Id: bs_quotes.c v1.1.1 20-06-2007 mikea $"

/* Various messages sent to channel, careful when changing not to remove any arguments! */
#define QUOTE_SYNTAX         "[QUOTE] Syntax: add <msg>, del <id>, view/get <id>, random, search <text>, count."
#define QUOTE_NO_QUOTE       "[QUOTE] No such quote."
#define QUOTE_SUCCESS_ADD    "[QUOTE] Successfully added quote #%llu."
#define QUOTE_SUCCESS_DEL    "[QUOTE] Successfully removed quote #%llu."
#define QUOTE_TEMPLATE       "[QUOTE #%s] %s"
#define QUOTE_TEMPLATE_BY    "[QUOTE INFO] Added by %s on the %s."
#define QUOTE_SEARCH_REPLY   "[QUOTE] Results: "
#define QUOTE_COUNT_REPLY    "[QUOTE] %.*s quotes"
#define QUOTE_UNKNOWN_ERROR  "[QUOTE] Unknown error occurred!"

#define QUOTE_MYSQL_ERR      "[QUOTE] MySQL Error: %s.", GET_MYSQL_ERROR
#define GET_MYSQL_ERROR      mysql_error(mysql_obj)

/* Config options */
char *Q_MYSQL_HOST = NULL;
unsigned int Q_MYSQL_PORT = 0;
char *Q_MYSQL_SOCK = NULL;
char *Q_MYSQL_USER = NULL;
char *Q_MYSQL_PASS = NULL;
char *Q_MYSQL_DB = NULL;
int Q_ENABLE_MULTILINE = 0;

/* MySQL Variables */
MYSQL *mysql_obj = NULL;
MYSQL_RES *mysql_result_set = NULL;
my_ulonglong q_mysql_affected_rows;

/* Main module methods */
int load_config();
int reload_config(int ac, char **av);
int do_quote(int ac, char **av);
int do_quote_normal(int ac, char **av);

/* Methods for responding to !quote * */
void do_add_quote(ChannelInfo *ci, User *u, char *arg);
void do_del_quote(ChannelInfo *ci, User *u, char *arg);
void do_view_quote(ChannelInfo *ci, User *u, char *arg);
void do_search_quote(ChannelInfo *ci, User *u, char *arg);
void do_count_quote(ChannelInfo *ci, User *u);

/* Helper methods */
int do_mysql_connect();
unsigned int do_mysql_query(char *format, ...);
void do_mysql_close();
char *str(char *format, ...);
char *vstr(char *format, va_list args);
char *str_escape(MYSQL *sql, char *in_format, ...);
char *nth(int num);
char *str_month(int mon);
char *ampm(int hr);

/**
 * Called when module is loaded.
 **/
int AnopeInit(void)
{
  EvtHook *hook;

  moduleAddAuthor(AUTHOR);
  moduleAddVersion(VERSION);

  hook = createEventHook(EVENT_BOT_FANTASY, do_quote);
  if (moduleAddEventHook(hook) != MOD_ERR_OK)
  {
    alog("[\002bs_quotes\002] Can't hook to EVENT_BOT_FANTASY event");
    return MOD_STOP;
  }

  hook = createEventHook(EVENT_BOT_FANTASY_NO_ACCESS, do_quote_normal);
  if (moduleAddEventHook(hook) != MOD_ERR_OK)
  {
    alog("[\002bs_quotes\002] Can't hook to EVENT_BOT_FANTASY_NO_ACCESS event");
    return MOD_STOP;
  }

  hook = createEventHook(EVENT_RELOAD, reload_config);
  if (moduleAddEventHook(hook) != MOD_ERR_OK)
  {
    alog("[\002bs_quotes\002] Can't hook to EVENT_RELOAD event");
    return MOD_STOP;
  }

  /* Load config before creating table, any errors in config are dealt with in the load_config */
  if (load_config() == MOD_STOP) return MOD_STOP;

  /* Connect to MySQL server */
  if (do_mysql_connect())
  {
    alog("[\002bs_quotes\002] Can't connect to MySQL server");
    return MOD_STOP;
  }

  /* Create our tables */
  unsigned int ret = do_mysql_query("CREATE TABLE IF NOT EXISTS `quotes` ("
                                    " `id` int(10) unsigned NOT NULL auto_increment,"
                                    "  `user` text NOT NULL,"
                                    " `quote` text NOT NULL,"
                                    " `time` timestamp NOT NULL default CURRENT_TIMESTAMP,"
                                    " `views` int(11) NOT NULL default '0',"
                                    " PRIMARY KEY  (`id`)"
                                    " ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;");

  /* Query error check */
  if (ret != 0)
  {
    alog("[\002bs_quotes\002] Module Init SQL Error: %s", GET_MYSQL_ERROR);
    return MOD_STOP;
  }

  alog("\002bs_quotes\002 loaded... [Author: \002%s\002] [Version: \002%s\002]", AUTHOR, VERSION);

  return MOD_CONT;
}

/**
 * Called when module is unloaded
 **/
void AnopeFini(void)
{
  if (Q_MYSQL_HOST) { free(Q_MYSQL_HOST); Q_MYSQL_HOST = NULL; }
  if (Q_MYSQL_SOCK) { free(Q_MYSQL_SOCK); Q_MYSQL_SOCK = NULL; }
  if (Q_MYSQL_USER) { free(Q_MYSQL_USER); Q_MYSQL_USER = NULL; }
  if (Q_MYSQL_PASS) { free(Q_MYSQL_PASS); Q_MYSQL_PASS = NULL; }
  if (Q_MYSQL_DB)   { free(Q_MYSQL_DB);   Q_MYSQL_DB = NULL;   }

  do_mysql_close();

  alog("[\002bs_quotes\002]: Module Unloaded");
}

/**
 * Load the configuration directives
 **/
int load_config(void)
{
  Directive confvalues[][1] = {
    {{ "QuoteMySQLHost",       { { PARAM_STRING, PARAM_RELOAD, &Q_MYSQL_HOST       } } }},
    {{ "QuoteMySQLPort",       { { PARAM_PORT,   PARAM_RELOAD, &Q_MYSQL_PORT       } } }},
    {{ "QuoteMySQLSocket",     { { PARAM_STRING, PARAM_RELOAD, &Q_MYSQL_SOCK       } } }},
    {{ "QuoteMySQLUser",       { { PARAM_STRING, PARAM_RELOAD, &Q_MYSQL_USER       } } }},
    {{ "QuoteMySQLPassword",   { { PARAM_STRING, PARAM_RELOAD, &Q_MYSQL_PASS       } } }},
    {{ "QuoteMySQLDatabase",   { { PARAM_STRING, PARAM_RELOAD, &Q_MYSQL_DB         } } }},
    {{ "QuoteEnableMultiLine", { { PARAM_SET,    PARAM_RELOAD, &Q_ENABLE_MULTILINE } } }}
  };

  /* free() any already set options */
  if (Q_MYSQL_HOST) { free(Q_MYSQL_HOST); Q_MYSQL_HOST = NULL; }
  Q_MYSQL_PORT = 3306;
  if (Q_MYSQL_SOCK) { free(Q_MYSQL_SOCK); Q_MYSQL_SOCK = NULL; }
  if (Q_MYSQL_USER) { free(Q_MYSQL_USER); Q_MYSQL_USER = NULL; }
  if (Q_MYSQL_PASS) { free(Q_MYSQL_PASS); Q_MYSQL_PASS = NULL; }
  if (Q_MYSQL_DB)   { free(Q_MYSQL_DB);   Q_MYSQL_DB = NULL;   }
  Q_ENABLE_MULTILINE = 0;

  /* Get each config option */
  unsigned int i = 0;
  for (i = 0; i < 7; i++)
  {
    moduleGetConfigDirective(confvalues[i]);
  }

  /* Check for valid options */

  /* We *need* user/pass/db */
  if (!Q_MYSQL_USER || !Q_MYSQL_PASS || !Q_MYSQL_DB)
  {
    alog("[\002bs_quotes\002]: Missing config options!");
    return MOD_STOP;
  }
  /* We need one of host or sock */
  if (!Q_MYSQL_HOST && !Q_MYSQL_SOCK)
  {
    alog("[\002bs_quotes\002]: Missing config options!");
    return MOD_STOP;
  }

  return MOD_CONT;
}

/**
 * Upon /os reload call the routines for reloading the configuration directives
 **/
int reload_config(int ac, char **av)
{
  if (ac >= 1)
  {
    if (!stricmp(av[0], EVENT_START))
    {
      alog("[\002bs_quotes\002]: Reloading configuration directives...");
      return load_config();
    }
  }
  return MOD_CONT;
}

/**
 * Handles all fantasy commands.
 * Here we ll identify the command and call the right routines.
 **/
int do_quote(int ac, char **av)
{
  User *u;
  ChannelInfo *ci;
  Channel *c;

  /* Some basic error checking... should never match */
  if (ac < 3) return MOD_CONT;

  if (!(ci = cs_findchan(av[2]))) return MOD_CONT;
  if (!(u = finduser(av[1])))     return MOD_CONT;
  if (!(c = findchan(ci->name)))  return MOD_CONT;

  /* Only check !quote commands */
  if (stricmp(av[0], "quote") != 0) return MOD_CONT;

  /* Only our messages from now on */

  /* Just !quote on it's own? */
  if (ac == 3)
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_SYNTAX);
    return MOD_CONT;
  }

  if ((strnicmp(av[3], "add ", 4)==0) && (strlen(av[3]) > 4))
  {
    if (check_access(u, ci, CA_AUTOHALFOP))
    {
      do_add_quote(ci, u, av[3] + 4);
    }
    else
    {
      notice_lang(ci->bi->nick, u, PERMISSION_DENIED);
    }
  }
  else if ((strnicmp(av[3], "del ", 4)==0) && (strlen(av[3]) > 4))
  {
    if (check_access(u, ci, CA_AUTOHALFOP))
    {
      do_del_quote(ci, u, av[3] + 4);
    }
    else
    {
      notice_lang(ci->bi->nick, u, PERMISSION_DENIED);
    }
  }
  else if (((strnicmp(av[3], "view ", 5)==0) && (strlen(av[3]) > 5)) || ((strnicmp(av[3], "get ", 4)==0) && (strlen(av[3]) > 4)))
  {
    int arg_skip = 4;
    if (strnicmp(av[3], "view", 4)==0) arg_skip++;
    do_view_quote(ci, u, av[3] + arg_skip);
  }
  else if (stricmp(av[3], "random")==0)
  {
    do_view_quote(ci, u, NULL);
  }
  else if ((strnicmp(av[3], "search ", 7)==0) && (strlen(av[3]) > 7))
  {
    do_search_quote(ci, u, av[3] + 7);
  }
  else if (stricmp(av[3], "count")==0)
  {
    do_count_quote(ci, u);
  }
  else
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_SYNTAX);
  }

  return MOD_CONT;
}

/**
 * Handles the !quote command for people not on the fantasy list
 **/
int do_quote_normal(int ac, char **av)
{
  User *u;
  ChannelInfo *ci;
  Channel *c;

  /* Some basic error checking... should never match */
  if (ac < 3) return MOD_CONT;

  if (!(ci = cs_findchan(av[2]))) return MOD_CONT;
  if (!(u = finduser(av[1])))     return MOD_CONT;
  if (!(c = findchan(ci->name)))  return MOD_CONT;

  /* Only check !quote commands */
  if (stricmp(av[0], "quote") != 0) return MOD_CONT;

  /* Only our messages from now on */

  /* Just !quote on it's own? */
  if (ac == 3)
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_SYNTAX);
    return MOD_CONT;
  }

  if ((strnicmp(av[3], "add ", 4)==0) && (strlen(av[3]) > 4))
  {
    notice_lang(ci->bi->nick, u, PERMISSION_DENIED);
  }
  else if ((strnicmp(av[3], "del ", 4)==0) && (strlen(av[3]) > 4))
  {
    notice_lang(ci->bi->nick, u, PERMISSION_DENIED);
  }
  else if (((strnicmp(av[3], "view ", 5)==0) && (strlen(av[3]) > 5)) || ((strnicmp(av[3], "get ", 4)==0) && (strlen(av[3]) > 4)))
  {
    int arg_skip = 4;
    if (strnicmp(av[3], "view", 4)==0) arg_skip++;
    do_view_quote(ci, u, av[3] + arg_skip);
  }
  else if (stricmp(av[3], "random")==0)
  {
    do_view_quote(ci, u, NULL);
  }
  else if ((strnicmp(av[3], "search ", 7)==0) && (strlen(av[3]) > 7))
  {
    do_search_quote(ci, u, av[3] + 7);
  }
  else if (stricmp(av[3], "count")==0)
  {
    do_count_quote(ci, u);
  }
  else
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_SYNTAX);
  }

  return MOD_CONT;
}

/**
 * Handles the !quote add command
 **/
void do_add_quote(ChannelInfo *ci, User *u, char *arg)
{
  /* Prepare arguments */
  char *user = str_escape(mysql_obj, "%s", u->nick);
  char *quote = str_escape(mysql_obj, "%s", arg);

  /* Do query */
  unsigned int ret = do_mysql_query("INSERT INTO `%s`.`quotes` ( `id` , `user` , `quote`, `time` , `views` ) VALUES ( NULL , '%s', '%s', CURRENT_TIMESTAMP, 0 );", Q_MYSQL_DB, user, quote);
  
  /* Free the arguments */
  free(user);
  free(quote);

  if (ret != 0)
  {
    /* Failed, dump error to irc */
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_MYSQL_ERR);
  }
  else
  {
    /* Success, show new id */
    my_ulonglong new_id = mysql_insert_id(mysql_obj);
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_SUCCESS_ADD, new_id);
  }
}

/**
 * Handles the !quote del command
 **/
void do_del_quote(ChannelInfo *ci, User *u, char *arg)
{
  if ((strlen(arg) > 1) && (*arg == '#')) arg++;

  my_ulonglong id_num;
  if (1 != sscanf(arg, "%llu", &id_num))
  {
    /* Bad ID */
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_NO_QUOTE);
    return;
  }

  unsigned int ret = do_mysql_query("DELETE FROM `%s`.`quotes` WHERE id = %llu;", Q_MYSQL_DB, id_num);
  if (ret != 0)
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_MYSQL_ERR);
    return;
  }

  if (q_mysql_affected_rows > 0)
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_SUCCESS_DEL, id_num);
  else
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_NO_QUOTE);
}

/**
 * Handles the !quote view command
 **/
void do_view_quote(ChannelInfo *ci, User *u, char *arg)
{
  my_ulonglong id_num;
  /* if arg, then id was specified - find it */
  if (arg)
  {
    if ((strlen(arg) > 1) && (*arg == '#')) arg++;

    if (1 != sscanf(arg, "%llu", &id_num))
    {
      /* Bad ID */
      anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_NO_QUOTE);
      return;
    }
  }

  unsigned int ret;
  /* arg != NULL, use id; else randomise */
  if (arg)
    ret = do_mysql_query("SELECT quote, user, time FROM `%s`.`quotes` WHERE id = %llu;", Q_MYSQL_DB, id_num);
  else
    ret = do_mysql_query("SELECT quote, user, time, id FROM `%s`.`quotes` ORDER BY RAND() LIMIT 1;", Q_MYSQL_DB);

  if (ret != 0)
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_MYSQL_ERR);
    return;
  }

  MYSQL_ROW row = mysql_fetch_row(mysql_result_set);
  if (row)
  {
    char *quote = NULL, *user = NULL, *date = NULL, *id_str = NULL;

    /* Get results */
    unsigned long *lengths = mysql_fetch_lengths(mysql_result_set);
    quote = str("%.*s", (int) lengths[0], row[0] ? row[0] : "");
    user  = str("%.*s", (int) lengths[1], row[1] ? row[1] : "");
    id_str = arg ? str("%llu", id_num) : str("%.*s", (int) lengths[3], row[3] ? row[3] : "");

    /* Format the date */          
    date  = str("%.*s", (int) lengths[2], row[2] ? row[2] : "");
    int date_parts[6];
    sscanf(date, "%d-%d-%d %d:%d:%d", &date_parts[0], &date_parts[1], &date_parts[2], &date_parts[3], &date_parts[4], &date_parts[5]); 
    free(date);
    date = str("%d%s of %s %d at %d:%s%d%s", date_parts[2], nth(date_parts[2]), str_month(date_parts[1]), date_parts[0], date_parts[3], 
               date_parts[4] < 10 ? "0" : "", date_parts[4], ampm(date_parts[3]));

    if (Q_ENABLE_MULTILINE == 1)
    {
      /* Find each !n! and send the parts as separate lines */
      char *quote_mk1 = quote;
      while (*quote_mk1 != 0)
      {
        char *quote_mk2 = quote_mk1;
  
        /* Find the next !n! */
        while ((*quote_mk2 != 0) && (strncmp(quote_mk2, "!n!", 3) != 0)) quote_mk2++;
    
        /* Create the line */
        char *line = (char*)malloc(quote_mk2 - quote_mk1 + 1);
        memset(line, 0, quote_mk2 - quote_mk1 + 1);
        strncpy(line, quote_mk1, quote_mk2 - quote_mk1);

        /* Send the line */
        anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_TEMPLATE, id_str, line);

        /* Free the malloc'd stuff */
        free(line);

        /* Move pointer on */
        quote_mk1 = quote_mk2 + (*quote_mk2 != 0 ? 3 : 0);
      }
    }
    else
    {
      /* Send the line without multi-line support*/
      anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_TEMPLATE, id_str, quote);
    }

    /* Send quote by */
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_TEMPLATE_BY, user, date);
  
    /* Free the quote parts */
    free(id_str);
    free(quote);
    free(user);
    free(date);

    do_mysql_query("UPDATE `%s`.`quotes` SET views = views + 1 WHERE id = %llu;", Q_MYSQL_DB, id_num);
  }
  else
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_NO_QUOTE);
  }
}

/**
 * Handles the !quote search command
 **/
void do_search_quote(ChannelInfo *ci, User *u, char *arg)
{
  /* Build query */
  char *search_string = str_escape(mysql_obj, "%s", arg);
  unsigned int ret = do_mysql_query("SELECT id FROM `%s`.`quotes` WHERE quote LIKE '%%%s%%'", Q_MYSQL_DB, search_string);
  free(search_string);

  if (ret != 0)
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_MYSQL_ERR);
    return;
  }

  MYSQL_ROW row;
  char *buffer = str(QUOTE_SEARCH_REPLY);
  my_ulonglong found_results = 0;
  my_ulonglong results = mysql_num_rows(mysql_result_set);
  while ((row = mysql_fetch_row(mysql_result_set)) != NULL)
  {
    found_results++;

    unsigned long *length = mysql_fetch_lengths(mysql_result_set);
    buffer = (char*)realloc(buffer, strlen(buffer) + ((int) length[0]) + 3);

    strncat(buffer, row[0], (int) length[0]);
    if (found_results != results) strcat(buffer, ", ");
  }
  
  if (found_results == 0)
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_NO_QUOTE);
  else
    anope_cmd_privmsg(ci->bi->nick, ci->name, buffer);

  /* Moved out of else{} above, otherwise at found_results == 0 => mem leak */
  free(buffer);
}

/**
 * Handles !quote count
 **/
void do_count_quote(ChannelInfo *ci, User *u)
{
  /* Run query */
  unsigned int ret = do_mysql_query("SELECT COUNT(id) AS ids FROM `%s`.`quotes`;", Q_MYSQL_DB);

  if (ret != 0)
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_MYSQL_ERR);
    return;
  }

  MYSQL_ROW row = mysql_fetch_row(mysql_result_set);
  if (row)
  {
    unsigned long *lengths = mysql_fetch_lengths(mysql_result_set);
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_COUNT_REPLY, (int)lengths[0], row[0]);
  }
  else
  {
    anope_cmd_privmsg(ci->bi->nick, ci->name, QUOTE_UNKNOWN_ERROR);
  }
}

/**
 * Method for connecting to mysql
 **/
int do_mysql_connect()
{
  if (mysql_obj) return 0;

  mysql_obj = mysql_init(NULL);
  if (!mysql_real_connect(mysql_obj, Q_MYSQL_HOST, Q_MYSQL_USER, Q_MYSQL_PASS, Q_MYSQL_DB, Q_MYSQL_PORT, Q_MYSQL_SOCK, CLIENT_IGNORE_SPACE))
  {
    mysql_close(mysql_obj);
    mysql_obj = NULL;
    return 1;
  }
  else
  {
    /* Bug causes this to be reset, set it here */
    /* Use mysql_options() if possible for >5.0.13 */
#ifdef MYSQL_OPT_RECONNECT
    my_bool my_true = true;
    mysql_options(mysql_obj, MYSQL_OPT_RECONNECT, &my_true);
#else
    mysql_obj->reconnect = 1;
#endif
    return 0;
  }
}

/**
 * Method for running mysql queries 
 **/
unsigned int do_mysql_query(char *format, ...)
{
  va_list args;
  va_start(args,format);
  char *query = vstr(format, args);
  va_end(args);

  if (mysql_result_set)
  {
    mysql_free_result(mysql_result_set);
    mysql_result_set = NULL;
  }

  /* Run query */
  if (mysql_query(mysql_obj, query))
  {
    free(query);
    return 1;
  }
  
  /* free() query */
  free(query);

  /* Fill in info */
  mysql_result_set = mysql_store_result(mysql_obj);
  q_mysql_affected_rows = mysql_affected_rows(mysql_obj);

  return 0;
}

/**
 * Method to close connection
 **/
void do_mysql_close()
{
  if (mysql_result_set)
  {
    mysql_free_result(mysql_result_set);
    mysql_result_set = NULL;
  }
  mysql_close(mysql_obj);
  mysql_obj = NULL;
}

/**
 * Method to produce a malloc()'d char*, similar to printf()
 **/
char *str(char *format, ...)
{
  va_list args;
  va_start(args,format);
  char *ret = vstr(format, args);
  va_end(args);
  return ret;
}

/**
 * The va_list method for str() above
 **/
char *vstr(char *format, va_list args)
{
  char *buffer = NULL;
  int va_size = 256;
  /* Parse the format and generate a proper string output */
  do
  {
    va_size *= 2;
    buffer = (char*)realloc(buffer, va_size);
    memset(buffer, 0, va_size);
  } while (vsnprintf(buffer, va_size, format, args) < 0);

  /* Trim any white space at the end */
  char *output = (char*)malloc(strlen(buffer) + 1);
  strcpy(output, buffer);
  free(buffer);
  return output;
}

/**
 * Similar to str(), but returns a result safe for use in SQL queries
 **/
char *str_escape(MYSQL *sql, char *in_format, ...)
{
  va_list args;
  va_start(args,in_format);
  char *in = vstr(in_format, args);
  va_end(args);

  char *safe_in = (char*)malloc(strlen(in)*2 + 1);
  mysql_real_escape_string(sql, safe_in, in, strlen(in));

  free(in);

  return safe_in;  
}

/**
 * Simple method to add the 'nth' suffix for the given number
 **/
char *nth(int num)
{
  if (num == 11 || num == 12 || num == 13) return "th";

  switch (num % 10)
  {
    case 1: return "st";
    case 2: return "nd";
    case 3: return "rd";
    default: return "th";
  }
}

/**
 * Method to return the short month name for the month number (1-12)
 **/
char *str_month(int mon)
{
  switch(mon)
  {
    case 1: return "Jan";
    case 2: return "Feb";
    case 3: return "Mar";
    case 4: return "Apr";
    case 5: return "May";
    case 6: return "June";
    case 7: return "July";
    case 8: return "Aug";
    case 9: return "Sept";
    case 10: return "Oct";
    case 11: return "Nov";
    case 12: return "Dec";
    default: return "???";
  }
}

/**
 * Method to return whether am or pm for the given hour
 **/
char *ampm(int hr)
{
  if (hr < 12) return "am";
  else return "pm";
}