diff --git a/README.md b/README.md
@@ -0,0 +1,360 @@
+# WeeChat
+> backup of my weechat setup
+
+## Table of Contents
+- [Setup](#setup)
+ - [WeeChat](#weechat)
+ - [Relay](#relay)
+- [Settings](#settings)
+ - [Appearance](#appearance)
+ - [Settings](#appearance)
+ - [IRC](#appearance)
+ - [CTCP](#appearance)
+ - [Scripts](#appearance)
+- [Triggers](#triggers)
+- [Servers](#servers)
+- [Services](#services)
+- [Proxy](#proxy)
+- [Relay](#relay)
+- [Keys](#keys)
+- [Todo](#todo)
+
+---
+
+### Setup
+###### WeeChat
+```shell
+weechat -P "alias,buflist,charset,exec,fifo,fset,irc,perl,python,relay,script,trigger" -r "/set weechat.plugin.autoload alias,buflist,charset,exec,fifo,fset,irc,perl,python,relay,script,trigger;/save;/quit"
+rm $HOME/.weechat/weechat.log && chmod 700 $HOME/.weechat && mkdir $HOME/.weechat/ssl
+git clone --depth 1 https://github.com/acidvegas/weechat.git $HOME/weechat
+mv $HOM E/weechat/alias.conf $HOME/.weechat/alias.conf && mv $HOME/weechat/scripts/perl/*.pl $HOME/.weechat/perl/autoload/ && mv $HOME/weechat/scripts/python/*.py $HOME/.weechat/python/autoload/
+mkdir $HOME/.weechat/logs
+openssl req -x509 -new -newkey rsa:4096 -sha256 -days 3650 -out $HOME/.weechat/ssl/cert.pem -keyout $HOME/.weechat/ssl/cert.pem
+chmod 400 $HOME/.weechat/ssl/cert.pem
+```
+
+###### Relay
+```shell
+certbot certonly --standalone -d chat.acid.vegas -m acid.vegas@acid.vegas
+echo -e "[Unit]\nDescription=cerbot renewal\n\n[Service]\nType=oneshot\nExecStart=/usr/bin/certbot renew -n --quiet --agree-tos --deploy-hook /home/acidvegas/.weechat/renew" > /etc/systemd/system/certbot.service
+echo -e "[Unit]\nDescription=cerbot renewal timer\n\n[Timer]\nOnCalendar=0/12:00:00\nRandomizedDelaySec=1h\nPersistent=true\n\n[Install]\nWantedBy=timers.target" > /etc/systemd/system/certbot.timer
+systemctl enable certbot.timer && systemctl start certbot.timer
+
+echo "#!/bin/bash" > /home/acidvegas/.weechat/renew
+echo "cat /etc/letsencrypt/live/chat.acid.vegas/fullchain.pem /etc/letsencrypt/live/chat.acid.vegas/privkey.pem > /home/acidvegas/.weechat/ssl/relay.pem" >> /home/acidvegas/.weechat/renew
+echo "chown -R acidvegas:acidvegas /home/acidvegas/.weechat/ssl/relay.pem && chmod 400 /home/acidvegas/.weechat/ssl/relay.pem" >> /home/acidvegas/.weechat/renew
+echo "printf '%b' '*/relay sslcertkey\n' > /home/acidvegas/.weechat/weechat_fifo" >> /home/acidvegas/.weechat/renew
+chmod +x /home/acidvegas/.weechat/renew
+
+mkdir -p $HOME/.config/systemd/user
+echo -e "[Unit]\nDescription=headless weechat relay service\nAfter=network.target\n\n[Service]\nType=forking\nExecStart=/usr/bin/weechat-headless --daemon\n\n[Install]\nWantedBy=default.target" > $HOME/.config/systemd/user/weechat-headless.service
+systemctl --user enable weechat-headless
+```
+
+---
+
+### Settings
+###### Appearance
+```
+/set buflist.format.buffer "${if:${type}==server?${if:${window[gui_current_window].buffer.local_variables.server}==${buffer.local_variables.server}?${if:${irc_server.is_connected}?${color:green,235}:${color:lightred,235}}• ${color:default,235}${name}:${if:${irc_server.is_connected}?${color:green,235}:${color:lightred,235}}• ${color:default,235}${indent}${name}}:}${if:${type}=~(channel|private)?${color_hotlist}${indent}${name}:}${if:${type}!~(channel|private|server)?${color:gray}${name}:}"
+/set buflist.format.buffer_current "${if:${type}==server?${if:${window[gui_current_window].buffer.local_variables.server}==${buffer.local_variables.server}?${color:lightred}${if:${irc_server.is_connected}?${color:green,235}:${color:lightred,235}}• ${name}${format_hotlist}:${color:237}${if:${irc_server.is_connected}?${color:green,235}:${color:lightred,235}}• ${name}}${format_lag}${format_hotlist}:${if:${type}=~(channel|private)?• ${name}:${if:${type}!~(channel|private|server)?${color:lightblue}${name}:}}}"
+/set buflist.format.hotlist_highlight "${color:yellow}"
+/set buflist.format.hotlist_message "${color:cyan}"
+/set buflist.format.hotlist_private "${color:yellow}"
+/set irc.color.input_nick default
+/set irc.color.nick_prefixes "y:green;q:green;a:lightred;o:red;h:yellow;v:lightblue;*:lightmagenta"
+/set irc.color.reason_quit darkgray
+/set irc.color.topic_new lightblue
+/set irc.look.display_join_message ""
+/set irc.look.display_old_topic off
+/set irc.look.item_nick_modes off
+/set irc.look.server_buffer independent
+/set weechat.bar.buflist.size_max 20
+/set weechat.bar.fset.separator off
+/set weechat.bar.input.color_delim darkgray
+/set weechat.bar.input.conditions "${window.buffer.full_name} != perl.highmon"
+/set weechat.bar.input.items "[input_prompt]+(away),[input_search],[input_paste],input_text"
+/set weechat.bar.input.separator off
+/set weechat.bar.nicklist.size_max 15
+/set weechat.bar.status.color_bg default
+/set weechat.bar.status.color_delim darkgray
+/set weechat.bar.status.conditions "${window.buffer.full_name} != perl.highmon"
+/set weechat.bar.status.items "buffer_name+(buffer_modes)+[buffer_nicklist_count]"
+/set weechat.bar.status.separator off
+/set weechat.bar.title.color_bg black
+/set weechat.bar.title.separator off
+/set weechat.bar.title.size_max 2
+/set weechat.look.chat_space_right on
+/set weechat.color.chat_delimiters darkgray
+/set weechat.color.chat_highlight_bg default
+/set weechat.color.chat_host darkgray
+/set weechat.color.chat_nick white
+/set weechat.color.chat_nick_colors "cyan,magenta,green,brown,lightblue,default,lightcyan,lightmagenta,lightgreen,blue,31,35,38,40,49,63,70,80,92,99,112,126,130,138,142,148,160,162,167,169,174,176,178,184,186,210,212,215,247"
+/set weechat.color.chat_prefix_error lightred
+/set weechat.color.chat_prefix_network lightblue
+/set weechat.color.chat_prefix_suffix darkgray
+/set weechat.color.chat_read_marker darkgray
+/set weechat.color.chat_time 235
+/set weechat.color.chat_time_delimiters 235
+/set weechat.color.separator darkgray
+/set weechat.color.status_name_ssl white
+/set weechat.look.bar_more_down "▼"
+/set weechat.look.bar_more_left "◀"
+/set weechat.look.bar_more_right "▶"
+/set weechat.look.bar_more_up "▲"
+/set weechat.look.buffer_time_format " %H:%M"
+/set weechat.look.day_change off
+/set weechat.look.item_buffer_filter "•"
+/set weechat.look.prefix_align_max 15
+/set weechat.look.prefix_join "▬▬▶"
+/set weechat.look.prefix_quit "◀▬▬"
+/set weechat.look.prefix_suffix "│"
+/set weechat.look.quote_time_format "%H:%M"
+/set weechat.look.read_marker_string "─"
+/set weechat.look.separator_horizontal "─"
+/set weechat.look.separator_vertical "│"
+/set weechat.look.window_title "hardchats"
+/set weechat.startup.display_logo off
+/set weechat.startup.display_version off
+```
+
+###### Settings
+```
+/set buflist.look.mouse_wheel off
+/set buflist.look.mouse off
+/set fifo.file.path "${weechat_data_dir}/weechat_fifo"
+/set irc.look.buffer_switch_autojoin off
+/set irc.look.buffer_switch_join on
+/set irc.look.join_auto_add_chantype on
+/set irc.look.smart_filter off
+/set irc.look.temporary_servers on
+/set irc.network.ban_mask_default "*!*@$host"
+/set logger.file.auto_log off
+/set sec.crypt.hash_algo sha512
+/set weechat.look.confirm_quit on
+/set weechat.look.highlight "acidvegas,supernets,super nets"
+/set weechat.look.mouse on
+/set weechat.plugin.autoload "alias,buflist,charset,exec,fifo,fset,irc,perl,python,relay,script,trigger"
+```
+
+###### IRC
+```
+/set irc.server_default.anti_flood_prio_high 0
+/set irc.server_default.anti_flood_prio_low 0
+/set irc.server_default.autorejoin on
+/set irc.server_default.autorejoin_delay 3
+/set irc.server_default.command_delay 3
+/set irc.server_default.msg_part "G-line: User has been permanently banned from this network."
+/set irc.server_default.msg_quit "G-line: User has been permanently banned from this network."
+/set irc.server_default.nicks "acidvegas,acid_vegas,acid.vegas,acidvegas_"
+/set irc.server_default.realname "04MOST DANGEROUS MOTHERFUCK"
+/set irc.server_default.sasl_mechanism external
+/set irc.server_default.sasl_username "acidvegas"
+/set irc.server_default.ssl_cert "%h/ssl/cert.pem"
+/set irc.server_default.ssl_password "REDACTED"
+/set irc.server_default.ssl_verify off
+/set irc.server_default.username "stillfree"
+```
+
+###### CTCP
+```
+/set irc.ctcp.clientinfo ""
+/set irc.ctcp.finger ""
+/set irc.ctcp.ping ""
+/set irc.ctcp.source ""
+/set irc.ctcp.time ""
+/set irc.ctcp.userinfo ""
+/set irc.ctcp.version ""
+/set irc.look.ctcp_time_format ""
+/set irc.look.display_ctcp_blocked off
+/set irc.look.display_ctcp_reply off
+/set irc.look.display_ctcp_unknown off
+```
+
+###### Scripts
+```
+/set plugins.var.perl.highmon.first_run false
+/set plugins.var.perl.highmon.short_names on
+/set plugins.var.perl.keepnick.default_enable 1
+/set plugins.var.perl.multiline.weechat_paste_fix "off"
+```
+
+---
+
+### Triggers
+```
+/trigger del beep
+/trigger add hate modifier irc_out1_PRIVMSG "" "/hate/04 HATE "
+/trigger add input_command_color modifier "500|input_text_display" "${tg_string} =~ ^/($|[^/])" "#/(.+)#${color:39}/${color:74}${re:1}#"
+/trigger add numberjump modifier "2000|input_text_for_buffer" "${tg_string} =~ ^/[0-9]+$" "=\/([0-9]+)=/buffer *${re:1}=" "" "" "none"
+/trigger add url_color modifier "weechat_print" "${tg_tags} !~ irc_quit" ";[a-z]+://\S+;${color:32}${color:underline}${re:0}${color:-underline}${color:reset};" ""
+/trigger add relay_awayclear signal relay_client_connected "" "" "/away -all"
+/trigger add relay_setaway signal relay_client_disconnected "" "" "/away -all I am away"
+/trigger add relay_setaway signal relay_client_disconnected "${info:relay_client_count,connected} == 0" "" "/away -all I am away"
+```
+
+---
+
+### Servers
+```
+/server add 2f30 irc.2f30.org/6697 -tls
+/server add anope irc.anope.org/6697 -tls
+/server add blcknd irc.blcknd.net/6697 -tls
+/server add efnet irc.servercentral.net/9999 -tls
+/server add libera irc.libera.chat/6697 -tls
+/server add gamesurge irc.gamesurge.net
+/server add ircd ircd.chat/6697 -tls
+/server add ircstorm irc.ircstorm.net/6699 -tls
+/server add malvager irc.malvager.net/6697 -tls
+/serber add netsec irc.priv8.chat/6697 -tls
+/server add oftc irc.oftc.net/6697 -tls
+/server add sandnet irc.sandngz.net/6697 -tls
+/server add silph irc.silph.co/6697 -tls
+/server add supernets irc.supernets.org/6697 -tls
+/server add twisted irc.twistednet.org/6697 -tls
+/server add unreal irc.unrealircd.org/6697 -tls
+/server add wormnet wormnet1.team17.com
+/server add wtfux irc.wtfux.org/6697 -tls
+
+/set irc.server.2f30.autojoin #2f30
+/set irc.server.anope.autojoin #anope
+/set irc.server.blacknd.autojoin #blacknd,#chat
+/set irc.server.efnet.autojoin #2600,#efnetnews,#exchange,#irc30,#lrh
+/set irc.server.libera.autojoin #archlinux,#ircv3,#matrix,#music-theory,#python,#raspberrypi,#weechat
+/set irc.server.gamesurge.autojoin #worms
+/set irc.server.ircd.autojoin #tcpdirect
+/set irc.server.malvager.autojoin #malvager
+/set irc.server.netsec.autojoin #ch@s
+/set irc.server.sandnet.autojoin #arab
+/set irc.server.silph.autojoin #ramen
+/set irc.server.twisted.autojoin #dev,#Twisted
+/set irc.server.unreal.autojoin #unreal-support
+/set irc.server.unreal.command /MODE acidvegas -x
+/set irc.server.wormnet.autojoin #anythinggoes
+/set irc.server.wormnet.password ELSILRACLIHP
+/set irc.server.wormnet.realname "48 0 US 3.7.2.1"
+/set irc.server.wtfux.autojoin #ED,#wtfux
+```
+
+---
+
+### Services
+```
+/secure passphrase PASSWORD
+/secure set NETWORK PASSWORD
+/set irc.server.networkname.command "/msg NickServ IDENTIFY ${sec.data.networkname}
+
+/msg NickServ register PASSWORD EMAIL
+/msg NickServ ACCESS DEL CHANGEME
+/msg NickServ ACCESS ADD *@big.dick.acid.vegas
+/msg NickServ AJOIN ADD <channel>
+/msg NickServ CERT ADD
+/msg NickServ SET AUTOOP ON
+/msg NickServ SET HIDE EMAIL ON
+/msg NickServ SET HIDE STATUS ON
+/msg NickServ SET HIDE USERMASK ON
+/msg NickServ SET HIDE QUIT ON
+/msg NickServ SET KEEPMODES ON
+/msg NickServ SET KILL QUICK
+/msg NickServ SET PRIVATE ON
+/msg NickServ SET SECURE ON
+/msg HostServ REQUEST MOST.DANGEROUS.MOTHER.FUCK
+/msg HostServ ON
+
+```
+
+---
+
+### Proxy
+```
+/proxy add tor socks5 127.0.0.1 9050
+/set irc.server.CHANGEME.proxy tor
+```
+
+---
+
+### Relay
+```
+/secure set relay PASSWORD
+/secure set totp SECRET
+/set relay.network.max_clients 2
+/set relay.network.password ${sec.data.relay}
+/set relay.network.totp_secret ${sec.data.totp}
+/relay sslcertkey
+/relay add ssl.weechat PORT
+```
+
+---
+
+### Keys
+| Keys | Description | Command |
+| --------- | ------------------------------------- | ----------------------------------- |
+| `ALT + n` | Scroll to next highlight | `/window scroll_next_highlight` |
+| `ALT + p` | Scroll to previous highlight | `/window scroll_previous_highlight` |
+| `ALT + u` | Scroll to first unread line in buffer | `/window scroll_unread` |
+
+| Keys | Description | Command |
+| ------------- | ------------------------ | ----------------------------- |
+| `Left` | Move cursor to the left | `/input move_previous_char` |
+| `Right` | Move cursor to the right | `/input move_next_char` |
+| `Backspace` | Delete character | `/input delete_previous_char` |
+| `Enter` | Send | `/input return` |
+| `ALT + Enter` | Insert new line | `/input insert \n` |
+
+| Keys | Description | Command |
+| ------------------ | ------------------- | ------------------------- |
+| `CTRL + r` | Search text | `/input search_text_here` |
+| `CTRL + y` | Paste | `/input clipboard_paste` |
+| `CTRL + l` | Refresh window | `/window refresh` |
+| `ALT + l` | Toggle bare display | `/window bare` |
+| `Alt + Shift + b` | Toggle buffer list | `/bar toggle buflist` |
+| `Alt + Shift + n` | Toggle nicklist | `/bar toggle buflist` |
+
+| Keys | Description | Command |
+| ------------- | ------------------- | -------------------------- |
+| `Tab` | Complete next | `/input complete_next` |
+| `Shift + Tab` | Complete previous | `/input complete_previous` |
+
+| Keys | Description | Command |
+| ------------- | ---------------------------------- | -------------------------------- |
+| `Up` | Show previous input history | `/input history_previous` |
+| `Down` | Show next input history | `/input history_next` |
+| `CTRL + Up` | Show previous global input history | `/input history_global_previous` |
+| `CTRL + Down` | Show next global input history | `/input history_global_next` |
+
+| Keys | Description | Command |
+| ------------ | --------------------- | ------------ |
+| `ALT + #` | Go to Nth buffer | `/buffer *N` |
+| `ALT + Up` | Go to previous buffer | `/buffer -1` |
+| `ALT + Down` | Go to next buffer | `/buffer +1` |
+
+| Keys | Description | Command |
+| ------------ | ----------------------------------------- | ----------------------- |
+| `PgUp` | Scroll up one page in buffer history | `/window page_up` |
+| `PgDn` | Scroll down one page in buffer history | `/window page_down` |
+| `ALT + PgUp` | Scroll up a few lines in buffer history | `/window scroll_up` |
+| `ALT + PgDn` | Scroll down a few lines in buffer history | `/window scroll_down` |
+| `ALT + Home` | Scroll to top of buffer | `/window scroll_top` |
+| `ALT + End` | Scroll to bottom of buffer | `/window scroll_bottom` |
+
+| Keys | Description | Command |
+| -------------- | ------------------------------- | -------------------- |
+| `Ctrl + c, b` | Insert code for bold text | `/input insert \x02` |
+| `Ctrl + c, c` | Insert code for colored text | `/input insert \x03` |
+| `Ctrl + c, i` | Insert code for italic text | `/input insert \x1D` |
+| `Ctrl + c, o` | Insert code for color reset | `/input insert \x0F` |
+| `Ctrl + c, v` | Insert code for reverse color | `/input insert \x16` |
+| `Ctrl + c, _` | Insert code for underlined text | `/input insert \x1F` |
+
+---
+
+### Todo
+- Information on commands for scripts
+- tdfiglet & curling git for ascii
+- `sudo cpan install Pod::Select` for multiline.pl
+
+## Mirrors
+- [acid.vegas](https://git.acid.vegas/weechat)
+- [GitHub](https://github.com/acidvegas/weechat)
+- [GitLab](https://gitlab.com/acidvegas/weechat)
+- [SuperNETs](https://git.supernets.org/acidvegas/weechat)
+\ No newline at end of file
diff --git a/alias.conf b/alias.conf
@@ -0,0 +1,108 @@
+# acidvegas alias.conf for weechat - https://git.acid.vegas/weechat
+# bold | color | underline | reset
+
+[cmd]
+# Art
+cig = "say 8,7\;.`-,:.`\;08,08 15,0||||||||||||||||||||4,14▄▀8,01▄▀14-\;`,-\;`"
+cigtoss = "me throws $1 an ice cold 8,7\;.`-,:.`\;08,08 15,0||||||||||||||||||||4,14▄▀8,01▄▀14-\;`,-\;`"
+derp = "say 0,1 10,10 11,10 10,10 0,1 1,1 ; say 0,1 11,10______0,1 1,1 ; say 0,1 11,10|10,10 11,10|10,10 11,10|10,10 11,10|10,10 0,1 1,1 ; say 0,1 8,8 1,8__8,8 1,8__8,8 0,1 1,1 ; say 8,8 1,0o8,8 1,0.8,8 1,1 ; say 0,1 8,8 1,8<.8,8 1,1 ; say 0,1 8,8 0,1 1,1 ; say 0,1 8,8 0,1,'8,8 0,1 1,1 ; say 0,1 8,8 0,1 1,1.;"
+lovecock = "say 0,1 ; say 0,1 5,7#######0,1 ; say 0,1 5,7#####0 0,1 hey guys im $1, let me tell ; say 0,1 5,7#####0 5_1 5)0,1 you a little about myself... ; say 0,1 5,7#####0 0100 1o0 0,1 1 ; say 0,1 5,7###1@5# 0 1 0 0,1 i LOVE cock ; say 0,1 5,7#0 5# 0 1>0 0,1 ; say 0,1 5,7#0 5\;\;\;\;\;\;\;\;0,1 i LOVE cock ; say 0,1 0,7 0,1 ; say 0,1 0,7 0,1 1 0 ; say 0,1 0,7 1 5...W.0,1 I ROMANTICIZE THE NOTION ; say 0,1 0,7 1 5...0,1 OF HAVING COCKS AND BALLS ; say 0,1 0,7 0,1 ON ME AT ALL TIMES OF MY ; say 0,7 1 >0,1 LIFE. ; say 0,7 0,1;"
+pump = "exec -o curl -s 'https://raw.githubusercontent.com/ircart/ircart/master/ircart/$0.txt'"
+umad = "say 0,1<$1> umadyet? 0,7 0,1 ; say 0,1<$1> hemad 0,7 0,8 0,1 0,8 0,1 4hehe am ownin these fkn noobs; say 0,1<$1> umad 0,8 0,1 ; say 0,1<$1> umad 0,8 0,1 ; say 0,1<$1> umadyet? 0,5 0,8 0,1 0,15 0,1 ; say 0,1<$1> nou 0,5 0,2 0,1 0,11 0,15 0,1 ; say 0,1<$1> umad? 0,5 0,2 0,8 0,1 0,11 0,15 0,1 ; say 0,1<$1> umad 0,5 0,2 0,8 0,1 0,11 0,15 0,1 ; say 0,1<$1> cum @ me bra 0,5 0,2 0,8 0,1 0,15 0,1 ; say 0,1<$1> umad 0,5 0,2 0,1 0,8 0,1 0,8 0,1 0,5 ; say 0,1<$1> umadyet? 0,5 0,2 0,1 0,15 0,5 0,1 0,5 ; say 0,1<$1> umad 0,5 0,8 0,1 0,5 0,1 0,5 ; say 0,1<$1> somad 0,5 0,8 0,1 0,5 0,1 0,5 ; say 0,1<$1> umadyet? 0,5 0,14 0,1 0,5 0,1 0,15 0,5 ; say 0,1<$1> umad 0,5 0,1 0,14 0,1 0,5 0,1 0,15 0,5 ; say 0,1<$1> hes ragin 0,5 0,1 0,5 0,1 0,14 0,1 5 0,5 0,1 0,15 0,5 ; say 0,1<$1> umad 0,5 0,1 0,5 0,1 0,4 0,14 0,1 5 0,5 0,1 0,15 0,5 ; say 0,1<$1> umadyet? 0,5 0,1 0,5 0,1 0,4 0,1 5 0,5 0,1 0,15 0,5 ;"
+
+# Alerts
+chat_norm = "say 8,5▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄ NORMAL CHATS 8,5▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄"
+chat_srs = "say 8,5▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄ SERIOUS CHATS 8,5▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄"
+emergency = "say 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!0,4 IRC EMERGENCY 8!1,8!8!"
+flashbang = "cflood -fg 0 -bg 8 FLASHBANG"
+prison = "say 04----------------------------------------------------------------------[IMPENETRABLE IRC PRISON WALL]-----------------------------------------------------------------------"
+terminate = "kick $1 08,04 T E R M I N A T E D "
+warning = "say 1,8/!\08,04 WARNING 1,8/!\ 04$*"
+
+# Chatters
+dm_ssh = "say i did a video on hiding my ssh banner a while back"
+dzl_credit = "say im with a credit union"
+dzl_26 = "say i dont troll because im 26"
+dzl_job = "say i need a better job im too good at entry level shit"
+dzl_reality = "say nice perception of reality"
+tp_boom = "say just wait till i get a supernets then enough http requests then boom"
+tp_dumb = "say your suck a dumb fucking idiot..."
+
+# Faces
+shrug = "say ¯\_(ツ)_/¯"
+shades = "say (⌐■_■)"
+srs = "say (ಠ_ಠ)"
+
+# Random
+beer = "me throws $1 an ice cold 0,0 0,2 BUD 1,0c =)"
+beer2 = "me throw $1 an ice cold 0,0 0,5 BUD 1,0c =)"
+blog = "say Hi! It looks like you're blogging on IRC. Would you like to: 2[Add Comments] 2[e-Mail this to a Friend] 2[Digg This!] 6[Submit to Slashdot] 2[Add to del.icio.us] 5[Kill yourself because0⬉5 nobody cares]"
+chatrain = "say ya nice; say chillin u?; say nice ty np; say ok nice %% true; say same"
+coffee = "me hands $1 a 5,2""] of coffee! =)"
+cooldown = "say 1,8/!\8,4 WARNING 1,8/!\ 4This keyboard is currently on 12COOL DOWN4 until $1 can handle it."
+cooled = "say [9ALERT] This keyboard is no longer on fire. Chats may proceed accordingly."
+doritos = "say ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄ ▲ ▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ ◄▼ ◄ ▲ ► ▼ ◄ ▲ ► ▼ Ah Mannn! I dropped my bag of Doritos!!!"
+en_gold = "say You must have an EFNet 8,7GOLD account to view this message."
+fadeop = "me 14<02Mode14> [-o $114] by $nick"
+fakick = "me 14<05Kick14> $1 was kicked by $nick 14[$2-14]"
+fred = "say 4()_ <- FRED DURST'S HAT LOL"
+gotem = "say 1,0 GOT EM "
+hate = "say 4 HATE 4 HATE 4 HATE "
+mail = "notice $channel You have unread messages from another user! Type /server mail read to read them."
+wave = "cflood ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"
+weed = "say 9DUUUUUUUUUUUUUUUUUUUUUUUUUUUUDE DUDE DUDE DUDE DUDE DUDE DUDE DUDE FUCKING WEEEEEEEEED AHAHAHAHAHAHAHA DUDE!!!!!!!!!! WEED!!!!!!!!!!!! *hits bong* FUCKING DUUUUUUDE that WEEED like just...................DUDE"
+
+# Text
+1stup = "say YO BITCH IM THE 1ST UP IN THIS PIECE. RESPECT MY ABILITY TO BE UP EARLY AND CHAT BEFORE YOU COCK SUCKER!!!!!!!!!"
+chatmaster = "say YOUR CHAT MASTER WILL RETURN SHORTLY"
+dna = "say i got your moms dna inserted into each of my sperms and everytime i fuck you your mom is somehow cumming inside you"
+dummies = "me closes IRC For Dummies"
+extroverts = "say bruh i hate extroverts yall niggas never shut the fuck up"
+fbi = "notice $channel The Federal Bureau of Investigation logged a record of your entry into this illegal chatroom along with your IP address due to potential violations of 18 U.S.C. º 1030. Your IP address is entered into our criminaldatabase as well as the Department of Homeland Security. If you are not associated with these claims we strongly advise you to type '/part'. Thank you."
+heartbeat = "say $1: your heart will skip one (1) beat in -5 seconds; say do not read this message;"
+icmp = "say how about i do a nice icmp ping 2 u, figure out where you are and what you are on, spawn your command.com to a port of my choice and log in with telnet and have a field day with you"
+ircjack = "say remember when i turned your IRC to a CNAME grabbed a valid cert and had your old IRC server DDoS'd so you and everyone else reconnected with their clients with no warning to irc.supernets.org?"
+limp = "say OR YOULL BE LEAVIN WITH A FAT LIP; say ITS ALL ABOUT THE HE SAYS SHE SAYS BULL SHIT.; say I THINK YOU BETTER QUIT TALKIN THAT SHIT PUNK; say SO COME ON AND GET IT.; say ITS JUST ONE OF THOSE DAYS"
+knuckz = "say my knuckz: (C)(H)(A)(T)b d(L)(I)(F)(E)"
+linkin = "say CRAWLING IN MY SKIN. THESE WOUNDS THEY WILL NOT HEAL. FEAR IS HOW I FALL. CONFUSING WHAT IS REAL."
+money = "say u look like a fucking money irl"
+pasta = "say yo bitch what kinda pasta you into, im all about barbine, bavette, bigoli, bucatini, busiate, capellini, fedellini, ferrazzuoli, fettuccine, fileja, linguine, lagane, lasagna, lasagnette, lasagnotte, maccheroni alla molinara. maccheroncini di campofilone mafalde, matriciani, pappardelle, perciatteli, picagge, pici, pillus, rustiche, sagne 'ncannulate, scialatelli, spaghetti, spagetthi alla chitarra, spaghettini, spaghettoni, stringozzi, su filindeu, tagliatelle, taglierini, trenette, tripoline, vermicelli, ziti, anelli, boccoli, calamarata, campanelle, cappeli da chef, casarecce, casacatelli, castellane, cavatappi, cavatelli, chifferi, cicioneddos, conchiglie, creste di galli, fagioloni, farfalle, fazzoletti, festoni, fiorentine, fiori, fusilli, fusilli bucati, garganelli, gemelli, gnocchi, lanterne, lorighittas, macaroni, maccheroncelli, mafaldine, malloreddus, mandala, marrile, mezzani, mezze maniche, mezze penne, mezzi, bombardoni, nuvole, paccheri, paϟϟatelli, pasta el ceppo, penne, penne ricce, picchiarelli, pipe rigate, pizzoccheri, quadrefiore, radiatory, ricciollini, ricciutelle, rigatoncini, rigatoni, rombi, rotelle, sagnette, sagneralli, sedani, spirali, strapponi, strozzaperti, testaroli, tortigiloni, treccioni, trenne, trofie, trottole, tuffoli, vesuvio, cencioni, corzetti, fainelle, fogile, d'ulivo, orecchiette, acini de pepe, alphabet pasta, anchellini, anelli, anellini, armonie, conchigliette, corquilettes, coralli, corallini, cuscuϟϟu, ditali, egg barley, fideos, filini, fregula, funghini, gianduietta, grano, gramigne, grattini, grattoni, margerthine, merletti, midolline, occhi di paϟϟero, orzo, pastina, piombi, ptitim, puntine, quadrettini, sorprese, stelle, stortini, tripolini, agnolini, agnolotti, caccavelle, canneloni, cappelletti, caramelle, casoncelli, casunziei, conchiglioni, culurgiones, fagottini, lumache, mezzelune, occhi di lupo, pansotti, ravioli, rotolo ripieno, sacchettoni, tortelli, tortelloni, tufoli, canederli, donderet"
+operscum = "say I have like 50,000 shells/vhosts/bncs. Bans won't do a thing except fill your list. And 'get to where i am today' is a sad testament to your life apparently. On a side note, I used to op other chans that are far bigger than this chan. I didn't get ops by k/b'ing everyone in sight or anyone that carried a touch of annoyance to me. I reserved k/bs for spammers and flooders, that's it. Everyone else I /ignored"
+peep = "say ight so boom, i told son chill ah ah ah, he like nah, this that and the 3rd, blazhee blah, so i ping ponged poofed ol boi shit then i peeped the boyz so"
+worms = "say super models give me dome while i count stacks and play worms"
+your = "say look its not hard, your isnt you're.. you're is YOU ARE. jesus. did you even make it out of 8th grade. can you even COUNT TO EIGHT, fuckin your an IDIOT.. and im sure right now your eaiting to CORRECT ME on using your in the wrong context now that i've TAUGHT YOU HOW TO USE IT OMG JESUS"
+
+# SuperNETs (Oper)
+botsay = "msg BotServ SAY $channel"
+deprotect = "msg ChanServ MODE $channel LOCK DEL +eI ~a:$1 ~a:$1; mode $channel -eI ~a:$1 ~a:$1"
+protect = "mode $channel +eI ~a:$1 ~a:$1; msg chanserv mode $channel lock add +eI ~a:$1 ~a:$1"
+sf = "spamfilter add simple cpnN kill - "08,04 E N T E R T H E V O I D " "$*"
+
+# SuperNETs
+sn_auto = "say yeah you can go ahead and add irc.supernets.org to your clients auto-connect config now."
+sn_cont = "masshl %n 04CAN WE CONTINUE THIS CONVERSATION AT IRC.SUPERNETS.ORG #SUPERBOWL FOR FUCKS SAKE %n"
+sn_dad = "say this isnt your dads football channel"
+sn_move = "masshl %n: 1,8/!\08,04 WARNING 1,8/!\ 04This channel has moved to 12IRC.SUPERNETS.ORG #SUPERBOWL"
+sn_gold = "say You must have a SuperNETs 8,7GOLD account to view this message."
+
+# Useful
+ajoin = "set irc.server.$server.autojoin ${irc.server.$server.autojoin},$channel"
+clear = "buffer clear"
+close = "buffer close"
+collapse = "allchan -current /buffer hide; /allpv -current /buffer hide"
+colors = "say 1,00001,01011,02021,03031,04041,05051,06061,07071,08081,09091,10101,11111,12121,13131,14141,1515 ; say 1,16161,17171,18181,19191,20201,21211,22221,23231,24241,25251,26261,2727 ; say 1,28281,29291,30301,31311,32321,33331,34341,35351,36361,37371,38381,3939 ; say 1,40401,41411,42421,43431,44441,45451,46461,47471,48481,49491,50501,5151 ; say 1,52521,53531,54541,55551,56561,57571,58581,59591,60601,61611,62621,6363 ; say 1,64641,65651,66661,67671,68681,69691,70701,71711,72721,73731,74741,7575 ; say 1,76761,77771,78781,79791,80801,81811,82821,83831,84841,85851,86861,8787 ; say 1,88881,89891,90901,91911,92921,93931,94941,95951,96961,97971,98981,9999;"
+exempt = "mode +eI *!*@*.acid.vegas *!*@*.acid.vegas"
+gh = "say https://github.com/$*"
+harchats = "set irc.server.$server.anti_flood_prio_high 0 ; /set irc.server.$server.anti_flood_prio_low 0"
+msgbuf = "command -buffer $1 * /input send $2-"
+ns_ident = "msg NickServ IDENTIFY ${sec.data.${server}}"
+ns_setup = "msg NickServ ACCESS ADD *@big.dick.acid.vegas; msg NickServ CERT ADD; msg NickServ SET AUTOOP ON; msg NickServ SET HIDE EMAIL ON; msg NickServ SET HIDE STATUS ON; msg NickServ SET HIDE USERMASK ON; msg NickServ SET HIDE QUIT ON; msg NickServ SET KEEPMODES ON; msg NickServ SET KILL QUICK; msg NickServ SET PRIVATE ON; msg NickServ SET SECURE ON"
+redraw = "color reset;window refresh"
+say = "msg *"
+softchats = "set irc.server.$server.anti_flood_prio_high 2 ; /set irc.server.$server.anti_flood_prio_low 2"
+uncollapse = "allchan -current /buffer unhide; /allpv -current /buffer unhide"
+v = "voice * -yes"
+wx = "who * n%nc"
+
+[completion]
+msgbuf = "%(buffers_plugins_names)"
+\ No newline at end of file
diff --git a/scripts/perl/antifuck.pl b/scripts/perl/antifuck.pl
@@ -0,0 +1,369 @@
+# Released into the Public Domain
+
+use strict;
+use warnings;
+
+no strict 'subs';
+
+my $SCRIPT_NAME = 'antifuck';
+my $SCRIPT_AUTHOR = 'The Krusty Krab <wowaname@volatile.ch>';
+my $SCRIPT_VERSION = '1.1';
+my $SCRIPT_LICENCE = 'Public domain';
+my $SCRIPT_DESC = 'Defend against forcejoins (e.g. from fuckyou.pl) and '.
+ 'forceparts (e.g. from /remove)';
+
+my %OPTIONS = (
+ autopart => ['Whether to automatically part forcejoined channels. '.
+ 'You can always do this manually with /antifuck part', '0'],
+ delay => ['Delay in milliseconds to wait before autoparting', '5000'],
+ forward => ['Whether to allow channel forwards (+f on freenode)', '1'],
+ ignore => ['Servers to ignore (e.g. for bouncers), separated by comma', ''],
+ nobufs => ['If 1, do not create buffers for forcejoined channels', '0'],
+ timeout =>
+ ['Delay in milliseconds to wait for server to send JOIN after join',
+ '60000'],
+ );
+
+# %channels: channels we joined and received JOIN / NAMES for
+# %zombie: channels we joined but aren't yet in
+# %part: channels we were forced into and will part soon
+# %partbuf: buffers belonging to parted channels, we'll close these on
+# /antifuck part
+our (%channels, %zombie, %part, %partbuf, $fuckbuf, $timeout_cb, $gc_cb);
+
+if (weechat::register($SCRIPT_NAME, $SCRIPT_AUTHOR, $SCRIPT_VERSION,
+ $SCRIPT_LICENCE, $SCRIPT_DESC, '', '')) {
+ weechat::hook_command('antifuck', $SCRIPT_DESC, 'part', <<'HELP',
+This script defends against forced joins, such as from irssi's fuckyou.pl or
+from channel forwards, as well as forced parts, such as from the /remove
+command. You can configure certain behaviour using the options under
+"plugins.var.perl.antifuck.*". Configure rejoin-on-/remove with the
+irc.server_default.autorejoin and .autorejoin_delay commands.
+
+Running "/antifuck part" will close all forcejoined channels and part them where
+appropriate.
+HELP
+ 'part', 'cmd_antifuck', '');
+ weechat::hook_signal('irc_server_connected', 'irc_connect', '');
+ weechat::hook_signal('irc_server_disconnected', 'irc_disconnect', '');
+ weechat::hook_signal('irc_channel_opened', 'buffer_opened', '');
+ weechat::hook_signal('buffer_closed', 'buffer_closed', '');
+ weechat::hook_signal('*,irc_out1_join', 'client_join', '');
+ weechat::hook_signal('*,irc_out1_part', 'client_part', '');
+ weechat::hook_signal('*,irc_raw_in_001', 'irc_001', '');
+ weechat::hook_signal('*,irc_raw_in_470', 'irc_470', '');
+ weechat::hook_modifier('irc_in_366', 'irc_366', '');
+ weechat::hook_modifier('irc_in_join', 'irc_join', '');
+ weechat::hook_modifier('irc_in_part', 'irc_part', '');
+
+ for my $option (keys %OPTIONS) {
+ weechat::config_set_plugin($option, $OPTIONS{$option}[1])
+ unless weechat::config_is_set_plugin($option);
+ weechat::config_set_desc_plugin($option, $OPTIONS{$option}[0]);
+ }
+
+ my $iptr = weechat::infolist_get('buffer', '', '');
+
+ while (weechat::infolist_next($iptr)) {
+ next unless weechat::infolist_string($iptr, 'plugin_name') eq 'irc';
+ my $buf = weechat::infolist_pointer($iptr, 'pointer');
+ $channels{
+ lc weechat::buffer_get_string($buf, 'localvar_server')}{
+ lc weechat::buffer_get_string($buf, 'localvar_channel')} = 1;
+ }
+ weechat::infolist_free($iptr);
+}
+
+sub mynick
+{
+ my ($buf, $nick) = ($_[0], $_[1]);
+
+ return lc weechat::buffer_get_string($buf, 'localvar_nick') eq lc $nick;
+}
+
+sub ignored
+{
+ my $server = shift;
+ my $ignore_conf = lc weechat::config_get_plugin('ignore');
+
+ return $ignore_conf =~ /(^|,)$server($|,)/;
+}
+
+sub nobufs { weechat::config_get_plugin('nobufs') }
+
+sub ircbuf { weechat::buffer_search('irc', "(?i)".(join '.', @_)) }
+sub ircparse { weechat::info_get_hashtable(irc_message_parse =>
+ { message => shift }) }
+
+sub servchan
+{
+ my $buf = shift;
+
+ return (lc weechat::buffer_get_string($buf, 'localvar_server'),
+ lc weechat::buffer_get_string($buf, 'localvar_channel'));
+}
+
+sub reset_gc
+{
+ weechat::unhook($gc_cb) if $gc_cb;
+ $gc_cb = weechat::hook_timer(weechat::config_get_plugin('timeout'), 0, 1,
+ 'run_gc', '');
+}
+
+sub cmd_antifuck
+{
+ my (undef, $buffer, $args) = @_;
+
+ if ($args eq 'part') {
+ # TODO: we really need to spend more time here making sure we send the
+ # fewest PARTs possible, a la irc_join_delay
+ weechat::buffer_close($fuckbuf);
+ }
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub fuckbuf_input { return weechat::WEECHAT_RC_OK; }
+
+sub fuckbuf_close
+{
+ weechat::buffer_close($_) for (keys %partbuf);
+ %partbuf = ();
+ $fuckbuf = '';
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_connect
+{
+ my $server = pop;
+ my ($autojoin) = (weechat::config_string(weechat::config_get(
+ "irc.server.$server.autojoin")) =~ /^([^ ]*)/);
+
+ $zombie{$server}{$_} = 1 for (split ',', lc($autojoin));
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_disconnect
+{
+ my $server = pop;
+
+ $server = lc $server;
+ delete $channels{$server};
+ delete $zombie{$server};
+ delete $part{$server};
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub buffer_opened {
+ my $buffer = pop;
+ my ($server, $channel) = servchan($buffer);
+ return weechat::WEECHAT_RC_OK if exists $channels{$server}{$channel};
+ return weechat::WEECHAT_RC_OK if ignored($server);
+
+ $fuckbuf = weechat::buffer_new(
+ 'antifuck',
+ 'fuckbuf_input',
+ '',
+ 'fuckbuf_close',
+ ''
+ ) unless $fuckbuf;
+
+ weechat::buffer_merge($buffer, $fuckbuf);
+ #return weechat::WEECHAT_RC_OK unless weechat::config_get_plugin('autopart');
+
+ $partbuf{$buffer} = 1;
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub buffer_closed {
+ my $buffer = pop;
+
+ delete $partbuf{$buffer};
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub client_join
+{
+ my (undef, $server, $channel) = (shift,
+ shift =~ /(.+),irc_out1_join/i,
+ shift =~ /^join :?([^ ]*)/i);
+ ($server, $channel) = (lc $server, lc $channel);
+
+ reset_gc();
+
+ ($_ eq '0' ? %{$channels{$server}} = () : $zombie{$server}{$_} = 1)
+ for (split ',', $channel);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub client_part
+{
+ my (undef, $server, $channel) = (shift,
+ shift =~ /(.+),irc_out1_part/i,
+ shift =~ /^part ([^ ]*)/i);
+ ($server, $channel) = (lc $server, lc $channel);
+
+ delete $channels{$server}{$_} for (split ',', $channel);
+ return weechat::WEECHAT_RC_OK;
+}
+
+# RPL_WELCOME
+sub irc_001
+{
+ my (undef, $server, $message) = (shift,
+ shift =~ /(.+),irc_raw_in_001/, shift);
+
+ $server = lc $server;
+ return weechat::WEECHAT_RC_OK unless $message =~ / :- Welcome to ZNC -$/;
+
+ my $ignore_conf = lc weechat::config_get_plugin('ignore');
+ return weechat::WEECHAT_RC_OK if $ignore_conf =~ /(^|,)$server($|,)/;
+
+ weechat::config_set_plugin('ignore', "$ignore_conf,$server");
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_join
+{
+ my ($server, $message, $msghash) = (lc $_[2], $_[3], ircparse($_[3]));
+ my ($nick, $channel) = ($msghash->{nick}, lc $msghash->{channel});
+ my $buffer = ircbuf("$server.$channel");
+
+ return $message if exists $channels{$server}{$channel};
+ if (exists $zombie{$server}{$channel} || ignored($server)) {
+ delete $zombie{$server}{$channel};
+ $channels{$server}{$channel} = 1;
+ return $message;
+ }
+ # XXX return $message unless mynick($buffer, $nick);
+
+ $part{$server}{$channel} = 1;
+ $timeout_cb = weechat::hook_timer(
+ weechat::config_get_plugin('delay'), 0, 1, 'irc_join_delay', $buffer)
+ unless $timeout_cb || !weechat::config_get_plugin('autopart');
+
+ return $message unless nobufs();
+
+ $fuckbuf = weechat::buffer_new(
+ 'antifuck',
+ 'fuckbuf_input',
+ '',
+ 'fuckbuf_close',
+ ''
+ ) unless $fuckbuf;
+ weechat::print($fuckbuf, weechat::prefix('join').
+ weechat::color('irc.color.message_join').
+ 'You were forced to join '.weechat::color('chat_channel').$channel.
+ weechat::color('irc.color.message_join').', leaving');
+
+ return '';
+}
+
+# RPL_ENDOFNAMES
+sub irc_366
+{
+ my ($server, $message) = ($_[2], $_[3]);
+ my ($nick, $channel) = $message =~ /^:[^ ]* 366 ([^ ]*) ([^ ]*)/i;
+ my $buffer = ircbuf("$server.$channel");
+ ($server, $channel) = (lc $server, lc $channel);
+
+ return $message if exists $channels{$server}{$channel};
+ return '' if nobufs();
+
+ weechat::print($buffer, weechat::prefix('network').
+ 'Forcejoined, not syncing modes');
+
+ return '';
+}
+
+# ERR_LINKCHANNEL
+sub irc_470
+{
+ my (undef, $server, $oldchan, $newchan) = (shift,
+ shift =~ /(.+),irc_raw_in_470/,
+ shift =~ /^:[^ ]* 470 [^ ]+ ([^ ]+) ([^ ]+)/);
+ ($server, $oldchan, $newchan) = (lc $server, lc $oldchan, lc $newchan);
+
+ delete $channels{$server}{$oldchan};
+ $channels{$server}{$newchan} = 1 if weechat::config_get_plugin('forward');
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_join_delay
+{
+ my $buffer = shift;
+
+ for my $server (keys %part) {
+ my $chans = '';
+
+ for my $chan (keys %{$part{$server}}) {
+ if (length($chans) + length($chan) > 500) {
+ weechat::hook_signal_send('irc_input_send',
+ weechat::WEECHAT_HOOK_SIGNAL_STRING,
+ "$server;;priority_low;;/part $chans");
+ $chans = '';
+ }
+
+ $chans .= "$chan,";
+ }
+
+ weechat::hook_signal_send('irc_input_send',
+ weechat::WEECHAT_HOOK_SIGNAL_STRING,
+ "$server;;priority_low;;/part $chans");
+ }
+ $timeout_cb = '';
+ %part = ();
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub run_gc
+{
+ %zombie = ();
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_part
+{
+ my ($server, $message, $msghash) = ($_[2], $_[3], ircparse($_[3]));
+ my ($arj, $arj_delay, $arjd, $arjd_delay) = (
+ weechat::config_get("irc.server.$server.autorejoin"),
+ weechat::config_get("irc.server.$server.autorejoin_delay"),
+ weechat::config_get("irc.server_default.autorejoin"),
+ weechat::config_get("irc.server_default.autorejoin_delay")
+ );
+ return $message unless (
+ weechat::config_option_is_null($arj) ?
+ weechat::config_boolean($arjd) :
+ weechat::config_boolean($arj)
+ );
+
+ my ($nick, $channel, $reason) = ($msghash->{nick}, $msghash->{channel},
+ $msghash->{text});
+
+ my $buffer = ircbuf("$server.$channel");
+ my ($lserver, $lchannel) = (lc $server, lc $channel);
+
+ return $message unless mynick($buffer, $nick);
+ return $message unless exists $channels{$lserver}{$lchannel};
+ return $message if ignored($lserver);
+
+ weechat::print($buffer, weechat::prefix('quit').
+ weechat::color('irc.color.message_quit').
+ 'You were forced to part '.weechat::color('chat_channel').$channel.
+ weechat::color('chat_delimiters').' ('.weechat::color('reset').
+ $reason.weechat::color('chat_delimiters').')'.
+ weechat::color('irc.color.message_quit').', rejoining');
+ my $delay = (
+ weechat::config_option_is_null($arj_delay) ?
+ weechat::config_integer($arjd_delay) :
+ weechat::config_integer($arj_delay)
+ );
+ weechat::command($buffer, ($delay ? "/wait $delay " : "").
+ "/join $channel");
+
+ return '';
+}
diff --git a/scripts/perl/cflood.pl b/scripts/perl/cflood.pl
@@ -0,0 +1,150 @@
+#{{{ BSD License
+# Copyright (c) 2008 hzu/zionist
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#}}}
+
+# Ported from irssi to WeeChat by the Krusty Krab
+
+use strict;
+use warnings;
+no strict 'subs';
+
+my $SCRIPT_NAME = 'colourflood';
+my $SCRIPT_AUTHOR = 'hzu';
+my $SCRIPT_VERSION = '0.3';
+my $SCRIPT_LICENCE = 'BSD';
+my $SCRIPT_DESC = 'A-rab style ircing';
+
+my $USAGE = <<EOF;
+options:
+ -r Random back & foreground colours (default)
+ -f Amount of times the message is flooded
+ -fg font colour, available colours:
+ black, blue, green
+ lightred, red, magenta, orange
+ yellow, lightgreen, cyan,
+ lightcyan, lightblue,
+ lightmagenta, gray, lightgray.
+ -bg background colour, available colours:
+ black, blue, green, red,
+ magenta, orange, green, cyan,
+ lightcyan, lightblue,
+ lightmagenta, gray, lightgray
+EOF
+
+our %clr = (
+ black => 1,
+ blue => 2,
+ green => 3,
+ lightred => 4,
+ red => 5,
+ magenta => 6,
+ orange => 7,
+ yellow => 8,
+ lightgreen => 9,
+ cyan => 10,
+ lightcyan => 11,
+ lightblue => 12,
+ lightmagenta => 13,
+ gray => 14,
+ lightgray => 15,
+);
+
+if (weechat::register($SCRIPT_NAME, $SCRIPT_AUTHOR, $SCRIPT_VERSION,
+ $SCRIPT_LICENCE, $SCRIPT_DESC, '', '')) {
+ weechat::hook_command('cflood', $SCRIPT_DESC, '[options] text',
+ $USAGE, '', 'cmd_cflood', '');
+
+ my %OPTIONS = (
+ dir => ['Database directory',
+ weechat::info_get('weechat_dir', '').'/yiffs'],
+ db => ['Default database', 'yiffs'],
+ solodb => ['Default database when nick is omitted', 'solos'],
+ );
+
+ for my $option (keys %OPTIONS) {
+ weechat::config_set_plugin($option, $OPTIONS{$option}[1])
+ unless weechat::config_is_set_plugin($option);
+ weechat::config_set_desc_plugin($option, $OPTIONS{$option}[0]);
+ }
+}
+
+sub colour {
+ my ($fg, $bg, $text) = @_;
+ my $fore = ($fg =~ /^[0-9][0-9]?$/) ? $fg : $clr{$fg};
+ my $back = ($fg =~ /^[0-9][0-9]?$/) ? $bg : $clr{$bg};
+ $fore-- if $fore == $back;
+ $text = "\003$fore,$back $text \003$back,$fore $text ";
+ $text x= (int 400 / length $text);
+ return "$text\003";
+}
+
+sub parse {
+ my @args = ( split / +/, shift );
+ my ( %todo, $text, $body );
+
+ while ( ($_ = shift @args) ne '' ) {
+ /^-r$/ and next;
+ /^-f$/ and $todo{f} = shift @args, next;
+ /^-fg$/ and $todo{fg} = shift @args, next;
+ /^-bg$/ and $todo{bg} = shift @args, next;
+ /^-/ and weechat::print('', weechat::prefix('error').
+ 'Invalid arguments (see /help cflood)'), return;
+ $text = @args < 1 ? $_ : "$_ " . join ' ', @args;
+ last;
+ }
+
+ if (!(defined $todo{fg}) || !(defined $todo{bg})) {
+ $body = "";
+ my @rnd_clr = keys %clr;
+ foreach ( 1 .. (defined $todo{f} ? $todo{f} : 1 ) ) {
+ $body .= colour($rnd_clr[rand @rnd_clr],
+ $rnd_clr[rand @rnd_clr], $text, $todo{f});
+ $body .= "\n";
+ }
+ } else {
+ $body = "";
+ foreach ( 1 .. (defined $todo{f} ? $todo{f} : 1 ) ) {
+ $body .= colour( $todo{fg}, $todo{bg}, $text );
+ $body .= "\n";
+ }
+ }
+ return $body;
+}
+
+sub cmd_cflood {
+ my (undef, $buffer, $data) = @_;
+ my $ret;
+
+ return weechat::WEECHAT_RC_OK if ($data eq '');
+
+ chomp( $ret = parse($data) );
+
+ if ($ret =~ /\n/) {
+ map { weechat::command($buffer, "/msg * $_") } (split /\n/, $ret);
+ } else {
+ weechat::command($buffer, "/msg * $ret");
+ }
+
+ return weechat::WEECHAT_RC_OK;
+}
diff --git a/scripts/perl/color_popup.pl b/scripts/perl/color_popup.pl
@@ -0,0 +1,38 @@
+use strict; use warnings;
+$INC{'Encode/ConfigLocal.pm'}=1;
+require Encode;
+use utf8;
+
+use constant SCRIPT_NAME => 'color_popup';
+weechat::register(SCRIPT_NAME, 'Nei <anti.teamidiot.de>', '0.4', 'GPL3', 'show mirc color codes', '', '') || return;
+
+my %ones = map { $_ => 1 } 0, 8, 14, 15, 42, 43, 45, 53 .. 58, 65 .. 86, 95 .. 98;
+weechat::hook_modifier('input_text_display_with_cursor', 'color_popup', '');
+
+## color_popup -- show mirc colors
+## () - modifier handler
+## $_[2] - buffer pointer
+## $_[3] - input string
+## returns modified input string
+sub color_popup {
+ Encode::_utf8_on($_[3]);
+ my $cc = qr/(?:\03(?:\d{1,2}(?:,(?:\d{1,2})?)?)?|\02|\x1d|\x0f|\x12|\x15|\x16|\x1f)/;
+ my ($p1, $x, $p2) = split /((?:$cc)?\x19b#)/, $_[3], 2;
+ for ($p1, $p2) {
+ s/($cc)/$1■/g if weechat::config_string_to_boolean(weechat::config_get_plugin('reveal'));
+ Encode::_utf8_on($_ = weechat::hook_modifier_exec(irc_color_decode => 1, weechat::hook_modifier_exec(irc_color_encode => 1, $_)));
+ }
+ $x .= ' ' . weechat::hook_modifier_exec(
+ irc_color_decode => 1, sub {
+ $x =~ /\cC(\d{1,2})(,(\d{1,2})?)?/;
+ my ($fg, $bg) = ($1//-1, $3//-1);
+ my $sc = $bg >= 0 ? $bg : $2?-1:$fg;
+ (join '', map {
+ "\03" . ($ones{0+$_} // 0) . ",$_$_"
+ }
+ $sc >= 0 ? grep /^0?$sc/, '00' .. '99' : ('00' .. '15'))
+ . "\03"
+ }->()
+ ) if $x =~ /^\03/ and weechat::current_buffer() eq $_[2];
+ "$p1$x$p2"
+}
diff --git a/scripts/perl/fuckyou.pl b/scripts/perl/fuckyou.pl
@@ -0,0 +1,188 @@
+# Ported from irssi to WeeChat by the Krusty Krab
+
+use Time::HiRes qw(time);
+use Digest::MD5 qw(md5_hex);
+
+
+#__ ___ ____ _ _ ___ _ _ ____ _ _ ____ ___ _ _ ____
+#\ \ / / \ | _ \| \ | |_ _| \ | |/ ___| | | | / ___|_ _| \ | |/ ___|
+# \ \ /\ / / _ \ | |_) | \| || || \| | | _ | | | \___ \| || \| | | _
+# \ V V / ___ \| _ <| |\ || || |\ | |_| | | |_| |___) | || |\ | |_| |
+# \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| \___/|____/___|_| \_|\____|
+#
+# _____ _ _ _____ ____ _____ ____ ____ ____ ___ ____ _____ ____
+#|_ _| | | | ____/ ___|| ____| / ___| / ___| _ \|_ _| _ \_ _/ ___|
+# | | | |_| | _| \___ \| _| \___ \| | | |_) || || |_) || | \___ \
+# | | | _ | |___ ___) | |___ ___) | |___| _ < | || __/ | | ___) |
+# |_| |_| |_|_____|____/|_____| |____/ \____|_| \_\___|_| |_| |____/
+#
+# __ __ _ __ __ ____ _____ _ _ _ __ __
+#| \/ | / \\ \ / / | _ \| ____| / \ | | | | \ \ / /
+#| |\/| | / _ \\ V / | |_) | _| / _ \ | | | | \ \//
+#| | | |/ ___ \| | | _ <| |___ / ___ \| |___| |___| |
+#|_| |_/_/ \_\_| |_| \_\_____/_/ \_\_____|_____|_|
+#
+# _____ _ _ ____ _ _____ _ _ ____ _ _ _ _ _ _____ __
+#| ___| | | |/ ___| |/ /_ _| \ | |/ ___| / \ | \ | | \ | |/ _ \ \ / /
+#| |_ | | | | | | ' / | || \| | | _ / _ \ | \| | \| | | | \ V /
+#| _| | |_| | |___| . \ | || |\ | |_| | / ___ \| |\ | |\ | |_| || |
+#|_| \___/ \____|_|\_\___|_| \_|\____| /_/ \_\_| \_|_| \_|\___/ |_|
+#
+#__ _____ _ _ ____ _ _ ____ _____ ____ ____
+#\ \ / / _ \| | | | _ \ | | | / ___|| ____| _ \/ ___|
+# \ V / | | | | | | |_) | | | | \___ \| _| | |_) \___ \
+# | || |_| | |_| | _ < | |_| |___) | |___| _ < ___) |
+# |_| \___/ \___/|_| \_\ \___/|____/|_____|_| \_\____/
+
+
+my $SCRIPT_NAME = 'fuckyou';
+my $SCRIPT_AUTHOR = 'Goat-See <mrtheplague@gmail.com>';
+my $SCRIPT_VERSION = '2.3';
+my $SCRIPT_LICENCE = 'urmom';
+my $SCRIPT_DESC = '/fuckyou NICK numberchannels';
+
+my %OPTIONS = (
+ forcejoin => ['Command to forcejoin. ratbox uses forcejoin, unreal sajoin',
+ 'forcejoin'],
+ forcepart => ['Command to forcepart. ratbox uses forcepart, unreal sapart',
+ 'forcepart'],
+ furry => ['Channel prefix (include # or &)', '&HYE'],
+ parallel => ['Number of channels to send per forcejoin command', 1],
+ whois_cmd => ['Prefix to whois user (e.g. for ratbox operspy, "/whois !")',
+ '/whois '],
+ );
+
+sub fuckyou
+{
+ my $buffer = shift;
+ my $nig = $$ * time;
+ my @jews;
+ push @jews, "${FURRY}_".md5_hex($nig + $_) for (1..$PARALLEL);
+
+ weechat::command($buffer, "/quote $FORCEJOIN $target ".join(',', @jews));
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub cmd_fuckyou
+{
+ my (undef, $buffer, $data) = @_;
+ my $server = weechat::buffer_get_string($buffer, 'localvar_server');
+ my $amt_end;
+ ($target, $amt_end) = split / +/, $data;
+ our ($FORCEJOIN, $FURRY, $PARALLEL) = (
+ weechat::config_get_plugin('forcejoin'),
+ weechat::config_get_plugin('furry'),
+ weechat::config_get_plugin('parallel'));
+
+ weechat::unhook($signal) if $signal;
+ weechat::unhook($timer) if $timer;
+
+ unless ($target) {
+ weechat::print($buffer, 'Stopped any current /fuckyou');
+ return weechat::WEECHAT_RC_OK;
+ }
+ $amt_end //= 0;
+
+ $signal = weechat::hook_signal("$server,irc_raw_in_402", 'irc_402', '');
+ $timer = weechat::hook_timer(50, 0, $amt_end, 'fuckyou', $buffer);
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub cmd_unfuckyou
+{
+ my (undef, $buffer, $data) = @_;
+ my ($server, $channel) = (
+ weechat::buffer_get_string($buffer, 'localvar_server'),
+ weechat::buffer_get_string($buffer, 'localvar_channel')
+ );
+ my $WHOIS = weechat::config_get_plugin('whois_cmd');
+
+ unless ($data) {
+ weechat::print($buffer, '/unfuckyou user user2 user3');
+ return weechat::WEECHAT_RC_OK;
+ }
+
+ foreach my $dick (split / +/, $data) {
+ weechat::hook_hsignal_send(
+ 'irc_redirect_command',
+ {
+ server => "$server",
+ pattern => "whois",
+ signal => "sigwhois"
+ });
+ weechat::hook_signal_send(
+ 'irc_input_send',
+ weechat::WEECHAT_HOOK_SIGNAL_STRING,
+ "$server;;1;;$WHOIS$dick"
+ );
+ }
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub event_whois_channels
+{
+ my %hashtable = %{$_[2]};
+ my $FORCEPART = weechat::config_get_plugin('forcepart');
+ my $FURRY = weechat::config_get_plugin('furry');
+ my $counter = 0;
+ my ($nick, $channels);
+
+ for (split /^/, $hashtable{output}) {
+ if (/^:[^ ]* 319 [^ ]+ ([^ ]+) :(.*)$/) {
+ ($nick, $channels) = ($1, $2);
+ } else { next; }
+
+ $channels =~ s/ +$//;
+
+ my @niggers = split / +/, $channels;
+ foreach (@niggers)
+ {
+ s/^[!@%+]*([&#])/$1/;
+ if(/${FURRY}_[a-f0-9]+/i)
+ {
+ #Irssi::print("Forceparting $nick from $_");
+ weechat::command('', "/quote $FORCEPART $nick $_");
+ ++$counter;
+ }
+ }
+ }
+
+ weechat::print('', "Forceparted $nick from $counter channels")
+ if $counter;
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_402 {
+ my $message = pop;
+ my $targmatch = quotemeta $target;
+
+ return weechat::WEECHAT_RC_OK
+ unless ($message =~ /^[^ ]* 402 [^ ]+ $targmatch /i);
+ weechat::unhook($signal);
+ weechat::unhook($timer);
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+if (weechat::register($SCRIPT_NAME, $SCRIPT_AUTHOR, $SCRIPT_VERSION,
+ $SCRIPT_LICENCE, $SCRIPT_DESC, '', '')) {
+ weechat::hook_command('fuckyou', '', '[<nick> [<amt>]]',
+ "if nick is not given, stops current fuckyou, if any is running\n\n".
+ "amt is 0 by default - user will be fuckyoud until they disconnect\n".
+ "or you stop it manually\n",
+ '', 'cmd_fuckyou', '');
+ weechat::hook_command('unfuckyou', '', '<nick> [nick...]', '', '',
+ 'cmd_unfuckyou', '');
+ weechat::hook_hsignal('irc_redirection_sigwhois_whois',
+ 'event_whois_channels', '');
+
+ for my $option (keys %OPTIONS) {
+ weechat::config_set_plugin($option, $OPTIONS{$option}[1])
+ unless weechat::config_is_set_plugin($option);
+ weechat::config_set_desc_plugin($option, $OPTIONS{$option}[0]);
+ }
+}
diff --git a/scripts/perl/highmon.pl b/scripts/perl/highmon.pl
@@ -0,0 +1,1146 @@
+#
+# highmon.pl - Highlight Monitoring for weechat 0.3.0
+# Version 2.6
+#
+# Add 'Highlight Monitor' buffer/bar to log all highlights in one spot
+#
+# Usage:
+# /highmon [help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar
+# Command wrapper for highmon commands
+#
+# /highmon clean default|orphan|all will clean the config section of default 'on' entries,
+# channels you are no longer joined, or both
+#
+# /highmon clearbar will clear the contents of highmon's bar output
+#
+# /highmon monitor [channel] [server] is used to toggle a highlight monitoring on and off, this
+# can be used in the channel buffer for the channel you wish to toggle, or be given
+# with arguments e.g. /highmon monitor #weechat freenode
+#
+# /set plugins.var.perl.highmon.alignment
+# The config setting "alignment" can be changed to;
+# "channel", "schannel", "nchannel", "channel,nick", "schannel,nick", "nchannel,nick"
+# to change how the monitor appears
+# The 'channel' value will show: "#weechat"
+# The 'schannel' value will show: "6"
+# The 'nchannel' value will show: "6:#weechat"
+#
+# /set plugins.var.perl.highmon.short_names
+# Setting this to 'on' will trim the network name from highmon, ala buffers.pl
+#
+# /set plugins.var.perl.highmon.merge_private
+# Setting this to 'on' will merge private messages to highmon's display
+#
+# /set plugins.var.perl.highmon.color_buf
+# This turns colored buffer names on or off, you can also set a single fixed color by using a weechat color name.
+# This *must* be a valid color name, or weechat will likely do unexpected things :)
+#
+# /set plugins.var.perl.highmon.hotlist_show
+# Setting this to 'on' will let the highmon buffer appear in hotlists
+# (status bar/buffer.pl)
+#
+# /set plugins.var.perl.highmon.away_only
+# Setting this to 'on' will only put messages in the highmon buffer when
+# you set your status to away
+#
+# /set plugins.var.perl.highmon.logging
+# Toggles logging status for highmon buffer (default: off)
+#
+# /set plugins.var.perl.highmon.output
+# Changes where output method of highmon; takes either "bar" or "buffer" (default; buffer)
+# /set plugins.var.perl.highmon.bar_lines
+# Changes the amount of lines the output bar will hold.
+# (Only appears once output has been set to bar, defaults to 10)
+# /set plugins.var.perl.highmon.bar_scrolldown
+# Toggles the bar scrolling at the bottom when new highlights are received
+# (Only appears once output has been set to bar, defaults to off)
+#
+# /set plugins.var.perl.highmon.nick_prefix
+# /set plugins.var.perl.highmon.nick_suffix
+# Sets the prefix and suffix chars in the highmon buffer
+# (Defaults to <> if nothing set, and blank if there is)
+#
+# servername.#channel
+# servername is the internal name for the server (set when you use /server add)
+# #channel is the channel name, (where # is whatever channel type that channel happens to be)
+#
+# Optional, set up tweaks; Hide the status and input lines on highmon
+#
+# /set weechat.bar.status.conditions "${window.buffer.full_name} != perl.highmon"
+# /set weechat.bar.input.conditions "${window.buffer.full_name} != perl.highmon"
+#
+
+# Bugs and feature requests at: https://github.com/KenjiE20/highmon
+
+# History:
+# 2019-05-13, HubbeKing <hubbe128@gmail.com>
+# v2.6: -add: send "logger_backlog" signal on buffer open if logging is enabled
+# 2014-08-16, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.5: -add: clearbar command to clear bar output
+# -add: firstrun output prompt to check the help text for set up hints as they were being missed
+# and update hint for conditions to use eval
+# -change: Make all outputs use the date callback for more accurate timestamps (thanks Germainz)
+# 2013-12-04, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.4: -add: Support for eval style colour codes in time format used for bar output
+# 2013-10-22, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.3.3.2: -fix: Typo in fix command
+# 2013-10-10, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.3.3.1: -fix: Typo in closed buffer warning
+# 2013-10-07, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.3.3: -add: Warning and fixer for accidental buffer closes
+# 2013-01-15, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.3.2: -fix: Let bar output use the string set in weechat's config option
+# -add: github info
+# 2012-04-15, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.3.1: -fix: Colour tags in bar timestamp string
+# 2012-02-28, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.3: -feature: Added merge_private option to display private messages (default: off)
+# -fix: Channel name colours now show correctly when set to on
+# 2011-08-07, Sitaktif <romainchossart_at_gmail.com>:
+# v2.2.1: -feature: Add "bar_scrolldown" option to have the bar display the latest hl at anytime
+# -fix: Set up bar-specific config at startup if 'output' is already configured as 'bar'
+# 2010-12-22, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.2: -change: Use API instead of config to find channel colours, ready for 0.3.4 and 256 colours
+# 2010-12-13, idl0r & KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.1.3: -fix: perl errors caused by bar line counter
+# -fix: Add command list to inbuilt help
+# 2010-09-30, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.1.2: -fix: logging config was not correctly toggling back on (thanks to sleo for noticing)
+# -version sync w/ chanmon
+# 2010-08-27, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.1: -feature: Add 'nchannel' option to alignment to display buffer and name
+# 2010-04-25, KenjiE20 <longbow@longbowslair.co.uk>:
+# v2.0: Release as version 2.0
+# 2010-04-24, KenjiE20 <longbow@longbowslair.co.uk>:
+# v1.9: Rewrite for v2.0
+# Bring feature set in line with chanmon 2.0
+# -code change: Made more subs to shrink the code down in places
+# -fix: Stop highmon attempting to double load/hook
+# -fix: Add version dependant check for away status
+# 2010-01-25, KenjiE20 <longbow@longbowslair.co.uk>:
+# v1.7: -fixture: Let highmon be aware of nick_prefix/suffix
+# and allow custom prefix/suffix for chanmon buffer
+# (Defaults to <> if nothing set, and blank if there is)
+# (Thanks to m4v for these)
+# 2009-09-07, KenjiE20 <longbow@longbowslair.co.uk>:
+# v1.6: -feature: colored buffer names
+# -change: version sync with chanmon
+# 2009-09-05, KenjiE20 <longbow@longbowslair.co.uk>:
+# v1.2: -fix: disable buffer highlight
+# 2009-09-02, KenjiE20 <longbow@longbowslair.co.uk>:
+# v.1.1.1 -change: Stop unsightly text block on '/help'
+# 2009-08-10, KenjiE20 <longbow@longbowslair.co.uk>:
+# v1.1: In-client help added
+# 2009-08-02, KenjiE20 <longbow@longbowslair.co.uk>:
+# v1.0: Initial Public Release
+
+# Copyright (c) 2009 by KenjiE20 <longbow@longbowslair.co.uk>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+@bar_lines = ();
+@bar_lines_time = ();
+# Replicate info earlier for in-client help
+
+$highmonhelp = weechat::color("bold")."/highmon [help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar".weechat::color("-bold")."
+ Command wrapper for highmon commands
+
+".weechat::color("bold")."/highmon clean default|orphan|all".weechat::color("-bold")." will clean the config section of default 'on' entries, channels you are no longer joined, or both
+
+".weechat::color("bold")."/highmon clearbar".weechat::color("-bold")." will clear the contents of highmon's bar output
+
+".weechat::color("bold")."/highmon monitor [channel] [server]".weechat::color("-bold")." is used to toggle a highlight monitoring on and off, this can be used in the channel buffer for the channel you wish to toggle, or be given with arguments e.g. /highmon monitor #weechat freenode
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.alignment".weechat::color("-bold")."
+ The config setting \"alignment\" can be changed to;
+ \"channel\", \"schannel\", \"nchannel\", \"channel,nick\", \"schannel,nick\", \"nchannel,nick\"
+ to change how the monitor appears
+ The 'channel' value will show: \"#weechat\"
+ The 'schannel' value will show: \"6\"
+ The 'nchannel' value will show: \"6:#weechat\"
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.short_names".weechat::color("-bold")."
+ Setting this to 'on' will trim the network name from highmon, ala buffers.pl
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.merge_private".weechat::color("-bold")."
+ Setting this to 'on' will merge private messages to highmon's display
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.color_buf".weechat::color("-bold")."
+ This turns colored buffer names on or off, you can also set a single fixed color by using a weechat color name.
+ This ".weechat::color("bold")."must".weechat::color("-bold")." be a valid color name, or weechat will likely do unexpected things :)
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.hotlist_show".weechat::color("-bold")."
+Setting this to 'on' will let the highmon buffer appear in hotlists (status bar/buffer.pl)
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.away_only".weechat::color("-bold")."
+Setting this to 'on' will only put messages in the highmon buffer when you set your status to away
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.logging".weechat::color("-bold")."
+ Toggles logging status for highmon buffer (default: off)
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.output".weechat::color("-bold")."
+ Changes where output method of highmon; takes either \"bar\" or \"buffer\" (default; buffer)
+".weechat::color("bold")."/set plugins.var.perl.highmon.bar_lines".weechat::color("-bold")."
+ Changes the amount of lines the output bar will hold.
+ (Only appears once output has been set to bar, defaults to 10)
+".weechat::color("bold")."/set plugins.var.perl.highmon.bar_scrolldown".weechat::color("-bold")."
+ Toggles the bar scrolling at the bottom when new highlights are received
+ (Only appears once output has been set to bar, defaults to off)
+
+".weechat::color("bold")."/set plugins.var.perl.highmon.nick_prefix".weechat::color("-bold")."
+".weechat::color("bold")."/set plugins.var.perl.highmon.nick_suffix".weechat::color("-bold")."
+ Sets the prefix and suffix chars in the highmon buffer
+ (Defaults to <> if nothing set, and blank if there is)
+
+".weechat::color("bold")."servername.#channel".weechat::color("-bold")."
+ servername is the internal name for the server (set when you use /server add)
+ #channel is the channel name, (where # is whatever channel type that channel happens to be)
+
+".weechat::color("bold")."Optional, set up tweaks;".weechat::color("-bold")." Hide the status and input lines on highmon
+
+".weechat::color("bold")."/set weechat.bar.status.conditions \"\${window.buffer.full_name} != perl.highmon\"".weechat::color("-bold")."
+".weechat::color("bold")."/set weechat.bar.input.conditions \"\${window.buffer.full_name} != perl.highmon\"".weechat::color("-bold");
+# Print verbose help
+sub print_help
+{
+ weechat::print("", "\t".weechat::color("bold")."Highmon Help".weechat::color("-bold")."\n\n");
+ weechat::print("", "\t".$highmonhelp);
+ return weechat::WEECHAT_RC_OK;
+}
+
+# Bar item build
+sub highmon_bar_build
+{
+ # Get max lines
+ $max_lines = weechat::config_get_plugin("bar_lines");
+ $max_lines = $max_lines ? $max_lines : 10;
+ $str = '';
+ $align_num = 0;
+ $count = 0;
+ # Keep lines within max
+ while ($#bar_lines > $max_lines)
+ {
+ shift(@bar_lines);
+ shift(@bar_lines_time);
+ }
+ # So long as we have some lines, build a string
+ if (@bar_lines)
+ {
+ # Build loop
+ $sep = " ".weechat::config_string(weechat::config_get("weechat.look.prefix_suffix"))." ";
+ foreach(@bar_lines)
+ {
+ # Find max align needed
+ $prefix_num = (index(weechat::string_remove_color($_, ""), $sep));
+ $align_num = $prefix_num if ($prefix_num > $align_num);
+ }
+ foreach(@bar_lines)
+ {
+ # Get align for this line
+ $prefix_num = (index(weechat::string_remove_color($_, ""), $sep));
+
+ # Make string
+ $str = $str.$bar_lines_time[$count]." ".(" " x ($align_num - $prefix_num)).$_."\n";
+ # Increment count for sync with time list
+ $count++;
+ }
+ }
+ return $str;
+}
+
+# Make a new bar
+sub highmon_bar_open
+{
+ # Make the bar item
+ weechat::bar_item_new("highmon", "highmon_bar_build", "");
+
+ $highmon_bar = weechat::bar_new ("highmon", "off", 100, "root", "", "bottom", "vertical", "vertical", 0, 0, "default", "cyan", "default", "on", "highmon");
+
+ return weechat::WEECHAT_RC_OK;
+}
+# Close bar
+sub highmon_bar_close
+{
+ # Find if bar exists
+ $highmon_bar = weechat::bar_search("highmon");
+ # If is does, close it
+ if ($highmon_bar ne "")
+ {
+ weechat::bar_remove($highmon_bar);
+ }
+
+ # Find if bar item exists
+ $highmon_bar_item = weechat::bar_item_search("highmon_bar");
+ # If is does, close it
+ if ($highmon_bar_item ne "")
+ {
+ weechat::bar_remove($highmon_bar_item);
+ }
+
+ @bar_lines = ();
+ return weechat::WEECHAT_RC_OK;
+}
+
+# Make a new buffer
+sub highmon_buffer_open
+{
+ # Search for pre-existing buffer
+ $highmon_buffer = weechat::buffer_search("perl", "highmon");
+
+ # Make a new buffer
+ if ($highmon_buffer eq "")
+ {
+ $highmon_buffer = weechat::buffer_new("highmon", "highmon_buffer_input", "", "highmon_buffer_close", "");
+ }
+
+ # Turn off notify, highlights
+ if ($highmon_buffer ne "")
+ {
+ if (weechat::config_get_plugin("hotlist_show") eq "off")
+ {
+ weechat::buffer_set($highmon_buffer, "notify", "0");
+ }
+ weechat::buffer_set($highmon_buffer, "highlight_words", "-");
+ weechat::buffer_set($highmon_buffer, "title", "Highlight Monitor");
+ # Set no_log
+ if (weechat::config_get_plugin("logging") eq "off")
+ {
+ weechat::buffer_set($highmon_buffer, "localvar_set_no_log", "1");
+ }
+ # send "logger_backlog" signal if logging is enabled to display backlog
+ if (weechat::config_get_plugin("logging") eq "on")
+ {
+ weechat::hook_signal_send("logger_backlog", weechat::WEECHAT_HOOK_SIGNAL_POINTER, $highmon_buffer)
+ }
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+# Buffer input has no action
+sub highmon_buffer_input
+{
+ return weechat::WEECHAT_RC_OK;
+}
+# Close up
+sub highmon_buffer_close
+{
+ $highmon_buffer = "";
+ # If user hasn't changed output style warn user
+ if (weechat::config_get_plugin("output") eq "buffer")
+ {
+ weechat::print("", "\tHighmon buffer has been closed but output is still set to buffer, unusual results may occur. To recreate the buffer use ".weechat::color("bold")."/highmon fix".weechat::color("-bold"));
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+# Highmon command wrapper
+sub highmon_command_cb
+{
+ $data = $_[0];
+ $buffer = $_[1];
+ $args = $_[2];
+ my $cmd = '';
+ my $arg = '';
+
+ if ($args ne "")
+ {
+ # Split argument up
+ @arg_array = split(/ /,$args);
+ # Take first as command
+ $cmd = shift(@arg_array);
+ # Rebuild string to pass to subs
+ if (@arg_array)
+ {
+ $arg = join(" ", @arg_array);
+ }
+ }
+
+ # Help command
+ if ($cmd eq "" || $cmd eq "help")
+ {
+ print_help();
+ }
+ # /monitor command
+ elsif ($cmd eq "monitor")
+ {
+ highmon_toggle($data, $buffer, $arg);
+ }
+ # /highclean command
+ elsif ($cmd eq "clean")
+ {
+ highmon_config_clean($data, $buffer, $arg);
+ }
+ # clearbar command
+ elsif ($cmd eq "clearbar")
+ {
+ if (weechat::config_get_plugin("output") eq "bar")
+ {
+ @bar_lines = ();
+ weechat::bar_item_update("highmon");
+ }
+ }
+ # Fix closed buffer
+ elsif ($cmd eq "fix")
+ {
+ if (weechat::config_get_plugin("output") eq "buffer" && $highmon_buffer eq "")
+ {
+ highmon_buffer_open();
+ }
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+# Clean up config entries
+sub highmon_config_clean
+{
+ $data = $_[0];
+ $buffer = $_[1];
+ $args = $_[2];
+
+ # Don't do anything if bad option given
+ if ($args ne "default" && $args ne "orphan" && $args ne "all")
+ {
+ weechat::print("", "\thighmon.pl: Unknown option");
+ return weechat::WEECHAT_RC_OK;
+ }
+
+ @chans = ();
+ # Load an infolist of highmon options
+ $infolist = weechat::infolist_get("option", "", "*highmon*");
+ while (weechat::infolist_next($infolist))
+ {
+ $name = weechat::infolist_string($infolist, "option_name");
+ $name =~ s/perl\.highmon\.(\w*)\.([#&\+!])(.*)/$1.$2$3/;
+ if ($name =~ /^(.*)\.([#&\+!])(.*)$/)
+ {
+ $action = 0;
+ # Clean up all 'on's
+ if ($args eq "default" || $args eq "all")
+ {
+ # If value in config is "on"
+ if (weechat::config_get_plugin($name) eq "on")
+ {
+ # Unset and if successful flag as changed
+ $rc = weechat::config_unset_plugin($name);
+ if ($rc eq weechat::WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED)
+ {
+ $action = 1;
+ }
+ }
+ }
+ # Clean non joined
+ if ($args eq "orphan" || $args eq "all")
+ {
+ # If we can't find the buffer for this entry
+ if (weechat::buffer_search("irc", $name) eq "")
+ {
+ # Unset and if successful flag as changed
+ $rc = weechat::config_unset_plugin($name);
+ if ($rc eq weechat::WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED)
+ {
+ $action = 1;
+ }
+ }
+ }
+ # Add changed entry names to list
+ push (@chans, $name) if ($action);
+ }
+ }
+ weechat::infolist_free($infolist);
+ # If channels were cleaned from config
+ if (@chans)
+ {
+ # If only one entry
+ if (@chans == 1)
+ {
+ $str = "\thighmon.pl: Cleaned ".@chans." entry from the config:";
+ }
+ else
+ {
+ $str = "\thighmon.pl: Cleaned ".@chans." entries from the config:";
+ }
+ # Build a list of channels
+ foreach(@chans)
+ {
+ $str = $str." ".$_;
+ }
+ # Print what happened
+ weechat::print("",$str);
+ }
+ # Config seemed to be clean
+ else
+ {
+ weechat::print("", "\thighmon.pl: No entries removed");
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+# Check config elements
+sub highmon_config_init
+{
+ # First run default
+ if (!(weechat::config_is_set_plugin ("first_run")))
+ {
+ if (weechat::config_get_plugin("first_run") ne "true")
+ {
+ weechat::print("", "\tThis appears to be the first time highmon has been run. For help and common set up hints see /highmon help");
+ weechat::config_set_plugin("first_run", "true");
+ }
+ }
+ # Alignment default
+ if (!(weechat::config_is_set_plugin ("alignment")))
+ {
+ weechat::config_set_plugin("alignment", "channel");
+ }
+ if (weechat::config_get_plugin("alignment") eq "")
+ {
+ weechat::config_set_plugin("alignment", "none");
+ }
+
+ # Short name default
+ if (!(weechat::config_is_set_plugin ("short_names")))
+ {
+ weechat::config_set_plugin("short_names", "off");
+ }
+
+ # Coloured names default
+ if (!(weechat::config_is_set_plugin ("color_buf")))
+ {
+ weechat::config_set_plugin("color_buf", "on");
+ }
+
+ # Hotlist show default
+ if (!(weechat::config_is_set_plugin ("hotlist_show")))
+ {
+ weechat::config_set_plugin("hotlist_show", "off");
+ }
+
+ # Away only default
+ if (!(weechat::config_is_set_plugin ("away_only")))
+ {
+ weechat::config_set_plugin("away_only", "off");
+ }
+
+ # highmon log default
+ if (!(weechat::config_is_set_plugin ("logging")))
+ {
+ weechat::config_set_plugin("logging", "off");
+ }
+
+ # Output default
+ if (!(weechat::config_is_set_plugin ("output")))
+ {
+ weechat::config_set_plugin("output", "buffer");
+ }
+
+ # Private message merging
+ if (!(weechat::config_is_set_plugin ("merge_private")))
+ {
+ weechat::config_set_plugin("merge_private", "off");
+ }
+
+ # Set bar config in case output was set to "bar" before even changing the setting
+ if (weechat::config_get_plugin("output") eq "bar")
+ {
+ # Output bar lines default
+ if (!(weechat::config_is_set_plugin ("bar_lines")))
+ {
+ weechat::config_set_plugin("bar_lines", "10");
+ }
+ if (!(weechat::config_is_set_plugin ("bar_scrolldown")))
+ {
+ weechat::config_set_plugin("bar_scrolldown", "off");
+ }
+ }
+
+ # Check for exisiting prefix/suffix chars, and setup accordingly
+ $prefix = weechat::config_get("irc.look.nick_prefix");
+ $prefix = weechat::config_string($prefix);
+ $suffix = weechat::config_get("irc.look.nick_suffix");
+ $suffix = weechat::config_string($suffix);
+
+ if (!(weechat::config_is_set_plugin("nick_prefix")))
+ {
+ if ($prefix eq "" && $suffix eq "")
+ {
+ weechat::config_set_plugin("nick_prefix", "<");
+ }
+ else
+ {
+ weechat::config_set_plugin("nick_prefix", "");
+ }
+ }
+
+ if (!(weechat::config_is_set_plugin("nick_suffix")))
+ {
+ if ($prefix eq "" && $suffix eq "")
+ {
+ weechat::config_set_plugin("nick_suffix", ">");
+ }
+ else
+ {
+ weechat::config_set_plugin("nick_suffix", "");
+ }
+ }
+}
+
+# Get config updates
+sub highmon_config_cb
+{
+ $point = $_[0];
+ $name = $_[1];
+ $value = $_[2];
+
+ $name =~ s/^plugins\.var\.perl\.highmon\.//;
+
+ # Set logging on buffer
+ if ($name eq "logging")
+ {
+ # Search for pre-existing buffer
+ $highmon_buffer = weechat::buffer_search("perl", "highmon");
+ if ($value eq "off")
+ {
+ weechat::buffer_set($highmon_buffer, "localvar_set_no_log", "1");
+ }
+ else
+ {
+ weechat::buffer_set($highmon_buffer, "localvar_del_no_log", "");
+ }
+ }
+ # Output changer
+ elsif ($name eq "output")
+ {
+ if ($value eq "bar")
+ {
+ # Search for pre-existing buffer
+ $highmon_buffer = weechat::buffer_search("perl", "highmon");
+ # Close if it exists
+ if ($highmon_buffer ne "")
+ {
+ weechat::buffer_close($highmon_buffer)
+ }
+
+ # Output bar lines default
+ if (!(weechat::config_is_set_plugin ("bar_lines")))
+ {
+ weechat::config_set_plugin("bar_lines", "10");
+ }
+ if (!(weechat::config_is_set_plugin ("bar_scrolldown")))
+ {
+ weechat::config_set_plugin("bar_scrolldown", "off");
+ }
+ # Make a bar if doesn't exist
+ highmon_bar_open();
+ }
+ elsif ($value eq "buffer")
+ {
+ # If a bar exists, close it
+ highmon_bar_close();
+ # Open buffer
+ highmon_buffer_open();
+ }
+
+ }
+ # Change if hotlist config changes
+ elsif ($name eq "hotlist_show")
+ {
+ # Search for pre-existing buffer
+ $highmon_buffer = weechat::buffer_search("perl", "highmon");
+ if ($value eq "off" && $highmon_buffer)
+ {
+ weechat::buffer_set($highmon_buffer, "notify", "0");
+ }
+ elsif ($value ne "off" && $highmon_buffer)
+ {
+ weechat::buffer_set($highmon_buffer, "notify", "3");
+ }
+ }
+ elsif ($name eq "weechat.look.prefix_suffix")
+ {
+ if (weechat::config_get_plugin("output") eq "bar")
+ {
+ @bar_lines = ();
+ weechat::print("", "\thighmon: weechat.look.prefix_suffix changed, clearing highmon bar");
+ weechat::bar_item_update("highmon");
+ }
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+# Set up weechat hooks / commands
+sub highmon_hook
+{
+ weechat::hook_print("", "", "", 0, "highmon_new_message", "");
+ weechat::hook_command("highclean", "Highmon config clean up", "default|orphan|all", " default: Cleans all config entries with the default \"on\" value\n orphan: Cleans all config entries for channels you aren't currently joined\n all: Does both defaults and orphan", "default|orphan|all", "highmon_config_clean", "");
+
+ weechat::hook_command("highmon", "Highmon help", "[help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar", " help: Print help on config options for highmon\n monitor: Toggles monitoring for a channel\n clean: Highmon config clean up (/highclean)\nclearbar: Clear Highmon bar", "help || monitor %(irc_channels) %(irc_servers) || clean default|orphan|all || clearbar", "highmon_command_cb", "");
+
+ weechat::hook_config("plugins.var.perl.highmon.*", "highmon_config_cb", "");
+ weechat::hook_config("weechat.look.prefix_suffix", "highmon_config_cb", "");
+}
+
+# Main body, Callback for hook_print
+sub highmon_new_message
+{
+ my $net = "";
+ my $chan = "";
+ my $nick = "";
+ my $outstr = "";
+ my $window_displayed = "";
+ my $dyncheck = "0";
+
+# DEBUG point
+# $string = "\t"."0: ".$_[0]." 1: ".$_[1]." 2: ".$_[2]." 3: ".$_[3]." 4: ".$_[4]." 5: ".$_[5]." 6: ".$_[6]." 7: ".$_[7];
+# weechat::print("", "\t".$string);
+
+ $cb_datap = $_[0];
+ $cb_bufferp = $_[1];
+ $cb_date = $_[2];
+ $cb_tags = $_[3];
+ $cb_disp = $_[4];
+ $cb_high = $_[5];
+ $cb_prefix = $_[6];
+ $cb_msg = $_[7];
+
+ # Only work on highlighted messages or private message when enabled
+ if ($cb_high == "1" || (weechat::config_get_plugin("merge_private") eq "on" && $cb_tags =~ /notify_private/))
+ {
+ # Pre bug #29618 (0.3.3) away detect
+ if (weechat::info_get("version_number", "") <= 197120)
+ {
+ $away = '';
+ # Get infolist for this server
+ $infolist = weechat::infolist_get("irc_server", "", weechat::buffer_get_string($cb_bufferp, "localvar_server"));
+ while (weechat::infolist_next($infolist))
+ {
+ # Get away message is is_away is on
+ $away = weechat::infolist_string($infolist, "away_message") if (weechat::infolist_integer($infolist, "is_away"));
+ }
+ weechat::infolist_free($infolist);
+ }
+ # Post bug #29618 fix
+ else
+ {
+ $away = weechat::buffer_get_string($cb_bufferp, "localvar_away");
+ }
+ if (weechat::config_get_plugin("away_only") ne "on" || ($away ne ""))
+ {
+ # Check buffer name is an IRC channel
+ $bufname = weechat::buffer_get_string($cb_bufferp, 'name');
+ if ($bufname =~ /(.*)\.([#&\+!])(.*)/)
+ {
+ # Are we running on this channel
+ if (weechat::config_get_plugin($bufname) ne "off" && $cb_disp eq "1")
+ {
+ # Format nick
+ # Line isn't action or topic notify
+ if (!($cb_tags =~ /irc_action/) && !($cb_tags =~ /irc_topic/))
+ {
+ # Strip nick colour
+ $uncolnick = weechat::string_remove_color($cb_prefix, "");
+ # Format nick
+ $nick = " ".weechat::config_get_plugin("nick_prefix").weechat::color("chat_highlight").$uncolnick.weechat::color("reset").weechat::config_get_plugin("nick_suffix");
+ }
+ # Topic line
+ elsif ($cb_tags =~ /irc_topic/)
+ {
+ $nick = " ".$cb_prefix.weechat::color("reset");
+ }
+ # Action line
+ else
+ {
+ $uncolnick = weechat::string_remove_color($cb_prefix, "");
+ $nick = weechat::color("chat_highlight").$uncolnick.weechat::color("reset");
+ }
+ # Send to output
+ highmon_print ($cb_msg, $cb_bufferp, $nick, $cb_date, $cb_tags);
+ }
+ }
+ # Or is private message
+ elsif (weechat::config_get_plugin("merge_private") eq "on" && $cb_tags =~ /notify_private/)
+ {
+ # Strip nick colour
+ $uncolnick = weechat::buffer_get_string($cb_bufferp, 'short_name');
+ # Format nick
+ $nick = " ".weechat::config_get_plugin("nick_prefix").weechat::color("chat_highlight").$uncolnick.weechat::color("reset").weechat::config_get_plugin("nick_suffix");
+ #Send to output
+ highmon_print ($cb_msg, $cb_bufferp, $nick, $cb_date, $cb_tags);
+ }
+ }
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+# Output formatter and printer takes (msg bufpointer nick)
+sub highmon_print
+{
+ $cb_msg = $_[0];
+ my $cb_bufferp = $_[1] if ($_[1]);
+ my $nick = $_[2] if ($_[2]);
+ my $cb_date = $_[3] if ($_[3]);
+ my $cb_tags = $_[4] if ($_[4]);
+
+ #Normal channel message
+ if ($cb_bufferp && $nick)
+ {
+ # Format buffer name
+ $bufname = format_buffer_name($cb_bufferp);
+
+ # If alignment is #channel | nick msg
+ if (weechat::config_get_plugin("alignment") eq "channel")
+ {
+ $nick =~ s/\s(.*)/$1/;
+ # Build string
+ $outstr = $bufname."\t".$nick." ".$cb_msg;
+ }
+ # or if it is channel number | nick msg
+ elsif (weechat::config_get_plugin("alignment") eq "schannel")
+ {
+ $nick =~ s/\s(.*)/$1/;
+ # Use channel number instead
+ $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset");
+ # Build string
+ $outstr = $bufname."\t".$nick." ".$cb_msg;
+ }
+ # or if it is number:#channel | nick msg
+ elsif (weechat::config_get_plugin("alignment") eq "nchannel")
+ {
+ $nick =~ s/\s(.*)/$1/;
+ # Place channel number in front of formatted name
+ $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname;
+ # Build string
+ $outstr = $bufname."\t".$nick." ".$cb_msg;
+ }
+ # or if it is #channel nick | msg
+ elsif (weechat::config_get_plugin("alignment") eq "channel,nick")
+ {
+ # Build string
+ $outstr = $bufname.":".$nick."\t".$cb_msg;
+ }
+ # or if it is channel number nick | msg
+ elsif (weechat::config_get_plugin("alignment") eq "schannel,nick")
+ {
+ # Use channel number instead
+ $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset");
+ # Build string
+ $outstr = $bufname.":".$nick."\t".$cb_msg;
+ }
+ # or if it is number:#channel nick | msg
+ elsif (weechat::config_get_plugin("alignment") eq "nchannel,nick")
+ {
+ # Place channel number in front of formatted name
+ $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname;
+ # Build string
+ $outstr = $bufname.":".$nick."\t".$cb_msg;
+ }
+ # or finally | #channel nick msg
+ else
+ {
+ # Build string
+ $outstr = "\t".$bufname.":".$nick." ".$cb_msg;
+ }
+ }
+ # highmon channel toggle message
+ elsif ($cb_bufferp && !$nick)
+ {
+ # Format buffer name
+ $bufname = format_buffer_name($cb_bufferp);
+
+ # If alignment is #channel * | *
+ if (weechat::config_get_plugin("alignment") =~ /channel/)
+ {
+ # If it's actually channel number * | *
+ if (weechat::config_get_plugin("alignment") =~ /schannel/)
+ {
+ # Use channel number instead
+ $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset");
+ }
+ # Or if it's actually number:#channel * | *
+ if (weechat::config_get_plugin("alignment") =~ /nchannel/)
+ {
+ # Place channel number in front of formatted name
+ $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname;
+ }
+ $outstr = $bufname."\t".$cb_msg;
+ }
+ # or if alignment is | *
+ else
+ {
+ $outstr = $bufname.": ".$cb_msg;
+ }
+ }
+ # highmon dynmon
+ elsif (!$cb_bufferp && !$nick)
+ {
+ $outstr = "\t".$cb_msg;
+ }
+
+ # Send string to buffer
+ if (weechat::config_get_plugin("output") eq "buffer")
+ {
+ # Search for and confirm buffer
+ $highmon_buffer = weechat::buffer_search("perl", "highmon");
+ # Print
+ if ($cb_date)
+ {
+ weechat::print_date_tags($highmon_buffer, $cb_date, $cb_tags, $outstr);
+ }
+ else
+ {
+ weechat::print($highmon_buffer, $outstr);
+ }
+ }
+ elsif (weechat::config_get_plugin("output") eq "bar")
+ {
+ # Add time string
+ use POSIX qw(strftime);
+ if ($cb_date)
+ {
+ $time = strftime(weechat::config_string(weechat::config_get("weechat.look.buffer_time_format")), localtime($cb_date));
+ }
+ else
+ {
+ $time = strftime(weechat::config_string(weechat::config_get("weechat.look.buffer_time_format")), localtime);
+ }
+ # Colourise
+ if ($time =~ /\$\{(?:color:)?[\w,]+\}/) # Coloured string
+ {
+ while ($time =~ /\$\{(?:color:)?([\w,]+)\}/)
+ {
+ $color = weechat::color($1);
+ $time =~ s/\$\{(?:color:)?[\w,]+\}/$color/;
+ }
+ $time .= weechat::color("reset");
+ }
+ else # Default string
+ {
+ $colour = weechat::color(weechat::config_string(weechat::config_get("weechat.color.chat_time_delimiters")));
+ $reset = weechat::color("reset");
+ $time =~ s/(\d*)(.)(\d*)/$1$colour$2$reset$3/g;
+ }
+ # Push updates to bar lists
+ push (@bar_lines_time, $time);
+
+ # Change tab char
+ $delim = " ".weechat::color(weechat::config_string(weechat::config_get("weechat.color.chat_delimiters"))).weechat::config_string(weechat::config_get("weechat.look.prefix_suffix")).weechat::color("reset")." ";
+ $outstr =~ s/\t/$delim/;
+
+ push (@bar_lines, $outstr);
+ # Trigger update
+ weechat::bar_item_update("highmon");
+
+ if (weechat::config_get_plugin("bar_scrolldown") eq "on")
+ {
+ weechat::command("", "/bar scroll highmon * ye")
+ }
+ }
+}
+
+# Start the output display
+sub highmon_start
+{
+ if (weechat::config_get_plugin("output") eq "buffer")
+ {
+ highmon_buffer_open();
+ }
+ elsif (weechat::config_get_plugin("output") eq "bar")
+ {
+ highmon_bar_open();
+ }
+}
+
+# Takes two optional args (channel server), toggles monitoring on/off
+sub highmon_toggle
+{
+ $data = $_[0];
+ $buffer = $_[1];
+ $args = $_[2];
+
+ # Check if we've been told what channel to act on
+ if ($args ne "")
+ {
+ # Split argument up
+ @arg_array = split(/ /,$args);
+ # Check if a server was given
+ if ($arg_array[1])
+ {
+ # Find matching
+ $bufp = weechat::buffer_search("irc", $arg_array[1].".".$arg_array[0]);
+ }
+ else
+ {
+ $found_chans = 0;
+ # Loop through defined servers
+ $infolist = weechat::infolist_get("buffer", "", "");
+ while (weechat::infolist_next($infolist))
+ {
+ # Only interesting in IRC buffers
+ if (weechat::infolist_string($infolist, "plugin_name") eq "irc")
+ {
+ # Find buffers that maych
+ $sname = weechat::infolist_string($infolist, "short_name");
+ if ($sname eq $arg_array[0])
+ {
+ $found_chans++;
+ $bufp = weechat::infolist_pointer($infolist, "pointer");
+ }
+ }
+ }
+ weechat::infolist_free($infolist);
+ # If the infolist found more than one channel, halt as we need to know which one
+ if ($found_chans > 1)
+ {
+ weechat::print("", "Channel name is not unique, please define server");
+ return weechat::WEECHAT_RC_OK;
+ }
+ }
+ # Something didn't return right
+ if ($bufp eq "")
+ {
+ weechat::print("", "Could not find buffer");
+ return weechat::WEECHAT_RC_OK;
+ }
+ }
+ else
+ {
+ # Get pointer from where we are
+ $bufp = weechat::current_buffer();
+ }
+ # Get buffer name
+ $bufname = weechat::buffer_get_string($bufp, 'name');
+ # Test if buffer is an IRC channel
+ if ($bufname =~ /(.*)\.([#&\+!])(.*)/)
+ {
+ if (weechat::config_get_plugin($bufname) eq "off")
+ {
+ # If currently off, set on
+ weechat::config_set_plugin($bufname, "on");
+
+ # Send to output formatter
+ highmon_print("Highlight Monitoring Enabled", $bufp);
+ return weechat::WEECHAT_RC_OK;
+ }
+ elsif (weechat::config_get_plugin($bufname) eq "on" || weechat::config_get_plugin($bufname) eq "")
+ {
+ # If currently on, set off
+ weechat::config_set_plugin($bufname, "off");
+
+ # Send to output formatter
+ highmon_print("Highlight Monitoring Disabled", $bufp);
+ return weechat::WEECHAT_RC_OK;
+ }
+ }
+}
+
+# Takes a buffer pointer and returns a formatted name
+sub format_buffer_name
+{
+ $cb_bufferp = $_[0];
+ $bufname = weechat::buffer_get_string($cb_bufferp, 'name');
+
+ # Set colour from buffer name
+ if (weechat::config_get_plugin("color_buf") eq "on")
+ {
+ # Determine what colour to use
+ $color = weechat::info_get("irc_nick_color", $bufname);
+ if (!$color)
+ {
+ $color = 0;
+ @char_array = split(//,$bufname);
+ foreach $char (@char_array)
+ {
+ $color += ord($char);
+ }
+ $color %= 10;
+ $color = sprintf "weechat.color.chat_nick_color%02d", $color+1;
+ $color = weechat::config_get($color);
+ $color = weechat::config_string($color);
+ $color = weechat::color($color);
+ }
+
+ # Private message just show network
+ if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private")
+ {
+ $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server");
+ }
+ # Format name to short or 'nicename'
+ elsif (weechat::config_get_plugin("short_names") eq "on")
+ {
+ $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name');
+ }
+ else
+ {
+ $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/;
+ }
+
+ # Build a coloured string
+ $bufname = $color.$bufname.weechat::color("reset");
+ }
+ # User set colour name
+ elsif (weechat::config_get_plugin("color_buf") ne "off")
+ {
+ # Private message just show network
+ if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private")
+ {
+ $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server");
+ }
+ # Format name to short or 'nicename'
+ elsif (weechat::config_get_plugin("short_names") eq "on")
+ {
+ $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name');
+ }
+ else
+ {
+ $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/;
+ }
+
+ $color = weechat::config_get_plugin("color_buf");
+ $bufname = weechat::color($color).$bufname.weechat::color("reset");
+ }
+ # Stick with default colour
+ else
+ {
+ # Private message just show network
+ if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private")
+ {
+ $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server");
+ }
+ # Format name to short or 'nicename'
+ elsif (weechat::config_get_plugin("short_names") eq "on")
+ {
+ $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name');
+ }
+ else
+ {
+ $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/;
+ }
+ }
+
+ return $bufname;
+}
+
+# Check result of register, and attempt to behave in a sane manner
+if (!weechat::register("highmon", "KenjiE20", "2.6", "GPL3", "Highlight Monitor", "", ""))
+{
+ # Double load
+ weechat::print ("", "\tHighmon is already loaded");
+ return weechat::WEECHAT_RC_OK;
+}
+else
+{
+ # Start everything
+ highmon_hook();
+ highmon_config_init();
+ highmon_start();
+}
diff --git a/scripts/perl/hueg.pl b/scripts/perl/hueg.pl
@@ -0,0 +1,1255 @@
+#!/usr/bin/perl
+
+
+######
+# hueg.pl PRO MODE
+# modded by ma0 and others
+# respekts 2 jakk and others
+######
+
+$maxchars = 10; #num of chars b4 split
+my $reverse = 0;
+my $flip = 0;
+my $mirror = 0;
+my $scale = 1;
+
+my $SCRIPT_NAME = 'hueg';
+my $SCRIPT_AUTHOR = 'LIFELIKE <i@wnt2die.com>';
+my $SCRIPT_VERSION = '1.1';
+my $SCRIPT_LICENCE = 'GPL3';
+my $SCRIPT_DESC = 'make text hueg LOL';
+
+if (weechat::register($SCRIPT_NAME, $SCRIPT_AUTHOR, $SCRIPT_VERSION,
+ $SCRIPT_LICENCE, $SCRIPT_DESC, '', '')) {
+ weechat::hook_command('hueg', '', '<string> [options]',
+ " -rep <num> num of times to scroll msg\n".
+ " -re reverses text\n".
+ " -flip flips text\n".
+ " -mir mirrors your text [NOT WORKIN LOL]\n".
+ " -scale <num> scales shit\n".
+ " num,num,num fg, shadow, bg colors (bg optional)",
+ '-rep|-re|-flip|-mir|-scale|%*', 'hueg', '');
+}
+
+sub hueg {
+ (undef, $buffer, $data) = @_;
+ $in = $data;
+
+ if ($in =~ /-rep (\d+)/i) {
+ $rep = $1;
+ $in =~ s/-rep \d+//i;
+ } else {
+ $rep = 1;
+ }
+
+ if($in =~ /-scale (\d+)/i) {
+ $scale = $1;
+ $in =~ s/-scale \d+//i;
+ } else {
+ $scale = 1;
+ }
+
+ if($in =~ /-re/i) {
+ $reverse = 1;
+ $in =~ s/-re//i;
+ } else {
+ $reverse = 0;
+ }
+
+ if($in =~ /-flip/i){
+ $flip = 1;
+ $in =~ s/-flip//i;
+ } else {
+ $flip = 0;
+ }
+
+ if($in =~ /-mir/i) {
+ $mirror = 1;
+ $in =~ s/-mir//i;
+ } else {
+ $mirror = 0;
+ }
+
+ $in =~ s/\s+$//;
+
+ if ($in eq '') {
+ weechat::print($buffer, weechat::prefix('error').
+ "Invalid syntax; see /help hueg");
+ return weechat::WEECHAT_RC_ERROR;
+ } else {
+ until ($rep == 0) {
+ colors();
+ parse();
+ process();
+ select(undef,undef,undef,.1);
+ $rep--;
+ }
+ return weechat::WEECHAT_RC_OK;
+ }
+}
+
+sub colors {
+ if ($data =~ /(\d+),(\d+),(\d+)/) {
+ $c2 = "\cC$1,$1"; #fg
+ $c1 = "\cC$2,$2"; #sh
+ $c3 = "\cC$3,$3"; #bg
+ $in =~ s/\d+,\d+,\d+//;
+ } elsif ($data =~ /(\d+),(\d+)/) {
+ $c2 = "\cC$1,$1"; #fg
+ $c1 = "\cC$2,$2"; #sh
+ $c3 = "\cO"; #bg (trans)
+ $in =~ s/\d+,\d+//;
+ } else {
+ $r1 = $r2 = 0;
+ until ($r1 > 1) { $r1 = int rand(15); }
+ until ($r2 > 1 && $r2 != $r1) { $r2 = int rand(15); }
+ $c2 = "\cC$r1,$r1"; #fg (rand)
+ $c1 = "\cC$r2,$r2"; #sh (rand)
+ $c3 = "\cO"; #bg (trans)
+ }
+ db1();
+}
+
+sub parse {
+ $in =~ s/(\S{$maxchars})/$1 /g;
+ undef @s0;
+ @s0 = split(' ',$in);
+ undef @s1;
+ $s1n = 0;
+ for $n (@s0) {
+ $nlen = length($n);
+ $slen = length($s1[$s1n] // '') + $nlen;
+ if ($slen <= $maxchars) {
+ $s1[$s1n] .= "$n ";
+ } else {
+ $s1n++;
+ $s1[$s1n] .= "$n ";
+ }
+ }
+}
+
+sub process {
+ for $n (@s1) { #each line
+ if ($reverse) {
+ $n = reverse $n;
+ }
+
+ $n =~ s/\s$//;
+ $n =~ s/^\s//;
+ undef @s2;
+ @s2 = split('',$n);
+ my $cur; # current string
+ my $tmp;
+
+ for $f (0..8*$scale) {
+ for $l (@s2) { #each letter
+ $all .= "$c3 \cO";
+
+ if($flip) { $cur = $db{$l}[(9-$f)/$scale] // $db{'?'}[(9-$f)/$scale]; } #line of letter
+ else { $cur = $db{$l}[$f/$scale] // $db{'?'}[$f/$scale]; }
+
+ $whitespace = " " x $scale;
+ $cur =~ s/ /$whitespace/g;
+ $whitespace = "#" x $scale;
+ $cur =~ s/#/$whitespace/g;
+ $whitespace = "." x $scale;
+ $cur =~ s/\./$whitespace/g;
+ $all .= $cur;
+ }
+
+ $all .= "$c3 ";
+ if($mirror) { $all = reverse $all; }
+ weechat::command($buffer, "/msg * $all");
+ $all = '';
+ }
+
+ }
+}
+
+#------------------#
+# character db #
+# lol #
+#------------------#
+
+sub db1 {
+%db = (
+" " => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+"\cC" => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2#######$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2############$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2#######$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+"\cB" => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2#######$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2############$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2#######$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+"\cO" => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2#######$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2############$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2#######$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+"0" => [
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+"1" => [
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2###$c3 ",
+"$c1.$c2####$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2######$c3",
+"$c3 ",
+],
+"2" => [
+"$c3 ",
+"$c3 $c1.$c2#####$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2#######$c3",
+"$c3 ",
+],
+"3" => [
+"$c3 ",
+"$c3 $c1.$c2#####$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2###$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2#####$c3 ",
+"$c3 ",
+],
+"4" => [
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2###$c3 ",
+"$c3 $c1.$c2#$c1.$c2##$c3 ",
+"$c3 $c1.$c2#$c3 $c1.$c2##$c3 ",
+"$c1.$c2#######$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+"5" => [
+"$c3 ",
+"$c1.$c2######$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2######$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2#####$c3 ",
+"$c3 ",
+],
+"6" => [
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2#####$c3 ",
+"$c3 ",
+],
+"7" => [
+"$c3 ",
+"$c1.$c2########$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+"8" => [
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+"9" => [
+"$c3 ",
+"$c3 $c1.$c2#####$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+A => [
+"$c3 ",
+"$c3 $c1.$c2###$c3 ",
+"$c3 $c1.$c2##$c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2#######$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+a => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2#####$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2#######$c3",
+"$c3 ",
+],
+B => [
+"$c3 ",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2#######$c3 ",
+"$c3 ",
+],
+b => [
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2######$c3 ",
+"$c3 ",
+],
+C => [
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+c => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2#####$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2#####$c3",
+"$c3 ",
+],
+D => [
+"$c3 ",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2#######$c3 ",
+"$c3 ",
+],
+d => [
+"$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3",
+"$c3 ",
+],
+E => [
+"$c3 ",
+"$c1.$c2#######$c3",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2######$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2#######$c3",
+"$c3 ",
+],
+e => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+F => [
+"$c3 ",
+"$c1.$c2#######",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2######$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 ",
+],
+f => [
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2###",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2#####",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+G => [
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2####$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+g => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2#######$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3 ",
+],
+H => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2########$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+h => [
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+I => [
+"$c3 ",
+"$c1.$c2######$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2######$c3",
+"$c3 ",
+],
+i => [
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+"$c1.$c2###$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2####",
+"$c3 ",
+],
+J => [
+"$c3 ",
+"$c3 $c1.$c2######",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2####$c3 ",
+"$c3 ",
+],
+j => [
+"$c3 ",
+"$c3 $c1.$c2##",
+"$c3 ",
+"$c3 $c1.$c2###",
+"$c3 $c1.$c2##",
+"$c3 $c1.$c2##",
+"$c3 $c1.$c2##",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c3 $c1.$c2####$c3 ",
+],
+K => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c1.$c2##$c3 ",
+"$c1.$c2####$c3 ",
+"$c1.$c2##$c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+k => [
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2#####$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c3 ",
+],
+L => [
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2#######$c3",
+"$c3 ",
+],
+l => [
+"$c3 ",
+"$c1.$c2###$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2####",
+"$c3 ",
+],
+M => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2###$c3 $c1.$c2###$c3",
+"$c1.$c2####$c3 $c1.$c2####$c3",
+"$c1.$c2##$c1.$c2##$c1.$c2##$c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2###$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2#$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+m => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2####$c1.$c2####$c3",
+"$c1.$c2##$c1.$c2###$c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2#$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+N => [
+"$c3 ",
+"$c1.$c2###$c3 $c1.$c2##",
+"$c1.$c2####$c3 $c1.$c2##",
+"$c1.$c2##$c1.$c2##$c3 $c1.$c2##",
+"$c1.$c2##$c3 $c1.$c2##$c3 $c1.$c2##",
+"$c1.$c2##$c3 $c1.$c2##$c1.$c2##",
+"$c1.$c2##$c3 $c1.$c2####",
+"$c1.$c2##$c3 $c1.$c2###",
+"$c3 ",
+],
+n => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+O => [
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+o => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+P => [
+"$c3 ",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 ",
+],
+p => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+],
+Q => [
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2###$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c3 $c1.$c2##$c3 ",
+],
+q => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2#######$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+],
+R => [
+"$c3 ",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2#######$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c3 ",
+],
+r => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2#####$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 ",
+],
+S => [
+"$c3 ",
+"$c3 $c1.$c2#####$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2#####$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2#####$c3 ",
+"$c3 ",
+],
+s => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2#####$c3",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2####$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c1.$c2#####$c3 ",
+"$c3 ",
+],
+T => [
+"$c3 ",
+"$c1.$c2########$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+t => [
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2#####$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c1.$c2##",
+"$c3 $c1.$c2###$c3 ",
+"$c3 ",
+],
+U => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+u => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2######$c3 ",
+"$c3 ",
+],
+V => [
+"$c3 ",
+"$c1.$c2#$c3 $c1.$c2#",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2####$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+v => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c1.$c2##$c3 ",
+"$c3 $c1.$c2###$c3 ",
+"$c3 ",
+],
+W => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c1.$c2##$c3 $c1.$c2##$c3 $c1.$c2##",
+"$c1.$c2##$c1.$c2####$c1.$c2##",
+"$c1.$c2####$c3 $c1.$c2####",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+w => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2#$c3 $c1.$c2##$c3",
+"$c1.$c2##$c1.$c2###$c1.$c2##$c3",
+"$c1.$c2####$c1.$c2####$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+X => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c1.$c2##$c3 ",
+"$c3 $c1.$c2###$c3 ",
+"$c3 $c1.$c2##$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+x => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c1.$c2##$c3 ",
+"$c3 $c2###$c3 ",
+"$c3 $c1.$c2##$c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+Y => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2####$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+y => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2####$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+],
+Z => [
+"$c3 ",
+"$c1.$c2########$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2########$c3",
+"$c3 ",
+],
+z => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2########$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2########$c3 ",
+"$c3 ",
+],
+'~' => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2####$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2####$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'`' => [
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'!' => [
+"$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 ",
+],
+'@' => [
+"$c3 ",
+"$c3 $c1.$c2#######$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2#####$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2#######$c3 ",
+"$c3 ",
+],
+'#' => [
+"$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##########",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##########",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+'$' => [
+"$c3 $c1.$c2#$c3 ",
+"$c3 $c1.$c2#######$c3 ",
+"$c1.$c2##$c3 $c1.$c2#$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2#$c3 ",
+"$c3 $c1.$c2#######$c3 ",
+"$c3 $c1.$c2#$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2#$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2#######$c3 ",
+"$c3 $c1.$c2#$c3 ",
+],
+'%' => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##",
+"$c3 ",
+],
+'^' => [
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2###$c3 ",
+"$c3 $c1.$c2##$c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'&' => [
+"$c3 ",
+"$c3 $c1.$c2####$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2####$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2######$c1.$c2##$c3",
+"$c3 ",
+],
+'*' => [
+"$c3 ",
+"$c3 ",
+"$c1.$c2#$c3 $c1.$c2#",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2#$c3 $c1.$c2#",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'(' => [
+"$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+')' => [
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 ",
+],
+'_' => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2########$c3",
+"$c3 ",
+],
+'-' => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2########$c3",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'+' => [
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2########$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'=' => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2########$c3",
+"$c3 ",
+"$c1.$c2########$c3",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'|' => [
+"$c3 ",
+"$c1.$c2##$c3",
+"$c1.$c2##$c3",
+"$c1.$c2##$c3",
+"$c1.$c2##$c3",
+"$c1.$c2##$c3",
+"$c1.$c2##$c3",
+"$c1.$c2##$c3",
+"$c3 ",
+],
+'\\' => [
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 ",
+],
+'[' => [
+"$c3 ",
+"$c1.$c2####$c3",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c1.$c2####$c3",
+"$c3 ",
+],
+']' => [
+"$c3 ",
+"$c1.$c2####$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3",
+"$c1.$c2####$c3",
+"$c3 ",
+],
+'{' => [
+"$c3 ",
+"$c3 $c1.$c2###$c3",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2###$c3",
+"$c3 ",
+],
+'}' => [
+"$c3 ",
+"$c1.$c2###$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c1.$c2###$c3 ",
+"$c3 ",
+],
+':' => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+"$c3 ",
+],
+';' => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2#$c3 ",
+"$c3 ",
+],
+'\'' => [
+"$c3 ",
+"$c3 $c1.$c2##",
+"$c1.$c2##$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'"' => [
+"$c3 ",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c1.$c2##$c3 $c1.$c2##$c3",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'<' => [
+"$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 ",
+"$c3 ",
+],
+'>' => [
+"$c3 ",
+"$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 ",
+"$c3 ",
+],
+'?' => [
+"$c3 ",
+"$c3 $c1.$c2#####$c3 ",
+"$c3 $c1.$c2##$c3 $c1.$c2##",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 ",
+],
+"\," => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2##$c3",
+"$c3 $c1.$c2#$c3",
+],
+"\." => [
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c3 ",
+"$c1.$c2##",
+"$c3 ",
+],
+"\/" => [
+"$c3 ",
+"$c3 $c1.$c2##$c3",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c3 $c1.$c2##$c3 ",
+"$c1.$c2##$c3 ",
+"$c3 ",
+],
+);
+}
diff --git a/scripts/perl/keepnick.pl b/scripts/perl/keepnick.pl
@@ -0,0 +1,279 @@
+# This script was made to improve upon keepnick.py
+
+use strict;
+use warnings;
+
+no strict 'subs';
+
+my $SCRIPT_NAME = 'keepnick';
+my $SCRIPT_AUTHOR = 'The Krusty Krab <wowaname@volatile.ch>';
+my $SCRIPT_VERSION = '1.0';
+my $SCRIPT_LICENCE = 'Public domain';
+my $SCRIPT_DESC = 'Keep your primary nickname';
+
+our (%waiting, %connecting, %noison);
+
+my %OPTIONS = (
+ default_enable => ['Whether to enable keepnick on servers by default', '0'],
+ check_time => ['Time between ISON checks (reload script to take effect)', '60'],
+);
+
+
+sub arrindex
+{
+ 1 while $_[0] ne pop;
+ return @_ - 1;
+}
+
+sub info
+{
+ weechat::print(weechat::buffer_search('irc', 'server.'.shift),
+ weechat::prefix('server').shift);
+}
+
+sub my_nick
+{ return lc weechat::info_get('irc_nick', shift); }
+
+sub is_preferable
+{
+ my ($server, $target) = @_;
+ my ($targidx, $myidx) = (
+ arrindex($target, @{$waiting{$server}}),
+ arrindex(my_nick($server), @{$waiting{$server}}));
+ return ($myidx == -1 or $targidx < $myidx);
+}
+
+sub is_waiting
+{
+ my $server = lc shift;
+ return exists $waiting{$server} ? @{ $waiting{$server} } : ();
+}
+
+
+sub disable
+{ delete $waiting{lc shift}; }
+
+sub enable
+{
+ my $server = shift;
+ my $force = shift // 0;
+ my @target = @_ ? split ',', shift : ();
+ unless (@target) {
+ my ($conf_nicks, $confd_nicks) = (
+ weechat::config_get("irc.server.$server.nicks"),
+ weechat::config_get("irc.server_default.nicks"));
+ @target = split ',', weechat::config_string(
+ weechat::config_option_is_null($conf_nicks)
+ ? $confd_nicks : $conf_nicks);
+ }
+ $server = lc $server;
+ my ($defaultconf, $serverconf) = (
+ weechat::config_get_plugin('default_enable'),
+ weechat::config_get_plugin("server.$server"));
+ unless ($force) {
+ return () if $serverconf ne '' and !$serverconf;
+ return () if $serverconf eq '' and !$defaultconf;
+ }
+ return () if lc $target[0] eq my_nick($server);
+
+ $waiting{$server} = \@target;
+ return @target;
+}
+
+
+sub do_nick
+{
+ my ($server, $nick) = @_;
+ weechat::hook_signal_send('irc_input_send',
+ weechat::WEECHAT_HOOK_SIGNAL_STRING,
+ "$server;;;;/nick $nick");
+ if (lc $waiting{$server}->[0] eq lc $nick) {
+ # we're done if we have our primary nick choice, otherwise keep trying
+ disable($server);
+ info($server, "got your primary nick!");
+ } else {
+ info($server, "got one of your nicks; still trying for your primary nick");
+ }
+}
+
+sub try_nick
+{
+ my ($server, $oldnick) = @_;
+ ($server, $oldnick) = (lc $server, lc $oldnick);
+ my ($mynick, @confnick) = (my_nick($server), is_waiting($server));
+ return unless @confnick;
+ return unless is_preferable($server, $oldnick);
+
+ if ($mynick eq $oldnick) { disable($server); }
+ else { lc $_ eq $oldnick and do_nick($server, $_) for @confnick; }
+}
+
+sub ison_check
+{
+ my $iptr = weechat::infolist_get('irc_server', '', '');
+
+ while (weechat::infolist_next($iptr)) {
+ next unless weechat::infolist_integer($iptr, 'is_connected');
+ my $server = lc weechat::infolist_string($iptr, 'name');
+ next unless is_waiting($server);
+ next unless exists $noison{$server};
+ weechat::hook_hsignal_send('irc_redirect_command', {
+ server => $server,
+ pattern => 'ison',
+ signal => $SCRIPT_NAME,
+ timeout => weechat::config_get_plugin('check_time') - 1,
+ });
+ weechat::hook_signal_send('irc_input_send',
+ weechat::WEECHAT_HOOK_SIGNAL_STRING,
+ "$server;;;;/ison ".(join ' ', @{ $waiting{$server} }));
+ }
+
+ weechat::infolist_free($iptr);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_ison
+{
+ my %hashtable = %{ pop() };
+ unless ($hashtable{output}) {
+ $noison{lc $hashtable{server}} = 1;
+ return weechat::WEECHAT_RC_ERROR;
+ }
+ my %nicks = map { lc $_ => 1 }
+ split / +/, ($hashtable{output} =~ s/^:[^ ]* 303 [^ ]+ :?//r);
+
+ for my $confnick (is_waiting($hashtable{server})) {
+ next if exists $nicks{lc $confnick}; # still taken
+ do_nick($hashtable{server}, $confnick);
+ last;
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_nick
+{
+ my (undef, $server, $oldnick, $newnick) = (shift,
+ shift =~ /(.+),irc_raw_in_nick/i,
+ shift =~ /:([^! ]+)[^ ]* nick :?(.*)/i);
+ return weechat::WEECHAT_RC_OK if lc $oldnick eq lc $newnick;
+ try_nick($server, $oldnick);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_quit
+{
+ my (undef, $server, $nick) = (shift,
+ shift =~ /(.+),irc_raw_in_quit/i,
+ shift =~ /:([^! ]+)[^ ]* quit /i);
+ try_nick($server, $nick);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_433
+{
+ my ($server) = $_[1] =~ /(.+),irc_raw_in2_433/i;
+ $server = lc $server;
+ # nick is taken when we connect? enable
+ if (exists $connecting{$server}) {
+ enable($server) and info($server, 'keepnick enabled');
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_connecting
+{
+ $connecting{lc pop} = 1;
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_connected
+{
+ delete $connecting{lc pop};
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub irc_disconnect
+{
+ my $server = lc pop;
+ delete $connecting{$server};
+ disable($server);
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub cmd_keepnick
+{
+ my (undef, $buffer, $command) = @_;
+ my $server = lc weechat::buffer_get_string($buffer, 'localvar_server');
+ for ($command) {
+ chomp;
+ if (/^$/) {
+ info($server, is_waiting($server)
+ ? "trying to get '".(join "', '", is_waiting($server))."'"
+ : "not keeping your nick");
+ }
+ elsif (/^-enable$/) {
+ my @waiting = enable($server, 1);
+ if (@waiting) { info($server, "enabled, now waiting for '".(join "', '", @waiting)."'"); }
+ else { info($server, "you already have your primary nick! doing nothing"); }
+ }
+ elsif (/^-disable$/) {
+ disable($server);
+ info($server, "no longer trying to keep your nick");
+ }
+ elsif (/^-autoenable$/) {
+ weechat::config_get_plugin('default_enable')
+ ? weechat::config_unset_plugin("server.$server")
+ : weechat::config_set_plugin("server.$server", '1');
+ enable($server);
+ info($server, "keepnick enabled on this server");
+ }
+ elsif (/^-autodisable$/) {
+ weechat::config_get_plugin('default_enable')
+ ? weechat::config_set_plugin("server.$server", '0')
+ : weechat::config_unset_plugin("server.$server");
+ disable($server);
+ info($server, "keepnick disabled on this server");
+ }
+ else {
+ my @waiting = enable($server, 1, $command =~ s/ /,/gr);
+ if (@waiting) { info($server, "now waiting for '".(join "', '", @waiting)."'"); }
+ else { info($server, "you already have your primary nick! doing nothing"); }
+ }
+ }
+
+ return weechat::WEECHAT_RC_OK;
+}
+
+if (weechat::register($SCRIPT_NAME, $SCRIPT_AUTHOR, $SCRIPT_VERSION,
+ $SCRIPT_LICENCE, $SCRIPT_DESC, '', '')) {
+ for my $option (keys %OPTIONS) {
+ weechat::config_set_plugin($option, $OPTIONS{$option}[1])
+ unless weechat::config_is_set_plugin($option);
+ weechat::config_set_desc_plugin($option, $OPTIONS{$option}[0]);
+ }
+
+ weechat::hook_command('keepnick', $SCRIPT_DESC,
+ "[nick]\n-enable|-disable\n-autoenable|-autodisable",
+ "This command allows you to check the current server keepnick\n".
+ "status or to manually specify nicks (separated by comma) to keep.\n".
+ "You may also manually -enable or -disable keepnick on the current\n".
+ "server. Both -autoenable and -autodisable choices are saved in\n".
+ "config under 'plugins.var.perl.$SCRIPT_NAME.server.xxx' where 'xxx'\n".
+ "is the server name.",
+ '-enable|-disable|-autoenable|-autodisable %-', 'cmd_keepnick', '');
+ weechat::hook_signal('*,irc_raw_in_nick', 'irc_nick', '');
+ weechat::hook_signal('*,irc_raw_in_quit', 'irc_quit', '');
+ weechat::hook_signal('*,irc_raw_in2_433', 'irc_433', '');
+ weechat::hook_signal('irc_server_connecting', 'irc_connecting', '');
+ weechat::hook_signal('irc_server_connected', 'irc_connected', '');
+ weechat::hook_signal('irc_server_disconnected', 'irc_disconnect', '');
+ weechat::hook_timer(weechat::config_get_plugin('check_time') * 1000, 0, 0,
+ 'ison_check', '');
+ weechat::hook_hsignal("irc_redirection_${SCRIPT_NAME}_ison", 'irc_ison', '');
+
+ my $iptr = weechat::infolist_get('irc_server', '', '');
+ enable(weechat::infolist_string($iptr, 'name'))
+ while (weechat::infolist_next($iptr));
+ weechat::infolist_free($iptr);
+}
diff --git a/scripts/perl/multiline.pl b/scripts/perl/multiline.pl
@@ -0,0 +1,798 @@
+use strict; use warnings;
+$INC{'Encode/ConfigLocal.pm'}=1;
+require Encode;
+use utf8;
+
+# multiline.pl is written by Nei <anti.teamidiot.de>
+# and licensed under the under GNU General Public License v3
+# or any later version
+
+# to read the following docs, you can use "perldoc multiline.pl"
+
+=head1 NAME
+
+multiline - Multi-line edit box for WeeChat (weechat edition)
+
+=head1 DESCRIPTION
+
+multiline will draw a multi-line edit box to your WeeChat window so
+that when you hit the return key, you can first compose a complete
+multi-line message before sending it all at once.
+
+Furthermore, if you have multi-line pastes then you can edit them
+before sending out all the lines.
+
+=head1 USAGE
+
+make a key binding to send the finished message:
+
+ /key bind meta-s /input return
+
+then you can send the multi-line message with Alt+S
+
+=head1 SETTINGS
+
+the settings are usually found in the
+
+ plugins.var.perl.multiline
+
+namespace, that is, type
+
+ /set plugins.var.perl.multiline.*
+
+to see them and
+
+ /set plugins.var.perl.multiline.SETTINGNAME VALUE
+
+to change a setting C<SETTINGNAME> to a new value C<VALUE>. Finally,
+
+ /unset plugins.var.perl.multiline.SETTINGNAME
+
+will reset a setting to its default value.
+
+the following settings are available:
+
+=head2 char
+
+character(s) which should be displayed to indicate end of line
+
+=head2 tab
+
+character(s) which should be displayed instead of Tab key character
+
+=head2 lead_linebreak
+
+if turned on, multi-line messages always start on a new line
+
+=head2 modify_keys
+
+if turned on, cursor keys are modified so that they respect line
+boundaries instead of treating the whole multi-line message as a
+single line
+
+=head2 magic
+
+indicator displayed when message will be sent soon
+
+=head2 magic_enter_time
+
+delay after pressing enter before sending automatically (in ms), or 0
+to disable
+
+=head2 magic_paste_only
+
+only use multi-line messages for multi-line pastes (multi-line on
+enter is disabled by this)
+
+=head2 paste_lock
+
+time-out to detect pastes (disable the weechat built-in paste
+detection if you want to use this)
+
+=head2 send_empty
+
+set to on to automatically disregard enter key on empty line
+
+=head2 hide_magic_nl
+
+whether the new line inserted by magic enter key will be hidden
+
+=head2 weechat_paste_fix
+
+disable ctrl-j binding when paste is detected to stop silly weechat
+sending out pastes without allowing to edit them
+
+=head2 ipl
+
+this setting controls override of ctrl-m (enter key) by script. Turn
+it off if you don't want multiline.pl to set and re-set the key binding.
+
+=head1 FUNCTION DESCRIPTION
+
+for full pod documentation, filter this script with
+
+ perl -pE'
+ (s/^## (.*?) -- (.*)/=head2 $1\n\n$2\n\n=over\n/ and $o=1) or
+ s/^## (.*?) - (.*)/=item I<$1>\n\n$2\n/ or
+ (s/^## (.*)/=back\n\n$1\n\n=cut\n/ and $o=0,1) or
+ ($o and $o=0,1 and s/^sub /=back\n\n=cut\n\nsub /)'
+
+=cut
+
+use constant SCRIPT_NAME => 'multiline';
+our $VERSION = '0.6.4';
+weechat::register(SCRIPT_NAME,
+ 'Nei <anti.teamidiot.de>', # Author
+ $VERSION,
+ 'GPL3', # License
+ 'Multi-line edit box', # Description
+ 'stop_multiline', '') || return;
+sub SCRIPT_FILE() {
+ my $infolistptr = weechat::infolist_get('perl_script', '', SCRIPT_NAME);
+ my $filename = weechat::infolist_string($infolistptr, 'filename') if weechat::infolist_next($infolistptr);
+ weechat::infolist_free($infolistptr);
+ return $filename unless @_;
+}
+
+{
+package Nlib;
+# this is a weechat perl library
+use strict; use warnings; no warnings 'redefine';
+
+## get_key_name -- get key name valid for any weechat version
+## $key_name - name of key as valid in weechat 4.0.0; note: only supports keys used in this file, not any key
+## $secondary_code - for keys with multiple codes, set to 1 to return the second code instead of the first
+## returns key name unchanged if weechat >= 4.0.0; key name translated to old name if weechat < 4.0.0
+sub get_key_name {
+ my $key_name = shift;
+ my $secondary_code = shift;
+ return $key_name if (weechat::info_get('version_number', '') || 0) >= 0x04000000;
+ return "ctrl-H" if $key_name eq "backspace" && $secondary_code;
+ return "ctrl-?" if $key_name eq "backspace" && !$secondary_code;
+ return "meta2-A" if $key_name eq "up";
+ return "meta2-B" if $key_name eq "down";
+ $key_name =~ s/ctrl-(\w+)/ctrl-\U$1/;
+ return $key_name;
+}
+
+## i2h -- copy weechat infolist content into perl hash
+## $infolist - name of the infolist in weechat
+## $ptr - pointer argument (infolist dependend)
+## @args - arguments to the infolist (list dependend)
+## $fields - string of ref type "fields" if only certain keys are needed (optional)
+## returns perl list with perl hashes for each infolist entry
+sub i2h {
+ my %i2htm = (i => 'integer', s => 'string', p => 'pointer', b => 'buffer', t => 'time');
+ local *weechat::infolist_buffer = sub { '(not implemented)' };
+ my ($infolist, $ptr, @args) = @_;
+ $ptr ||= "";
+ my $fields = ref $args[-1] eq 'fields' ? ${ pop @args } : undef;
+ my $infptr = weechat::infolist_get($infolist, $ptr, do { local $" = ','; "@args" });
+ my @infolist;
+ while (weechat::infolist_next($infptr)) {
+ my @fields = map {
+ my ($t, $v) = split ':', $_, 2;
+ bless \$v, $i2htm{$t};
+ }
+ split ',',
+ ($fields || weechat::infolist_fields($infptr));
+ push @infolist, +{ do {
+ my (%list, %local, @local);
+ map {
+ my $fn = 'weechat::infolist_'.ref $_;
+ my $r = do { no strict 'refs'; &$fn($infptr, $$_) };
+ if ($$_ =~ /^localvar_name_(\d+)$/) {
+ $local[$1] = $r;
+ ()
+ }
+ elsif ($$_ =~ /^(localvar)_value_(\d+)$/) {
+ $local{$local[$2]} = $r;
+ $1 => \%local
+ }
+ elsif ($$_ =~ /(.*?)((?:_\d+)+)$/) {
+ my ($key, $idx) = ($1, $2);
+ my @idx = split '_', $idx; shift @idx;
+ my $target = \$list{$key};
+ for my $x (@idx) {
+ my $o = 1;
+ if ($key eq 'key' or $key eq 'key_command') {
+ $o = 0;
+ }
+ if ($x-$o < 0) {
+ local $" = '|';
+ weechat::print('',"list error: $target/$$_/$key/$x/$idx/@idx(@_)");
+ $o = 0;
+ }
+ $target = \$$target->[$x-$o]
+ }
+ $$target = $r;
+
+ $key => $list{$key}
+ }
+ else {
+ $$_ => $r
+ }
+ } @fields
+ } };
+ }
+ weechat::infolist_free($infptr);
+ !wantarray && @infolist ? \@infolist : @infolist
+}
+
+## hdh -- hdata helper
+## $_[0] - arg pointer or hdata list name
+## $_[1] - hdata name
+## $_[2..$#_] - hdata variable name
+## $_[-1] - hashref with key/value to update (optional)
+## returns value of hdata, and hdata name in list ctx, or number of variables updated
+sub hdh {
+ if (@_ > 1 && $_[0] !~ /^0x/ && $_[0] !~ /^\d+$/) {
+ my $arg = shift;
+ unshift @_, weechat::hdata_get_list(weechat::hdata_get($_[0]), $arg);
+ }
+ while (@_ > 2) {
+ my ($arg, $name, $var) = splice @_, 0, 3;
+ my $hdata = weechat::hdata_get($name);
+ unless (ref $var eq 'HASH') {
+ $var =~ s/!(.*)/weechat::hdata_get_string($hdata, $1)/e;
+ (my $plain_var = $var) =~ s/^\d+\|//;
+ my $type = weechat::hdata_get_var_type_string($hdata, $plain_var);
+ if ($type eq 'pointer') {
+ my $name = weechat::hdata_get_var_hdata($hdata, $var);
+ unshift @_, $name if $name;
+ }
+
+ my $fn = "weechat::hdata_$type";
+ unshift @_, do { no strict 'refs';
+ &$fn($hdata, $arg, $var) };
+ }
+ else {
+ return weechat::hdata_update($hdata, $arg, $var);
+ }
+ }
+ wantarray ? @_ : $_[0]
+}
+
+use Pod::Select qw();
+use Pod::Simple::TextContent;
+
+## get_desc_from_pod -- return setting description from pod documentation
+## $file - filename with pod
+## $setting - name of setting
+## returns description as text
+sub get_desc_from_pod {
+ my $file = shift;
+ return unless -s $file;
+ my $setting = shift;
+
+ open my $pod_sel, '>', \my $ss;
+ Pod::Select::podselect({
+ -output => $pod_sel,
+ -sections => ["SETTINGS/$setting"]}, $file);
+
+ my $pt = new Pod::Simple::TextContent;
+ $pt->output_string(\my $ss_f);
+ $pt->parse_string_document($ss);
+
+ my ($res) = $ss_f =~ /^\s*\Q$setting\E\s+(.*)\s*/;
+ $res
+}
+
+## get_settings_from_pod -- retrieve all settings in settings section of pod
+## $file - file with pod
+## returns list of all settings
+sub get_settings_from_pod {
+ my $file = shift;
+ return unless -s $file;
+
+ open my $pod_sel, '>', \my $ss;
+ Pod::Select::podselect({
+ -output => $pod_sel,
+ -sections => ["SETTINGS//!.+"]}, $file);
+
+ $ss =~ /^=head2\s+(.*)\s*$/mg
+}
+
+## mangle_man_for_wee -- turn man output into weechat codes
+## @_ - list of grotty lines that should be turned into weechat attributes
+## returns modified lines and modifies lines in-place
+sub mangle_man_for_wee {
+ for (@_) {
+ s/_\x08(.)/weechat::color('underline').$1.weechat::color('-underline')/ge;
+ s/(.)\x08\1/weechat::color('bold').$1.weechat::color('-bold')/ge;
+ }
+ wantarray ? @_ : $_[0]
+}
+
+## read_manpage -- read a man page in weechat window
+## $file - file with pod
+## $name - buffer name
+sub read_manpage {
+ my $caller_package = (caller)[0];
+ my $file = shift;
+ my $name = shift;
+
+ if (my $obuf = weechat::buffer_search('perl', "man $name")) {
+ eval qq{
+ package $caller_package;
+ weechat::buffer_close(\$obuf);
+ };
+ }
+
+ my @wee_keys = Nlib::i2h('key');
+ my @keys;
+
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+ my $buf = weechat::buffer_new("man $name", '', '', '', '');
+ return weechat::WEECHAT_RC_OK unless $buf;
+
+ my $width = $wininfo->{chat_width};
+ --$width if $wininfo->{chat_width} < $wininfo->{width} || ($wininfo->{width_pct} < 100 && (grep { $_->{y} == $wininfo->{y} } Nlib::i2h('window'))[-1]{x} > $wininfo->{x});
+ $width -= 2; # when prefix is shown
+
+ weechat::buffer_set($buf, 'time_for_each_line', 0);
+ eval qq{
+ package $caller_package;
+ weechat::buffer_set(\$buf, 'display', 'auto');
+ };
+ die $@ if $@;
+
+ @keys = map { $_->{key} }
+ grep { $_->{command} eq '/input history_previous' ||
+ $_->{command} eq '/input history_global_previous' } @wee_keys;
+ @keys = Nlib::get_key_name('up') unless @keys;
+ weechat::buffer_set($buf, "key_bind_$_", '/window scroll -1') for @keys;
+
+ @keys = map { $_->{key} }
+ grep { $_->{command} eq '/input history_next' ||
+ $_->{command} eq '/input history_global_next' } @wee_keys;
+ @keys = Nlib::get_key_name('down') unless @keys;
+ weechat::buffer_set($buf, "key_bind_$_", '/window scroll +1') for @keys;
+
+ weechat::buffer_set($buf, 'key_bind_ ', '/window page_down');
+
+ @keys = map { $_->{key} }
+ grep { $_->{command} eq '/input delete_previous_char' } @wee_keys;
+ @keys = (Nlib::get_key_name('backspace'), Nlib::get_key_name('backspace', 1)) unless @keys;
+ weechat::buffer_set($buf, "key_bind_$_", '/window page_up') for @keys;
+
+ weechat::buffer_set($buf, 'key_bind_g', '/window scroll_top');
+ weechat::buffer_set($buf, 'key_bind_G', '/window scroll_bottom');
+
+ weechat::buffer_set($buf, 'key_bind_q', '/buffer close');
+
+ weechat::print($buf, " \t".mangle_man_for_wee($_)) # weird bug with \t\t showing nothing?
+ for `pod2man \Q$file\E 2>/dev/null | GROFF_NO_SGR=1 nroff -mandoc -rLL=${width}n -rLT=${width}n -Tutf8 2>/dev/null`;
+ weechat::command($buf, '/window scroll_top');
+
+ unless (hdh($buf, 'buffer', 'lines', 'lines_count') > 0) {
+ weechat::print($buf, weechat::prefix('error').$_)
+ for "Unfortunately, your @{[weechat::color('underline')]}nroff".
+ "@{[weechat::color('-underline')]} command did not produce".
+ " any output.",
+ "Working pod2man and nroff commands are required for the ".
+ "help viewer to work.",
+ "In the meantime, please use the command ", '',
+ "\tperldoc $file", '',
+ "on your shell instead in order to read the manual.",
+ "Thank you and sorry for the inconvenience."
+ }
+}
+
+1
+}
+
+our $MAGIC_ENTER_TIMER;
+our $MAGIC_LOCK;
+our $MAGIC_LOCK_TIMER;
+our $WEECHAT_PASTE_FIX_CTRLJ_CMD;
+our $INPUT_CHANGED_EATER_FLAG;
+our $IGNORE_INPUT_CHANGED;
+our $IGNORE_INPUT_CHANGED2;
+
+use constant INPUT_NL => '/input insert \x0a';
+use constant INPUT_MAGIC => '/input magic_enter';
+our $KEY_RET = Nlib::get_key_name('ctrl-m');
+our $NL = "\x0a";
+
+init_multiline();
+
+my $magic_enter_cancel_dynamic = 1;
+my $paste_undo_start_ignore_dynamic = 0;
+my $input_changed_eater_dynamic = 0;
+my $multiline_complete_fix_dynamic = 1;
+
+sub magic_enter_cancel_dynamic { $magic_enter_cancel_dynamic ? &magic_enter_cancel : weechat::WEECHAT_RC_OK }
+sub paste_undo_start_ignore_dynamic { $paste_undo_start_ignore_dynamic ? &paste_undo_start_ignore : weechat::WEECHAT_RC_OK }
+sub input_changed_eater_dynamic { $input_changed_eater_dynamic ? &input_changed_eater : weechat::WEECHAT_RC_OK }
+sub multiline_complete_fix_dynamic { $multiline_complete_fix_dynamic ? &multiline_complete_fix : weechat::WEECHAT_RC_OK }
+
+weechat::hook_config('plugins.var.perl.'.SCRIPT_NAME.'.*', 'default_options', '');
+weechat::hook_modifier('input_text_display_with_cursor', 'multiline_display', '');
+weechat::hook_command_run('/help '.SCRIPT_NAME, 'help_cmd', '');
+weechat::hook_command_run(INPUT_MAGIC, 'magic_enter', '');
+weechat::hook_signal('input_text_*', 'magic_enter_cancel_dynamic', '');
+weechat::hook_command_run('/input *', 'paste_undo_start_ignore_dynamic', '');
+weechat::hook_signal('2000|input_text_changed', 'input_changed_eater_dynamic', '');
+weechat::hook_signal('key_pressed', 'magic_lock_hatch', '');
+# we need lower than default priority here or the first character is separated
+weechat::hook_signal('500|input_text_changed', 'paste_undo_hack', '')
+ # can only do this on weechat 0.4.0
+ if (weechat::info_get('version_number', '') || 0) >= 0x00040000;
+weechat::hook_command_run("1500|/input complete*", 'multiline_complete_fix_dynamic', 'complete*');
+weechat::hook_command_run("1500|/input delete_*", 'multiline_complete_fix_dynamic', 'delete_*');
+weechat::hook_command_run("1500|/input move_*", 'multiline_complete_fix_dynamic', 'move_*');
+
+sub _stack_depth {
+ my $depth = -1;
+ 1 while caller(++$depth);
+ $depth;
+}
+
+## multiline_display -- show multi-lines on display of input string
+## () - modifier handler
+## $_[2] - buffer pointer
+## $_[3] - input string
+## returns modified input string
+sub multiline_display {
+ Encode::_utf8_on($_[3]);
+ Encode::_utf8_on(my $nl = weechat::config_get_plugin('char') || ' ');
+ Encode::_utf8_on(my $tab = weechat::config_get_plugin('tab'));
+ my $cb = weechat::current_buffer() eq $_[2] && $MAGIC_ENTER_TIMER;
+ if ($cb) {
+ $_[3] =~ s/$NL\x19b#/\x19b#/ if weechat::config_string_to_boolean(weechat::config_get_plugin('hide_magic_nl'));
+ }
+ if ($_[3] =~ s/$NL/$nl\x0d/g) {
+ $_[3] =~ s/\A/ \x0d/ if weechat::config_string_to_boolean(weechat::config_get_plugin('lead_linebreak'));
+ }
+ $_[3] =~ s/\x09/$tab/g if $tab;
+ if ($cb) {
+ Encode::_utf8_on(my $magic = weechat::config_get_plugin('magic'));
+ $_[3] =~ s/\Z/$magic/ if $magic;
+ }
+ $_[3]
+}
+
+## lock_timer_exp -- expire the magic lock timer
+sub lock_timer_exp {
+ if ($MAGIC_LOCK_TIMER) {
+ weechat::unhook($MAGIC_LOCK_TIMER);
+ $MAGIC_LOCK_TIMER = undef;
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## paste_undo_stop_ignore -- unset ignore2 flag
+sub paste_undo_stop_ignore {
+ $IGNORE_INPUT_CHANGED2 = undef;
+ weechat::WEECHAT_RC_OK
+}
+
+## paste_undo_start_ignore -- set ignore2 flag when /input is received so to allow /input undo/redo
+## () - command_run handler
+## $_[2] - command that was called
+sub paste_undo_start_ignore {
+ return weechat::WEECHAT_RC_OK if $IGNORE_INPUT_CHANGED;
+ return weechat::WEECHAT_RC_OK if $_[2] =~ /insert/;
+ $IGNORE_INPUT_CHANGED2 = 1;
+ weechat::WEECHAT_RC_OK
+}
+
+## paste_undo_hack -- fix up undo stack when paste is detected by calling /input undo
+## () - signal handler
+## $_[2] - buffer pointer
+sub paste_undo_hack {
+ return weechat::WEECHAT_RC_OK if $IGNORE_INPUT_CHANGED;
+ return paste_undo_stop_ignore() if $IGNORE_INPUT_CHANGED2;
+ if ($MAGIC_LOCK > 0 && get_lock_enabled()) {
+ signall_ignore_input_changed(1);
+ $paste_undo_start_ignore_dynamic = 1;
+
+ Encode::_utf8_on(my $input = weechat::buffer_get_string($_[2], 'input'));
+ my $pos = weechat::buffer_get_integer($_[2], 'input_pos');
+
+ weechat::command($_[2], '/input undo') for 1..2;
+
+ weechat::buffer_set($_[2], 'input', $input);
+ weechat::buffer_set($_[2], 'input_pos', $pos);
+
+ $paste_undo_start_ignore_dynamic = 0;
+ signall_ignore_input_changed(0);
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## input_changed_eater -- suppress input_text_changed signal on new weechats
+## () - signal handler
+sub input_changed_eater {
+ $INPUT_CHANGED_EATER_FLAG = undef;
+ weechat::WEECHAT_RC_OK_EAT
+}
+
+## signall_ignore_input_changed -- use various methods to "ignore" input_text_changed signal
+## $_[0] - start ignore or stop ignore
+sub signall_ignore_input_changed {
+ if ($_[0]) {
+ weechat::hook_signal_send('input_flow_free', weechat::WEECHAT_HOOK_SIGNAL_INT, 1);
+ $input_changed_eater_dynamic = 1;
+ $IGNORE_INPUT_CHANGED = 1;
+ weechat::buffer_set('', 'completion_freeze', '1');
+ }
+ else {
+ weechat::buffer_set('', 'completion_freeze', '0');
+ $IGNORE_INPUT_CHANGED = undef;
+ $input_changed_eater_dynamic = 0;
+ weechat::hook_signal_send('input_flow_free', weechat::WEECHAT_HOOK_SIGNAL_INT, 0);
+ }
+}
+
+## multiline_complete_fix -- add per line /input handling for completion, movement and deletion
+## () - command_run handler
+## $_[0] - original bound data
+## $_[1] - buffer pointer
+## $_[2] - original command
+sub multiline_complete_fix {
+ $magic_enter_cancel_dynamic = 0;
+ $multiline_complete_fix_dynamic = 0;
+ if ($_[2] =~ s/_message$/_line/ || !weechat::config_string_to_boolean(weechat::config_get_plugin('modify_keys'))) {
+ weechat::command($_[1], $_[2]);
+ }
+ else {
+ signall_ignore_input_changed(1);
+ Encode::_utf8_on(my $input = weechat::buffer_get_string($_[1], 'input'));
+ my $pos = weechat::buffer_get_integer($_[1], 'input_pos');
+ if ($pos && $_[2] =~ /(?:previous|beginning_of)_/ && (substr $input, $pos-1, 1) eq $NL) {
+ substr $input, $pos-1, 1, "\0"
+ }
+ elsif ($pos < length $input && $_[2] =~ /(?:next|end_of)_/ && (substr $input, $pos, 1) eq $NL) {
+ substr $input, $pos, 1, "\0"
+ }
+ my @lines = $pos ? (split /$NL/, (substr $input, 0, $pos), -1) : '';
+ my @after = $pos < length $input ? (split /$NL/, (substr $input, $pos), -1) : '';
+ $lines[-1] =~ s/\0$/$NL/;
+ $after[0] =~ s/^\0/$NL/;
+ my ($p1, $p2) = (pop @lines, shift @after);
+ weechat::buffer_set($_[1], 'input', $p1.$p2);
+ weechat::buffer_set($_[1], 'input_pos', length $p1);
+
+ $magic_enter_cancel_dynamic = 1;
+ $INPUT_CHANGED_EATER_FLAG = 1;
+ weechat::command($_[1], $_[2]);
+ my $changed_later = !$INPUT_CHANGED_EATER_FLAG;
+ magic_enter_cancel() if $changed_later;
+ $magic_enter_cancel_dynamic = 0;
+
+ Encode::_utf8_on(my $p = weechat::buffer_get_string($_[1], 'input'));
+ $pos = weechat::buffer_get_integer($_[1], 'input_pos');
+ weechat::command($_[1], '/input undo') if @lines || @after;
+ weechat::command($_[1], '/input undo');
+ weechat::buffer_set($_[1], 'input', join $NL, @lines, $p, @after);
+ weechat::buffer_set($_[1], 'input_pos', $pos+length join $NL, @lines, '');
+
+ signall_ignore_input_changed(0);
+ weechat::hook_signal_send('input_text_changed', weechat::WEECHAT_HOOK_SIGNAL_POINTER, $_[1]) if $changed_later;
+ }
+ $multiline_complete_fix_dynamic = 1;
+ $magic_enter_cancel_dynamic = 1;
+ weechat::WEECHAT_RC_OK_EAT
+}
+
+## help_cmd -- show multi-line script documentation
+## () - command_run handler
+sub help_cmd {
+ Nlib::read_manpage(SCRIPT_FILE, SCRIPT_NAME);
+ weechat::WEECHAT_RC_OK_EAT
+}
+
+## get_lock_time -- gets timeout for paste detection according to setting
+## returns timeout (at least 1)
+sub get_lock_time {
+ my $lock_time = weechat::config_get_plugin('paste_lock');
+ $lock_time = 1 unless $lock_time =~ /^\d+$/ && $lock_time;
+ $lock_time
+}
+
+## get_lock_enabled -- checks whether the paste detection lock is enabled
+## returns bool
+sub get_lock_enabled {
+ my $lock = weechat::config_get_plugin('paste_lock');
+ $lock = weechat::config_string_to_boolean($lock)
+ unless $lock =~ /^\d+$/;
+ $lock
+}
+
+## magic_lock_hatch -- set a timer for paste detection
+## () - signal handler
+sub magic_lock_hatch {
+ lock_timer_exp();
+ $MAGIC_LOCK_TIMER = weechat::hook_timer(get_lock_time(), 0, 1, 'lock_timer_exp', '');
+ weechat::WEECHAT_RC_OK
+}
+
+## magic_unlock -- reduce the lock added by paste detection
+## () - timer handler
+sub magic_unlock {
+ if ($MAGIC_LOCK_TIMER) {
+ weechat::hook_timer(get_lock_time(), 0, 1, 'magic_unlock', '');
+ }
+ else {
+ --$MAGIC_LOCK;
+ if (!$MAGIC_LOCK && $WEECHAT_PASTE_FIX_CTRLJ_CMD) {
+ do_key_bind(Nlib::get_key_name('ctrl-j'), $WEECHAT_PASTE_FIX_CTRLJ_CMD);
+ $WEECHAT_PASTE_FIX_CTRLJ_CMD = undef;
+ }
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## get_magic_enter_time -- get timeout for auto-sending messages according to config
+## returns timeout
+sub get_magic_enter_time {
+ my $magic_enter = weechat::config_get_plugin('magic_enter_time');
+ $magic_enter = 1000 * weechat::config_string_to_boolean($magic_enter)
+ unless $magic_enter =~ /^\d+$/;
+ $magic_enter
+}
+
+## magic_enter -- receive enter key and do magic things: set up a timer for sending the message, add newline
+## () - command_run handler
+## $_[1] - buffer pointer
+sub magic_enter {
+ Encode::_utf8_on(my $input = weechat::buffer_get_string($_[1], 'input'));
+ if (!length $input && weechat::config_string_to_boolean(weechat::config_get_plugin('send_empty'))) {
+ weechat::command($_[1], '/input return');
+ }
+ else {
+ magic_enter_cancel();
+ weechat::command($_[1], INPUT_NL);
+
+ unless (get_lock_enabled() && $MAGIC_LOCK) {
+ if (weechat::config_string_to_boolean(weechat::config_get_plugin('magic_paste_only')) &&
+ $input !~ /$NL/) {
+ magic_enter_send($_[1]);
+ }
+ elsif (my $magic_enter = get_magic_enter_time()) {
+ $MAGIC_ENTER_TIMER = weechat::hook_timer($magic_enter, 0, 1, 'magic_enter_send', $_[1]);
+ }
+ }
+ }
+ weechat::WEECHAT_RC_OK_EAT
+}
+
+## magic_enter_send -- actually send enter key when triggered by magic_enter, remove preceding newline
+## $_[0] - buffer pointer
+## sending is delayed by 1ms to circumvent crash bug in api
+sub magic_enter_send {
+ magic_enter_cancel();
+ weechat::command($_[0], '/input delete_previous_char');
+ weechat::command($_[0], '/wait 1ms /input return');
+ weechat::WEECHAT_RC_OK
+}
+
+## magic_enter_cancel -- cancel the timer for automatic sending of message, for example when more text was added, increase the paste lock for paste detection when used as signal handler
+## () - signal handler when @_ is set
+sub magic_enter_cancel {
+ if ($MAGIC_ENTER_TIMER) {
+ weechat::unhook($MAGIC_ENTER_TIMER);
+ $MAGIC_ENTER_TIMER = undef;
+ }
+ if ($MAGIC_LOCK_TIMER && @_) {
+ if (!$MAGIC_LOCK && !$WEECHAT_PASTE_FIX_CTRLJ_CMD &&
+ weechat::config_string_to_boolean(weechat::config_get_plugin('weechat_paste_fix'))) {
+ ($WEECHAT_PASTE_FIX_CTRLJ_CMD) = get_key_command(Nlib::get_key_name('ctrl-j'));
+ $WEECHAT_PASTE_FIX_CTRLJ_CMD = '-' unless defined $WEECHAT_PASTE_FIX_CTRLJ_CMD;
+ do_key_bind(Nlib::get_key_name('ctrl-j'), '-');
+ }
+ if ($MAGIC_LOCK < 1) {
+ my $lock_time = get_lock_time();
+ ++$MAGIC_LOCK;
+ weechat::hook_timer(get_lock_time(), 0, 1, 'magic_unlock', '');
+ }
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## need_magic_enter -- check if magic enter keybinding is needed according to config settings
+## returns bool
+sub need_magic_enter {
+ weechat::config_string_to_boolean(weechat::config_get_plugin('send_empty')) || get_magic_enter_time() ||
+ weechat::config_string_to_boolean(weechat::config_get_plugin('magic_paste_only'))
+}
+
+## do_key_bind -- mute execute a key binding, or unbind if $_[-1] is '-'
+## @_ - arguments to /key bind
+sub do_key_bind {
+ if ($_[-1] eq '-') {
+ pop;
+ weechat::command('', "/mute /key unbind @_");
+ }
+ elsif ($_[-1] eq '!') {
+ pop;
+ weechat::command('', "/mute /key reset @_");
+ }
+ else {
+ weechat::command('', "/mute /key bind @_");
+ }
+}
+
+{ my %keys;
+## get_key_command -- get the command bound to a key
+## $_[0] - key in weechat syntax
+## returns the command
+ sub get_key_command {
+ unless (exists $keys{$_[0]}) {
+ ($keys{$_[0]}) =
+ map { $_->{command} } grep { $_->{key} eq $_[0] }
+ Nlib::i2h('key')
+ }
+ $keys{$_[0]}
+ }
+}
+
+## default_options -- set up default option values on start and when unset
+## () - config handler if @_ is set
+sub default_options {
+ my %defaults = (
+ char => '↩',
+ tab => '──▶▏',
+ magic => '‼',
+ ipl => 'on',
+ lead_linebreak => 'on',
+ modify_keys => 'on',
+ send_empty => 'on',
+ magic_enter_time => '1000',
+ paste_lock => '1',
+ magic_paste_only => 'off',
+ hide_magic_nl => 'on',
+ weechat_paste_fix => 'on',
+ );
+ unless (weechat::config_is_set_plugin('ipl')) {
+ if (my $bar = weechat::bar_search('input')) {
+ weechat::bar_set($bar, $_, '0') for 'size', 'size_max';
+ }
+ }
+ for (keys %defaults) {
+ weechat::config_set_plugin($_, $defaults{$_})
+ unless weechat::config_is_set_plugin($_);
+ }
+ do_key_bind($KEY_RET, INPUT_NL)
+ if weechat::config_string_to_boolean(weechat::config_get_plugin('ipl'));
+ my ($enter_key) = get_key_command($KEY_RET);
+ if (need_magic_enter()) {
+ do_key_bind($KEY_RET, INPUT_MAGIC)
+ if defined $enter_key && $enter_key eq INPUT_NL;
+ }
+ else {
+ do_key_bind($KEY_RET, INPUT_NL)
+ if defined $enter_key && $enter_key eq INPUT_MAGIC;
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+sub init_multiline {
+ $MAGIC_LOCK = -1;
+ default_options();
+ my $sf = SCRIPT_FILE;
+ for (Nlib::get_settings_from_pod($sf)) {
+ weechat::config_set_desc_plugin($_, Nlib::get_desc_from_pod($sf, $_));
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+sub stop_multiline {
+ magic_enter_cancel();
+ if (need_magic_enter()) {
+ my ($enter_key) = get_key_command($KEY_RET);
+ do_key_bind($KEY_RET, INPUT_NL)
+ if defined $enter_key && $enter_key eq INPUT_MAGIC;
+ }
+ if ($WEECHAT_PASTE_FIX_CTRLJ_CMD) {
+ do_key_bind(Nlib::get_key_name('ctrl-j'), $WEECHAT_PASTE_FIX_CTRLJ_CMD);
+ $WEECHAT_PASTE_FIX_CTRLJ_CMD = undef;
+ }
+ if (weechat::config_string_to_boolean(weechat::config_get_plugin('ipl'))) {
+ do_key_bind($KEY_RET, '!');
+ }
+ weechat::WEECHAT_RC_OK
+}
diff --git a/scripts/python/autosort.py b/scripts/python/autosort.py
@@ -0,0 +1,1075 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013-2017 Maarten de Vries <maarten@de-vri.es>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+#
+# Autosort automatically keeps your buffers sorted and grouped by server.
+# You can define your own sorting rules. See /help autosort for more details.
+#
+# https://github.com/de-vri-es/weechat-autosort
+#
+
+#
+# Changelog:
+# 3.9:
+# * Remove `buffers.pl` from recommended settings.
+# 3,8:
+# * Fix relative sorting on script name in default rules.
+# * Document a useful property of stable sort algorithms.
+# 3.7:
+# * Make default rules work with bitlbee, matrix and slack.
+# 3.6:
+# * Add more documentation on provided info hooks.
+# 3.5:
+# * Add ${info:autosort_escape,...} to escape arguments for other info hooks.
+# 3.4:
+# * Fix rate-limit of sorting to prevent high CPU load and lock-ups.
+# * Fix bug in parsing empty arguments for info hooks.
+# * Add debug_log option to aid with debugging.
+# * Correct a few typos.
+# 3.3:
+# * Fix the /autosort debug command for unicode.
+# * Update the default rules to work better with Slack.
+# 3.2:
+# * Fix python3 compatiblity.
+# 3.1:
+# * Use colors to format the help text.
+# 3.0:
+# * Switch to evaluated expressions for sorting.
+# * Add `/autosort debug` command.
+# * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules.
+# * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules.
+# * Make tab completion context aware.
+# 2.8:
+# * Fix compatibility with python 3 regarding unicode handling.
+# 2.7:
+# * Fix sorting of buffers with spaces in their name.
+# 2.6:
+# * Ignore case in rules when doing case insensitive sorting.
+# 2.5:
+# * Fix handling unicode buffer names.
+# * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on.
+# 2.4:
+# * Make script python3 compatible.
+# 2.3:
+# * Fix sorting items without score last (regressed in 2.2).
+# 2.2:
+# * Add configuration option for signals that trigger a sort.
+# * Add command to manually trigger a sort (/autosort sort).
+# * Add replacement patterns to apply before sorting.
+# 2.1:
+# * Fix some minor style issues.
+# 2.0:
+# * Allow for custom sort rules.
+#
+
+
+import json
+import math
+import re
+import sys
+import time
+import weechat
+
+SCRIPT_NAME = 'autosort'
+SCRIPT_AUTHOR = 'Maarten de Vries <maarten@de-vri.es>'
+SCRIPT_VERSION = '3.9'
+SCRIPT_LICENSE = 'GPL3'
+SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.'
+
+
+config = None
+hooks = []
+signal_delay_timer = None
+sort_limit_timer = None
+sort_queued = False
+
+
+# Make sure that unicode, bytes and str are always available in python2 and 3.
+# For python 2, str == bytes
+# For python 3, str == unicode
+if sys.version_info[0] >= 3:
+ unicode = str
+
+def ensure_str(input):
+ '''
+ Make sure the given type if the correct string type for the current python version.
+ That means bytes for python2 and unicode for python3.
+ '''
+ if not isinstance(input, str):
+ if isinstance(input, bytes):
+ return input.encode('utf-8')
+ if isinstance(input, unicode):
+ return input.decode('utf-8')
+ return input
+
+
+if hasattr(time, 'perf_counter'):
+ perf_counter = time.perf_counter
+else:
+ perf_counter = time.clock
+
+def casefold(string):
+ if hasattr(string, 'casefold'): return string.casefold()
+ # Fall back to lowercasing for python2.
+ return string.lower()
+
+def list_swap(values, a, b):
+ values[a], values[b] = values[b], values[a]
+
+def list_move(values, old_index, new_index):
+ values.insert(new_index, values.pop(old_index))
+
+def list_find(collection, value):
+ for i, elem in enumerate(collection):
+ if elem == value: return i
+ return None
+
+class HumanReadableError(Exception):
+ pass
+
+def parse_int(arg, arg_name = 'argument'):
+ ''' Parse an integer and provide a more human readable error. '''
+ arg = arg.strip()
+ try:
+ return int(arg)
+ except ValueError:
+ raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg))
+
+def decode_rules(blob):
+ parsed = json.loads(blob)
+ if not isinstance(parsed, list):
+ log('Malformed rules, expected a JSON encoded list of strings, but got a {0}. No rules have been loaded. Please fix the setting manually.'.format(type(parsed)))
+ return []
+
+ for i, entry in enumerate(parsed):
+ if not isinstance(entry, (str, unicode)):
+ log('Rule #{0} is not a string but a {1}. No rules have been loaded. Please fix the setting manually.'.format(i, type(entry)))
+ return []
+
+ return parsed
+
+def decode_helpers(blob):
+ parsed = json.loads(blob)
+ if not isinstance(parsed, dict):
+ log('Malformed helpers, expected a JSON encoded dictionary but got a {0}. No helpers have been loaded. Please fix the setting manually.'.format(type(parsed)))
+ return {}
+
+ for key, value in parsed.items():
+ if not isinstance(value, (str, unicode)):
+ log('Helper "{0}" is not a string but a {1}. No helpers have been loaded. Please fix setting manually.'.format(key, type(value)))
+ return {}
+ return parsed
+
+class Config:
+ ''' The autosort configuration. '''
+
+ default_rules = json.dumps([
+ '${core_first}',
+ '${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}',
+ '${script_or_plugin}',
+ '${irc_raw_first}',
+ '${server}',
+ '${info:autosort_order,${type},server,*,channel,private}',
+ '${hashless_name}',
+ '${buffer.full_name}',
+ ])
+
+ default_helpers = json.dumps({
+ 'core_first': '${if:${buffer.full_name}!=core.weechat}',
+ 'irc_raw_first': '${if:${buffer.full_name}!=irc.irc_raw}',
+ 'irc_raw_last': '${if:${buffer.full_name}==irc.irc_raw}',
+ 'hashless_name': '${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}',
+ 'script_or_plugin': '${if:${script_name}?${script_name}:${plugin}}',
+ })
+
+ default_signal_delay = 5
+ default_sort_limit = 100
+
+ default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed'
+
+ def __init__(self, filename):
+ ''' Initialize the configuration. '''
+
+ self.filename = filename
+ self.config_file = weechat.config_new(self.filename, '', '')
+ self.sorting_section = None
+ self.v3_section = None
+
+ self.case_sensitive = False
+ self.rules = []
+ self.helpers = {}
+ self.signals = []
+ self.signal_delay = Config.default_signal_delay,
+ self.sort_limit = Config.default_sort_limit,
+ self.sort_on_config = True
+ self.debug_log = False
+
+ self.__case_sensitive = None
+ self.__rules = None
+ self.__helpers = None
+ self.__signals = None
+ self.__signal_delay = None
+ self.__sort_limit = None
+ self.__sort_on_config = None
+ self.__debug_log = None
+
+ if not self.config_file:
+ log('Failed to initialize configuration file "{0}".'.format(self.filename))
+ return
+
+ self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '')
+ self.v3_section = weechat.config_new_section(self.config_file, 'v3', False, False, '', '', '', '', '', '', '', '', '', '')
+
+ if not self.sorting_section:
+ log('Failed to initialize section "sorting" of configuration file.')
+ weechat.config_free(self.config_file)
+ return
+
+ self.__case_sensitive = weechat.config_new_option(
+ self.config_file, self.sorting_section,
+ 'case_sensitive', 'boolean',
+ 'If this option is on, sorting is case sensitive.',
+ '', 0, 0, 'off', 'off', 0,
+ '', '', '', '', '', ''
+ )
+
+ weechat.config_new_option(
+ self.config_file, self.sorting_section,
+ 'rules', 'string',
+ 'Sort rules used by autosort v2.x and below. Not used by autosort anymore.',
+ '', 0, 0, '', '', 0,
+ '', '', '', '', '', ''
+ )
+
+ weechat.config_new_option(
+ self.config_file, self.sorting_section,
+ 'replacements', 'string',
+ 'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.',
+ '', 0, 0, '', '', 0,
+ '', '', '', '', '', ''
+ )
+
+ self.__rules = weechat.config_new_option(
+ self.config_file, self.v3_section,
+ 'rules', 'string',
+ 'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.',
+ '', 0, 0, Config.default_rules, Config.default_rules, 0,
+ '', '', '', '', '', ''
+ )
+
+ self.__helpers = weechat.config_new_option(
+ self.config_file, self.v3_section,
+ 'helpers', 'string',
+ 'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.',
+ '', 0, 0, Config.default_helpers, Config.default_helpers, 0,
+ '', '', '', '', '', ''
+ )
+
+ self.__signals = weechat.config_new_option(
+ self.config_file, self.sorting_section,
+ 'signals', 'string',
+ 'A space separated list of signals that will cause autosort to resort your buffer list.',
+ '', 0, 0, Config.default_signals, Config.default_signals, 0,
+ '', '', '', '', '', ''
+ )
+
+ self.__signal_delay = weechat.config_new_option(
+ self.config_file, self.sorting_section,
+ 'signal_delay', 'integer',
+ 'Delay in milliseconds to wait after a signal before sorting the buffer list. This prevents triggering many times if multiple signals arrive in a short time. It can also be needed to wait for buffer localvars to be available.',
+ '', 0, 1000, str(Config.default_signal_delay), str(Config.default_signal_delay), 0,
+ '', '', '', '', '', ''
+ )
+
+ self.__sort_limit = weechat.config_new_option(
+ self.config_file, self.sorting_section,
+ 'sort_limit', 'integer',
+ 'Minimum delay in milliseconds to wait after sorting before signals can trigger a sort again. This is effectively a rate limit on sorting. Keeping signal_delay low while setting this higher can reduce excessive sorting without a long initial delay.',
+ '', 0, 1000, str(Config.default_sort_limit), str(Config.default_sort_limit), 0,
+ '', '', '', '', '', ''
+ )
+
+ self.__sort_on_config = weechat.config_new_option(
+ self.config_file, self.sorting_section,
+ 'sort_on_config_change', 'boolean',
+ 'Decides if the buffer list should be sorted when autosort configuration changes.',
+ '', 0, 0, 'on', 'on', 0,
+ '', '', '', '', '', ''
+ )
+
+ self.__debug_log = weechat.config_new_option(
+ self.config_file, self.sorting_section,
+ 'debug_log', 'boolean',
+ 'If enabled, print more debug messages. Not recommended for normal usage.',
+ '', 0, 0, 'off', 'off', 0,
+ '', '', '', '', '', ''
+ )
+
+ if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK:
+ log('Failed to load configuration file.')
+
+ if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK:
+ log('Failed to write configuration file.')
+
+ self.reload()
+
+ def reload(self):
+ ''' Load configuration variables. '''
+
+ self.case_sensitive = weechat.config_boolean(self.__case_sensitive)
+
+ rules_blob = weechat.config_string(self.__rules)
+ helpers_blob = weechat.config_string(self.__helpers)
+ signals_blob = weechat.config_string(self.__signals)
+
+ self.rules = decode_rules(rules_blob)
+ self.helpers = decode_helpers(helpers_blob)
+ self.signals = signals_blob.split()
+ self.signal_delay = weechat.config_integer(self.__signal_delay)
+ self.sort_limit = weechat.config_integer(self.__sort_limit)
+ self.sort_on_config = weechat.config_boolean(self.__sort_on_config)
+ self.debug_log = weechat.config_boolean(self.__debug_log)
+
+ def save_rules(self, run_callback = True):
+ ''' Save the current rules to the configuration. '''
+ weechat.config_option_set(self.__rules, json.dumps(self.rules), run_callback)
+
+ def save_helpers(self, run_callback = True):
+ ''' Save the current helpers to the configuration. '''
+ weechat.config_option_set(self.__helpers, json.dumps(self.helpers), run_callback)
+
+
+def pad(sequence, length, padding = None):
+ ''' Pad a list until is has a certain length. '''
+ return sequence + [padding] * max(0, (length - len(sequence)))
+
+def log(message, buffer = 'NULL'):
+ weechat.prnt(buffer, 'autosort: {0}'.format(message))
+
+def debug(message, buffer = 'NULL'):
+ if config.debug_log:
+ weechat.prnt(buffer, 'autosort: debug: {0}'.format(message))
+
+def get_buffers():
+ ''' Get a list of all the buffers in weechat. '''
+ hdata = weechat.hdata_get('buffer')
+ buffer = weechat.hdata_get_list(hdata, "gui_buffers");
+
+ result = []
+ while buffer:
+ number = weechat.hdata_integer(hdata, buffer, 'number')
+ result.append((number, buffer))
+ buffer = weechat.hdata_pointer(hdata, buffer, 'next_buffer')
+ return hdata, result
+
+class MergedBuffers(list):
+ """ A list of merged buffers, possibly of size 1. """
+ def __init__(self, number):
+ super(MergedBuffers, self).__init__()
+ self.number = number
+
+def merge_buffer_list(buffers):
+ '''
+ Group merged buffers together.
+ The output is a list of MergedBuffers.
+ '''
+ if not buffers: return []
+ result = {}
+ for number, buffer in buffers:
+ if number not in result: result[number] = MergedBuffers(number)
+ result[number].append(buffer)
+ return result.values()
+
+def sort_buffers(hdata, buffers, rules, helpers, case_sensitive):
+ for merged in buffers:
+ for buffer in merged:
+ name = weechat.hdata_string(hdata, buffer, 'name')
+
+ return sorted(buffers, key=merged_sort_key(rules, helpers, case_sensitive))
+
+def buffer_sort_key(rules, helpers, case_sensitive):
+ ''' Create a sort key function for a list of lists of merged buffers. '''
+ def key(buffer):
+ extra_vars = {}
+ for helper_name, helper in sorted(helpers.items()):
+ expanded = weechat.string_eval_expression(helper, {"buffer": buffer}, {}, {})
+ extra_vars[helper_name] = expanded if case_sensitive else casefold(expanded)
+ result = []
+ for rule in rules:
+ expanded = weechat.string_eval_expression(rule, {"buffer": buffer}, extra_vars, {})
+ result.append(expanded if case_sensitive else casefold(expanded))
+ return result
+
+ return key
+
+def merged_sort_key(rules, helpers, case_sensitive):
+ buffer_key = buffer_sort_key(rules, helpers, case_sensitive)
+ def key(merged):
+ best = None
+ for buffer in merged:
+ this = buffer_key(buffer)
+ if best is None or this < best: best = this
+ return best
+ return key
+
+def apply_buffer_order(buffers):
+ ''' Sort the buffers in weechat according to the given order. '''
+ for i, buffer in enumerate(buffers):
+ weechat.buffer_set(buffer[0], "number", str(i + 1))
+
+def split_args(args, expected, optional = 0):
+ ''' Split an argument string in the desired number of arguments. '''
+ split = args.split(' ', expected - 1)
+ if (len(split) < expected):
+ raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split)))
+ return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '')
+
+def do_sort(verbose = False):
+ start = perf_counter()
+
+ hdata, buffers = get_buffers()
+ buffers = merge_buffer_list(buffers)
+ buffers = sort_buffers(hdata, buffers, config.rules, config.helpers, config.case_sensitive)
+ apply_buffer_order(buffers)
+
+ elapsed = perf_counter() - start
+ if verbose:
+ log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed))
+ else:
+ debug("Finished sorting buffers in {0:.4f} seconds.".format(elapsed))
+
+def command_sort(buffer, command, args):
+ ''' Sort the buffers and print a confirmation. '''
+ do_sort(True)
+ return weechat.WEECHAT_RC_OK
+
+def command_debug(buffer, command, args):
+ hdata, buffers = get_buffers()
+ buffers = merge_buffer_list(buffers)
+
+ # Show evaluation results.
+ log('Individual evaluation results:')
+ start = perf_counter()
+ key = buffer_sort_key(config.rules, config.helpers, config.case_sensitive)
+ results = []
+ for merged in buffers:
+ for buffer in merged:
+ fullname = weechat.hdata_string(hdata, buffer, 'full_name')
+ results.append((fullname, key(buffer)))
+ elapsed = perf_counter() - start
+
+ for fullname, result in results:
+ fullname = ensure_str(fullname)
+ result = [ensure_str(x) for x in result]
+ log('{0}: {1}'.format(fullname, result))
+ log('Computing evaluation results took {0:.4f} seconds.'.format(elapsed))
+
+ return weechat.WEECHAT_RC_OK
+
+def command_rule_list(buffer, command, args):
+ ''' Show the list of sorting rules. '''
+ output = 'Sorting rules:\n'
+ for i, rule in enumerate(config.rules):
+ output += ' {0}: {1}\n'.format(i, rule)
+ if not len(config.rules):
+ output += ' No sorting rules configured.\n'
+ log(output )
+
+ return weechat.WEECHAT_RC_OK
+
+
+def command_rule_add(buffer, command, args):
+ ''' Add a rule to the rule list. '''
+ config.rules.append(args)
+ config.save_rules()
+ command_rule_list(buffer, command, '')
+
+ return weechat.WEECHAT_RC_OK
+
+
+def command_rule_insert(buffer, command, args):
+ ''' Insert a rule at the desired position in the rule list. '''
+ index, rule = split_args(args, 2)
+ index = parse_int(index, 'index')
+
+ config.rules.insert(index, rule)
+ config.save_rules()
+ command_rule_list(buffer, command, '')
+ return weechat.WEECHAT_RC_OK
+
+
+def command_rule_update(buffer, command, args):
+ ''' Update a rule in the rule list. '''
+ index, rule = split_args(args, 2)
+ index = parse_int(index, 'index')
+
+ config.rules[index] = rule
+ config.save_rules()
+ command_rule_list(buffer, command, '')
+ return weechat.WEECHAT_RC_OK
+
+
+def command_rule_delete(buffer, command, args):
+ ''' Delete a rule from the rule list. '''
+ index = args.strip()
+ index = parse_int(index, 'index')
+
+ config.rules.pop(index)
+ config.save_rules()
+ command_rule_list(buffer, command, '')
+ return weechat.WEECHAT_RC_OK
+
+
+def command_rule_move(buffer, command, args):
+ ''' Move a rule to a new position. '''
+ index_a, index_b = split_args(args, 2)
+ index_a = parse_int(index_a, 'index')
+ index_b = parse_int(index_b, 'index')
+
+ list_move(config.rules, index_a, index_b)
+ config.save_rules()
+ command_rule_list(buffer, command, '')
+ return weechat.WEECHAT_RC_OK
+
+
+def command_rule_swap(buffer, command, args):
+ ''' Swap two rules. '''
+ index_a, index_b = split_args(args, 2)
+ index_a = parse_int(index_a, 'index')
+ index_b = parse_int(index_b, 'index')
+
+ list_swap(config.rules, index_a, index_b)
+ config.save_rules()
+ command_rule_list(buffer, command, '')
+ return weechat.WEECHAT_RC_OK
+
+
+def command_helper_list(buffer, command, args):
+ ''' Show the list of helpers. '''
+ output = 'Helper variables:\n'
+
+ width = max(map(lambda x: len(x) if len(x) <= 30 else 0, config.helpers.keys()))
+
+ for name, expression in sorted(config.helpers.items()):
+ output += ' {0:>{width}}: {1}\n'.format(name, expression, width=width)
+ if not len(config.helpers):
+ output += ' No helper variables configured.'
+ log(output)
+
+ return weechat.WEECHAT_RC_OK
+
+
+def command_helper_set(buffer, command, args):
+ ''' Add/update a helper to the helper list. '''
+ name, expression = split_args(args, 2)
+
+ config.helpers[name] = expression
+ config.save_helpers()
+ command_helper_list(buffer, command, '')
+
+ return weechat.WEECHAT_RC_OK
+
+def command_helper_delete(buffer, command, args):
+ ''' Delete a helper from the helper list. '''
+ name = args.strip()
+
+ del config.helpers[name]
+ config.save_helpers()
+ command_helper_list(buffer, command, '')
+ return weechat.WEECHAT_RC_OK
+
+
+def command_helper_rename(buffer, command, args):
+ ''' Rename a helper to a new position. '''
+ old_name, new_name = split_args(args, 2)
+
+ try:
+ config.helpers[new_name] = config.helpers[old_name]
+ del config.helpers[old_name]
+ except KeyError:
+ raise HumanReadableError('No such helper: {0}'.format(old_name))
+ config.save_helpers()
+ command_helper_list(buffer, command, '')
+ return weechat.WEECHAT_RC_OK
+
+
+def command_helper_swap(buffer, command, args):
+ ''' Swap two helpers. '''
+ a, b = split_args(args, 2)
+ try:
+ config.helpers[b], config.helpers[a] = config.helpers[a], config.helpers[b]
+ except KeyError as e:
+ raise HumanReadableError('No such helper: {0}'.format(e.args[0]))
+
+ config.helpers.swap(index_a, index_b)
+ config.save_helpers()
+ command_helper_list(buffer, command, '')
+ return weechat.WEECHAT_RC_OK
+
+def call_command(buffer, command, args, subcommands):
+ ''' Call a subcommand from a dictionary. '''
+ subcommand, tail = pad(args.split(' ', 1), 2, '')
+ subcommand = subcommand.strip()
+ if (subcommand == ''):
+ child = subcommands.get(' ')
+ else:
+ command = command + [subcommand]
+ child = subcommands.get(subcommand)
+
+ if isinstance(child, dict):
+ return call_command(buffer, command, tail, child)
+ elif callable(child):
+ return child(buffer, command, tail)
+
+ log('{0}: command not found'.format(' '.join(command)))
+ return weechat.WEECHAT_RC_ERROR
+
+def on_signal(data, signal, signal_data):
+ global signal_delay_timer
+ global sort_queued
+
+ # If the sort limit timeout is started, we're in the hold-off time after sorting, just queue a sort.
+ if sort_limit_timer is not None:
+ if sort_queued:
+ debug('Signal {0} ignored, sort limit timeout is active and sort is already queued.'.format(signal))
+ else:
+ debug('Signal {0} received but sort limit timeout is active, sort is now queued.'.format(signal))
+ sort_queued = True
+ return weechat.WEECHAT_RC_OK
+
+ # If the signal delay timeout is started, a signal was recently received, so ignore this signal.
+ if signal_delay_timer is not None:
+ debug('Signal {0} ignored, signal delay timeout active.'.format(signal))
+ return weechat.WEECHAT_RC_OK
+
+ # Otherwise, start the signal delay timeout.
+ debug('Signal {0} received, starting signal delay timeout of {1} ms.'.format(signal, config.signal_delay))
+ weechat.hook_timer(config.signal_delay, 0, 1, "on_signal_delay_timeout", "")
+ return weechat.WEECHAT_RC_OK
+
+def on_signal_delay_timeout(pointer, remaining_calls):
+ """ Called when the signal_delay_timer triggers. """
+ global signal_delay_timer
+ global sort_limit_timer
+ global sort_queued
+
+ signal_delay_timer = None
+
+ # If the sort limit timeout was started, we're still in the no-sort period, so just queue a sort.
+ if sort_limit_timer is not None:
+ debug('Signal delay timeout expired, but sort limit timeout is active, sort is now queued.')
+ sort_queued = True
+ return weechat.WEECHAT_RC_OK
+
+ # Time to sort!
+ debug('Signal delay timeout expired, starting sort.')
+ do_sort()
+
+ # Start the sort limit timeout if not disabled.
+ if config.sort_limit > 0:
+ debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit))
+ sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "")
+
+ return weechat.WEECHAT_RC_OK
+
+def on_sort_limit_timeout(pointer, remainin_calls):
+ """ Called when de sort_limit_timer triggers. """
+ global sort_limit_timer
+ global sort_queued
+
+ # If no signal was received during the timeout, we're done.
+ if not sort_queued:
+ debug('Sort limit timeout expired without receiving a signal.')
+ sort_limit_timer = None
+ return weechat.WEECHAT_RC_OK
+
+ # Otherwise it's time to sort.
+ debug('Signal received during sort limit timeout, starting queued sort.')
+ do_sort()
+ sort_queued = False
+
+ # Start the sort limit timeout again if not disabled.
+ if config.sort_limit > 0:
+ debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit))
+ sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "")
+
+ return weechat.WEECHAT_RC_OK
+
+
+def apply_config():
+ # Unhook all signals and hook the new ones.
+ for hook in hooks:
+ weechat.unhook(hook)
+ for signal in config.signals:
+ hooks.append(weechat.hook_signal(signal, 'on_signal', ''))
+
+ if config.sort_on_config:
+ debug('Sorting because configuration changed.')
+ do_sort()
+
+def on_config_changed(*args, **kwargs):
+ ''' Called whenever the configuration changes. '''
+ config.reload()
+ apply_config()
+
+ return weechat.WEECHAT_RC_OK
+
+def parse_arg(args):
+ if not args: return '', None
+
+ result = ''
+ escaped = False
+ for i, c in enumerate(args):
+ if not escaped:
+ if c == '\\':
+ escaped = True
+ continue
+ elif c == ',':
+ return result, args[i+1:]
+ result += c
+ escaped = False
+ return result, None
+
+def parse_args(args, max = None):
+ result = []
+ i = 0
+ while max is None or i < max:
+ i += 1
+ arg, args = parse_arg(args)
+ if arg is None: break
+ result.append(arg)
+ if args is None: break
+ return result, args
+
+def on_info_escape(pointer, name, arguments):
+ result = ''
+ for c in arguments:
+ if c == '\\':
+ result += '\\\\'
+ elif c == ',':
+ result += '\\,'
+ else:
+ result +=c
+ return result
+
+def on_info_replace(pointer, name, arguments):
+ arguments, rest = parse_args(arguments, 3)
+ if rest or len(arguments) < 3:
+ log('usage: ${{info:{0},old,new,text}}'.format(name))
+ return ''
+ old, new, text = arguments
+
+ return text.replace(old, new)
+
+def on_info_order(pointer, name, arguments):
+ arguments, rest = parse_args(arguments)
+ if len(arguments) < 1:
+ log('usage: ${{info:{0},value,first,second,third,...}}'.format(name))
+ return ''
+
+ value = arguments[0]
+ keys = arguments[1:]
+ if not keys: return '0'
+
+ # Find the value in the keys (or '*' if we can't find it)
+ result = list_find(keys, value)
+ if result is None: result = list_find(keys, '*')
+ if result is None: result = len(keys)
+
+ # Pad result with leading zero to make sure string sorting works.
+ width = int(math.log10(len(keys))) + 1
+ return '{0:0{1}}'.format(result, width)
+
+
+def on_autosort_command(data, buffer, args):
+ ''' Called when the autosort command is invoked. '''
+ try:
+ return call_command(buffer, ['/autosort'], args, {
+ ' ': command_sort,
+ 'sort': command_sort,
+ 'debug': command_debug,
+
+ 'rules': {
+ ' ': command_rule_list,
+ 'list': command_rule_list,
+ 'add': command_rule_add,
+ 'insert': command_rule_insert,
+ 'update': command_rule_update,
+ 'delete': command_rule_delete,
+ 'move': command_rule_move,
+ 'swap': command_rule_swap,
+ },
+ 'helpers': {
+ ' ': command_helper_list,
+ 'list': command_helper_list,
+ 'set': command_helper_set,
+ 'delete': command_helper_delete,
+ 'rename': command_helper_rename,
+ 'swap': command_helper_swap,
+ },
+ })
+ except HumanReadableError as e:
+ log(e)
+ return weechat.WEECHAT_RC_ERROR
+
+def add_completions(completion, words):
+ for word in words:
+ weechat.hook_completion_list_add(completion, word, 0, weechat.WEECHAT_LIST_POS_END)
+
+def autosort_complete_rules(words, completion):
+ if len(words) == 0:
+ add_completions(completion, ['add', 'delete', 'insert', 'list', 'move', 'swap', 'update'])
+ if len(words) == 1 and words[0] in ('delete', 'insert', 'move', 'swap', 'update'):
+ add_completions(completion, map(str, range(len(config.rules))))
+ if len(words) == 2 and words[0] in ('move', 'swap'):
+ add_completions(completion, map(str, range(len(config.rules))))
+ if len(words) == 2 and words[0] in ('update'):
+ try:
+ add_completions(completion, [config.rules[int(words[1])]])
+ except KeyError: pass
+ except ValueError: pass
+ else:
+ add_completions(completion, [''])
+ return weechat.WEECHAT_RC_OK
+
+def autosort_complete_helpers(words, completion):
+ if len(words) == 0:
+ add_completions(completion, ['delete', 'list', 'rename', 'set', 'swap'])
+ elif len(words) == 1 and words[0] in ('delete', 'rename', 'set', 'swap'):
+ add_completions(completion, sorted(config.helpers.keys()))
+ elif len(words) == 2 and words[0] == 'swap':
+ add_completions(completion, sorted(config.helpers.keys()))
+ elif len(words) == 2 and words[0] == 'rename':
+ add_completions(completion, sorted(config.helpers.keys()))
+ elif len(words) == 2 and words[0] == 'set':
+ try:
+ add_completions(completion, [config.helpers[words[1]]])
+ except KeyError: pass
+ return weechat.WEECHAT_RC_OK
+
+def on_autosort_complete(data, name, buffer, completion):
+ cmdline = weechat.buffer_get_string(buffer, "input")
+ cursor = weechat.buffer_get_integer(buffer, "input_pos")
+ prefix = cmdline[:cursor]
+ words = prefix.split()[1:]
+
+ # If the current word isn't finished yet,
+ # ignore it for coming up with completion suggestions.
+ if prefix[-1] != ' ': words = words[:-1]
+
+ if len(words) == 0:
+ add_completions(completion, ['debug', 'helpers', 'rules', 'sort'])
+ elif words[0] == 'rules':
+ return autosort_complete_rules(words[1:], completion)
+ elif words[0] == 'helpers':
+ return autosort_complete_helpers(words[1:], completion)
+ return weechat.WEECHAT_RC_OK
+
+command_description = r'''{*white}# General commands{reset}
+
+{*white}/autosort {brown}sort{reset}
+Manually trigger the buffer sorting.
+
+{*white}/autosort {brown}debug{reset}
+Show the evaluation results of the sort rules for each buffer.
+
+
+{*white}# Sorting rule commands{reset}
+
+{*white}/autosort{brown} rules list{reset}
+Print the list of sort rules.
+
+{*white}/autosort {brown}rules add {cyan}<expression>{reset}
+Add a new rule at the end of the list.
+
+{*white}/autosort {brown}rules insert {cyan}<index> <expression>{reset}
+Insert a new rule at the given index in the list.
+
+{*white}/autosort {brown}rules update {cyan}<index> <expression>{reset}
+Update a rule in the list with a new expression.
+
+{*white}/autosort {brown}rules delete {cyan}<index>
+Delete a rule from the list.
+
+{*white}/autosort {brown}rules move {cyan}<index_from> <index_to>{reset}
+Move a rule from one position in the list to another.
+
+{*white}/autosort {brown}rules swap {cyan}<index_a> <index_b>{reset}
+Swap two rules in the list
+
+
+{*white}# Helper variable commands{reset}
+
+{*white}/autosort {brown}helpers list
+Print the list of helper variables.
+
+{*white}/autosort {brown}helpers set {cyan}<name> <expression>
+Add or update a helper variable with the given name.
+
+{*white}/autosort {brown}helpers delete {cyan}<name>
+Delete a helper variable.
+
+{*white}/autosort {brown}helpers rename {cyan}<old_name> <new_name>
+Rename a helper variable.
+
+{*white}/autosort {brown}helpers swap {cyan}<name_a> <name_b>
+Swap the expressions of two helper variables in the list.
+
+
+{*white}# Info hooks{reset}
+Autosort comes with a number of info hooks to add some extra functionality to regular weechat eval strings.
+Info hooks can be used in eval strings in the form of {cyan}${{info:some_hook,arguments}}{reset}.
+
+Commas and backslashes in arguments to autosort info hooks (except for {cyan}${{info:autosort_escape}}{reset}) must be escaped with a backslash.
+
+{*white}${{info:{brown}autosort_replace{white},{cyan}pattern{white},{cyan}replacement{white},{cyan}source{white}}}{reset}
+Replace all occurrences of {cyan}pattern{reset} with {cyan}replacement{reset} in the string {cyan}source{reset}.
+Can be used to ignore certain strings when sorting by replacing them with an empty string.
+
+For example: {cyan}${{info:autosort_replace,cat,dog,the dog is meowing}}{reset} expands to "the cat is meowing".
+
+{*white}${{info:{brown}autosort_order{white},{cyan}value{white},{cyan}option0{white},{cyan}option1{white},{cyan}option2{white},{cyan}...{white}}}
+Generate a zero-padded number that corresponds to the index of {cyan}value{reset} in the list of options.
+If one of the options is the special value {brown}*{reset}, then any value not explicitly mentioned will be sorted at that position.
+Otherwise, any value that does not match an option is assigned the highest number available.
+Can be used to easily sort buffers based on a manual sequence.
+
+For example: {cyan}${{info:autosort_order,${{server}},freenode,oftc,efnet}}{reset} will sort freenode before oftc, followed by efnet and then any remaining servers.
+Alternatively, {cyan}${{info:autosort_order,${{server}},freenode,oftc,*,efnet}}{reset} will sort any unlisted servers after freenode and oftc, but before efnet.
+
+{*white}${{info:{brown}autosort_escape{white},{cyan}text{white}}}{reset}
+Escape commas and backslashes in {cyan}text{reset} by prepending them with a backslash.
+This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks.
+Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.
+
+For example, it can be used to safely pass buffer names to {cyan}${{info:autosort_replace}}{reset} like so:
+{cyan}${{info:autosort_replace,##,#,${{info:autosort_escape,${{buffer.name}}}}}}{reset}.
+
+
+{*white}# Description
+Autosort is a weechat script to automatically keep your buffers sorted. The sort
+order can be customized by defining your own sort rules, but the default should
+be sane enough for most people. It can also group IRC channel/private buffers
+under their server buffer if you like.
+
+Autosort uses a stable sorting algorithm, meaning that you can manually move buffers
+to change their relative order, if they sort equal with your rule set.
+
+{*white}# Sort rules{reset}
+Autosort evaluates a list of eval expressions (see {*default}/help eval{reset}) and sorts the
+buffers based on evaluated result. Earlier rules will be considered first. Only
+if earlier rules produced identical results is the result of the next rule
+considered for sorting purposes.
+
+You can debug your sort rules with the `{*default}/autosort debug{reset}` command, which will
+print the evaluation results of each rule for each buffer.
+
+{*brown}NOTE:{reset} The sort rules for version 3 are not compatible with version 2 or vice
+versa. You will have to manually port your old rules to version 3 if you have any.
+
+{*white}# Helper variables{reset}
+You may define helper variables for the main sort rules to keep your rules
+readable. They can be used in the main sort rules as variables. For example,
+a helper variable named `{cyan}foo{reset}` can be accessed in a main rule with the
+string `{cyan}${{foo}}{reset}`.
+
+{*white}# Automatic or manual sorting{reset}
+By default, autosort will automatically sort your buffer list whenever a buffer
+is opened, merged, unmerged or renamed. This should keep your buffers sorted in
+almost all situations. However, you may wish to change the list of signals that
+cause your buffer list to be sorted. Simply edit the `{cyan}autosort.sorting.signals{reset}`
+option to add or remove any signal you like.
+
+If you remove all signals you can still sort your buffers manually with the
+`{*default}/autosort sort{reset}` command. To prevent all automatic sorting, the option
+`{cyan}autosort.sorting.sort_on_config_change{reset}` should also be disabled.
+
+{*white}# Recommended settings
+For the best visual effect, consider setting the following options:
+ {*white}/set {cyan}irc.look.server_buffer{reset} {brown}independent{reset}
+
+This setting allows server buffers to be sorted independently, which is
+needed to create a hierarchical tree view of the server and channel buffers.
+
+If you are using the {*default}buflist{reset} plugin you can (ab)use Unicode to draw a tree
+structure with the following setting (modify to suit your need):
+ {*white}/set {cyan}buflist.format.indent {brown}"${{color:237}}${{if:${{buffer.next_buffer.local_variables.type}}=~^(channel|private)$?├─:└─}}"{reset}
+'''
+
+command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)'
+
+info_replace_description = (
+ 'Replace all occurrences of `pattern` with `replacement` in the string `source`. '
+ 'Can be used to ignore certain strings when sorting by replacing them with an empty string. '
+ 'See /help autosort for examples.'
+)
+info_replace_arguments = 'pattern,replacement,source'
+
+info_order_description = (
+ 'Generate a zero-padded number that corresponds to the index of `value` in the list of options. '
+ 'If one of the options is the special value `*`, then any value not explicitly mentioned will be sorted at that position. '
+ 'Otherwise, any value that does not match an option is assigned the highest number available. '
+ 'Can be used to easily sort buffers based on a manual sequence. '
+ 'See /help autosort for examples.'
+)
+info_order_arguments = 'value,first,second,third,...'
+
+info_escape_description = (
+ 'Escape commas and backslashes in `text` by prepending them with a backslash. '
+ 'This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. '
+ 'Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.'
+ 'See /help autosort for examples.'
+)
+info_escape_arguments = 'text'
+
+
+if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""):
+ config = Config('autosort')
+
+ colors = {
+ 'default': weechat.color('default'),
+ 'reset': weechat.color('reset'),
+ 'black': weechat.color('black'),
+ 'red': weechat.color('red'),
+ 'green': weechat.color('green'),
+ 'brown': weechat.color('brown'),
+ 'yellow': weechat.color('yellow'),
+ 'blue': weechat.color('blue'),
+ 'magenta': weechat.color('magenta'),
+ 'cyan': weechat.color('cyan'),
+ 'white': weechat.color('white'),
+ '*default': weechat.color('*default'),
+ '*black': weechat.color('*black'),
+ '*red': weechat.color('*red'),
+ '*green': weechat.color('*green'),
+ '*brown': weechat.color('*brown'),
+ '*yellow': weechat.color('*yellow'),
+ '*blue': weechat.color('*blue'),
+ '*magenta': weechat.color('*magenta'),
+ '*cyan': weechat.color('*cyan'),
+ '*white': weechat.color('*white'),
+ }
+
+ weechat.hook_config('autosort.*', 'on_config_changed', '')
+ weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '')
+ weechat.hook_command('autosort', command_description.format(**colors), '', '', command_completion, 'on_autosort_command', '')
+ weechat.hook_info('autosort_escape', info_escape_description, info_escape_arguments, 'on_info_escape', '')
+ weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '')
+ weechat.hook_info('autosort_order', info_order_description, info_order_arguments, 'on_info_order', '')
+
+ apply_config()
diff --git a/scripts/python/bufsave.py b/scripts/python/bufsave.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# bufsave script for weechat - developed by acidvegas (https://git.acid.vegas/weechat)
+# usage: /bufsave saves current buffer to a $HOME/.weechats/logs/
+
+import time
+import weechat
+
+def cstrip(text):
+ return weechat.string_remove_color(text, '')
+
+def bufsave_cmd(data, buffer, args):
+ filename = weechat.buffer_get_string(buffer, 'localvar_server') + '.' + weechat.buffer_get_string(buffer, 'localvar_channel') + '-' + time.strftime('%y_%m_%d-%I_%M_%S') + '.log'
+ filename = weechat.string_eval_path_home('%h/logs/' + filename, {}, {}, {})
+ try:
+ fp = open(filename, 'w')
+ except:
+ weechat.prnt('', 'Error writing to target file!')
+ return weechat.WEECHAT_RC_OK
+ own_lines = weechat.hdata_pointer(weechat.hdata_get('buffer'), buffer, 'own_lines')
+ if own_lines:
+ line = weechat.hdata_pointer(weechat.hdata_get('lines'), own_lines, 'first_line')
+ hdata_line = weechat.hdata_get('line')
+ hdata_line_data = weechat.hdata_get('line_data')
+ while line:
+ data = weechat.hdata_pointer(hdata_line, line, 'data')
+ if data:
+ date = weechat.hdata_time(hdata_line_data, data, 'date')
+ if not isinstance(date, str):
+ date = time.strftime('%F %T', time.localtime(int(date)))
+ fp.write('{0} {1} {2}\n'.format(date, cstrip(weechat.hdata_string(hdata_line_data, data, 'prefix')), cstrip(weechat.hdata_string(hdata_line_data, data, 'message'))))
+ line = weechat.hdata_move(hdata_line, line, 1)
+ fp.close()
+ return weechat.WEECHAT_RC_OK
+
+if weechat.register('bufsave', 'acidvegas', '1.0', 'ISC', 'save buffer to file', '', ''):
+ weechat.hook_command('bufsave', 'save current buffer to a file', '[filename]', 'filename: target file (must not exist)\n', '%f', 'bufsave_cmd', '')
+\ No newline at end of file
diff --git a/scripts/python/colorize_nicks.py b/scripts/python/colorize_nicks.py
@@ -0,0 +1,416 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2010 by xt <xt@bash.no>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# This script colors nicks in IRC channels in the actual message
+# not just in the prefix section.
+#
+#
+# History:
+# 2022-11-07: mva
+# version 30: add ":" and "," to VALID_NICK regexp,
+# to don't reset colorization in input_line
+# 2022-07-11: ncfavier
+# version 29: check nick for exclusion *after* stripping
+# decrease minimum min_nick_length to 1
+# 2020-11-29: jess
+# version 28: fix ignore_tags having been broken by weechat 2.9 changes
+# 2020-05-09: Sébastien Helleu <flashcode@flashtux.org>
+# version 27: add compatibility with new weechat_print modifier data
+# (WeeChat >= 2.9)
+# 2018-04-06: Joey Pabalinas <joeypabalinas@gmail.com>
+# version 26: fix freezes with too many nicks in one line
+# 2018-03-18: nils_2
+# version 25: fix unable to run function colorize_config_reload_cb()
+# 2017-06-20: lbeziaud <louis.beziaud@ens-rennes.fr>
+# version 24: colorize utf8 nicks
+# 2017-03-01, arza <arza@arza.us>
+# version 23: don't colorize nicklist group names
+# 2016-05-01, Simmo Saan <simmo.saan@gmail.com>
+# version 22: invalidate cached colors on hash algorithm change
+# 2015-07-28, xt
+# version 21: fix problems with nicks with commas in them
+# 2015-04-19, xt
+# version 20: fix ignore of nicks in URLs
+# 2015-04-18, xt
+# version 19: new option ignore nicks in URLs
+# 2015-03-03, xt
+# version 18: iterate buffers looking for nicklists instead of servers
+# 2015-02-23, holomorph
+# version 17: fix coloring in non-channel buffers (#58)
+# 2014-09-17, holomorph
+# version 16: use weechat config facilities
+# clean unused, minor linting, some simplification
+# 2014-05-05, holomorph
+# version 15: fix python2-specific re.search check
+# 2013-01-29, nils_2
+# version 14: make script compatible with Python 3.x
+# 2012-10-19, ldvx
+# version 13: Iterate over every word to prevent incorrect colorization of
+# nicks. Added option greedy_matching.
+# 2012-04-28, ldvx
+# version 12: added ignore_tags to avoid colorizing nicks if tags are present
+# 2012-01-14, nesthib
+# version 11: input_text_display hook and modifier to colorize nicks in input bar
+# 2010-12-22, xt
+# version 10: hook config option for updating blacklist
+# 2010-12-20, xt
+# version 0.9: hook new config option for weechat 0.3.4
+# 2010-11-01, nils_2
+# version 0.8: hook_modifier() added to communicate with rainbow_text
+# 2010-10-01, xt
+# version 0.7: changes to support non-irc-plugins
+# 2010-07-29, xt
+# version 0.6: compile regexp as per patch from Chris quigybo@hotmail.com
+# 2010-07-19, xt
+# version 0.5: fix bug with incorrect coloring of own nick
+# 2010-06-02, xt
+# version 0.4: update to reflect API changes
+# 2010-03-26, xt
+# version 0.3: fix error with exception
+# 2010-03-24, xt
+# version 0.2: use ignore_channels when populating to increase performance.
+# 2010-02-03, xt
+# version 0.1: initial (based on ruby script by dominikh)
+#
+# Known issues: nicks will not get colorized if they begin with a character
+# such as ~ (which some irc networks do happen to accept)
+
+import weechat
+import re
+w = weechat
+
+SCRIPT_NAME = "colorize_nicks"
+SCRIPT_AUTHOR = "xt <xt@bash.no>"
+SCRIPT_VERSION = "30"
+SCRIPT_LICENSE = "GPL"
+SCRIPT_DESC = "Use the weechat nick colors in the chat area"
+
+# Based on the recommendations in RFC 7613. A valid nick is composed
+# of anything but " ,*?.!@".
+VALID_NICK = r'([@~&!%+-])?([^\s,\*?\.!@:,]+)'
+valid_nick_re = re.compile(VALID_NICK)
+ignore_channels = []
+ignore_nicks = []
+
+# Dict with every nick on every channel with its color as lookup value
+colored_nicks = {}
+
+CONFIG_FILE_NAME = "colorize_nicks"
+
+# config file and options
+colorize_config_file = ""
+colorize_config_option = {}
+
+def colorize_config_init():
+ '''
+ Initialization of configuration file.
+ Sections: look.
+ '''
+ global colorize_config_file, colorize_config_option
+ colorize_config_file = weechat.config_new(CONFIG_FILE_NAME,
+ "", "")
+ if colorize_config_file == "":
+ return
+
+ # section "look"
+ section_look = weechat.config_new_section(
+ colorize_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "")
+ if section_look == "":
+ weechat.config_free(colorize_config_file)
+ return
+ colorize_config_option["blacklist_channels"] = weechat.config_new_option(
+ colorize_config_file, section_look, "blacklist_channels",
+ "string", "Comma separated list of channels", "", 0, 0,
+ "", "", 0, "", "", "", "", "", "")
+ colorize_config_option["blacklist_nicks"] = weechat.config_new_option(
+ colorize_config_file, section_look, "blacklist_nicks",
+ "string", "Comma separated list of nicks", "", 0, 0,
+ "so,root", "so,root", 0, "", "", "", "", "", "")
+ colorize_config_option["min_nick_length"] = weechat.config_new_option(
+ colorize_config_file, section_look, "min_nick_length",
+ "integer", "Minimum length nick to colorize", "",
+ 1, 20, "2", "2", 0, "", "", "", "", "", "")
+ colorize_config_option["colorize_input"] = weechat.config_new_option(
+ colorize_config_file, section_look, "colorize_input",
+ "boolean", "Whether to colorize input", "", 0,
+ 0, "off", "off", 0, "", "", "", "", "", "")
+ colorize_config_option["ignore_tags"] = weechat.config_new_option(
+ colorize_config_file, section_look, "ignore_tags",
+ "string", "Comma separated list of tags to ignore; i.e. irc_join,irc_part,irc_quit", "", 0, 0,
+ "", "", 0, "", "", "", "", "", "")
+ colorize_config_option["greedy_matching"] = weechat.config_new_option(
+ colorize_config_file, section_look, "greedy_matching",
+ "boolean", "If off, then use lazy matching instead", "", 0,
+ 0, "on", "on", 0, "", "", "", "", "", "")
+ colorize_config_option["match_limit"] = weechat.config_new_option(
+ colorize_config_file, section_look, "match_limit",
+ "integer", "Fall back to lazy matching if greedy matches exceeds this number", "",
+ 20, 1000, "", "", 0, "", "", "", "", "", "")
+ colorize_config_option["ignore_nicks_in_urls"] = weechat.config_new_option(
+ colorize_config_file, section_look, "ignore_nicks_in_urls",
+ "boolean", "If on, don't colorize nicks inside URLs", "", 0,
+ 0, "off", "off", 0, "", "", "", "", "", "")
+
+def colorize_config_read():
+ ''' Read configuration file. '''
+ global colorize_config_file
+ return weechat.config_read(colorize_config_file)
+
+def colorize_nick_color(nick, my_nick):
+ ''' Retrieve nick color from weechat. '''
+ if nick == my_nick:
+ return w.color(w.config_string(w.config_get('weechat.color.chat_nick_self')))
+ else:
+ return w.info_get('nick_color', nick)
+
+def colorize_cb(data, modifier, modifier_data, line):
+ ''' Callback that does the colorizing, and returns new line if changed '''
+
+ global ignore_nicks, ignore_channels, colored_nicks
+
+ if modifier_data.startswith('0x'):
+ # WeeChat >= 2.9
+ buffer, tags = modifier_data.split(';', 1)
+ else:
+ # WeeChat <= 2.8
+ plugin, buffer_name, tags = modifier_data.split(';', 2)
+ buffer = w.buffer_search(plugin, buffer_name)
+
+ channel = w.buffer_get_string(buffer, 'localvar_channel')
+ tags = tags.split(',')
+
+ # Check if buffer has colorized nicks
+ if buffer not in colored_nicks:
+ return line
+
+ if channel and channel in ignore_channels:
+ return line
+
+ min_length = w.config_integer(colorize_config_option['min_nick_length'])
+ reset = w.color('reset')
+
+ # Don't colorize if the ignored tag is present in message
+ tag_ignores = w.config_string(colorize_config_option['ignore_tags']).split(',')
+ for tag in tags:
+ if tag in tag_ignores:
+ return line
+
+ for words in valid_nick_re.findall(line):
+ nick = words[1]
+
+ # If the matched word is not a known nick, we try to match the
+ # word without its first or last character (if not a letter).
+ # This is necessary as "foo:" is a valid nick, which could be
+ # adressed as "foo::".
+ if nick not in colored_nicks[buffer]:
+ if not nick[-1].isalpha() and not nick[0].isalpha():
+ if nick[1:-1] in colored_nicks[buffer]:
+ nick = nick[1:-1]
+ elif not nick[0].isalpha():
+ if nick[1:] in colored_nicks[buffer]:
+ nick = nick[1:]
+ elif not nick[-1].isalpha():
+ if nick[:-1] in colored_nicks[buffer]:
+ nick = nick[:-1]
+
+ # Check that nick is not ignored and longer than minimum length
+ if len(nick) < min_length or nick in ignore_nicks:
+ continue
+
+ # Check that nick is in the dictionary colored_nicks
+ if nick in colored_nicks[buffer]:
+ nick_color = colored_nicks[buffer][nick]
+
+ try:
+ # Let's use greedy matching. Will check against every word in a line.
+ if w.config_boolean(colorize_config_option['greedy_matching']):
+ cnt = 0
+ limit = w.config_integer(colorize_config_option['match_limit'])
+
+ for word in line.split():
+ cnt += 1
+ assert cnt < limit
+ # if cnt > limit:
+ # raise RuntimeError('Exceeded colorize_nicks.look.match_limit.');
+
+ if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \
+ word.startswith(('http://', 'https://')):
+ continue
+
+ if nick in word:
+ # Is there a nick that contains nick and has a greater lenght?
+ # If so let's save that nick into var biggest_nick
+ biggest_nick = ""
+ for i in colored_nicks[buffer]:
+ cnt += 1
+ assert cnt < limit
+
+ if nick in i and nick != i and len(i) > len(nick):
+ if i in word:
+ # If a nick with greater len is found, and that word
+ # also happens to be in word, then let's save this nick
+ biggest_nick = i
+ # If there's a nick with greater len, then let's skip this
+ # As we will have the chance to colorize when biggest_nick
+ # iterates being nick.
+ if len(biggest_nick) > 0 and biggest_nick in word:
+ pass
+ elif len(word) < len(biggest_nick) or len(biggest_nick) == 0:
+ new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset))
+ line = line.replace(word, new_word)
+
+ # Switch to lazy matching
+ else:
+ raise AssertionError
+
+ except AssertionError:
+ # Let's use lazy matching for nick
+ nick_color = colored_nicks[buffer][nick]
+ # The two .? are in case somebody writes "nick:", "nick,", etc
+ # to address somebody
+ regex = r"(\A|\s).?(%s).?(\Z|\s)" % re.escape(nick)
+ match = re.search(regex, line)
+ if match is not None:
+ new_line = line[:match.start(2)] + nick_color+nick+reset + line[match.end(2):]
+ line = new_line
+
+ return line
+
+def colorize_input_cb(data, modifier, modifier_data, line):
+ ''' Callback that does the colorizing in input '''
+
+ global ignore_nicks, ignore_channels, colored_nicks
+
+ min_length = w.config_integer(colorize_config_option['min_nick_length'])
+
+ if not w.config_boolean(colorize_config_option['colorize_input']):
+ return line
+
+ buffer = w.current_buffer()
+ # Check if buffer has colorized nicks
+ if buffer not in colored_nicks:
+ return line
+
+ channel = w.buffer_get_string(buffer, 'name')
+ if channel and channel in ignore_channels:
+ return line
+
+ reset = w.color('reset')
+
+ for words in valid_nick_re.findall(line):
+ nick = words[1]
+ # Check that nick is not ignored and longer than minimum length
+ if len(nick) < min_length or nick in ignore_nicks:
+ continue
+ if nick in colored_nicks[buffer]:
+ nick_color = colored_nicks[buffer][nick]
+ line = line.replace(nick, '%s%s%s' % (nick_color, nick, reset))
+
+ return line
+
+def populate_nicks(*args):
+ ''' Fills entire dict with all nicks weechat can see and what color it has
+ assigned to it. '''
+ global colored_nicks
+
+ colored_nicks = {}
+
+ buffers = w.infolist_get('buffer', '', '')
+ while w.infolist_next(buffers):
+ buffer_ptr = w.infolist_pointer(buffers, 'pointer')
+ my_nick = w.buffer_get_string(buffer_ptr, 'localvar_nick')
+ nicklist = w.infolist_get('nicklist', buffer_ptr, '')
+ while w.infolist_next(nicklist):
+ if buffer_ptr not in colored_nicks:
+ colored_nicks[buffer_ptr] = {}
+
+ if w.infolist_string(nicklist, 'type') != 'nick':
+ continue
+
+ nick = w.infolist_string(nicklist, 'name')
+ nick_color = colorize_nick_color(nick, my_nick)
+
+ colored_nicks[buffer_ptr][nick] = nick_color
+
+ w.infolist_free(nicklist)
+
+ w.infolist_free(buffers)
+
+ return w.WEECHAT_RC_OK
+
+def add_nick(data, signal, type_data):
+ ''' Add nick to dict of colored nicks '''
+ global colored_nicks
+
+ # Nicks can have , in them in some protocols
+ splitted = type_data.split(',')
+ pointer = splitted[0]
+ nick = ",".join(splitted[1:])
+ if pointer not in colored_nicks:
+ colored_nicks[pointer] = {}
+
+ my_nick = w.buffer_get_string(pointer, 'localvar_nick')
+ nick_color = colorize_nick_color(nick, my_nick)
+
+ colored_nicks[pointer][nick] = nick_color
+
+ return w.WEECHAT_RC_OK
+
+def remove_nick(data, signal, type_data):
+ ''' Remove nick from dict with colored nicks '''
+ global colored_nicks
+
+ # Nicks can have , in them in some protocols
+ splitted = type_data.split(',')
+ pointer = splitted[0]
+ nick = ",".join(splitted[1:])
+
+ if pointer in colored_nicks and nick in colored_nicks[pointer]:
+ del colored_nicks[pointer][nick]
+
+ return w.WEECHAT_RC_OK
+
+def update_blacklist(*args):
+ ''' Set the blacklist for channels and nicks. '''
+ global ignore_channels, ignore_nicks
+ ignore_channels = w.config_string(colorize_config_option['blacklist_channels']).split(',')
+ ignore_nicks = w.config_string(colorize_config_option['blacklist_nicks']).split(',')
+ return w.WEECHAT_RC_OK
+
+if __name__ == "__main__":
+ if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE,
+ SCRIPT_DESC, "", ""):
+ colorize_config_init()
+ colorize_config_read()
+
+ # Run once to get data ready
+ update_blacklist()
+ populate_nicks()
+
+ w.hook_signal('nicklist_nick_added', 'add_nick', '')
+ w.hook_signal('nicklist_nick_removed', 'remove_nick', '')
+ w.hook_modifier('weechat_print', 'colorize_cb', '')
+ # Hook config for changing colors
+ w.hook_config('weechat.color.chat_nick_colors', 'populate_nicks', '')
+ w.hook_config('weechat.look.nick_color_hash', 'populate_nicks', '')
+ # Hook for working togheter with other scripts (like colorize_lines)
+ w.hook_modifier('colorize_nicks', 'colorize_cb', '')
+ # Hook for modifying input
+ w.hook_modifier('250|input_text_display', 'colorize_input_cb', '')
+ # Hook for updating blacklist (this could be improved to use fnmatch)
+ weechat.hook_config('%s.look.blacklist*' % SCRIPT_NAME, 'update_blacklist', '')
diff --git a/scripts/python/fullwidth.py b/scripts/python/fullwidth.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# fullwidth script for weechat - developed by acidvegas in python (https://git.acid.vegas/weechat)
+
+import weechat
+
+def cmd_fullwidth(data, buf, args):
+ chars = list()
+ for char in list(args):
+ if ord(char) == 32:
+ char = chr(12288)
+ elif ord(char) > 32 and ord(char) <= 126:
+ char = chr(ord(char) + 65248)
+ chars.append(char)
+ weechat.command(buf, '/input send ' + ''.join(chars))
+ return weechat.WEECHAT_RC_OK
+
+if weechat.register('fullwidth', 'acidvegas', '1.0', 'ISC', 'convert text to wide characters.', '', ''):
+ weechat.hook_command('fullwidth', 'convert text to wide characters.', '<text>', '', '', 'cmd_fullwidth', '')
+\ No newline at end of file
diff --git a/scripts/python/go.py b/scripts/python/go.py
@@ -0,0 +1,574 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2014 Sébastien Helleu <flashcode@flashtux.org>
+# Copyright (C) 2010 m4v <lambdae2@gmail.com>
+# Copyright (C) 2011 stfn <stfnmd@googlemail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+#
+# History:
+#
+# 2023-01-08, Sébastien Helleu <flashcode@flashtux.org>:
+# version 2.8: send buffer pointer with signal "input_text_changed"
+# 2021-05-25, Tomáš Janoušek <tomi@nomi.cz>:
+# version 2.7: add new option to prefix short names with server names
+# 2019-07-11, Simmo Saan <simmo.saan@gmail.com>
+# version 2.6: fix detection of "/input search_text_here"
+# 2017-04-01, Sébastien Helleu <flashcode@flashtux.org>:
+# version 2.5: add option "buffer_number"
+# 2017-03-02, Sébastien Helleu <flashcode@flashtux.org>:
+# version 2.4: fix syntax and indentation error
+# 2017-02-25, Simmo Saan <simmo.saan@gmail.com>
+# version 2.3: fix fuzzy search breaking buffer number search display
+# 2016-01-28, ylambda <ylambda@koalabeast.com>
+# version 2.2: add option "fuzzy_search"
+# 2015-11-12, nils_2 <weechatter@arcor.de>
+# version 2.1: fix problem with buffer short_name "weechat", using option
+# "use_core_instead_weechat", see:
+# https://github.com/weechat/weechat/issues/574
+# 2014-05-12, Sébastien Helleu <flashcode@flashtux.org>:
+# version 2.0: add help on options, replace option "sort_by_activity" by
+# "sort" (add sort by name and first match at beginning of
+# name and by number), PEP8 compliance
+# 2012-11-26, Nei <anti.teamidiot.de>
+# version 1.9: add auto_jump option to automatically go to buffer when it
+# is uniquely selected
+# 2012-09-17, Sébastien Helleu <flashcode@flashtux.org>:
+# version 1.8: fix jump to non-active merged buffers (jump with buffer name
+# instead of number)
+# 2012-01-03 nils_2 <weechatter@arcor.de>
+# version 1.7: add option "use_core_instead_weechat"
+# 2012-01-03, Sébastien Helleu <flashcode@flashtux.org>:
+# version 1.6: make script compatible with Python 3.x
+# 2011-08-24, stfn <stfnmd@googlemail.com>:
+# version 1.5: /go with name argument jumps directly to buffer
+# Remember cursor position in buffer input
+# 2011-05-31, Elián Hanisch <lambdae2@gmail.com>:
+# version 1.4: Sort list of buffers by activity.
+# 2011-04-25, Sébastien Helleu <flashcode@flashtux.org>:
+# version 1.3: add info "go_running" (used by script input_lock.rb)
+# 2010-11-01, Sébastien Helleu <flashcode@flashtux.org>:
+# version 1.2: use high priority for hooks to prevent conflict with other
+# plugins/scripts (WeeChat >= 0.3.4 only)
+# 2010-03-25, Elián Hanisch <lambdae2@gmail.com>:
+# version 1.1: use a space to match the end of a string
+# 2009-11-16, Sébastien Helleu <flashcode@flashtux.org>:
+# version 1.0: add new option to display short names
+# 2009-06-15, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.9: fix typo in /help go with command /key
+# 2009-05-16, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.8: search buffer by number, fix bug when window is split
+# 2009-05-03, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.7: eat tab key (do not complete input, just move buffer
+# pointer)
+# 2009-05-02, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.6: sync with last API changes
+# 2009-03-22, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.5: update modifier signal name for input text display,
+# fix arguments for function string_remove_color
+# 2009-02-18, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.4: do not hook command and init options if register failed
+# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.3: case insensitive search for buffers names
+# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.2: add help about Tab key
+# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
+# version 0.1: initial release
+#
+
+"""
+Quick jump to buffers.
+(this script requires WeeChat 0.3.0 or newer)
+"""
+
+from __future__ import print_function
+
+SCRIPT_NAME = 'go'
+SCRIPT_AUTHOR = 'Sébastien Helleu <flashcode@flashtux.org>'
+SCRIPT_VERSION = '2.8'
+SCRIPT_LICENSE = 'GPL3'
+SCRIPT_DESC = 'Quick jump to buffers'
+
+SCRIPT_COMMAND = 'go'
+
+IMPORT_OK = True
+
+try:
+ import weechat
+except ImportError:
+ print('This script must be run under WeeChat.')
+ print('Get WeeChat now at: http://www.weechat.org/')
+ IMPORT_OK = False
+
+import re
+
+# script options
+SETTINGS = {
+ 'color_number': (
+ 'yellow,magenta',
+ 'color for buffer number (not selected)'),
+ 'color_number_selected': (
+ 'yellow,red',
+ 'color for selected buffer number'),
+ 'color_name': (
+ 'black,cyan',
+ 'color for buffer name (not selected)'),
+ 'color_name_selected': (
+ 'black,brown',
+ 'color for a selected buffer name'),
+ 'color_name_highlight': (
+ 'red,cyan',
+ 'color for highlight in buffer name (not selected)'),
+ 'color_name_highlight_selected': (
+ 'red,brown',
+ 'color for highlight in a selected buffer name'),
+ 'message': (
+ 'Go to: ',
+ 'message to display before list of buffers'),
+ 'short_name': (
+ 'off',
+ 'display and search in short names instead of buffer name'),
+ 'short_name_server': (
+ 'off',
+ 'prefix short names with server names for search and display'),
+ 'sort': (
+ 'number,beginning',
+ 'comma-separated list of keys to sort buffers '
+ '(the order is important, sorts are performed in the given order): '
+ 'name = sort by name (or short name), ',
+ 'hotlist = sort by hotlist order, '
+ 'number = first match a buffer number before digits in name, '
+ 'beginning = first match at beginning of names (or short names); '
+ 'the default sort of buffers is by numbers'),
+ 'use_core_instead_weechat': (
+ 'off',
+ 'use name "core" instead of "weechat" for core buffer'),
+ 'auto_jump': (
+ 'off',
+ 'automatically jump to buffer when it is uniquely selected'),
+ 'fuzzy_search': (
+ 'off',
+ 'search buffer matches using approximation'),
+ 'buffer_number': (
+ 'on',
+ 'display buffer number'),
+}
+
+# hooks management
+HOOK_COMMAND_RUN = {
+ 'input': ('/input *', 'go_command_run_input'),
+ 'buffer': ('/buffer *', 'go_command_run_buffer'),
+ 'window': ('/window *', 'go_command_run_window'),
+}
+hooks = {}
+
+# input before command /go (we'll restore it later)
+saved_input = ''
+saved_input_pos = 0
+
+# last user input (if changed, we'll update list of matching buffers)
+old_input = None
+
+# matching buffers
+buffers = []
+buffers_pos = 0
+
+
+def go_option_enabled(option):
+ """Checks if a boolean script option is enabled or not."""
+ return weechat.config_string_to_boolean(weechat.config_get_plugin(option))
+
+
+def go_info_running(data, info_name, arguments):
+ """Returns "1" if go is running, otherwise "0"."""
+ return '1' if 'modifier' in hooks else '0'
+
+
+def go_unhook_one(hook):
+ """Unhook something hooked by this script."""
+ global hooks
+ if hook in hooks:
+ weechat.unhook(hooks[hook])
+ del hooks[hook]
+
+
+def go_unhook_all():
+ """Unhook all."""
+ go_unhook_one('modifier')
+ for hook in HOOK_COMMAND_RUN:
+ go_unhook_one(hook)
+
+
+def go_hook_all():
+ """Hook command_run and modifier."""
+ global hooks
+ priority = ''
+ version = weechat.info_get('version_number', '') or 0
+ # use high priority for hook to prevent conflict with other plugins/scripts
+ # (WeeChat >= 0.3.4 only)
+ if int(version) >= 0x00030400:
+ priority = '2000|'
+ for hook, value in HOOK_COMMAND_RUN.items():
+ if hook not in hooks:
+ hooks[hook] = weechat.hook_command_run(
+ '%s%s' % (priority, value[0]),
+ value[1], '')
+ if 'modifier' not in hooks:
+ hooks['modifier'] = weechat.hook_modifier(
+ 'input_text_display_with_cursor', 'go_input_modifier', '')
+
+
+def go_start(buf):
+ """Start go on buffer."""
+ global saved_input, saved_input_pos, old_input, buffers_pos
+ go_hook_all()
+ saved_input = weechat.buffer_get_string(buf, 'input')
+ saved_input_pos = weechat.buffer_get_integer(buf, 'input_pos')
+ weechat.buffer_set(buf, 'input', '')
+ old_input = None
+ buffers_pos = 0
+
+
+def go_end(buf):
+ """End go on buffer."""
+ global saved_input, saved_input_pos, old_input
+ go_unhook_all()
+ weechat.buffer_set(buf, 'input', saved_input)
+ weechat.buffer_set(buf, 'input_pos', str(saved_input_pos))
+ old_input = None
+
+
+def go_match_beginning(buf, string):
+ """Check if a string matches the beginning of buffer name/short name."""
+ if not string:
+ return False
+ esc_str = re.escape(string)
+ if re.search(r'^#?' + esc_str, buf['name']) \
+ or re.search(r'^#?' + esc_str, buf['short_name']):
+ return True
+ return False
+
+
+def go_match_fuzzy(name, string):
+ """Check if string matches name using approximation."""
+ if not string:
+ return False
+
+ name_len = len(name)
+ string_len = len(string)
+
+ if string_len > name_len:
+ return False
+ if name_len == string_len:
+ return name == string
+
+ # Attempt to match all chars somewhere in name
+ prev_index = -1
+ for i, char in enumerate(string):
+ index = name.find(char, prev_index+1)
+ if index == -1:
+ return False
+ prev_index = index
+ return True
+
+
+def go_now(buf, args):
+ """Go to buffer specified by args."""
+ listbuf = go_matching_buffers(args)
+ if not listbuf:
+ return
+
+ # prefer buffer that matches at beginning (if option is enabled)
+ if 'beginning' in weechat.config_get_plugin('sort').split(','):
+ for index in range(len(listbuf)):
+ if go_match_beginning(listbuf[index], args):
+ weechat.command(buf,
+ '/buffer ' + str(listbuf[index]['full_name']))
+ return
+
+ # jump to first buffer in matching buffers by default
+ weechat.command(buf, '/buffer ' + str(listbuf[0]['full_name']))
+
+
+def go_cmd(data, buf, args):
+ """Command "/go": just hook what we need."""
+ global hooks
+ if args:
+ go_now(buf, args)
+ elif 'modifier' in hooks:
+ go_end(buf)
+ else:
+ go_start(buf)
+ return weechat.WEECHAT_RC_OK
+
+
+def go_matching_buffers(strinput):
+ """Return a list with buffers matching user input."""
+ global buffers_pos
+ listbuf = []
+ if len(strinput) == 0:
+ buffers_pos = 0
+ strinput = strinput.lower()
+ infolist = weechat.infolist_get('buffer', '', '')
+ while weechat.infolist_next(infolist):
+ pointer = weechat.infolist_pointer(infolist, 'pointer')
+ short_name = weechat.infolist_string(infolist, 'short_name')
+ server = weechat.buffer_get_string(pointer, 'localvar_server')
+ if go_option_enabled('short_name'):
+ if go_option_enabled('short_name_server') and server:
+ name = server + '.' + short_name
+ else:
+ name = short_name
+ else:
+ name = weechat.infolist_string(infolist, 'name')
+ if name == 'weechat' \
+ and go_option_enabled('use_core_instead_weechat') \
+ and weechat.infolist_string(infolist, 'plugin_name') == 'core':
+ name = 'core'
+ number = weechat.infolist_integer(infolist, 'number')
+ full_name = weechat.infolist_string(infolist, 'full_name')
+ if not full_name:
+ full_name = '%s.%s' % (
+ weechat.infolist_string(infolist, 'plugin_name'),
+ weechat.infolist_string(infolist, 'name'))
+ matching = name.lower().find(strinput) >= 0
+ if not matching and strinput[-1] == ' ':
+ matching = name.lower().endswith(strinput.strip())
+ if not matching and go_option_enabled('fuzzy_search'):
+ matching = go_match_fuzzy(name.lower(), strinput)
+ if not matching and strinput.isdigit():
+ matching = str(number).startswith(strinput)
+ if len(strinput) == 0 or matching:
+ listbuf.append({
+ 'number': number,
+ 'short_name': short_name,
+ 'name': name,
+ 'full_name': full_name,
+ 'pointer': pointer,
+ })
+ weechat.infolist_free(infolist)
+
+ # sort buffers
+ hotlist = []
+ infolist = weechat.infolist_get('hotlist', '', '')
+ while weechat.infolist_next(infolist):
+ hotlist.append(
+ weechat.infolist_pointer(infolist, 'buffer_pointer'))
+ weechat.infolist_free(infolist)
+ last_index_hotlist = len(hotlist)
+
+ def _sort_name(buf):
+ """Sort buffers by name (or short name)."""
+ return buf['name']
+
+ def _sort_hotlist(buf):
+ """Sort buffers by hotlist order."""
+ try:
+ return hotlist.index(buf['pointer'])
+ except ValueError:
+ # not in hotlist, always last.
+ return last_index_hotlist
+
+ def _sort_match_number(buf):
+ """Sort buffers by match on number."""
+ return 0 if str(buf['number']) == strinput else 1
+
+ def _sort_match_beginning(buf):
+ """Sort buffers by match at beginning."""
+ return 0 if go_match_beginning(buf, strinput) else 1
+
+ funcs = {
+ 'name': _sort_name,
+ 'hotlist': _sort_hotlist,
+ 'number': _sort_match_number,
+ 'beginning': _sort_match_beginning,
+ }
+
+ for key in weechat.config_get_plugin('sort').split(','):
+ if key in funcs:
+ listbuf = sorted(listbuf, key=funcs[key])
+
+ if not strinput:
+ index = [i for i, buf in enumerate(listbuf)
+ if buf['pointer'] == weechat.current_buffer()]
+ if index:
+ buffers_pos = index[0]
+
+ return listbuf
+
+
+def go_buffers_to_string(listbuf, pos, strinput):
+ """Return string built with list of buffers found (matching user input)."""
+ string = ''
+ strinput = strinput.lower()
+ for i in range(len(listbuf)):
+ selected = '_selected' if i == pos else ''
+ buffer_name = listbuf[i]['name']
+ index = buffer_name.lower().find(strinput)
+ if index >= 0:
+ index2 = index + len(strinput)
+ name = '%s%s%s%s%s' % (
+ buffer_name[:index],
+ weechat.color(weechat.config_get_plugin(
+ 'color_name_highlight' + selected)),
+ buffer_name[index:index2],
+ weechat.color(weechat.config_get_plugin(
+ 'color_name' + selected)),
+ buffer_name[index2:])
+ elif go_option_enabled("fuzzy_search") and \
+ go_match_fuzzy(buffer_name.lower(), strinput):
+ name = ""
+ prev_index = -1
+ for char in strinput.lower():
+ index = buffer_name.lower().find(char, prev_index+1)
+ if prev_index < 0:
+ name += buffer_name[:index]
+ name += weechat.color(weechat.config_get_plugin(
+ 'color_name_highlight' + selected))
+ if prev_index >= 0 and index > prev_index+1:
+ name += weechat.color(weechat.config_get_plugin(
+ 'color_name' + selected))
+ name += buffer_name[prev_index+1:index]
+ name += weechat.color(weechat.config_get_plugin(
+ 'color_name_highlight' + selected))
+ name += buffer_name[index]
+ prev_index = index
+
+ name += weechat.color(weechat.config_get_plugin(
+ 'color_name' + selected))
+ name += buffer_name[prev_index+1:]
+ else:
+ name = buffer_name
+ string += ' '
+ if go_option_enabled('buffer_number'):
+ string += '%s%s' % (
+ weechat.color(weechat.config_get_plugin(
+ 'color_number' + selected)),
+ str(listbuf[i]['number']))
+ string += '%s%s%s' % (
+ weechat.color(weechat.config_get_plugin(
+ 'color_name' + selected)),
+ name,
+ weechat.color('reset'))
+ return ' ' + string if string else ''
+
+
+def go_input_modifier(data, modifier, modifier_data, string):
+ """This modifier is called when input text item is built by WeeChat.
+
+ This is commonly called after changes in input or cursor move: it builds
+ a new input with prefix ("Go to:"), and suffix (list of buffers found).
+ """
+ global old_input, buffers, buffers_pos
+ if modifier_data != weechat.current_buffer():
+ return ''
+ names = ''
+ new_input = weechat.string_remove_color(string, '')
+ new_input = new_input.lstrip()
+ if old_input is None or new_input != old_input:
+ old_buffers = buffers
+ buffers = go_matching_buffers(new_input)
+ if buffers != old_buffers and len(new_input) > 0:
+ if len(buffers) == 1 and go_option_enabled('auto_jump'):
+ weechat.command(modifier_data, '/wait 1ms /input return')
+ buffers_pos = 0
+ old_input = new_input
+ names = go_buffers_to_string(buffers, buffers_pos, new_input.strip())
+ return weechat.config_get_plugin('message') + string + names
+
+
+def go_command_run_input(data, buf, command):
+ """Function called when a command "/input xxx" is run."""
+ global buffers, buffers_pos
+ if command.startswith('/input search_text') or command.startswith('/input jump'):
+ # search text or jump to another buffer is forbidden now
+ return weechat.WEECHAT_RC_OK_EAT
+ elif command == '/input complete_next':
+ # choose next buffer in list
+ buffers_pos += 1
+ if buffers_pos >= len(buffers):
+ buffers_pos = 0
+ weechat.hook_signal_send('input_text_changed',
+ weechat.WEECHAT_HOOK_SIGNAL_POINTER, buf)
+ return weechat.WEECHAT_RC_OK_EAT
+ elif command == '/input complete_previous':
+ # choose previous buffer in list
+ buffers_pos -= 1
+ if buffers_pos < 0:
+ buffers_pos = len(buffers) - 1
+ weechat.hook_signal_send('input_text_changed',
+ weechat.WEECHAT_HOOK_SIGNAL_POINTER, buf)
+ return weechat.WEECHAT_RC_OK_EAT
+ elif command == '/input return':
+ # switch to selected buffer (if any)
+ go_end(buf)
+ if len(buffers) > 0:
+ weechat.command(
+ buf, '/buffer ' + str(buffers[buffers_pos]['full_name']))
+ return weechat.WEECHAT_RC_OK_EAT
+ return weechat.WEECHAT_RC_OK
+
+
+def go_command_run_buffer(data, buf, command):
+ """Function called when a command "/buffer xxx" is run."""
+ return weechat.WEECHAT_RC_OK_EAT
+
+
+def go_command_run_window(data, buf, command):
+ """Function called when a command "/window xxx" is run."""
+ return weechat.WEECHAT_RC_OK_EAT
+
+
+def go_unload_script():
+ """Function called when script is unloaded."""
+ go_unhook_all()
+ return weechat.WEECHAT_RC_OK
+
+
+def go_main():
+ """Entry point."""
+ if not weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
+ SCRIPT_LICENSE, SCRIPT_DESC,
+ 'go_unload_script', ''):
+ return
+ weechat.hook_command(
+ SCRIPT_COMMAND,
+ 'Quick jump to buffers', '[name]',
+ 'name: directly jump to buffer by name (without argument, list is '
+ 'displayed)\n\n'
+ 'You can bind command to a key, for example:\n'
+ ' /key bind meta-g /go\n\n'
+ 'You can use completion key (commonly Tab and shift-Tab) to select '
+ 'next/previous buffer in list.',
+ '%(buffers_names)',
+ 'go_cmd', '')
+
+ # set default settings
+ version = weechat.info_get('version_number', '') or 0
+ for option, value in SETTINGS.items():
+ if not weechat.config_is_set_plugin(option):
+ weechat.config_set_plugin(option, value[0])
+ if int(version) >= 0x00030500:
+ weechat.config_set_desc_plugin(
+ option, '%s (default: "%s")' % (value[1], value[0]))
+ weechat.hook_info('go_running',
+ 'Return "1" if go is running, otherwise "0"',
+ '',
+ 'go_info_running', '')
+
+
+if __name__ == "__main__" and IMPORT_OK:
+ go_main()
diff --git a/scripts/python/greentext.py b/scripts/python/greentext.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# greentext script for weechat - developed by acidvegas in python (https://git.acid.vegas/weechat)
+
+'''
+Todo: this can be turned into a trigger
+'''
+
+import re,weechat
+
+def between(source, start, stop):
+ data = re.compile(start + '(.*?)' + stop, re.IGNORECASE|re.MULTILINE).search(source)
+ if data:
+ return data.group(1)
+ else:
+ return False
+
+def cb_greentext(data,buffer,command):
+ if command=='/input return':
+ data=weechat.buffer_get_string(buffer,'input')
+ if data:
+ if data[0]=='>':
+ data='\x0303'+data
+ elif '!!' in data or '__' in data or '**' in data:
+ for word in data.split():
+ if word[:2] == '!!':
+ data = data.replace(word, '\x1F\x02\x0304 ' + word[2:].upper() + ' \x0f', 1)
+ elif word[:2] == '__':
+ data = data.replace(word, '\x1F\x02' + word[2:].upper() + '\x0f', 1)
+ weechat.buffer_set(buffer,'input',data)
+ return weechat.WEECHAT_RC_OK
+
+if weechat.register('greentext','','','','','',''):weechat.hook_command_run('/input return','cb_greentext','')
+\ No newline at end of file
diff --git a/scripts/python/masshl.py b/scripts/python/masshl.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# masshl script for weechat - developed by acidvegas (https://git.acid.vegas/weechat)
+
+import weechat
+
+nicks = list()
+
+def timer_cb(data, remaining_calls):
+ try:
+ chan = data.split('[split]')[0]
+ msg = data.split('[split]')[1]
+ if '%n' in msg:
+ while '%n' in msg:
+ msg = msg.replace('%n', nicks[0], 1)
+ nicks.pop(0)
+ weechat.command(chan, msg))
+ except:
+ pass
+ finally:
+ return weechat.WEECHAT_RC_OK
+
+def masshl_cmd_cb(data, buffer, args):
+ server = weechat.buffer_get_string(buffer, 'localvar_server')
+ channel = weechat.buffer_get_string(buffer, 'localvar_channel')
+ nicklist = weechat.infolist_get('irc_nick', '', server+','+channel)
+ while weechat.infolist_next(nicklist):
+ nicks.append(weechat.infolist_string(nicklist, 'name')
+ weechat.infolist_free(nicklist)
+ del server, channel, nicklist
+ nicks.pop(0)
+ if args[:2] == '-1':
+ weechat.command(buffer, ', '.join(nicks))
+ else:
+ weechat.hook_timer(100, 0, len(nicks), 'timer_cb', '[split]'.join((buffer,args)))
+ return weechat.WEECHAT_RC_OK
+
+if weechat.register('masshl', 'acidvegas', '1.0', 'ISC', 'mass hilight all nicks in a channel', '', ''):
+ weechat.hook_command('masshl', 'mass hilight all nicks in a channel', '', '', '', 'masshl_cmd_cb', '')
+\ No newline at end of file
diff --git a/scripts/python/rainbow.py b/scripts/python/rainbow.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# rainbow script for weechat - developed by acidvegas in python (https://git.acid.vegas/weechat)
+
+import weechat
+
+def cmd_rainbow(data, buf, args):
+ colors = [5,4,7,8,3,9,10,11,2,12,6,13]
+ output = ''
+ if args[:2] == '-w':
+ for word in args[2:].split():
+ output += '\x03' + str(colors[0]) + word + ' '
+ colors.append(colors.pop(0))
+ else:
+ for char in list(args):
+ if char == ' ':
+ output += char
+ else:
+ output += '\x03' + str(colors[0]) + char
+ colors.append(colors.pop(0))
+ weechat.command(buf, '/input send ' + output)
+ return weechat.WEECHAT_RC_OK
+
+if weechat.register('rainbow', 'acidvegas', '1.0', 'ISC', 'rainbow text', '', ''):
+ weechat.hook_command('rainbow', 'rainbow text', '<text>', '', '', 'cmd_rainbow', '')
+\ No newline at end of file
diff --git a/scripts/python/unifuck.py b/scripts/python/unifuck.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# unifuck script for weechat - developed by acidvegas in python (https://git.acid.vegas/weechat)
+
+import random
+import weechat
+
+def cmd_unifuck(data, buf, args):
+ if args[:2] == '-e':
+ msg = ''
+ for i in range(random.randint(300, 400)):
+ msg += chr(random.randint(0x1F600,0x1F64F))
+ else:
+ msg='\u202e\u0007\x03' + str(random.randint(2,13))
+ for i in range(random.randint(300, 400)):
+ msg += chr(random.randint(0x1000,0x3000))
+ weechat.command(buf, '/input send ' + msg)
+ return weechat.WEECHAT_RC_OK
+
+if weechat.register('unifuck', 'acidvegas', '1.0', 'ISC', 'random unicode spam', '', ''):
+ weechat.hook_command('unifuck', 'random unicode spam', '', '', '', 'cmd_unifuck', '')
+\ No newline at end of file
| | | | | | | | | | | | | | | | | | |