Skip to content

Commit e9cddd0

Browse files
committed
server_autoswitch.py 0.5: improve script logic and overall stability, with better error handling, add /help text, add SPDX copyright and license tags
1 parent 727fe6f commit e9cddd0

2 files changed

Lines changed: 171 additions & 108 deletions

File tree

‎REUSE.toml‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,12 +1528,6 @@ precedence = "override"
15281528
SPDX-FileCopyrightText = "2010 0x1cedd1ce <0x1cedd1ce@freeunix.net>"
15291529
SPDX-License-Identifier = "GPL-3.0-or-later"
15301530

1531-
[[annotations]]
1532-
path = "python/server_autoswitch.py"
1533-
precedence = "override"
1534-
SPDX-FileCopyrightText = "2012-2013 Nils Görs <weechatter@arcor.de>"
1535-
SPDX-License-Identifier = "GPL-3.0-or-later"
1536-
15371531
[[annotations]]
15381532
path = "python/shutup.py"
15391533
precedence = "override"

‎python/server_autoswitch.py‎

Lines changed: 171 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,189 @@
1-
# -*- coding: utf-8 -*-
1+
# SPDX-FileCopyrightText: 2012-2026 Nils Görs <weechatter@arcor.de>
22
#
3-
# Copyright (c) 2012-2013 by nils_2 <weechatter@arcor.de>
4-
#
5-
# cylce to currently used server if you are using merged server buffer
6-
#
7-
# This program is free software; you can redistribute it and/or modify
8-
# it under the terms of the GNU General Public License as published by
9-
# the Free Software Foundation; either version 3 of the License, or
10-
# (at your option) any later version.
11-
#
12-
# This program is distributed in the hope that it will be useful,
13-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15-
# GNU General Public License for more details.
16-
#
17-
# You should have received a copy of the GNU General Public License
18-
# along with this program. If not, see <http://www.gnu.org/licenses/>.
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
# 2012-01-22: nils_2,(freenode.#weechat)
6+
# 0.1 : initial release
197
#
20-
# 2013-01-25: nils_2, (freenode.#weechat)
21-
# 0.4 : make script compatible with Python 3.x
8+
# 2012-01-27: nils_2,(freenode.#weechat)
9+
# 0.2 : fix: bug with split windows removed (reported by meingtsla)
2210
#
2311
# 2012-01-28: nils_2,(freenode.#weechat)
2412
# 0.3 : adapted to bugfix #31158 and new signal hook_signal("window_switch")
2513
#
26-
# 2012-01-27: nils_2,(freenode.#weechat)
27-
# 0.2 : fix: bug with split windows removed (reported by meingtsla)
28-
#
29-
# 2012-01-22: nils_2,(freenode.#weechat)
30-
# 0.1 : initial release
14+
# 2013-01-25: nils_2, (freenode.#weechat)
15+
# 0.4 : make script compatible with Python 3.x
3116
#
32-
# Development is currently hosted at
33-
# https://github.com/weechatter/weechat-scripts
17+
# 2026-09-11: nils_2, (libera.#weechat)
18+
# 0.5 : improve script logic and overall stability, with better error handling
19+
# : add: /help text
3420

3521
try:
36-
import weechat,re
37-
38-
except Exception:
22+
import weechat
23+
except ImportError:
3924
print("This script must be run under WeeChat.")
40-
print("Get WeeChat now at: http://www.weechat.org/")
41-
quit()
25+
print("Get WeeChat now at: https://weechat.org/")
26+
raise SystemExit(1)
27+
28+
import traceback
29+
30+
SCRIPT_NAME = "server_autoswitch"
31+
SCRIPT_AUTHOR = "nils_2 <weechatter@arcor.de>"
32+
SCRIPT_VERSION = "0.5"
33+
SCRIPT_LICENSE = "GPL3"
34+
SCRIPT_DESC = "cycle merged server buffers to match the server of the current channel"
4235

43-
SCRIPT_NAME = "server_autoswitch"
44-
SCRIPT_AUTHOR = "nils_2 <weechatter@arcor.de>"
45-
SCRIPT_VERSION = "0.4"
46-
SCRIPT_LICENSE = "GPL"
47-
SCRIPT_DESC = "cycle to currently used server if you are using merged server buffer"
4836

49-
look_server = ""
37+
# ================================[ safety wrapper ]==========================
38+
def safe_cb(func):
39+
"""Catch any exception in a hook callback and log it to the core buffer
40+
instead of letting it break the script or leave a merge group mid-cycle."""
41+
42+
def wrapper(*args, **kwargs):
43+
try:
44+
return func(*args, **kwargs)
45+
except Exception as exc: # noqa: BLE001 - intentionally broad
46+
try:
47+
weechat.prints(
48+
"",
49+
"%s%s: error in %s: %s"
50+
% (weechat.prefix("error"), SCRIPT_NAME, func.__name__, exc),
51+
)
52+
weechat.prints("", traceback.format_exc())
53+
except Exception:
54+
pass
55+
return weechat.WEECHAT_RC_OK
56+
57+
wrapper.__name__ = func.__name__
58+
return wrapper
5059

