Mercurial > pidgin
comparison src/protocols/oscar/oscar.c @ 4151:1a5dcfa1823e
[gaim-migrate @ 4377]
Why do I make these things so long? I'm defective, that's why.
Mr. Walp pointed out a problem with "allow only peeps in my buddy list"
for ICQ, so I fixed that. One important problem: If you set your
permdeny to "allow only peeps in my buddy list," and then add or remove
someone from your buddy list, it will not update the allow/deny list on
the server. And that's a bad thing.
I changed an error message string or 4 in oscar.c for various reasons.
1) I feel that "he/she" is much better than "it." If you disagree,
please let me know, because I'm not sure of the correct phrasing.
2) There is only 1 unknown reason, it just applies to multiple messages.
I shuffled some of the clientauto functions around in oscar.c to make
it more uniform. I intend to look into why status messages aren't
working well soon.
I added some semblance of more advanced ICQ info support to libfaim.
There's also a bit of support in oscar.c for it, but making it display
itself nicely will take a little work, so I'll do it later.
A patch from the good Mr. Blanton taking out a non-ascii character
from oscar.c (my bad).
A patch from the good Mr. Blanton adding support for i18n to away
messages and aim profile info. Questions for the good Mr. Blanton:
1) Line 59 of info.c, in the first half of that if statement, should
profile_len also be &&'ed in with the other 2?
2) I changed a gaim_parse_user_info so that it works for non-unicode
away messages and profiles. Or so I think.
3) I changed little bits of your patch to appease my annoyingness,
so it might not cvs update cleanly for you. Sorry.
I organized the ChangeLog entries for 0.60. I tried to put stuff
that I thought was more important near the top of each category.
Please change stuff around, because I'm pretty sure it could be
better.
Breathe in, breathe out, breathe in, breathe out...
Tied to a wheel...
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Sun, 29 Dec 2002 17:12:05 +0000 |
parents | 69c049c48a96 |
children | 57bf2c1b76e0 |
comparison
equal
deleted
inserted
replaced
4150:1bd663beada5 | 4151:1a5dcfa1823e |
---|---|
366 static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...); | 366 static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...); |
367 static int gaim_memrequest (aim_session_t *, aim_frame_t *, ...); | 367 static int gaim_memrequest (aim_session_t *, aim_frame_t *, ...); |
368 static int gaim_selfinfo (aim_session_t *, aim_frame_t *, ...); | 368 static int gaim_selfinfo (aim_session_t *, aim_frame_t *, ...); |
369 static int gaim_offlinemsg (aim_session_t *, aim_frame_t *, ...); | 369 static int gaim_offlinemsg (aim_session_t *, aim_frame_t *, ...); |
370 static int gaim_offlinemsgdone (aim_session_t *, aim_frame_t *, ...); | 370 static int gaim_offlinemsgdone (aim_session_t *, aim_frame_t *, ...); |
371 static int gaim_simpleinfo (aim_session_t *, aim_frame_t *, ...); | 371 static int gaim_icqsimpleinfo (aim_session_t *, aim_frame_t *, ...); |
372 static int gaim_icqallinfo (aim_session_t *, aim_frame_t *, ...); | |
372 static int gaim_popup (aim_session_t *, aim_frame_t *, ...); | 373 static int gaim_popup (aim_session_t *, aim_frame_t *, ...); |
373 static int gaim_ssi_parserights (aim_session_t *, aim_frame_t *, ...); | 374 static int gaim_ssi_parserights (aim_session_t *, aim_frame_t *, ...); |
374 static int gaim_ssi_parselist (aim_session_t *, aim_frame_t *, ...); | 375 static int gaim_ssi_parselist (aim_session_t *, aim_frame_t *, ...); |
375 | 376 |
376 static int gaim_directim_initiate(aim_session_t *, aim_frame_t *, ...); | 377 static int gaim_directim_initiate(aim_session_t *, aim_frame_t *, ...); |
384 static void oscar_file_transfer_cancel(struct gaim_connection *, | 385 static void oscar_file_transfer_cancel(struct gaim_connection *, |
385 struct file_transfer *); | 386 struct file_transfer *); |
386 static int oscar_sendfile_request(aim_session_t *sess, | 387 static int oscar_sendfile_request(aim_session_t *sess, |
387 struct oscar_file_transfer *oft); | 388 struct oscar_file_transfer *oft); |
388 static int oscar_sendfile_timeout(aim_session_t *sess, aim_frame_t *fr, ...); | 389 static int oscar_sendfile_timeout(aim_session_t *sess, aim_frame_t *fr, ...); |
390 | |
391 static fu32_t check_encoding(const char *utf8); | |
392 static fu32_t parse_encoding(const char *enc); | |
389 | 393 |
390 static char *msgerrreason[] = { | 394 static char *msgerrreason[] = { |
391 N_("Invalid error"), | 395 N_("Invalid error"), |
392 N_("Invalid SNAC"), | 396 N_("Invalid SNAC"), |
393 N_("Rate to host"), | 397 N_("Rate to host"), |
882 aim_conn_addhandler(sess, bosconn, 0x0001, 0x001f, gaim_memrequest, 0); | 886 aim_conn_addhandler(sess, bosconn, 0x0001, 0x001f, gaim_memrequest, 0); |
883 aim_conn_addhandler(sess, bosconn, 0x0001, 0x000f, gaim_selfinfo, 0); | 887 aim_conn_addhandler(sess, bosconn, 0x0001, 0x000f, gaim_selfinfo, 0); |
884 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0); | 888 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0); |
885 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE, gaim_offlinemsgdone, 0); | 889 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE, gaim_offlinemsgdone, 0); |
886 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_POP, 0x0002, gaim_popup, 0); | 890 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_POP, 0x0002, gaim_popup, 0); |
887 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_SIMPLEINFO, gaim_simpleinfo, 0); | 891 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_SIMPLEINFO, gaim_icqsimpleinfo, 0); |
892 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_ALLINFO, gaim_icqallinfo, 0); | |
888 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_RIGHTSINFO, gaim_ssi_parserights, 0); | 893 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_RIGHTSINFO, gaim_ssi_parserights, 0); |
889 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_LIST, gaim_ssi_parselist, 0); | 894 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_LIST, gaim_ssi_parselist, 0); |
890 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_NOLIST, gaim_ssi_parselist, 0); | 895 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_NOLIST, gaim_ssi_parselist, 0); |
891 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_MSGTIMEOUT, oscar_sendfile_timeout, 0); | 896 aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_MSGTIMEOUT, oscar_sendfile_timeout, 0); |
892 | 897 |
2071 return 1; | 2076 return 1; |
2072 | 2077 |
2073 debug_printf("Received a channel 4 message of type %d.\n", args->type); | 2078 debug_printf("Received a channel 4 message of type %d.\n", args->type); |
2074 | 2079 |
2075 /* Split up the message at the delimeter character, then convert each string to UTF-8 */ | 2080 /* Split up the message at the delimeter character, then convert each string to UTF-8 */ |
2076 msg1 = g_strsplit(args->msg, "þ", 0); | 2081 msg1 = g_strsplit(args->msg, "\177", 0); |
2077 msg2 = (gchar **)g_malloc(10*sizeof(gchar *)); /* AAA */ | 2082 msg2 = (gchar **)g_malloc(10*sizeof(gchar *)); /* AAA */ |
2078 for (i=0; msg1[i]; i++) { | 2083 for (i=0; msg1[i]; i++) { |
2079 strip_linefeed(msg1[i]); | 2084 strip_linefeed(msg1[i]); |
2080 msg2[i] = g_convert(msg1[i], strlen(msg1[i]), "UTF-8", "ISO-8859-1", NULL, NULL, &err); | 2085 msg2[i] = g_convert(msg1[i], strlen(msg1[i]), "UTF-8", "ISO-8859-1", NULL, NULL, &err); |
2081 if (err) | 2086 if (err) |
2154 } break; | 2159 } break; |
2155 | 2160 |
2156 case 0x0013: { /* Someone has sent you some ICQ contacts */ | 2161 case 0x0013: { /* Someone has sent you some ICQ contacts */ |
2157 int i, num; | 2162 int i, num; |
2158 gchar **text; | 2163 gchar **text; |
2159 text = g_strsplit(args->msg, "þ", 0); | 2164 text = g_strsplit(args->msg, "\177", 0); |
2160 if (text) { | 2165 if (text) { |
2161 num = 0; | 2166 num = 0; |
2162 for (i=0; i<strlen(text[0]); i++) | 2167 for (i=0; i<strlen(text[0]); i++) |
2163 num = num*10 + text[0][i]-48; | 2168 num = num*10 + text[0][i]-48; |
2164 for (i=0; i<num; i++) { | 2169 for (i=0; i<num; i++) { |
2274 case 3: | 2279 case 3: |
2275 /* Evil Sender */ | 2280 /* Evil Sender */ |
2276 g_snprintf(buf, | 2281 g_snprintf(buf, |
2277 sizeof(buf), | 2282 sizeof(buf), |
2278 nummissed == 1 ? | 2283 nummissed == 1 ? |
2279 _("You missed %d message from %s because it was too evil.") : | 2284 _("You missed %d message from %s because he/she was too evil.") : |
2280 _("You missed %d messages from %s because they are too evil."), | 2285 _("You missed %d messages from %s because he/she was too evil."), |
2281 nummissed, | 2286 nummissed, |
2282 userinfo->sn); | 2287 userinfo->sn); |
2283 break; | 2288 break; |
2284 case 4: | 2289 case 4: |
2285 /* Evil Receiver */ | 2290 /* Evil Receiver */ |
2293 break; | 2298 break; |
2294 default: | 2299 default: |
2295 g_snprintf(buf, | 2300 g_snprintf(buf, |
2296 sizeof(buf), | 2301 sizeof(buf), |
2297 nummissed == 1 ? | 2302 nummissed == 1 ? |
2298 _("You missed %d message from %s for unknown reasons.") : | 2303 _("You missed %d message from %s for an unknown reason.") : |
2299 _("You missed %d messages from %s for unknown reasons."), | 2304 _("You missed %d messages from %s for an unknown reason."), |
2300 nummissed, | 2305 nummissed, |
2301 userinfo->sn); | 2306 userinfo->sn); |
2302 break; | 2307 break; |
2303 } | 2308 } |
2304 do_error_dialog(buf, NULL, GAIM_ERROR); | 2309 do_error_dialog(buf, NULL, GAIM_ERROR); |
2324 return g_strdup_printf("Invisible"); | 2329 return g_strdup_printf("Invisible"); |
2325 else | 2330 else |
2326 return g_strdup_printf("Online"); | 2331 return g_strdup_printf("Online"); |
2327 } | 2332 } |
2328 | 2333 |
2329 static int gaim_parse_clientauto_rend(aim_session_t *sess, | 2334 static int gaim_parse_clientauto_ch2(aim_session_t *sess, const char *who, int reason, const char *cookie) { |
2330 const char *who, int reason, const char *cookie) { | |
2331 struct gaim_connection *gc = sess->aux_data; | 2335 struct gaim_connection *gc = sess->aux_data; |
2332 struct oscar_file_transfer *oft; | |
2333 char *buf; | |
2334 | 2336 |
2335 switch (reason) { | 2337 switch (reason) { |
2336 case 3: /* Decline sendfile. */ | 2338 case 3: { /* Decline sendfile. */ |
2337 oft = find_oft_by_cookie(gc, cookie); | 2339 struct oscar_file_transfer *oft = find_oft_by_cookie(gc, cookie); |
2338 | 2340 |
2339 if (oft) { | 2341 if (oft) { |
2340 buf = g_strdup_printf(_("%s has declined to receive a file from %s.\n"), | 2342 char *buf = g_strdup_printf(_("%s has declined to receive a file from %s.\n"), |
2341 who, gc->username); | 2343 who, gc->username); |
2342 transfer_abort(oft->xfer, buf); | 2344 transfer_abort(oft->xfer, buf); |
2343 g_free(buf); | 2345 g_free(buf); |
2344 oscar_file_transfer_disconnect(sess, oft->conn); | 2346 oscar_file_transfer_disconnect(sess, oft->conn); |
2345 } | 2347 } |
2346 break; | 2348 } break; |
2347 default: | 2349 |
2350 default: { | |
2348 debug_printf("Received an unknown rendezvous client auto-response from %s. Type 0x%04x\n", who, reason); | 2351 debug_printf("Received an unknown rendezvous client auto-response from %s. Type 0x%04x\n", who, reason); |
2352 } | |
2349 | 2353 |
2350 } | 2354 } |
2351 | 2355 |
2352 return 0; | 2356 return 0; |
2353 } | 2357 } |
2354 | 2358 |
2359 static int gaim_parse_clientauto_ch4(aim_session_t *sess, char *who, int reason, int state, char *msg) { | |
2360 struct gaim_connection *gc = sess->aux_data; | |
2361 | |
2362 switch(reason) { | |
2363 case 0x0003: { /* Reply from an ICQ status message request */ | |
2364 char *status_msg = gaim_icq_status(state); | |
2365 char *dialog_msg, **splitmsg; | |
2366 struct oscar_data *od = gc->proto_data; | |
2367 GSList *l = od->evilhack; | |
2368 gboolean evilhack = FALSE; | |
2369 | |
2370 /* Split at (carriage return/newline)'s, then rejoin later with BRs between. */ | |
2371 splitmsg = g_strsplit(msg, "\r\n", 0); | |
2372 | |
2373 /* If who is in od->evilhack, then we're just getting the away message, otherwise this | |
2374 * will just get appended to the info box (which is already showing). */ | |
2375 while (l) { | |
2376 char *x = l->data; | |
2377 if (!strcmp(x, normalize(who))) { | |
2378 evilhack = TRUE; | |
2379 g_free(x); | |
2380 od->evilhack = g_slist_remove(od->evilhack, x); | |
2381 break; | |
2382 } | |
2383 l = l->next; | |
2384 } | |
2385 | |
2386 if (evilhack) | |
2387 dialog_msg = g_strdup_printf(_("<B>UIN:</B> %s<BR><B>Status:</B> %s<BR><HR>%s<BR>"), who, status_msg, g_strjoinv("<BR>", splitmsg)); | |
2388 else | |
2389 dialog_msg = g_strdup_printf(_("<B>Status:</B> %s<BR><HR>%s<BR>"), status_msg, g_strjoinv("<BR>", splitmsg)); | |
2390 g_show_info_text(gc, who, 2, dialog_msg, NULL); | |
2391 | |
2392 g_free(status_msg); | |
2393 g_free(dialog_msg); | |
2394 g_strfreev(splitmsg); | |
2395 } break; | |
2396 | |
2397 default: { | |
2398 debug_printf("Received an unknown client auto-response from %s. Type 0x%04x\n", who, reason); | |
2399 } break; | |
2400 } /* end of switch */ | |
2401 | |
2402 return 0; | |
2403 } | |
2404 | |
2355 static int gaim_parse_clientauto(aim_session_t *sess, aim_frame_t *fr, ...) { | 2405 static int gaim_parse_clientauto(aim_session_t *sess, aim_frame_t *fr, ...) { |
2356 struct gaim_connection *gc = sess->aux_data; | |
2357 va_list ap; | 2406 va_list ap; |
2358 fu16_t chan, reason; | 2407 fu16_t chan, reason; |
2359 char *who; | 2408 char *who; |
2360 | 2409 |
2361 va_start(ap, fr); | 2410 va_start(ap, fr); |
2363 who = va_arg(ap, char *); | 2412 who = va_arg(ap, char *); |
2364 reason = (fu16_t)va_arg(ap, unsigned int); | 2413 reason = (fu16_t)va_arg(ap, unsigned int); |
2365 | 2414 |
2366 if (chan == 0x0002) { /* File transfer declined */ | 2415 if (chan == 0x0002) { /* File transfer declined */ |
2367 char *cookie = va_arg(ap, char *); | 2416 char *cookie = va_arg(ap, char *); |
2368 return gaim_parse_clientauto_rend(sess, who, reason, cookie); | 2417 return gaim_parse_clientauto_ch2(sess, who, reason, cookie); |
2369 } else if (chan == 0x0004) { /* ICQ message */ | 2418 } else if (chan == 0x0004) { /* ICQ message */ |
2370 switch(reason) { | 2419 int state = 0; |
2371 case 0x0003: { /* Reply from an ICQ status message request */ | 2420 char *msg = NULL; |
2372 int state = (int)va_arg(ap, fu32_t); | 2421 if (reason == 0x0003) { |
2373 char *msg = va_arg(ap, char *); | 2422 state = (int)va_arg(ap, fu32_t); |
2374 char *status_msg = gaim_icq_status(state); | 2423 msg = va_arg(ap, char *); |
2375 char *dialog_msg, **splitmsg; | 2424 } |
2376 struct oscar_data *od = gc->proto_data; | 2425 return gaim_parse_clientauto_ch4(sess, who, reason, state, msg); |
2377 GSList *l = od->evilhack; | 2426 } |
2378 gboolean evilhack = FALSE; | |
2379 | |
2380 /* Split at (carriage return/newline)'s, then rejoin later with BRs between. */ | |
2381 splitmsg = g_strsplit(msg, "\r\n", 0); | |
2382 | |
2383 /* If who is in od->evilhack, then we're just getting the away message, otherwise this | |
2384 * will just get appended to the info box (which is already showing). */ | |
2385 while (l) { | |
2386 char *x = l->data; | |
2387 if (!strcmp(x, normalize(who))) { | |
2388 evilhack = TRUE; | |
2389 g_free(x); | |
2390 od->evilhack = g_slist_remove(od->evilhack, x); | |
2391 break; | |
2392 } | |
2393 l = l->next; | |
2394 } | |
2395 | |
2396 if (evilhack) | |
2397 dialog_msg = g_strdup_printf(_("<B>UIN:</B> %s<BR><B>Status:</B> %s<BR><HR>%s<BR>"), who, status_msg, g_strjoinv("<BR>", splitmsg)); | |
2398 else | |
2399 dialog_msg = g_strdup_printf(_("<B>Status:</B> %s<BR><HR>%s<BR>"), status_msg, g_strjoinv("<BR>", splitmsg)); | |
2400 g_show_info_text(gc, who, 2, dialog_msg, NULL); | |
2401 | |
2402 g_free(status_msg); | |
2403 g_free(dialog_msg); | |
2404 g_strfreev(splitmsg); | |
2405 } break; | |
2406 | |
2407 default: { | |
2408 debug_printf("Received an unknown client auto-response from %s. Type 0x%04x\n", who, reason); | |
2409 } break; | |
2410 } /* end of switch */ | |
2411 | |
2412 } /* end of if */ | |
2413 | 2427 |
2414 va_end(ap); | 2428 va_end(ap); |
2415 | 2429 |
2416 return 1; | 2430 return 1; |
2417 } | 2431 } |
2603 return buf; | 2617 return buf; |
2604 } | 2618 } |
2605 | 2619 |
2606 static int gaim_parse_user_info(aim_session_t *sess, aim_frame_t *fr, ...) { | 2620 static int gaim_parse_user_info(aim_session_t *sess, aim_frame_t *fr, ...) { |
2607 aim_userinfo_t *info; | 2621 aim_userinfo_t *info; |
2608 char *prof_enc = NULL, *prof = NULL; | 2622 char *text_enc = NULL, *text = NULL, *utf8 = NULL; |
2623 int text_len; | |
2609 fu16_t infotype; | 2624 fu16_t infotype; |
2625 fu32_t flags; | |
2610 char header[BUF_LONG]; | 2626 char header[BUF_LONG]; |
2611 char legend[BUF_LONG]; | 2627 char legend[BUF_LONG]; |
2612 struct gaim_connection *gc = sess->aux_data; | 2628 struct gaim_connection *gc = sess->aux_data; |
2613 struct oscar_data *od = gc->proto_data; | 2629 struct oscar_data *od = gc->proto_data; |
2614 GSList *l = od->evilhack; | 2630 GSList *l = od->evilhack; |
2617 gchar *membersince = NULL, *onlinesince = NULL, *idle = NULL; | 2633 gchar *membersince = NULL, *onlinesince = NULL, *idle = NULL; |
2618 | 2634 |
2619 va_start(ap, fr); | 2635 va_start(ap, fr); |
2620 info = va_arg(ap, aim_userinfo_t *); | 2636 info = va_arg(ap, aim_userinfo_t *); |
2621 infotype = (fu16_t)va_arg(ap, unsigned int); | 2637 infotype = (fu16_t)va_arg(ap, unsigned int); |
2622 prof_enc = va_arg(ap, char *); | 2638 text_enc = va_arg(ap, char *); |
2623 prof = va_arg(ap, char *); | 2639 text = va_arg(ap, char *); |
2640 text_len = va_arg(ap, int); | |
2624 va_end(ap); | 2641 va_end(ap); |
2642 | |
2643 if (text_len > 0) { | |
2644 flags = parse_encoding (text_enc); | |
2645 switch (flags) { | |
2646 case 0: | |
2647 utf8 = g_strdup(text); | |
2648 break; | |
2649 case AIM_IMFLAGS_UNICODE: | |
2650 utf8 = g_convert(text, text_len, "UTF-8", "UCS-2BE", NULL, NULL, NULL); | |
2651 break; | |
2652 default: | |
2653 debug_printf("Encountered an unknown encoding parsing userinfo\n"); | |
2654 } | |
2655 } | |
2625 | 2656 |
2626 if (!od->icq) { | 2657 if (!od->icq) { |
2627 g_snprintf(legend, sizeof legend, | 2658 g_snprintf(legend, sizeof legend, |
2628 _("<BODY BGCOLOR=WHITE><hr><I>Legend:</I><br><br>" | 2659 _("<BODY BGCOLOR=WHITE><hr><I>Legend:</I><br><br>" |
2629 "<IMG SRC=\"free_icon.gif\"> : Normal AIM User<br>" | 2660 "<IMG SRC=\"free_icon.gif\"> : Normal AIM User<br>" |
2682 | 2713 |
2683 if (infotype == AIM_GETINFO_AWAYMESSAGE) { | 2714 if (infotype == AIM_GETINFO_AWAYMESSAGE) { |
2684 if (evilhack) { | 2715 if (evilhack) { |
2685 g_show_info_text(gc, info->sn, 2, | 2716 g_show_info_text(gc, info->sn, 2, |
2686 header, | 2717 header, |
2687 (prof && *prof) ? away_subs(prof, gc->username) : | 2718 (utf8 && *utf8) ? away_subs(utf8, gc->username) : |
2688 _("<i>User has no away message</i>"), | 2719 _("<i>User has no away message</i>"), |
2689 legend, NULL); | 2720 legend, NULL); |
2690 } else { | 2721 } else { |
2691 g_show_info_text(gc, info->sn, 0, | 2722 g_show_info_text(gc, info->sn, 0, |
2692 header, | 2723 header, |
2693 (prof && *prof) ? away_subs(prof, gc->username) : NULL, | 2724 (utf8 && *utf8) ? away_subs(utf8, gc->username) : NULL, |
2694 (prof && *prof) ? "<BR><HR>" : NULL, | 2725 (utf8 && *utf8) ? "<BR><HR>" : NULL, |
2695 NULL); | 2726 NULL); |
2696 } | 2727 } |
2697 } else if (infotype == AIM_GETINFO_CAPABILITIES) { | 2728 } else if (infotype == AIM_GETINFO_CAPABILITIES) { |
2698 g_show_info_text(gc, info->sn, 2, | 2729 g_show_info_text(gc, info->sn, 2, |
2699 header, | 2730 header, |
2702 "</i>", | 2733 "</i>", |
2703 legend, | 2734 legend, |
2704 NULL); | 2735 NULL); |
2705 } else { | 2736 } else { |
2706 g_show_info_text(gc, info->sn, 1, | 2737 g_show_info_text(gc, info->sn, 1, |
2707 (prof && *prof) ? away_subs(prof, gc->username) : | 2738 (utf8 && *utf8) ? away_subs(utf8, gc->username) : |
2708 _("<i>No Information Provided</i>"), | 2739 _("<i>No Information Provided</i>"), |
2709 legend, | 2740 legend, |
2710 NULL); | 2741 NULL); |
2711 } | 2742 } |
2743 | |
2744 g_free(utf8); | |
2712 | 2745 |
2713 return 1; | 2746 return 1; |
2714 } | 2747 } |
2715 | 2748 |
2716 static int gaim_parse_motd(aim_session_t *sess, aim_frame_t *fr, ...) { | 2749 static int gaim_parse_motd(aim_session_t *sess, aim_frame_t *fr, ...) { |
3121 { | 3154 { |
3122 va_list ap; | 3155 va_list ap; |
3123 fu16_t maxsiglen; | 3156 fu16_t maxsiglen; |
3124 struct gaim_connection *gc = sess->aux_data; | 3157 struct gaim_connection *gc = sess->aux_data; |
3125 struct oscar_data *odata = (struct oscar_data *)gc->proto_data; | 3158 struct oscar_data *odata = (struct oscar_data *)gc->proto_data; |
3159 char *unicode; | |
3160 int unicode_len; | |
3161 fu32_t flags; | |
3126 | 3162 |
3127 va_start(ap, fr); | 3163 va_start(ap, fr); |
3128 maxsiglen = va_arg(ap, int); | 3164 maxsiglen = va_arg(ap, int); |
3129 va_end(ap); | 3165 va_end(ap); |
3130 | 3166 |
3131 debug_printf("locate rights: max sig len = %d\n", maxsiglen); | 3167 debug_printf("locate rights: max sig len = %d\n", maxsiglen); |
3132 | 3168 |
3133 odata->rights.maxsiglen = odata->rights.maxawaymsglen = (guint)maxsiglen; | 3169 odata->rights.maxsiglen = odata->rights.maxawaymsglen = (guint)maxsiglen; |
3134 | 3170 |
3135 if (odata->icq) | 3171 if (odata->icq) |
3136 aim_bos_setprofile(sess, fr->conn, NULL, NULL, caps_icq); | 3172 aim_bos_setprofile(sess, fr->conn, NULL, NULL, 0, NULL, NULL, 0, caps_icq); |
3137 else | 3173 else { |
3138 aim_bos_setprofile(sess, fr->conn, gc->user->user_info, NULL, caps_aim); | 3174 flags = check_encoding (gc->user->user_info); |
3175 | |
3176 if (flags == 0) { | |
3177 aim_bos_setprofile(sess, fr->conn, "us-ascii", gc->user->user_info, | |
3178 strlen(gc->user->user_info), NULL, NULL, 0, caps_aim); | |
3179 } else { | |
3180 unicode = g_convert (gc->user->user_info, strlen(gc->user->user_info), | |
3181 "UCS-2BE", "UTF-8", NULL, &unicode_len, NULL); | |
3182 aim_bos_setprofile(sess, fr->conn, "unicode-2-0", unicode, unicode_len, | |
3183 NULL, NULL, 0, caps_aim); | |
3184 g_free(unicode); | |
3185 } | |
3186 } | |
3139 | 3187 |
3140 return 1; | 3188 return 1; |
3141 } | 3189 } |
3142 | 3190 |
3143 static int gaim_parse_buddyrights(aim_session_t *sess, aim_frame_t *fr, ...) { | 3191 static int gaim_parse_buddyrights(aim_session_t *sess, aim_frame_t *fr, ...) { |
3228 { | 3276 { |
3229 aim_icq_ackofflinemsgs(sess); | 3277 aim_icq_ackofflinemsgs(sess); |
3230 return 1; | 3278 return 1; |
3231 } | 3279 } |
3232 | 3280 |
3233 static int gaim_simpleinfo(aim_session_t *sess, aim_frame_t *fr, ...) | 3281 static int gaim_icqsimpleinfo(aim_session_t *sess, aim_frame_t *fr, ...) |
3234 { | 3282 { |
3235 struct gaim_connection *gc = sess->aux_data; | 3283 struct gaim_connection *gc = sess->aux_data; |
3236 struct buddy *budlight; | 3284 struct buddy *budlight; |
3237 va_list ap; | 3285 va_list ap; |
3238 struct aim_icq_simpleinfo *info; | 3286 struct aim_icq_info *info; |
3239 char buf[16 * 1024]; | 3287 gchar *buf, *tmp; |
3240 char who[16]; | 3288 gchar who[16]; |
3241 | 3289 |
3242 va_start(ap, fr); | 3290 va_start(ap, fr); |
3243 info = va_arg(ap, struct aim_icq_simpleinfo *); | 3291 info = va_arg(ap, struct aim_icq_info *); |
3244 va_end(ap); | 3292 va_end(ap); |
3245 | 3293 |
3246 g_snprintf(who, sizeof who, "%lu", info->uin); | 3294 g_snprintf(who, sizeof(who), "%lu", info->uin); |
3247 g_snprintf(buf, sizeof buf, | 3295 buf = g_strdup_printf("<b>UIN:</b> %s<br>", who); |
3248 "<B>UIN:</B> %lu<BR>" | 3296 if (info->nick) { |
3249 "<B>Nick:</B> %s<BR>" | 3297 tmp = buf; |
3250 "<B>Name:</B> %s %s<BR>" | 3298 buf = g_strconcat(tmp, "<b>Nick:</b> ", info->nick, "<br>\n", NULL); |
3251 "<B>Email:</B> %s<BR>\n", | 3299 g_free(tmp); |
3252 info->uin, | 3300 } |
3253 info->nick, | 3301 if (info->first) { |
3254 info->first, info->last, | 3302 tmp = buf; |
3255 info->email); | 3303 buf = g_strconcat(tmp, "<b>First Name:</b> ", info->first, "<br>\n", NULL); |
3304 g_free(tmp); | |
3305 } | |
3306 if (info->last) { | |
3307 tmp = buf; | |
3308 buf = g_strconcat(tmp, "<b>Last Name:</b> ", info->last, "<br>\n", NULL); | |
3309 g_free(tmp); | |
3310 } | |
3311 if (info->email) { | |
3312 tmp = buf; | |
3313 buf = g_strconcat(tmp, "<b>Email Address:</b> ", info->email, "<br>\n", NULL); | |
3314 g_free(tmp); | |
3315 } | |
3256 | 3316 |
3257 /* If the contact is away, then we also want to get their status message | 3317 /* If the contact is away, then we also want to get their status message |
3258 * and show it in the same window as info. g_show_info_text gets the status | 3318 * and show it in the same window as info. g_show_info_text gets the status |
3259 * message if the third arg is 0 (this seems really gross to me). The | 3319 * message if the third arg is 0 (this seems really gross to me). The |
3260 * parse-icq-status-message function knows if it is putting it's message in | 3320 * parse-icq-status-message function knows if it is putting it's message in |
3274 g_show_info_text(gc, who, 2, buf, "<B>Status:</B> ", state_msg, NULL); | 3334 g_show_info_text(gc, who, 2, buf, "<B>Status:</B> ", state_msg, NULL); |
3275 free(state_msg); | 3335 free(state_msg); |
3276 } | 3336 } |
3277 } else | 3337 } else |
3278 g_show_info_text(gc, who, 2, buf, NULL); | 3338 g_show_info_text(gc, who, 2, buf, NULL); |
3339 | |
3340 g_free(buf); | |
3341 | |
3342 return 1; | |
3343 } | |
3344 | |
3345 static int gaim_icqallinfo(aim_session_t *sess, aim_frame_t *fr, ...) | |
3346 { | |
3347 struct gaim_connection *gc = sess->aux_data; | |
3348 va_list ap; | |
3349 struct aim_icq_info *info; | |
3350 gchar *buf, *tmp; | |
3351 gchar who[16]; | |
3352 | |
3353 va_start(ap, fr); | |
3354 info = va_arg(ap, struct aim_icq_info *); | |
3355 va_end(ap); | |
3356 | |
3357 g_snprintf(who, sizeof(who), "%lu", info->uin); | |
3358 buf = g_strdup_printf("<b>UIN:</b> %s<br>", who); | |
3359 if (info->nick) { | |
3360 tmp = buf; | |
3361 buf = g_strconcat(tmp, "<b>Nick:</b> ", info->nick, "<br>\n", NULL); | |
3362 g_free(tmp); | |
3363 } | |
3364 if (info->first) { | |
3365 tmp = buf; | |
3366 buf = g_strconcat(tmp, "<b>First Name:</b> ", info->first, "<br>\n", NULL); | |
3367 g_free(tmp); | |
3368 } | |
3369 if (info->last) { | |
3370 tmp = buf; | |
3371 buf = g_strconcat(tmp, "<b>Last Name:</b> ", info->last, "<br>\n", NULL); | |
3372 g_free(tmp); | |
3373 } | |
3374 if (info->email) { | |
3375 tmp = buf; | |
3376 buf = g_strconcat(tmp, "<b>Email Address:</b> ", info->email, "<br>\n", NULL); | |
3377 g_free(tmp); | |
3378 } | |
3379 if (info->personalwebpage) { | |
3380 tmp = buf; | |
3381 buf = g_strconcat(tmp, "<b>Personal Webpage:</b> ", info->personalwebpage, "<br>\n", NULL); | |
3382 g_free(tmp); | |
3383 } | |
3384 if (info->info) { | |
3385 tmp = buf; | |
3386 buf = g_strconcat(tmp, "<br><b>Additional Information:</b><br>", info->info, "<br><hr>\n", NULL); | |
3387 g_free(tmp); | |
3388 } | |
3389 if (info->homecity && info->homestate && info->homeaddr && info->homezip) { | |
3390 tmp = buf; | |
3391 buf = g_strconcat(tmp, "<br><b>Home Address:</b><br>\n", info->homeaddr, "<br>\n", info->homecity, ", ", info->homestate, " ", info->homezip, "<hr>\n", NULL); | |
3392 g_free(tmp); | |
3393 } | |
3394 | |
3395 g_show_info_text(gc, who, 2, buf, NULL); | |
3396 g_free(buf); | |
3279 | 3397 |
3280 return 1; | 3398 return 1; |
3281 } | 3399 } |
3282 | 3400 |
3283 static int gaim_popup(aim_session_t *sess, aim_frame_t *fr, ...) | 3401 static int gaim_popup(aim_session_t *sess, aim_frame_t *fr, ...) |
3476 struct aim_sendimext_args args; | 3594 struct aim_sendimext_args args; |
3477 GSList *h = odata->hasicons; | 3595 GSList *h = odata->hasicons; |
3478 struct icon_req *ir = NULL; | 3596 struct icon_req *ir = NULL; |
3479 char *who = normalize(name); | 3597 char *who = normalize(name); |
3480 struct stat st; | 3598 struct stat st; |
3481 int i, len; | 3599 int len; |
3482 | 3600 |
3483 args.flags = AIM_IMFLAGS_ACK | AIM_IMFLAGS_CUSTOMFEATURES; | 3601 args.flags = AIM_IMFLAGS_ACK | AIM_IMFLAGS_CUSTOMFEATURES; |
3484 if (odata->icq) | 3602 if (odata->icq) |
3485 args.flags |= AIM_IMFLAGS_OFFLINE; | 3603 args.flags |= AIM_IMFLAGS_OFFLINE; |
3486 | 3604 |
3517 } | 3635 } |
3518 } | 3636 } |
3519 | 3637 |
3520 args.destsn = name; | 3638 args.destsn = name; |
3521 | 3639 |
3522 /* Determine how we can send this message. Per the | |
3523 * warnings elsewhere in this file, these little | |
3524 * checks determine the simplest encoding we can use | |
3525 * for a given message send using it. */ | |
3526 len = strlen(message); | 3640 len = strlen(message); |
3527 i = 0; | 3641 args.flags |= check_encoding(message); |
3528 while (message[i]) { | |
3529 if ((unsigned char)message[i] > 0x7f) { | |
3530 /* not ASCII! */ | |
3531 args.flags |= AIM_IMFLAGS_ISO_8859_1; | |
3532 break; | |
3533 } | |
3534 i++; | |
3535 } | |
3536 while (message[i]) { | |
3537 /* ISO-8859-1 is 0x00-0xbf in the first byte | |
3538 * followed by 0xc0-0xc3 in the second */ | |
3539 if ((unsigned char)message[i] < 0x80) { | |
3540 i++; | |
3541 continue; | |
3542 } else if (((unsigned char)message[i] & 0xfc) == 0xc0 && | |
3543 ((unsigned char)message[i + 1] & 0xc0) == 0x80) { | |
3544 i += 2; | |
3545 continue; | |
3546 } | |
3547 args.flags ^= AIM_IMFLAGS_ISO_8859_1; | |
3548 args.flags |= AIM_IMFLAGS_UNICODE; | |
3549 break; | |
3550 } | |
3551 if (args.flags & AIM_IMFLAGS_UNICODE) { | 3642 if (args.flags & AIM_IMFLAGS_UNICODE) { |
3552 debug_printf ("Sending Unicode IM\n"); | 3643 debug_printf ("Sending Unicode IM\n"); |
3553 args.msg = g_convert(message, len, "UCS-2BE", "UTF-8", NULL, &len, &err); | 3644 args.msg = g_convert(message, len, "UCS-2BE", "UTF-8", NULL, &len, &err); |
3554 if (err) { | 3645 if (err) { |
3555 debug_printf("Error converting a unicode message: %s\n", err->message); | 3646 debug_printf("Error converting a unicode message: %s\n", err->message); |
3630 aim_bos_setidle(odata->sess, odata->conn, time); | 3721 aim_bos_setidle(odata->sess, odata->conn, time); |
3631 } | 3722 } |
3632 | 3723 |
3633 static void oscar_set_info(struct gaim_connection *g, char *info) { | 3724 static void oscar_set_info(struct gaim_connection *g, char *info) { |
3634 struct oscar_data *odata = (struct oscar_data *)g->proto_data; | 3725 struct oscar_data *odata = (struct oscar_data *)g->proto_data; |
3635 gchar *inforeal; | 3726 gchar *inforeal, *unicode; |
3727 fu32_t flags; | |
3728 int unicode_len; | |
3636 | 3729 |
3637 if (odata->rights.maxsiglen == 0) | 3730 if (odata->rights.maxsiglen == 0) |
3638 do_error_dialog(_("Unable to set AIM profile."), | 3731 do_error_dialog(_("Unable to set AIM profile."), |
3639 _("You have probably requested to set your profile before the login procedure completed. " | 3732 _("You have probably requested to set your profile before the login procedure completed. " |
3640 "Your profile remains unset; try setting it again when you are fully connected."), GAIM_ERROR); | 3733 "Your profile remains unset; try setting it again when you are fully connected."), GAIM_ERROR); |
3650 } | 3743 } |
3651 | 3744 |
3652 inforeal = g_strndup(info, odata->rights.maxsiglen); | 3745 inforeal = g_strndup(info, odata->rights.maxsiglen); |
3653 | 3746 |
3654 if (odata->icq) | 3747 if (odata->icq) |
3655 aim_bos_setprofile(odata->sess, odata->conn, NULL, NULL, caps_icq); | 3748 aim_bos_setprofile(odata->sess, odata->conn, NULL, NULL, 0, NULL, NULL, 0, caps_icq); |
3656 else | 3749 else { |
3657 aim_bos_setprofile(odata->sess, odata->conn, inforeal, NULL, caps_aim); | 3750 flags = check_encoding(inforeal); |
3658 | 3751 |
3752 if (flags == 0) { | |
3753 aim_bos_setprofile(odata->sess, odata->conn, "us-ascii", inforeal, strlen (inforeal), | |
3754 NULL, NULL, 0, caps_aim); | |
3755 } else { | |
3756 unicode = g_convert (inforeal, strlen(inforeal), "UCS-2BE", "UTF-8", NULL, | |
3757 &unicode_len, NULL); | |
3758 aim_bos_setprofile(odata->sess, odata->conn, "unicode-2-0", unicode, unicode_len, | |
3759 NULL, NULL, 0, caps_aim); | |
3760 g_free(unicode); | |
3761 } | |
3762 } | |
3659 g_free(inforeal); | 3763 g_free(inforeal); |
3660 | 3764 |
3661 return; | 3765 return; |
3662 } | 3766 } |
3663 | 3767 |
3664 static void oscar_set_away_aim(struct gaim_connection *gc, struct oscar_data *od, const char *message) | 3768 static void oscar_set_away_aim(struct gaim_connection *gc, struct oscar_data *od, const char *message) |
3665 { | 3769 { |
3770 fu32_t flags; | |
3771 char *unicode; | |
3772 int unicode_len; | |
3666 | 3773 |
3667 if (od->rights.maxawaymsglen == 0) | 3774 if (od->rights.maxawaymsglen == 0) |
3668 do_error_dialog(_("Unable to set AIM away message."), | 3775 do_error_dialog(_("Unable to set AIM away message."), |
3669 _("You have probably requested to set your away message before the login procedure completed. " | 3776 _("You have probably requested to set your away message before the login procedure completed. " |
3670 "You remain in a \"present\" state; try setting it again when you are fully connected."), GAIM_ERROR); | 3777 "You remain in a \"present\" state; try setting it again when you are fully connected."), GAIM_ERROR); |
3673 g_free(gc->away); | 3780 g_free(gc->away); |
3674 gc->away = NULL; | 3781 gc->away = NULL; |
3675 } | 3782 } |
3676 | 3783 |
3677 if (!message) { | 3784 if (!message) { |
3678 aim_bos_setprofile(od->sess, od->conn, NULL, "", caps_aim); | 3785 aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, NULL, "", 0, caps_aim); |
3679 return; | 3786 return; |
3680 } | 3787 } |
3681 | 3788 |
3682 if (strlen(message) > od->rights.maxawaymsglen) { | 3789 if (strlen(message) > od->rights.maxawaymsglen) { |
3683 gchar *errstr; | 3790 gchar *errstr; |
3687 do_error_dialog("Away message too long.", errstr, GAIM_WARNING); | 3794 do_error_dialog("Away message too long.", errstr, GAIM_WARNING); |
3688 g_free(errstr); | 3795 g_free(errstr); |
3689 } | 3796 } |
3690 | 3797 |
3691 gc->away = g_strndup(message, od->rights.maxawaymsglen); | 3798 gc->away = g_strndup(message, od->rights.maxawaymsglen); |
3692 aim_bos_setprofile(od->sess, od->conn, NULL, gc->away, caps_aim); | 3799 flags = check_encoding(message); |
3800 | |
3801 if (flags == 0) { | |
3802 aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, "us-ascii", gc->away, strlen(gc->away), | |
3803 caps_aim); | |
3804 } else { | |
3805 unicode = g_convert(message, strlen(message), "UCS-2BE", "UTF-8", NULL, &unicode_len, NULL); | |
3806 aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, "unicode-2-0", unicode, unicode_len, | |
3807 caps_aim); | |
3808 g_free(unicode); | |
3809 } | |
3693 | 3810 |
3694 return; | 3811 return; |
3695 } | 3812 } |
3696 | 3813 |
3697 static void oscar_set_away_icq(struct gaim_connection *gc, struct oscar_data *od, const char *state, const char *message) | 3814 static void oscar_set_away_icq(struct gaim_connection *gc, struct oscar_data *od, const char *state, const char *message) |
4644 } | 4761 } |
4645 aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, buf); | 4762 aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, buf); |
4646 break; | 4763 break; |
4647 case 5: | 4764 case 5: |
4648 g = gc->groups; | 4765 g = gc->groups; |
4649 at = 0; | 4766 at = 0; |
4650 while (g) { | 4767 while (g) { |
4651 list = ((struct group *)g->data)->members; | 4768 list = ((struct group *)g->data)->members; |
4652 while (list) { | 4769 while (list) { |
4653 at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", (char *)list->data); | 4770 at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", ((struct buddy *)list->data)->name); |
4654 list = list->next; | 4771 list = list->next; |
4655 } | 4772 } |
4656 g = g->next; | 4773 g = g->next; |
4657 } | 4774 } |
4658 aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, buf); | 4775 aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, buf); |
4838 gaim_input_remove(dim->watcher); | 4955 gaim_input_remove(dim->watcher); |
4839 aim_conn_kill(od->sess, &dim->conn); | 4956 aim_conn_kill(od->sess, &dim->conn); |
4840 g_free(dim); | 4957 g_free(dim); |
4841 } | 4958 } |
4842 | 4959 |
4843 | 4960 static fu32_t check_encoding(const char *utf8) |
4961 { | |
4962 int i = 0; | |
4963 fu32_t encodingflag = 0; | |
4964 | |
4965 /* Determine how we can send this message. Per the | |
4966 * warnings elsewhere in this file, these little | |
4967 * checks determine the simplest encoding we can use | |
4968 * for a given message send using it. */ | |
4969 while (utf8[i]) { | |
4970 if ((unsigned char)utf8[i] > 0x7f) { | |
4971 /* not ASCII! */ | |
4972 encodingflag = AIM_IMFLAGS_ISO_8859_1; | |
4973 break; | |
4974 } | |
4975 i++; | |
4976 } | |
4977 while (utf8[i]) { | |
4978 /* ISO-8859-1 is 0x00-0xbf in the first byte | |
4979 * followed by 0xc0-0xc3 in the second */ | |
4980 if ((unsigned char)utf8[i] < 0x80) { | |
4981 i++; | |
4982 continue; | |
4983 } else if (((unsigned char)utf8[i] & 0xfc) == 0xc0 && | |
4984 ((unsigned char)utf8[i + 1] & 0xc0) == 0x80) { | |
4985 i += 2; | |
4986 continue; | |
4987 } | |
4988 encodingflag = AIM_IMFLAGS_UNICODE; | |
4989 break; | |
4990 } | |
4991 | |
4992 return encodingflag; | |
4993 } | |
4994 | |
4995 static fu32_t parse_encoding(const char *enc) | |
4996 { | |
4997 char *charset; | |
4998 | |
4999 /* If anything goes wrong, fall back on ASCII and print a message */ | |
5000 charset = strstr(enc, "charset="); | |
5001 if (charset == NULL) { | |
5002 debug_printf("No charset specified for info, assuming ASCII\n"); | |
5003 return 0; | |
5004 } | |
5005 charset += 8; | |
5006 if (!strcmp(charset, "\"us-ascii\"")) { | |
5007 return 0; | |
5008 } else if (!strcmp(charset, "\"unicode-2-0\"")) { | |
5009 return AIM_IMFLAGS_UNICODE; | |
5010 } else { | |
5011 debug_printf("Unrecognized character set '%s', using ASCII\n", charset); | |
5012 return 0; | |
5013 } | |
5014 } | |
4844 | 5015 |
4845 static struct prpl *my_protocol = NULL; | 5016 static struct prpl *my_protocol = NULL; |
4846 | 5017 |
4847 G_MODULE_EXPORT void oscar_init(struct prpl *ret) { | 5018 G_MODULE_EXPORT void oscar_init(struct prpl *ret) { |
4848 struct proto_user_opt *puo; | 5019 struct proto_user_opt *puo; |