### # Copyright (c) 2024, codey # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. ### import textwrap import time import re import requests from supybot import utils, plugins, ircutils, callbacks, conf, ircdb from supybot.commands import * from supybot.i18n import PluginInternationalization from supybot.i18n import internationalizeDocstring _ = PluginInternationalization('Lyrics') class SearchException(Exception): pass class Lyrics(callbacks.Plugin): """Song Lyrics Search""" threaded = True @wrap(['text']) def sing(self, irc, msg, args, query): """ Sing : """ """Check if Lyrics Search is enabled in this context/channel""" if not(self.registryValue('enable', network=irc.network, channel=msg.channel)): return irc.noReply() """Check for botwide ignores""" if ircdb.checkIgnored(msg.prefix, msg.args[0]): return irc.noReply() use_mores = self.registryValue('mores', network=irc.network, channel=msg.channel) truncate = self.registryValue('truncate', network=irc.network, channel=msg.channel) lines_to_send = self.registryValue('lines', network=irc.network, channel=msg.channel) quiet = self.registryValue('quiet', network=irc.network, channel=msg.channel) verbose = self.registryValue('verbose', network=irc.network, channel=msg.channel) can_flood = self.registryValue('floodexempt') subsearch = None trailer = " [...]" query = "".join(query.strip()) colons = len(re.findall(r':', query)) dashes = len(re.findall(r'\s-\s', query)) has_subsearch = (colons > 1) """Check for valid query ( must contain : or - )""" if not(colons) and not(dashes): irc.reply("\002\00304Invalid query! " \ "Query format should be either : , - , or optionally " \ " : : ", prefixNick=False) return if colons: artist = query.split(":")[0].strip() song = query.split(":")[1].strip() else: artist = query.split(" - ", maxsplit=1)[0].strip() song = query.split(" - ", maxsplit=1)[1].strip() searchObj = { 'a': artist, 's': song, 'src': 'IRC-SHARED', 'extras': True } headers = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0', 'Content-Type': 'application/json; charset=utf-8' } if has_subsearch: subsearch = query.split(":")[2].strip() searchObj['sub'] = subsearch try: if not(quiet): irc.reply("\002\00307Searching...", private=not(verbose), notice=not(verbose), prefixNick=False) request = requests.post(self.registryValue('host'), json=searchObj, timeout=(10, 20), verify=False, headers=headers) request.raise_for_status() response = request.json() if response.get('err'): raise SearchException(response.get('errorText')) returned_artist = response.get('artist') returned_song = response.get('song') if not(quiet): irc.reply("\002\00309Found: %s - %s" % (returned_artist, returned_song), notice=not(verbose), private=not(verbose), prefixNick=False) lyrics = response.get('lyrics') lyrics = re.sub(r'
', ' / ', lyrics) if truncate and use_mores: irc.reply(lyrics, prefixNick=False) return wrapped_lyrics = textwrap.wrap(text=lyrics, width=360, break_on_hyphens=True, break_long_words=True) if not(truncate): lines_to_send = len(wrapped_lyrics) for idx, line in enumerate(wrapped_lyrics): line = re.sub(r'/(\s{0,})$', '', line).strip() if idx >= lines_to_send: break truncated = (idx+1 == lines_to_send and truncate) irc.reply(line.strip() + (trailer if truncated else ''), prefixNick=False) if idx >= 5 and not(can_flood): time.sleep(0.5) # Avoid RecvQ by sleeping for 0.5s starting with the 5th line of output return except Exception as e: irc.reply(f"\002\00304Error: {str(e)}", prefixNick=False) return Class = Lyrics