60+
61+
# ================================[ helpers ]=================================
62+
def merging_enabled():
63+
"""Fast pre-check: is server-buffer merging switched on at all? Purely
64+
an optimization to skip the infolist scan for the (very common) case
65+
where it's off; the actual decision still happens via buffer numbers."""
66+
option = weechat.config_get("irc.look.server_buffer")
67+
if not option:
68+
return True # option not found (future WeeChat change?) - fail open
69+
return weechat.config_string(option) != "independent"
70+
71+
72+
def buffers_sharing_number(number):
73+
"""Pointers of every buffer that currently shares this buffer number,
74+
in the same order WeeChat itself cycles through them. A number held by
75+
only one buffer means "not merged with anything right now"."""
76+
members = []
77+
infolist = weechat.infolist_get("buffer", "", "")
78+
if not infolist:
79+
return members
80+
try:
81+
while weechat.infolist_next(infolist):
82+
if weechat.infolist_integer(infolist, "number") == number:
83+
buf = weechat.infolist_pointer(infolist, "pointer")
84+
if buf:
85+
members.append(buf)
86+
finally:
87+
weechat.infolist_free(infolist)
88+
return members
89+
90+
91+
def cycle_to_server_buffer(target_buf):
92+
"""Cycle target_buf's merge group (if it is merged with anything) until
93+
target_buf itself becomes the active/displayed member."""
94+
if not target_buf:
95+
return
96+
if weechat.buffer_get_integer(target_buf, "active") == 1:
97+
return # already showing - nothing to do
98+
99+
number = weechat.buffer_get_integer(target_buf, "number")
100+
group = buffers_sharing_number(number)
101+
if len(group) < 2:
102+
return # not currently merged with anything
103+
104+
# Bounded: never take more steps than the group has members, so a
105+
# buffer that unexpectedly never becomes active can't loop forever.
106+
for _ in range(len(group)):
107+
active_buf = next(
108+
(buf for buf in group if weechat.buffer_get_integer(buf, "active") == 1),
109+
None,
110+
)
111+
if active_buf is None:
112+
return # shouldn't happen, but don't spin on it
113+
weechat.command(active_buf, "/input switch_active_buffer")
114+
if weechat.buffer_get_integer(target_buf, "active") == 1:
115+
return
116+
117+
118+
def handle_buffer(buf):
119+
"""If buf is an irc channel/private buffer, bring its server buffer to
120+
the front of its merge group (if merged)."""
121+
if not buf:
122+
return
123+
if not merging_enabled():
124+
return
125+
if weechat.buffer_get_string(buf, "localvar_plugin") != "irc":
126+
return
127+
if weechat.buffer_get_string(buf, "localvar_type") not in ("channel", "private"):
128+
return
129+
130+
servername = weechat.buffer_get_string(buf, "localvar_server")
131+
if not servername:
132+
return
133+
134+
server_buf = weechat.info_get("irc_buffer", servername)
135+
cycle_to_server_buffer(server_buf)
136+
137+
138+
# ================================[ signal callbacks ]=========================
139+
@safe_cb
140+
def buffer_switch_cb(data, signal, signal_data):
141+
handle_buffer(signal_data)
142+
return weechat.WEECHAT_RC_OK
143+
144+
145+
@safe_cb
51146
def window_switch_cb(data, signal, signal_data):
52-
bufpointer = weechat.window_get_pointer(signal_data,"buffer")
53-
buffer_switch_cb(data,signal,bufpointer)
147+
# signal_data is a window pointer here, not a buffer pointer - resolve
148+
# the buffer that window is actually showing.
149+
handle_buffer(weechat.window_get_pointer(signal_data, "buffer"))
54150
return weechat.WEECHAT_RC_OK
55-
def buffer_switch_cb(data, signal, signal_data):
56-
global look_server
57-
look_server = ""
58-
look_server = weechat.config_string(weechat.config_get("irc.look.server_buffer"))
59-
if look_server == "independent": # server buffer independent?
60-
return weechat.WEECHAT_RC_OK # better remove script, you don't need it.
61-
62-
if weechat.buffer_get_string(signal_data,'name') != 'weechat': # not weechat core buffer
63-
if (weechat.buffer_get_string(signal_data,'localvar_type') == '') or (weechat.buffer_get_string(signal_data,'localvar_type') == 'server'):
64-
return weechat.WEECHAT_RC_OK
65-
elif weechat.buffer_get_string(signal_data,'name') == 'weechat':
66-
return weechat.WEECHAT_RC_OK
67-
68-
# buffer is channel or private?
69-
if (weechat.buffer_get_string(signal_data,'localvar_type') == 'channel') or (weechat.buffer_get_string(signal_data,'localvar_type') == 'private'):
70-
bufpointer = weechat.window_get_pointer(weechat.current_window(),"buffer")
71-
servername_from_current_buffer = weechat.buffer_get_string(bufpointer, 'localvar_server')
72-
name = weechat.buffer_get_string(bufpointer, 'name')
73-
server_switch(signal_data,servername_from_current_buffer,name)
151+
152+
153+
# ================================[ command ]==================================
154+
@safe_cb
155+
def command_cb(data, buffer, args):
156+
handle_buffer(weechat.current_buffer())
74157
return weechat.WEECHAT_RC_OK
75158

