8 #include "pluginpref.h"
10 #include "stringref.h"
15 /* This must be the last Purple header included. */
20 /* Where is the Windows partition mounted? */
21 #ifndef PURPLE_LOG_READER_WINDOWS_MOUNT_POINT
22 #define PURPLE_LOG_READER_WINDOWS_MOUNT_POINT "/mnt/windows"
31 /* Some common functions. */
32 static int get_month(const char *month
)
35 const char *months
[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
36 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL
};
37 for (iter
= 0; months
[iter
]; iter
++) {
38 if (strcmp(month
, months
[iter
]) == 0)
45 /*****************************************************************************
47 *****************************************************************************/
49 /* The adium logger doesn't write logs, only reads them. This is to include
50 * Adium logs in the log viewer transparently.
53 static PurpleLogLogger
*adium_logger
;
60 struct adium_logger_data
{
62 enum adium_log_type type
;
65 static GList
*adium_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
70 PurplePluginProtocolInfo
*prpl_info
;
76 g_return_val_if_fail(sn
!= NULL
, NULL
);
77 g_return_val_if_fail(account
!= NULL
, NULL
);
79 logdir
= purple_prefs_get_string("/plugins/core/log_reader/adium/log_directory");
81 /* By clearing the log directory path, this logger can be (effectively) disabled. */
82 if (!logdir
|| !*logdir
)
85 plugin
= purple_find_prpl(purple_account_get_protocol_id(account
));
89 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
90 if (!prpl_info
->list_icon
)
93 prpl_name
= g_ascii_strup(prpl_info
->list_icon(account
, NULL
), -1);
95 temp
= g_strdup_printf("%s.%s", prpl_name
, account
->username
);
96 path
= g_build_filename(logdir
, temp
, sn
, NULL
);
99 dir
= g_dir_open(path
, 0, NULL
);
103 while ((file
= g_dir_read_name(dir
))) {
104 if (!purple_str_has_prefix(file
, sn
))
106 if (purple_str_has_suffix(file
, ".html") || purple_str_has_suffix(file
, ".AdiumHTMLLog")) {
108 const char *date
= file
;
110 date
+= strlen(sn
) + 2;
111 if (sscanf(date
, "%u|%u|%u",
112 &tm
.tm_year
, &tm
.tm_mon
, &tm
.tm_mday
) != 3) {
114 purple_debug_error("Adium log parse",
115 "Filename timestamp parsing error\n");
117 char *filename
= g_build_filename(path
, file
, NULL
);
118 FILE *handle
= g_fopen(filename
, "rb");
119 char contents
[57]; /* XXX: This is really inflexible. */
121 struct adium_logger_data
*data
;
130 rd
= fread(contents
, 1, 56, handle
) == 0;
134 /* XXX: This is fairly inflexible. */
135 contents2
= contents
;
136 while (*contents2
&& *contents2
!= '>')
140 while (*contents2
&& *contents2
!= '>')
145 if (sscanf(contents2
, "%u.%u.%u",
146 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
) != 3) {
148 purple_debug_error("Adium log parse",
149 "Contents timestamp parsing error\n");
154 data
= g_new0(struct adium_logger_data
, 1);
155 data
->path
= filename
;
156 data
->type
= ADIUM_HTML
;
161 /* XXX: Look into this later... Should we pass in a struct tm? */
162 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, mktime(&tm
), NULL
);
163 log
->logger
= adium_logger
;
164 log
->logger_data
= data
;
166 list
= g_list_prepend(list
, log
);
168 } else if (purple_str_has_suffix(file
, ".adiumLog")) {
170 const char *date
= file
;
172 date
+= strlen(sn
) + 2;
173 if (sscanf(date
, "%u|%u|%u",
174 &tm
.tm_year
, &tm
.tm_mon
, &tm
.tm_mday
) != 3) {
176 purple_debug_error("Adium log parse",
177 "Filename timestamp parsing error\n");
179 char *filename
= g_build_filename(path
, file
, NULL
);
180 FILE *handle
= g_fopen(filename
, "rb");
181 char contents
[14]; /* XXX: This is really inflexible. */
183 struct adium_logger_data
*data
;
192 rd
= fread(contents
, 1, 13, handle
);
196 contents2
= contents
;
197 while (*contents2
&& *contents2
!= '(')
202 if (sscanf(contents2
, "%u.%u.%u",
203 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
) != 3) {
205 purple_debug_error("Adium log parse",
206 "Contents timestamp parsing error\n");
214 data
= g_new0(struct adium_logger_data
, 1);
215 data
->path
= filename
;
216 data
->type
= ADIUM_TEXT
;
218 /* XXX: Look into this later... Should we pass in a struct tm? */
219 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, mktime(&tm
), NULL
);
220 log
->logger
= adium_logger
;
221 log
->logger_data
= data
;
223 list
= g_list_prepend(list
, log
);
236 static char *adium_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
)
238 struct adium_logger_data
*data
;
239 GError
*error
= NULL
;
242 /* XXX: TODO: We probably want to set PURPLE_LOG_READ_NO_NEWLINE
243 * XXX: TODO: for HTML logs. */
247 g_return_val_if_fail(log
!= NULL
, g_strdup(""));
249 data
= log
->logger_data
;
251 g_return_val_if_fail(data
->path
!= NULL
, g_strdup(""));
253 purple_debug_info("Adium log read", "Reading %s\n", data
->path
);
254 if (!g_file_get_contents(data
->path
, &read
, NULL
, &error
)) {
255 purple_debug_error("Adium log read", "Error reading log: %s\n",
256 (error
&& error
->message
) ? error
->message
: "Unknown error");
262 if (data
->type
!= ADIUM_HTML
) {
263 char *escaped
= g_markup_escape_text(read
, -1);
269 /* This problem only seems to show up on Windows.
270 * The BOM is displaying as a space at the beginning of the log.
272 if (purple_str_has_prefix(read
, "\xef\xbb\xbf"))
274 /* FIXME: This feels so wrong... */
275 char *temp
= g_strdup(&(read
[3]));
281 /* TODO: Apply formatting.
282 * Replace the above hack with something better, since we'll
283 * be looping over the entire log file contents anyway.
289 static int adium_logger_size (PurpleLog
*log
)
291 struct adium_logger_data
*data
;
295 g_return_val_if_fail(log
!= NULL
, 0);
297 data
= log
->logger_data
;
299 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
302 if (!data
->path
|| stat(data
->path
, &st
))
308 text
= adium_logger_read(log
, NULL
);
315 static void adium_logger_finalize(PurpleLog
*log
)
317 struct adium_logger_data
*data
;
319 g_return_if_fail(log
!= NULL
);
321 data
= log
->logger_data
;
328 /*****************************************************************************
330 *****************************************************************************/
333 /* The fire logger doesn't write logs, only reads them. This is to include
334 * Fire logs in the log viewer transparently.
337 static PurpleLogLogger
*fire_logger
;
339 struct fire_logger_data
{
342 static GList
*fire_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
344 /* TODO: Do something here. */
348 static char * fire_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
)
350 struct fire_logger_data
*data
;
352 g_return_val_if_fail(log
!= NULL
, g_strdup(""));
354 data
= log
->logger_data
;
356 /* TODO: Do something here. */
360 static int fire_logger_size (PurpleLog
*log
)
362 g_return_val_if_fail(log
!= NULL
, 0);
364 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
367 /* TODO: Do something here. */
371 static void fire_logger_finalize(PurpleLog
*log
)
373 g_return_if_fail(log
!= NULL
);
375 /* TODO: Do something here. */
380 /*****************************************************************************
381 * Messenger Plus! Logger *
382 *****************************************************************************/
385 /* The messenger_plus logger doesn't write logs, only reads them. This is to include
386 * Messenger Plus! logs in the log viewer transparently.
389 static PurpleLogLogger
*messenger_plus_logger
;
391 struct messenger_plus_logger_data
{
394 static GList
*messenger_plus_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
396 /* TODO: Do something here. */
400 static char * messenger_plus_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
)
402 struct messenger_plus_logger_data
*data
= log
->logger_data
;
404 g_return_val_if_fail(log
!= NULL
, g_strdup(""));
406 data
= log
->logger_data
;
408 /* TODO: Do something here. */
412 static int messenger_plus_logger_size (PurpleLog
*log
)
414 g_return_val_if_fail(log
!= NULL
, 0);
416 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
419 /* TODO: Do something here. */
423 static void messenger_plus_logger_finalize(PurpleLog
*log
)
425 g_return_if_fail(log
!= NULL
);
427 /* TODO: Do something here. */
432 /*****************************************************************************
433 * MSN Messenger Logger *
434 *****************************************************************************/
436 /* The msn logger doesn't write logs, only reads them. This is to include
437 * MSN Messenger message histories in the log viewer transparently.
440 static PurpleLogLogger
*msn_logger
;
442 struct msn_logger_data
{
445 const char *session_id
;
450 /* This function is really confusing. It makes baby rlaager cry...
451 In other news: "You lost a lot of blood but we found most of it."
453 static time_t msn_logger_parse_timestamp(xmlnode
*message
, struct tm
**tm_out
)
455 const char *datetime
;
456 static struct tm tm2
;
472 #ifndef G_DISABLE_CHECKS
477 /* Trigger the usual warning. */
478 g_return_val_if_fail(message
!= NULL
, (time_t)0);
482 datetime
= xmlnode_get_attrib(message
, "DateTime");
483 if (!(datetime
&& *datetime
))
485 purple_debug_error("MSN log timestamp parse",
486 "Attribute missing: %s\n", "DateTime");
490 stamp
= purple_str_to_time(datetime
, TRUE
, &tm2
, NULL
, NULL
);
491 #ifdef HAVE_TM_GMTOFF
494 #ifdef HAVE_STRUCT_TM_TM_ZONE
495 /* This is used in the place of a timezone abbreviation if the
496 * offset is way off. The user should never really see it, but
497 * it's here just in case. The parens are to make it clear it's
498 * not a real timezone. */
499 tm2
.tm_zone
= _("(UTC)");
503 date
= xmlnode_get_attrib(message
, "Date");
504 if (!(date
&& *date
))
506 purple_debug_error("MSN log timestamp parse",
507 "Attribute missing: %s\n", "Date");
512 time
= xmlnode_get_attrib(message
, "Time");
513 if (!(time
&& *time
))
515 purple_debug_error("MSN log timestamp parse",
516 "Attribute missing: %s\n", "Time");
521 if (sscanf(date
, "%u/%u/%u", &month
, &day
, &year
) != 3)
523 purple_debug_error("MSN log timestamp parse",
524 "%s parsing error\n", "Date");
538 if (sscanf(time
, "%u:%u:%u %c", &hour
, &min
, &sec
, &am_pm
) != 4)
540 purple_debug_error("MSN log timestamp parse",
541 "%s parsing error\n", "Time");
548 } else if (hour
== 12) {
553 str
= g_strdup_printf("%04i-%02i-%02iT%02i:%02i:%02i", year
, month
, day
, hour
, min
, sec
);
554 t
= purple_str_to_time(str
, TRUE
, &tm
, NULL
, NULL
);
562 if (diff
> (14 * 60 * 60))
566 /* Swap day & month variables, to see if it's a non-US date. */
568 str
= g_strdup_printf("%04i-%02i-%02iT%02i:%02i:%02i", year
, month
, day
, hour
, min
, sec
);
569 t
= purple_str_to_time(str
, TRUE
, &tm
, NULL
, NULL
);
576 if (diff
> (14 * 60 * 60))
578 /* We got a time, it's not impossible, but
579 * the diff is too large. Display the UTC time. */
592 /* We got a time, it's not impossible, but
593 * the diff is too large. Display the UTC time. */
600 /* If we got here, the time is legal with a reasonable offset.
601 * Let's find out if it's in our TZ. */
602 if (purple_str_to_time(str
, FALSE
, &tm
, NULL
, NULL
) == stamp
)
610 /* The time isn't in our TZ, but it's reasonable. */
611 #ifdef HAVE_STRUCT_TM_TM_ZONE
618 static GList
*msn_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
624 const char *savedfilename
= NULL
;
627 GError
*error
= NULL
;
628 gchar
*contents
= NULL
;
632 const char *old_session_id
= "";
633 struct msn_logger_data
*data
= NULL
;
635 g_return_val_if_fail(sn
!= NULL
, NULL
);
636 g_return_val_if_fail(account
!= NULL
, NULL
);
638 if (strcmp(account
->protocol_id
, "prpl-msn"))
641 logdir
= purple_prefs_get_string("/plugins/core/log_reader/msn/log_directory");
643 /* By clearing the log directory path, this logger can be (effectively) disabled. */
644 if (!logdir
|| !*logdir
)
647 buddy
= purple_find_buddy(account
, sn
);
649 if ((username
= g_strdup(purple_account_get_string(
650 account
, "log_reader_msn_log_folder", NULL
)))) {
651 /* As a special case, we allow the null string to kill the parsing
652 * straight away. This would allow the user to deal with the case
653 * when two account have the same username at different domains and
654 * only one has logs stored.
661 username
= g_strdup(purple_normalize(account
, account
->username
));
665 savedfilename
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
,
666 "log_reader_msn_log_filename");
670 /* As a special case, we allow the null string to kill the parsing
671 * straight away. This would allow the user to deal with the case
672 * when two buddies have the same username at different domains and
673 * only one has logs stored.
675 if (!*savedfilename
) {
680 logfile
= g_strdup(savedfilename
);
682 logfile
= g_strdup_printf("%s.xml", purple_normalize(account
, sn
));
685 path
= g_build_filename(logdir
, username
, "History", logfile
, NULL
);
687 if (!g_file_test(path
, G_FILE_TEST_EXISTS
)) {
688 gboolean found
= FALSE
;
695 /* We had a saved filename, but it doesn't exist.
696 * Returning now is the right course of action because we don't
697 * want to detect another file incorrectly.
704 /* Perhaps we're using a new version of MSN with the weird numbered folders.
705 * I don't know how the numbers are calculated, so I'm going to attempt to
706 * find logs by pattern matching...
709 at_sign
= g_strrstr(username
, "@");
713 dir
= g_dir_open(logdir
, 0, NULL
);
717 while ((name
= g_dir_read_name(dir
))) {
718 const char *c
= name
;
720 if (!purple_str_has_prefix(c
, username
))
723 c
+= strlen(username
);
725 if (!g_ascii_isdigit(*c
))
731 path
= g_build_filename(logdir
, name
, NULL
);
732 /* The !c makes sure we got to the end of the while loop above. */
733 if (!*c
&& g_file_test(path
, G_FILE_TEST_IS_DIR
)) {
734 char *history_path
= g_build_filename(
735 path
, "History", NULL
);
736 if (g_file_test(history_path
, G_FILE_TEST_IS_DIR
)) {
737 purple_account_set_string(account
,
738 "log_reader_msn_log_folder", name
);
745 g_free(history_path
);
759 /* If we've reached this point, we've found a History folder. */
761 username
= g_strdup(purple_normalize(account
, sn
));
762 at_sign
= g_strrstr(username
, "@");
767 dir
= g_dir_open(path
, 0, NULL
);
771 while ((name
= g_dir_read_name(dir
))) {
772 const char *c
= name
;
774 if (!purple_str_has_prefix(c
, username
))
777 c
+= strlen(username
);
779 if (!g_ascii_isdigit(*c
))
785 path
= g_build_filename(path
, name
, NULL
);
786 if (!strcmp(c
, ".xml") &&
787 g_file_test(path
, G_FILE_TEST_EXISTS
)) {
790 logfile
= g_strdup(name
);
807 logfile
= NULL
; /* No sense saving the obvious buddy@domain.com. */
810 purple_debug_info("MSN log read", "Reading %s\n", path
);
811 if (!g_file_get_contents(path
, &contents
, &length
, &error
)) {
813 purple_debug_error("MSN log read", "Error reading log\n");
820 /* Reading the file was successful...
821 * Save its name if it involves the crazy numbers. The idea here is that you could
822 * then tweak the blist.xml file by hand if need be. This would be the case if two
823 * buddies have the same username at different domains. One set of logs would get
824 * detected for both buddies.
826 if (buddy
&& logfile
) {
827 PurpleBlistNode
*node
= (PurpleBlistNode
*)buddy
;
828 purple_blist_node_set_string(node
, "log_reader_msn_log_filename", logfile
);
832 root
= xmlnode_from_str(contents
, length
);
837 for (message
= xmlnode_get_child(root
, "Message"); message
;
838 message
= xmlnode_get_next_twin(message
)) {
839 const char *session_id
;
841 session_id
= xmlnode_get_attrib(message
, "SessionID");
843 purple_debug_error("MSN log parse",
844 "Error parsing message: %s\n", "SessionID missing");
848 if (strcmp(session_id
, old_session_id
)) {
850 * The session ID differs from the last message.
851 * Thus, this is the start of a new conversation.
857 data
= g_new0(struct msn_logger_data
, 1);
859 data
->message
= message
;
860 data
->session_id
= session_id
;
862 data
->last_log
= FALSE
;
864 stamp
= msn_logger_parse_timestamp(message
, &tm
);
866 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, stamp
, tm
);
867 log
->logger
= msn_logger
;
868 log
->logger_data
= data
;
870 list
= g_list_prepend(list
, log
);
872 old_session_id
= session_id
;
876 data
->last_log
= TRUE
;
878 return g_list_reverse(list
);
881 static char * msn_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
)
883 struct msn_logger_data
*data
;
884 GString
*text
= NULL
;
888 *flags
= PURPLE_LOG_READ_NO_NEWLINE
;
889 g_return_val_if_fail(log
!= NULL
, g_strdup(""));
891 data
= log
->logger_data
;
894 /* The GTK code which displays the logs g_free()s whatever is
895 * returned from this function. Thus, we can't reuse the str
896 * part of the GString. The only solution is to free it and
899 g_string_free(data
->text
, FALSE
);
902 text
= g_string_new("");
904 if (!data
->root
|| !data
->message
|| !data
->session_id
) {
905 /* Something isn't allocated correctly. */
906 purple_debug_error("MSN log parse",
907 "Error parsing message: %s\n", "Internal variables inconsistent");
913 for (message
= data
->message
; message
;
914 message
= xmlnode_get_next_twin(message
)) {
916 const char *new_session_id
;
918 const char *from_name
= NULL
;
919 const char *to_name
= NULL
;
922 enum name_guesses name_guessed
= NAME_GUESS_UNKNOWN
;
923 const char *their_name
;
930 new_session_id
= xmlnode_get_attrib(message
, "SessionID");
932 /* If this triggers, something is wrong with the XML. */
933 if (!new_session_id
) {
934 purple_debug_error("MSN log parse",
935 "Error parsing message: %s\n", "New SessionID missing");
939 if (strcmp(new_session_id
, data
->session_id
)) {
940 /* The session ID differs from the first message.
941 * Thus, this is the start of a new conversation.
946 text_node
= xmlnode_get_child(message
, "Text");
950 from
= xmlnode_get_child(message
, "From");
952 xmlnode
*user
= xmlnode_get_child(from
, "User");
955 from_name
= xmlnode_get_attrib(user
, "FriendlyName");
957 /* This saves a check later. */
963 to
= xmlnode_get_child(message
, "To");
965 xmlnode
*user
= xmlnode_get_child(to
, "User");
967 to_name
= xmlnode_get_attrib(user
, "FriendlyName");
969 /* This saves a check later. */
975 their_name
= from_name
;
976 if (from_name
&& purple_prefs_get_bool("/plugins/core/log_reader/use_name_heuristics")) {
977 const char *friendly_name
= purple_connection_get_display_name(log
->account
->gc
);
979 if (friendly_name
!= NULL
) {
980 int friendly_name_length
= strlen(friendly_name
);
983 PurpleBuddy
*buddy
= purple_find_buddy(log
->account
, log
->name
);
984 gboolean from_name_matches
;
985 gboolean to_name_matches
;
988 their_name
= purple_buddy_get_alias(buddy
);
990 if (log
->account
->alias
)
992 alias
= log
->account
->alias
;
993 alias_length
= strlen(alias
);
1001 /* Try to guess which user is me.
1002 * The first step is to determine if either of the names matches either my
1003 * friendly name or alias. For this test, "match" is defined as:
1004 * ^(friendly_name|alias)([^a-zA-Z0-9].*)?$
1006 from_name_matches
= (purple_str_has_prefix(from_name
, friendly_name
) &&
1007 !isalnum(*(from_name
+ friendly_name_length
))) ||
1008 (purple_str_has_prefix(from_name
, alias
) &&
1009 !isalnum(*(from_name
+ alias_length
)));
1011 to_name_matches
= to_name
!= NULL
&& (
1012 (purple_str_has_prefix(to_name
, friendly_name
) &&
1013 !isalnum(*(to_name
+ friendly_name_length
))) ||
1014 (purple_str_has_prefix(to_name
, alias
) &&
1015 !isalnum(*(to_name
+ alias_length
))));
1017 if (from_name_matches
) {
1018 if (!to_name_matches
) {
1019 name_guessed
= NAME_GUESS_ME
;
1021 } else if (to_name_matches
) {
1022 name_guessed
= NAME_GUESS_THEM
;
1025 const char *server_alias
= NULL
;
1026 char *alias
= g_strdup(purple_buddy_get_alias(buddy
));
1029 /* "Truncate" the string at the first non-alphanumeric
1030 * character. The idea is to relax the comparison.
1032 for (temp
= alias
; *temp
; temp
++) {
1033 if (!isalnum(*temp
)) {
1038 alias_length
= strlen(alias
);
1040 /* Try to guess which user is them.
1041 * The first step is to determine if either of the names
1042 * matches their alias. For this test, "match" is
1043 * defined as: ^alias([^a-zA-Z0-9].*)?$
1045 from_name_matches
= (purple_str_has_prefix(
1046 from_name
, alias
) &&
1047 !isalnum(*(from_name
+
1050 to_name_matches
= to_name
&& (purple_str_has_prefix(
1052 !isalnum(*(to_name
+
1057 if (from_name_matches
) {
1058 if (!to_name_matches
) {
1059 name_guessed
= NAME_GUESS_THEM
;
1061 } else if (to_name_matches
) {
1062 name_guessed
= NAME_GUESS_ME
;
1063 } else if ((server_alias
= purple_buddy_get_server_alias(buddy
))) {
1064 friendly_name_length
=
1065 strlen(server_alias
);
1067 /* Try to guess which user is them.
1068 * The first step is to determine if either of
1069 * the names matches their friendly name. For
1070 * this test, "match" is defined as:
1071 * ^friendly_name([^a-zA-Z0-9].*)?$
1073 from_name_matches
= (purple_str_has_prefix(
1076 !isalnum(*(from_name
+
1077 friendly_name_length
)));
1079 to_name_matches
= to_name
&& (
1080 (purple_str_has_prefix(
1081 to_name
, server_alias
) &&
1082 !isalnum(*(to_name
+
1083 friendly_name_length
))));
1085 if (from_name_matches
) {
1086 if (!to_name_matches
) {
1087 name_guessed
= NAME_GUESS_THEM
;
1089 } else if (to_name_matches
) {
1090 name_guessed
= NAME_GUESS_ME
;
1098 if (name_guessed
!= NAME_GUESS_UNKNOWN
) {
1099 text
= g_string_append(text
, "<span style=\"color: #");
1100 if (name_guessed
== NAME_GUESS_ME
)
1101 text
= g_string_append(text
, "16569E");
1103 text
= g_string_append(text
, "A82F2F");
1104 text
= g_string_append(text
, ";\">");
1107 time_unix
= msn_logger_parse_timestamp(message
, &tm
);
1109 timestamp
= g_strdup_printf("<font size=\"2\">(%02u:%02u:%02u)</font> ",
1110 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
);
1111 text
= g_string_append(text
, timestamp
);
1115 text
= g_string_append(text
, "<b>");
1117 if (name_guessed
== NAME_GUESS_ME
) {
1118 if (log
->account
->alias
)
1119 text
= g_string_append(text
, log
->account
->alias
);
1121 text
= g_string_append(text
, log
->account
->username
);
1123 else if (name_guessed
== NAME_GUESS_THEM
)
1124 text
= g_string_append(text
, their_name
);
1126 text
= g_string_append(text
, from_name
);
1128 text
= g_string_append(text
, ":</b> ");
1131 if (name_guessed
!= NAME_GUESS_UNKNOWN
)
1132 text
= g_string_append(text
, "</span>");
1134 style
= xmlnode_get_attrib(text_node
, "Style");
1136 tmp
= xmlnode_get_data(text_node
);
1137 if (style
&& *style
) {
1138 text
= g_string_append(text
, "<span style=\"");
1139 text
= g_string_append(text
, style
);
1140 text
= g_string_append(text
, "\">");
1141 text
= g_string_append(text
, tmp
);
1142 text
= g_string_append(text
, "</span><br>");
1144 text
= g_string_append(text
, tmp
);
1145 text
= g_string_append(text
, "<br>");
1155 static int msn_logger_size (PurpleLog
*log
)
1160 g_return_val_if_fail(log
!= NULL
, 0);
1162 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
1165 text
= msn_logger_read(log
, NULL
);
1166 size
= strlen(text
);
1172 static void msn_logger_finalize(PurpleLog
*log
)
1174 struct msn_logger_data
*data
;
1176 g_return_if_fail(log
!= NULL
);
1178 data
= log
->logger_data
;
1181 xmlnode_free(data
->root
);
1184 g_string_free(data
->text
, FALSE
);
1190 /*****************************************************************************
1192 *****************************************************************************/
1194 /* The trillian logger doesn't write logs, only reads them. This is to include
1195 * Trillian logs in the log viewer transparently.
1198 static PurpleLogLogger
*trillian_logger
;
1199 static void trillian_logger_finalize(PurpleLog
*log
);
1201 struct trillian_logger_data
{
1202 char *path
; /* FIXME: Change this to use PurpleStringref like log.c:old_logger_list */
1205 char *their_nickname
;
1208 static GList
*trillian_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
1212 PurplePlugin
*plugin
;
1213 PurplePluginProtocolInfo
*prpl_info
;
1215 const char *buddy_name
;
1218 GError
*error
= NULL
;
1219 gchar
*contents
= NULL
;
1224 g_return_val_if_fail(sn
!= NULL
, NULL
);
1225 g_return_val_if_fail(account
!= NULL
, NULL
);
1227 logdir
= purple_prefs_get_string("/plugins/core/log_reader/trillian/log_directory");
1229 /* By clearing the log directory path, this logger can be (effectively) disabled. */
1230 if (!logdir
|| !*logdir
)
1233 plugin
= purple_find_prpl(purple_account_get_protocol_id(account
));
1237 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
1238 if (!prpl_info
->list_icon
)
1241 prpl_name
= g_ascii_strup(prpl_info
->list_icon(account
, NULL
), -1);
1243 buddy_name
= purple_normalize(account
, sn
);
1245 filename
= g_strdup_printf("%s.log", buddy_name
);
1246 path
= g_build_filename(
1247 logdir
, prpl_name
, filename
, NULL
);
1249 purple_debug_info("Trillian log list", "Reading %s\n", path
);
1250 /* FIXME: There's really no need to read the entire file at once.
1251 * See src/log.c:old_logger_list for a better approach.
1253 if (!g_file_get_contents(path
, &contents
, &length
, &error
)) {
1255 g_error_free(error
);
1260 path
= g_build_filename(
1261 logdir
, prpl_name
, "Query", filename
, NULL
);
1262 purple_debug_info("Trillian log list", "Reading %s\n", path
);
1263 if (!g_file_get_contents(path
, &contents
, &length
, &error
)) {
1265 g_error_free(error
);
1271 struct trillian_logger_data
*data
= NULL
;
1273 int last_line_offset
= 0;
1286 if (purple_str_has_prefix(line
, "Session Close ")) {
1287 if (data
&& !data
->length
) {
1288 if (!(data
->length
= last_line_offset
- data
->offset
)) {
1289 /* This log had no data, so we remove it. */
1290 GList
*last
= g_list_last(list
);
1292 purple_debug_info("Trillian log list",
1293 "Empty log. Offset %i\n", data
->offset
);
1295 trillian_logger_finalize((PurpleLog
*)last
->data
);
1296 list
= g_list_delete_link(list
, last
);
1299 } else if (line
[0] && line
[1] && line
[2] &&
1300 purple_str_has_prefix(&line
[3], "sion Start ")) {
1301 /* The conditional is to make sure we're not reading off
1302 * the end of the string. We don't want strlen(), as that'd
1303 * have to count the whole string needlessly.
1305 * The odd check here is because a Session Start at the
1306 * beginning of the file can be overwritten with a UTF-8
1307 * byte order mark. Yes, it's weird.
1309 char *their_nickname
= line
;
1312 if (data
&& !data
->length
)
1313 data
->length
= last_line_offset
- data
->offset
;
1315 while (*their_nickname
&& (*their_nickname
!= ':'))
1319 /* This code actually has nothing to do with
1320 * the timestamp YET. I'm simply using this
1321 * variable for now to NUL-terminate the
1322 * their_nickname string.
1324 timestamp
= their_nickname
;
1325 while (*timestamp
&& *timestamp
!= ')')
1328 if (*timestamp
== ')') {
1333 if (line
[0] && line
[1] && line
[2])
1336 /* Now we start dealing with the timestamp. */
1338 /* Skip over the day name. */
1339 while (*timestamp
&& (*timestamp
!= ' '))
1344 /* Parse out the month. */
1346 while (*timestamp
&& (*timestamp
!= ' '))
1351 /* Parse the day, time, and year. */
1352 if (sscanf(timestamp
, "%u %u:%u:%u %u",
1353 &tm
.tm_mday
, &tm
.tm_hour
,
1354 &tm
.tm_min
, &tm
.tm_sec
,
1355 &tm
.tm_year
) != 5) {
1357 purple_debug_error("Trillian log timestamp parse",
1358 "Session Start parsing error\n");
1364 /* Let the C library deal with
1365 * daylight savings time.
1368 tm
.tm_mon
= get_month(month
);
1371 struct trillian_logger_data
, 1);
1372 data
->path
= g_strdup(path
);
1373 data
->offset
= offset
;
1375 data
->their_nickname
=
1376 g_strdup(their_nickname
);
1378 /* XXX: Look into this later... Should we pass in a struct tm? */
1379 log
= purple_log_new(PURPLE_LOG_IM
,
1380 sn
, account
, NULL
, mktime(&tm
), NULL
);
1381 log
->logger
= trillian_logger
;
1382 log
->logger_data
= data
;
1384 list
= g_list_prepend(list
, log
);
1390 last_line_offset
= offset
;
1399 return g_list_reverse(list
);
1402 static char * trillian_logger_read (PurpleLog
*log
, PurpleLogReadFlags
*flags
)
1404 struct trillian_logger_data
*data
;
1414 *flags
= PURPLE_LOG_READ_NO_NEWLINE
;
1416 g_return_val_if_fail(log
!= NULL
, g_strdup(""));
1418 data
= log
->logger_data
;
1420 g_return_val_if_fail(data
->path
!= NULL
, g_strdup(""));
1421 g_return_val_if_fail(data
->length
> 0, g_strdup(""));
1422 g_return_val_if_fail(data
->their_nickname
!= NULL
, g_strdup(""));
1424 purple_debug_info("Trillian log read", "Reading %s\n", data
->path
);
1426 read
= g_malloc(data
->length
+ 2);
1428 file
= g_fopen(data
->path
, "rb");
1429 fseek(file
, data
->offset
, SEEK_SET
);
1430 data
->length
= fread(read
, 1, data
->length
, file
);
1433 if (read
[data
->length
-1] == '\n') {
1434 read
[data
->length
] = '\0';
1436 read
[data
->length
] = '\n';
1437 read
[data
->length
+1] = '\0';
1440 /* Load miscellaneous data. */
1441 buddy
= purple_find_buddy(log
->account
, log
->name
);
1443 escaped
= g_markup_escape_text(read
, -1);
1447 /* Apply formatting... */
1448 formatted
= g_string_sized_new(strlen(read
));
1454 const char *footer
= NULL
;
1455 GString
*temp
= NULL
;
1457 if ((c
= strstr(c
, "\n")))
1465 * The format is (Link: URL)URL
1466 * So, I want to find each occurance of "(Link: " and replace that chunk with:
1468 * Then, replace the next ")" with:
1470 * Then, replace the next " " (or add this if the end-of-line is reached) with:
1473 * As implemented, this isn't perfect, but it should cover common cases.
1475 while (line
&& (link
= strstr(line
, "(Link: ")))
1477 const char *tmp
= link
;
1485 if (!(end_paren
= strstr(link
, ")")))
1487 /* Something is not as we expect. Bail out. */
1492 temp
= g_string_sized_new(c
? (c
- 1 - line
) : strlen(line
));
1494 g_string_append_len(temp
, line
, (tmp
- line
));
1496 /* Start an <a> tag. */
1497 g_string_append(temp
, "<a href=\"");
1499 /* Append up to the ) */
1500 g_string_append_len(temp
, link
, end_paren
- link
);
1502 /* Finish the <a> tag. */
1503 g_string_append(temp
, "\">");
1505 /* The \r is a bit of a hack to keep there from being a \r in
1506 * the link text, which may not matter. */
1507 if ((space
= strstr(end_paren
, " ")) || (space
= strstr(end_paren
, "\r")))
1509 g_string_append_len(temp
, end_paren
+ 1, space
- end_paren
- 1);
1511 /* Close the <a> tag. */
1512 g_string_append(temp
, "</a>");
1518 /* There is no space before the end of the line. */
1519 g_string_append(temp
, end_paren
+ 1);
1520 /* Close the <a> tag. */
1521 g_string_append(temp
, "</a>");
1527 /* Something is not as we expect. Bail out. */
1535 g_string_append(temp
, line
);
1540 const char *timestamp
;
1542 if ((timestamp
= strstr(line
, "]"))) {
1544 /* TODO: Parse the timestamp and convert it to Purple's format. */
1545 g_string_append(formatted
, "<font size=\"2\">(");
1546 g_string_append_len(formatted
, line
, (timestamp
- line
));
1547 g_string_append(formatted
,")</font> ");
1548 line
= timestamp
+ 1;
1549 if (line
[0] && line
[1])
1553 if (purple_str_has_prefix(line
, "*** ")) {
1554 line
+= (sizeof("*** ") - 1);
1555 g_string_append(formatted
, "<b>");
1557 if (purple_str_has_prefix(line
, "NOTE: This user is offline.")) {
1558 line
= _("User is offline.");
1559 } else if (purple_str_has_prefix(line
,
1560 "NOTE: Your status is currently set to ")) {
1562 line
+= (sizeof("NOTE: ") - 1);
1563 } else if (purple_str_has_prefix(line
, "Auto-response sent to ")) {
1564 g_string_append(formatted
, _("Auto-response sent:"));
1565 while (*line
&& *line
!= ':')
1569 g_string_append(formatted
, "</b>");
1571 } else if (strstr(line
, " signed off ")) {
1572 const char *alias
= NULL
;
1575 alias
= purple_buddy_get_alias(buddy
);
1577 if (alias
!= NULL
) {
1578 g_string_append_printf(formatted
,
1579 _("%s has signed off."), alias
);
1581 g_string_append_printf(formatted
,
1582 _("%s has signed off."), log
->name
);
1585 } else if (strstr(line
, " signed on ")) {
1586 const char *alias
= NULL
;
1589 alias
= purple_buddy_get_alias(buddy
);
1592 g_string_append(formatted
, alias
);
1594 g_string_append(formatted
, log
->name
);
1596 line
= " logged in.";
1597 } else if (purple_str_has_prefix(line
,
1598 "One or more messages may have been undeliverable.")) {
1600 g_string_append(formatted
,
1601 "<span style=\"color: #ff0000;\">");
1602 g_string_append(formatted
,
1603 _("One or more messages may have been "
1606 footer
= "</span></b>";
1607 } else if (purple_str_has_prefix(line
,
1608 "You have been disconnected.")) {
1610 g_string_append(formatted
,
1611 "<span style=\"color: #ff0000;\">");
1612 g_string_append(formatted
,
1613 _("You were disconnected from the server."));
1615 footer
= "</span></b>";
1616 } else if (purple_str_has_prefix(line
,
1617 "You are currently disconnected.")) {
1619 g_string_append(formatted
,
1620 "<span style=\"color: #ff0000;\">");
1621 line
= _("You are currently disconnected. Messages "
1622 "will not be received unless you are "
1624 footer
= "</span></b>";
1625 } else if (purple_str_has_prefix(line
,
1626 "Your previous message has not been sent.")) {
1628 g_string_append(formatted
,
1629 "<span style=\"color: #ff0000;\">");
1631 if (purple_str_has_prefix(line
,
1632 "Your previous message has not been sent. "
1633 "Reason: Maximum length exceeded.")) {
1635 g_string_append(formatted
,
1636 _("Message could not be sent because "
1637 "the maximum length was exceeded."));
1640 g_string_append(formatted
,
1641 _("Message could not be sent."));
1643 "Your previous message "
1644 "has not been sent. ") - 1);
1647 footer
= "</span></b>";
1649 } else if (purple_str_has_prefix(line
, data
->their_nickname
)) {
1650 if (buddy
!= NULL
) {
1651 const char *alias
= purple_buddy_get_alias(buddy
);
1653 if (alias
!= NULL
) {
1654 line
+= strlen(data
->their_nickname
) + 2;
1655 g_string_append_printf(formatted
,
1656 "<span style=\"color: #A82F2F;\">"
1657 "<b>%s</b></span>: ", alias
);
1661 const char *line2
= strstr(line
, ":");
1663 const char *acct_name
;
1666 acct_name
= purple_account_get_alias(log
->account
);
1668 acct_name
= purple_account_get_username(log
->account
);
1670 g_string_append_printf(formatted
,
1671 "<span style=\"color: #16569E;\">"
1672 "<b>%s</b></span>:", acct_name
);
1677 g_string_append(formatted
, line
);
1681 g_string_free(temp
, TRUE
);
1684 g_string_append(formatted
, footer
);
1686 g_string_append(formatted
, "<br>");
1691 /* XXX: TODO: What can we do about removing \r characters?
1692 * XXX: TODO: and will that allow us to avoid this
1693 * XXX: TODO: g_strchomp(), or is that unrelated? */
1694 /* XXX: TODO: Avoid this g_strchomp() */
1695 return g_strchomp(g_string_free(formatted
, FALSE
));
1698 static int trillian_logger_size (PurpleLog
*log
)
1700 struct trillian_logger_data
*data
;
1704 g_return_val_if_fail(log
!= NULL
, 0);
1706 data
= log
->logger_data
;
1708 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
1709 return data
? data
->length
: 0;
1712 text
= trillian_logger_read(log
, NULL
);
1713 size
= strlen(text
);
1719 static void trillian_logger_finalize(PurpleLog
*log
)
1721 struct trillian_logger_data
*data
;
1723 g_return_if_fail(log
!= NULL
);
1725 data
= log
->logger_data
;
1728 g_free(data
->their_nickname
);
1732 /*****************************************************************************
1734 *****************************************************************************/
1736 /* The QIP logger doesn't write logs, only reads them. This is to include
1737 * QIP logs in the log viewer transparently.
1739 #define QIP_LOG_DELIMITER "--------------------------------------"
1740 #define QIP_LOG_IN_MESSAGE (QIP_LOG_DELIMITER "<-")
1741 #define QIP_LOG_OUT_MESSAGE (QIP_LOG_DELIMITER ">-")
1742 #define QIP_LOG_IN_MESSAGE_ESC (QIP_LOG_DELIMITER "<-")
1743 #define QIP_LOG_OUT_MESSAGE_ESC (QIP_LOG_DELIMITER ">-")
1744 #define QIP_LOG_TIMEOUT (60*60)
1746 static PurpleLogLogger
*qip_logger
;
1748 struct qip_logger_data
{
1750 char *path
; /* FIXME: Change this to use PurpleStringref like log.c:old_logger_list */
1755 static GList
*qip_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
1759 PurplePlugin
*plugin
;
1760 PurplePluginProtocolInfo
*prpl_info
;
1765 struct qip_logger_data
*data
= NULL
;
1768 gboolean prev_tm_init
= FALSE
;
1769 gboolean main_cycle
= TRUE
;
1772 char *new_line
= NULL
;
1776 g_return_val_if_fail(sn
!= NULL
, NULL
);
1777 g_return_val_if_fail(account
!= NULL
, NULL
);
1779 /* QIP only supports ICQ. */
1780 if (strcmp(account
->protocol_id
, "prpl-icq"))
1783 logdir
= purple_prefs_get_string("/plugins/core/log_reader/qip/log_directory");
1785 /* By clearing the log directory path, this logger can be (effectively) disabled. */
1786 if (!logdir
|| !*logdir
)
1789 plugin
= purple_find_prpl(purple_account_get_protocol_id(account
));
1793 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
1794 if (!prpl_info
->list_icon
)
1797 username
= g_strdup(purple_normalize(account
, account
->username
));
1798 filename
= g_strdup_printf("%s.txt", purple_normalize(account
, sn
));
1799 path
= g_build_filename(logdir
, username
, "History", filename
, NULL
);
1803 purple_debug_info("QIP logger", "Reading %s\n", path
);
1806 if (!g_file_get_contents(path
, &contents
, NULL
, &error
)) {
1807 purple_debug_error("QIP logger",
1808 "Couldn't read file %s: %s \n", path
,
1809 (error
&& error
->message
) ? error
->message
: "Unknown error");
1811 g_error_free(error
);
1817 start_log
= contents
;
1818 while (main_cycle
) {
1820 gboolean add_new_log
= FALSE
;
1823 if (purple_str_has_prefix(c
, QIP_LOG_IN_MESSAGE
) ||
1824 purple_str_has_prefix(c
, QIP_LOG_OUT_MESSAGE
)) {
1831 c
= strstr(c
, "\n");
1834 /* Find the last '(' character. */
1835 if ((tmp
= strstr(c
, "\n")) != NULL
) {
1836 while (*tmp
&& *tmp
!= '(') --tmp
;
1842 c
= g_strrstr(c
, "(");
1846 const char *timestamp
= ++c
;
1848 /* Parse the time, day, month and year */
1849 if (sscanf(timestamp
, "%u:%u:%u %u/%u/%u",
1850 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
,
1851 &tm
.tm_mday
, &tm
.tm_mon
, &tm
.tm_year
) != 6) {
1853 purple_debug_error("QIP logger list",
1854 "Parsing timestamp error\n");
1859 /* Let the C library deal with
1860 * daylight savings time. */
1863 if (!prev_tm_init
) {
1865 prev_tm_init
= TRUE
;
1867 add_new_log
= difftime(mktime(&tm
), mktime(&prev_tm
)) > QIP_LOG_TIMEOUT
;
1879 if (add_new_log
&& prev_tm_init
) {
1883 data
= g_new0(struct qip_logger_data
, 1);
1884 data
->path
= g_strdup(path
);
1885 data
->length
= new_line
- start_log
;
1886 data
->offset
= offset
;
1887 offset
+= data
->length
;
1888 purple_debug_info("QIP logger list",
1889 "Creating log: path = (%s); length = (%d); offset = (%d)\n",
1890 data
->path
, data
->length
, data
->offset
);
1892 /* XXX: Look into this later... Should we pass in a struct tm? */
1893 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
,
1894 NULL
, mktime(&prev_tm
), NULL
);
1896 log
->logger
= qip_logger
;
1897 log
->logger_data
= data
;
1899 list
= g_list_prepend(list
, log
);
1902 start_log
= new_line
;
1907 c
= strstr(c
, "\n");
1914 return g_list_reverse(list
);
1917 static char *qip_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
)
1919 struct qip_logger_data
*data
;
1930 *flags
= PURPLE_LOG_READ_NO_NEWLINE
;
1932 g_return_val_if_fail(log
!= NULL
, g_strdup(""));
1934 data
= log
->logger_data
;
1936 g_return_val_if_fail(data
->path
!= NULL
, g_strdup(""));
1937 g_return_val_if_fail(data
->length
> 0, g_strdup(""));
1939 file
= g_fopen(data
->path
, "rb");
1940 g_return_val_if_fail(file
!= NULL
, g_strdup(""));
1942 contents
= g_malloc(data
->length
+ 2);
1944 fseek(file
, data
->offset
, SEEK_SET
);
1945 data
->length
= fread(contents
, 1, data
->length
, file
);
1948 contents
[data
->length
] = '\n';
1949 contents
[data
->length
+ 1] = '\0';
1951 /* Convert file contents from Cp1251 to UTF-8 codeset */
1953 if (!(utf8_string
= g_convert(contents
, -1, "UTF-8", "Cp1251", NULL
, NULL
, &error
))) {
1954 purple_debug_error("QIP logger",
1955 "Couldn't convert file %s to UTF-8: %s\n", data
->path
,
1956 (error
&& error
->message
) ? error
->message
: "Unknown error");
1958 g_error_free(error
);
1960 return g_strdup("");
1964 contents
= g_markup_escape_text(utf8_string
, -1);
1965 g_free(utf8_string
);
1967 buddy
= purple_find_buddy(log
->account
, log
->name
);
1969 /* Apply formatting... */
1970 formatted
= g_string_sized_new(data
->length
+ 2);
1975 gboolean is_in_message
= FALSE
;
1977 if (purple_str_has_prefix(line
, QIP_LOG_IN_MESSAGE_ESC
) ||
1978 purple_str_has_prefix(line
, QIP_LOG_OUT_MESSAGE_ESC
)) {
1981 const char *buddy_name
;
1983 is_in_message
= purple_str_has_prefix(line
, QIP_LOG_IN_MESSAGE_ESC
);
1986 c
= strstr(c
, "\n");
1988 /* XXX: Do we need buddy_name when we have buddy->alias? */
1991 /* Find the last '(' character. */
1992 if ((tmp
= strstr(c
, "\n")) != NULL
) {
1993 while (*tmp
&& *tmp
!= '(') --tmp
;
1999 c
= g_strrstr(c
, "(");
2003 const char *timestamp
= c
;
2010 /* Parse the time, day, month and year */
2011 if (sscanf(timestamp
, "%u:%u:%u",
2012 &hour
, &min
, &sec
) != 3) {
2013 purple_debug_error("QIP logger read",
2014 "Parsing timestamp error\n");
2016 g_string_append(formatted
, "<font size=\"2\">");
2017 /* TODO: Figure out if we can do anything more locale-independent. */
2018 g_string_append_printf(formatted
,
2019 "(%u:%02u:%02u) %cM ", hour
% 12,
2020 min
, sec
, (hour
>= 12) ? 'P': 'A');
2021 g_string_append(formatted
, "</font> ");
2023 if (is_in_message
) {
2024 const char *alias
= NULL
;
2026 if (buddy_name
!= NULL
&& buddy
!= NULL
&&
2027 (alias
= purple_buddy_get_alias(buddy
)))
2029 g_string_append_printf(formatted
,
2030 "<span style=\"color: #A82F2F;\">"
2031 "<b>%s</b></span>: ", alias
);
2034 const char *acct_name
;
2035 acct_name
= purple_account_get_alias(log
->account
);
2037 acct_name
= purple_account_get_username(log
->account
);
2039 g_string_append_printf(formatted
,
2040 "<span style=\"color: #16569E;\">"
2041 "<b>%s</b></span>: ", acct_name
);
2045 c
= strstr(c
, "\n");
2050 if ((c
= strstr(c
, "\n")))
2053 if (line
[0] != '\n' && line
[0] != '\r') {
2055 g_string_append(formatted
, line
);
2056 g_string_append(formatted
, "<br>");
2065 /* XXX: TODO: Avoid this g_strchomp() */
2066 return g_strchomp(g_string_free(formatted
, FALSE
));
2069 static int qip_logger_size (PurpleLog
*log
)
2071 struct qip_logger_data
*data
;
2075 g_return_val_if_fail(log
!= NULL
, 0);
2077 data
= log
->logger_data
;
2079 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
2080 return data
? data
->length
: 0;
2083 text
= qip_logger_read(log
, NULL
);
2084 size
= strlen(text
);
2090 static void qip_logger_finalize(PurpleLog
*log
)
2092 struct qip_logger_data
*data
;
2094 g_return_if_fail(log
!= NULL
);
2096 data
= log
->logger_data
;
2102 /*************************************************************************
2104 *************************************************************************/
2106 /* The aMSN logger doesn't write logs, only reads them. This is to include
2107 * aMSN logs in the log viewer transparently.
2110 static PurpleLogLogger
*amsn_logger
;
2112 struct amsn_logger_data
{
2118 #define AMSN_LOG_CONV_START "|\"LRED[Conversation started on "
2119 #define AMSN_LOG_CONV_END "|\"LRED[You have closed the window on "
2120 #define AMSN_LOG_CONV_EXTRA "01 Aug 2001 00:00:00]"
2122 static GList
*amsn_logger_parse_file(char *filename
, const char *sn
, PurpleAccount
*account
)
2127 struct amsn_logger_data
*data
;
2130 purple_debug_info("aMSN logger", "Reading %s\n", filename
);
2132 if (!g_file_get_contents(filename
, &contents
, NULL
, &error
)) {
2133 purple_debug_error("aMSN logger",
2134 "Couldn't read file %s: %s \n", filename
,
2135 (error
&& error
->message
) ?
2136 error
->message
: "Unknown error");
2138 g_error_free(error
);
2141 gboolean found_start
= FALSE
;
2142 char *start_log
= c
;
2146 if (purple_str_has_prefix(c
, AMSN_LOG_CONV_START
)) {
2148 if (sscanf(c
+ strlen(AMSN_LOG_CONV_START
),
2149 "%u %3s %u %u:%u:%u",
2150 &tm
.tm_mday
, (char*)&month
, &tm
.tm_year
,
2151 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
) != 6) {
2152 found_start
= FALSE
;
2153 purple_debug_error("aMSN logger",
2154 "Error parsing start date for %s\n",
2159 /* Let the C library deal with
2160 * daylight savings time.
2163 tm
.tm_mon
= get_month(month
);
2166 offset
= c
- contents
;
2169 } else if (purple_str_has_prefix(c
, AMSN_LOG_CONV_END
) && found_start
) {
2170 data
= g_new0(struct amsn_logger_data
, 1);
2171 data
->path
= g_strdup(filename
);
2172 data
->offset
= offset
;
2173 data
->length
= c
- start_log
2174 + strlen(AMSN_LOG_CONV_END
)
2175 + strlen(AMSN_LOG_CONV_EXTRA
);
2176 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, mktime(&tm
), NULL
);
2177 log
->logger
= amsn_logger
;
2178 log
->logger_data
= data
;
2179 list
= g_list_prepend(list
, log
);
2180 found_start
= FALSE
;
2182 purple_debug_info("aMSN logger",
2187 sn
, data
->path
, data
->offset
, data
->length
);
2189 c
= strstr(c
, "\n");
2193 /* I've seen the file end without the AMSN_LOG_CONV_END bit */
2195 data
= g_new0(struct amsn_logger_data
, 1);
2196 data
->path
= g_strdup(filename
);
2197 data
->offset
= offset
;
2198 data
->length
= c
- start_log
2199 + strlen(AMSN_LOG_CONV_END
)
2200 + strlen(AMSN_LOG_CONV_EXTRA
);
2201 log
= purple_log_new(PURPLE_LOG_IM
, sn
, account
, NULL
, mktime(&tm
), NULL
);
2202 log
->logger
= amsn_logger
;
2203 log
->logger_data
= data
;
2204 list
= g_list_prepend(list
, log
);
2205 found_start
= FALSE
;
2207 purple_debug_info("aMSN logger",
2212 sn
, data
->path
, data
->offset
, data
->length
);
2220 /* `log_dir`/username@hotmail.com/logs/buddyname@hotmail.com.log */
2221 /* `log_dir`/username@hotmail.com/logs/Month Year/buddyname@hotmail.com.log */
2222 static GList
*amsn_logger_list(PurpleLogType type
, const char *sn
, PurpleAccount
*account
)
2233 logdir
= purple_prefs_get_string("/plugins/core/log_reader/amsn/log_directory");
2235 /* By clearing the log directory path, this logger can be (effectively) disabled. */
2236 if (!logdir
|| !*logdir
)
2239 /* aMSN only works with MSN/WLM */
2240 if (strcmp(account
->protocol_id
, "prpl-msn"))
2243 username
= g_strdup(purple_normalize(account
, account
->username
));
2244 buddy_log
= g_strdup_printf("%s.log", purple_normalize(account
, sn
));
2245 log_path
= g_build_filename(logdir
, username
, "logs", NULL
);
2247 /* First check in the top-level */
2248 filename
= g_build_filename(log_path
, buddy_log
, NULL
);
2249 if (g_file_test(filename
, G_FILE_TEST_EXISTS
))
2250 list
= amsn_logger_parse_file(filename
, sn
, account
);
2254 /* Check in previous months */
2255 dir
= g_dir_open(log_path
, 0, NULL
);
2257 while ((name
= g_dir_read_name(dir
)) != NULL
) {
2258 filename
= g_build_filename(log_path
, name
, buddy_log
, NULL
);
2259 if (g_file_test(filename
, G_FILE_TEST_EXISTS
))
2260 list
= g_list_concat(list
, amsn_logger_parse_file(filename
, sn
, account
));
2268 /* New versions use 'friendlier' directory names */
2269 purple_util_chrreplace(username
, '@', '_');
2270 purple_util_chrreplace(username
, '.', '_');
2272 log_path
= g_build_filename(logdir
, username
, "logs", NULL
);
2274 /* First check in the top-level */
2275 filename
= g_build_filename(log_path
, buddy_log
, NULL
);
2276 if (g_file_test(filename
, G_FILE_TEST_EXISTS
))
2277 list
= g_list_concat(list
, amsn_logger_parse_file(filename
, sn
, account
));
2280 /* Check in previous months */
2281 dir
= g_dir_open(log_path
, 0, NULL
);
2283 while ((name
= g_dir_read_name(dir
)) != NULL
) {
2284 filename
= g_build_filename(log_path
, name
, buddy_log
, NULL
);
2285 if (g_file_test(filename
, G_FILE_TEST_EXISTS
))
2286 list
= g_list_concat(list
, amsn_logger_parse_file(filename
, sn
, account
));
2299 /* Really it's |"L, but the string's been escaped */
2300 #define AMSN_LOG_FORMAT_TAG "|"L"
2302 static char *amsn_logger_read(PurpleLog
*log
, PurpleLogReadFlags
*flags
)
2304 struct amsn_logger_data
*data
;
2310 gboolean in_span
= FALSE
;
2313 *flags
= PURPLE_LOG_READ_NO_NEWLINE
;
2315 g_return_val_if_fail(log
!= NULL
, g_strdup(""));
2317 data
= log
->logger_data
;
2319 g_return_val_if_fail(data
->path
!= NULL
, g_strdup(""));
2320 g_return_val_if_fail(data
->length
> 0, g_strdup(""));
2322 contents
= g_malloc(data
->length
+ 2);
2324 file
= g_fopen(data
->path
, "rb");
2325 g_return_val_if_fail(file
!= NULL
, g_strdup(""));
2327 fseek(file
, data
->offset
, SEEK_SET
);
2328 data
->length
= fread(contents
, 1, data
->length
, file
);
2331 contents
[data
->length
] = '\n';
2332 contents
[data
->length
+ 1] = '\0';
2334 escaped
= g_markup_escape_text(contents
, -1);
2338 formatted
= g_string_sized_new(data
->length
+ 2);
2341 while (start
&& *start
) {
2345 end
= strstr(start
, "\n");
2349 if (purple_str_has_prefix(start
, AMSN_LOG_FORMAT_TAG
) && in_span
) {
2350 /* New format for this line */
2351 g_string_append(formatted
, "</span><br>");
2353 } else if (start
!= contents
) {
2354 /* Continue format from previous line */
2355 g_string_append(formatted
, "<br>");
2358 tag
= strstr(start
, AMSN_LOG_FORMAT_TAG
);
2360 g_string_append_len(formatted
, old_tag
, tag
- old_tag
);
2361 tag
+= strlen(AMSN_LOG_FORMAT_TAG
);
2363 g_string_append(formatted
, "</span>");
2367 /* |"LCxxxxxx is a hex colour */
2369 strncpy(colour
, tag
+ 1, 6);
2371 g_string_append_printf(formatted
, "<span style=\"color: #%s;\">", colour
);
2372 /* This doesn't appear to work? */
2373 /* g_string_append_printf(formatted, "<span style=\"color: #%6s;\">", tag + 1); */
2375 old_tag
= tag
+ 7; /* C + xxxxxx */
2377 /* |"Lxxx is a 3-digit colour code */
2378 if (purple_str_has_prefix(tag
, "RED")) {
2379 g_string_append(formatted
, "<span style=\"color: red;\">");
2381 } else if (purple_str_has_prefix(tag
, "GRA")) {
2382 g_string_append(formatted
, "<span style=\"color: gray;\">");
2384 } else if (purple_str_has_prefix(tag
, "NOR")) {
2385 g_string_append(formatted
, "<span style=\"color: black;\">");
2387 } else if (purple_str_has_prefix(tag
, "ITA")) {
2388 g_string_append(formatted
, "<span style=\"color: blue;\">");
2390 } else if (purple_str_has_prefix(tag
, "GRE")) {
2391 g_string_append(formatted
, "<span style=\"color: darkgreen;\">");
2394 purple_debug_info("aMSN logger", "Unknown colour format: %3s\n", tag
);
2398 tag
= strstr(tag
, AMSN_LOG_FORMAT_TAG
);
2400 g_string_append(formatted
, old_tag
);
2404 g_string_append(formatted
, "</span>");
2408 return g_string_free(formatted
, FALSE
);
2411 static int amsn_logger_size(PurpleLog
*log
)
2413 struct amsn_logger_data
*data
;
2417 g_return_val_if_fail(log
!= NULL
, 0);
2419 data
= log
->logger_data
;
2421 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
2422 return data
? data
->length
: 0;
2425 text
= amsn_logger_read(log
, NULL
);
2426 size
= strlen(text
);
2432 static void amsn_logger_finalize(PurpleLog
*log
)
2434 struct amsn_logger_data
*data
;
2436 g_return_if_fail(log
!= NULL
);
2438 data
= log
->logger_data
;
2443 /*****************************************************************************
2445 *****************************************************************************/
2448 init_plugin(PurplePlugin
*plugin
)
2453 static void log_reader_init_prefs(void) {
2457 gboolean found
= FALSE
;
2460 purple_prefs_add_none("/plugins/core/log_reader");
2463 /* Add general preferences. */
2465 purple_prefs_add_bool("/plugins/core/log_reader/fast_sizes", FALSE
);
2466 purple_prefs_add_bool("/plugins/core/log_reader/use_name_heuristics", TRUE
);
2469 /* Add Adium log directory preference. */
2470 purple_prefs_add_none("/plugins/core/log_reader/adium");
2472 /* Calculate default Adium log directory. */
2474 purple_prefs_add_string("/plugins/core/log_reader/adium/log_directory", "");
2476 path
= g_build_filename(purple_home_dir(), "Library", "Application Support",
2477 "Adium 2.0", "Users", "Default", "Logs", NULL
);
2478 purple_prefs_add_string("/plugins/core/log_reader/adium/log_directory", path
);
2483 /* Add Fire log directory preference. */
2484 purple_prefs_add_none("/plugins/core/log_reader/fire");
2486 /* Calculate default Fire log directory. */
2488 purple_prefs_add_string("/plugins/core/log_reader/fire/log_directory", "");
2490 path
= g_build_filename(purple_home_dir(), "Library", "Application Support",
2491 "Fire", "Sessions", NULL
);
2492 purple_prefs_add_string("/plugins/core/log_reader/fire/log_directory", path
);
2497 /* Add Messenger Plus! log directory preference. */
2498 purple_prefs_add_none("/plugins/core/log_reader/messenger_plus");
2500 /* Calculate default Messenger Plus! log directory. */
2503 folder
= wpurple_get_special_folder(CSIDL_PERSONAL
);
2505 path
= g_build_filename(folder
, "My Chat Logs", NULL
);
2509 path
= g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT
,
2510 "Documents and Settings", g_get_user_name(),
2511 "My Documents", "My Chat Logs", NULL
);
2513 purple_prefs_add_string("/plugins/core/log_reader/messenger_plus/log_directory", path
? path
: "");
2517 /* Add MSN Messenger log directory preference. */
2518 purple_prefs_add_none("/plugins/core/log_reader/msn");
2520 /* Calculate default MSN message history directory. */
2523 folder
= wpurple_get_special_folder(CSIDL_PERSONAL
);
2525 path
= g_build_filename(folder
, "My Received Files", NULL
);
2529 path
= g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT
,
2530 "Documents and Settings", g_get_user_name(),
2531 "My Documents", "My Received Files", NULL
);
2533 purple_prefs_add_string("/plugins/core/log_reader/msn/log_directory", path
? path
: "");
2537 /* Add Trillian log directory preference. */
2538 purple_prefs_add_none("/plugins/core/log_reader/trillian");
2541 /* XXX: While a major hack, this is the most reliable way I could
2542 * think of to determine the Trillian installation directory.
2546 if ((folder
= wpurple_read_reg_string(HKEY_CLASSES_ROOT
, "Trillian.SkinZip\\shell\\Add\\command\\", NULL
))) {
2547 char *value
= folder
;
2550 /* Break apart buffer. */
2551 if (*value
== '"') {
2554 while (*temp
&& *temp
!= '"')
2558 while (*temp
&& *temp
!= ' ')
2564 if (purple_str_has_suffix(value
, "trillian.exe")) {
2565 value
[strlen(value
) - (sizeof("trillian.exe") - 1)] = '\0';
2566 path
= g_build_filename(value
, "users", "default", "talk.ini", NULL
);
2572 char *folder
= wpurple_get_special_folder(CSIDL_PROGRAM_FILES
);
2574 path
= g_build_filename(folder
, "Trillian",
2575 "users", "default", "talk.ini", NULL
);
2581 /* Read talk.ini file to find the log directory. */
2582 GError
*error
= NULL
;
2584 #if 0 && GLIB_CHECK_VERSION(2,6,0) /* FIXME: Not tested yet. */
2587 purple_debug_info("Trillian talk.ini read", "Reading %s\n", path
);
2590 if (!g_key_file_load_from_file(key_file
, path
, G_KEY_FILE_NONE
, GError
&error
)) {
2591 purple_debug_error("Trillian talk.ini read",
2592 "Error reading talk.ini\n");
2594 g_error_free(error
);
2596 char *logdir
= g_key_file_get_string(key_file
, "Logging", "Directory", &error
);
2598 purple_debug_error("Trillian talk.ini read",
2599 "Error reading Directory value from Logging section\n");
2600 g_error_free(error
);
2605 purple_prefs_add_string("/plugins/core/log_reader/trillian/log_directory", logdir
);
2609 g_key_file_free(key_file
);
2611 #else /* !GLIB_CHECK_VERSION(2,6,0) */
2612 gchar
*contents
= NULL
;
2614 purple_debug_info("Trillian talk.ini read",
2615 "Reading %s\n", path
);
2616 if (!g_file_get_contents(path
, &contents
, NULL
, &error
)) {
2617 purple_debug_error("Trillian talk.ini read",
2618 "Error reading talk.ini: %s\n",
2619 (error
&& error
->message
) ? error
->message
: "Unknown error");
2621 g_error_free(error
);
2623 char *cursor
, *line
;
2624 line
= cursor
= contents
;
2626 if (*cursor
== '\n') {
2629 /* XXX: This assumes the first Directory key is under [Logging]. */
2630 if (purple_str_has_prefix(line
, "Directory=")) {
2631 line
+= (sizeof("Directory=") - 1);
2633 purple_prefs_add_string(
2634 "/plugins/core/log_reader/trillian/log_directory",
2647 #endif /* !GLIB_CHECK_VERSION(2,6,0) */
2652 folder
= wpurple_get_special_folder(CSIDL_PROGRAM_FILES
);
2654 path
= g_build_filename(folder
, "Trillian", "users",
2655 "default", "logs", NULL
);
2659 purple_prefs_add_string(
2660 "/plugins/core/log_reader/trillian/log_directory", path
? path
: "");
2663 #else /* !defined(_WIN32) */
2664 /* TODO: At some point, this could attempt to parse talk.ini
2665 * TODO: from the default Trillian install directory on the
2666 * TODO: Windows mount point. */
2668 /* Calculate default Trillian log directory. */
2669 path
= g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT
,
2670 "Program Files", "Trillian", "users",
2671 "default", "logs", NULL
);
2672 purple_prefs_add_string(
2673 "/plugins/core/log_reader/trillian/log_directory", path
);
2677 /* Add QIP log directory preference. */
2678 purple_prefs_add_none("/plugins/core/log_reader/qip");
2680 /* Calculate default QIP log directory. */
2683 folder
= wpurple_get_special_folder(CSIDL_PROGRAM_FILES
);
2685 path
= g_build_filename(folder
, "QIP", "Users", NULL
);
2689 path
= g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT
,
2690 "Program Files", "QIP", "Users", NULL
);
2692 purple_prefs_add_string("/plugins/core/log_reader/qip/log_directory", path
? path
: "");
2695 /* Add aMSN Messenger log directory preference. */
2696 purple_prefs_add_none("/plugins/core/log_reader/amsn");
2698 /* Calculate default aMSN log directory. */
2701 folder
= wpurple_get_special_folder(CSIDL_PROFILE
); /* Silly aMSN, not using CSIDL_APPDATA */
2703 path
= g_build_filename(folder
, "amsn", NULL
);
2707 path
= g_build_filename(purple_home_dir(), ".amsn", NULL
);
2709 purple_prefs_add_string("/plugins/core/log_reader/amsn/log_directory", path
? path
: "");
2714 plugin_load(PurplePlugin
*plugin
)
2716 g_return_val_if_fail(plugin
!= NULL
, FALSE
);
2718 log_reader_init_prefs();
2720 /* The names of IM clients are marked for translation at the request of
2721 translators who wanted to transliterate them. Many translators
2722 choose to leave them alone. Choose what's best for your language. */
2723 adium_logger
= purple_log_logger_new("adium", _("Adium"), 6,
2726 adium_logger_finalize
,
2730 purple_log_logger_add(adium_logger
);
2733 /* The names of IM clients are marked for translation at the request of
2734 translators who wanted to transliterate them. Many translators
2735 choose to leave them alone. Choose what's best for your language. */
2736 fire_logger
= purple_log_logger_new("fire", _("Fire"), 6,
2739 fire_logger_finalize
,
2743 purple_log_logger_add(fire_logger
);
2745 /* The names of IM clients are marked for translation at the request of
2746 translators who wanted to transliterate them. Many translators
2747 choose to leave them alone. Choose what's best for your language. */
2748 messenger_plus_logger
= purple_log_logger_new("messenger_plus", _("Messenger Plus!"), 6,
2751 messenger_plus_logger_finalize
,
2752 messenger_plus_logger_list
,
2753 messenger_plus_logger_read
,
2754 messenger_plus_logger_size
);
2755 purple_log_logger_add(messenger_plus_logger
);
2759 /* The names of IM clients are marked for translation at the request of
2760 translators who wanted to transliterate them. Many translators
2761 choose to leave them alone. Choose what's best for your language. */
2762 qip_logger
= purple_log_logger_new("qip", _("QIP"), 6,
2765 qip_logger_finalize
,
2769 purple_log_logger_add(qip_logger
);
2771 /* The names of IM clients are marked for translation at the request of
2772 translators who wanted to transliterate them. Many translators
2773 choose to leave them alone. Choose what's best for your language. */
2774 msn_logger
= purple_log_logger_new("msn", _("MSN Messenger"), 6,
2777 msn_logger_finalize
,
2781 purple_log_logger_add(msn_logger
);
2783 /* The names of IM clients are marked for translation at the request of
2784 translators who wanted to transliterate them. Many translators
2785 choose to leave them alone. Choose what's best for your language. */
2786 trillian_logger
= purple_log_logger_new("trillian", _("Trillian"), 6,
2789 trillian_logger_finalize
,
2790 trillian_logger_list
,
2791 trillian_logger_read
,
2792 trillian_logger_size
);
2793 purple_log_logger_add(trillian_logger
);
2795 /* The names of IM clients are marked for translation at the request of
2796 translators who wanted to transliterate them. Many translators
2797 choose to leave them alone. Choose what's best for your language. */
2798 amsn_logger
= purple_log_logger_new("amsn", _("aMSN"), 6,
2801 amsn_logger_finalize
,
2805 purple_log_logger_add(amsn_logger
);
2811 plugin_unload(PurplePlugin
*plugin
)
2813 g_return_val_if_fail(plugin
!= NULL
, FALSE
);
2815 purple_log_logger_remove(adium_logger
);
2816 purple_log_logger_free(adium_logger
);
2817 adium_logger
= NULL
;
2820 purple_log_logger_remove(fire_logger
);
2821 purple_log_logger_free(fire_logger
);
2824 purple_log_logger_remove(messenger_plus_logger
);
2825 purple_log_logger_free(messenger_plus_logger
);
2826 messenger_plus_logger
= NULL
;
2829 purple_log_logger_remove(msn_logger
);
2830 purple_log_logger_free(msn_logger
);
2833 purple_log_logger_remove(trillian_logger
);
2834 purple_log_logger_free(trillian_logger
);
2835 trillian_logger
= NULL
;
2837 purple_log_logger_remove(qip_logger
);
2838 purple_log_logger_free(qip_logger
);
2841 purple_log_logger_remove(amsn_logger
);
2842 purple_log_logger_free(amsn_logger
);
2848 static PurplePluginPrefFrame
*
2849 get_plugin_pref_frame(PurplePlugin
*plugin
)
2851 PurplePluginPrefFrame
*frame
;
2852 PurplePluginPref
*ppref
;
2854 g_return_val_if_fail(plugin
!= NULL
, FALSE
);
2856 frame
= purple_plugin_pref_frame_new();
2859 /* Add general preferences. */
2861 ppref
= purple_plugin_pref_new_with_label(_("General Log Reading Configuration"));
2862 purple_plugin_pref_frame_add(frame
, ppref
);
2864 ppref
= purple_plugin_pref_new_with_name_and_label(
2865 "/plugins/core/log_reader/fast_sizes", _("Fast size calculations"));
2866 purple_plugin_pref_frame_add(frame
, ppref
);
2868 ppref
= purple_plugin_pref_new_with_name_and_label(
2869 "/plugins/core/log_reader/use_name_heuristics", _("Use name heuristics"));
2870 purple_plugin_pref_frame_add(frame
, ppref
);
2873 /* Add Log Directory preferences. */
2875 ppref
= purple_plugin_pref_new_with_label(_("Log Directory"));
2876 purple_plugin_pref_frame_add(frame
, ppref
);
2878 ppref
= purple_plugin_pref_new_with_name_and_label(
2879 "/plugins/core/log_reader/adium/log_directory", _("Adium"));
2880 purple_plugin_pref_frame_add(frame
, ppref
);
2883 ppref
= purple_plugin_pref_new_with_name_and_label(
2884 "/plugins/core/log_reader/fire/log_directory", _("Fire"));
2885 purple_plugin_pref_frame_add(frame
, ppref
);
2887 ppref
= purple_plugin_pref_new_with_name_and_label(
2888 "/plugins/core/log_reader/messenger_plus/log_directory", _("Messenger Plus!"));
2889 purple_plugin_pref_frame_add(frame
, ppref
);
2892 ppref
= purple_plugin_pref_new_with_name_and_label(
2893 "/plugins/core/log_reader/qip/log_directory", _("QIP"));
2894 purple_plugin_pref_frame_add(frame
, ppref
);
2896 ppref
= purple_plugin_pref_new_with_name_and_label(
2897 "/plugins/core/log_reader/msn/log_directory", _("MSN Messenger"));
2898 purple_plugin_pref_frame_add(frame
, ppref
);
2900 ppref
= purple_plugin_pref_new_with_name_and_label(
2901 "/plugins/core/log_reader/trillian/log_directory", _("Trillian"));
2902 purple_plugin_pref_frame_add(frame
, ppref
);
2904 ppref
= purple_plugin_pref_new_with_name_and_label(
2905 "/plugins/core/log_reader/amsn/log_directory", _("aMSN"));
2906 purple_plugin_pref_frame_add(frame
, ppref
);
2911 static PurplePluginUiInfo prefs_info
= {
2912 get_plugin_pref_frame
,
2913 0, /* page_num (reserved) */
2914 NULL
, /* frame (reserved) */
2923 static PurplePluginInfo info
=
2925 PURPLE_PLUGIN_MAGIC
,
2926 PURPLE_MAJOR_VERSION
,
2927 PURPLE_MINOR_VERSION
,
2928 PURPLE_PLUGIN_STANDARD
, /**< type */
2929 NULL
, /**< ui_requirement */
2931 NULL
, /**< dependencies */
2932 PURPLE_PRIORITY_DEFAULT
, /**< priority */
2933 "core-log_reader", /**< id */
2934 N_("Log Reader"), /**< name */
2935 DISPLAY_VERSION
, /**< version */
2938 N_("Includes other IM clients' logs in the "
2942 N_("When viewing logs, this plugin will include "
2943 "logs from other IM clients. Currently, this "
2944 "includes Adium, MSN Messenger, aMSN, and "
2946 "WARNING: This plugin is still alpha code and "
2947 "may crash frequently. Use it at your own risk!"),
2949 "Richard Laager <rlaager@pidgin.im>", /**< author */
2950 PURPLE_WEBSITE
, /**< homepage */
2951 plugin_load
, /**< load */
2952 plugin_unload
, /**< unload */
2953 NULL
, /**< destroy */
2954 NULL
, /**< ui_info */
2955 NULL
, /**< extra_info */
2956 &prefs_info
, /**< prefs_info */
2957 NULL
, /**< actions */
2966 PURPLE_INIT_PLUGIN(log_reader
, init_plugin
, info
)