view pidgin-twitter.h @ 222:b168502b73c3

expanded size of substitution buffer to 32KB. 128 bytes were not sufficient if both user name and channel name are rather long. tenforward reported this problem and umq pointed out the cause.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Wed, 03 Sep 2008 18:39:39 +0900
parents 739ed7a4426c
children c3efae72f72a
line wrap: on
line source

#ifndef _PIDGIN_TWITTER_H_
#define _PIDGIN_TWITTER_H_

#define _BSD_SOURCE
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <sys/stat.h>
#include <time.h>
#include <locale.h>

#include <gdk-pixbuf/gdk-pixbuf.h>
#include <libxml/xmlreader.h>

#include "gtkplugin.h"
#include "util.h"
#include "debug.h"
#include "connection.h"
#include "version.h"
#include "sound.h"
#include "gtkconv.h"
#include "gtkimhtml.h"

/* regp id */
enum {
    RECIPIENT = 0,
    SENDER,
    COMMAND,
    PSEUDO,
    USER,
    USER_FIRST_LINE,
    USER_FORMATTED,
    CHANNEL_WASSR,
    IMAGE_TWITTER,
    IMAGE_WASSR,
    IMAGE_IDENTICA,
    TAG_IDENTICA,
    SIZE_128_WASSR
};

/* service id */
enum {
    unknown_service = -1,
    twitter_service,
    wassr_service,
    identica_service
};

/* container to hold icon data */
typedef struct _icon_data {
    GdkPixbuf *pixbuf;      /* icon pixmap */
    gboolean requested;     /* TRUE if download icon has been requested */
    GList *request_list;    /* marker list */
    PurpleUtilFetchUrlData *fetch_data; /* icon fetch data */
    const gchar *img_type;  /* image type */
    gchar *icon_url;        /* url for the user's icon */
    gint use_count;         /* usage count */
    time_t mtime;           /* mtime of file */
} icon_data;

/* used by got_icon_cb */
typedef struct _got_icon_data {
    gchar *user_name;
    gint service;
} got_icon_data;

/* used by eval */
typedef struct _eval_data {
    gint which;
    gint service;
} eval_data;

/* container for api based retrieve */
typedef struct _status {
    gchar *created_at;
    gchar *text;
    gchar *screen_name;
    gchar *profile_image_url;
    time_t time;
    guint id;
} status_t;

/* container for api based post */
typedef struct twitter_message {
    PurpleAccount *account;
    char *status;
    time_t time;
} twitter_message_t;

#define PLUGIN_ID	            "gtk-honeyplanet-pidgin_twitter"
#define PLUGIN_NAME	            "pidgin-twitter"

/* options */
#define OPT_PIDGINTWITTER 		"/plugins/pidgin_twitter"
#define OPT_TRANSLATE_RECIPIENT OPT_PIDGINTWITTER "/translate_recipient"
#define OPT_TRANSLATE_SENDER    OPT_PIDGINTWITTER "/translate_sender"
#define OPT_TRANSLATE_CHANNEL   OPT_PIDGINTWITTER "/translate_channel"
#define OPT_PLAYSOUND_RECIPIENT OPT_PIDGINTWITTER "/playsound_recipient"
#define OPT_PLAYSOUND_SENDER    OPT_PIDGINTWITTER "/playsound_sender"
#define OPT_SOUNDID_RECIPIENT   OPT_PIDGINTWITTER "/soundid_recipient"
#define OPT_SOUNDID_SENDER      OPT_PIDGINTWITTER "/soundid_sender"
#define OPT_ESCAPE_PSEUDO       OPT_PIDGINTWITTER "/escape_pseudo"
#define OPT_USERLIST_RECIPIENT  OPT_PIDGINTWITTER "/userlist_recipient"
#define OPT_USERLIST_SENDER     OPT_PIDGINTWITTER "/userlist_sender"
#define OPT_COUNTER             OPT_PIDGINTWITTER "/counter"
#define OPT_SUPPRESS_OOPS       OPT_PIDGINTWITTER "/suppress_oops"
#define OPT_PREVENT_NOTIFICATION OPT_PIDGINTWITTER "/prevent_notification"
#define OPT_ICON_DIR            OPT_PIDGINTWITTER "/icon_dir"
#define OPT_API_BASE_POST       OPT_PIDGINTWITTER "/api_base_post"
#define OPT_SCREEN_NAME_TWITTER OPT_PIDGINTWITTER "/screen_name_twitter"
#define OPT_SCREEN_NAME_WASSR   OPT_PIDGINTWITTER "/screen_name_wassr"
#define OPT_SCREEN_NAME_IDENTICA OPT_PIDGINTWITTER "/screen_name_identica"
#define OPT_PASSWORD_TWITTER    OPT_PIDGINTWITTER "/password_twitter"
#define OPT_SHOW_ICON           OPT_PIDGINTWITTER "/show_icon"
#define OPT_ICON_SIZE           OPT_PIDGINTWITTER "/icon_size"
#define OPT_UPDATE_ICON         OPT_PIDGINTWITTER "/update_icon"
#define OPT_ICON_MAX_COUNT      OPT_PIDGINTWITTER "/icon_max_count"
#define OPT_ICON_MAX_DAYS       OPT_PIDGINTWITTER "/icon_max_days"
#define OPT_API_BASE_GET_INTERVAL OPT_PIDGINTWITTER "/api_base_get_interval"
#define OPT_LOG_OUTPUT          OPT_PIDGINTWITTER "/log_output"
#define OPT_FILTER              OPT_PIDGINTWITTER "/filter"
#define OPT_FILTER_EXCLUDE_REPLY OPT_PIDGINTWITTER "/filter_exclude_reply"
#define OPT_FILTER_TWITTER      OPT_PIDGINTWITTER "/filter_twitter"
#define OPT_FILTER_WASSR        OPT_PIDGINTWITTER "/filter_wassr"
#define OPT_FILTER_IDENTICA     OPT_PIDGINTWITTER "/filter_identica"

