Tkabber contrib

Check-in [7e0bf9ef1f]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:juick: Code refactoring mixed with handling Juick changes More or less accurate history of changes can be found in [1]. [1]: https://github.com/Totktonada/tkabber4juick/
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:7e0bf9ef1f8996d3660f2e3794df74684d71b816
User & Date: totktonada 2016-06-29 22:55:19
Context
2018-03-16
04:16
[juick] Remove a roster push hook which asks juick.com for my nickname since this query is no lnger supported. Leaf check-in: 937698bef0 user: sgolovan tags: trunk
2016-06-29
22:55
juick: Code refactoring mixed with handling Juick changes More or less accurate history of changes can be found in [1]. [1]: https://github.com/Totktonada/tkabber4juick/ check-in: 7e0bf9ef1f user: totktonada tags: trunk
2016-02-04
05:26
flip: Fixed joining group with current Tkabber. check-in: 1874531298 user: sgolovan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to juick/BUGS.

1
2
3
4
5
# You can report about all bugs this plugin via jabber: totktonada@totktonada.ru.
# Matched: "-". Fixed: "+".

- Citate with smile in middle.
- Replies to your messages currently not indicated as personal.




<
1
2
3
4

# You can report about all bugs this plugin via jabber: totktonada@totktonada.ru.
# Matched: "-". Fixed: "+".

- Citate with smile in middle.

Changes to juick/juick.tcl.







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


28
29
30
31
32
33
34
35
36
37
38
39








40
41
42
43
44


45
46
47
48
49
50
51
52
53
54
55
56


57
58
59
60
61
62
63










64
65
66
67
68
69
70
71
72
73
74
75




76
77
78
79
80
81


82
83
84
85
86
87
88
89
90
91








92
93
94
95



96
97
98

99
100
101
102
103
104
105
106
107
108
109
110

111
112
113
114


115
116
117
118
119
120




121
122
123
124
125
126
127


128
129
130
131
132
133
134



135
136
137


138
139


140
141
142



143

144
145
146
147
148
149



150
151
152
153
154
155
156
157
158
159
160
161

















162
163
164
165
166
167



168
169
170


171
172
173

174
175
176
177
178
179
180
181
182


183
184
185
186
187
188
189
190


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

213
214


215
216
217
218
219
220
221
...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
...
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275








276
277
278
279
280
281
282
283
284

285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
...
344
345
346
347
348
349
350
351
352
353
354
355
356

357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
...
444
445
446
447
448
449
450
451
452
453
454

455
456
457
458
459
460
461
462
463

464
465
466
467
468
469
470
...
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502

503
504
505
506
507
508
509

510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628

629









630
631
632
633





634
635
636
637
638
639
640
641
642

643
644
645
646
647



648
649
650
651




652

653
654
655




656
657
658
659
660
661
662


663
664
665







666
667
668




669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723

724


725
726
727
728
729
730

731




732
733
734
735
736
737
738

739

740

741
742
743
744
745
746
747
748

749
750
751
752
753
754
755
756



757
758
759



760


761
762
763



764



765
766
767
768


769
770
771
772





































773
774




775
776
777
778
779
780
781
782
783
784
785



786
787
788
789
790
791
792
793
794
795
796
797
798

799
800
801
802
803
804
805

806
807
808
809
810
811
812
813
814
815
816
817
818
819
820

821
822
823
824

825
826

827
828
829

830
831

832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886






package require msgcat
package require http

option add *juick.nick                  red            widgetDefault
option add *juick.tag                   ForestGreen    widgetDefault
option add *juick.my                    gray           widgetDefault
option add *juick.number                blue           widgetDefault
option add *juick.private_foreground    blue           widgetDefault
option add *juick.private_background    #FF9A15        widgetDefault
option add *juick.citing                gray35         widgetDefault

if {[string equal $::tkabber_version "0.11.1"]} {
    set scriptdir [file dirname [info script]]

    catch {source [file join $scriptdir \
        tkabber-0.11.1-compatibility.tcl]} \
        source_compatibility_file_result
}

