Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
39781d0149
|
|||
|
94e9b155ef
|
|||
|
38b8a2fdf6
|
|||
|
325316c3b2
|
|||
|
68800cf823
|
|||
|
|
179785ce4e | ||
|
|
ade6439157 | ||
|
|
0812b6c1e8 | ||
|
|
92e708a10b | ||
|
|
255846954a | ||
|
|
339ad336e2 | ||
|
|
2bff450e80 | ||
|
|
e53ecc76da | ||
|
|
6559d236d1 | ||
|
|
f8587a26ca | ||
|
|
65a820b27a | ||
|
|
c3a9420ad7 | ||
|
|
0ef7d9d1de | ||
|
|
eb87f21922 | ||
|
|
3972260733 | ||
|
|
efa13667ca | ||
|
|
f5956dddf0 | ||
|
|
ab9a606473 | ||
|
|
6567aae509 | ||
|
|
a479d2da7d | ||
|
|
2d9793cf32 | ||
|
|
55da731bf6 | ||
|
|
fdb6d70f63 | ||
|
|
19ff42c94f | ||
|
|
8a3089b5be | ||
|
|
d69df863fe | ||
|
|
c49d29846c | ||
|
|
16cb1452c0 | ||
|
|
4f9f513c08 | ||
|
|
9fb6ea1cf3 | ||
|
|
c0eefa2ff7 | ||
|
|
e84dd620c8 | ||
|
|
ce1c777338 | ||
|
|
148c4b3ee1 | ||
|
|
3f6ac94acb | ||
|
|
3bbe6c6202 | ||
|
|
ff4b5198d8 | ||
|
|
e2ab722f93 | ||
|
|
3f11319a5b |
16
ChatStart.rb
16
ChatStart.rb
@@ -1,16 +0,0 @@
|
||||
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
|
||||
138
README.md
138
README.md
@@ -1,126 +1,56 @@
|
||||
profanity-plugins
|
||||
=================
|
||||
# CProof Plugins
|
||||
|
||||
Plugin support for Profanity is currently in development.
|
||||
[Website documentation](https://jabber.space/guides/plugins/)
|
||||
|
||||
* 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.
|
||||
`Master` is a development branch compatible with the latest version of CProof.
|
||||
|
||||
For a list of outstanding issues before releasing 0.5.0, see: https://github.com/boothj5/profanity/milestones/0.5.0
|
||||
## Installing plugins
|
||||
|
||||
* [Website documentation](http://www.profanity.im/plugins.html)
|
||||
Use the `/plugins install` command, e.g.
|
||||
|
||||
Building Profanity with plugin support
|
||||
--------------------------------------
|
||||
```bash
|
||||
/plugins install ~/path-to/sounds.py
|
||||
```
|
||||
|
||||
Additional dependencies required:
|
||||
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:
|
||||
|
||||
```
|
||||
autoconf-archive
|
||||
libtool
|
||||
```
|
||||
|
||||
Plus the development packages for supported languages:
|
||||
For Python plugins the following is also required:
|
||||
|
||||
```
|
||||
python-dev
|
||||
lua-dev
|
||||
ruby-dev
|
||||
```
|
||||
|
||||
Build with:
|
||||
## Developing plugins
|
||||
|
||||
```
|
||||
./bootstrap.sh
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
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/)
|
||||
|
||||
After building, run `profanity -v` to see which language support is available, example output:
|
||||
## External plugins (C)
|
||||
|
||||
```
|
||||
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
|
||||
------------------
|
||||
|
||||
Use the `/plugins install` command, e.g.
|
||||
|
||||
```
|
||||
/plugins install ~/projects-git/profanity-plugins/say.py
|
||||
```
|
||||
|
||||
See the `/help plugins` command for further plugin management options.
|
||||
|
||||
More 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
|
||||
```
|
||||
|
||||
Plugin settings
|
||||
---------------
|
||||
|
||||
The plugins API also includes functions to read and write settings. The settings are stored in:
|
||||
|
||||
```
|
||||
~/.local/share/profanity/plugin_settings
|
||||
```
|
||||
|
||||
For example ([say.py](https://github.com/boothj5/profanity-plugins/blob/master/say.py) plugin):
|
||||
|
||||
```
|
||||
[say]
|
||||
args=-v english -s 120
|
||||
enabled=active
|
||||
```
|
||||
|
||||
Developing plugins
|
||||
------------------
|
||||
|
||||
API Documentation:
|
||||
* [Python API](http://www.profanity.im/plugins/python/html/prof.html)
|
||||
* [Python hooks](http://www.profanity.im/plugins/python/html/plugin.html)
|
||||
* [C API](http://www.profanity.im/plugins/c/html/profapi_8h.html)
|
||||
* [C hooks](http://www.profanity.im/plugins/c/html/profhooks_8h.html)
|
||||
|
||||
Example test plugins (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)
|
||||
- [profanity-plugins by Reimar](https://github.com/ReimarPB/profanity-plugins) - Collection of plugins
|
||||
|
||||
|
||||
## External plugins (Python)
|
||||
|
||||
- [profanity-copy-message-plugin](https://github.com/lonski/profanity-copy-message-plugin) - Copy messages to clipboard.
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
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(barejid, resource, message):
|
||||
if bot_state:
|
||||
if barejid not in bot_session:
|
||||
bot_session[barejid] = bot.create_session()
|
||||
response = bot_session[barejid].think(message)
|
||||
prof.send_line("/msg " + barejid + " " + 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" ])
|
||||
@@ -1,190 +0,0 @@
|
||||
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 ''
|
||||
@@ -1,27 +0,0 @@
|
||||
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 presence_notify.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 ../..
|
||||
|
||||
|
||||
30
emoticons.py
30
emoticons.py
@@ -1,30 +0,0 @@
|
||||
"""
|
||||
Convert smileys ( :) :( etc) to the Unicode character representation
|
||||
|
||||
Dependencies:
|
||||
pip install future
|
||||
"""
|
||||
|
||||
import prof
|
||||
from builtins import chr
|
||||
|
||||
|
||||
def _emote(input_str):
|
||||
result = input_str
|
||||
result = result.replace(":-)", chr(9786))
|
||||
result = result.replace(":)", chr(9786))
|
||||
result = result.replace(":-(", chr(9785))
|
||||
result = result.replace(":(", chr(9785))
|
||||
return result
|
||||
|
||||
|
||||
def prof_pre_chat_message_display(barejid, resource, message):
|
||||
return _emote(message)
|
||||
|
||||
|
||||
def prof_pre_room_message_display(barejid, nick, message):
|
||||
return _emote(message)
|
||||
|
||||
|
||||
def prof_pre_priv_message_display(barejid, nick, message):
|
||||
return _emote(message)
|
||||
531
jenkins.py
531
jenkins.py
@@ -1,531 +0,0 @@
|
||||
"""
|
||||
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.")
|
||||
27
paste.py
27
paste.py
@@ -1,27 +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():
|
||||
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)
|
||||
198
sounds.py
198
sounds.py
@@ -1,198 +0,0 @@
|
||||
"""
|
||||
Play sounds on various events.
|
||||
Requires mpg123 to be installed on the host system:
|
||||
sudo apt-get install mpg123
|
||||
brew install mpg123
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
from os.path import expanduser
|
||||
|
||||
import prof
|
||||
|
||||
|
||||
def _play_sound(soundfile):
|
||||
if soundfile.startswith("~/"):
|
||||
soundfilenew = soundfile.replace("~", expanduser("~"), 1)
|
||||
else:
|
||||
soundfilenew = soundfile
|
||||
subprocess.Popen(["mpg123", "-q", soundfilenew])
|
||||
|
||||
|
||||
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")
|
||||
if chatsound or roomsound or privatesound:
|
||||
if enabled:
|
||||
prof.cons_show("Sounds: ON")
|
||||
else:
|
||||
prof.cons_show("Sounds: OFF")
|
||||
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 == "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)
|
||||
else:
|
||||
prof.cons_bad_cmd_usage("/sounds")
|
||||
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
prof.cons_bad_cmd_usage("/sounds")
|
||||
|
||||
|
||||
def prof_init(version, status, account_name, fulljid):
|
||||
synopsis = [
|
||||
"/sounds",
|
||||
"/sounds on|off",
|
||||
"/sounds set chat <file>",
|
||||
"/sounds set room <file>",
|
||||
"/sounds set private <file>",
|
||||
"/sounds clear chat",
|
||||
"/sounds clear room",
|
||||
"/sounds clear private",
|
||||
"/sounds rooms add <roomjid>"
|
||||
]
|
||||
description = "Play mp3 sounds on various Profanity events. Calling with no args shows current sound files."
|
||||
args = [
|
||||
[ "on|off", "Enable or disable playing sounds." ],
|
||||
[ "set chat <file>", "Path to mp3 file to play on chat messages." ],
|
||||
[ "set room <file>", "Path to mp3 file to play on room messages." ],
|
||||
[ "set private <file>", "Path to mp3 file to play on private room messages." ],
|
||||
[ "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 chat ~/sounds/woof.mp3",
|
||||
"/sounds set room ~/sounds/meow.mp3",
|
||||
"/sounds set private ~/sounds/shhh.mp3",
|
||||
"/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" ])
|
||||
prof.completer_add("/sounds set", [ "chat", "room", "private" ])
|
||||
prof.completer_add("/sounds clear", [ "chat", "room", "private" ])
|
||||
prof.completer_add("/sounds rooms", [ "add", "remove", "clear" ])
|
||||
|
||||
|
||||
def prof_post_chat_message_display(barejid, resource, message):
|
||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
soundfile = prof.settings_string_get("sounds", "chat", None)
|
||||
if not soundfile:
|
||||
return
|
||||
|
||||
_play_sound(soundfile)
|
||||
|
||||
|
||||
def prof_post_room_message_display(barejid, nick, message):
|
||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
soundfile = prof.settings_string_get("sounds", "room", None)
|
||||
if not soundfile:
|
||||
return
|
||||
|
||||
roomlist = prof.settings_string_list_get("sounds", "rooms")
|
||||
if roomlist and (barejid not in roomlist):
|
||||
return
|
||||
|
||||
_play_sound(soundfile)
|
||||
|
||||
|
||||
def prof_post_priv_message_display(barejid, nick, message):
|
||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
soundfile = prof.settings_string_get("sounds", "private", None)
|
||||
if soundfile:
|
||||
_play_sound(soundfile)
|
||||
|
||||
276
src/ai.py
Normal file
276
src/ai.py
Normal file
@@ -0,0 +1,276 @@
|
||||
"""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
|
||||
549
src/antispam.py
Normal file
549
src/antispam.py
Normal file
@@ -0,0 +1,549 @@
|
||||
"""
|
||||
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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""
|
||||
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
|
||||
|
||||
110
src/autocorrector.py
Normal file
110
src/autocorrector.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
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" ])
|
||||
|
||||
@@ -72,7 +72,7 @@ def prof_init(version, status, account_name, fulljid):
|
||||
[ "<url>", "URL to open in the browser" ]
|
||||
]
|
||||
examples = [
|
||||
"/browser http://www.profanity.im"
|
||||
"/browser http://jabber.space/"
|
||||
]
|
||||
|
||||
prof.register_command("/browser", 0, 1, synopsis, description, args, examples, _cmd_browser)
|
||||
7
src/c/moj/.gitignore
vendored
Normal file
7
src/c/moj/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
moj
|
||||
moj.exe
|
||||
*.o
|
||||
*.so
|
||||
gen/lookup.c
|
||||
gen/emoji.json
|
||||
gen/emoji.csv
|
||||
15
src/c/moj/COPYING
Normal file
15
src/c/moj/COPYING
Normal file
@@ -0,0 +1,15 @@
|
||||
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.
|
||||
68
src/c/moj/Makefile
Normal file
68
src/c/moj/Makefile
Normal file
@@ -0,0 +1,68 @@
|
||||
# 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
|
||||
28
src/c/moj/README.md
Normal file
28
src/c/moj/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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.
|
||||
1810
src/c/moj/gen/emoji.gperf
Normal file
1810
src/c/moj/gen/emoji.gperf
Normal file
File diff suppressed because it is too large
Load Diff
1646
src/c/moj/gen/emoji_list.h
Normal file
1646
src/c/moj/gen/emoji_list.h
Normal file
File diff suppressed because it is too large
Load Diff
15
src/c/moj/src/gen_gperf.sh
Executable file
15
src/c/moj/src/gen_gperf.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/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;}'
|
||||
26
src/c/moj/src/gen_list.sh
Executable file
26
src/c/moj/src/gen_list.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/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
|
||||
|
||||
124
src/c/moj/src/main.c
Normal file
124
src/c/moj/src/main.c
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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;
|
||||
}
|
||||
98
src/c/moj/src/moj.c
Normal file
98
src/c/moj/src/moj.c
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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;
|
||||
}
|
||||
5
src/c/moj/src/moj.h
Normal file
5
src/c/moj/src/moj.h
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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);
|
||||
93
src/c/moj/src/process.awk
Normal file
93
src/c/moj/src/process.awk
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/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";
|
||||
}
|
||||
10
src/c/moj/src/profanity_emoji.c
Normal file
10
src/c/moj/src/profanity_emoji.c
Normal file
@@ -0,0 +1,10 @@
|
||||
// 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.
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Simple plugin to display the PID of the Profanity process and its parent
|
||||
Simple plugin to display the PID of the CProof process and its parent
|
||||
|
||||
Theme example in ~/.local/share/profanity/plugin_themes
|
||||
|
||||
33
src/emoticons.py
Normal file
33
src/emoticons.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
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)
|
||||
167
src/history.py
Normal file
167
src/history.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""
|
||||
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}")
|
||||
47
src/htmlstrip.py
Normal file
47
src/htmlstrip.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# -*- 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 <, >, 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)
|
||||
@@ -5,7 +5,6 @@ Requires scrot installed for screenshot (on linux)
|
||||
sudo apt-get install scrot
|
||||
|
||||
Uses built in screencapture on OSX
|
||||
|
||||
"""
|
||||
|
||||
import prof
|
||||
@@ -59,3 +58,4 @@ def prof_init(version, status, account_name, fulljid):
|
||||
|
||||
prof.register_command("/imgur", 1, 1, synopsis, description, args, examples, _cmd_imgur)
|
||||
prof.completer_add("/imgur", [ "screenshot" ])
|
||||
prof.filepath_completer_add("/imgur")
|
||||
137
src/notifycmd.py
Normal file
137
src/notifycmd.py
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/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" ])
|
||||
60
src/paste.py
Normal file
60
src/paste.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
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" ])
|
||||
144
src/sanitize.py
Normal file
144
src/sanitize.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""
|
||||
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)
|
||||
@@ -9,15 +9,16 @@ 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", "")
|
||||
|
||||
if platform == "darwin":
|
||||
os.system("say " + args + " '" + message + "' 2>/dev/null")
|
||||
os.system("say " + args + " " + shlex.quote(message) + " 2>/dev/null")
|
||||
elif platform == "linux" or platform == "linux2":
|
||||
os.system("echo '" + message + "' | espeak " + args + " 2>/dev/null")
|
||||
os.system("echo " + shlex.quote(message) + " | espeak " + args + " 2>/dev/null")
|
||||
|
||||
|
||||
def prof_post_chat_message_display(barejid, resource, message):
|
||||
128
src/shortcuts.py
Normal file
128
src/shortcuts.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# -*- 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)
|
||||
313
src/sounds.py
Normal file
313
src/sounds.py
Normal file
@@ -0,0 +1,313 @@
|
||||
"""
|
||||
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")
|
||||
129
src/syscmd.py
Normal file
129
src/syscmd.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
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
79
syscmd.py
@@ -1,79 +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 _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" ])
|
||||
@@ -1,161 +0,0 @@
|
||||
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
|
||||
@@ -1,23 +0,0 @@
|
||||
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
|
||||
@@ -1,116 +0,0 @@
|
||||
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
|
||||
@@ -1,692 +0,0 @@
|
||||
import prof
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
plugin_win = "Python Test"
|
||||
|
||||
count = 0
|
||||
ping_id = 1
|
||||
count_thread = None
|
||||
thread_stop = None
|
||||
|
||||
def _inc_counter():
|
||||
global count
|
||||
global thread_stop
|
||||
|
||||
while not thread_stop.is_set():
|
||||
time.sleep(5)
|
||||
count = count + 1
|
||||
|
||||
|
||||
def _handle_win_input(win, line):
|
||||
prof.win_show(win, "Input received: " + line)
|
||||
|
||||
|
||||
def _consalert():
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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()
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
prof.log_debug(msg)
|
||||
prof.win_show(plugin_win, "called -> prof.log_debug: " + msg)
|
||||
elif level == "info":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
prof.log_info(msg)
|
||||
prof.win_show(plugin_win, "called -> prof.log_info: " + msg)
|
||||
elif level == "warning":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
prof.log_warning(msg)
|
||||
prof.win_show(plugin_win, "called -> prof.log_warning: " + msg)
|
||||
elif level == "error":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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():
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
res = prof.settings_boolean_get(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
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
prof.settings_boolean_set(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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
res = prof.settings_string_get(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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
prof.settings_string_set(group, key, value)
|
||||
prof.win_show(plugin_win, "Set [" + group + "] " + key + " to " + value)
|
||||
|
||||
|
||||
def _string_list(op, group, key, value):
|
||||
if op != "get" and op != "add" and op !="remove" and op != "remove_all":
|
||||
prof.cons_bad_cmd_usage("/python-test")
|
||||
return
|
||||
|
||||
if op == "get":
|
||||
if group == None or key == None:
|
||||
prof.cons_bad_cmd_usage("/python-test")
|
||||
return
|
||||
res = prof.settings_string_list_get(group, key)
|
||||
prof.win_focus(plugin_win)
|
||||
if res is None:
|
||||
prof.win_show(plugin_win, "No list found")
|
||||
return
|
||||
prof.win_show(plugin_win, "String list:")
|
||||
for el in res:
|
||||
prof.win_show(plugin_win, " " + el)
|
||||
return
|
||||
|
||||
if op == "add":
|
||||
if group == None or key == None or value == None:
|
||||
prof.cons_bad_cmd_usage("/python-test")
|
||||
return
|
||||
prof.settings_string_list_add(group, key, value)
|
||||
prof.win_focus(plugin_win)
|
||||
prof.win_show(plugin_win, "Added '" + value + "' to [" + group + "]" + " " + key)
|
||||
return
|
||||
|
||||
if op == "remove":
|
||||
if group == None or key == None or value == None:
|
||||
prof.cons_bad_cmd_usage("/python-test")
|
||||
return
|
||||
res = prof.settings_string_list_remove(group, key, value)
|
||||
prof.win_focus(plugin_win)
|
||||
if res:
|
||||
prof.win_show(plugin_win, "Removed '" + value + "' from [" + group + "]" + " " + key)
|
||||
else:
|
||||
prof.win_show(plugin_win, "Error removing string item from list")
|
||||
return;
|
||||
|
||||
if op == "remove_all":
|
||||
if group == None or key == None:
|
||||
prof.cons_bad_cmd_usage("/python-test")
|
||||
return
|
||||
res = prof.settings_string_list_clear(group, key)
|
||||
prof.win_focus(plugin_win)
|
||||
if res:
|
||||
prof.win_show(plugin_win, "Removed all items from [" + group + "]" + " " + key)
|
||||
else:
|
||||
prof.win_show(plugin_win, "Error removing list")
|
||||
return
|
||||
|
||||
|
||||
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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
res = prof.settings_int_get(group, key, 0)
|
||||
prof.win_show(plugin_win, "Integer setting: " + str(res))
|
||||
elif op == "set":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_focus(plugin_win)
|
||||
prof.settings_int_set(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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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":
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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 == "string_list": _string_list(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():
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "timed -> timed_callback called")
|
||||
|
||||
|
||||
def prof_init(version, status, account_name, fulljid):
|
||||
global count
|
||||
global ping_id
|
||||
global thread_stop
|
||||
|
||||
count = 0
|
||||
ping_id = 1
|
||||
|
||||
thread_stop = threading.Event()
|
||||
count_thread = threading.Thread(target=_inc_counter)
|
||||
count_thread.daemon = True
|
||||
count_thread.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 string_list get <group> <key>",
|
||||
"/python-test string_list add <group> <key> <value>",
|
||||
"/python-test string_list remove <group> <key> <value>",
|
||||
"/python-test string_list remove_all <group> <key>",
|
||||
"/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" ],
|
||||
[ "string_list get <group> <key>", "Get a string list setting" ],
|
||||
[ "string_list add <group> <key> <value>", "Add a string to a string list setting" ],
|
||||
[ "string_list remove <group> <key> <value>", "Remove a string from a string list setting" ],
|
||||
[ "string_list remove_all <group> <key>", "Remove all strings from a string list 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",
|
||||
"string_list",
|
||||
"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 string_list",
|
||||
[ "get", "add", "remove", "remove_all" ]
|
||||
)
|
||||
prof.completer_add("/python-test int",
|
||||
[ "get", "set" ]
|
||||
)
|
||||
prof.completer_add("/python-test completer",
|
||||
[ "add", "remove" ]
|
||||
)
|
||||
|
||||
prof.register_timed(timed_callback, 5)
|
||||
|
||||
|
||||
def prof_on_start():
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_start")
|
||||
|
||||
|
||||
def prof_on_shutdown():
|
||||
global thread_stop
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_shutdown")
|
||||
thread_stop.set()
|
||||
count_thread.join()
|
||||
|
||||
|
||||
def prof_on_unload():
|
||||
global thread_stop
|
||||
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_unload")
|
||||
thread_stop.set()
|
||||
count_thread.join()
|
||||
|
||||
|
||||
def prof_on_connect(account_name, fulljid):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_connect: " + account_name + ", " + fulljid)
|
||||
|
||||
|
||||
def prof_on_disconnect(account_name, fulljid):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_disconnect: " + account_name + ", " + fulljid)
|
||||
|
||||
|
||||
def prof_pre_chat_message_display(barejid, resource, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_pre_chat_message_display: " + barejid + "/" + resource + ", " + message)
|
||||
|
||||
|
||||
def prof_post_chat_message_display(barejid, resource, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_post_chat_message_display: " + barejid + "/" + resource + ", " + message)
|
||||
|
||||
|
||||
def prof_pre_chat_message_send(barejid, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_pre_chat_message_send: " + barejid + ", " + message)
|
||||
|
||||
|
||||
def prof_post_chat_message_send(barejid, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_post_chat_message_send: " + barejid + ", " + message)
|
||||
|
||||
|
||||
def prof_pre_room_message_display(barejid, nick, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_pre_room_message_display: " + barejid + ", " + nick + ", " + message)
|
||||
|
||||
|
||||
def prof_post_room_message_display(barejid, nick, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_post_room_message_display: " + barejid + ", " + nick + ", " + message)
|
||||
|
||||
|
||||
def prof_pre_room_message_send(barejid, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_pre_room_message_send: " + barejid + ", " + message)
|
||||
|
||||
|
||||
def prof_post_room_message_send(barejid, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_post_room_message_send: " + barejid + ", " + message)
|
||||
|
||||
|
||||
def prof_on_room_history_message(barejid, nick, message, timestamp):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
if timestamp:
|
||||
prof.win_show(plugin_win, "fired -> prof_on_room_history_message: " + barejid + ", " + nick + ", " + message + ", " + timestamp)
|
||||
else:
|
||||
prof.win_show(plugin_win, "fired -> prof_on_room_history_message: " + barejid + ", " + nick + ", " + message)
|
||||
|
||||
|
||||
def prof_pre_priv_message_display(barejid, nick, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_pre_priv_message_display: " + barejid + ", " + nick + ", " + message)
|
||||
|
||||
|
||||
def prof_post_priv_message_display(barejid, nick, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_post_priv_message_display: " + barejid + ", " + nick + ", " + message)
|
||||
|
||||
|
||||
def prof_pre_priv_message_send(barejid, nick, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_pre_priv_message_send: " + barejid + ", " + nick + ", " + message)
|
||||
|
||||
|
||||
def prof_post_priv_message_send(barejid, nick, message):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_post_priv_message_send: " + barejid + ", " + nick + ", " + message)
|
||||
|
||||
|
||||
def prof_on_message_stanza_send(stanza):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_message_stanza_send: " + stanza)
|
||||
|
||||
|
||||
def prof_on_message_stanza_receive(stanza):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_message_stanza_receive: " + stanza)
|
||||
return True
|
||||
|
||||
|
||||
def prof_on_presence_stanza_send(stanza):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_presence_stanza_send: " + stanza)
|
||||
|
||||
|
||||
def prof_on_presence_stanza_receive(stanza):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_presence_stanza_receive: " + stanza)
|
||||
return True
|
||||
|
||||
|
||||
def prof_on_iq_stanza_send(stanza):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_iq_stanza_send: " + stanza)
|
||||
|
||||
|
||||
def prof_on_iq_stanza_receive(stanza):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_iq_stanza_receive: " + stanza)
|
||||
return True
|
||||
|
||||
|
||||
def prof_on_contact_offline(barejid, resource, status):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
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):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_chat_win_focus: " + barejid)
|
||||
|
||||
|
||||
def prof_on_room_win_focus(barejid):
|
||||
prof.win_create(plugin_win, _handle_win_input)
|
||||
prof.win_show(plugin_win, "fired -> prof_on_room_win_focus: " + barejid)
|
||||
@@ -1,18 +0,0 @@
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user