#ifdef _WIN32
#define OPT_PIDGIN_BLINK_IM     PIDGIN_PREFS_ROOT "/win32/blink_im"
#endif

/* formats and templates */
#define RECIPIENT_FORMAT_TWITTER "@<a href='http://twitter.com/%s'>%s</a>"
#define SENDER_FORMAT_TWITTER   "%s<a href='http://twitter.com/%s'>%s</a>: "
#define RECIPIENT_FORMAT_WASSR  "@<a href='http://wassr.jp/user/%s'>%s</a>"
#define SENDER_FORMAT_WASSR     "%s<a href='http://wassr.jp/user/%s'>%s</a>: "
#define RECIPIENT_FORMAT_IDENTICA "@<a href='http://identi.ca/%s'>%s</a>"
#define SENDER_FORMAT_IDENTICA  "%s<a href='http://identi.ca/%s'>%s</a>: "
#define CHANNEL_FORMAT_WASSR    "%s<a href='http://wassr.jp/channel/%s'>%s</a> "
#define CHANNEL_FORMAT_IDENTICA "%s<a href='http://identi.ca/tag/%s'>%s</a> "
#define TAG_FORMAT_IDENTICA     "#<a href='http://identi.ca/tag/%s'>%s</a>"

#define DEFAULT_LIST            "(list of users: separated with ' ,:;')"
#define OOPS_MESSAGE            "<body>Oops! Your update was over 140 characters. We sent the short version to your friends (they can view the entire update on the web).<BR></body>"
#define EMPTY                   ""

/* patterns */
#define P_RECIPIENT         "@([A-Za-z0-9_]+)"
#define P_SENDER            "^(\\r?\\n?)\\s*([A-Za-z0-9_]+)(?:\\s*\\(.+\\))?: "
#define P_COMMAND           "^(?:\\s*)([dDfFgGlLmMnNtTwW]{1}\\s+[A-Za-z0-9_]+)(?:\\s*\\Z)"
#define P_PSEUDO            "^\\s*(?:[\"#$%&'()*+,\\-./:;<=>?\\[\\\\\\]_`{|}~]|[^\\s\\x21-\\x7E])*([dDfFgGlLmMnNtTwW]{1})(?:\\Z|\\s+|[^\\x21-\\x7E]+\\Z)"
#define P_USER              "^\\(.+?\\)\\s*([A-Za-z0-9_]+):"
#define P_USER_FIRST_LINE   "^\\(.+?\\)\\s*.+:\\s*([A-Za-z0-9_]+):"
#define P_USER_FORMATTED    "^.*?<a .+?>([A-Za-z0-9_]+)</a>:"
#define P_CHANNEL           "^(.*?<a .+?>[A-Za-z0-9_]+</a>: \\r?\\n?#)([A-Za-z0-9_]+) "
#define P_IMAGE_TWITTER     "<a href=\"/account/profile_image/.+?\"><img .+? id=\"profile-image\".*src=\"(http://.+)\" .+?/>"
#define P_IMAGE_WASSR       "<div class=\"image\"><a href=\".+\"><img src=\"(.+)\" width=\".+?\" /></a></div>"
#define P_IMAGE_IDENTICA    "<img src=\"(http://.+.identi.ca/.+)\" class=\"avatar profile\" width=\"96\" height=\"96\" alt=\"[A-Za-z0-0_]+\"/>"
#define P_TAG_IDENTICA      "#([A-Za-z0-9]+)"
#define P_SIZE_128_WASSR    "\\.128\\."

/* twitter API specific macros */
#define TWITTER_BASE_URL "http://twitter.com"
#define TWITTER_STATUS_GET "GET /statuses/friends_timeline.xml HTTP/1.1\r\n" \
    "Host: twitter.com\r\n"                                          \
    "User-Agent: pidgin-twitter\r\n"                                 \
    "Authorization: Basic %s\r\n"
#define TWITTER_STATUS_POST "POST /statuses/update.xml HTTP/1.1\r\n" \
    "Host: twitter.com\r\n"                                          \
    "User-Agent: pidgin-twitter\r\n"                                 \
    "Authorization: Basic %s\r\n"                                    \
    "Content-Length: %d\r\n"