76-
def server_switch(signal_data,servername_from_current_buffer,name):
77-
global look_server
78-
SERVER = {}
79-
80-
bufpointer = weechat.window_get_pointer(weechat.current_window(),"buffer")
81-
servername_current_buffer = servername_from_current_buffer
82-
if look_server == "merge_with_core": # merge_with_core
83-
SERVER["weechat"] = "core.weechat"
84-
85-
# get ALL server buffers and save them
86-
infolist = weechat.infolist_get("buffer","","*server.*") # we are only interest in server-buffers
87-
while weechat.infolist_next(infolist):
88-
bufpointer = weechat.infolist_pointer(infolist,"pointer")
89-
server = weechat.infolist_string(infolist, "name") # full servername (server.<servername>)
90-
servername = weechat.infolist_string(infolist, "short_name") # get servername from server (without prefix "server")
91-
active = weechat.infolist_integer(infolist,"active")
92-
SERVER[servername] = server
93-
if (active == 1) and (servername_current_buffer != servername): # buffer active but not correct server buffer?
94-
weechat.command(bufpointer,"/input switch_active_buffer") # switch server buffer
95-
weechat.infolist_free(infolist) # do not forget to free infolist!
96-
97-
# switch though all server and stop at server from current buffer
98-
i = 0
99-
while i <= len(SERVER):
100-
for servername,full_name in list(SERVER.items()):
101-
bufpointer = weechat.buffer_search("irc","%s" % full_name) # search pointer from server buffer
102-
if bufpointer == "": # core buffer
103-
if weechat.buffer_get_integer(weechat.buffer_search_main(),'active') == 1:
104-
weechat.command(weechat.buffer_search_main(),"/input switch_active_buffer")
105-
else: # server buffer!
106-
if (servername != servername_current_buffer) and (weechat.buffer_get_integer(bufpointer,'active') == 1):
107-
weechat.command(bufpointer,"/input switch_active_buffer")
108-
elif (servername == servername_current_buffer) and (weechat.buffer_get_integer(bufpointer,'active') == 1):
109-
i = len(SERVER)
110-
break
111-
i += 1
112-
# ================================[ main ]===============================
159+
160+
# ================================[ main ]=====================================
113161
if __name__ == "__main__":
114-
if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
115-
version = weechat.info_get("version_number", "") or 0
116-
if int(version) >= 0x00030600:
117-
weechat.hook_signal("buffer_switch","buffer_switch_cb","")
118-
weechat.hook_signal("window_switch","window_switch_cb","")
119-
else:
120-
weechat.prnt("","%s%s %s" % (weechat.prefix("error"),SCRIPT_NAME,": needs version 0.3.6 or higher"))
162+
if weechat.register(
163+
SCRIPT_NAME,
164+
SCRIPT_AUTHOR,
165+
SCRIPT_VERSION,
166+
SCRIPT_LICENSE,
167+
SCRIPT_DESC,
168+
"",
169+
"",
170+
):
171+
weechat.hook_command(
172+
SCRIPT_NAME,
173+
SCRIPT_DESC,
174+
"",
175+
"The script only makes sense if you have merged the server buffers.\n\n"
176+
"This script has no options. It runs automatically on every "
177+
"buffer switch and window switch.\n\n"
178+
"Running /%s manually re-syncs the merged server buffer to "
179+
"the currently viewed channel right away." % SCRIPT_NAME,
180+
"",
181+
"command_cb",
182+
"",
183+
)
184+
weechat.hook_signal("buffer_switch", "buffer_switch_cb", "")
185+
weechat.hook_signal("window_switch", "window_switch_cb", "")
186+
187+
# Sync once immediately in case a channel is already open when the
188+
# script gets (re)loaded.
189+
handle_buffer(weechat.current_buffer())

0 commit comments

Comments
 (0)