namespace eval juick {

variable options
variable juick_nicknames
variable chat_things

::msgcat::mcload [file join [file dirname [info script]] msgs]



if {![::plugins::is_registered juick]} {
    ::plugins::register juick \
        -namespace [namespace current] \
        -source [info script] \
        -description [::msgcat::mc \
            "Whether the Juick plugin is loaded."] \
        -loadcommand [namespace code load] \
        -unloadcommand [namespace code unload]
    return
}

custom::defgroup Plugins [::msgcat::mc "Plugins options."] -group Tkabber









set group "Juick"
custom::defgroup $group \
    [::msgcat::mc "Juick settings."] \
    -group Plugins



custom::defvar options(main_jid) "juick@juick.com/Juick" \
    [::msgcat::mc \
        "Main Juick JID. This used for forwarding things from other chats."] \
    -group $group \
    -type string
custom::defvar options(special_update_juick_tab) 1 \
    [format "%s\n%s" \
        [::msgcat::mc "Indicate as personal message only private messages and replies to you."] \
        [::msgcat::mc "Your Juick nickname determines at roster receiving, so after enable option you need to reconnecting."]] \
    -group $group \
    -type boolean



proc load {} {
    ::richtext::entity_state juick_numbers 1
    ::richtext::entity_state citing 1
    ::richtext::entity_state juick 1
    ::richtext::entity_state juick_ligth 1
    ::richtext::entity_state markdown_url 1











    ::richtext::register_entity juick_numbers \
        -configurator [namespace current]::configure_juick_numbers \
        -parser [namespace current]::process_juick_numbers \
        -renderer [namespace current]::render_juick \
        -parser-priority 54

    ::richtext::register_entity juick_ligth \
        -configurator [namespace current]::configure_juick_ligth \
        -parser [namespace current]::process_juick_ligth \
        -renderer [namespace current]::render_juick_ligth \
        -parser-priority 81





    ::richtext::register_entity citing \
        -configurator [namespace current]::configure_citing \
        -parser [namespace current]::process_citing \
        -renderer [namespace current]::render_citing \
        -parser-priority 82



    ::richtext::register_entity juick \
        -configurator [namespace current]::configure_juick \
        -parser [namespace current]::process_juick \
        -renderer [namespace current]::render_juick \
        -parser-priority 85

    ::richtext::register_entity markdown_url \
	    -parser [namespace current]::process_markdown_urls \
	    -parser-priority 49









    hook::add draw_message_hook        \
        [namespace current]::ignore_server_messages 0




    hook::add draw_message_hook        \
        [namespace current]::handle_message 21


    hook::add chat_window_click_hook   \
        [namespace current]::insert_from_window

    hook::add chat_win_popup_menu_hook \
        [namespace current]::add_juick_things_menu 20

    hook::add rewrite_message_hook     \
        [namespace current]::rewrite_juick_message 20

    hook::add chat_send_message_hook   \
        [namespace current]::rewrite_send_juick_message 19


    hook::add draw_message_hook \
        [namespace current]::update_juick_tab 8

    hook::remove draw_message_hook ::plugins::update_tab::update 8



    hook::add draw_message_hook \
        [namespace current]::add_number_of_messages_from_juick_to_title 18

    hook::remove draw_message_hook \
        ::::ifacetk::add_number_of_messages_to_title 18





    hook::add roster_push_hook \
        [namespace current]::get_juick_nick 99

    hook::add generate_completions_hook \
        [namespace current]::juick_commands_comps 99
}



proc unload {} {
    ::richtext::unregister_entity juick_numbers
    ::richtext::unregister_entity citing
    ::richtext::unregister_entity juick
    ::richtext::unregister_entity juick_ligth
    ::richtext::unregister_entity process_markdown_urls




    hook::remove draw_message_hook        \
        [namespace current]::ignore_server_messages 0



    hook::remove draw_message_hook        \


        [namespace current]::handle_message 21

    hook::remove chat_window_click_hook   \



        [namespace current]::insert_from_window


    hook::remove chat_win_popup_menu_hook \
        [namespace current]::add_juick_things_menu 20

    hook::remove rewrite_message_hook     \
        [namespace current]::rewrite_juick_message 20




    hook::remove chat_send_message_hook   \
        [namespace current]::rewrite_send_juick_message 19

    hook::remove draw_message_hook \
        [namespace current]::update_juick_tab 8

    hook::add draw_message_hook \
        ::plugins::update_tab::update 8

    hook::remove draw_message_hook \
        [namespace current]::add_number_of_messages_from_juick_to_title 18


















    hook::add draw_message_hook \
        ::::ifacetk::add_number_of_messages_to_title 18

    hook::remove roster_push_hook \
        [namespace current]::get_juick_nick 99




    hook::remove generate_completions_hook \
        [namespace current]::juick_commands_comps 99



    ::richtext::entity_state juick_numbers 0
    ::richtext::entity_state citing 0

    ::richtext::entity_state juick 0
    ::richtext::entity_state juick_ligth 0
}

proc is_juick_jid {jid} {
    set jid [::xmpp::jid::removeResource $jid]
    set node [::xmpp::jid::node $jid]
    return [expr {[string equal $jid "juick@juick.com"] || \
        [string equal $node "juick%juick.com"]}]



#    if {$without_resource} { \
#        return [expr {[string equal $jid "juick@juick.com"] \
#           || [regexp "juick%juick.com@.+" $jid]}] \
#    } else { \
#       return [expr {[string equal $jid "juick@juick.com/Juick"] \
#           || [regexp "juick%juick.com@.+/Juick" $jid]}] \
#    }


}

# Determines whether given chatid correspond to Juick
proc is_juick {chatid} {
    set jid [chat::get_jid $chatid]
    return [is_juick_jid $jid]
}

proc get_juick_nick {xlib jid name groups subsc ask} {
    variable options
    variable juick_nicknames

    if {![info exists options(special_update_juick_tab)] \
        || ! $options(special_update_juick_tab)} \
    {
        return
    }

    if {![is_juick_jid $jid] || [info exists juick_nicknames($jid)]} {
        return
    }


    # For Juick contacts connected via j2j transport is difficult
    # to determine user jid.


    if {[string first % $jid] >= 0} {
        return
    }

    set my_jid [connection_jid $xlib]
    set my_jid [::xmpp::jid::removeResource $my_jid]
    set nick_request_url "http://api.juick.com/users?jid=$my_jid"
................................................................................
    # If has no json package, try to parse by regexp.
    if {[catch {package require json}]} {
        if {![regexp {\[{.*"uname"\w*:\w*"([^"]+)".*}\]} $data -> uname]} {
            # For vim syntax highlighter: "
            return
        }

        set juick_nicknames($jid) $uname
        return
    }

    set ds [::json::json2dict $data]

    if {[llength $ds] != 1} {
        return
................................................................................
            # If it fails, get uname value without dict command.
            foreach {key value} $d {
                switch -- $key {
                    uname { set uname $value }
                }
            }

            set juick_nicknames($jid) $uname
            return
        }
    }

    if {[catch {dict get $d uname} uname]} {
        return
    }

    set juick_nicknames($jid) $uname
}

proc handle_message {chatid from type body x} {
    if {![is_juick $chatid]} return









    ::richtext::property_add {JUICK} {}

    set chatw [chat::chat_win $chatid]
    set jid [chat::get_jid $chatid]

    set tags {}
    if {![string equal $jid $from]} {
        lappend tags JMY

    }

    ::richtext::render_message $chatw $body $tags
    return stop
}

proc get_my_juick_nickname {jid} {
    variable juick_nicknames

    set uname ""
    set jid [::xmpp::jid::removeResource $jid]

    if {[info exists juick_nicknames($jid)]} {
        set uname $juick_nicknames($jid)
    }

    return $uname
}

proc is_personal_juick_message {from body} {
    variable options




    set private_msg [regexp {^Private message from @.+:\n} $body]

    set reply_to_comment [regexp \
        {Reply by @[^\n ]+:\n>.+\n\n@([^\n ]+) .+\n\n#\d+/\d+ http://juick.com/\d+#\d+$} \
        $body -> reply_to_nick]

    if {$reply_to_comment} {
        set reply_to_me [string equal \
            [get_my_juick_nickname $from] $reply_to_nick]
    } else {
        set reply_to_me 0
    }

    return [expr {$private_msg || $reply_to_me}]
}

proc update_juick_tab {chatid from type body x} {
    variable options
    if {!([is_juick_jid $from] && [string equal $type "chat"] \
        && $options(special_update_juick_tab))} \
    {
        ::plugins::update_tab::update $chatid $from $type $body $x
        return
    }

    # See ${PATH_TO_TKABBER}/plugins/chat/update_tab.tcl
    foreach xelem $x {
        ::xmpp::xml::split $xelem tag xmlns attrs cdata subels
        if {[string equal $tag ""] && \
            [string equal $xmlns tkabber:x:nolog]} \
        {
            return
        }
    }

    set cw [chat::winid $chatid]

................................................................................
    if {[is_personal_juick_message $from $body]} {
        tab_set_updated $cw 1 mesg_to_user
    } else {
        tab_set_updated $cw 1 message
    }
}

proc ignore_server_messages {chatid from type body x} {
    if {[is_juick $chatid] && $from eq ""} {
        return stop
    }
}


proc add_number_of_messages_from_juick_to_title {chatid from type body x} {
    variable options
    if {!([is_juick_jid $from] && [string equal $type "chat"] \
        && $options(special_update_juick_tab))} \
    {
        ::ifacetk::add_number_of_messages_to_title $chatid $from $type \
            $body $x
        return
    }

    # See ${PATH_TO_TKABBER}/ifacetk/iface.tcl
    foreach xelem $x {
        ::xmpp::xml::split $xelem tag xmlns attrs cdata subels
        if {[string equal $tag ""] && \
            [string equal $xmlns tkabber:x:nolog]} \
        {
            return
        }
    }

    if {[::ifacetk::chat_window_is_active $chatid]} return
    if {$from eq ""} return
................................................................................
        incr personal_msg($chatid)
    }

    ::ifacetk::update_chat_title $chatid
    ::ifacetk::update_main_window_title
}

proc rewrite_juick_message \
     {vxlib vfrom vid vtype vis_subject vsubject \
      vbody verr vthread vpriority vx} {
    upvar 2 $vfrom from
    upvar 2 $vtype type
    upvar 2 $vbody body
    upvar 2 $vx x

    if {![is_juick_jid $from] || ![string equal $type "chat"]} {
        return
    }

#############################
# Remove jabber:x:oob element
    set newx {}

    foreach xe $x {
        ::xmpp::xml::split $xe tag xmlns attrs cdata subels

        if {![string equal $xmlns "jabber:x:oob"]} {
            lappend newx $xe
        }
    }

    set x $newx

#############################
# Add GMT time
    foreach xe $x {
        ::xmpp::xml::split $xe tag xmlns attrs cdata subels

        if {[string equal $xmlns "http://juick.com/message"]} {
            foreach {key val} $attrs {
                if {[string equal $key "ts"]} {
                    set body "$val GMT\n$body"
                }
            }
        }
    }
}

proc rewrite_send_juick_message {chatid user body type} {
    if {![is_juick $chatid] || ![string equal $type "chat"]} {
        return
    }

    if {[regexp {^[Ss] (#\d+)\+\s*$} $body -> thing]} {
        set xlib [chat::get_xlib $chatid]
        set jid [chat::get_jid $chatid]

................................................................................
        message::send_msg $xlib $jid -type chat -body "S $thing"
        message::send_msg $xlib $jid -type chat -body "$thing+"

        return stop
    }
}

proc insert_from_window {chatid w x y} {
    variable options
    set thing ""
    set cw [chat::chat_win $chatid]

    set ci [chat::input_win $chatid]
    set jid [::xmpp::jid::removeResource [chat::get_jid $chatid]]

    set tags [$cw tag names "@$x,$y"]

    if {[set idx [lsearch -glob $tags JUICK-*]] >= 0} {
        set thing [string range [lindex $tags $idx] 6 end]
    }


    if {$thing eq ""} return

    if {![is_juick_jid $jid]} {
        set xlib [chat::get_xlib $chatid]
        set mainchat [chat::chatid $xlib $options(main_jid)]

        if {[chat::is_opened $mainchat]} {
................................................................................
    }

    $ci insert insert "$thing "
    focus -force $ci
    return stop
}

proc add_juick_things_menu {m chatwin X Y x y} {
    set thing ""
    set tags [$chatwin tag names "@$x,$y"]

    if {[set idx [lsearch -glob $tags JUICK-*]] >= 0} {
        set thing [string range [lindex $tags $idx] 6 end]
    }

    if {$thing eq ""} return

    $m add command -label [format [::msgcat::mc \
        "\[J\] Copy %s to clipboard"] $thing] \
        -command [list [namespace current]::copy_thing $chatwin $thing]

    $m add command -label [::msgcat::mc \
        "\[J\] Open in browser"]\
        -command [list [namespace current]::browse_thing $chatwin $thing]

    if {[regexp {#\d+$} $thing]} {

# Currently not implemented.
if {0} {
        $m add command -label [::msgcat::mc \
            "\[J\] Open in new tab"] \
            -command [list [namespace current]::get_juick_thread \
            $chatwin $thing]
}


        $m add command -label [::msgcat::mc \
            "\[J\] Subscribe"] \
            -command [list [namespace current]::send_to_juick \
            $chatwin "S $thing"]

        $m add command -label [::msgcat::mc \
            "\[J\] Unsubscribe"] \
            -command [list [namespace current]::send_to_juick \
            $chatwin "U $thing"]
    }
}

proc send_to_juick {w body} {
    variable options
    set cw [join [lrange [split $w .] 0 end-1] .]
    set chatid [chat::winid_to_chatid $cw]
    set xlib [chat::get_xlib $chatid]
    set jid [chat::get_jid $chatid]

    if {![is_juick_jid $jid]} {
        set mainchat [chat::chatid $xlib $options(main_jid)]
        set jid [chat::get_jid $mainchat]
    }

    message::send_msg $xlib $jid -type chat -body $body
}

proc get_juick_thread {w thing} {
    set cw [join [lrange [split $w .] 0 end-1] .]
    set chatid [chat::winid_to_chatid $cw]
    set xlib [chat::get_xlib $chatid]
    set jid [chat::get_jid $chatid]
    set mid [string range $thing 1 end]

    ::xmpp::sendIQ $xlib get \
        -query [::xmpp::xml::create query \
            -xmlns "http://juick.com/query#messages" \
            -attrs [list mid $mid]] \
        -to $jid \
        -command [list [namespace current]::receive_juick_thread $jid]
}

proc receive_juick_thread {jid res child0} {
    if {![string equal $res ok]} return

    ::xmpp::xml::split $child0 tag0 xmlns0 attrs0 cdata0 subels0

    if {![string equal $xmlns0 "http://juick.com/query#messages"]} return

    set child1 [lindex $subels0 0]
    ::xmpp::xml::split $child1 tag1 xmlns1 attrs1 cdata1 subels1

    if {![string equal $xmlns1 "http://juick.com/message"]} return

    set msg ""

    foreach child2 $subels1 {
        ::xmpp::xml::split $child2 tag2 xmlns2 attrs2 cdata2 subels2
        switch -- $tag2 {
            body {
                set msg $cdata2
            }
        }
    }

    #puts "Get message from $jid: \"$msg\""

    # open new tab

    return
}

proc copy_thing {w thing} {
    clipboard clear -displayof $w
    clipboard append -displayof $w $thing
}

proc browse_thing {w thing} {
    switch -regexp -- $thing {
        {^#} {
          regsub -- "/" [string range $thing 1 end] "#" jurl
          browseurl http://juick.com/$jurl
          }
        {^@} {
          browseurl http://juick.com/[string range $thing 1 end]
          }
        {^\*} {
          browseurl http://juick.com/tag/[string range $thing 1 end]
          }
    }
}

# See commented code in juick_commands_comps.
#variable commands {HELP NICK LOGIN "S " "U " ON OFF "D " "BL " "WL " "PM " VCARD PING INVITE}
variable commands {HELP NICK LOGIN S U ON OFF D BL WL PM CARD PING INVITE}
proc correct_command {chatid user body type} {
   # Maybe once I'll get arount to it
}

proc juick_commands_comps {chatid compsvar wordstart line} {
    if {![is_juick $chatid]} return

    upvar 0 $compsvar comps
    variable chat_things
    variable commands

    if {!$wordstart} {
       set comps [concat $commands $comps]
    } else {
if {0} {
        # This code don't work.
        # See ${PATH_TO_TKABBER}/plugins/chat/completion.tcl at line 94.
        # Idea: use *rename* for procedure completion::complete.
        set q 0
        foreach cmd $commands {
            if {[string equal -length [string length $cmd] $cmd $line]} {
                set q 1
                break

            }









        }

        if {!$q} return
}





    }

    if {[info exist chat_things($chatid)]} {
       set comps [concat $chat_things($chatid) $comps]
    }
}

# --------------
# RichText stuff


proc configure_juick {w} {
    set options(juick.nick) [option get $w juick.nick Text]
    set options(juick.tag) [option get $w juick.tag Text]
    set options(juick.my) [option get $w juick.my Text]




    $w tag configure JNICK -foreground $options(juick.nick)
    $w tag configure JTAG -foreground $options(juick.tag)
    $w tag configure JMY -foreground $options(juick.my)




}


proc configure_juick_numbers {w} {
    set options(juick.number) [option get $w juick.number Text]





    $w tag configure JNUM -foreground $options(juick.number)
}

proc configure_juick_ligth {w} {
    set options(juick.private_foreground) \
        [option get $w juick.private_foreground Text]



    set options(juick.private_background) \
        [option get $w juick.private_background Text]








    $w tag configure JLIGTH -foreground $options(juick.private_foreground)
    $w tag configure JLIGTH -background $options(juick.private_background)




}

proc configure_citing {w} {
    set options(juick.citing) [option get $w juick.citing Text]

    $w tag configure CITING -foreground $options(juick.citing)
}

proc spot_juick_ligth {what at startVar endVar} {
    set matched [regexp -indices -start $at -- \
        {(^Private message)(?: from @.+:\n)} $what -> bounds]

    if {!$matched} { return false }

    upvar 1 $startVar uStart $endVar uEnd
    lassign $bounds uStart uEnd
    return true
}

proc spot_citing {what at startVar endVar} {
    set matched [regexp -indices -start $at -- \
        {(?:\n|\A)(>[^\n]+)} $what -> bounds]

    if {!$matched} { return false }

    upvar 1 $startVar uStart $endVar uEnd
    lassign $bounds uStart uEnd
    return true
}

proc spot_juick {what at startVar endVar} {
    set matched [regexp -indices -start $at -- \
        {(?:\s|\n|\A|\(|\>)(@[\w@.-]+|\*[\w?!+'/.-]+)(?:(\.(\s|\n))?)} \
        $what -> bounds]

    if {!$matched} { return false }

    upvar 1 $startVar uStart $endVar uEnd
    lassign $bounds uStart uEnd
    return true
}

proc spot_juick_numbers {what at startVar endVar} {
    set matched [regexp -indices -start $at -- \
        {(?:\s|\n|\A|\(|\>)(#\d+(/\d+)?)(?:(\.(\s|\n))?)} $what -> bounds]

    if {!$matched} { return false }

    upvar 1 $startVar uStart $endVar uEnd
    lassign $bounds uStart uEnd
    return true
}

proc spot_markdown_url {what at startVar endVar} {
    variable ::plugins::urls::url_regexp




    # [title][URL]
    set matched [regexp -indices -start $at -- \
        {(\[([^\]]+)\]\[([^\]]+)\])} $what -> \
        bounds title_bounds url_bounds]

    if {!$matched} {

        # [title](URL)




        set matched [regexp -indices -start $at -- \
            {(\[([^\]]+)\]\(([^\)]+)\))} $what -> \
            bounds title_bounds url_bounds]
    }

    if {!$matched} { return false }


    lassign $url_bounds us ue

    set url [string range $what $us $ue]

    set md_url_regexp [format "^%s$" $::plugins::urls::url_regexp]
    set matched [regexp -expanded -nocase -- $md_url_regexp $url]

    if {!$matched} { return false }

    upvar 1 $startVar uStart $endVar uEnd
    lassign $bounds uStart uEnd
    upvar 1 title_start title_start title_end title_end

    lassign $title_bounds title_start title_end
    upvar 1 url_start url_start url_end url_end
    lassign $url_bounds url_start url_end

    return true
}

proc process_juick {atLevel accName} {



    if {[::richtext::property_exists {JUICK}]} {
        return [process $atLevel $accName juick]
    }



}



proc process_juick_numbers {atLevel accName} {
    return [process $atLevel $accName juick_numbers]



}




proc process_citing {atLevel accName} {
    if {[::richtext::property_exists {JUICK}]} {
        return [process $atLevel $accName citing]


    }
}

proc process_juick_ligth {atLevel accName} {





































    if {[::richtext::property_exists {JUICK}]} {
       return [process $atLevel $accName juick_ligth]




    }
}

proc process_markdown_urls {atLevel accName} {
    if {[::richtext::property_exists {JUICK}]} {
       return [process $atLevel $accName markdown_url]
    }
}

proc process {atLevel accName what} {
    upvar #$atLevel $accName chunks




    set out {}

    foreach {s type tags} $chunks {
        if {[lsearch -regexp $type (text)|(citing)|(juick_ligth)] < 0} {
            # pass through
            lappend out $s $type $tags
            continue
        }

        if {[lsearch -exact $type citing] >=0} {
            lappend tags CITING
        }


        if {[lsearch -exact $type juick_ligth] >= 0} {
            lappend tags JLIGTH
        }

        set index 0; set uStart 0; set uEnd 0
        while {[eval {spot_$what $s $index uStart uEnd}]} {

            if {$uStart - $index > 0} {
                # Write out text before current thing, if any:
                lappend out \
                    [string range $s $index [expr {$uStart - 1}]] \
                    $type $tags
            }

            set thing [string range $s $uStart $uEnd]

            if {[string equal $what markdown_url]} {
                set title [string range $s $title_start $title_end]
                set url [string range $s $url_start $url_end]
                lappend out $url url $tags
                ::richtext::property_update url:title,$url $title
            } else {

                # Write out current thing:
                lappend out $thing $what $tags
            }


            set index [expr {$uEnd + 1}]
        }

        # Write out text after the last thing, if any:
        if {[string length $s] - $index > 0} {
            lappend out [string range $s $index end] $type $tags

        }
    }

    set chunks $out
}

proc render_juick {w type thing tags args} {
    if {[lsearch -exact $tags CITING] < 0 && \
        [lsearch -exact $tags JLIGTH] < 0} \
    {
        if {[string equal [string index $thing 0] "#"]} {
            set type JNUM
        } elseif {[string equal [string index $thing 0] "*"]} {
            set type JTAG
        } elseif {[string equal [string index $thing 0] "@"]} {
            set type JNICK
        }
    } else {
           if {[lsearch -exact $tags CITING] >= 0} {
               set type CITING
           }
           if {[lsearch -exact $tags JLIGTH] >= 0} {
               set type JLIGTH
           }
    }

#################
            variable chat_things
            set cw [join [lrange [split $w .] 0 end-1] .]
            set chatid [chat::winid_to_chatid $cw]
            if {![info exist chat_things($chatid)]} {
                set chat_things($chatid) [list $thing]
            } else {
                set chat_things($chatid) [linsert \
                    $chat_things($chatid) 0 $thing]
            }
#################

    set id JUICK-$thing
    $w insert end $thing [lfuse $tags [list $id $type JUICK]]
    return $id
}

proc render_citing {w type thing tags args} {
    set id CITING-$thing
    $w insert end $thing [lfuse $tags [list $id $type CITING]]
    return $id
}

proc render_juick_ligth {w type thing tags args} {
    set id JLIGTH-$thing
    $w insert end $thing [lfuse $tags [list $id $type JLIGTH]]
    return $id
}

# namespace eval juick
}
# vi:ts=4:et
>
>
>
>
>
>



<
<
<
<
<
<
<
<
<
|
<
|
<
<
|
<
<
<
<
<
<
<
<
<
>
>











<
>
>
>
>
>
>
>
>

<
<
<
<
>
>

<
<
<
<
<
<
<
<
<
<
<
>
>

<
<
<
<
<
<
>
>
>
>
>
>
>
>
>
>
|
<
<
<
<
<

<
<
<
<
<
>
>
>
>

<
<
<
<
<
>
>
|
<
<
<
<
<

<
<
<
>
>
>
>
>
>
>
>
|
<
<

>
>
>
|
<
<
>
|
<
<
|
<
<
|
<
<
|
<
<
>
|
<
<
|
>
>
|
<
<

<
<
>
>
>
>

<
|
|
<
<
|
>
>

|
<
<
<
<
<
>
>
>

<
<
>
>

<
>
>
|

<
>
>
>
|
>
|
<
<

<
<
>
>
>
|
<
<

<
|
|
<
<

<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
<

<
<
>
>
>

<
<
>
>

<
<
>
|
|
|

<
<
<
<
<
>
>

<
<
<
<
<
<
<
>
>
|
|
<
<
<
<


|

|







|



>


>
>







 







|







 







|








|


|
|
>
>
>
>
>
>
>
>
|
<




|
|
|
>


<



|
<
<
<
<
<
<
<
<
<
<
<
<
<

>
>
>
|
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
|
<







|
<







 







|
|




>
|
<
<
|
<








|
<







 







|







|










|











|

|







|
|







 







|

<
<
>


<
<
<
<
<
<
<
>







 







|
|
<
<
<
<
<
<











>
|
|
|
|
|
|
|
>













<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<

<


<
<
<
<
<
<
<
<
<
<
<
<
>
|
>
>
>
>
>
>
>
>
>

|
<
|
>
>
>
>
>


<
|
|
|
<
<

>

<
<
<
<
>
>
>

<
<
<
>
>
>
>
|
>

<
<
>
>
>
>
|
<
|

<
<
<
>
>

<
<
>
>
>
>
>
>
>

<
<
>
>
>
>


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

>

>
>
|
<
|
<
|
<
>
|
>
>
>
>
|
|
|
|
<


>

>

>







<
>
|
<
<




<
>
>
>
|
|
|
>
>
>
|
>
>
|
<
<
>
>
>
|
>
>
>
|
<
<
<
>
>
|
|

<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>
>
>



|
|
|
|
|
|
|
|
>
>
>




|





|
<
|
>

|
|
<
|
<
<
>

<
<
|
|



<
<
<
<
<
<
<
>
|
<
<
<
>


>
|

|
>


>



<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

1
2
3
4
5
6
7
8
9









10

11


12









13
14
15
16
17
18
19
20
21
22
23
24
25

26
27
28
29
30
31
32
33
34




35
36
37











38
39
40






41
42
43
44
45
46
47
48
49
50
51





52





53
54
55
56
57





58
59
60





61



62
63
64
65
66
67
68
69
70


71
72
73
74
75


76
77


78


79


80


81
82


83
84
85
86


87


88
89
90
91
92

93
94


95
96
97
98
99





100
101
102
103


104
105
106

107
108
109
110

111
112
113
114
115
116


117


118
119
120
121


122

123
124


125


126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143


144


145
146
147
148


149
150
151


152
153
154
155
156





157
158
159







160
161
162
163




164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
...
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254

255
256
257
258
259
260
261
262
263
264

265
266
267
268













269
270
271
272
273

274














275


276

277
278
279
280
281
282
283
284

285
286
287
288
289
290
291
...
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306


307

308
309
310
311
312
313
314
315
316

317
318
319
320
321
322
323
...
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
...
389
390
391
392
393
394
395
396
397


398
399
400







401
402
403
404
405
406
407
408
...
415
416
417
418
419
420
421
422
423






424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456























































































457


458

459
460












461
462
463
464
465
466
467
468
469
470
471
472
473

474
475
476
477
478
479
480
481

482
483
484


485
486
487




488
489
490
491



492
493
494
495
496
497
498


499
500
501
502
503

504
505



506
507
508


509
510
511
512
513
514
515
516


517
518
519
520
521
522

















523


































524
525
526
527
528
529

530

531

532
533
534
535
536
537
538
539
540
541

542
543
544
545
546
547
548
549
550
551
552
553
554
555

556
557


558
559
560
561

562
563
564
565
566
567
568
569
570
571
572
573
574


575
576
577
578
579
580
581
582



583
584
585
586
587

588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625

626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654

655
656
657
658
659

660


661
662


663
664
665
666
667







668
669



670
671
672
673
674
675
676
677
678
679
680
681
682
683



















































684
# TODO: j2j, see below

# TODO: idea: ... (end of citate) as button for decollapse citate.

namespace eval juick {

package require msgcat
package require http










variable scriptdir [file dirname [info script]]

::msgcat::mcload [file join $scriptdir msgs]












# Register plugin for further load/unload via GUI.
# Stop loading (by source) file, if plugin currently is not registered.
if {![::plugins::is_registered juick]} {
    ::plugins::register juick \
        -namespace [namespace current] \
        -source [info script] \
        -description [::msgcat::mc \
            "Whether the Juick plugin is loaded."] \
        -loadcommand [namespace code load] \
        -unloadcommand [namespace code unload]
    return
}


# XRDB options
option add *juick_number      blue         widgetDefault
option add *juick_private_fg  blue         widgetDefault
option add *juick_private_bg  #FF9A15      widgetDefault
option add *juick_citing      gray35       widgetDefault
option add *juick_nick        red          widgetDefault
option add *juick_tag         ForestGreen  widgetDefault
option add *juick_my_msg      gray         widgetDefault





# Global variables and constants
# ==============================












variable options
variable nicknames







# list of {tag_name xrdb_name xrdb_option} sequences
# used in 'configurator' proc
variable richtext_tags {
    juick_number  juick_number     foreground
    juick_private juick_private_fg foreground
    juick_private juick_private_bg background
    juick_citing  juick_citing     foreground
    juick_nick    juick_nick       foreground
    juick_tag     juick_tag        foreground
    juick_my_msg  juick_my_msg     foreground
}











# Tags which not configured (has not special colors),
# just contain some information:
# 1. juick_clickable  -- for juick_(number|nick|tag)
# 2. juick_id_*       -- for the same






variable commands {
    S U D BL WL HELP NICK LOGIN ON OFF VCARD PING INVITE
}









# list of {name priority} sequences
variable richtext_parsers {
    juick_md_url_square_brackets  49
    juick_md_url_round_brackets   49
    juick_number                  54
    juick_private                 48
    juick_citing                  48
    juick_nicks_tags              49
}



# list of {hook proc priority orig_proc} sequences
variable plugin_hooks {
    roster_push_hook          determine_juick_nick       99 ""
    draw_message_hook         ignore_server_messages      0 ""


    draw_message_hook         draw_message_handle        21 ""
    chat_window_click_hook    insert_by_click            99 ""


    chat_win_popup_menu_hook  add_chat_things_menu       20 ""


    rewrite_message_hook      rewrite_incoming_message   20 ""


    chat_send_message_hook    rewrite_subscribe_plus_cmd 19 ""


    generate_completions_hook juick_commands_comps       99 ""
    draw_message_hook         update_juick_tab            8 \


        ::plugins::update_tab::update
    draw_message_hook         add_number_to_tab_title    18 \
        ::::ifacetk::add_number_of_messages_to_title
}





# Compatibility with tkabber-1.0* and some tkabber-1.1*
set scriptname "tkabber-1.0-1.1-compatibility.tcl"
namespace eval [namespace parent] \
    [format {source [file join "%s" "%s"]} $scriptdir $scriptname]


# namespace juick
}



# Procedures
# ==========

proc juick::load {} {





    variable scriptdir
    variable richtext_parsers
    variable plugin_hooks



    namespace eval [namespace parent] \
        [format {source [file join "%s" utils.tcl]} $scriptdir]


    ::richtext::entity_state juick_configurator 1
    ::richtext::register_entity juick_configurator \
        -configurator [namespace current]::configurator


    foreach {name priority} $richtext_parsers {
        ::richtext::entity_state $name 1
        ::richtext::register_entity $name \
            -parser [list [namespace current]::parser $name] \
            -parser-priority $priority
    }





    foreach {hook proc priority orig_proc} $plugin_hooks {
        if {$orig_proc ne ""} {
            hook::remove $hook $orig_proc $priority
        }




        hook::add $hook [namespace current]::$proc $priority
    }





    # Create 'Customize -> Plugins -> Juick' menu
    # Note: we not touch it in 'unload' proc for ability of tuning parameters
    # with unloaded plugin.
    custom::defgroup Plugins [::msgcat::mc "Plugins options."] \
        -group Tkabber
    custom::defgroup Juick [::msgcat::mc "Juick settings."] \
        -group Plugins
    custom::defvar options(main_jid) "juick@juick.com/Juick" \
        [::msgcat::mc "Main Juick JID. This used for\
            forwarding things from other chats."] \
        -group Juick -type string
    custom::defvar options(special_update_juick_tab) 1 \
        [::msgcat::mc "Indicate as personal message only private\
            messages and replies to you.\nYour Juick nickname\
            determines at roster receiving, so after enable option\
            you need to reconnecting."] \
        -group Juick -type boolean
}





proc juick::unload {} {
    variable richtext_parsers
    variable plugin_hooks



    ::richtext::unregister_entity juick_configurator
    ::richtext::entity_state juick_configurator 0



    foreach {name _} $richtext_parsers {
        ::richtext::unregister_entity $name
        ::richtext::entity_state $name 0
    }






    foreach {hook proc priority orig_proc} $plugin_hooks {
        hook::remove $hook [namespace current]::$proc $priority








        if {$orig_proc ne ""} {
            hook::add $hook $orig_proc $priority
        }
    }




}

proc juick::determine_juick_nick {xlib jid name groups subsc ask} {
    variable options
    variable nicknames

    if {![info exists options(special_update_juick_tab)] \
        || ! $options(special_update_juick_tab)} \
    {
        return
    }

    if {![is_juick_jid $jid] || [info exists nicknames($jid)]} {
        return
    }

    # TODO
    # For Juick contacts connected via j2j transport is difficult
    # to determine user jid.
    # Idea: send iq register to proper j2j, get form, and read
    # second JID from it.
    if {[string first % $jid] >= 0} {
        return
    }

    set my_jid [connection_jid $xlib]
    set my_jid [::xmpp::jid::removeResource $my_jid]
    set nick_request_url "http://api.juick.com/users?jid=$my_jid"
................................................................................
    # If has no json package, try to parse by regexp.
    if {[catch {package require json}]} {
        if {![regexp {\[{.*"uname"\w*:\w*"([^"]+)".*}\]} $data -> uname]} {
            # For vim syntax highlighter: "
            return
        }

        set nicknames($jid) $uname
        return
    }

    set ds [::json::json2dict $data]

    if {[llength $ds] != 1} {
        return
................................................................................
            # If it fails, get uname value without dict command.
            foreach {key value} $d {
                switch -- $key {
                    uname { set uname $value }
                }
            }

            set nicknames($jid) $uname
            return
        }
    }

    if {[catch {dict get $d uname} uname]} {
        return
    }

    set nicknames($jid) $uname
}

proc juick::draw_message_handle {chatid from type body x} {
    if {[is_juick $chatid]} {
        # Allow juick::parser to process given chat as a juick chat.
        ::richtext::property_add {PROCESS_AS_JUICK_MESSAGE} {}
    } elseif {[is_jubo $chatid]} {
        # Allow juick::parser to process given chat as a jubo chat.
        ::richtext::property_add {PROCESS_AS_JUBO_MESSAGE} {}
    } else {
        # Disallow juick::parser to process message.
        return
    }


    set chatw [chat::chat_win $chatid]
    set jid [chat::get_jid $chatid]

    if {$jid ne $from} {
        ::richtext::render_message $chatw $body {juick_my_msg}
    } else {
        ::richtext::render_message $chatw $body {}
    }


    return stop
}

proc juick::is_allow_to_special_update_juick_tab {from type} {













    variable options
    return [expr {!([is_juick_jid $from] || [is_jubo_jid $from]) || \
        !($type eq "chat") || \
        !$options(special_update_juick_tab)}]
}
















proc juick::update_juick_tab {chatid from type body x} {


    if {[is_allow_to_special_update_juick_tab $from $type]} {

        ::plugins::update_tab::update $chatid $from $type $body $x
        return
    }

    # See ${PATH_TO_TKABBER}/plugins/chat/update_tab.tcl
    foreach xelem $x {
        ::xmpp::xml::split $xelem tag xmlns attrs cdata subels
        if {($tag eq "") && ($xmlns eq "tkabber:x:nolog")} \

        {
            return
        }
    }

    set cw [chat::winid $chatid]

................................................................................
    if {[is_personal_juick_message $from $body]} {
        tab_set_updated $cw 1 mesg_to_user
    } else {
        tab_set_updated $cw 1 message
    }
}

proc juick::ignore_server_messages {chatid from type body x} {
    if {([is_juick $chatid] || [is_jubo $chatid]) && $from eq ""} {
        return stop
    }
}

# Add number of messages from Juick to the tab title
proc juick::add_number_to_tab_title {chatid from type body x} {


    if {[is_allow_to_special_update_juick_tab $from $type]} {

        ::ifacetk::add_number_of_messages_to_title $chatid $from $type \
            $body $x
        return
    }

    # See ${PATH_TO_TKABBER}/ifacetk/iface.tcl
    foreach xelem $x {
        ::xmpp::xml::split $xelem tag xmlns attrs cdata subels
        if {($tag eq "") && ($xmlns eq "tkabber:x:nolog")} \

        {
            return
        }
    }

    if {[::ifacetk::chat_window_is_active $chatid]} return
    if {$from eq ""} return
................................................................................
        incr personal_msg($chatid)
    }

    ::ifacetk::update_chat_title $chatid
    ::ifacetk::update_main_window_title
}

proc juick::rewrite_incoming_message \
     {vxlib vfrom vid vtype vis_subject vsubject \
      vbody verr vthread vpriority vx} {
    upvar 2 $vfrom from
    upvar 2 $vtype type
    upvar 2 $vbody body
    upvar 2 $vx x

    if {![is_juick_jid $from] || ($type ne "chat")} {
        return
    }

#############################
# Remove jabber:x:oob element
    set newx {}

    foreach xe $x {
        ::xmpp::xml::split $xe tag xmlns attrs cdata subels

        if {$xmlns ne "jabber:x:oob"} {
            lappend newx $xe
        }
    }

    set x $newx

#############################
# Add GMT time
    foreach xe $x {
        ::xmpp::xml::split $xe tag xmlns attrs cdata subels

        if {$xmlns eq "http://juick.com/message"} {
            foreach {key val} $attrs {
                if {$key eq "ts"} {
                    set body "$val GMT\n$body"
                }
            }
        }
    }
}

proc juick::rewrite_subscribe_plus_cmd {chatid user body type} {
    if {![is_juick $chatid] || ($type ne "chat")} {
        return
    }

    if {[regexp {^[Ss] (#\d+)\+\s*$} $body -> thing]} {
        set xlib [chat::get_xlib $chatid]
        set jid [chat::get_jid $chatid]

................................................................................
        message::send_msg $xlib $jid -type chat -body "S $thing"
        message::send_msg $xlib $jid -type chat -body "$thing+"

        return stop
    }
}

proc juick::insert_by_click {chatid w x y} {
    variable options



    set ci [chat::input_win $chatid]
    set jid [::xmpp::jid::removeResource [chat::get_jid $chatid]]







    set thing [get_clickable_thing_at $w $x $y]
    if {$thing eq ""} return

    if {![is_juick_jid $jid]} {
        set xlib [chat::get_xlib $chatid]
        set mainchat [chat::chatid $xlib $options(main_jid)]

        if {[chat::is_opened $mainchat]} {
................................................................................
    }

    $ci insert insert "$thing "
    focus -force $ci
    return stop
}

proc juick::add_chat_things_menu {m chatwin X Y x y} {
    set thing [get_clickable_thing_at $chatwin $x $y]






    if {$thing eq ""} return

    $m add command -label [format [::msgcat::mc \
        "\[J\] Copy %s to clipboard"] $thing] \
        -command [list [namespace current]::copy_thing $chatwin $thing]

    $m add command -label [::msgcat::mc \
        "\[J\] Open in browser"]\
        -command [list [namespace current]::browse_thing $chatwin $thing]

    if {[regexp {#\d+$} $thing]} {
####################
        # Currently not implemented.
        if {0} {
            $m add command -label [::msgcat::mc \
                "\[J\] Open in new tab"] \
                -command [list [namespace current]::get_juick_thread \
                $chatwin $thing]
        }
####################

        $m add command -label [::msgcat::mc \
            "\[J\] Subscribe"] \
            -command [list [namespace current]::send_to_juick \
            $chatwin "S $thing"]

        $m add command -label [::msgcat::mc \
            "\[J\] Unsubscribe"] \
            -command [list [namespace current]::send_to_juick \
            $chatwin "U $thing"]
    }
}
























































































proc juick::juick_commands_comps {chatid compsvar wordstart line} {


    upvar 0 $compsvar comps

    variable commands













    if {![is_juick $chatid]} return

    # Collect chat things
    set chat_items {}
    set chatw [chat::chat_win $chatid]
    set tags [$chatw tag names]
    set ids [lsearch -glob -inline -all $tags juick_id_*]
    foreach id $ids {
        foreach {idx1 idx2} [$chatw tag ranges $id] {
            set thing [$chatw get $idx1 $idx2]
            set chat_items [linsert $chat_items 0 $idx1 $thing]
        }
    }


    # Sort chat things
    set chat_things {}
    set chat_items [lsort -real -decreasing -stride 2 -index 0 $chat_items]
    foreach {idx thing} $chat_items {
        lappend chat_things $thing
    }


    set comps [concat $chat_things $commands $comps]
}



# RichText stuff
# --------------





proc juick::configurator {w} {
    variable options
    variable richtext_tags




    # Seems hacky, but found in events::event_composing in Tkabber sources.
    set cw [join [lrange [split $w .] 0 end-1] .]
    set chatid [chat::winid_to_chatid $cw]
    if {![chat::is_chat $chatid]} { return }

    if {![is_juick $chatid] && ![is_jubo $chatid]} { return }



    foreach {tag_name xrdb_name xrdb_option} $richtext_tags {
        if {$xrdb_name ne ""} {
            set options($xrdb_name) [option get $w $xrdb_name Text]
            $w tag configure $tag_name -$xrdb_option $options($xrdb_name)
        }

    }




    # Display all things and citing in our message with 'my message' colors.
    $w tag raise juick_my_msg juick_citing



    # Display all things in citing with citing colors.
    # Priorities not affect clickableness.
    # Example:
    # > Text text #1234 @nick *tag text
    $w tag raise juick_citing juick_number
    $w tag raise juick_citing juick_nick
    $w tag raise juick_citing juick_tag



    # Just for certain and predictable priority.
    $w tag raise juick_number juick_private
    $w tag raise juick_nick   juick_private
    $w tag raise juick_tag    juick_private
}


















proc juick::parser_spot_md_url {ptype what at startVar endVar url_infoVar} {


































    variable ::plugins::urls::url_regexp
    upvar 1 $url_infoVar url_info

    switch -exact $ptype {
        juick_md_url_square_brackets {
            # [title][URL]

            set md_regexp {(\[([^\]]+)\]\[([^\]]+)\])}

        }

        juick_md_url_round_brackets {
            # [title](URL)
            set md_regexp {(\[([^\]]+)\]\(([^\)]+)\))}
        }
    }

    set matched [regexp -indices -start $at -- \
        $md_regexp $what -> \
        bounds title_bounds url_bounds]


    if {!$matched} { return false }

    lassign $title_bounds ts te
    lassign $url_bounds us ue
    set title [string range $what $ts $te]
    set url [string range $what $us $ue]

    set md_url_regexp [format "^%s$" $::plugins::urls::url_regexp]
    set matched [regexp -expanded -nocase -- $md_url_regexp $url]

    if {!$matched} { return false }

    upvar 1 $startVar uStart $endVar uEnd
    lassign $bounds uStart uEnd


    set url_info [list $title $url]



    return true
}


proc juick::parser_spot {ptype processing_type what at startVar endVar \
    url_infoVar} \
{
    upvar 1 $startVar uStart $endVar uEnd
    upvar 1 $url_infoVar url_info

    switch -glob $ptype {
        juick_md_url* {
            return [parser_spot_md_url $ptype $what $at uStart uEnd url_info]
        }
        juick_number {
            set re {(?:\s|\n|\A|\(|\>)(#\d+(/\d+)?)(?:(\.(\s|\n))?)}
        }


        juick_private {
            if {$processing_type != "juick"} { return false }
            set re {(^Private message)(?: from @.+:\n)}
        }
        juick_citing {
            if {$processing_type != "juick"} { return false }
            set re {(?:\n|\A)(>[^\n]+)}
        }



        juick_nicks_tags {
            set re {(?:\s|\n|\A|\(|\>|:)(@[\w@.-]+|\*[\w?!+'/.-@*]+)(?:(\.(\s|\n))?)}
        }
    }


    set matched [regexp -indices -start $at -- $re $what -> bounds]
    if {!$matched} { return false }
    lassign $bounds uStart uEnd
    set thing [string range $what $uStart $uEnd]

    return true
}

proc juick::update_tags_type {ptype thing id tagsVar typeVar} {
    upvar 1 $tagsVar tags
    upvar 1 $typeVar type

    if {$ptype eq "juick_nicks_tags"} {
        switch -exact [string index $thing 0] {
            "@" { set newtag juick_nick }
            "*" { set newtag juick_tag }
        }
    } else {
        set newtag $ptype
    }

    lappend tags $newtag

    if {[lsearch -exact {juick_number juick_nick juick_tag} $newtag] >= 0} {
        lappend tags juick_clickable
        lappend tags "juick_id_$id"
    }

    set type $newtag
}

proc juick::parser_write {ptype thing id tags url_info outVar} {
    upvar 1 $outVar out
    lassign $url_info title url

    if {[string match juick_md_url* $ptype]} {
        lappend out $url url $tags
        ::richtext::property_update url:title,$url $title

    } else {
        set type {}
        update_tags_type $ptype $thing $id tags type
        lappend out $thing $type $tags
    }
}

proc juick::parser {ptype atLevel accName} {
    upvar #$atLevel $accName chunks

    set processing_type {}
    if {[::richtext::property_exists {PROCESS_AS_JUICK_MESSAGE}]} {
        set processing_type "juick"
    } elseif {[::richtext::property_exists {PROCESS_AS_JUBO_MESSAGE}]} {
        set processing_type "jubo"
    } else {
        return
    }

    set out {}

    foreach {s type tags} $chunks {
        if {[lsearch -exact {text juick_citing} $type] == -1} {
            # pass through
            lappend out $s $type $tags
            continue
        }

        set index 0; set uStart 0; set uEnd 0


        set url_info {}

        while {[parser_spot $ptype $processing_type $s $index uStart uEnd \
            url_info]} \

        {


            # Write out text before current thing, if exists
            if {$uStart - $index > 0} {


                set text_before [string range $s $index [expr {$uStart - 1}]]
                lappend out $text_before $type $tags
            }

            set thing [string range $s $uStart $uEnd]







            set id "${thing}_${uStart}_${uEnd}"
            # Write out current thing



            parser_write $ptype $thing $id $tags $url_info out
            set index [expr {$uEnd + 1}]
        }

        # Write out text after the last thing, if exists
        if {[string length $s] - $index > 0} {
            set text_after [string range $s $index end]
            lappend out $text_after $type $tags
        }
    }

    set chunks $out
}




















































# vi:ts=4:et

Changes to juick/msgs/ru.msg.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
::msgcat::mcset ru "\[J\] Copy %s to clipboard" "\[J\] Скопировать %s в буфер обмена"
::msgcat::mcset ru "\[J\] Open in browser"      "\[J\] Открыть в браузере"
::msgcat::mcset ru "\[J\] Open in new tab"      "\[J\] Открыть в новой вкладке"
::msgcat::mcset ru "\[J\] Subscribe"            "\[J\] Подписаться"
::msgcat::mcset ru "\[J\] Unsubscribe"          "\[J\] Отписаться"

::msgcat::mcset ru "Whether the Juick plugin is loaded" \
"Загружено ли расширение для удобной работы с Juick"

::msgcat::mcset ru "Juick settings." \
"Параметры расширения для удобной работы с Juick."

::msgcat::mcset ru "Main Juick JID. This used for forwarding things from other chats." \
"Главный JID Juick'а. Используется для перенаправления сущностей из других чатов."

::msgcat::mcset ru "Indicate as personal message only private messages and replies to you." \
"Отмечать как персональные только приватные и адресованные вам сообщения."

::msgcat::mcset ru "Your Juick nickname determines at roster receiving, so after enable option you need to reconnecting." \
"Ваш псевдоним на Juick определяется при получении ростера, поэтому для работы опции после её включения требуется переподключение."






|
|





|

|
|
<
<
<
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17



::msgcat::mcset ru "\[J\] Copy %s to clipboard" "\[J\] Скопировать %s в буфер обмена"
::msgcat::mcset ru "\[J\] Open in browser"      "\[J\] Открыть в браузере"
::msgcat::mcset ru "\[J\] Open in new tab"      "\[J\] Открыть в новой вкладке"
::msgcat::mcset ru "\[J\] Subscribe"            "\[J\] Подписаться"
::msgcat::mcset ru "\[J\] Unsubscribe"          "\[J\] Отписаться"

::msgcat::mcset ru "Whether the Juick plugin is loaded." \
"Загружено ли расширение для удобной работы с Juick."

::msgcat::mcset ru "Juick settings." \
"Параметры расширения для удобной работы с Juick."

::msgcat::mcset ru "Main Juick JID. This used for forwarding things from other chats." \
"Главный JID Juickа. Используется для перенаправления сущностей из других чатов."

::msgcat::mcset ru "Indicate as personal message only private messages and replies to you.\nYour Juick nickname determines at roster receiving, so after enable option you need to reconnecting." \
"Отмечать как персональные только приватные и адресованные вам сообщения.\nВаш псевдоним на Juick определяется при получении ростера, поэтому для работы опции после её включения требуется переподключение."



Deleted juick/tkabber-0.11.1-compatibility.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
if {[info exist ::tkabber-0.11.1-compatibility-sentry]} {
    return
}

set ::tkabber-0.11.1-compatibility-sentry 1

rename ::hook::add ::hook::add_orig

proc ::hook::add {hook func {seq 50}} {
    if {[string equal $hook roster_push_hook] && \
        [string equal $func "::plugins::juick::request_juick_nick"] && \
        $seq == 99} \
    {
        ::hook::add_orig roster_item_hook $func $seq
    } else {
        ::hook::add_orig $hook $func $seq
    }
}

proc ::hook::remove {hook func {seq 50}} {
    variable $hook

    set idx [lsearch -exact [set $hook] [list $func $seq]]
    set $hook [lreplace [set $hook] $idx $idx]
}

proc ::plugins::is_registered {name} {
    variable juick_plugin_loaded

    if {![info exists juick_plugin_loaded]} {
        set juick_plugin_loaded 0
    }

    return $juick_plugin_loaded
}

proc ::plugins::register {name args} {
    variable juick_plugin_loaded

    if {$juick_plugin_loaded} {
        return
    }

    set juick_plugin_loaded 1

    foreach {key val} $args {
        switch -- $key {
            -source {
                source $val
            }
            -loadcommand {
                eval $val
            }
        }
    }
}

proc ::chat::get_xlib {chatid} {
    ::chat::get_connid $chatid
}

namespace eval ::xmpp::jid {}

proc ::xmpp::jid::node {jid} {
    set a [string first @ $jid]
    if {$a < 0} {
        return
    } else {
        set b [string first / $jid]
        if {$b >= 0 && $a > $b} {
            return
        } else {
            string range $jid 0 [incr a -1]
        }
    }
}

proc ::xmpp::jid::server {jid} {
    set a [string first @ $jid]
    set b [string first / $jid]

    if {$a < 0} {
        if {$b < 0} {
            return $jid
        } else {
            string range $jid 0 [incr b -1]
        }
    } else {
        if {$b < 0} {
            string range $jid [incr a] end
        } elseif {$a >= $b} {
            string range $jid 0 [incr b -1]
        } else {
            string range $jid [incr a] [incr b -1]
        }
    }
}

proc ::xmpp::jid::removeResource {jid} {
    set node     [node $jid]
    set server   [server $jid]

    if {[string equal $node ""]} {
        set jid $server
    } else {
        set jid $node@$server
    }

    return $jid
}

namespace eval ::xmpp::xml {}

proc ::xmpp::xml::split {xmldata tagVar xmlnsVar attrsVar \
                        cdataVar subelsVar} {
    upvar 1 $tagVar tag $xmlnsVar xmlns $attrsVar attrs $cdataVar cdata \
            $subelsVar subels

    jlib::wrapper:splitxml $xmldata tag attrs isempty cdata subels

    foreach {key val} $attrs {
        switch -- $key {
            xmlns {
                set xmlns $val
            }
        }
    }
}


proc ::xmpp::xml::create {tag args} {
    set invoke [list jlib::wrapper:createtag $tag -vars]

    foreach {key val} $args {
        switch -- $key {
            -xmlns {
                lappend invoke [list xmlns $val]
            }
            -attrs {
                lappend invoke $val
            }
        }
    }

    eval $invoke
}

proc ::xmpp::sendIQ {xlib type args} {
    set invoke [list jlib::send_iq $type]
    set xmldata {}
    set newargs {}

    foreach {key val} $args {
        switch -- $key {
            -query {
                set xmldata $val
            }
            -to {
                lappend newargs $key $val
            }
            -command {
                lappend newargs $key \
                    [list [namespace current]::sendIQAnswer $val]
            }
        }
    }

    lappend invoke $xmldata
    lappend invoke -connection $xlib

    foreach arg $newargs {
        lappend invoke $arg
    }

    eval $invoke
}

proc ::xmpp::sendIQAnswer {cmd res child} {
    if {[string equal $res OK]} {
        set res ok
    }

    uplevel #0 $cmd [list $res $child]
}

proc ::xmpp::xml::getAttr {attrList attrName {fallback ""}} {
    set res $fallback

    foreach {attr val} $attrList {
        if {[string equal $attr $attrName]} {
            set res $val
        }
    }

    return $res
}

proc ::chat::activate {chatid} {
    raise_win [winid $chatid]
    focus -force [input_win $chatid]
}

rename ::message::send_msg ::message::send_msg_tkabber_0_11_1

proc ::message::send_msg {xlib args} {
    set invoke [list ::message::send_msg_tkabber_0_11_1]

    if {[llength $args] % 2 == 0} {
        # tkabber-0.11.1 invoke
        set jid $xlib
        lappend invoke $jid
    } else {
        set jid [lindex $args 0]
        set args [lrange $args 1 end]
        lappend invoke $jid -connection $xlib
    }

    foreach {key val} $args {
        lappend invoke $key $val
    }

    eval $invoke
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































































































































































































































































































































Added juick/tkabber-1.0-1.1-compatibility.tcl.



























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
if {[info exists ::tkabber-1.0-1.1-compatibility-tcl-sentry]} {
    return
}

# Just for avoid double including.
set ::tkabber-1.0-1.1-compatibility-tcl-sentry 1

proc juick::patch_proc {proc_name replace_from replace_to fail_on_error} {
    set proc_args [info args $proc_name]
    set proc_body [info body $proc_name]

    set fst [string first $replace_from $proc_body]
    set lst [expr {$fst + [string length $replace_from] - 1}]

    if {$fst == -1} {
        if {$fail_on_error} {
            return -code error "Cannot patch $proc_name in [info script]"
        } else {
            return
        }
    }

    # Restore original arguments list with defaults.
    set proc_args_defs {}
    foreach arg $proc_args {
        if {[info default $proc_name $arg def]} {
            lappend proc_args_defs [list $arg $def]
        } else {
            lappend proc_args_defs $arg
        }
    }

    set new_proc_body [string replace $proc_body $fst $lst $replace_to]

    rename $proc_name ""
    uplevel #0 [format "proc %s {%s} {%s}" $proc_name $proc_args_defs $new_proc_body]
}

juick::patch_proc ::richtext::render_message \
    {$entities($type,parser) [info level] chunks} \
    {eval [linsert $entities($type,parser) end [info level] chunks]} \
    0

# Used once, already not needed.
rename juick::patch_proc ""

Added juick/utils.tcl.































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
if {[llength [info procs juick::juick-utils-tcl-sentry]] > 0} {
    return
}

# Just for avoid double including.
proc juick::juick-utils-tcl-sentry {} {}

proc juick::get_clickable_thing_at {w x y} {
    set tags [$w tag names "@$x,$y"]

    if {[lsearch -exact $tags juick_clickable] >= 0} {
        set id [lsearch -glob -inline $tags juick_id_*]
        if {$id == ""} { return "" }
        lassign [$w tag prevrange $id "@$x,$y + 1c"] idx1 idx2
        return [$w get $idx1 $idx2]
    }

    return ""
}

proc juick::is_juick_jid {jid} {
    set jid [::xmpp::jid::removeResource $jid]
    set node [::xmpp::jid::node $jid]
    return [expr {[string equal $jid "juick@juick.com"] || \
        [string equal $node "juick%juick.com"]}]
}

# Determines whether given chatid correspond to Juick
proc juick::is_juick {chatid} {
    set jid [chat::get_jid $chatid]
    return [is_juick_jid $jid]
}

proc juick::is_jubo_jid {jid} {
    set jid [::xmpp::jid::removeResource $jid]
    set node [::xmpp::jid::node $jid]
    return [expr {[string equal $jid "jubo@nologin.ru"] || \
        [string equal $node "jubo%nologin.ru"]}]
}

# Determines whether given chatid correspond to Jubo
proc juick::is_jubo {chatid} {
    set jid [chat::get_jid $chatid]
    return [is_jubo_jid $jid]
}

proc juick::get_my_juick_nickname {jid} {
    variable nicknames

    set uname ""
    set jid [::xmpp::jid::removeResource $jid]

    if {[info exists nicknames($jid)]} {
        set uname $nicknames($jid)
    }

    return $uname
}

proc juick::is_personal_juick_message {from body} {
    variable options

    if {![is_juick_jid $from]} { return 0 }

    set private_msg [regexp {^Private message from @.+:\n} $body]

    set reply_to_comment [regexp \
        {Reply by @[^\n ]+:\n>.+\n\n@([^\n ]+) .+\n\n#\d+/\d+ http://juick.com/\d+#\d+$} \
        $body -> reply_to_nick]

    if {$reply_to_comment} {
        set reply_to_me [string equal \
            [get_my_juick_nickname $from] $reply_to_nick]
    } else {
        set reply_to_me 0
    }

    return [expr {$private_msg || $reply_to_me}]
}

proc juick::send_to_juick {w body} {
    variable options
    set cw [join [lrange [split $w .] 0 end-1] .]
    set chatid [chat::winid_to_chatid $cw]
    set xlib [chat::get_xlib $chatid]
    set jid [chat::get_jid $chatid]

    if {![is_juick_jid $jid]} {
        set mainchat [chat::chatid $xlib $options(main_jid)]
        set jid [chat::get_jid $mainchat]
    }

    message::send_msg $xlib $jid -type chat -body $body
}

proc juick::get_juick_thread {w thing} {
    set cw [join [lrange [split $w .] 0 end-1] .]
    set chatid [chat::winid_to_chatid $cw]
    set xlib [chat::get_xlib $chatid]
    set jid [chat::get_jid $chatid]
    set mid [string range $thing 1 end]

    ::xmpp::sendIQ $xlib get \
        -query [::xmpp::xml::create query \
            -xmlns "http://juick.com/query#messages" \
            -attrs [list mid $mid]] \
        -to $jid \
        -command [list [namespace current]::receive_juick_thread $jid]
}

proc juick::receive_juick_thread {jid res child0} {
    if {![string equal $res ok]} return

    ::xmpp::xml::split $child0 tag0 xmlns0 attrs0 cdata0 subels0

    if {![string equal $xmlns0 "http://juick.com/query#messages"]} return

    set child1 [lindex $subels0 0]
    ::xmpp::xml::split $child1 tag1 xmlns1 attrs1 cdata1 subels1

    if {![string equal $xmlns1 "http://juick.com/message"]} return

    set msg ""

    foreach child2 $subels1 {
        ::xmpp::xml::split $child2 tag2 xmlns2 attrs2 cdata2 subels2
        switch -- $tag2 {
            body {
                set msg $cdata2
            }
        }
    }

    #puts "Get message from $jid: \"$msg\""

    # open new tab

    return
}

proc juick::copy_thing {w thing} {
    clipboard clear -displayof $w
    clipboard append -displayof $w $thing
}

proc juick::browse_thing {w thing} {
    switch -regexp -- $thing {
        {^#} {
          regsub -- "/" [string range $thing 1 end] "#" jurl
          browseurl http://juick.com/$jurl
        }
        {^@} {
          browseurl http://juick.com/[string range $thing 1 end]
        }
        {^\*} {
          browseurl http://juick.com/tag/[string range $thing 1 end]
        }
    }
}