#define TWITTER_STATUS_FORMAT "status=%s&source=pidgintwitter"
#define TWITTER_DEFAULT_INTERVAL (60)
#define TWITTER_DEFAULT_ICON_URL "http://static.twitter.com/images/default_profile_bigger.png"

/* size of substitution buffer */
#define SUBST_BUF_SIZE (32 * 1024)

/* wassr specific macros */
#define WASSR_POST_LEN (255)
#define IDENTICA_POST_LEN (140)

/* misc macros */
#define DEFAULT_ICON_SIZE (48)
#define DEFAULT_ICON_MAX_COUNT (50)
#define DEFAULT_ICON_MAX_DAYS (7)
#define DAYS_TO_SECONDS(d) ((d) * 86400)

/* debug macros */
#define twitter_debug(fmt, ...)	do { if(purple_prefs_get_bool(OPT_LOG_OUTPUT)) purple_debug(PURPLE_DEBUG_INFO, PLUGIN_NAME, "%s():%4d:  " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__); } while(0);
#define twitter_error(fmt, ...)	do { if(purple_prefs_get_bool(OPT_LOG_OUTPUT)) purple_debug(PURPLE_DEBUG_ERROR, PLUGIN_NAME, "%s():%4d:  " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__); } while(0);

/* prototypes */
static void escape(gchar **str);
static gboolean sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, void *data);
static gboolean eval(const GMatchInfo *match_info, GString *result, gpointer user_data);
static void translate(gchar **str, gint which, gint service);
static void playsound(gchar **str, gint which);
static gboolean writing_im_cb(PurpleAccount *account, char *sender, char **buffer, PurpleConversation *conv, int flags, void *data);
static void insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, gchar *new_text, gint new_text_length, gpointer user_data);
static void delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, GtkTextIter *end_pos, gpointer user_data);
static void detach_from_window(void);
static void detach_from_conv(PurpleConversation *conv, gpointer null);
static void delete_requested_icon_marks(PidginConversation *gtkconv, GHashTable *table);
static void attach_to_window(void);
static void attach_to_conv(PurpleConversation *conv, gpointer null);
static gboolean is_twitter_account(PurpleAccount *account, const char *name);
static gboolean is_twitter_conv(PurpleConversation *conv);
static gboolean is_wassr_account(PurpleAccount *account, const char *name);
static gboolean is_wassr_conv(PurpleConversation *conv);
static gboolean is_identica_account(PurpleAccount *account, const char *name);
static gboolean is_identica_conv(PurpleConversation *conv);
static void conv_created_cb(PurpleConversation *conv, gpointer null);
static void deleting_conv_cb(PurpleConversation *conv);
static gboolean receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, PurpleConversation *conv, PurpleMessageFlags *flags, void *data);
static void insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data);
static void insert_requested_icon(const gchar *user_name, gint service);
static void got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
static void request_icon(const char *user_name, gint service, gboolean renew);
static void mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service);
static gboolean displaying_im_cb(PurpleAccount *account, const char *who, char **message, PurpleConversation *conv, PurpleMessageFlags flags, void *data);
static void displayed_im_cb(PurpleAccount *account, const char *who, char *message, PurpleConversation *conv, PurpleMessageFlags flags);
static gboolean load_plugin(PurplePlugin *plugin);
static gboolean unload_plugin(PurplePlugin *plugin);
static void counter_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);

static void init_plugin(PurplePlugin *plugin);
static void remove_marks_func(gpointer key, gpointer value, gpointer user_data);
static void cancel_fetch_func(gpointer key, gpointer value, gpointer user_data);
static gint get_service_type(PurpleConversation *conv);
static GdkPixbuf *make_scaled_pixbuf(const gchar *url_text, gsize len);

static void parse_user(xmlNode *user, status_t *st);
static void parse_status(xmlNode *status, status_t *st);
static void get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);
static gboolean get_status_with_api(gpointer data);
static void read_timestamp(const char *str, struct tm *res);

static void strip_markup(gchar **str, gboolean escape);
static gchar *strip_html_markup(const gchar *src);
static GtkWidget *prefs_get_frame(PurplePlugin *plugin);

/* more prototypes */
static gboolean is_posted_message(status_t *status, guint lastid);
static void free_status(status_t *st);
static gboolean ensure_path_exists(const char *dir);
static gchar *twitter_memrchr(const gchar *s, int c, size_t n);
static void got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
static void cleanup_hash_entry_func(gpointer key, gpointer value, gpointer user_data);
static void invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data);
static void icon_size_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
static void interval_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
static void text_changed_cb(gpointer *data);
static void bool_toggled_cb(gpointer *data);
static void spin_changed_cb(gpointer *data);
static void combo_changed_cb(gpointer *data);
static void disconnect_prefs_cb(GtkObject *object, gpointer data);
static GtkWidget *prefs_get_frame(PurplePlugin *plugin);
static void apply_filter(gchar **sender, gchar **buffer, PurpleMessageFlags *flags, int service);
static gint get_service_type_by_account(PurpleAccount *account, const char *sender);

#endif