1 Commits

Author SHA1 Message Date
James Booth
4e5cd39bdb Add download to browser command 2016-06-09 23:28:06 +01:00
53 changed files with 3064 additions and 6466 deletions

1
.gitignore vendored
View File

@@ -1,2 +1 @@
*.so
.idea/

16
ChatStart.rb Normal file
View File

@@ -0,0 +1,16 @@
module ChatStart
@@starts = {
"prof1@panesar" => [ "\"Prof 2\"", "prof3@panesar" ],
"prof2@panesar" => [ "prof1@panesar" ]
}
def self.prof_on_connect(account_name, fulljid)
if @@starts[account_name]
@@starts[account_name].each { | contact |
Prof::send_line("/msg " + contact)
}
Prof::send_line("/win 1")
end
end
end

124
README.md
View File

@@ -1,56 +1,110 @@
# CProof Plugins
profanity-plugins
=================
[Website documentation](https://jabber.space/guides/plugins/)
Plugin support for Profanity is currently in development.
`Master` is a development branch compatible with the latest version of CProof.
* The `master` branch of Profanity now includes support for C and Python plugins.
* The `plugins` branch contains additional support for Ruby and Lua, but is unstable.
## Installing plugins
For a list of outstanding issues before releasing 0.5.0, see: https://github.com/boothj5/profanity/milestones/0.5.0
Use the `/plugins install` command, e.g.
Building Profanity with plugin support
--------------------------------------
```bash
/plugins install ~/path-to/sounds.py
```
Alternatively, you can use URLs to raw files to install Python plugins. For example,
```bash
/plugins install https://git.jabber.space/devs/cproof-plugins/raw/branch/master/src/sounds.py
```
See the `/help plugins` command for further plugin management options.
## Plugins description
To be described.
## Building CProof with plugin support
Dependencies required:
Additional dependencies required:
```
autoconf-archive
libtool
```
For Python plugins the following is also required:
Plus the development packages for supported languages:
```
python-dev
lua-dev
ruby-dev
```
## Developing plugins
Build with:
API Documentation is available on our website, [jabber.space](https://jabber.space/).
- [Python API](https://jabber.space/api-reference/python/prof/)
- [Python hooks](https://jabber.space/api-reference/python/plugin/)
- [C API](https://jabber.space/api-reference/c/profapi/)
- [C hooks](https://jabber.space/api-reference/c/profhooks/)
```
./bootstrap.sh
./configure --enable-python-plugins
make
sudo make install
```
## External plugins (C)
Only support for Python 2.7 is currently included, after building, run `profanity -v` to see which language support is available, example output:
- [profanity-plugins by Reimar](https://github.com/ReimarPB/profanity-plugins) - Collection of plugins
```
Profanity, version 0.5.0dev.plugins-python.63a7316
Copyright (C) 2012 - 2016 James Booth <boothj5web@gmail.com>.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Build information:
XMPP library: libmesode
Desktop notification support: Enabled
OTR support: Enabled
PGP support: Enabled
C plugins: Enabled
Python plugins: Enabled
```
Installing plugins
------------------
1 - Copy the plugin
For Python, Ruby and Lua plugins, copy the plugin to `$XDG_DATA/profanity/plugins/`, (`~/.local/share/profanity/plugins/` on most systems).
For C plugins, build the plugin using the supplied Makefile, and then copy the `.so` file to the same location.
2 - Load the plugin
Run the `/plugins load <plugin>` command to load the plugin, e.g. `/plugins load browser.py`, the plugin can also be manually added to the config file `$XDG_CONFIG/profanity/profrc` (`~/.config/profanity/profrc` on most systems).
For example:
```
[plugins]
load=browser.py;platform-info.py;ascii.py;pid.so
```
Getting help on plugins:
* `/plugins` - Shows a list of loaded plugins.
* `/help commands plugins` - Shows commands defined by plugins
* `/help <plugin_cmd>` - Show help for a plugin command, e.g. `/help browser`
Plugin themes
-------------
The plugins API includes functions to print themed output to the console or a plugins window. The themes need to be added to
```
~/.local/share/profanity/plugin_themes
```
For example ([syscmd.py](https://github.com/boothj5/profanity-plugins/blob/master/syscmd.py) plugin):
```
[system]
command=cyan
result=green
```
Example plugin code
-------------------
Whilst the API is being developed, the following test plugins are a good reference of possible hooks and API calls available, (Ruby and Lua examples might not be up to date):
* [tests/test-c-plugin.c](https://github.com/boothj5/profanity-plugins/blob/master/tests/test-c-plugin/test-c-plugin.c)
* [tests/python-test.py](https://github.com/boothj5/profanity-plugins/blob/master/tests/python-test.py)
* [tests/RubyTest.rb](https://github.com/boothj5/profanity-plugins/blob/master/tests/RubyTest.rb)
* [tests/luatest.lua](https://github.com/boothj5/profanity-plugins/blob/master/tests/luatest.lua)
## External plugins (Python)
- [profanity-copy-message-plugin](https://github.com/lonski/profanity-copy-message-plugin) - Copy messages to clipboard.

View File

@@ -1,6 +1,5 @@
"""
Print something in ASCII text.
Running from the console will print the text to the console.
Running in a chat or room window will send the ASCII text

175
browser.py Normal file
View File

@@ -0,0 +1,175 @@
"""
Open the last link received in a chat or room window using the systems default browser
"""
import prof
import os
import webbrowser
import re
import requests
from os.path import expanduser
_links = {}
_lastlink = {}
def _get_url():
link = None
jid = prof.get_current_recipient()
room = prof.get_current_muc()
# check if in chat window
if jid is not None:
# check for link from recipient
if jid in _lastlink.keys():
link = _lastlink[jid]
else:
prof.cons_show("No links found from " + jid)
# check if in muc window
elif room is not None:
if room in _lastlink.keys():
link = _lastlink[room]
else:
prof.cons_show("No links found in " + room)
# not in chat/muc window
else:
prof.cons_show("You must supply a URL to the /browser command")
return link
def _cmd_browser(arg1=None, arg2=None):
global _lastlink
link = None
if arg1 == "setdir":
if not arg2:
prof.cons_bad_cmd_usage("/browser")
else:
prof.settings_set_string("browser", "download.path", arg2)
prof.cons_show("browser.py set download path to" + arg2)
return
if arg1 == "get":
link = arg2 if arg2 is not None else _get_url()
if not link:
prof.log_debug("browser.py: no link found for get command")
return
folder = prof.settings_get_string("browser", "download.path", "")
if folder == "":
prof.cons_show("Download path not set, see /help browser")
return
if folder[0] == '~':
folder = expanduser("~") + folder[1:]
response = requests.get(link)
if response.status_code != 200:
prof.log_debug("browser.py: received non 200 response for get command")
return
filename = link.split("/")[-1]
full_path = folder + "/" + filename
prof.log_debug("browser.py: Saving link {link} to file {file}".format(link=link, file=full_path))
with open(full_path, 'wb') as f:
for chunk in response.iter_content(8):
f.write(chunk)
return
link = arg1 if arg1 is not None else _get_url();
if link is not None:
prof.cons_show("Opening " + link + " in browser")
_open_browser(link)
def _open_browser(url):
savout = os.dup(1)
saverr = os.dup(2)
os.close(1)
os.close(2)
os.open(os.devnull, os.O_RDWR)
try:
webbrowser.open(url, new=2)
finally:
os.dup2(savout, 1)
os.dup2(saverr, 2)
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/browser",
"/browser <url>",
"/browser get <url>",
"/browser setdir <folder>"
]
description = (
"View a URL in the systems default browser, or download and view link contents " +
"If no argument is supplied, the last URL in the current chat or room will be used. " +
"Tab autocompletion will go through all previous links in the current chat/room"
)
args = [
[ "<url>", "URL to open in the browser" ],
[ "download <url>", "Download contents of specified URL" ],
[ "setdir <dir>", "Filesystem path to store downloaded content" ]
]
examples = [
"/browser http://www.profanity.im",
"/browser setdir ~/Downloads",
"/browser get http://www.movenoticias.com/wp-content/uploads/2015/05/Bruce-Iron-Maiden-850x583.jpg"
]
prof.register_command("/browser", 0, 2, synopsis, description, args, examples, _cmd_browser)
prof.completer_add("/browser", [ "get", "setdir" ])
def prof_on_chat_win_focus(jid):
prof.completer_clear("/browser")
if jid in _links:
prof.completer_add("/browser", _links[jid])
def prof_on_room_win_focus(room):
prof.completer_clear("/browser")
if room in _links:
prof.completer_add("/browser", _links[room])
def _process_message(jid, current_jid, message):
links = re.findall(r'(https?://\S+)', message)
if len(links) > 0:
if jid not in _links:
_links[jid] = []
# add to list of links for jid
for link in links:
if link not in _links[jid]:
prof.log_debug("browser.py: Saving {link} for {jid}".format(link=link, jid=jid))
_links[jid].append(link)
# add to autocompleter if message for current window
if current_jid == jid:
prof.completer_add("/browser", _links[jid])
# set last link for jid
_lastlink[jid] = links[len(links)-1]
def prof_post_chat_message_display(jid, message):
current_jid = prof.get_current_recipient()
_process_message(jid, current_jid, message)
def prof_post_room_message_display(room, nick, message):
current_jid = prof.get_current_muc()
_process_message(room, current_jid, message)
def prof_on_room_history_message(room, nick, message, timestamp):
current_jid = prof.get_current_muc()
_process_message(room, current_jid, message)

51
chatterbot/chatterbot.py Normal file
View File

@@ -0,0 +1,51 @@
import prof
from chatterbotapi import ChatterBotFactory, ChatterBotType
factory = ChatterBotFactory()
bot = factory.create(ChatterBotType.CLEVERBOT)
# bot = factory.create(ChatterBotType.JABBERWACKY)
# bot = factory.create(ChatterBotType.PANDORABOTS, 'b0dafd24ee35a477')
bot_session = {}
bot_state = False
def prof_post_chat_message_display(jid, message):
if bot_state:
if jid not in bot_session:
bot_session[jid] = bot.create_session()
response = bot_session[jid].think(message)
prof.send_line("/msg " + jid + " " + response)
def _cmd_chatterbot(state):
global bot_state
if state == "enable":
prof.cons_show("ChatterBot Activated")
bot_state = True
elif state == "disable":
prof.cons_show("ChatterBot Stopped")
bot_state = False
else:
if bot_state:
prof.cons_show("ChatterBot is running - current sessions:")
prof.cons_show(str(bot_session))
else:
prof.cons_show("ChatterBot is stopped - /chatterbot enable to activate.")
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/chatterbot",
"/chatterbot enable|disable"
]
description = "ChatterBot, running with no args will show the current chatterbot status"
args = [
[ "enable", "Enable chatterbot" ],
[ "disable", "Disable chatterbot" ]
]
examples = []
prof.register_command("/chatterbot", 0, 1, synopsis, description, args, examples, _cmd_chatterbot)
prof.completer_add("/chatterbot", [ "enable", "disable" ])

190
chatterbot/chatterbotapi.py Normal file
View File

@@ -0,0 +1,190 @@
import re
import sys
import hashlib
if sys.version_info >= (3, 0):
from urllib.request import build_opener, HTTPCookieProcessor, urlopen
from urllib.parse import urlencode
import http.cookiejar as cookielib
else:
from urllib import urlencode, urlopen
from urllib2 import build_opener, HTTPCookieProcessor
import cookielib
import uuid
import xml.dom.minidom
"""
chatterbotapi
Copyright (C) 2011 pierredavidbelanger@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
#################################################
# API
#################################################
class ChatterBotType:
CLEVERBOT = 1
JABBERWACKY = 2
PANDORABOTS = 3
class ChatterBotFactory:
def create(self, type, arg = None):
if type == ChatterBotType.CLEVERBOT:
return _Cleverbot('http://www.cleverbot.com', 'http://www.cleverbot.com/webservicemin', 35)
elif type == ChatterBotType.JABBERWACKY:
return _Cleverbot('http://jabberwacky.com', 'http://jabberwacky.com/webservicemin', 29)
elif type == ChatterBotType.PANDORABOTS:
if arg == None:
raise Exception('PANDORABOTS needs a botid arg')
return _Pandorabots(arg)
return None
class ChatterBot:
def create_session(self):
return None
class ChatterBotSession:
def think_thought(self, thought):
return thought
def think(self, text):
thought = ChatterBotThought()
thought.text = text
return self.think_thought(thought).text
class ChatterBotThought:
pass
#################################################
# Cleverbot impl
#################################################
class _Cleverbot(ChatterBot):
def __init__(self, baseUrl, serviceUrl, endIndex):
self.baseUrl = baseUrl
self.serviceUrl = serviceUrl
self.endIndex = endIndex
def create_session(self):
return _CleverbotSession(self)
class _CleverbotSession(ChatterBotSession):
def __init__(self, bot):
self.bot = bot
self.vars = {}
self.vars['start'] = 'y'
self.vars['icognoid'] = 'wsf'
self.vars['fno'] = '0'
self.vars['sub'] = 'Say'
self.vars['islearning'] = '1'
self.vars['cleanslate'] = 'false'
self.cookieJar = cookielib.CookieJar()
self.opener = build_opener(HTTPCookieProcessor(self.cookieJar))
self.opener.open(self.bot.baseUrl)
def think_thought(self, thought):
self.vars['stimulus'] = thought.text
data = urlencode(self.vars)
data_to_digest = data[9:self.bot.endIndex]
data_digest = hashlib.md5(data_to_digest.encode('utf-8')).hexdigest()
data = data + '&icognocheck=' + data_digest
url_response = self.opener.open(self.bot.serviceUrl, data.encode('utf-8'))
response = str(url_response.read())
response_values = re.split(r'\\r|\r', response)
#self.vars['??'] = _utils_string_at_index(response_values, 0)
self.vars['sessionid'] = _utils_string_at_index(response_values, 1)
self.vars['logurl'] = _utils_string_at_index(response_values, 2)
self.vars['vText8'] = _utils_string_at_index(response_values, 3)
self.vars['vText7'] = _utils_string_at_index(response_values, 4)
self.vars['vText6'] = _utils_string_at_index(response_values, 5)
self.vars['vText5'] = _utils_string_at_index(response_values, 6)
self.vars['vText4'] = _utils_string_at_index(response_values, 7)
self.vars['vText3'] = _utils_string_at_index(response_values, 8)
self.vars['vText2'] = _utils_string_at_index(response_values, 9)
self.vars['prevref'] = _utils_string_at_index(response_values, 10)
#self.vars['??'] = _utils_string_at_index(response_values, 11)
self.vars['emotionalhistory'] = _utils_string_at_index(response_values, 12)
self.vars['ttsLocMP3'] = _utils_string_at_index(response_values, 13)
self.vars['ttsLocTXT'] = _utils_string_at_index(response_values, 14)
self.vars['ttsLocTXT3'] = _utils_string_at_index(response_values, 15)
self.vars['ttsText'] = _utils_string_at_index(response_values, 16)
self.vars['lineRef'] = _utils_string_at_index(response_values, 17)
self.vars['lineURL'] = _utils_string_at_index(response_values, 18)
self.vars['linePOST'] = _utils_string_at_index(response_values, 19)
self.vars['lineChoices'] = _utils_string_at_index(response_values, 20)
self.vars['lineChoicesAbbrev'] = _utils_string_at_index(response_values, 21)
self.vars['typingData'] = _utils_string_at_index(response_values, 22)
self.vars['divert'] = _utils_string_at_index(response_values, 23)
response_thought = ChatterBotThought()
response_thought.text = _utils_string_at_index(response_values, 16)
return response_thought
#################################################
# Pandorabots impl
#################################################
class _Pandorabots(ChatterBot):
def __init__(self, botid):
self.botid = botid
def create_session(self):
return _PandorabotsSession(self)
class _PandorabotsSession(ChatterBotSession):
def __init__(self, bot):
self.vars = {}
self.vars['botid'] = bot.botid
self.vars['custid'] = uuid.uuid1()
def think_thought(self, thought):
self.vars['input'] = thought.text
data = urlencode(self.vars)
url_response = urlopen('http://www.pandorabots.com/pandora/talk-xml', data)
response = url_response.read()
response_dom = xml.dom.minidom.parseString(response)
response_thought = ChatterBotThought()
that_elements = response_dom.getElementsByTagName('that')
if that_elements is None or len(that_elements) == 0 or that_elements[0] is None:
return ''
that_elements_child_nodes = that_elements[0].childNodes
if that_elements_child_nodes is None or len(that_elements_child_nodes) == 0 or that_elements_child_nodes[0] is None:
return ''
that_elements_child_nodes_data = that_elements_child_nodes[0].data
if that_elements_child_nodes_data is None:
return ''
response_thought.text = that_elements_child_nodes_data.strip()
return response_thought
#################################################
# Utils
#################################################
def _utils_string_at_index(strings, index):
if len(strings) > index:
return strings[index]
else:
return ''

View File

@@ -63,7 +63,6 @@ def _sv_send(muc, occupant):
ET.SubElement(iq, "query", {
"xmlns": "jabber:iq:version"
})
iq_id_count = iq_id_count + 1
prof.send_stanza(ET.tostring(iq))
@@ -72,12 +71,10 @@ def _sv_send(muc, occupant):
def _cmd_clients():
muc = prof.get_current_muc()
if muc == None:
prof.cons_show("Command only valid in chat rooms.")
return
occupants = prof.get_current_occupants()
if occupants == None or len(occupants) == 0:
prof.cons_show("No occupants for /clients command.")
return
nick = prof.get_current_nick()

26
copy-all-stable.sh Executable file
View File

@@ -0,0 +1,26 @@
rm -rf ~/.local/share/profanity/plugins/*
cp ascii.py ~/.local/share/profanity/plugins/.
cp syscmd.py ~/.local/share/profanity/plugins/.
cp paste.py ~/.local/share/profanity/plugins/.
cp browser.py ~/.local/share/profanity/plugins/.
cp platform-info.py ~/.local/share/profanity/plugins/.
cp say.py ~/.local/share/profanity/plugins/.
cp whoami.py ~/.local/share/profanity/plugins/.
cp wikipedia-prof.py ~/.local/share/profanity/plugins/.
cp clients.py ~/.local/share/profanity/plugins/.
cp tests/python-test.py ~/.local/share/profanity/plugins/.
cd pid
make clean
make
cp pid.so ~/.local/share/profanity/plugins/.
cd ..
cd tests/test-c-plugin
make clean
make
cp test-c-plugin.so ~/.local/share/profanity/plugins/.
cd ../..

26
emoticons.py Normal file
View File

@@ -0,0 +1,26 @@
"""
Convert smileys ( :) :( etc) to the Unicode character representation
"""
import prof
def _emote(input_str):
result = input_str
result = result.replace(":-)", u'\u263a'.encode("utf-8"))
result = result.replace(":)", u'\u263a'.encode("utf-8"))
result = result.replace(":-(", u'\u2639'.encode("utf-8"))
result = result.replace(":(", u'\u2639'.encode("utf-8"))
return result
def prof_pre_chat_message_display(jid, message):
return _emote(message)
def prof_pre_room_message_display(room, nick, message):
return _emote(message)
def prof_pre_priv_message_display(room, nick, message):
return _emote(message)

531
jenkins.py Normal file
View File

@@ -0,0 +1,531 @@
"""
Manage jenkins jobs
Prerequisites:
The jenkinsapi module is required https://pypi.python.org/pypi/jenkinsapi
To install the module using pip:
sudo pip install jenkinsapi
The plugin polls the jenkins server at 'jenkins_url' every 'jenkins_poll_interval' seconds.
The results are checked by profanity every 'prof_cb_interval'.
New failures are reported in the jenkins window, and a desktop notification is sent.
The 'prof_remind_interval' specifies the time between reminder notifications of broken builds
"""
import prof
import os
import threading
import time
import webbrowser
import urllib2
import base64
import jenkinsapi
from jenkinsapi.jenkins import Jenkins
# Settings
jenkins_url = "http://localhost:8080"
username = None
password = None
jenkins_poll_interval = 5
prof_cb_interval = 5
prof_remind_interval = 10
enable_notify = True
enable_remind = True
# Global state
last_state = {}
STATE_SUCCESS = "SUCCESS"
STATE_UNSTABLE = "UNSTABLE"
STATE_FAILURE = "FAILURE"
STATE_QUEUED = "QUEUED"
STATE_RUNNING = "RUNNING"
STATE_NOBUILDS = "NOBUILDS"
STATE_UNKNOWN = "UNKNOWN"
win_tag = "Jenkins"
poll_fail = False
poll_fail_message = None
job_list = None
changes_list = None
def _help():
prof.win_show(win_tag, "Commands:")
prof.win_show(win_tag, " /jenkins help - Show this help")
prof.win_show(win_tag, " /jenkins jobs - List all jobs")
prof.win_show(win_tag, " /jenkins failing - List all failing jobs")
prof.win_show(win_tag, " /jenkins passing - List all passing jobs")
prof.win_show(win_tag, " /jenkins unstable - List all unstable jobs")
prof.win_show(win_tag, " /jenkins build [job] - Trigger build for job")
prof.win_show(win_tag, " /jenkins open [job] - Open job in browser")
prof.win_show(win_tag, " /jenkins log [job] - Show the latest build log")
prof.win_show(win_tag, " /jenkins remind on|off - Enable/disable reminder notifications")
prof.win_show(win_tag, " /jenkins notify on|off - Enable/disable build notifications")
prof.win_show(win_tag, " /jenkins settings - Show current settings")
def _settings():
prof.win_show(win_tag, "Jenkins settings:")
prof.win_show(win_tag, " Jenkins URL : " + jenkins_url)
prof.win_show(win_tag, " Jenkins poll interval : " + str(jenkins_poll_interval) + " seconds")
prof.win_show(win_tag, " Profanity update interval : " + str(prof_cb_interval) + " seconds")
prof.win_show(win_tag, " Reminder interval : " + str(prof_remind_interval) + " seconds")
prof.win_show(win_tag, " Notifications enabled : " + str(enable_notify))
prof.win_show(win_tag, " Reminders enabled : " + str(enable_remind))
def _safe_remove(jobname, state):
if jobname in last_state[state]:
last_state[state].remove(jobname)
def _set_state(jobname, state):
if not jobname in last_state[state]:
_safe_remove(jobname, STATE_SUCCESS)
_safe_remove(jobname, STATE_UNSTABLE)
_safe_remove(jobname, STATE_FAILURE)
_safe_remove(jobname, STATE_QUEUED)
_safe_remove(jobname, STATE_RUNNING)
_safe_remove(jobname, STATE_NOBUILDS)
_safe_remove(jobname, STATE_UNKNOWN)
last_state[state].append(jobname)
return True
else:
return False
def _open_job_url(url):
savout = os.dup(1)
saverr = os.dup(2)
os.close(1)
os.close(2)
os.open(os.devnull, os.O_RDWR)
try:
webbrowser.open(url, new=2)
finally:
os.dup2(savout, 1)
os.dup2(saverr, 2)
class JobList():
def __init__(self):
self.jobs = []
def add_job(self, name, build_number, state):
self.jobs.append((name, build_number, state))
def get_jobs(self, in_state=None):
if not in_state:
return self.jobs
else:
result = []
for name, build_number, state in self.jobs:
if state == in_state:
result.append((name, build_number, state))
return result
def get_job(self, jobname):
for name, build_number, state in self.jobs:
if name == jobname:
return name, build_number
return None
def contains_job(self, jobname):
for name, build_number, state in self.jobs:
if name == jobname:
return True
return False
def num_in_state(self, given_state):
count = 0
for name, build_number, state in self.jobs:
if state == given_state:
count = count + 1
return count
class JobUpdates():
def __init__(self):
self.states = {}
self.states[STATE_FAILURE] = []
self.states[STATE_SUCCESS] = []
self.states[STATE_UNSTABLE] = []
self.states[STATE_QUEUED] = []
self.states[STATE_RUNNING] = []
def add_update(self, state, name, build_number=None):
if build_number:
self.states[state].append((name, build_number))
else:
self.states[state].append(name)
def get_in_state(self, state):
return self.states[state]
def _process_build(name, build, new_job_list, new_changes_list):
if build.get_status() == STATE_FAILURE:
new_job_list.add_job(name, build.get_number(), STATE_FAILURE)
changed = _set_state(name, STATE_FAILURE)
if changed:
new_changes_list.add_update(STATE_FAILURE, name, build.get_number())
elif build.get_status() == STATE_SUCCESS:
new_job_list.add_job(name, build.get_number(), STATE_SUCCESS)
changed = _set_state(name, STATE_SUCCESS)
if changed:
new_changes_list.add_update(STATE_SUCCESS, name, build.get_number())
elif build.get_status() == STATE_UNSTABLE:
new_job_list.add_job(name, build.get_number(), STATE_UNSTABLE)
changed = _set_state(name, STATE_UNSTABLE)
if changed:
new_changes_list.add_update(STATE_UNSTABLE, name, build.get_number())
def _process_queued_or_running(name, job, new_job_list, new_changes_list):
if job.is_queued():
new_job_list.add_job(name, None, STATE_QUEUED)
changed = _set_state(name, STATE_QUEUED)
if changed:
new_changes_list.add_update(STATE_QUEUED, name)
elif job.is_running():
new_job_list.add_job(name, None, STATE_RUNNING)
changed = _set_state(name, STATE_RUNNING)
if changed:
new_changes_list.add_update(STATE_RUNNING, name)
def _jenkins_poll():
global poll_fail
global poll_fail_message
global job_list
global changes_list
job_ac = []
while True:
time.sleep(jenkins_poll_interval)
try:
j = Jenkins(jenkins_url, username, password)
except Exception, e:
poll_fail = True
poll_fail_message = str(e)
else:
poll_fail = False
poll_fail_message = None
new_job_list = JobList()
new_changes_list = JobUpdates()
for name, job in j.get_jobs():
if not job.is_queued_or_running():
build = job.get_last_build_or_none()
if build:
_process_build(name, build, new_job_list, new_changes_list)
else:
new_job_list.add_job(name, None, STATE_NOBUILDS)
else:
_process_queued_or_running(name, job, new_job_list, new_changes_list)
if not job_list:
job_ac.append(name)
if not job_list:
prof.completer_add("/jenkins build", job_ac)
prof.completer_add("/jenkins open", job_ac)
prof.completer_add("/jenkins log", job_ac)
job_list = new_job_list
changes_list = new_changes_list
def _prof_callback():
global changes_list
if poll_fail:
if not prof.win_exists(win_tag):
prof.win_create(win_tag, _handle_input)
prof.win_show(win_tag, "Could not connect to jenkins, see the logs.")
if poll_fail_message:
prof.log_warning("Jenkins poll failed: " + str(poll_fail_message))
else:
prof.log_warning("Jenkins poll failed")
elif changes_list:
for name in changes_list.get_in_state(STATE_QUEUED):
if not prof.win_exists(win_tag):
prof.win_create(win_tag, _handle_input)
prof.win_show_themed(win_tag, None, None, "cyan", name + " " + STATE_QUEUED)
for name in changes_list.get_in_state(STATE_RUNNING):
if not prof.win_exists(win_tag):
prof.win_create(win_tag, _handle_input)
prof.win_show_themed(win_tag, None, None, "cyan", name + " " + STATE_RUNNING)
for name, build_number in changes_list.get_in_state(STATE_SUCCESS):
if not prof.win_exists(win_tag):
prof.win_create(win_tag, _handle_input)
prof.win_show_themed(win_tag, None, None, "green", name + " #" + str(build_number) + " " + STATE_SUCCESS)
if enable_notify:
prof.notify(name + " " + STATE_SUCCESS, 5000, "Jenkins")
for name, build_number in changes_list.get_in_state(STATE_UNSTABLE):
if not prof.win_exists(win_tag):
prof.win_create(win_tag, _handle_input)
prof.win_show_themed(win_tag, None, None, "yellow", name + " #" + str(build_number) + " " + STATE_UNSTABLE)
if enable_notify:
prof.notify(name + " " + STATE_UNSTABLE, 5000, "Jenkins")
for name, build_number in changes_list.get_in_state(STATE_FAILURE):
if not prof.win_exists(win_tag):
prof.win_create(win_tag, _handle_input)
prof.win_show_themed(win_tag, None, None, "red", name + " #" + str(build_number) + " " + STATE_FAILURE)
if enable_notify:
prof.notify(name + " " + STATE_FAILURE, 5000, "Jenkins")
changes_list = None
def _handle_input(win, line):
pass
def _list_jobs(jobs):
for name, build_number, state in jobs:
if state == STATE_SUCCESS:
prof.win_show_themed(win_tag, None, None, "green", " " + name + " #" + str(build_number) + " " + STATE_SUCCESS)
elif state == STATE_UNSTABLE:
prof.win_show_themed(win_tag, None, None, "yellow", " " + name + " #" + str(build_number) + " " + STATE_UNSTABLE)
elif state == STATE_FAILURE:
prof.win_show_themed(win_tag, None, None, "red", " " + name + " #" + str(build_number) + " " + STATE_FAILURE)
elif state == STATE_NOBUILDS:
prof.win_show(win_tag, " " + name + ", no builds")
else:
prof.win_show_themed(win_tag, None, None, "cyan", " " + name + " " + state)
def _build_job(job):
try:
request = urllib2.Request(jenkins_url + "/job/" + job + "/build");
if username:
basicauth = base64.encodestring("%s:%s" %(username, password)).replace("\n", "")
request.add_header("Authorization", "Basic %s" %basicauth);
request.add_data("")
urllib2.urlopen(request)
except Exception, e:
prof.win_show(win_tag, "Failed to build " + job + ", see the logs.")
prof.log_warning("Failed to build " + job + ": " + str(e))
else:
prof.win_show(win_tag, "Build request sent for " + job)
def _job_log(job):
name = job[0]
build_no = job[1]
if build_no:
try:
response = urllib2.urlopen(jenkins_url + "/job/" + name + "/" + str(build_no) + "/consoleText")
except Exception, e:
prof.win_show(win_tag, "Unable to fetch log for " + name + "#" + str(build_no) + ", see the logs.")
prof.log_warning("Unable to fetch log for " + name + "#" + str(build_no) + ": " + str(e))
else:
log_str = "Log for " + name + " #" + str(build_no) + ":\n" + response.read()
prof.win_show(win_tag, log_str)
else:
prof.win_show(win_tag, "No build found for " + name)
def _cmd_jenkins(cmd=None, arg=None):
global enable_remind
global enable_notify
if not prof.win_exists(win_tag):
prof.win_create(win_tag, _handle_input)
prof.win_focus(win_tag)
if cmd == "jobs":
if job_list and job_list.get_jobs():
prof.win_show(win_tag, "Jobs:")
_list_jobs(job_list.get_jobs())
else:
prof.win_show(win_tag, "No job data yet.")
elif cmd == "failing":
if job_list:
jobs_in_state = job_list.get_jobs(STATE_FAILURE)
if jobs_in_state:
prof.win_show(win_tag, "Failing jobs:")
_list_jobs(jobs_in_state)
else:
prof.win_show(win_tag, "No failing jobs.")
else:
prof.win_show(win_tag, "No job data yet.")
elif cmd == "passing":
if job_list:
jobs_in_state = job_list.get_jobs(STATE_SUCCESS)
if jobs_in_state:
prof.win_show(win_tag, "Passing jobs:")
_list_jobs(jobs_in_state)
else:
prof.win_show(win_tag, "No passing jobs.")
else:
prof.win_show(win_tag, "No job data yet.")
elif cmd == "unstable":
if job_list:
jobs_in_state = job_list.get_jobs(STATE_UNSTABLE)
if jobs_in_state:
prof.win_show(win_tag, "Unstable jobs:")
_list_jobs(jobs_in_state)
else:
prof.win_show(win_tag, "No unstable jobs.")
else:
prof.win_show(win_tag, "No job data yet.")
elif cmd == "build":
if not arg:
prof.win_show(win_tag, "You must supply a job argument.")
elif job_list and job_list.contains_job(arg):
_build_job(arg)
else:
prof.win_show(win_tag, "No such job: " + arg)
elif cmd == "open":
if not arg:
prof.win_show(win_tag, "You must supply a job argument.")
elif job_list and job_list.contains_job(arg):
_open_job_url(jenkins_url + "/job/" + arg)
else:
prof.win_show(win_tag, "No such job: " + arg)
elif cmd == "remind":
if not arg:
prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
elif arg == "on":
enable_remind = True
prof.win_show(win_tag, "Reminder notifications enabled.")
elif arg == "off":
enable_remind = False
prof.win_show(win_tag, "Reminder notifications disabled.")
else:
prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
elif cmd == "notify":
if not arg:
prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
elif arg == "on":
enable_notify = True
prof.win_show(win_tag, "Build notifications enabled.")
elif arg == "off":
enable_notify = False
prof.win_show(win_tag, "Build notifications disabled.")
else:
prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
elif cmd == "settings":
_settings()
elif cmd == "help":
_help()
elif cmd == "log":
if not arg:
prof.win_show(win_tag, "You must specify a job.")
else:
job = job_list.get_job(arg)
if job:
_job_log(job)
else:
prof.win_show(win_tag, "No job found: " + arg)
else:
prof.win_show(win_tag, "Unknown command.")
def _remind():
if enable_remind and job_list:
notify_string = ""
failing = job_list.num_in_state(STATE_FAILURE)
unstable = job_list.num_in_state(STATE_UNSTABLE)
if failing == 1:
notify_string = notify_string + "1 failing build"
if failing > 1:
notify_string = notify_string + str(failing) + " failing builds"
if failing > 0 and unstable > 0:
notify_string = notify_string + "\n"
if unstable == 1:
notify_string = notify_string + "1 unstable build"
if unstable > 1:
notify_string = notify_string + str(unstable) + " unstable builds"
if not notify_string == "":
prof.notify(notify_string, 5000, "Jenkins")
def prof_init(version, status, account_name, fulljid):
last_state[STATE_SUCCESS] = []
last_state[STATE_UNSTABLE] = []
last_state[STATE_FAILURE] = []
last_state[STATE_QUEUED] = []
last_state[STATE_RUNNING] = []
last_state[STATE_NOBUILDS] = []
last_state[STATE_UNKNOWN] = []
jenkins_t = threading.Thread(target=_jenkins_poll)
jenkins_t.daemon = True;
jenkins_t.start()
prof.register_timed(_prof_callback, prof_cb_interval)
prof.register_timed(_remind, prof_remind_interval)
prof.completer_add(
"/jenkins", [
"help",
"jobs",
"failing",
"passing",
"unstable",
"build",
"open",
"log",
"remind",
"notify",
"settings"
]
);
prof.completer_add(
"/jenkins remind", [
"on",
"off"
]
);
prof.completer_add(
"/jenkins notify", [
"on",
"off"
]
);
synopsis = [
"/jenkins jobs|failing|passing|unstable",
"/jenkins build <job>",
"/jenkins open <job>",
"/jenkins log <job>",
"/jenkins remind on|off",
"/jenkins notify on|off",
"/jenkins settings"
]
description = "Monitor, run and view Jenkins jobs."
args = [
[ "jobs", "List all jobs" ],
[ "failing", "List all failing jobs" ],
[ "passing", "List all passing jobs" ],
[ "unstable", "List all unstable jobs" ],
[ "build <job>", "Trigger build for job" ],
[ "open <job>", "Open job in browser" ],
[ "log <job>", "Show the latest build log for job" ],
[ "remind on|off", "Enable/disable reminder notifications" ],
[ "notify on|off", "Enable/disable build notifications" ],
[ "settings", "Show current settings" ]
]
examples = [
"/kenkins failing",
"/kenkins build stabber-build",
"/kenkins remind off"
]
prof.register_command("/jenkins", 0, 2, synopsis, description, args, examples, _cmd_jenkins)
def prof_on_start():
prof.win_create(win_tag, _handle_input)
prof.win_show(win_tag, "Jenkins plugin started.")

24
paste.py Normal file
View File

@@ -0,0 +1,24 @@
"""
Paste the contents of the clipboard when in a chat or room window.
"""
import prof
import sys
import Tkinter as tk
def _cmd_paste():
root = tk.Tk(baseName="")
root.withdraw()
result = root.clipboard_get()
prof.send_line(u'\u000A' + result)
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/paste"
]
description = "Paste contents of clipboard."
args = []
examples = []
prof.register_command("/paste", 0, 0, synopsis, description, args, examples, _cmd_paste)

View File

@@ -1,5 +1,5 @@
/*
Simple plugin to display the PID of the CProof process and its parent
Simple plugin to display the PID of the Profanity process and its parent
Theme example in ~/.local/share/profanity/plugin_themes
@@ -30,10 +30,10 @@ cmd_pid(char **args)
void
prof_init(const char * const version, const char * const status, const char *const account_name, const char *const fulljid)
{
char *synopsis[] = { "/pid", NULL };
char *description = "Show process ID and parent Process ID in the console window.";
char *args[][2] = { { NULL, NULL } };
char *examples[] = { NULL };
const char *synopsis[] = { "/pid", NULL };
const char *description = "Show process ID and parent Process ID in the console window.";
const char *args[][2] = { { NULL, NULL } };
const char *examples[] = { NULL };
prof_register_command("/pid", 0, 0, synopsis, description, args, examples, cmd_pid);
}

View File

@@ -9,39 +9,36 @@ On OSX will use the built in 'say' command.
import prof
import os
import shlex
from sys import platform
def say(message):
args = prof.settings_string_get("say", "args", "")
args = prof.settings_get_string("say", "args", "")
if platform == "darwin":
os.system("say " + args + " " + shlex.quote(message) + " 2>/dev/null")
os.system("say " + args + " '" + message + "' 2>/dev/null")
elif platform == "linux" or platform == "linux2":
os.system("echo " + shlex.quote(message) + " | espeak " + args + " 2>/dev/null")
os.system("echo '" + message + "' | espeak " + args + " 2>/dev/null")
def prof_post_chat_message_display(barejid, resource, message):
enabled = prof.settings_string_get("say", "enabled", "off")
def prof_post_chat_message_display(jid, message):
enabled = prof.settings_get_string("say", "enabled", "off")
current_recipient = prof.get_current_recipient()
if enabled == "on" or (enabled == "active" and current_recipient == barejid):
say(barejid + " says " + message)
if enabled == "on" or (enabled == "active" and current_recipient == jid):
say(jid + " says " + message)
return message
def prof_post_room_message_display(barejid, nick, message):
enabled = prof.settings_string_get("say", "enabled", "off")
def prof_post_room_message_display(room, nick, message):
enabled = prof.settings_get_string("say", "enabled", "off")
current_muc = prof.get_current_muc()
if enabled == "on":
say(nick + " in " + barejid + " says " + message)
elif enabled == "active" and current_muc == barejid:
say(nick + " says " + message)
if enabled == "on" or (enable == "active" and current_muc == jid):
say(nick + " says " + message + " in " + room)
return message
def prof_post_priv_message_display(barejid, nick, message):
def prof_post_priv_message_display(room, nick, message):
# TODO add get current nick hook for private chats
if enabled:
say(nick + " says " + message)
@@ -51,22 +48,22 @@ def prof_post_priv_message_display(barejid, nick, message):
def _cmd_say(arg1=None, arg2=None):
if arg1 == "on":
prof.settings_string_set("say", "enabled", "on")
prof.settings_set_string("say", "enabled", "on")
prof.cons_show("Say plugin enabled")
elif arg1 == "off":
prof.settings_string_set("say", "enabled", "off")
prof.settings_set_string("say", "enabled", "off")
prof.cons_show("Say plugin disabled")
elif arg1 == "active":
prof.settings_string_set("say", "enabled", "active")
prof.settings_set_string("say", "enabled", "active")
prof.cons_show("Say plugin enabled for active window only")
elif arg1 == "args":
if arg2 == None:
prof.cons_bad_cmd_usage("/say")
else:
prof.settings_string_set("say", "args", arg2)
prof.settings_set_string("say", "args", arg2)
prof.cons_show("Say plugin arguments set to: " + arg2)
elif arg1 == "clearargs":
prof.settings_string_set("say", "args", "")
prof.settings_set_string("say", "args", "")
prof.cons_show("Say plugin arguments cleared")
elif arg1 == "test":
if arg2 == None:
@@ -74,8 +71,8 @@ def _cmd_say(arg1=None, arg2=None):
else:
say(arg2)
else:
enabled = prof.settings_string_get("say", "enabled", "off")
args = prof.settings_string_get("say", "args", "")
enabled = prof.settings_get_string("say", "enabled", "off")
args = prof.settings_get_string("say", "args", "")
prof.cons_show("Say plugin settings:")
prof.cons_show("enabled : " + enabled)
if args != "":
@@ -89,7 +86,7 @@ def prof_init(version, status, account_name, fulljid):
"/say clearargs",
"/say test <message>"
]
description = "Read messages out loud"
description = "Read all messages out loud"
args = [
[ "on|off", "Enable/disable say for all windows" ],
[ "active", "Enable say for active window only" ],

276
src/ai.py
View File

@@ -1,276 +0,0 @@
"""AI Chat Plugin for CProof XMPP Client
This plugin enables interaction with AI models via a dedicated chat window using the
LiteLLM library, supporting providers like xAI (Grok) and OpenAI. Commands allow setting
default models, API tokens, starting chats, clearing windows, and correcting messages.
- Requires the `litellm` library (`pip install litellm`).
- Configure API tokens with zero-data-retention agreements for maximum privacy.
- Use models like xAI's Grok for minimal data retention.
- Chat history is not persisted locally beyond the active session.
See Also:
- LiteLLM documentation: https://docs.litellm.ai/docs/
- xAI API: https://x.ai/api
"""
import threading
import time
import json
import os
import litellm
import prof
from typing import Callable, Dict, List, Optional, Tuple
# Global settings
DEFAULT_MODEL_KEY = "default_model" # Changed to avoid hardcoding model
TOKEN_KEY = "ai_tokens"
CHAT_WIN_ID: Optional[int] = None
# Global message history: {win_id: [{"role": "user"|"assistant", "content": str}, ...]}
CHAT_HISTORY: Dict[str, List[Dict[str, str]]] = {}
# Output file for safe display
OUTPUT_FILE = "/tmp/ai_output.txt"
FILE_LOCK = threading.Lock()
# Privacy settings for LiteLLM
litellm.drop_params = True
litellm.set_verbose = False
# Disable printing in the console to avoid breaking profanity's outline
litellm.suppress_debug_info = True
def _get_tokens() -> Dict[str, str]:
"""Retrieve tokens from settings as a dictionary."""
tokens: Dict[str, str] = {}
token_strings = prof.settings_string_list_get("ai_plugin", TOKEN_KEY)
if not token_strings:
return tokens
for ts in token_strings:
if ":" in ts:
company, token = ts.split(":", 1)
tokens[company] = token
return tokens
def _save_tokens(tokens: Dict[str, str]) -> None:
"""Save tokens to settings as a list of 'company:token' strings."""
prof.settings_string_list_clear("ai_plugin", TOKEN_KEY)
for company, token in tokens.items():
prof.settings_string_list_add("ai_plugin", TOKEN_KEY, f"{company}:{token}")
def display_settings() -> None:
"""Show the current default model and registered tokens."""
model = get_default_model()
tokens = _get_tokens()
token_info = ", ".join([f"{company}: {token[:4]}..." for company, token in tokens.items()])
prof.cons_show(f"AI Settings: Default Model: {model}, Tokens: {token_info or 'None set'}")
def set_model(model) -> None:
"""Set the default model to be used for AI chats."""
set_default_model(model)
prof.cons_show(f"Default model set to: {model}")
def run_completion(window_id: str, model: str, history: list, outfile: str, tokens: dict, file_lock) -> None:
"""Run AI completion and write response to file."""
try:
response = litellm.completion(
model=model,
messages=history,
api_key=tokens.get(model.split("/")[0], None),
).choices[0].message.content
with file_lock:
with open(outfile, "a") as f:
json.dump({"win_id": window_id, "content": f"AI: {response}"}, f)
f.write("\n")
except Exception as e:
with file_lock:
with open(outfile, "a") as f:
json.dump({"win_id": window_id, "content": f"Error: {str(e)}"}, f)
f.write("\n")
def handler(win_id: str, message: str) -> None:
"""Process messages in a chat window using the model from the window title.
Args:
win_id: Identifier for the chat window, used to extract the model name.
message: The user's message to process.
"""
model = win_id.split(" - ", 2)[1]
prof.win_show(win_id, f"Me: {message}")
# Create a new history list for this call to avoid modifying shared state
current_history = CHAT_HISTORY.get(win_id, []) + [{"role": "user", "content": message}]
thread = threading.Thread(target=run_completion, args=(win_id, model, current_history, OUTPUT_FILE, _get_tokens(), FILE_LOCK))
thread.start()
def process_queued_outputs() -> None:
"""Process outputs from the file using prof.win_show."""
if os.path.exists(OUTPUT_FILE):
with open(OUTPUT_FILE, "r") as f:
lines = f.readlines()
outputs = []
for line in lines:
if line.strip():
try:
data = json.loads(line)
outputs.append((data["win_id"], data["content"]))
except json.JSONDecodeError:
pass
for win_id, content in outputs:
prof.win_show(win_id, content)
open(OUTPUT_FILE, "w").close() # truncate the file
def create_chat_window(model: str) -> str:
"""Create a new window for AI chat with the specified model."""
win_id = f"AI Chat - {model} - {int(time.time())}"
prof.win_create(win_id, handler)
prof.win_show(win_id, f"Chat started with {model}. Type a message to interact.")
prof.win_focus(win_id)
return win_id
def start_chat(model: Optional[str] = None) -> None:
"""Open a new chat window with the specified or default model."""
model = model if model else get_default_model()
create_chat_window(model)
prof.cons_show(f"Started AI chat with model: {model}")
def set_token(company: str, token: str) -> None:
"""Store an API token for a specific company."""
tokens = _get_tokens()
tokens[company] = token
_save_tokens(tokens)
prof.cons_show(f"Token set for {company}")
def clear_chat() -> None:
"""Notify that the current chat window is cleared."""
prof.cons_show("Sorry, current API doesn't support cleaning windows")
return
win_id = prof.get_current_win()
if win_id:
prof.win_show(win_id, "Chat cleared.")
else:
prof.cons_show("No active chat window.")
def correct_message(corrected_text: str) -> None:
"""Replace the latest user message in the current window's history and get AI response."""
prof.cons_show("Sorry, current API doesn't support correcting messages and getting current window")
return
# work in progress (get_current_win doesn't work)
win_id = prof.get_current_win()
if not win_id:
prof.cons_show("No active chat window. Use /ai start <model> first.")
return
title = prof.get_win_title(win_id)
if not title or not title.startswith("AI Chat - "):
prof.cons_show("Invalid chat window. Use /ai start <model> to create one.")
return
model = title.split(" - ", 2)[1]
history = CHAT_HISTORY.get(win_id, [])
user_messages = [msg for msg in history if msg["role"] == "user"]
if not user_messages:
prof.cons_show("No user messages in this chat to correct.")
return
# Replace the latest user message
for msg in history[::-1]:
if msg["role"] == "user":
msg["content"] = corrected_text
break
handler(win_id, corrected_text)
def get_default_model() -> str:
"""Retrieve the default model from settings, defaulting to gpt-5.0."""
return prof.settings_string_get("ai_plugin", DEFAULT_MODEL_KEY, "openai/gpt-5.0")
def set_default_model(model: str) -> None:
"""Save the default model to settings."""
prof.settings_string_set("ai_plugin", DEFAULT_MODEL_KEY, model)
def _cmd_ai(*args: Tuple[Optional[str], ...]) -> None:
"""Handle /ai commands for interacting with AI models.
Synopsis:
/ai
/ai set model <model>
/ai start [<model>]
/ai set token <company> <token>
/ai clear
/ai correct <message>
"""
if not args:
display_settings()
return
if args[0] == "set" and len(args) >= 2:
if args[1] == "model" and len(args) == 3:
set_model(args[2])
elif args[1] == "token" and len(args) == 3:
try:
company, token = args[2].split(" ", 1)
set_token(company, token)
except ValueError:
prof.cons_show("Invalid format, use: /ai set token <company> <token>")
else:
prof.cons_show("Invalid command, use: /ai set model|token")
elif args[0] == "start" and len(args) <= 3:
start_chat(args[1] if len(args) == 2 else None)
elif args[0] == "clear" and len(args) == 1:
clear_chat()
elif args[0] == "correct" and len(args) == 3:
correct_message(args[2])
else:
prof.cons_bad_cmd_usage("/ai")
def prof_init(version, status, account_name, fulljid):
"""Initialize the AI chat plugin and register commands."""
synopsis = [
"/ai",
"/ai set model <model>",
"/ai start [<model>]",
"/ai set token <company> <token>",
"/ai clear",
"/ai correct <message>",
]
description = """Interact with AI models via a chat interface using LiteLLM.
You can see the list of available models here: https://models.litellm.ai/"""
args = [
["", "Display current AI plugin settings"],
["set model <model>", "Set the default AI model (e.g., gpt-3.5-turbo)"],
["start [<model>]", "Start a new AI chat with the specified or default model"],
["set token <company> <token>", "Set an API token for a specific company"],
["clear", "Clear the current AI chat window"],
["correct <message>", "Correct a message using the current AI model"],
]
examples = [
"/ai",
"/ai set token openai sk-xxx",
"/ai set model gpt-4",
"/ai start xai/grok-4",
"/ai clear",
'/ai correct I has a error',
]
prof.register_command("/ai", 0, 3, synopsis, description, args, examples, _cmd_ai)
prof.completer_add("/ai", ["set", "start", "clear", "correct"])
prof.completer_add("/ai set", ["model", "token"])
prof.completer_add("/ai set model", ["openai/gpt-5", "xai/grok-3-mini"])
prof.completer_add("/ai start", ["xai/grok-4", "openai/gpt-4o"])
prof.completer_add("/ai set token", ["openai", "xai"])
prof.register_timed(process_queued_outputs, 1) # 1s interval to process AI message output

View File

@@ -1,549 +0,0 @@
"""
Antispam plugin for CProof.
Asks a question from new contacts to verify their humanity.
"""
import math
import prof
import re
import datetime
from uuid import uuid4
# Constants
_antispam_defaults = {
'msg': 'Please, solve the captcha',
'question': 'Where a bullet must be lodged in order for rifle to shoot?',
'answer': 'Chamber',
'donemsg': 'You passed antispam. Congratulations!',
'blockmsg': 'You\'ve been blocked. Try again {} minutes later',
'errormsg': 'Wrong answer. Please, try again. The question is',
'otrmsg': 'Please, turn off the OTR and then send the answer.',
'unbanmsg': 'Your ban has been lifted. You can try again.',
'maxtries': 5,
'blocktime': 5,
'debug': 'off',
'dry': 'off'
}
_time_format = "%d.%m.%Y %H:%M:%S"
_roster = []
_page_limit = 10
_egg_counter = 0
_as_settings_strings = ['msg', 'question', 'answer', 'donemsg', 'blockmsg', 'otrmsg', 'errormsg', 'unbanmsg', 'debug', 'dry']
_as_settings_ints = {'maxtries': {'min': 2, 'max': 1000}, 'blocktime': {'min': -1, 'max': 100000}}
_as_settings = [*_as_settings_strings, *_as_settings_ints]
# Handlers
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/antispam on|off",
"/antispam settings",
*[f"/antispam set {x} <msg>" for x in _as_settings_strings],
"/antispam set maxtries <tries>",
"/antispam set blocktime <minutes>",
"/antispam counter [<page|clear>]",
"/antispam history [<page|clear>]",
"/antispam blocklist [<page>|add|remove <jid or part of jid>|clear]",
"/antispam whitelist [<page>|add|remove <jid or part of jid>|clear]",
]
description = "Prevents spam messages by asking new contacts questions."
args = [
[ "set msg <msg>", "Set captcha message" ],
[ "set question <msg>", "Set custom captcha question" ],
[ "set answer <msg>", "Set answer to captcha question" ],
[ "set donemsg <msg>", "Set congratulation message for passing antispam" ],
[ "set blockmsg <msg>", "Set message that user is blocked. Use \"{}\" to show amounts of minutes before unlock. Example: You are banned. Unban in {} minutes." ],
[ "set otrmsg <msg>", "Set answer in case if unauthorized user tries to initiate OTR session." ],
[ "set errormsg <msg>", "Set prefix to answer in case if wrong answer given." ],
[ "set unbanmsg <msg>", "Set message given when temporary ban is finished. Set to \"None\" to prevent sending message." ],
[ "set maxtries <tries>", "Set maximum amount of tries before ban. Set -1 to make unlimited." ],
[ "set blocktime <minutes>", "Set time to block user for. Set -1 to make ban unlimited." ],
[ "set debug <on|off>", "Set debug mode on|off." ],
[ "set dry <on|off>", "Set dry mode on|off. In dry mode antispam doesn't block messages, mode is needed for debug use." ],
[ "counter [<page>|clear]", "Show or clear wrong answer counter" ],
[ "history [<page>|clear]", "Show or clear history" ],
[ "blocklist [add|remove <jid or part of jid>|clear]", "Show banned users, or add|remove user from ban" ],
[ "whitelist [add|remove <jid or part of jid>|clear]", "Whitelist users. Whitelist have priority over bans." ],
]
examples = [
"/antispam on",
"/antispam set msg Please, solve the captcha.",
"/antispam set question 2 + 2? (1984)",
"/antispam set answer 5",
"/antispam set donemsg You passed antispam. Now your messages are delivered.",
"/antispam set blockmsg You've been blocked by antispam. Please, try again later in {} minute(s).",
"/antispam set maxtries 5",
"/antispam set blocktime 69",
"/antispam blocklist clear",
"/antispam history 42",
]
prof.register_command("/antispam", 1, 40, synopsis, description, args, examples, _cmd_antispam)
prof.register_command("/as", 1, 40, _prep_alias(synopsis), description, args, _prep_alias(examples), _cmd_antispam)
prof.completer_add("/antispam",
[ "set", "history", "blocklist", "whitelist" ]
)
prof.completer_add("/antispam set",
_as_settings
)
prof.completer_add("/antispam history",
[ 'clear' ]
)
prof.completer_add("/antispam blocklist",
[ 'add', 'remove', 'clear' ]
)
prof.completer_add("/antispam whitelist",
[ 'add', 'remove', 'clear' ]
)
prof.register_timed(_update_banned_list, 60)
_force_update_roster((fulljid.split('/')[0] if fulljid else ''))
prof.cons_show("[AntiSpam] plugin started.")
if _is_dry_mode():
prof.cons_show("[AntiSpam] WARNING: dry-mode is on, no messages are going to be blocked.")
def prof_on_message_stanza_send(stanza):
body = _parse_tag_content(stanza, 'body')
if body and (to := _parse_attr(stanza, 'to').split('/', 1)[0]):
if not _is_authorized(to):
prof.settings_string_list_add("antispam", "authorized", to)
return stanza
def prof_on_presence_stanza_send(stanza):
if _parse_attr(stanza, 'type') == 'subscribe' and (to := _parse_attr(stanza, 'to').split('/', 1)[0]):
if not _is_authorized(to):
prof.settings_string_list_add("antispam", "authorized", to)
return stanza
def prof_on_message_stanza_receive(stanza) -> bool:
return _antispam_check_wrapper(stanza, 'msg')
def prof_on_presence_stanza_receive(stanza) -> bool:
return _antispam_check_wrapper(stanza, 'presence')
def prof_on_iq_stanza_receive(stanza) -> bool:
if 'jabber:iq:roster' in stanza and 'type="result"' in stanza:
_update_roster(stanza)
return _antispam_check_wrapper(stanza, 'iq')
# Custom functions
def _cmd_antispam(*args):
if not len(args):
_show_cmd_error()
if args[0] == "on":
prof.settings_boolean_set("antispam", "enabled", True)
prof.cons_show("Antispam is enabled.")
return
if args[0] == "off":
prof.settings_boolean_set("antispam", "enabled", False)
prof.cons_show("Antispam is disabled.")
return
if args[0] == "set":
if len(args) < 3:
_show_cmd_error('More arguments required')
return
key = args[1]
if key not in _as_settings:
_show_cmd_error(f'Wrong setting. You can use only {_as_settings}')
return
if args[2] == "default":
_set_default_values(key)
return
if _set_values(key, args[2:]):
prof.cons_show(f'Antispam setting {key} was successfully changed to "{" ".join(args[2:])}"')
else:
_show_cmd_error("Something went wrong, setting wasn't changed")
return
if args[0] == "settings":
prof.cons_show("===== Antispam settings =====")
for key in _as_settings:
value = _get_string_setting(key) if key in _as_settings_strings else _get_int_setting(key)
fvalue = f'"{value}"' if key in _as_settings_strings else value
prof.cons_show(f'{key} is {fvalue}')
return
if args[0] in ["counter", "history", "authorized"]:
return _cmd_show_or_clear(args)
if args[0] == "blocklist":
return _cmd_as_blocklist(args)
if args[0] == "whitelist":
return _cmd_as_whitelist(args)
if args[0] == "egg":
global _egg_counter
if _egg_counter == 10:
prof.cons_show(f"Do you know that it's an antispam plugin and not an egg farm? No more eggs for you!")
return
_egg_counter += 1
prof.cons_show(f"Egg number {_egg_counter} has been planted.")
if _egg_counter == 5:
prof.cons_show(f"What do you need so many eggs for?")
return
if args[0] == "roster":
prof.cons_show("\n".join(_roster))
return
prof.cons_bad_cmd_usage('/antispam')
def _cmd_show_or_clear(args):
if len(args) == 1:
return _show_page(args[0])
if len(args) == 2:
if args[1] == "clear":
if prof.settings_string_list_clear("antispam", args[0]):
prof.cons_show(f'{args[0].capitalize()} has been cleared.')
else:
prof.cons_show(f'{args[0].capitalize()} is already empty.')
return
return _show_page(args[0], args[1])
prof.cons_bad_cmd_usage('/antispam')
def _cmd_as_blocklist(args):
if len(args) < 3:
return _cmd_show_or_clear(args)
if args[1] == "add":
for jid in args[2:]:
ban_time = _ban_user(jid, full_jid=False, time_limited=False)
prof.cons_show(f'JID "{jid}" has been banned {ban_time}.')
elif args[1] == "remove":
for jid in args[2:]:
if _unban_user(jid):
prof.cons_show(f'JID "{jid}" has been unbanned.')
else:
prof.cons_show(f'JID "{jid}" has not been found in the blocklist.')
def _cmd_as_whitelist(args):
if len(args) < 3:
return _cmd_show_or_clear(args)
if args[1] == "add":
for jid in args[2:]:
prof.settings_string_list_add("antispam", "whitelist", jid)
prof.cons_show(f'JID "{jid}" has been whitelisted.')
elif args[1] == "remove":
for jid in args[2:]:
if prof.settings_string_list_remove("antispam", "whitelist", jid):
prof.cons_show(f'JID "{jid}" has been removed from the whitelist.')
else:
prof.cons_show(f'JID "{jid}" has not been found in the whitelist.')
def _show_cmd_error(error="", user_mistake=True) -> None:
"""Used to shows str:error when user typed wrong input"""
if user_mistake:
prof.cons_bad_cmd_usage('/antispam')
if error:
prof.cons_show(error)
def _set_default_values(key) -> bool:
default_value = _antispam_defaults[key]
if (key in _as_settings_strings):
prof.settings_string_set("antispam", key, default_value)
elif (key in _as_settings_ints):
prof.settings_int_set("antispam", key, default_value)
prof.cons_show(f'Antispam setting {key} was successfully set to its default ({default_value}).')
def _set_values(key, value) -> bool:
"""Returns Bool True if values has been set, False is value is invalid"""
value = ' '.join(value)
if key in _as_settings_strings:
prof.settings_string_set("antispam", key, value)
return True
if key in _as_settings_ints:
min_val, max_val = _as_settings_ints[key]['min'], _as_settings_ints[key]['max']
if not _represents_int_in_range(value, min_val, max_val):
prof.cons_show(f'Value of {key} must be an integer between {min_val} and {max_val}!')
return False
prof.settings_int_set("antispam", key, int(value))
return True
return False
def _parse_tag_content(stanza, tag):
tmp = re.search(f"<{tag}.*?>([\S\s]*?)</{tag}>", stanza) # don't blame me, it works :D
try:
return tmp.group(1) if tmp else ""
except IndexError as e:
return ""
def _parse_attr(stanza, attribute) -> str:
"""Parse attribute from the first tag"""
tmp = re.search(f'^\s*<[^>]+{attribute}="([^"]+)"', stanza)
try:
return tmp.group(1) if tmp else ""
except IndexError as e:
return ""
def _parse_sender(stanza) -> str:
"""Returns sender from stanza or empty string if not found"""
if "<forwarded" in stanza:
stanza = _parse_tag_content(stanza, "forwarded")
return _parse_attr(stanza, "from").split('/', 1)[0]
def _parse_id(stanza) -> str:
"""Returns id from stanza or empty string if not found"""
return _parse_attr(stanza, "id")
def _is_dry_mode() -> bool:
return _get_string_setting("dry") == "on"
def _antispam_check_wrapper(stanza, stanza_type) -> bool:
result = _antispam_check(stanza, stanza_type)
dry_mode = _is_dry_mode()
if not result:
if _get_string_setting("debug") == "on" or dry_mode:
prof.cons_show(f"[AntiSpam{' (DryMode)' if dry_mode else ''}] Blocked stanza: {stanza}")
else:
prof.log_debug(f"[AntiSpam] Blocked stanza: {stanza}")
return result or _is_dry_mode()
def _antispam_check(stanza, stanza_type) -> bool:
"""Returns Bool True if message is not spam, False if message should be blocked (spam/unverified sender)"""
sender = _parse_sender(stanza)
stanza_id = _parse_id(stanza)
if not sender and "from=" in stanza:
prof.log_warning(f"In the following stanza From is present, but not catched: \n{stanza}")
if not _is_activated():
return True
if _is_whitelisted(sender) or _is_in_roster(sender):
return True
if _is_banned(sender):
return False
if _is_authorized(sender):
return True
if not _roster:
prof.log_debug(f"[AntiSpam] Passed message because roster was empty. Stanza: {stanza}")
return True
if stanza_type == "presence":
if re.search('^\s*<presence.+type="subscribe".+>', stanza):
_send_stanza(f'<presence type="unsubscribed" to="{sender}" />')
_send_stanza(f'<message type="chat" to="{sender}"><subject>{_get_string_setting("msg")}</subject><body>{_get_string_setting("question")}</body></message>')
_counter_inc(sender)
elif stanza_type == "iq":
if re.search('^\s*<iq[^>]+type="set"', stanza):
to = f'to="{sender}" ' if sender else ''
_send_stanza(f'<iq type="error" id="{stanza_id}" {to}/>')
return False
return True
elif stanza_type == "msg":
if _parse_attr(stanza, "type") == "error":
prof.settings_string_list_add("antispam", "history", f"{_get_time()}|{sender}|{_parse_tag_content(stanza, 'body')}")
return False
if not _is_correct_answer(stanza):
if (is_otr_requested := "OTRv2" in stanza):
_send_stanza(f'<message type="chat" to="{sender}"><body>{_get_string_setting("otrmsg")}</body></message>')
prof.settings_string_list_add("antispam", "history", f"{_get_time()}|{sender}|{_parse_tag_content(stanza, 'body')}")
wrong_tries = _get_counter(sender)
subject = f'<subject>{_get_string_setting("msg")}</subject>' if wrong_tries == 0 else ''
wrong_answer = _get_string_setting("errormsg")+'\n' if wrong_tries > 0 and not is_otr_requested else ''
_send_stanza(f'<message type="chat" to="{sender}">{subject}<body>{wrong_answer}{_get_string_setting("question")}</body></message>')
_counter_inc(sender)
return False
prof.settings_string_list_add("antispam", "authorized", sender)
_send_stanza(f'<message type="chat" to="{sender}"><body>{_get_string_setting("donemsg")}</body></message>')
return False
def _force_update_roster(myjid=None) -> None:
global _roster
if not _roster and myjid:
_roster = [myjid]
prof.send_stanza(f'<iq id="{uuid4()}" type="get"><query xmlns="jabber:iq:roster"/>\</iq>')
return
def _update_roster(stanza) -> None:
global _roster
_roster = [
*re.findall('<iq[^>]+to="([^"/]+)["/]', stanza),
*[x for x,y in re.findall('<item[^>]+jid="([^"]+?)"[^>]+subscription="([^"]+?)"', stanza) if y == "both" or y == "to"],
*[x for y,x in re.findall('<item[^>]+subscription="([^"]+?)"[^>]+jid="([^"]+?)"', stanza) if y == "both" or y == "to"],
]
def _is_activated() -> bool:
return prof.settings_boolean_get("antispam", "enabled", True)
def _update_banned_list() -> None:
ban_list = prof.settings_string_list_get("antispam", "blocklist") or []
for banned_user in ban_list:
if not (tmp := _unpack_banned_user(banned_user)):
continue
ban_time, ban_type, jid = tmp
if ban_time != "Forever" and not _is_future_time(ban_time):
unbanmsg = _get_string_setting("unbanmsg")
if unbanmsg != "None":
_send_stanza(f'<message type="chat" to="{jid}"><body>{unbanmsg}</body></message>')
prof.settings_string_list_remove("antispam", "blocklist", banned_user)
return
def _unpack_banned_user(banned_user):
"""Returns None or Tuple of banned user info in (time_banned, ban_type, jid) format"""
tmp = banned_user.split("|", 2)
if len(tmp) != 3:
prof.cons_show(f"Problem on banned user unpacking: \"{banned_user}\". Deleting...")
prof.settings_string_list_remove("antispam", "blocklist", banned_user)
return
return tmp
def _is_in_roster(jid) -> bool:
return jid in _roster
def _is_authorized(jid) -> bool:
return jid in (prof.settings_string_list_get("antispam", "authorized") or [])
def _is_banned(jid) -> bool:
ban_list = prof.settings_string_list_get("antispam", "blocklist") or []
for banned_user in ban_list:
if not (tmp := _unpack_banned_user(banned_user)):
continue
ban_time, ban_type, bjid = tmp
if ban_type == "F": # On full JID ban (e.g. "user@example.com")
if jid == bjid and (ban_time == "Forever" or _is_future_time(ban_time)):
return True
else: # On partial JID ban (e.g. "@example.com", "spammer")
if bjid in jid and (ban_time == "Forever" or _is_future_time(ban_time)):
return True
return False
def _is_whitelisted(jid) -> bool:
wl_users = (prof.settings_string_list_get("antispam", "whitelist") or [])
return any(wj in jid for wj in wl_users)
def _get_validate_count(c):
tmp = c.split('|', 1)
if len(tmp) != 2:
prof.log_warning(f'Invalid counter entry: "{c}". Removing...')
prof.settings_string_list_remove("antispam", "counter", c)
return
return int(tmp[0]), tmp[1]
def _counter_inc(jid) -> None:
counter = prof.settings_string_list_get("antispam", "counter") or []
for c in counter:
if not (tmp := _get_validate_count(c)):
continue
count, cjid = tmp
if jid == cjid:
prof.settings_string_list_remove("antispam", "counter", c)
prof.settings_string_list_add("antispam", "counter", f"{count+1}|{jid}")
if count % _get_int_setting("maxtries") == 0:
ban_time = _ban_user(jid)
blockmsg = _get_string_setting("blockmsg").replace('{}', str(ban_time))
_send_stanza(f'<message type="chat" to="{jid}"><body>{blockmsg}</body></message>')
return
prof.settings_string_list_add("antispam", "counter", f"1|{jid}")
def _get_counter(jid) -> int:
counter = prof.settings_string_list_get("antispam", "counter") or []
for c in counter:
if not (tmp := _get_validate_count(c)):
continue
count, cjid = tmp
if jid == cjid:
return count
return 0
def _show_page(table, page_num=1) -> None:
"""Shows a page from table N (table is fetched from string_list setting with name) to plugin user through cons_show."""
if not _represents_int_in_range(page_num, 1):
prof.cons_show("Use correct page number. It must be integer from 1 to 10000.")
return
page_num = int(page_num)
page = page_num-1
table_content = prof.settings_string_list_get("antispam", table) or []
table_size = len(table_content)
total_pages = math.ceil(table_size/_page_limit)
if not table_content:
return prof.cons_show(f"Nothing to display, {table} is empty")
if page_num > total_pages:
return prof.cons_show(f"Page {page_num} does not exist. There are only {total_pages} pages")
page_content = table_content[page*_page_limit:page_num*_page_limit]
if not page_content:
return prof.cons_show(f"Nothing to display, {table} on Page {page_num} is empty")
for x in page_content:
formatting = (lambda x: x)
prof.cons_show(formatting(x))
prof.cons_show(f"Page {page_num}/{total_pages}. Currently {table_size} entries.")
def _ban_user(jid, full_jid=True, time_limited=True, custom_time_limit=None):
"""Adds user to ban list"""
ban_type = "F" if full_jid else "P"
if time_limited and (ban_time := custom_time_limit if custom_time_limit else _get_int_setting("blocktime")) != -1:
ban_time_formatted = (datetime.datetime.now() + datetime.timedelta(minutes=ban_time)).strftime("%d.%m.%Y %H:%M:%S")
_unban_user(jid)
prof.settings_string_list_add("antispam", "blocklist", f"{ban_time_formatted}|{ban_type}|{jid}")
return ban_time
prof.settings_string_list_add("antispam", "blocklist", f"Forever|{ban_type}|{jid}")
return "forever"
def _unban_user(jid):
"""Removes user from ban list. True on success, otherwise false."""
ban_list = prof.settings_string_list_get("antispam", "blocklist") or []
for banned_user in ban_list:
if not (tmp := _unpack_banned_user(banned_user)):
continue
ban_time, ban_type, bjid = tmp
if bjid == jid:
return prof.settings_string_list_remove("antispam", "blocklist", banned_user)
return False
# Settings utils
def _get_string_setting(setting) -> str:
return prof.settings_string_get("antispam", setting, _antispam_defaults[setting])
def _get_int_setting(setting) -> int:
return prof.settings_int_get("antispam", setting, _antispam_defaults[setting])
# Utils
def _prep_alias(replace_list):
return [x.replace("/antispam", "/as") for x in replace_list]
def _is_correct_answer(stanza):
correct_answer = _get_string_setting('answer')
answer = _parse_tag_content(stanza, "body")
if not answer:
prof.log_warning(f'Invalid mesage stanza. Ignoring user input. Stanza: {stanza}')
return False
return answer.strip().lower() == correct_answer.strip().lower()
def _is_future_time(time) -> bool:
"""Checks formatted time and returns True if time from parameter is in the future"""
try:
ptime = datetime.datetime.strptime(time, _time_format)
except ValueError as e:
prof.log_error(f"Exception raised when time was compared (input time: {time})\n{e}")
return True
return ptime > datetime.datetime.now()
def _get_time():
"""Returns current time formatted"""
return datetime.datetime.now().strftime(_time_format)
def _represents_int_in_range(s, num_min=-1, num_max=10000) -> bool:
"""Returns True if passed number is integer and it is in provided range (from -1 to 10000 by default)"""
try:
num = int(s)
return num >= num_min and num <= num_max
except ValueError:
return False
else:
return True
def _send_stanza(stanza):
if not _is_dry_mode():
return prof.send_stanza(stanza)

View File

@@ -1,110 +0,0 @@
"""
autocorrector - Replace common typos
My typing style is such a mess. But I invest more time in correcting it
programmatically than training myself to type better. Maybe thats the
problem? Anyway, here is a plugin for all keyboard messies like me.
"""
import prof
def prof_pre_chat_message_send(barejid, message):
enabled = prof.settings_string_get("autocorrector", "enabled", "on")
if enabled == "off":
return message
else:
replacements = prof.settings_string_list_get("autocorrector","replacements")
for replacement in replacements:
repl=replacement.split(':')
message=message.replace(repl[0],repl[1])
return message
def prof_pre_priv_message_send(barejid, nick, message):
enabled = prof.settings_string_get("autocorrector", "enabled", "on")
if enabled == "off":
return message
else:
replacements = prof.settings_string_list_get("autocorrector","replacements")
for replacement in replacements:
repl=replacement.split(':')
message=message.replace(repl[0],repl[1])
return message
def prof_pre_room_message_send(barejid, message):
enabled = prof.settings_string_get("autocorrector", "enabled", "on")
if enabled == "off":
return message
else:
replacements = prof.settings_string_list_get("autocorrector","replacements")
for replacement in replacements:
repl=replacement.split(':')
message=message.replace(repl[0],repl[1])
return message
def _cmd_autocorrector(arg1=None, arg2=None):
if arg1 == "on":
prof.settings_string_set("autocorrector", "enabled", "on")
prof.cons_show("autocorrector plugin enabled")
elif arg1 == "off":
prof.settings_string_set("autocorrector", "enabled", "off")
prof.cons_show("autocorrector plugin disabled")
elif arg1 == "clearall":
prof.settings_string_list_clear("autocorrector","replacements")
prof.cons_show("autocorrector plugin replacement list cleared.")
elif arg1 == "showlist":
replacements = prof.settings_string_list_get("autocorrector","replacements")
prof.cons_show("Replacement string list:")
for replacement in replacements:
prof.cons_show(replacement)
elif arg1 == "add":
if arg2 == None:
prof.cons_bad_cmd_usage("/autocorrector")
else:
if len(arg2.split(':')) == 2:
prof.settings_string_list_add("autocorrector","replacements",arg2)
prof.cons_show("Replacement String added.")
else:
prof.cons_show("Invalid replacement String. Please use the following format to specify replacements: pattern:replacement.")
elif arg1 == "rm":
if arg2 == None:
prof.cons_bad_cmd_usage("/autocorrector")
else:
if len(arg2.split(':')) == 2:
prof.settings_string_list_remove("autocorrector","replacements",arg2)
prof.cons_show("Replacement String removed.")
else:
prof.cons_show("Invalid replacement String. Please use the following format to specify replacements: pattern:replacement.")
else:
enabled = prof.settings_string_get("autocorrector", "enabled", "on")
if enabled == "off":
prof.cons_show("autocorrector inactive.")
else:
prof.cons_show("autocorrector active.")
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/autocorrector on|off",
"/autocorrector showlist",
"/autocorrector clearall",
"/autocorrector add"
"/autocorrector rm"
]
description = "Messy typist autocorrector. Replaces common typos in your outgoing messages."
args = [
[ "on|off", "Enable/disable correction for all windows" ],
[ "showlist", "show typolist." ],
[ "add", "Add entry to typolist. Use pattern:replacement as format." ],
[ "rm", "Remove entry from typolist. Use pattern:replacement as format." ],
[ "clearall", "Remove all entries from typolist" ]
]
examples = [
"/autocorrector on",
"/autocorrector add tpyo:typo",
"/autocorrector add tset:test",
"/autocorrector rm tset:test",
"/autocorrector showlist",
]
prof.settings_string_set("autocorrector", "enabled", "on")
prof.register_command("/autocorrector", 0, 2, synopsis, description, args, examples, _cmd_autocorrector)
prof.completer_add("/autocorrector", [ "on", "off", "clearall", "add", "rm", "showlist" ])

View File

@@ -1,121 +0,0 @@
"""
Open the last link received in a chat or room window using the systems default browser
"""
import prof
import os
import webbrowser
import re
_links = {}
_lastlink = {}
def _cmd_browser(url):
global _lastlink
link = None
# use arg if supplied
if (url is not None):
link = url
else:
jid = prof.get_current_recipient()
room = prof.get_current_muc()
# check if in chat window
if (jid is not None):
# check for link from recipient
if jid in _lastlink.keys():
link = _lastlink[jid]
else:
prof.cons_show("No links found from " + jid)
# check if in muc window
elif (room is not None):
if room in _lastlink.keys():
link = _lastlink[room]
else:
prof.cons_show("No links found from " + room)
# not in chat/muc window
else:
prof.cons_show("You must supply a URL to the /browser command")
# open the browser if link found
if (link is not None):
prof.cons_show("Opening " + link + " in browser")
_open_browser(link)
def _open_browser(url):
savout = os.dup(1)
saverr = os.dup(2)
os.close(1)
os.close(2)
os.open(os.devnull, os.O_RDWR)
try:
webbrowser.open(url, new=2)
finally:
os.dup2(savout, 1)
os.dup2(saverr, 2)
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/browser",
"/browser <url>"
]
description = "View a URL in the systems default browser. If no argument is supplied, the last URL in the current chat or room will be used. " \
"Tab autocompletion will go through all previous links in the current chat/room"
args = [
[ "<url>", "URL to open in the browser" ]
]
examples = [
"/browser http://jabber.space/"
]
prof.register_command("/browser", 0, 1, synopsis, description, args, examples, _cmd_browser)
def prof_on_chat_win_focus(barejid):
prof.completer_clear("/browser")
if barejid in _links:
prof.completer_add("/browser", _links[barejid])
def prof_on_room_win_focus(barejid):
prof.completer_clear("/browser")
if barejid in _links:
prof.completer_add("/browser", _links[barejid])
def _process_message(barejid, current_jid, message):
links = re.findall(r'(https?://\S+)', message)
if (len(links) > 0):
if barejid not in _links:
_links[barejid] = []
# add to list of links for barejid
for link in links:
if link not in _links[barejid]:
_links[barejid].append(link)
# add to autocompleter if message for current window
if current_jid == barejid:
prof.completer_add("/browser", _links[barejid])
# set last link for barejid
_lastlink[barejid] = links[len(links)-1]
def prof_post_chat_message_display(barejid, resource, message):
current_jid = prof.get_current_recipient()
_process_message(barejid, current_jid, message)
def prof_post_room_message_display(barejid, nick, message):
current_jid = prof.get_current_muc()
_process_message(barejid, current_jid, message)
def prof_on_room_history_message(barejid, nick, message, timestamp):
current_jid = prof.get_current_muc()
_process_message(barejid, current_jid, message)

View File

@@ -1,7 +0,0 @@
moj
moj.exe
*.o
*.so
gen/lookup.c
gen/emoji.json
gen/emoji.csv

View File

@@ -1,15 +0,0 @@
ISC License
Copyright (c) 2018, Adrian "vifino" Pistol
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,68 +0,0 @@
# moj makefile
CC ?= cc
MOJILIST=https://raw.githubusercontent.com/milesj/emojibase/master/packages/data/en/raw.json
# Do not touch.
all: moj libmoj.so
# Generate lookup lists
gen/emoji.json:
mkdir -p gen
wget -O $@ ${MOJILIST}
gen/emoji.csv: gen/emoji.json
jq -r '.[] | select(.emoji) | [.emoji] + .shortcodes | @csv' -- \
gen/emoji.json | \
sed 's/^"//;s/"$$//' | awk -f src/process.awk > $@
gen/emoji.gperf: src/gen_gperf.sh gen/emoji.csv
./src/gen_gperf.sh > ./gen/emoji.gperf
gen/emoji_list.h: src/gen_list.sh gen/emoji.csv
./src/gen_list.sh > $@
gen/lookup.c: gen/emoji.gperf
gperf --global-table --readonly-tables \
--struct-type --includes \
--compare-lengths --compare-strncmp \
--ignore-case -Q emoji_spool \
-H emoji_name_hash -N emoji_lookup_ref -m 3 \
--output-file=$@ $^
# Compile our code.
src/moj.o: gen/lookup.c gen/emoji_list.h src/moj.c
$(CC) -c ${CFLAGS} ${CPPFLAGS} -o $@ src/moj.c
libmoj.so: src/moj.o
$(CC) -shared -fPIC ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $<
moj: src/main.c src/moj.o
$(CC) ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $^
# Profanity plugin.
profanity_emoji.so: src/profanity_emoji.c src/moj.o
$(CC) -shared -fPIC ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $^
profanity: profanity_emoji.so
# Install
install: moj
install -d $(DESTDIR)/$(PREFIX)/bin
install moj $(DESTDIR)/$(PREFIX)/bin
install-devel: libmoj.so
install -d $(DESTDIR)/$(PREFIX)/lib
install libmoj.so $(DESTDIR)/$(PREFIX)/lib
install -d $(DESTDIR)/$(PREFIX)/include
cp src/moj.h $(DESTDIR)/$(PREFIX)/include/libmoj.h
uninstall:
rm -f $(DESTDIR)/$(PREFIX)/bin/moj
uninstall-devel:
rm -f $(DESTDIR)/$(PREFIX)/lib/libmoj.so $(DESTDIR)/$(PREFIX)/include/libmoj.h
# Cleanup
clean:
rm -f moj gen/lookup.c src/*.o profanity_emoji.so

View File

@@ -1,28 +0,0 @@
# moj
'moj is for those of us that want something more minimal than emojify.
Reasons to use moj instead of emojify:
- only 3 letters(!), huge reduction compared to 7 of emojify.
- written in C, compile time hash table generation, no interpreted language.
- VERY FAST. How does 0.002s to generate the average commit messages of this project sound? This is on a ThinkPad T61. A potato.
- data not mixed with code, only generated data/glue and handwritten code. (seperate files)
# Requirements
- Make (compile time)
- C99 compiler(?). (compile time)
- gperf (compile time)
No runtime deps.
To regen the emoji list you also need:
- wget
- jq
- awk
# Credits
[milesj/emojibase](https://github.com/milesj/emojibase) for the JSON Emoji list. Many thanks
vifino for doing everything else.
# License
ISC.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
#!/bin/sh
# Generator for the hash list.
# This is unquestionably magic.
set -e
cat <<HEADER
%{
// THIS FILE IS GENERATED BY src/gen_list.sh.
// DO NOT TOUCH HERE.
%}
struct emoji_ref { char* name; unsigned int ref; };
%%
HEADER
cat gen/emoji.csv | sed 's/"//g' | \
awk -F',' '{ count++; for (i = 2; i <= NF; i++) printf "%s, %i\n", $i, count;}'

View File

@@ -1,26 +0,0 @@
#!/bin/sh
# Generator for the actual emoji list.
set -e
EMOJIS=$(wc -l < gen/emoji.csv)
cat <<HEADER
// THIS FILE IS GENERATED BY src/gen_list.sh.
// DO NOT TOUCH HERE.
#define NUM_EMOJI $EMOJIS
static const char* emoji_list[$EMOJIS] = {
HEADER
gen() {
printf "\t\"%s\",\n" $1
}
cat gen/emoji.csv | sed 's/,/ /g' | while read -r input; do
gen $(eval "echo $input")
done
cat <<FOOTER
};
FOOTER

View File

@@ -1,124 +0,0 @@
// moj.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include "moj.h"
#define MODE_NORMAL 0
#define MODE_LOOKUP 1
#define MODE_USELIBRARY 2
void usage(char* name, int fail) {
FILE* f = stdout;
if (fail) f = stderr;
fprintf(f, "Usage: %s [-s sep] [-e] <:emoji: text>\n"
" -e: lookup mode - interpret arguments as emoji names\n"
" -s <sep>: use custom seperator\n", name);
exit(fail);
}
int main(int argc, char* argv[]) {
if (argc < 2) {
usage(argv[0], 1);
}
// parse opts
int opt;
int mode = MODE_NORMAL;
char* sep = NULL;
while ((opt = getopt(argc, argv, "hels:")) != -1) {
switch (opt) {
case 'h':
usage(argv[0], 0);
break;
default:
case '?':
usage(argv[0], 1);
break;
case 'e':
mode = MODE_LOOKUP;
break;
case 's':
sep = optarg;
break;
case 'l':
mode = MODE_USELIBRARY;
break;
}
}
if (optind >= argc)
usage(argv[0], 1);
int argi;
switch (mode) {
case MODE_LOOKUP:
for (argi = optind; argi < argc; argi++) {
char* found = moj_lookup(argv[argi], strlen(argv[argi]));
if (found) {
printf("%s%s", found, sep ? sep : "\n");
} else {
printf("Couldn't find %s. :(\n", argv[argi]);
return 1;
}
}
if (sep) printf("\n");
break;
case MODE_NORMAL:
for (argi = optind; argi < argc; argi++) {
char* s = argv[argi];
char c;
int ci;
int len = strlen(s);
int start = 0;
int attention = 0;
for (ci = 0; ci <= len; ci++) {
c = s[ci];
if (attention) { // in :
if (c == ' ') {
attention = 0;
s[ci] = 0;
fprintf(stdout, "%s ", s + start);
} else if (c == ':') {
// check if start to ci - start -1 is a valid emoji.
s[ci] = 0;
char* found = moj_lookup(s + start + 1, ci - start - 1);
if (found) {
fprintf(stdout, "%s", found);
} else {
// not an emoji? oops.
fprintf(stdout, "%s:", s + start);
start = ci;
// the next might be one, though..
}
attention = 0;
}
} else if (c == ':' && ci < len) {
attention = 1;
start = ci;
} else {
putc(c, stdout);
}
}
if (attention && start < len)
fprintf(stdout, "%s", s + start + 1);
if (argi < (argc - 1))
fputs(sep ? sep : " ", stdout);
}
fprintf(stdout, "\n");
break;
case MODE_USELIBRARY:
for (argi = optind; argi < argc; argi++) {
char* rep = moj_replace(argv[argi], strlen(argv[argi]));
printf("%s\n", rep);
free(rep);
}
break;;
}
return 0;
}

View File

@@ -1,98 +0,0 @@
// moj: Small Emoji library
// Based on our generated things.
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "../gen/lookup.c"
#include "../gen/emoji_list.h"
const char* moj_lookup(const char* name, size_t len) {
const struct emoji_ref *found = emoji_lookup_ref(name, len);
if (found) {
return emoji_list[found->ref - 1];
}
return NULL;
}
char* moj_replace(const char* text, size_t len) {
if (!len)
return strdup(text);
char c;
// count size delta to alloc a string just the right size.
int size_delta = 0;
size_t ci;
size_t start = 0;
size_t strend;
int attention = 0;
for (ci = 0; ci <= len; ci++) {
c = text[ci];
if (attention) { // in :
if (c == ' ') {
attention = 0;
} else if (c == ':') {
// check if start to ci - start -1 is a valid emoji.
size_t elen = ci - start - 1;
const char* found = moj_lookup(text + start + 1, elen);
if (found) {
size_delta += elen - strlen(found);
} else {
start = ci;
}
attention = 0;
}
} else if (c == ':' && ci < len) {
attention = 1;
start = ci;
}
}
// allocate buffer the right size.
size_t realsz = len + size_delta;
char* buf = malloc(realsz + 1);
if (!buf)
return NULL;
start = 0;
attention = 0;
size_t offset = 0;
for (ci = 0; ci < len; ci++) {
c = text[ci];
if (attention) { // in :
if (c == ' ') {
attention = 0;
memcpy(buf + offset, text + start, ci - start + 1);
offset += ci - start + 1;
} else if (c == ':') {
// check if start to ci - start -1 is a valid emoji.
const char* found = moj_lookup(text + start + 1, ci - start - 1);
if (found) {
offset += sprintf(buf + offset, "%s", found);
} else {
// not an emoji? oops.
memcpy(buf + offset, text + start, ci - start + 1);
offset += ci - start + 1;
start = ci;
// the next might be one, though..
}
attention = 0;
}
} else if (c == ':' && ci < len) {
attention = 1;
start = ci;
} else {
buf[offset] = c;
offset++;
}
}
if (attention) {
memcpy(buf + offset, text + start, ci - start);
offset += ci - start;
}
buf[realsz] = 0;
return buf;
}

View File

@@ -1,5 +0,0 @@
// moj: a small emoji library and cli.
#include <stddef.h>
char* moj_lookup(const char* name, size_t len);
char* moj_replace(const char* name, size_t len);

View File

@@ -1,93 +0,0 @@
#!/usr/bin/awk -f
# This file takes in our CSV, emits simpler stuff.
# It also fixes up some things.
# Bunch of convenience functions.
function didsee(name) {
if (name in seen)
return 1;
return 0;
}
function hasnew() {
for (i = 2; i <= NF; i++)
if (!didsee($i))
return 1;
return 0;
}
function newname(name) {
if (didsee(name))
return 0;
seen[name] = 1;
return 1;
}
function char(str, idx) {
return substr(str, idx, 1);
}
function prefixed(name, prefixes) {
for (prefix in prefixes)
if (index(name, prefixes[prefix]) == 1)
return 1;
return 0;
}
function inarray(name, array) {
for (i in array)
if (array[i] == name)
return 1;
return 0;
}
function noing(name) {
# yep thats the name.
len = length(name);
if (len > 5 && substr(name, len - 2, 3) == "ing" && !prefixed(name, persons) && !inarray(name, exemptions)) {
if (char(name, len - 4) == char(name, len - 3)) {
# double letter combo, gg, nn, etc..
short = substr(name, 0, len - 4);
} else {
short = substr(name, 0, len - 3);
}
if (newname(short))
printf ",%s", short;
}
}
BEGIN {
FS="\",\""
# I hate this.
persons[1] = "person_";
persons[2] = "women_";
persons[3] = "woman_"
persons[4] = "men_";
persons[5] = "man_";
# I hate this even more.
exemptions[1] = "bowing";
exemptions[2] = "golfing";
}
{
if (hasnew())
printf "%s", $1;
for (i = 2; i <= NF; i++) {
if (newname($i)) {
printf ",%s", $i;
noing($i);
if (index($i, "person_") == 1) {
replaced = substr($i, 8);
if (newname(replaced)) {
printf ",%s", replaced;
}
noing(replaced);
}
}
}
printf "\n";
}

View File

@@ -1,10 +0,0 @@
// moj: CProof plugin
// Intercepts text and replaces :emoji:
#include <profapi.h>
#include <string.h>
#include "moj.h"
char* prof_pre_chat_message_send(const char* const barejid, const char* message) {
return moj_replace(message, strlen(message)); // yep that is it.
}

View File

@@ -1,33 +0,0 @@
"""
Converts smileys in a text to their Unicode character representations.
"""
import prof
EMOTICON_UNICODE_DICT = {':)': '😃', ':(': '☹️', ':D': '😃', ':O': '😲', ';)': '😉', ':-)': '😊', ':-D': '😁', ':-(': '😞',
';-)': '😜', '<3': '❤️', ':P': '😛', ':|': '😐', ':/': '😕', ':*': '😘', ':]': '😃', ':[': '😢',
':-|': '😐', ':\\': '😕', ':3': '😺', 'O:)': '😇', '>:(': '😠', ':poop:': '💩', ':fire:': '🔥',
':heart:': '❤️', ':thumbsup:': '👍', ':star:': '', ':beer:': '🍺', ':pizza:': '🍕', ':sunglasses:': '😎',
':ok_hand:': '👌', ':heart_eyes:': '😍'}
def replace_emoticons(text, emoticon_dict):
for emoticon, unicode_char in emoticon_dict.items():
text = text.replace(emoticon, unicode_char)
return text
def convert_emoticons(input_str):
return replace_emoticons(input_str, EMOTICON_UNICODE_DICT)
def prof_pre_chat_message_display(barejid, resource, message):
return convert_emoticons(message)
def prof_pre_room_message_display(barejid, nick, message):
return convert_emoticons(message)
def prof_pre_priv_message_display(barejid, nick, message):
return convert_emoticons(message)

View File

@@ -1,167 +0,0 @@
"""
CProof plugin to read full chat history with custom editor using /hh command.
"""
import os
import prof
import re
import sqlite3
from pathlib import Path
_editor = ""
_current_user = ""
_logs_dir = ""
_db_file = ""
_win = "History"
_plugin_name = __file__.split('/')[-1] if __file__ else "history.py"
def _handle_win_input():
pass
def _create_win(win):
if not prof.win_exists(win):
prof.win_create(win, _handle_win_input)
def _show_error(error):
error_msg = f"[History Reader] Error happened: {error}"
prof.cons_alert()
prof.cons_show(error_msg)
prof.log_error(error_msg)
return
def _str_sanitize(text):
return re.sub('[^a-zA-Z0-9\._]', '', text.replace("@", "_at_"))
def _cmd_editor(*args):
global _editor
_db_connection = sqlite3.connect(f"{_db_file}")
_db_connection.text_factory = bytes
_cur = _db_connection.cursor()
msg_buffer = []
if args and args[0] == "set":
if len(args) < 2:
prof.cons_show("Please, use this format: \"/hh set <editor>\"")
return
_editor = args[1]
prof.settings_string_set("history", "editor", _editor)
prof.cons_show(
f"New editor set up successfully. New editor: {_editor}")
return
if args and args[0] == "--no-repeat":
prof.cons_show("[History Reader] Trying operation again...")
if not _current_user:
if args and args[0] == "--no-repeat":
_show_error("Can't fetch current user.")
return
prof.send_line(f"/plugins reload {_plugin_name}")
prof.send_line("/hh --no-repeat")
return
if not _editor:
prof.cons_show(
"Please, set up editor using /hh set. E.g. /hh set /usr/bin/vim")
return
if not (recipient := prof.get_current_recipient()):
prof.cons_show("Please, use this command in a chat window")
return
tmpfpath = _logs_dir / f"{_str_sanitize(recipient)}.log"
res = _cur.execute("""SELECT timestamp, from_jid, message FROM `chatlogs`
WHERE ((`from_jid` = :jid AND `to_jid` = :myjid)
OR (`from_jid` = :myjid AND `to_jid` = :jid))
ORDER BY id""", {"jid": recipient, "myjid": _current_user})
for bmsg in res.fetchall():
try:
msg = [x.decode("UTF-8", errors="backslashreplace") for x in bmsg]
except Exception as e:
msg = [x.decode("UTF-8", errors="replace") for x in bmsg]
sender = "me" if msg[1] == _current_user else msg[1]
msg_buffer.append(f"{msg[0]} - {sender}: {msg[2]}")
tmpfpath.write_text('\n'.join(msg_buffer),
encoding="UTF-8", errors="replace")
pid = os.fork()
if pid == 0:
os.execlp(_editor, _editor, str(tmpfpath))
else:
if pid == -1:
return
os.waitpid(pid, 0)
prof.send_line("/statusbar show name")
try:
tmpfpath.unlink()
except Exception as e:
prof.log_error(
f"[History Reader] Error on file deletion (path: {tmpfpath}): {e}")
return
def prof_on_connect(account_name, fulljid):
prof.log_debug(
f"[History Reader] prof_on_connect called with this JID: {fulljid}")
_init(fulljid)
def prof_on_disconnect(account_name, fulljid):
prof.log_debug(
f"[History Reader] prof_on_disconnect called with this JID: {fulljid}")
global _current_user
_current_user = ""
def prof_init(version, status, account_name, fulljid):
prof.log_debug(
f"[History Reader] prof_init called with this JID: {fulljid}")
_init(fulljid)
synopsis = ["/hh"]
description = "Open an editor and check user's history."
args = [
["set", "Set custom editor."]
]
examples = [
"/hh",
"/hh set /usr/bin/vim",
"/hh set gtk-open"
]
prof.register_command("/hh", 0, 2, synopsis,
description, args, examples, _cmd_editor)
prof.completer_add("/hh", ["set"])
prof.filepath_completer_add("/hh set")
def _init(fulljid):
global _current_user, _editor, _logs_dir, _db_file
prof.log_debug(
f"[History Reader] Initialization started with this JID: {fulljid}")
_current_user = fulljid and fulljid.split('/')[0]
if not _current_user:
prof.log_debug(
"[History Reader] Can't fetch current user, aborting initialization...")
return
_cproof_home = Path(os.getenv('XDG_DATA_HOME')
or "~/.local/share").expanduser() / "profanity"
_logs_dir = _cproof_home / "chatlogs" / _str_sanitize(_current_user)
db_dir = _cproof_home / "database" / _str_sanitize(_current_user)
if not _logs_dir.is_dir():
_show_error(f"Can't open logs directory. Path: {_logs_dir}")
return
if not db_dir.is_dir():
_show_error(f"Can't open DB directory. Path: {db_dir}")
return
_db_file = db_dir / "chatlog.db"
if not _db_file.is_file():
_show_error(f"Log db is not present. Path: {_db_file}")
return
_editor = prof.settings_string_get("history", "editor", "")
if not _editor:
prof.cons_show("Please, set up editor using /hh set.")
else:
prof.cons_show(
f"[History Reader] Successfully started. Editor: {_editor}")

View File

@@ -1,47 +0,0 @@
# -*- coding:utf-8 -*-
# vim: set ts=2 sw=2 sts=2 et:
"""
Removes "unwanted" HTML tags from incoming messages and unescapes html.
Author: Diego Blanco <diego.blanco@treitos.com>
Some XMPP clients like Adium or Pidgin send HTML tags like <font>, <b>, <u>, etc
to format text, specially when using OTR. This plugin removes them and also
unescapes HTML like &lt;, &gt;, etc that these clients also tend to escape.
"""
import re
import prof
import html
#( regular expressions
re_font = re.compile('</?FONT(>| [^>]+>)', re.IGNORECASE)
re_br = re.compile('</?BR>', re.IGNORECASE)
re_styles = re.compile('</?(U|B|I)>', re.IGNORECASE)
re_link = re.compile('</?a(>| [^>]+>)', re.IGNORECASE)
#)
def _htmlstrip(message):
""" Cleans message
"""
try:
_message = message
_message = re_styles.sub( '', _message )
_message = re_font.sub( '', _message )
_message = re_br.sub( '\n', _message )
_message = re_link.sub( '', _message )
_message = html.unescape( _message )
except:
return ("ERR@htmlstrip.py: " + message)
finally:
return (_message)
def prof_pre_chat_message_display(barejid, resource, message):
return _htmlstrip(message)
def prof_pre_room_message_display(barejid, nick, message):
return _htmlstrip(message)
def prof_pre_priv_message_display(barejid, nick, message):
return _htmlstrip(message)

View File

@@ -1,61 +0,0 @@
"""
Requires imgur API
pip install imgurpython
Requires scrot installed for screenshot (on linux)
sudo apt-get install scrot
Uses built in screencapture on OSX
"""
import prof
import subprocess
import sys
from os import path
from imgurpython import ImgurClient
from imgurpython.helpers.error import ImgurClientError
client_id = '19c4ba3fc2c26ef'
client_secret = 'dd5b0918a2f5d36391d574bd2061c50b47bade63'
client = ImgurClient(client_id, client_secret)
def _cmd_imgur(arg1):
if arg1 == "screenshot":
file_path = "/tmp/_prof_screenshot.png"
if sys.platform == "darwin":
subprocess.call("screencapture " + file_path, shell=True)
else:
subprocess.call("scrot " + file_path, shell=True)
else:
try:
file_path = path.expanduser(arg1)
except IOError as ioe:
prof.cons_show('Could not find file at ' + file_path)
return
try:
data = client.upload_from_path(file_path, config=None, anon=True)
prof.send_line(data['link'])
except ImgurClientError as e:
prof.log_error('Could not upload to Imgur - ' + e.error_message)
prof.log_error('Imgur status code - ' + e.status_code)
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/imgur <file_path>",
"/imgur screenshot"
]
description = "Upload an image or screenshot to imgur and send the link"
args = [
[ "<file_path>", "full path to image file" ],
[ "screenshot", "upload a full screen capture" ]
]
examples = [
"/imgur ~/images/cats.jpg",
"/imgur screenshot"
]
prof.register_command("/imgur", 1, 1, synopsis, description, args, examples, _cmd_imgur)
prof.completer_add("/imgur", [ "screenshot" ])
prof.filepath_completer_add("/imgur")

View File

@@ -1,137 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Executes a command when a message is received
MIT License
Copyright (c) 2016 Oliver Schmidhauser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import prof
from subprocess import Popen
import time
from sys import platform
def secure(string):
string=string.replace('\\','\\\\')
string=string.replace("\"","\\\"")
string=string.replace("$","\$")
string=string.replace("`","\`")
return string
def notifycmd(sender,message):
command = prof.settings_string_get("notifycmd", "command", "")
# replace markers with complex strings first to avoid "%%mittens"=>"(%%)mittens"=>"(%m)ittens"=>"<message>ittens" but rather do "(%%)mittens"=>"%mittens"
command = command.replace("%%","{percentreplace}")
command = command.replace("%s","${senderreplace}")
command = command.replace("%m","${messagereplace}")
command = command.replace("{percentreplace}","%")
command = "set -f;senderreplace=\"{}\";messagereplace=\"{}\";{}".format(secure(sender),secure(message),command)
p = Popen(['sh', '-c', command])
def prof_post_chat_message_display(barejid, resource, message):
enabled = prof.settings_string_get("notifycmd", "enabled", "on")
current_recipient = prof.get_current_recipient()
if enabled == "on" or (enabled == "active" and current_recipient == barejid):
notifycmd(barejid, message)
return message
def prof_post_room_message_display(barejid, nick, message):
enabled = prof.settings_string_get("notifycmd", "enabled", "on")
rooms = prof.settings_string_get("notifycmd", "rooms", "mention")
current_muc = prof.get_current_muc()
mynick = prof.get_room_nick(barejid)
# Don't notify for ones own messages
if nick != mynick:
if rooms == "on":
if enabled == "on":
notifycmd(nick + " in " + barejid, message)
elif enabled == "active" and current_muc == barejid:
notifycmd(nick, message)
elif rooms == "mention":
if mynick in message:
if enabled == "on":
notifycmd(nick, message)
elif enabled == "active" and current_muc == barejid:
notifycmd(nick, message)
return message
def prof_post_priv_message_display(barejid, nick, message):
if enabled:
notifycmd(nick,message)
return message
def _cmd_notifycmd(arg1=None, arg2=None):
if arg1 == "on":
prof.settings_string_set("notifycmd", "enabled", "on")
prof.cons_show("Notifycmd plugin enabled")
elif arg1 == "off":
prof.settings_string_set("notifycmd", "enabled", "off")
prof.cons_show("Notifycmd plugin disabled")
elif arg1 == "active":
prof.settings_string_set("notifycmd", "enabled", "active")
prof.cons_show("Notifycmd plugin enabled for active window only")
elif arg1 == "command":
if arg2 == None:
prof.cons_bad_cmd_usage("/notifycmd")
else:
prof.settings_string_set("notifycmd", "command", arg2)
prof.cons_show("notifycmd plugin command set to: " + arg2)
elif arg1 == "rooms":
if arg2 == None:
prof.cons_bad_cmd_usage("/notifycmd")
else:
prof.settings_string_set("notifycmd", "rooms", arg2)
prof.cons_show("notifycmd plugin notifications for rooms set to: " + arg2)
else:
enabled = prof.settings_string_get("notifycmd", "enabled", "on")
rooms = prof.settings_string_get("notifycmd", "rooms", "mention")
command = prof.settings_string_get("notifycmd", "command", "")
prof.cons_show("Notifycmd plugin settings:")
prof.cons_show("enabled : " + enabled)
prof.cons_show("command : " + command)
prof.cons_show("rooms : " + rooms)
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/notifycmd on|off|active",
"/notifycmd command <command>",
"/notifycmd rooms on|off|mention"
]
description = "Executes a command when a message is received"
args = [
[ "on|off", "Enable/disable notifycmd for all windows" ],
[ "active", "Enable notifycmd for active window only" ],
[ "command <args>", "Set command to execute. Replaces %s with sender, %m with message and %% with literal %." ],
[ "rooms <args>", "Setting for multi-user rooms. Set to 'on', 'off' or 'mention'. If set to mention it will only run it your nick was mentioned." ]
]
examples = []
prof.register_command("/notifycmd", 0, 2, synopsis, description, args, examples, _cmd_notifycmd)
prof.completer_add("/notifycmd", [ "on", "off","active","command","rooms" ])
prof.completer_add("/notifycmd rooms", [ "on", "off", "mention" ])

View File

@@ -1,60 +0,0 @@
"""
Paste the contents of the clipboard when in a chat or room window.
Dependencies:
pip install future
sudo apt-get python-tk (or python3-tk)
"""
import prof
import tkinter as tk
def _cmd_paste(arg1=None, arg2=None):
if not arg1:
root = tk.Tk(baseName="")
root.withdraw()
result = root.clipboard_get()
newline = prof.settings_boolean_get("paste", "newline", True)
if len(result.splitlines()) > 1 and newline:
prof.send_line(u'\u000A' + result)
else:
prof.send_line(result)
return
if arg1 == "newline":
if not arg2:
prof.cons_show("")
newline = prof.settings_boolean_get("paste", "newline", True)
if newline:
prof.cons_show("paste.py newline: on")
else:
prof.cons_show("paste.py newline: off")
elif arg2 == "on":
prof.settings_boolean_set("paste", "newline", True)
prof.cons_show("paste.py newline enabled.")
elif arg2 == "off":
prof.settings_boolean_set("paste", "newline", False)
prof.cons_show("paste.py newline disabled.")
else:
prof.cons_bad_cmd_usage("/paste")
return
prof.cons_bad_cmd_usage("/paste")
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/paste",
"/paste newline on|off"
]
description = "Paste contents of clipboard."
args = [
[ "newline on|off", "Send newline before multiline clipboard text, defaults to on" ]
]
examples = []
prof.register_command("/paste", 0, 2, synopsis, description, args, examples, _cmd_paste)
prof.completer_add("/paste", [ "newline" ])
prof.completer_add("/paste newline", [ "on", "off" ])

View File

@@ -1,164 +0,0 @@
import prof
online = set()
def _show_settings():
prof.cons_show("")
prof.cons_show("Presence notify plugin settings:")
mode = prof.settings_string_get("presence_notify", "mode", "all")
prof.cons_show("Mode: {mode}".format(mode=mode))
resource = prof.settings_boolean_get("presence_notify", "resource", False)
if resource:
prof.cons_show("Resource: ON")
else:
prof.cons_show("Resource: OFF")
ignored_list = prof.settings_string_list_get("presence_notify", "ignored")
if ignored_list and len(ignored_list) > 0:
prof.cons_show("Ignored:")
for contact in ignored_list:
prof.cons_show(" {barejid}".format(barejid=contact))
def _cmd_presence_notify(arg1=None, arg2=None, arg3=None):
if arg1 == "all":
prof.settings_string_set("presence_notify", "mode", "all")
prof.cons_show("Notifying on all presence changes")
return
if arg1 == "online":
prof.settings_string_set("presence_notify", "mode", "online")
prof.cons_show("Notifying on online/offline presence changes only")
return
if arg1 == "off":
prof.settings_string_set("presence_notify", "mode", "off")
prof.cons_show("Presence notifications disabled")
return
if arg1 == "ignored":
if arg2 == "clear":
prof.settings_string_list_clear("presence_notify", "ignored")
prof.cons_show("Removed all ignored contacts for presence notifications")
return
if arg2 == "add":
if not arg3:
prof.cons_bad_cmd_usage("/presence_notify")
return
prof.settings_string_list_add("presence_notify", "ignored", arg3)
prof.cons_show("Added {contact} to ignored contacts for presence notifications".format(contact=arg3))
return
if arg2 == "remove":
if not arg3:
prof.cons_bad_cmd_usage("/presence_notify")
return
res = prof.settings_string_list_remove("presence_notify", "ignored", arg3)
if res:
prof.cons_show("Removed {contact} from ignored contacts for presence notifications".format(contact=arg3))
else:
prof.cons_show("{contact} not in ignore list for presence notiications".format(contact=arg3))
return
prof.cons_bad_cmd_usage("/presence_notify")
return
if arg1 == "resource":
if arg2 == "on":
prof.settings_boolean_set("presence_notify", "resource", True)
prof.cons_show("Showing resource in presence notifications")
return;
if arg2 == "off":
prof.settings_boolean_set("presence_notify", "resource", False)
prof.cons_show("Hiding resource in presence notifications")
return;
prof.cons_bad_cmd_usage("/presence_notify")
return
_show_settings()
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/presence_notify all|online|off",
"/presence_notify ignored add|remove|clear [<barejid>]",
"/presence_notify resource on|off"
]
description = "Send a desktop notification on presence updates."
args = [
[ "all", "Enable all presence notifications" ],
[ "online", "Enable only online/offline presence notifications" ],
[ "off", "Disable presence notifications" ],
[ "ignored add|remove <barejid>", "Add or remove a contact from the list excluded from presence notifications"],
[ "ignored clear", "Clear the list of excluded contacts"],
[ "resource on|off", "Enable/disable showing the contacts resource in the notification"]
]
examples = [
"/presence_notify all",
"/presence_notify ignored add bob@server.org"
]
prof.register_command("/presence_notify", 0, 3, synopsis, description, args, examples, _cmd_presence_notify)
prof.completer_add("/presence_notify",
[ "all", "online", "off", "ignored", "resource" ]
)
prof.completer_add("/presence_notify ignored",
[ "add", "remove", "clear" ]
)
prof.completer_add("/presence_notify resource",
[ "on", "off" ]
)
def _do_notify(barejid, presence):
ignored = prof.settings_string_list_get("presence_notify", "ignored")
if ignored and barejid in ignored:
return False
mode = prof.settings_string_get("presence_notify", "mode", "all")
if mode == "all":
return True
elif mode == "online":
if barejid in online:
if presence == "offline":
online.remove(barejid)
return True
else:
return False
else:
if presence != "offline":
online.add(barejid)
return True
else:
return False
else: # off
return False
def prof_on_contact_presence(barejid, resource, presence, status, priority):
if _do_notify(barejid, presence):
jid = barejid
if prof.settings_boolean_get("presence_notify", "resource", False):
jid = jid + "/" + resource
message = "{jid} is {presence}".format(jid=jid, presence=presence)
if status:
message = message + ", \"{status}\"".format(status=status)
prof.notify(message, 5000, "Presence")
return
def prof_on_contact_offline(barejid, resource, status):
if _do_notify(barejid, "offline"):
jid = barejid
if prof.settings_boolean_get("presence_notify", "resource", False):
jid = jid + "/" + resource
message = "{jid} is offline".format(jid=jid)
if status:
message = message + ", \"{status}\"".format(status=status)
prof.notify(message, 5000, "Presence")
return

View File

@@ -1,144 +0,0 @@
"""
Sanitize incoming messages from Adium clients.
MIT License
Copyright (c) 2016 Nd2(SO4)3
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from io import StringIO
from lxml import etree
from lxml.etree import _ElementUnicodeResult, _ElementStringResult
import prof
# ROADMAP
#
# Version 2:
# "/sanitise on|off": global auto detect setting
# "/sanitise <jid> on|off|<mode>": setting per JID: can be autodetect, off,
# or a special "mode" (if there are any).
iq_count = 1
# Stores client information. JID/str~>Client/str.
clients = {}
def prof_on_presence_stanza_receive(stanza):
"""Send a software version request when a contact comes online."""
full_jid = etree.fromstring(stanza).get("from")
if full_jid:
send_version_request(full_jid)
return True
def send_version_request(full_jid):
global iq_count
iq = etree.Element(
"iq", {"type": "get", "to": full_jid, "id": "sanitiser_" + str(iq_count)}
)
etree.SubElement(iq, "query", {"xmlns": "jabber:iq:version"})
iq_count = iq_count + 1
prof.send_stanza(etree.tostring(iq))
def prof_on_iq_stanza_receive(stanza):
"""Listen for software version responses and save results in the
'clients' store.
"""
iq = etree.fromstring(stanza)
id = iq.get("id")
if not id:
return True
if not id.startswith("sanitiser_"):
return True
full_jid = iq.get("from")
if not full_jid:
return False
query = iq.find("{jabber:iq:version}query")
if query is None:
return False
client = query.find("{jabber:iq:version}name")
if client is not None:
clients[full_jid] = client.text
return False
def prof_pre_chat_message_display(jid, resource, message):
"""Sanitises chat room messages if necessary."""
full_jid = "%s/%s" % (jid, resource)
if full_jid not in clients:
return message
return get_sanitiser(clients[full_jid])(message)
# Sanitiser functions:
def get_sanitiser(client):
"""Returns appropriate sanitiser function for the given
Jabber client (string).
"""
if client.startswith("Adium"):
return sanitise
else:
return lambda msg: msg
def sanitise(message):
"""Cleans message from HTML. Raises exceptions on problems."""
parser = etree.HTMLParser()
tree = etree.parse(StringIO(unicode(message)), parser)
elems = tree.xpath("(//br|//a|//*[not(self::a)]/text())")
return "".join(map(substitude, elems))
def try_sanitise(message):
"""Same as 'sanitise' but returns unmodified message if it encounters
a problem, instead of throwing an exception.
"""
try:
return sanitise(message)
except:
return message
def substitude(elem):
"""Substitudes an lxml element with a string representation."""
if type(elem) in (_ElementUnicodeResult, _ElementStringResult):
return str(elem)
else:
if elem.tag == "br":
return "\n"
elif elem.tag == "a":
text = elem.text or ""
href = elem.get("href", "")
if not text and not href:
return ""
elif text == href:
return href
else:
return "%s (%s)" % (text, href)
else:
raise Exception("unhandled tag <%s>" % elem.tag)

View File

@@ -1,128 +0,0 @@
# -*- coding: utf-8 -*-
"""
Manage Message Shortcuts
The MIT License (MIT)
Copyright (c) 2016 René Calles
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import json
import re
import prof
ENABLED = True
SHORTCUTS_DICTIONARY = {u'shrug': u'¯\_(ツ)_/¯',
u'yay': u'\o/'}
plugin_win = "Shortcuts"
def _handle_win_input(win, line):
prof.win_show(win, line)
def create_win():
if prof.win_exists(plugin_win) == False:
prof.win_create(plugin_win, _handle_win_input)
def _substitute(message):
if ENABLED:
shortcuts = re.findall(r'(?:^|\s)(:\w+:)(?=\s|$)', message)
for s in shortcuts:
shortcut_text = SHORTCUTS_DICTIONARY.get(s.strip()[1:-1])
if shortcut_text:
message = message.replace(s.strip(), shortcut_text)
return message
def save(key, value):
global SHORTCUTS_DICTIONARY
SHORTCUTS_DICTIONARY[key] = value
def _list_shortcuts():
global SHORTCUTS_DICTIONARY
# ordered_dict = sorted(DICTIONARY.items(), key=lambda t: t[0])
prof.cons_show(u'Shortcuts: ')
for key, value in SHORTCUTS_DICTIONARY.iteritems():
prof.cons_show(u':{0}: => {1}'.format(key, value))
def _cmd_shortcuts(arg1=None, arg2=None, arg3=None):
global ENABLED
if arg1 == None:
prof.cons_show("Shortcuts Plugin is {}.".format("ON" if ENABLED else "OFF"))
elif arg1 == "on":
ENABLED = True
elif arg1 == "off":
ENABLED = False
elif arg1 == "list":
_list_shortcuts()
elif arg1 == "set":
if arg2 == None:
prof.cons_bad_cmd_usage("/shortcuts")
else:
save(arg2, arg3)
else:
prof.cons_bad_cmd_usage("/shortcuts")
def prof_pre_chat_message_send(jid, message):
return _substitute(message)
def prof_pre_room_message_send(room, message):
return _substitute(message)
def prof_pre_priv_message_send(room, nick, message):
return _substitute(message)
def prof_init(version, status):
synopsis = [
"/shortcuts",
"/shortcuts on|off",
"/shortcuts set <name> <text>"
]
description = "Manage shortcuts for emoticons or other stuff"
args = [
[ "on|off", "Enable/disable shortcuts" ],
[ "set <name> <text>", "Save shortcut" ],
[ "list", "List Shortcuts Dictionary"]
]
examples = [
"/shortcuts",
"/shortcuts on",
"/shortcuts set lol \"Laughing out loud\""
]
prof.register_command("/shortcuts", 0, 3, synopsis, description, args, examples, _cmd_shortcuts)
prof.register_ac("/shortcuts", [ "on", "off", "set" ])
shortcut_keys = [":{}:".format(k) for k in SHORTCUTS_DICTIONARY.keys()]
# prof.cons_show(", ".join(shortcut_keys))
prof.register_ac(" ", shortcut_keys)

View File

@@ -1,313 +0,0 @@
"""
Play sounds on receiving messages.
Default settings require mpg123 to be installed on the host system:
sudo apt-get install mpg123
brew install mpg123
"""
from functools import lru_cache
from pathlib import Path
from typing import Tuple
import prof
import subprocess
import time
_last_played = {}
@lru_cache(maxsize=16)
def get_expanded_path(path_str: str) -> Tuple[Path, str]:
path = Path(path_str).expanduser()
return path, str(path)
def _play_sound_file(soundfile: str):
soundfile_path, soundfile_str = get_expanded_path(soundfile)
if not soundfile_path.exists():
prof.cons_show(f"Sound file not found: {soundfile_str}")
return
now = time.monotonic()
if _last_played.get(soundfile_str, 0) + 0.5 > now:
return # skip if played too recently
_last_played[soundfile_str] = now
audioplayer = prof.settings_string_get("sounds", "audioplayer", "mpg123 -q")
subprocess.Popen([*audioplayer.split(), soundfile_str], start_new_session=True)
def _play_sound(kind: str = "chat"):
soundfile = prof.settings_string_get("sounds", kind, None)
if soundfile:
_play_sound_file(soundfile)
def _cmd_sounds(arg1=None, arg2=None, arg3=None):
if not arg1:
prof.cons_show("")
enabled = prof.settings_boolean_get("sounds", "enabled", False)
chatsound = prof.settings_string_get("sounds", "chat", None)
roomsound = prof.settings_string_get("sounds", "room", None)
privatesound = prof.settings_string_get("sounds", "private", None)
roomlist = prof.settings_string_list_get("sounds", "rooms")
audioplayer = prof.settings_string_get("sounds", "audioplayer", "mpg123 -q")
if chatsound or roomsound or privatesound:
if enabled:
prof.cons_show("Sounds: ON")
else:
prof.cons_show("Sounds: OFF")
prof.cons_show("Audio Player: " + audioplayer)
if chatsound:
prof.cons_show(" Chat : " + chatsound)
if roomsound:
prof.cons_show(" Room : " + roomsound)
if roomlist and len(roomlist) > 0:
for room in roomlist:
prof.cons_show(" " + room)
else:
prof.cons_show(" All rooms")
if privatesound:
prof.cons_show(" Private : " + privatesound)
else:
prof.cons_show("No sounds set.")
return
if arg1 == "on":
prof.settings_boolean_set("sounds", "enabled", True)
prof.cons_show("Sounds enabled")
return
if arg1 == "off":
prof.settings_boolean_set("sounds", "enabled", False)
prof.cons_show("Sounds disabled")
return
if arg1 == "test":
if arg2 not in (
"chat",
"private",
"room",
):
prof.cons_bad_cmd_usage("/sounds")
return
enabled = prof.settings_boolean_get("sounds", "enabled", False)
if not enabled:
prof.cons_show("Sounds disabled")
return
soundfile = prof.settings_string_get("sounds", arg2, None)
if not soundfile:
prof.cons_show("No sound file for " + arg2)
return
_play_sound(kind=arg2)
return
if arg1 == "set":
if arg2 == "chat":
prof.settings_string_set("sounds", "chat", arg3)
prof.cons_show("Set chat sound: " + arg3)
elif arg2 == "room":
prof.settings_string_set("sounds", "room", arg3)
prof.cons_show("Set room sound: " + arg3)
elif arg2 == "private":
prof.settings_string_set("sounds", "private", arg3)
prof.cons_show("Set private sound: " + arg3)
elif arg2 == "audioplayer":
prof.settings_string_set("sounds", "audioplayer", arg3)
prof.cons_show("Set audio player: " + arg3)
else:
prof.cons_bad_cmd_usage("/sounds")
return
if arg1 == "reset":
if arg2 == "audioplayer":
prof.settings_string_set("sounds", "audioplayer", "mpg123 -q")
prof.cons_show("Reset audio player to default.")
else:
prof.cons_bad_cmd_usage("/sounds")
if arg1 == "clear":
if arg2 == "chat":
prof.settings_string_set("sounds", "chat", "")
prof.cons_show("Removed chat sound.")
elif arg2 == "room":
prof.settings_string_set("sounds", "room", "")
prof.cons_show("Removed room sound.")
elif arg2 == "private":
prof.settings_string_set("sounds", "private", "")
prof.cons_show("Removed private sound.")
else:
prof.cons_bad_cmd_usage("/sounds")
return
if arg1 == "rooms":
if arg2 == "add":
if not arg3:
prof.cons_bad_cmd_usage("/sounds")
else:
prof.settings_string_list_add("sounds", "rooms", arg3)
prof.cons_show("Sounds enabled for room: " + arg3)
elif arg2 == "remove":
if not arg3:
prof.cons_bad_cmd_usage("/sounds")
else:
prof.settings_string_list_remove("sounds", "rooms", arg3)
roomlist = prof.settings_string_list_get("sounds", "rooms")
if roomlist and len(roomlist) > 0:
prof.cons_show("Sounds disabled for room: " + arg3)
else:
prof.cons_show("Empty room list for sounds, playing in all rooms.")
elif arg2 == "clear":
prof.settings_string_list_clear("sounds", "rooms")
prof.cons_show("Cleared sounds room list, playing in all rooms.")
else:
prof.cons_bad_cmd_usage("/sounds")
return
if arg1 == "mute":
if not arg2:
prof.cons_bad_cmd_usage("/sounds mute <jid>")
else:
prof.settings_string_list_add("sounds", "muted", arg2)
prof.cons_show("Muted JID: " + arg2)
return
if arg1 == "unmute":
if not arg2:
prof.cons_bad_cmd_usage("/sounds unmute <jid>")
else:
prof.settings_string_list_remove("sounds", "muted", arg2)
prof.cons_show("Unmuted JID: " + arg2)
return
prof.cons_bad_cmd_usage("/sounds")
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/sounds",
"/sounds on|off",
"/sounds set audioplayer <cmd>",
"/sounds reset audioplayer",
"/sounds set chat <file>",
"/sounds set room <file>",
"/sounds set private <file>",
"/sounds test private|room|chat",
"/sounds clear chat",
"/sounds clear room",
"/sounds clear private",
"/sounds rooms add <roomjid>",
]
description = "Play sounds on receiving messages. Executing the command without arguments displays current settings."
args = [
["on|off", "Enable or disable playing sounds."],
[
"set audioplayer <cmd>",
"Commmand line of audio player to use for playing sound files. Defaults to mpg123.",
],
["reset audioplayer", "Resets the audio player to the default mpg123."],
["set chat <file>", "Path to sound file to play on chat messages."],
["set room <file>", "Path to sound file to play on room messages."],
["set private <file>", "Path to sound file to play on private room messages."],
["test private|room|chat", "Test the configured audio player and sound file."],
["clear chat", "Remove the sound for chat messages."],
["clear room", "Remove the sound for room messages."],
["clear private", "Remove the sound for private messages."],
[
"rooms add <roomjid>",
"Add the room to the list that will play the room sound.",
],
[
"rooms remove <roomjid>",
"Remove the room from the list that will play the room sound.",
],
[
"rooms clear",
"Clear the room list, all rooms will play sounds on new messages.",
],
]
examples = [
"/sounds set audioplayer paplay",
"/sounds set chat ~/sounds/woof.mp3",
"/sounds set room ~/sounds/meow.mp3",
"/sounds set private ~/sounds/shhh.mp3",
"/sounds test private",
"/sounds remove private",
"/sounds rooms add myroom@conference.server.org",
"/sounds on",
]
prof.register_command(
"/sounds", 0, 3, synopsis, description, args, examples, _cmd_sounds
)
prof.completer_add(
"/sounds", ["set", "clear", "on", "off", "rooms", "reset", "mute", "unmute"]
)
prof.completer_add("/sounds set", ["chat", "room", "private", "audioplayer"])
prof.completer_add("/sounds reset", ["audioplayer"])
prof.completer_add("/sounds test", ["chat", "room", "private"])
prof.completer_add("/sounds clear", ["chat", "room", "private"])
prof.completer_add("/sounds rooms", ["add", "remove", "clear"])
prof.filepath_completer_add("/sounds set chat")
prof.filepath_completer_add("/sounds set room")
prof.filepath_completer_add("/sounds set private")
def prof_post_chat_message_display(barejid, resource, message):
enabled = prof.settings_boolean_get("sounds", "enabled", False)
if not enabled:
return
muted = prof.settings_string_list_get("sounds", "muted")
if muted and barejid in muted:
return
_play_sound(kind="chat")
def prof_post_room_message_display(barejid, nick, message):
my_nick = prof.get_room_nick(barejid)
if not my_nick:
return
if my_nick == nick:
return
enabled = prof.settings_boolean_get("sounds", "enabled", False)
if not enabled:
return
roomlist = prof.settings_string_list_get("sounds", "rooms")
if roomlist and (barejid not in roomlist):
return
muted = prof.settings_string_list_get("sounds", "muted")
if muted and barejid in muted:
return
_play_sound(soundfile, kind="room")
def prof_post_priv_message_display(barejid, nick, message):
enabled = prof.settings_boolean_get("sounds", "enabled", False)
if not enabled:
return
muted = prof.settings_string_list_get("sounds", "muted")
if muted and barejid in muted:
return
_play_sound(kind="private")

View File

@@ -1,129 +0,0 @@
"""
Allow running system commands in a plugin window, or send result to recipient
Theme options in ~/.local/share/profanity/plugin_themes
[system]
command=magenta
result=green
"""
import prof
import subprocess
system_win = "System"
def _get_result(command):
return subprocess.Popen(command, shell=True, stdout=subprocess.PIPE).stdout.read()
def _handle_win_input(win, command):
prof.win_show_themed(win, "system", "command", None, command)
prof.win_show(win, "")
result = _get_result(command)
split = result.splitlines()
for s in split:
prof.win_show_themed(win, "system", "result", None, s)
prof.win_show(win, "")
def create_win():
if prof.win_exists(system_win) == False:
prof.win_create(system_win, _handle_win_input)
def _handle_send(command=None):
if command == None:
prof.cons_bad_cmd_usage("/system")
return
room = prof.get_current_muc()
recipient = prof.get_current_recipient()
if room == None and recipient == None:
prof.cons_show("You must be in a chat or muc window to send a system command")
prof.cons_alert()
return
result = _get_result(command)
newline = prof.settings_boolean_get("system", "newline", True)
if len(result.splitlines()) > 1 and newline:
prof.send_line(u'\u000A' + result.decode('utf-8', errors='ignore'))
else:
prof.send_line(result)
def _handle_exec(command=None):
if command == None:
prof.cons_bad_cmd_usage("/system")
return;
create_win()
prof.win_focus(system_win)
_handle_win_input(system_win, command)
def _handle_newline(setting=None):
if not setting:
prof.cons_show("")
newline = prof.settings_boolean_get("system", "newline", True)
if newline:
prof.cons_show("syscmd.py newline: on")
else:
prof.cons_show("syscmd.py newline: off")
return
if setting == "on":
prof.settings_boolean_set("system", "newline", True)
prof.cons_show("syscmd.py newline enabled.")
return
if setting == "off":
prof.settings_boolean_set("system", "newline", False)
prof.cons_show("syscmd.py newline disabled.")
return
prof.cons_bad_cmd_usage("/paste")
def _cmd_system(arg1=None, arg2=None):
if not arg1:
create_win()
prof.win_focus(system_win)
return;
if arg1 == "newline":
_handle_newline(arg2)
return
if arg1 == "send":
_handle_send(arg2)
return
if arg1 == "exec":
_handle_exec(arg2)
return
prof.cons_bad_cmd_usage("/system")
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/system",
"/system newline on|off",
"/system exec <comman>",
"/system send <command>"
]
description = "Run a system command, calling with no arguments will open or focus the system window."
args = [
[ "newline on|off", "Send newline before multiline command output, defaults to on" ],
[ "exec <command>", "Execute a command" ],
[ "send <command>", "Send the result of the command to the current recipient or room" ]
]
examples = [
"/system exec ls -l",
"/system send uname -a"
]
prof.register_command("/system", 0, 2, synopsis, description, args, examples, _cmd_system)
prof.completer_add("/system", [ "exec", "send", "newline" ])
prof.completer_add("/system newline", [ "on", "off" ])

79
syscmd.py Normal file
View File

@@ -0,0 +1,79 @@
"""
Allow running system commands in a plugin window, or send result to recipient
Theme options in ~/.local/share/profanity/plugin_themes
[system]
command=magenta
result=green
"""
import prof
import subprocess
system_win = "System"
def _get_result(command):
return subprocess.Popen(command, shell=True, stdout=subprocess.PIPE).stdout.read()
def _handle_win_input(win, command):
prof.win_show_themed(win, "system", "command", None, command)
prof.win_show(win, "")
result = _get_result(command)
split = result.splitlines()
for s in split:
prof.win_show_themed(win, "system", "result", None, s)
prof.win_show(win, "")
def create_win():
if prof.win_exists(system_win) == False:
prof.win_create(system_win, _handle_win_input)
def _cmd_system(arg1=None, arg2=None):
if not arg1:
create_win()
prof.win_focus(system_win)
elif arg1 == "send":
if arg2 == None:
prof.cons_bad_cmd_usage("/system")
else:
room = prof.get_current_muc()
recipient = prof.get_current_recipient()
if room == None and recipient == None:
prof.cons_show("You must be in a chat or muc window to send a system command")
prof.cons_alert()
else:
result = _get_result(arg2)
prof.send_line(u'\u000A' + result)
elif arg1 == "exec":
if arg2 == None:
prof.cons_bad_cmd_usage("/system")
else:
create_win()
prof.win_focus(system_win)
_handle_win_input(system_win, arg2)
else:
prof.cons_bad_cmd_usage("/system")
def prof_init(version, status, account_name, fulljid):
synopsis = [
"/system",
"/system exec <comman>",
"/system send <command>"
]
description = "Run a system command, calling with no arguments will open or focus the system window."
args = [
[ "exec <command>", "Execute a command" ],
[ "send <command>", "Send the result of the command to the current recipient or room" ]
]
examples = [
"/system exec ls -l",
"/system send uname -a"
]
prof.register_command("/system", 0, 2, synopsis, description, args, examples, _cmd_system)
prof.completer_add("/system", [ "exec", "send" ])

161
tests/RubyTest.rb Normal file
View File

@@ -0,0 +1,161 @@
module RubyTest
def self.prof_init(version, status)
Prof::cons_show("RubyTest: init, " + version + ", " + status)
Prof::register_command("/ruby", 0, 1, "/ruby [arg]", "RubyTest", "RubyTest", cmd_ruby)
Prof::register_command("/rb_upper", 1, 1, "/rb_upper string", "RubyTest", "RubyTest", cmd_upper)
Prof::register_command("/rb_notify", 0, 0, "/rb_notify", "RubyTest", "RubyTest", cmd_notify)
Prof::register_command("/rb_vercheck", 0, 0, "/rb_vercheck", "RubyTest", "RubyTest", cmd_vercheck)
Prof::register_ac("/rb_complete", [ "aaaa", "bbbb", "bcbcbc" ])
Prof::register_ac("/rb_complete aaaa", [ "one", "two", "three", "four" ])
Prof::register_ac("/rb_complete bcbcbc", [ "james", "jim", "jane", "bob" ])
Prof::register_command("/rb_complete", 0, 2, "/rb_complete [arg1] [arg2]", "RubyTest", "RubyTest", cmd_ac)
Prof::register_timed(timer_test, 30)
end
def self.prof_on_start
Prof::cons_show("RubyTest: prof_on_start")
Prof::log_debug("RubyTest: logged debug");
Prof::log_info("RubyTest: logged info");
Prof::log_warning("RubyTest: logged warning");
Prof::log_error("RubyTest: logged error");
end
def self.prof_on_shutdown
Prof::log_info("RubyTest: prof_on_shutdown");
end
def self.prof_on_connect(account_name, fulljid)
Prof::cons_show("RubyTest: prof_on_connect, " + account_name + ", " + fulljid)
end
def self.prof_on_disconnect(account_name, fulljid)
Prof::cons_show("RubyTest: prof_on_disconnect, " + account_name + ", " + fulljid)
end
def self.prof_pre_chat_message_display(jid, message)
Prof::cons_show("RubyTest: prof_pre_chat_message_display, " + jid + ", " + message)
Prof::cons_alert
return message + "[RB_pre_chat_message_display]"
end
def self.prof_post_chat_message_display(jid, message)
Prof::cons_show("RubyTest: prof_post_chat_message_display, " + jid + ", " + message)
Prof::cons_alert
end
def self.prof_pre_chat_message_send(jid, message)
Prof::cons_show("RubyTest: prof_pre_chat_message_send, " + jid + ", " + message)
Prof::cons_alert
return message + "[RB_pre_chat_message_send]"
end
def self.prof_post_chat_message_send(jid, message)
Prof::cons_show("RubyTest: prof_post_chat_message_send, " + jid + ", " + message)
Prof::cons_alert
end
def self.prof_pre_room_message_display(room, nick, message)
Prof::cons_show("RubyTest: prof_pre_room_message_display, " + room + ", " + nick + ", " + message)
Prof::cons_alert
return message + "[RB_pre_room_message_display]"
end
def self.prof_post_room_message_display(room, nick, message)
Prof::cons_show("RubyTest: prof_post_room_message_display, " + room + ", " + nick + ", " + message)
Prof::cons_alert
end
def self.prof_pre_room_message_send(room, message)
Prof::cons_show("RubyTest: prof_pre_room_message_send, " + room + ", " + message)
Prof::cons_alert
return message + "[RB_pre_room_message_send]"
end
def self.prof_post_room_message_send(room, message)
Prof::cons_show("RubyTest: prof_post_room_message_send, " + room + ", " + message)
Prof::cons_alert
end
def self.prof_pre_priv_message_display(room, nick, message)
Prof::cons_show("RubyTest: prof_pre_priv_message_display, " + room + ", " + nick + ", " + message)
Prof::cons_alert
return message + "[RB_pre_priv_message_display]"
end
def self.prof_post_priv_message_display(room, nick, message)
Prof::cons_show("RubyTest: prof_post_priv_message_display, " + room + ", " + nick + ", " + message)
Prof::cons_alert
end
def self.prof_pre_priv_message_send(room, nick, message)
Prof::cons_show("RubyTest: prof_pre_priv_message_send, " + room + ", " + nick + ", " + message)
Prof::cons_alert
return message + "[RB_pre_priv_message_send]"
end
def self.prof_post_priv_message_send(room, nick, message)
Prof::cons_show("RubyTest: prof_post_priv_message_send, " + room + ", " + nick + ", " + message)
Prof::cons_alert
end
def self.cmd_ruby
return Proc.new { | msg |
if msg
Prof::cons_show("RubyTest: /ruby command called, arg = " + msg)
else
Prof::cons_show("RubyTest: /ruby command called with no arg")
end
}
end
def self.cmd_ac
return Proc.new { | arg1, arg2 |
Prof::cons_show("RubyTest: /rb_complete called")
}
end
def self.cmd_notify
return Proc.new {
Prof::notify("RubyTest: notify", 2000, "Plugins")
}
end
def self.cmd_vercheck
return Proc.new {
Prof::send_line("/vercheck")
Prof::cons_show("RubyTest: sent \"/vercheck\" command")
}
end
def self.timer_test
return Proc.new {
Prof::cons_show("RubyTest: timer fired.")
Prof::cons_alert
}
end
def self.cmd_upper
return Proc.new { | line |
win_tag = "Ruby Plygin"
if (Prof::win_exists(win_tag) == false)
Prof::win_create(win_tag, handle_upper)
end
Prof::win_focus(win_tag)
if (line)
handle_upper.call(win_tag, line)
end
}
end
def self.handle_upper
return Proc.new { | win, line |
upper = line.upcase
Prof::win_show(win, upper)
Prof::win_show_red(win, upper + " red")
Prof::win_show_yellow(win, upper + " yellow")
Prof::win_show_green(win, upper + " green")
Prof::win_show_cyan(win, upper + " cyan")
}
end
end

23
tests/RubyThread.rb Normal file
View File

@@ -0,0 +1,23 @@
module RubyThread
@counter = 0
def self.inc_counter()
while true
sleep(1)
@counter = @counter + 1
end
end
def self.cmd_rcount()
return Proc.new {
instance_eval {
Prof::cons_show("RCounter: " + @counter)
}
}
end
def self.prof_init(version, status)
Thread.new { self.inc_counter() }
Prof::register_command("/rcount", 0, 0, "/rcount", "Ruby threaded example", "Ruby threaded example", cmd_rcount)
end
end

116
tests/luatest.lua Normal file
View File

@@ -0,0 +1,116 @@
local luatest = {}
local function cmd_lua(msg)
if msg then
prof_cons_show("luatest: /lua command called, arg = " .. msg)
else
prof_cons_show("luatest: /lua command called with no arg")
end
prof_cons_alert()
prof_notify("luatest: notify", 8000, "Plugins")
prof_send_line("/account list")
prof_cons_show("luatest: sent \"/account list\" command")
end
local function timer_test()
prof_cons_show("luatest: timer fired.")
recipient = prof_get_current_recipient()
if recipient then
prof_cons_show(" current recipient = " .. recipient)
end
prof_cons_alert()
end
local function handle_bracket(win, line)
prof_win_show(win, "(" .. line .. ")")
prof_win_show_red(win, "Red")
prof_win_show_yellow(win, "Yellow")
prof_win_show_green(win, "Green")
prof_win_show_cyan(win, "Cyan")
end
local function cmd_bracket(line)
win_tag = "Parenthesise echo";
if prof_win_exists(win_tag) == false then
prof_win_create(win_tag, handle_bracket);
end
prof_win_focus(win_tag);
if line then
handle_bracket(win_tag, line)
end
end
function luatest.prof_init(version, status)
prof_cons_show("luatest: init, " .. version .. ", " .. status)
prof_register_command("/lua", 0, 1, "/lua", "luatest", "luatest", cmd_lua)
prof_register_command("/bracket", 0, 1, "/bracket", "Parenthesise input string", "Parenthesise input string", cmd_bracket);
prof_register_ac("/lcomplete", { "football", "footy", "tennis" })
prof_register_ac("/lcomplete football", { "goal", "out", "try", "given" })
prof_register_ac("/lcomplete tennis", { "test", "prod", "dev", "stage" })
prof_register_command("/lcomplete", 0, 2, "/lcomplete", "Lua completion", "Lua completion", cmd_bracket)
prof_register_timed(timer_test, 10)
end
function luatest.prof_on_start()
prof_cons_show("luatest: on_start")
prof_log_debug("luatest: logged debug");
prof_log_info("luatest: logged info");
prof_log_warning("luatest: logged warning");
prof_log_error("luatest: logged error");
end
function luatest.prof_on_connect(account_name, fulljid)
prof_cons_show("luatest: on_connect, " .. account_name .. ", " .. fulljid)
end
function luatest.prof_on_disconnect(account_name, fulljid)
prof_cons_show("luatest: on_disconnect, " .. account_name .. ", " .. fulljid)
prof_log_info("luatest: on_disconnect, " .. account_name .. ", " .. fulljid)
end
function luatest.prof_on_message_received(jid, message)
prof_cons_show("luatest: on_message_received, " .. jid .. ", " .. message)
prof_cons_alert()
return message .. "[LUA]"
end
function luatest.prof_on_room_message_received(room, nick, message)
prof_cons_show("luatest: on_room_message_received, " .. room .. ", " .. nick .. ", " .. message)
prof_cons_alert()
return message .. "[LUA]"
end
function luatest.prof_on_private_message_received(room, nick, message)
prof_cons_show("luatest: on_private_message_received, " .. room .. ", " .. nick .. ", " .. message)
prof_cons_alert()
return message .. "[LUA]"
end
function luatest.prof_on_message_send(jid, message)
prof_cons_show("luatest: on_message_send, " .. jid .. ", " .. message)
prof_cons_alert()
return message .. "[LUA]"
end
function luatest.prof_on_private_message_send(room, nick, message)
prof_cons_show("luatest: on_private_message_send, " .. room .. ", " .. nick .. ", " .. message)
prof_cons_alert()
return message .. "[LUA]"
end
function luatest.prof_on_room_message_send(room, message)
prof_cons_show("luatest: on_room_message_send, " .. room .. ", " .. message)
prof_cons_alert()
return message .. "[LUA]"
end
function luatest.prof_on_shutdown()
prof_log_info("luatest: on_shutdown")
end
return luatest

558
tests/python-test.py Normal file
View File

@@ -0,0 +1,558 @@
import prof
import threading
import time
plugin_win = "Python Test"
count = 0
ping_id = 1
def _inc_counter():
global count
while True:
time.sleep(5)
count = count + 1
def _handle_win_input(win, line):
prof.win_show(win, "Input received: " + line)
def _create_win():
if prof.win_exists(plugin_win) == False:
prof.win_create(plugin_win, _handle_win_input)
def _consalert():
_create_win()
prof.win_focus(plugin_win)
prof.cons_alert()
prof.win_show(plugin_win, "called -> prof.cons_alert")
def _consshow(msg):
if not msg:
prof.cons_bad_cmd_usage("/python-test")
return
_create_win()
prof.win_focus(plugin_win)
prof.cons_show(msg)
prof.win_show(plugin_win, "called -> prof.cons_show: " + msg)
def _consshow_t(group, key, dflt, msg):
if not group or not key or not dflt or not msg:
prof.cons_bad_cmd_usage("/python-test")
return
_create_win()
prof.win_focus(plugin_win)
groupval = None if group == "none" else group
keyval = None if key == "none" else key
dfltval = None if dflt == "none" else dflt
prof.cons_show_themed(groupval, keyval, dfltval, msg)
prof.win_show(plugin_win, "called -> prof.cons_show_themed: " + group + ", " + key + ", " + dflt + ", " + msg)
def _constest():
res = prof.current_win_is_console()
_create_win()
prof.win_focus(plugin_win)
if res:
prof.win_show(plugin_win, "called -> prof.current_win_is_console: true")
else:
prof.win_show(plugin_win, "called -> prof.current_win_is_console: false")
def _winshow(msg):
if not msg:
prof.cons_bad_cmd_usage("/python-test")
return
_create_win()
prof.win_focus(plugin_win)
prof.win_show(plugin_win, msg)
prof.win_show(plugin_win, "called -> prof.win_show: " + msg)
def _winshow_t(group, key, dflt, msg):
if not group or not key or not dflt or not msg:
prof.cons_bad_cmd_usage("/python-test")
return
_create_win()
prof.win_focus(plugin_win)
groupval = None if group == "none" else group
keyval = None if key == "none" else key
dfltval = None if dflt == "none" else dflt
prof.win_show_themed(plugin_win, groupval, keyval, dfltval, msg)
prof.win_show(plugin_win, "called -> prof_win_show_themed: " + group + ", " + key + ", " + dflt + ", " + msg)
def _sendline(line):
if not line:
prof.cons_bad_cmd_usage("/python-test")
return
_create_win()
prof.win_focus(plugin_win)
prof.send_line(line)
prof.win_show(plugin_win, "called -> prof.send_line: " + line)
def _notify(msg):
if not msg:
prof.cons_bad_cmd_usage("/python-test")
return
_create_win()
prof.win_focus(plugin_win)
prof.notify(msg, 5000, "python-test plugin")
prof.win_show(plugin_win, "called -> prof.notify: " + msg)
def _get(subject):
if subject == "recipient":
_create_win()
recipient = prof.get_current_recipient();
if recipient:
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "called -> prof.get_current_recipient: " + recipient)
else:
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "called -> prof_get_current_recipient: <none>")
elif subject == "room":
_create_win()
room = prof.get_current_muc()
if room:
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "called -> prof_get_current_muc: " + room)
else:
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "called -> prof_get_current_muc: <none>")
elif subject == "nick":
_create_win()
nick = prof.get_current_nick()
if nick:
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "called -> prof_get_current_nick: " + nick)
else:
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "called -> prof_get_current_nick: <none>")
elif subject == "occupants":
_create_win()
occupants = prof.get_current_occupants()
if occupants:
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "called -> prof_get_current_occupants:")
for occupant in occupants:
prof.win_show(plugin_win, occupant)
else:
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "called -> prof_get_current_occupants: <none>")
else:
prof.cons_bad_cmd_usage("/python-test")
def _log(level, msg):
if not level or not msg:
prof.cons_bad_cmd_usage("/python-test")
return
if level == "debug":
_create_win()
prof.win_focus(plugin_win)
prof.log_debug(msg)
prof.win_show(plugin_win, "called -> prof.log_debug: " + msg)
elif level == "info":
_create_win()
prof.win_focus(plugin_win)
prof.log_info(msg)
prof.win_show(plugin_win, "called -> prof.log_info: " + msg)
elif level == "warning":
_create_win()
prof.win_focus(plugin_win)
prof.log_warning(msg)
prof.win_show(plugin_win, "called -> prof.log_warning: " + msg)
elif level == "error":
_create_win()
prof.win_focus(plugin_win)
prof.log_error(msg)
prof.win_show(plugin_win, "called -> prof.log_error: " + msg)
else:
prof.cons_bad_cmd_usage("/python-test")
def _count():
_create_win()
prof.win_focus(plugin_win)
prof.win_show(plugin_win, "Count: " + str(count))
def _ping(jid):
global ping_id
if not jid:
prof.cons_bad_cmd_usage("/python-test")
return
_create_win()
prof.win_focus(plugin_win)
res = prof.send_stanza("<iq to='" + jid + "' id='pythonplugin-" + str(ping_id) + "' type='get'><ping xmlns='urn:xmpp:ping'/></iq>")
ping_id = ping_id + 1
if res:
prof.win_show(plugin_win, "Ping sent successfully")
else:
prof.win_show(plugin_win, "Error sending ping")
def _boolean(op, group, key, value_str):
if op != "get" and op != "set":
prof.cons_bad_cmd_usage("/python-test")
return
if group == None or key == None:
prof.cons_bad_cmd_usage("/python-test")
return
if op == "set" and value_str != "true" and value_str != "false":
prof.cons_bad_cmd_usage("/python-test")
return
if op == "get":
dflt = False
_create_win()
prof.win_focus(plugin_win)
res = prof.settings_get_boolean(group, key, dflt)
if res:
prof.win_show(plugin_win, "Boolean setting: TRUE")
else:
prof.win_show(plugin_win, "Boolean setting: FALSE")
elif op == "set":
value = False
if value_str == "true":
value = True
_create_win()
prof.win_focus(plugin_win)
prof.settings_set_boolean(group, key, value)
prof.win_show(plugin_win, "Set [" + group + "] " + key + " to " + str(value))
def _string(op, group, key, value):
if op != "get" and op != "set":
prof.cons_bad_cmd_usage("/python-test")
return
if group == None or key == None:
prof.cons_bad_cmd_usage("/python-test")
return
if op == "set" and not value:
prof.cons_bad_cmd_usage("/python-test")
return
if op == "get":
_create_win()
prof.win_focus(plugin_win)
res = prof.settings_get_string(group, key, None)
if res:
prof.win_show(plugin_win, "String setting: " + res)
else:
prof.win_show(plugin_win, "String setting: None")
elif op == "set":
_create_win()
prof.win_focus(plugin_win)
prof.settings_set_string(group, key, value)
prof.win_show(plugin_win, "Set [" + group + "] " + key + " to " + value)
def _int(op, group, key, value):
if op != "get" and op != "set":
prof.cons_bad_cmd_usage("/python-test")
return
if group == None or key == None:
prof.cons_bad_cmd_usage("/python-test")
return
if op == "get":
_create_win()
prof.win_focus(plugin_win)
res = prof.settings_get_int(group, key, 0)
prof.win_show(plugin_win, "Integer setting: " + str(res))
elif op == "set":
_create_win()
prof.win_focus(plugin_win)
prof.settings_set_int(group, key, int(value))
prof.win_show(plugin_win, "Set [" + group + "] " + key + " to " + str(value))
def _incoming(barejid, resource, message):
if not barejid or not resource or not message:
prof.cons_bad_cmd_usage("/python-test")
return
prof.incoming_message(barejid, resource, message)
def _completer(op, item):
if not item:
prof.cons_bad_cmd_usage("/python-test")
return
if op == "add":
_create_win()
prof.win_focus(plugin_win)
prof.completer_add("/python-test", [item])
prof.win_show(plugin_win, "Added \"" + item + "\" to /python-test completer")
prof.completer_add("/python-test completer remove", [item])
elif op == "remove":
_create_win()
prof.win_focus(plugin_win)
prof.completer_remove("/python-test", [item])
prof.win_show(plugin_win, "Removed \"" + item + "\" to /python-test completer")
prof.completer_remove("/python-test completer remove", [item])
else:
prof.cons_bad_cmd_usage("/python-test")
def _cmd_pythontest(subcmd=None, arg1=None, arg2=None, arg3=None, arg4=None):
if subcmd == "consalert": _consalert()
elif subcmd == "consshow": _consshow(arg1)
elif subcmd == "consshow_t": _consshow_t(arg1, arg2, arg3, arg4)
elif subcmd == "constest": _constest()
elif subcmd == "winshow": _winshow(arg1)
elif subcmd == "winshow_t": _winshow_t(arg1, arg2, arg3, arg4)
elif subcmd == "sendline": _sendline(arg1)
elif subcmd == "notify": _notify(arg1)
elif subcmd == "get": _get(arg1)
elif subcmd == "log": _log(arg1, arg2)
elif subcmd == "count": _count()
elif subcmd == "ping": _ping(arg1)
elif subcmd == "boolean": _boolean(arg1, arg2, arg3, arg4)
elif subcmd == "string": _string(arg1, arg2, arg3, arg4)
elif subcmd == "int": _int(arg1, arg2, arg3, arg4)
elif subcmd == "incoming": _incoming(arg1, arg2, arg3)
elif subcmd == "completer": _completer(arg1, arg2)
else: prof.cons_bad_cmd_usage("/python-test")
def timed_callback():
_create_win()
prof.win_show(plugin_win, "timed -> timed_callback called")
def prof_init(version, status, account_name, fulljid):
t = threading.Thread(target=_inc_counter)
t.daemon = True
t.start()
prof.disco_add_feature("urn:xmpp:profanity:python_test_plugin");
prof.win_create(plugin_win, _handle_win_input)
if account_name and fulljid:
prof.win_show(plugin_win, "fired -> prof_init: " + version + ", " + status + ", " + account_name + ", " + fulljid)
else:
prof.win_show(plugin_win, "fired -> prof_init: " + version + ", " + status)
synopsis = [
"/python-test consalert",
"/python-test consshow <message>",
"/python-test consshow_t <group> <key> <default> <message>",
"/python-test constest",
"/python-test winshow <message>",
"/python-test winshow_t <group> <key> <default> <message>",
"/python-test notify <message>",
"/python-test sendline <line>",
"/python-test get recipient|room|nick|occupants",
"/python-test log debug|info|warning|error <message>",
"/python-test count",
"/python-test ping <jid>",
"/python-test boolean get <group> <key>",
"/python-test boolean set <group> <key> <value>",
"/python-test string get <group> <key>",
"/python-test string set <group> <key> <value>",
"/python-test int get <group> <key>",
"/python-test int set <group> <key> <value>",
"/python-test incoming <barejid> <resource> <message>",
"/python-test completer add|remove <item>"
]
description = "Python test plugins. All commands focus the plugin window."
args = [
[ "consalert", "Highlight the console window in the status bar" ],
[ "consshow <message>", "Show the message in the console window" ],
[ "consshow_t <group> <key> <default> <message>", "Show the themed message in the console window. " ],
[ "constest", "Show whether the command was run in the console." ],
[ "winshow <message>", "Show the message in the plugin window" ],
[ "winshow_t <group> <key> <default> <message>", "Show the themed message in the plugin window. " ],
[ "notify <message>", "Send a desktop notification with message" ],
[ "sendline <line>", "Pass line to profanity to process" ],
[ "get recipient", "Show the current chat recipient, if in a chat window" ],
[ "get room", "Show the current room JID, if ina a chat room"],
[ "get nick", "Show nickname in current room, if ina a chat room"],
[ "get occupants", "Show occupants in current room, if ina a chat room"],
[ "log debug|info|warning|error <message>", "Log a message at the specified level" ],
[ "count", "Show the counter, incremented every 5 seconds by a worker thread" ],
[ "ping <jid>", "Send an XMPP ping to the specified Jabber ID" ],
[ "boolean get <group> <key>", "Get a boolean setting" ],
[ "boolean set <group> <key> <value>", "Set a boolean setting" ],
[ "string get <group> <key>", "Get a string setting" ],
[ "string set <group> <key> <value>", "Set a string setting" ],
[ "int get <group> <key>", "Get a integer setting" ],
[ "int set <group> <key> <value>", "Set a integer setting" ],
[ "incoming <barejid> <resource> <message>", "Show an incoming message." ],
[ "completer add <item>", "Add an autocomplete item to the /c-test command." ],
[ "completer remove <item>", "Remove an autocomplete item from the /c-test command." ]
]
examples = [
"/python-test sendline /about",
"/python-test log debug \"Test debug message\"",
"/python-test consshow_t c-test cons.show none \"This is themed\"",
"/python-test consshow_t none none bold_cyan \"This is bold_cyan\"",
"/python-test ping buddy@server.org"
]
prof.register_command("/python-test", 1, 5, synopsis, description, args, examples, _cmd_pythontest)
prof.completer_add("/python-test",
[
"consalert",
"consshow",
"consshow_t",
"constest",
"winshow",
"winshow_t",
"notify",
"sendline",
"get",
"log",
"count",
"ping",
"boolean",
"string",
"int",
"incoming",
"completer"
]
)
prof.completer_add("/python-test get",
[ "recipient", "room", "nick", "occupants" ]
)
prof.completer_add("/python-test log",
[ "debug", "info", "warning", "error" ]
)
prof.completer_add("/python-test boolean",
[ "get", "set" ]
)
prof.completer_add("/python-test string",
[ "get", "set" ]
)
prof.completer_add("/python-test int",
[ "get", "set" ]
)
prof.completer_add("/python-test completer",
[ "add", "remove" ]
)
prof.register_timed(timed_callback, 30)
def prof_on_start():
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_start")
def prof_on_shutdown():
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_shutdown")
def prof_on_connect(account_name, fulljid):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_connect: " + account_name + ", " + fulljid)
def prof_on_disconnect(account_name, fulljid):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_disconnect: " + account_name + ", " + fulljid)
def prof_pre_chat_message_display(jid, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_pre_chat_message_display: " + jid + ", " + message)
def prof_post_chat_message_display(jid, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_post_chat_message_display: " + jid + ", " + message)
def prof_pre_chat_message_send(jid, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_pre_chat_message_send: " + jid + ", " + message)
def prof_post_chat_message_send(jid, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_post_chat_message_send: " + jid + ", " + message)
def prof_pre_room_message_display(room, nick, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_pre_room_message_display: " + room + ", " + nick + ", " + message)
def prof_post_room_message_display(room, nick, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_post_room_message_display: " + room + ", " + nick + ", " + message)
def prof_pre_room_message_send(room, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_pre_room_message_send: " + room + ", " + message)
def prof_post_room_message_send(room, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_post_room_message_send: " + room + ", " + message)
def prof_on_room_history_message(room, nick, message, timestamp):
_create_win()
if timestamp:
prof.win_show(plugin_win, "fired -> prof_on_room_history_message: " + room + ", " + nick + ", " + message + ", " + timestamp)
else:
prof.win_show(plugin_win, "fired -> prof_on_room_history_message: " + room + ", " + nick + ", " + message)
def prof_pre_priv_message_display(room, nick, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_pre_priv_message_display: " + room + ", " + nick + ", " + message)
def prof_post_priv_message_display(room, nick, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_post_priv_message_display: " + room + ", " + nick + ", " + message)
def prof_pre_priv_message_send(room, nick, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_pre_priv_message_send: " + room + ", " + nick + ", " + message)
def prof_post_priv_message_send(room, nick, message):
_create_win()
prof.win_show(plugin_win, "fired -> prof_post_priv_message_send: " + room + ", " + nick + ", " + message)
def prof_on_message_stanza_send(stanza):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_message_stanza_send: " + stanza)
def prof_on_message_stanza_receive(stanza):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_message_stanza_receive: " + stanza)
return True
def prof_on_presence_stanza_send(stanza):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_presence_stanza_send: " + stanza)
def prof_on_presence_stanza_receive(stanza):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_presence_stanza_receive: " + stanza)
return True
def prof_on_iq_stanza_send(stanza):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_iq_stanza_send: " + stanza)
def prof_on_iq_stanza_receive(stanza):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_iq_stanza_receive: " + stanza)
return True
def prof_on_contact_offline(barejid, resource, status):
_create_win()
if status:
prof.win_show(plugin_win, "fired -> prof_on_contact_offline: " + barejid + "/" + resource + " \"" + status + "\"")
else:
prof.win_show(plugin_win, "fired -> prof_on_contact_offline: " + barejid + "/" + resource)
def prof_on_contact_presence(barejid, resource, presence, status, priority):
_create_win()
if status:
prof.win_show(plugin_win, "fired -> prof_on_contact_presence: " + barejid + "/" + resource + " " + presence + " " + str(priority) + " \"" + status + "\"")
else:
prof.win_show(plugin_win, "fired -> prof_on_contact_presence: " + barejid + "/" + resource + " " + presence + " " + str(priority))
def prof_on_chat_win_focus(barejid):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_chat_win_focus: " + barejid)
def prof_on_room_win_focus(roomjid):
_create_win()
prof.win_show(plugin_win, "fired -> prof_on_room_win_focus: " + roomjid)

View File

@@ -0,0 +1,18 @@
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
LINK_FLAGS := -fno-common -flat_namespace -bundle -undefined suppress
else
LINK_FLAGS := -shared -fpic
endif
all: test-c-plugin.so
%.so:%.o
$(CC) $(LINK_FLAGS) -lprofanity -o $@ $^
%.o:%.c
$(CC) $(INCLUDE) -D_GNU_SOURCE -D_BSD_SOURCE -fpic -g3 -O0 -std=c99 \
-pedantic -c -o $@ $<
clean:
$(RM) test-c-plugin.so

View File

@@ -0,0 +1,942 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <unistd.h>
#include <profapi.h>
static PROF_WIN_TAG plugin_win = "C Test";
static pthread_t worker_thread;
static int count = 0;
static int ping_id = 1;
void*
inc_counter(void *arg)
{
while (1) {
sleep(5);
count++;
}
}
void
handle_win_input(PROF_WIN_TAG win, char *line)
{
char *str = "Input received: ";
char buf[strlen(str) + strlen(line) + 1];
sprintf(buf, "%s%s", str, line);
prof_win_show(win, buf);
}
void
create_win(void)
{
if (!prof_win_exists(plugin_win)) {
prof_win_create(plugin_win, handle_win_input);
}
}
void
consalert(void)
{
create_win();
prof_win_focus(plugin_win);
prof_cons_alert();
prof_win_show(plugin_win, "called -> prof_cons_alert");
}
void
consshow(char *msg)
{
if (msg == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
create_win();
prof_win_focus(plugin_win);
prof_cons_show(msg);
char *str = "called -> prof_cons_show: ";
char buf[strlen(str) + strlen(msg)];
sprintf(buf, "%s%s", str, msg);
prof_win_show(plugin_win, buf);
}
void
consshow_t(char *group, char *key, char *def, char *msg)
{
if (group == NULL || key == NULL || def == NULL || msg == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
create_win();
prof_win_focus(plugin_win);
char *groupval = strcmp(group, "none") == 0 ? NULL : group;
char *keyval = strcmp(key, "none") == 0 ? NULL : key;
char *defval = strcmp(def, "none") == 0 ? NULL : def;
prof_cons_show_themed(groupval, keyval, defval, msg);
char *str = "called -> prof_cons_show_themed: ";
char buf[strlen(str) + strlen(group) + 2 + strlen(key) + 2 + strlen(def) + 2 + strlen(msg)];
sprintf(buf, "%s%s, %s, %s, %s", str, group, key, def, msg);
prof_win_show(plugin_win, buf);
}
void
constest(void)
{
int res = prof_current_win_is_console();
create_win();
prof_win_focus(plugin_win);
if (res) {
prof_win_show(plugin_win, "called -> prof_current_win_is_console: true");
} else {
prof_win_show(plugin_win, "called -> prof_current_win_is_console: false");
}
}
void
winshow(char *msg)
{
if (msg == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
create_win();
prof_win_focus(plugin_win);
prof_win_show(plugin_win, msg);
char *str = "called -> prof_win_show: ";
char buf[strlen(str) + strlen(msg)];
sprintf(buf, "%s%s", str, msg);
prof_win_show(plugin_win, buf);
}
void
winshow_t(char *group, char *key, char *def, char *msg)
{
if (group == NULL || key == NULL || def == NULL || msg == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
create_win();
prof_win_focus(plugin_win);
char *groupval = strcmp(group, "none") == 0 ? NULL : group;
char *keyval = strcmp(key, "none") == 0 ? NULL : key;
char *defval = strcmp(def, "none") == 0 ? NULL : def;
prof_win_show_themed(plugin_win, groupval, keyval, defval, msg);
char *str = "called -> prof_win_show_themed: ";
char buf[strlen(str) + strlen(group) + 2 + strlen(key) + 2 + strlen(def) + 2 + strlen(msg)];
sprintf(buf, "%s%s, %s, %s, %s", str, group, key, def, msg);
prof_win_show(plugin_win, buf);
}
void
sendline(char *line)
{
if (line == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
create_win();
prof_win_focus(plugin_win);
prof_send_line(line);
char *str = "called -> prof_send_line: ";
char buf[strlen(str) + strlen(line)];
sprintf(buf, "%s%s", str, line);
prof_win_show(plugin_win, buf);
}
void
donotify(char *msg)
{
if (msg == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
create_win();
prof_win_focus(plugin_win);
prof_notify(msg, 5000, "c-test plugin");
char *str = "called -> prof_notify: ";
char buf[strlen(str) + strlen(msg)];
sprintf(buf, "%s%s", str, msg);
prof_win_show(plugin_win, buf);
}
void
getsubject(char *subject)
{
if (subject == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if (strcmp(subject, "recipient") == 0) {
create_win();
char *recipient = prof_get_current_recipient();
if (recipient) {
prof_win_focus(plugin_win);
char *str = "called -> prof_get_current_recipient: ";
char buf[strlen(str) + strlen(recipient)];
sprintf(buf, "%s%s", str, recipient);
prof_win_show(plugin_win, buf);
} else {
prof_win_focus(plugin_win);
prof_win_show(plugin_win, "called -> prof_get_current_recipient: <none>");
}
} else if (strcmp(subject, "room") == 0) {
create_win();
char *room = prof_get_current_muc();
if (room) {
prof_win_focus(plugin_win);
char *str = "called -> prof_get_current_muc: ";
char buf[strlen(str) + strlen(room)];
sprintf(buf, "%s%s", str, room);
prof_win_show(plugin_win, buf);
} else {
prof_win_focus(plugin_win);
prof_win_show(plugin_win, "called -> prof_get_current_muc: <none>");
}
} else if (strcmp(subject, "nick") == 0) {
create_win();
char *nick = prof_get_current_nick();
if (nick) {
prof_win_focus(plugin_win);
char *str = "called -> prof_get_current_nick: ";
char buf[strlen(str) + strlen(nick)];
sprintf(buf, "%s%s", str, nick);
prof_win_show(plugin_win, buf);
} else {
prof_win_focus(plugin_win);
prof_win_show(plugin_win, "called -> prof_get_current_nick: <none>");
}
} else if (strcmp(subject, "occupants") == 0) {
create_win();
char **occupants = prof_get_current_occupants();
if (occupants) {
prof_win_focus(plugin_win);
prof_win_show(plugin_win, "called -> prof_get_current_occupants:");
int i = 0;
while(occupants[i] != NULL) {
prof_win_show(plugin_win, occupants[i]);
i++;
}
} else {
prof_win_focus(plugin_win);
prof_win_show(plugin_win, "called -> prof_get_current_occupants: <none>");
}
} else {
prof_cons_bad_cmd_usage("/c-test");
}
}
void
logmsg(char *level, char *msg)
{
if (level == NULL || msg == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if (strcmp(level, "debug") == 0) {
create_win();
prof_win_focus(plugin_win);
prof_log_debug(msg);
char *str = "called -> prof_log_debug: ";
char buf[strlen(str) + strlen(msg)];
sprintf(buf, "%s%s", str, msg);
prof_win_show(plugin_win, buf);
} else if (strcmp(level, "info") == 0) {
create_win();
prof_win_focus(plugin_win);
prof_log_info(msg);
char *str = "called -> prof_log_info: ";
char buf[strlen(str) + strlen(msg)];
sprintf(buf, "%s%s", str, msg);
prof_win_show(plugin_win, buf);
} else if (strcmp(level, "warning") == 0) {
create_win();
prof_win_focus(plugin_win);
prof_log_warning(msg);
char *str = "called -> prof_log_warning: ";
char buf[strlen(str) + strlen(msg)];
sprintf(buf, "%s%s", str, msg);
prof_win_show(plugin_win, buf);
} else if (strcmp(level, "error") == 0) {
create_win();
prof_win_focus(plugin_win);
prof_log_error(msg);
char *str = "called -> prof_log_error: ";
char buf[strlen(str) + strlen(msg)];
sprintf(buf, "%s%s", str, msg);
prof_win_show(plugin_win, buf);
} else {
prof_cons_bad_cmd_usage("/c-test");
}
}
void
docount(void)
{
create_win();
prof_win_focus(plugin_win);
char buf[100];
sprintf(buf, "Count: %d", count);
prof_win_show(plugin_win, buf);
}
void
doping(char *jid)
{
if (jid == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
create_win();
prof_win_focus(plugin_win);
char *strstart = "<iq to='";
char *strend = "' type='get'><ping xmlns='urn:xmpp:ping'/></iq>";
char *idstart = "' id='cplugin-";
char idbuf[strlen(idstart) + 10];
sprintf(idbuf, "%s%d", idstart, ping_id);
char buf[strlen(strstart) + strlen(jid) + strlen(idbuf) + strlen(strend)];
sprintf(buf, "%s%s%s%s", strstart, jid, idbuf, strend);
int res = prof_send_stanza(buf);
ping_id++;
if (res) {
prof_win_show(plugin_win, "Ping sent successfully");
} else {
prof_win_show(plugin_win, "Error sending ping");
}
}
void
booleansetting(char *op, char *group, char *key, char *value_str)
{
if (op == NULL || group == NULL || key == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if ((strcmp(op, "get") != 0) && (strcmp(op, "set") != 0)) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if ((strcmp(op, "set") == 0) && (strcmp(value_str, "true") != 0) && (strcmp(value_str, "false") != 0)) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if (strcmp(op, "get") == 0) {
int dflt = 0;
create_win();
prof_win_focus(plugin_win);
int res = prof_settings_get_boolean(group, key, dflt);
if (res) {
prof_win_show(plugin_win, "Boolean setting: TRUE");
} else {
prof_win_show(plugin_win, "Boolean setting: FALSE");
}
} else if (strcmp(op, "set") == 0) {
int value = 0;
if (strcmp(value_str, "true") == 0) {
value = 1;
}
create_win();
prof_win_focus(plugin_win);
prof_settings_set_boolean(group, key, value);
char buf[5 + strlen(group) + 2 + strlen(key) + 4 + strlen(value_str)];
sprintf(buf, "Set [%s] %s to %s", group, key, value_str);
prof_win_show(plugin_win, buf);
}
}
void
stringsetting(char *op, char *group, char *key, char *value)
{
if (op == NULL || group == NULL || key == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if ((strcmp(op, "get") != 0) && (strcmp(op, "set") != 0)) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if ((strcmp(op, "set") == 0) && (value == NULL)) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if (strcmp(op, "get") == 0) {
create_win();
prof_win_focus(plugin_win);
char *res = prof_settings_get_string(group, key, NULL);
if (res) {
char buf[16 + strlen(res)];
sprintf(buf, "String setting: %s", res);
prof_win_show(plugin_win, buf);
} else {
prof_win_show(plugin_win, "String setting: NULL");
}
} else if (strcmp(op, "set") == 0) {
create_win();
prof_win_focus(plugin_win);
prof_settings_set_string(group, key, value);
char buf[5 + strlen(group) + 2 + strlen(key) + 4 + strlen(value)];
sprintf(buf, "Set [%s] %s to %s", group, key, value);
prof_win_show(plugin_win, buf);
}
}
void
intsetting(char *op, char *group, char *key, char *value)
{
if (op == NULL || group == NULL || key == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if ((strcmp(op, "get") != 0) && (strcmp(op, "set") != 0)) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if (strcmp(op, "get") == 0) {
create_win();
prof_win_focus(plugin_win);
int res = prof_settings_get_int(group, key, 0);
char buf[256];
sprintf(buf, "Integer setting: %d", res);
prof_win_show(plugin_win, buf);
} else if (strcmp(op, "set") == 0) {
create_win();
prof_win_focus(plugin_win);
int int_value = atoi(value);
prof_settings_set_int(group, key, int_value);
char buf[256];
sprintf(buf, "Set [%s] %s to %d", group, key, int_value);
prof_win_show(plugin_win, buf);
}
}
void
incomingmsg(char *barejid, char *resource, char *message)
{
prof_incoming_message(barejid, resource, message);
}
void
completer(char *op, char *item)
{
if (item == NULL) {
prof_cons_bad_cmd_usage("/c-test");
return;
}
if (strcmp(op, "add") == 0) {
create_win();
prof_win_focus(plugin_win);
char *ac[] = { item, NULL };
prof_completer_add("/c-test", ac);
char buf[256];
sprintf(buf, "Added \"%s\" to /c-test completer", item);
prof_win_show(plugin_win, buf);
prof_completer_add("/c-test completer remove", ac);
} else if (strcmp(op, "remove") == 0) {
create_win();
prof_win_focus(plugin_win);
char *ac[] = { item, NULL };
prof_completer_remove("/c-test", ac);
char buf[256];
sprintf(buf, "Removed \"%s\" from /c-test completer", item);
prof_win_show(plugin_win, buf);
prof_completer_remove("/c-test completer remove", ac);
} else {
prof_cons_bad_cmd_usage("/c-test");
}
}
void
cmd_ctest(char **args)
{
if (strcmp(args[0], "consalert") == 0) consalert();
else if (strcmp(args[0], "consshow") == 0) consshow(args[1]);
else if (strcmp(args[0], "consshow_t") == 0) consshow_t(args[1], args[2], args[3], args[4]);
else if (strcmp(args[0], "constest") == 0) constest();
else if (strcmp(args[0], "winshow") == 0) winshow(args[1]);
else if (strcmp(args[0], "winshow_t") == 0) winshow_t(args[1], args[2], args[3], args[4]);
else if (strcmp(args[0], "sendline") == 0) sendline(args[1]);
else if (strcmp(args[0], "notify") == 0) donotify(args[1]);
else if (strcmp(args[0], "get") == 0) getsubject(args[1]);
else if (strcmp(args[0], "log") == 0) logmsg(args[1], args[2]);
else if (strcmp(args[0], "count") == 0) docount();
else if (strcmp(args[0], "ping") == 0) doping(args[1]);
else if (strcmp(args[0], "boolean") == 0) booleansetting(args[1], args[2], args[3], args[4]);
else if (strcmp(args[0], "string") == 0) stringsetting(args[1], args[2], args[3], args[4]);
else if (strcmp(args[0], "int") == 0) intsetting(args[1], args[2], args[3], args[4]);
else if (strcmp(args[0], "incoming") == 0) incomingmsg(args[1], args[2], args[3]);
else if (strcmp(args[0], "completer") == 0) completer(args[1], args[2]);
else prof_cons_bad_cmd_usage("/c-test");
}
void
timed_callback(void)
{
create_win();
prof_win_show(plugin_win, "timed -> timed_callback called");
}
void
prof_init(const char * const version, const char * const status, const char *const account_name, const char *const fulljid)
{
pthread_create(&worker_thread, NULL, inc_counter, NULL);
prof_disco_add_feature("urn:xmpp:profanity:c_test_plugin");
prof_win_create(plugin_win, handle_win_input);
char buf[256];
if (account_name != NULL && fulljid != NULL) {
sprintf(buf, "fired -> prof_init: %s, %s, %s, %s", version, status, account_name, fulljid);
} else {
sprintf(buf, "fired -> prof_init: %s, %s", version, status);
}
prof_win_show(plugin_win, buf);
const char *synopsis[] = {
"/c-test consalert",
"/c-test consshow <message>",
"/c-test consshow_t <group> <key> <default> <message>",
"/c-test constest",
"/c-test winshow <message>",
"/c-test winshow_t <group> <key> <default> <message>",
"/c-test notify <message>",
"/c-test sendline <line>",
"/c-test get recipient|room|nick|occupants",
"/c-test log debug|info|warning|error <message>",
"/c-test count",
"/c-test ping <jid>",
"/c-test boolean get <group> <key>",
"/c-test boolean set <group> <key> <value>",
"/c-test string get <group> <key>",
"/c-test string set <group> <key> <value>",
"/c-test int get <group> <key>",
"/c-test int set <group> <key> <value>",
"/c-test incoming <barejid> <resource> <message>",
"/c-test completer add|remove <item>",
NULL
};
const char *description = "C test plugin. All commands focus the plugin window.";
const char *args[][2] = {
{ "consalert", "Highlight the console window in the status bar" },
{ "consshow <message>", "Show the message in the console window" },
{ "consshow_t <group> <key> <default> <message>", "Show the themed message in the console window. " },
{ "constest", "Show whether the command was run in the console." },
{ "winshow <message>", "Show the message in the plugin window" },
{ "winshow_t <group> <key> <default> <message>", "Show the themed message in the plugin window. " },
{ "notify <message>", "Send a desktop notification with message" },
{ "sendline <line>", "Pass line to profanity to process" },
{ "get recipient", "Show the current chat recipient, if in a chat window" },
{ "get room", "Show the current room JID, if in a chat room" },
{ "get nick", "Show nickname in current room, if in a chat room" },
{ "get occupants", "Show occupants in current room, if in a chat room" },
{ "log debug|info|warning|error <message>", "Log a message at the specified level" },
{ "count", "Show the counter, incremented every 5 seconds by a worker thread" },
{ "ping <jid>", "Send an XMPP ping to the specified Jabber ID" },
{ "boolean get <group> <key>", "Get a boolean setting" },
{ "boolean set <group> <key> <value>", "Set a boolean setting" },
{ "string get <group> <key>", "Get a string setting" },
{ "string set <group> <key> <value>", "Set a string setting" },
{ "int get <group> <key>", "Get a integer setting" },
{ "int set <group> <key> <value>", "Set a integer setting" },
{ "incoming <barejid> <resource> <message>", "Show an incoming message." },
{ "completer add <item>", "Add an autocomplete item to the /c-test command." },
{ "completer remove <item>", "Remove an autocomplete item from the /c-test command." },
{ NULL, NULL }
};
const char *examples[] = {
"/c-test sendline /about",
"/c-test log debug \"Test debug message\"",
"/c-test consshow_t c-test cons.show none \"This is themed\"",
"/c-test consshow_t none none bold_cyan \"This is bold_cyan\"",
"/c-test ping buddy@server.org",
NULL
};
prof_register_command("/c-test", 1, 5, synopsis, description, args, examples, cmd_ctest);
char *cmd_ac[] = {
"consalert",
"consshow",
"consshow_t",
"constest",
"winshow",
"winshow_t",
"notify",
"sendline",
"get",
"log",
"count",
"ping",
"boolean",
"string",
"int",
"incoming",
"completer",
NULL
};
prof_completer_add("/c-test", cmd_ac);
char *get_ac[] = { "recipient", "room", "nick", "occupants", NULL };
prof_completer_add("/c-test get", get_ac);
char *log_ac[] = { "debug", "info", "warning", "error", NULL };
prof_completer_add("/c-test log", log_ac);
char *boolean_ac[] = { "get", "set", NULL };
prof_completer_add("/c-test boolean", boolean_ac);
char *string_ac[] = { "get", "set", NULL };
prof_completer_add("/c-test string", string_ac);
char *int_ac[] = { "get", "set", NULL };
prof_completer_add("/c-test int", int_ac);
char *completer_ac[] = { "add", "remove", NULL };
prof_completer_add("/c-test completer", completer_ac);
prof_register_timed(timed_callback, 30);
}
void
prof_on_start(void)
{
create_win();
prof_win_show(plugin_win, "fired -> prof_on_start");
}
void
prof_on_shutdown(void)
{
create_win();
prof_win_show(plugin_win, "fired -> prof_on_shutdown");
}
void
prof_on_connect(const char * const account_name, const char * const fulljid)
{
create_win();
char *str = "fired -> prof_on_connect: ";
char buf[strlen(str) + strlen(account_name) + 2 + strlen(fulljid) + 1];
sprintf(buf, "%s%s, %s", str, account_name, fulljid);
prof_win_show(plugin_win, buf);
}
void
prof_on_disconnect(const char * const account_name, const char * const fulljid)
{
create_win();
char *str = "fired -> prof_on_disconnect: ";
char buf[strlen(str) + strlen(account_name) + 2 + strlen(fulljid) + 1];
sprintf(buf, "%s%s, %s", str, account_name, fulljid);
prof_win_show(plugin_win, buf);
}
char*
prof_pre_chat_message_display(const char * const jid, const char *message)
{
create_win();
char *str = "fired -> prof_pre_chat_message_display: ";
char buf[strlen(str) + strlen(jid) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s", str, jid, message);
prof_win_show(plugin_win, buf);
return NULL;
}
void
prof_post_chat_message_display(const char * const jid, const char *message)
{
create_win();
char *str = "fired -> prof_post_chat_message_display: ";
char buf[strlen(str) + strlen(jid) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s", str, jid, message);
prof_win_show(plugin_win, buf);
}
char*
prof_pre_chat_message_send(const char * const jid, const char *message)
{
create_win();
char *str = "fired -> prof_pre_chat_message_send: ";
char buf[strlen(str) + strlen(jid) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s", str, jid, message);
prof_win_show(plugin_win, buf);
return NULL;
}
void
prof_post_chat_message_send(const char * const jid, const char *message)
{
create_win();
char *str = "fired -> prof_post_chat_message_send: ";
char buf[strlen(str) + strlen(jid) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s", str, jid, message);
prof_win_show(plugin_win, buf);
}
char*
prof_pre_room_message_display(const char * const room, const char * const nick, const char *message)
{
create_win();
char *str = "fired -> prof_pre_room_message_display: ";
char buf[strlen(str) + strlen(room) + 2 + strlen(nick) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s, %s", str, room, nick, message);
prof_win_show(plugin_win, buf);
return NULL;
}
void
prof_post_room_message_display(const char * const room, const char * const nick, const char *message)
{
create_win();
char *str = "fired -> prof_post_room_message_display: ";
char buf[strlen(str) + strlen(room) + 2 + strlen(nick) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s, %s", str, room, nick, message);
prof_win_show(plugin_win, buf);
}
char *
prof_pre_room_message_send(const char * const room, const char *message)
{
create_win();
char *str = "fired -> prof_pre_room_message_send: ";
char buf[strlen(str) + strlen(room) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s", str, room, message);
prof_win_show(plugin_win, buf);
return NULL;
}
void
prof_post_room_message_send(const char * const room, const char *message)
{
create_win();
char *str = "fired -> prof_post_room_message_send: ";
char buf[strlen(str) + strlen(room) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s", str, room, message);
prof_win_show(plugin_win, buf);
}
void
prof_on_room_history_message(const char * const room, const char *const nick, const char *const message, const char *const timestamp)
{
create_win();
char *str = "fired -> prof_on_room_history_message: ";
if (timestamp == NULL) {
char buf[strlen(str) + strlen(room) + 2 + strlen(nick) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s, %s", str, room, nick, message);
prof_win_show(plugin_win, buf);
} else {
char buf[strlen(str) + strlen(room) + 2 + strlen(nick) + 2 + strlen(message) + 2 + strlen(timestamp) + 1];
sprintf(buf, "%s%s, %s, %s, %s", str, room, nick, message, timestamp);
prof_win_show(plugin_win, buf);
}
}
char *
prof_pre_priv_message_display(const char * const room, const char * const nick, const char *message)
{
create_win();
char *str = "fired -> prof_pre_priv_message_display: ";
char buf[strlen(str) + strlen(room) + 2 + strlen(nick) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s, %s", str, room, nick, message);
prof_win_show(plugin_win, buf);
return NULL;
}
void
prof_post_priv_message_display(const char * const room, const char * const nick, const char *message)
{
create_win();
char *str = "fired -> prof_post_priv_message_display: ";
char buf[strlen(str) + strlen(room) + 2 + strlen(nick) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s, %s", str, room, nick, message);
prof_win_show(plugin_win, buf);
}
char *
prof_pre_priv_message_send(const char * const room, const char * const nick, const char *message)
{
create_win();
char *str = "fired -> prof_pre_priv_message_send: ";
char buf[strlen(str) + strlen(room) + 2 + strlen(nick) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s, %s", str, room, nick, message);
prof_win_show(plugin_win, buf);
return NULL;
}
void
prof_post_priv_message_send(const char * const room, const char * const nick, const char *message)
{
create_win();
char *str = "fired -> prof_post_priv_message_send: ";
char buf[strlen(str) + strlen(room) + 2 + strlen(nick) + 2 + strlen(message) + 1];
sprintf(buf, "%s%s, %s, %s", str, room, nick, message);
prof_win_show(plugin_win, buf);
}
char*
prof_on_message_stanza_send(const char *const stanza)
{
create_win();
char *str = "fired -> prof_on_message_stanza_send: ";
char buf[strlen(str) + strlen(stanza) + 1];
sprintf(buf, "%s%s", str, stanza);
prof_win_show(plugin_win, buf);
return NULL;
// char *new_stanza = strdup("<message id='ktx72v49' to='chanleemoon@ejabberd.local' type='chat' xml:lang='en'><body>Art thou not Romeo, and a Montague?</body></message>");
// return (char *)new_stanza;
}
int
prof_on_message_stanza_receive(const char *const stanza)
{
create_win();
char *str = "fired -> prof_on_message_stanza_receive: ";
char buf[strlen(str) + strlen(stanza) + 1];
sprintf(buf, "%s%s", str, stanza);
prof_win_show(plugin_win, buf);
return 1;
}
char*
prof_on_presence_stanza_send(const char *const stanza)
{
create_win();
char *str = "fired -> prof_on_presence_stanza_send: ";
char buf[strlen(str) + strlen(stanza) + 1];
sprintf(buf, "%s%s", str, stanza);
prof_win_show(plugin_win, buf);
return NULL;
}
int
prof_on_presence_stanza_receive(const char *const stanza)
{
create_win();
char *str = "fired -> prof_on_presence_stanza_receive: ";
char buf[strlen(str) + strlen(stanza) + 1];
sprintf(buf, "%s%s", str, stanza);
prof_win_show(plugin_win, buf);
return 1;
}
char*
prof_on_iq_stanza_send(const char *const stanza)
{
create_win();
char *str = "fired -> prof_on_iq_stanza_send: ";
char buf[strlen(str) + strlen(stanza) + 1];
sprintf(buf, "%s%s", str, stanza);
prof_win_show(plugin_win, buf);
return NULL;
}
int
prof_on_iq_stanza_receive(const char *const stanza)
{
create_win();
char *str = "fired -> prof_on_iq_stanza_receive: ";
char buf[strlen(str) + strlen(stanza) + 1];
sprintf(buf, "%s%s", str, stanza);
prof_win_show(plugin_win, buf);
return 1;
}
void
prof_on_contact_offline(const char *const barejid, const char *const resource, const char *const status)
{
create_win();
char *str = "fired -> prof_on_contact_offline: ";
int status_len = status == NULL ? 0 : strlen(status) + 2;
char buf[strlen(str) + strlen(barejid) + 1 + strlen(resource) + 1 + status_len + 1];
if (status) {
sprintf(buf, "%s%s/%s \"%s\"", str, barejid, resource, status);
} else {
sprintf(buf, "%s%s/%s", str, barejid, resource);
}
prof_win_show(plugin_win, buf);
}
void
prof_on_contact_presence(const char *const barejid, const char *const resource, const char *const presence, const char *const status, const int priority)
{
create_win();
char *str = "fired -> prof_on_contact_presence: ";
int status_len = status == NULL ? 0 : strlen(status) + 2;
char buf[strlen(str) + strlen(barejid) + 1 + strlen(resource) + 1 + strlen(presence) + 1 + 10 + status_len + 1];
if (status) {
sprintf(buf, "%s%s/%s %s %d \"%s\"", str, barejid, resource, presence, priority, status);
} else {
sprintf(buf, "%s%s/%s %s %d", str, barejid, resource, presence, priority);
}
prof_win_show(plugin_win, buf);
}
void
prof_on_chat_win_focus(const char *const barejid)
{
create_win();
char *str = "fired -> prof_on_chat_win_focus: ";
char buf[strlen(str) + strlen(barejid) + 1];
sprintf(buf, "%s%s", str, barejid);
prof_win_show(plugin_win, buf);
}
void
prof_on_room_win_focus(const char *const roomjid)
{
create_win();
char *str = "fired -> prof_on_room_win_focus: ";
char buf[strlen(str) + strlen(roomjid) + 1];
sprintf(buf, "%s%s", str, roomjid);
prof_win_show(plugin_win, buf);
}

View File

@@ -78,8 +78,8 @@ def _search(search_terms):
if len(results) > 0:
prof.win_show_themed(win, "wikipedia", "search", None, "Search results for \"" + search_terms + "\":")
for index, result in enumerate(results):
page_ac.append(result)
prof.win_show_themed(win, "wikipedia", "search.results", None, result)
page_ac.append(result.encode("utf-8"))
prof.win_show_themed(win, "wikipedia", "search.results", None, result.encode("utf-8"))
_update_autocomplete()
else:
prof.win_show_themed(win, "wikipedia", "search.noresults", None, "No search results found for \"" + search_terms + "\"")
@@ -98,14 +98,14 @@ def _summary(page_str):
prof.win_focus(win)
return
link_ac.append(page.url)
link_ac.append(page.url.encode("utf-8"))
prof.completer_add("/wikipedia open", link_ac)
prof.win_show_themed(win, "wikipedia", "summary.title", None, page.title)
prof.win_show_themed(win, "wikipedia", "summary.url", None, page.url)
prof.win_show_themed(win, "wikipedia", "summary.title", None, page.title.encode("utf-8"))
prof.win_show_themed(win, "wikipedia", "summary.url", None, page.url.encode("utf-8"))
summary = wikipedia.summary(page_str)
prof.win_show_themed(win, "wikipedia", "summary.text", None, summary)
prof.win_show_themed(win, "wikipedia", "summary.text", None, summary.encode("utf-8"))
prof.win_show(win, "")
prof.win_focus(win)
@@ -119,8 +119,8 @@ def _page(page_str):
prof.win_focus(win)
return
prof.win_show_themed(win, "wikipedia", "page.title", None, page.title)
prof.win_show_themed(win, "wikipedia", "page.text", None, page.content)
prof.win_show_themed(win, "wikipedia", "page.title", None, page.title.encode("utf-8"))
prof.win_show_themed(win, "wikipedia", "page.text", None, page.content.encode("utf-8"))
prof.win_show(win, "")
prof.win_focus(win)
@@ -138,8 +138,8 @@ def _images(page_str):
prof.win_show_themed(win, "wikipedia", "images", None, "Images for " + page_str)
for image in page.images:
prof.win_show_themed(win, "wikipedia", "images.url", None, image)
link_ac.append(image)
prof.win_show_themed(win, "wikipedia", "images.url", None, image.encode("utf-8"))
link_ac.append(image.encode("utf-8"))
prof.completer_add("/wikipedia open", link_ac)
prof.win_show(win, "")
prof.win_focus(win)
@@ -159,8 +159,8 @@ def _links(page_str):
prof.win_show_themed(win, "wikipedia", "links", None, "Links for " + page_str)
for link in page.links:
prof.win_show_themed(win, "wikipedia", "links.link", None, link)
page_ac.append(link)
prof.win_show_themed(win, "wikipedia", "links.link", None, link.encode("utf-8"))
page_ac.append(link.encode("utf-8"))
_update_autocomplete()
prof.win_show(win, "")
prof.win_focus(win)
@@ -179,8 +179,8 @@ def _refs(page_str):
prof.win_show_themed(win, "wikipedia", "refs", None, "References for " + page_str)
for ref in page.references:
prof.win_show_themed(win, "wikipedia", "refs.url", None, ref)
link_ac.append(ref)
prof.win_show_themed(win, "wikipedia", "refs.url", None, ref.encode("utf-8"))
link_ac.append(ref.encode("utf-8"))
prof.completer_add("/wikipedia open", link_ac)
prof.win_show(win, "")
prof.win_focus(win)