Compare commits
10 Commits
feature/ai
...
0.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
035c190e1c | ||
|
|
1dcd915678 | ||
|
|
2132d79094 | ||
|
|
5025312337 | ||
|
|
cdb6cd93b5 | ||
|
|
5eea0240ad | ||
|
|
0d4dbd6fd1 | ||
|
|
3afb6bb157 | ||
|
|
38cc929f86 | ||
|
|
144884d819 |
60
README.md
60
README.md
@@ -1,29 +1,10 @@
|
|||||||
# CProof Plugins
|
profanity-plugins
|
||||||
|
=================
|
||||||
|
|
||||||
[Website documentation](https://jabber.space/guides/plugins/)
|
* [Website documentation](http://www.profanity.im/plugins.html)
|
||||||
|
|
||||||
`Master` is a development branch compatible with the latest version of CProof.
|
Building Profanity with plugin support
|
||||||
|
--------------------------------------
|
||||||
## Installing plugins
|
|
||||||
|
|
||||||
Use the `/plugins install` command, e.g.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/plugins install ~/path-to/sounds.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can use URLs to raw files to install Python plugins. For example,
|
|
||||||
```bash
|
|
||||||
/plugins install https://git.jabber.space/devs/cproof-plugins/raw/branch/master/src/sounds.py
|
|
||||||
```
|
|
||||||
|
|
||||||
See the `/help plugins` command for further plugin management options.
|
|
||||||
|
|
||||||
## Plugins description
|
|
||||||
|
|
||||||
To be described.
|
|
||||||
|
|
||||||
## Building CProof with plugin support
|
|
||||||
|
|
||||||
Dependencies required:
|
Dependencies required:
|
||||||
|
|
||||||
@@ -38,19 +19,30 @@ For Python plugins the following is also required:
|
|||||||
python-dev
|
python-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Developing plugins
|
Installing plugins
|
||||||
|
------------------
|
||||||
|
|
||||||
API Documentation is available on our website, [jabber.space](https://jabber.space/).
|
Use the `/plugins install` command, e.g.
|
||||||
- [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/)
|
|
||||||
|
|
||||||
## External plugins (C)
|
```
|
||||||
|
/plugins install ~/projects-git/profanity-plugins/stable/say.py
|
||||||
|
```
|
||||||
|
|
||||||
- [profanity-plugins by Reimar](https://github.com/ReimarPB/profanity-plugins) - Collection of plugins
|
See the `/help plugins` command for further plugin management options.
|
||||||
|
|
||||||
|
Branches
|
||||||
|
--------
|
||||||
|
|
||||||
## External plugins (Python)
|
* `0.5.0`: Maintenance branch for plugins compatible with the current Profanity release 0.5.0
|
||||||
|
* `0.5.1`: Development for the next Profanity patch release 0.5.1
|
||||||
|
* `master`: Development for the next Profanity major release 0.6.0
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
- [profanity-copy-message-plugin](https://github.com/lonski/profanity-copy-message-plugin) - Copy messages to clipboard.
|
|
||||||
|
|||||||
16
development/ChatStart.rb
Normal file
16
development/ChatStart.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module ChatStart
|
||||||
|
|
||||||
|
@@starts = {
|
||||||
|
"prof1@panesar" => [ "\"Prof 2\"", "prof3@panesar" ],
|
||||||
|
"prof2@panesar" => [ "prof1@panesar" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
def self.prof_on_connect(account_name, fulljid)
|
||||||
|
if @@starts[account_name]
|
||||||
|
@@starts[account_name].each { | contact |
|
||||||
|
Prof::send_line("/msg " + contact)
|
||||||
|
}
|
||||||
|
Prof::send_line("/win 1")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
51
development/chatterbot/chatterbot.py
Normal file
51
development/chatterbot/chatterbot.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import prof
|
||||||
|
from chatterbotapi import ChatterBotFactory, ChatterBotType
|
||||||
|
|
||||||
|
|
||||||
|
factory = ChatterBotFactory()
|
||||||
|
bot = factory.create(ChatterBotType.CLEVERBOT)
|
||||||
|
# bot = factory.create(ChatterBotType.JABBERWACKY)
|
||||||
|
# bot = factory.create(ChatterBotType.PANDORABOTS, 'b0dafd24ee35a477')
|
||||||
|
bot_session = {}
|
||||||
|
bot_state = False
|
||||||
|
|
||||||
|
|
||||||
|
def prof_post_chat_message_display(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" ])
|
||||||
190
development/chatterbot/chatterbotapi.py
Normal file
190
development/chatterbot/chatterbotapi.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 0):
|
||||||
|
from urllib.request import build_opener, HTTPCookieProcessor, urlopen
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
import http.cookiejar as cookielib
|
||||||
|
|
||||||
|
else:
|
||||||
|
from urllib import urlencode, urlopen
|
||||||
|
from urllib2 import build_opener, HTTPCookieProcessor
|
||||||
|
import cookielib
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
import xml.dom.minidom
|
||||||
|
|
||||||
|
"""
|
||||||
|
chatterbotapi
|
||||||
|
Copyright (C) 2011 pierredavidbelanger@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#################################################
|
||||||
|
# API
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
class ChatterBotType:
|
||||||
|
|
||||||
|
CLEVERBOT = 1
|
||||||
|
JABBERWACKY = 2
|
||||||
|
PANDORABOTS = 3
|
||||||
|
|
||||||
|
class ChatterBotFactory:
|
||||||
|
|
||||||
|
def create(self, type, arg = None):
|
||||||
|
if type == ChatterBotType.CLEVERBOT:
|
||||||
|
return _Cleverbot('http://www.cleverbot.com', 'http://www.cleverbot.com/webservicemin', 35)
|
||||||
|
elif type == ChatterBotType.JABBERWACKY:
|
||||||
|
return _Cleverbot('http://jabberwacky.com', 'http://jabberwacky.com/webservicemin', 29)
|
||||||
|
elif type == ChatterBotType.PANDORABOTS:
|
||||||
|
if arg == None:
|
||||||
|
raise Exception('PANDORABOTS needs a botid arg')
|
||||||
|
return _Pandorabots(arg)
|
||||||
|
return None
|
||||||
|
|
||||||
|
class ChatterBot:
|
||||||
|
|
||||||
|
def create_session(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
class ChatterBotSession:
|
||||||
|
|
||||||
|
def think_thought(self, thought):
|
||||||
|
return thought
|
||||||
|
|
||||||
|
def think(self, text):
|
||||||
|
thought = ChatterBotThought()
|
||||||
|
thought.text = text
|
||||||
|
return self.think_thought(thought).text
|
||||||
|
|
||||||
|
class ChatterBotThought:
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
#################################################
|
||||||
|
# Cleverbot impl
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
class _Cleverbot(ChatterBot):
|
||||||
|
|
||||||
|
def __init__(self, baseUrl, serviceUrl, endIndex):
|
||||||
|
self.baseUrl = baseUrl
|
||||||
|
self.serviceUrl = serviceUrl
|
||||||
|
self.endIndex = endIndex
|
||||||
|
|
||||||
|
def create_session(self):
|
||||||
|
return _CleverbotSession(self)
|
||||||
|
|
||||||
|
class _CleverbotSession(ChatterBotSession):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
self.vars = {}
|
||||||
|
self.vars['start'] = 'y'
|
||||||
|
self.vars['icognoid'] = 'wsf'
|
||||||
|
self.vars['fno'] = '0'
|
||||||
|
self.vars['sub'] = 'Say'
|
||||||
|
self.vars['islearning'] = '1'
|
||||||
|
self.vars['cleanslate'] = 'false'
|
||||||
|
self.cookieJar = cookielib.CookieJar()
|
||||||
|
self.opener = build_opener(HTTPCookieProcessor(self.cookieJar))
|
||||||
|
self.opener.open(self.bot.baseUrl)
|
||||||
|
|
||||||
|
def think_thought(self, thought):
|
||||||
|
self.vars['stimulus'] = thought.text
|
||||||
|
data = urlencode(self.vars)
|
||||||
|
data_to_digest = data[9:self.bot.endIndex]
|
||||||
|
data_digest = hashlib.md5(data_to_digest.encode('utf-8')).hexdigest()
|
||||||
|
data = data + '&icognocheck=' + data_digest
|
||||||
|
url_response = self.opener.open(self.bot.serviceUrl, data.encode('utf-8'))
|
||||||
|
response = str(url_response.read())
|
||||||
|
response_values = re.split(r'\\r|\r', response)
|
||||||
|
#self.vars['??'] = _utils_string_at_index(response_values, 0)
|
||||||
|
self.vars['sessionid'] = _utils_string_at_index(response_values, 1)
|
||||||
|
self.vars['logurl'] = _utils_string_at_index(response_values, 2)
|
||||||
|
self.vars['vText8'] = _utils_string_at_index(response_values, 3)
|
||||||
|
self.vars['vText7'] = _utils_string_at_index(response_values, 4)
|
||||||
|
self.vars['vText6'] = _utils_string_at_index(response_values, 5)
|
||||||
|
self.vars['vText5'] = _utils_string_at_index(response_values, 6)
|
||||||
|
self.vars['vText4'] = _utils_string_at_index(response_values, 7)
|
||||||
|
self.vars['vText3'] = _utils_string_at_index(response_values, 8)
|
||||||
|
self.vars['vText2'] = _utils_string_at_index(response_values, 9)
|
||||||
|
self.vars['prevref'] = _utils_string_at_index(response_values, 10)
|
||||||
|
#self.vars['??'] = _utils_string_at_index(response_values, 11)
|
||||||
|
self.vars['emotionalhistory'] = _utils_string_at_index(response_values, 12)
|
||||||
|
self.vars['ttsLocMP3'] = _utils_string_at_index(response_values, 13)
|
||||||
|
self.vars['ttsLocTXT'] = _utils_string_at_index(response_values, 14)
|
||||||
|
self.vars['ttsLocTXT3'] = _utils_string_at_index(response_values, 15)
|
||||||
|
self.vars['ttsText'] = _utils_string_at_index(response_values, 16)
|
||||||
|
self.vars['lineRef'] = _utils_string_at_index(response_values, 17)
|
||||||
|
self.vars['lineURL'] = _utils_string_at_index(response_values, 18)
|
||||||
|
self.vars['linePOST'] = _utils_string_at_index(response_values, 19)
|
||||||
|
self.vars['lineChoices'] = _utils_string_at_index(response_values, 20)
|
||||||
|
self.vars['lineChoicesAbbrev'] = _utils_string_at_index(response_values, 21)
|
||||||
|
self.vars['typingData'] = _utils_string_at_index(response_values, 22)
|
||||||
|
self.vars['divert'] = _utils_string_at_index(response_values, 23)
|
||||||
|
response_thought = ChatterBotThought()
|
||||||
|
response_thought.text = _utils_string_at_index(response_values, 16)
|
||||||
|
return response_thought
|
||||||
|
|
||||||
|
#################################################
|
||||||
|
# Pandorabots impl
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
class _Pandorabots(ChatterBot):
|
||||||
|
|
||||||
|
def __init__(self, botid):
|
||||||
|
self.botid = botid
|
||||||
|
|
||||||
|
def create_session(self):
|
||||||
|
return _PandorabotsSession(self)
|
||||||
|
|
||||||
|
class _PandorabotsSession(ChatterBotSession):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.vars = {}
|
||||||
|
self.vars['botid'] = bot.botid
|
||||||
|
self.vars['custid'] = uuid.uuid1()
|
||||||
|
|
||||||
|
def think_thought(self, thought):
|
||||||
|
self.vars['input'] = thought.text
|
||||||
|
data = urlencode(self.vars)
|
||||||
|
url_response = urlopen('http://www.pandorabots.com/pandora/talk-xml', data)
|
||||||
|
response = url_response.read()
|
||||||
|
response_dom = xml.dom.minidom.parseString(response)
|
||||||
|
response_thought = ChatterBotThought()
|
||||||
|
that_elements = response_dom.getElementsByTagName('that')
|
||||||
|
if that_elements is None or len(that_elements) == 0 or that_elements[0] is None:
|
||||||
|
return ''
|
||||||
|
that_elements_child_nodes = that_elements[0].childNodes
|
||||||
|
if that_elements_child_nodes is None or len(that_elements_child_nodes) == 0 or that_elements_child_nodes[0] is None:
|
||||||
|
return ''
|
||||||
|
that_elements_child_nodes_data = that_elements_child_nodes[0].data
|
||||||
|
if that_elements_child_nodes_data is None:
|
||||||
|
return ''
|
||||||
|
response_thought.text = that_elements_child_nodes_data.strip()
|
||||||
|
return response_thought
|
||||||
|
|
||||||
|
#################################################
|
||||||
|
# Utils
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
def _utils_string_at_index(strings, index):
|
||||||
|
if len(strings) > index:
|
||||||
|
return strings[index]
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
531
development/jenkins.py
Normal file
531
development/jenkins.py
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
"""
|
||||||
|
Manage jenkins jobs
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
The jenkinsapi module is required https://pypi.python.org/pypi/jenkinsapi
|
||||||
|
To install the module using pip:
|
||||||
|
sudo pip install jenkinsapi
|
||||||
|
|
||||||
|
The plugin polls the jenkins server at 'jenkins_url' every 'jenkins_poll_interval' seconds.
|
||||||
|
|
||||||
|
The results are checked by profanity every 'prof_cb_interval'.
|
||||||
|
New failures are reported in the jenkins window, and a desktop notification is sent.
|
||||||
|
|
||||||
|
The 'prof_remind_interval' specifies the time between reminder notifications of broken builds
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import prof
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import webbrowser
|
||||||
|
import urllib2
|
||||||
|
import base64
|
||||||
|
import jenkinsapi
|
||||||
|
from jenkinsapi.jenkins import Jenkins
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
jenkins_url = "http://localhost:8080"
|
||||||
|
username = None
|
||||||
|
password = None
|
||||||
|
jenkins_poll_interval = 5
|
||||||
|
prof_cb_interval = 5
|
||||||
|
prof_remind_interval = 10
|
||||||
|
enable_notify = True
|
||||||
|
enable_remind = True
|
||||||
|
|
||||||
|
# Global state
|
||||||
|
last_state = {}
|
||||||
|
STATE_SUCCESS = "SUCCESS"
|
||||||
|
STATE_UNSTABLE = "UNSTABLE"
|
||||||
|
STATE_FAILURE = "FAILURE"
|
||||||
|
STATE_QUEUED = "QUEUED"
|
||||||
|
STATE_RUNNING = "RUNNING"
|
||||||
|
STATE_NOBUILDS = "NOBUILDS"
|
||||||
|
STATE_UNKNOWN = "UNKNOWN"
|
||||||
|
|
||||||
|
win_tag = "Jenkins"
|
||||||
|
|
||||||
|
poll_fail = False
|
||||||
|
poll_fail_message = None
|
||||||
|
|
||||||
|
job_list = None
|
||||||
|
changes_list = None
|
||||||
|
|
||||||
|
|
||||||
|
def _help():
|
||||||
|
prof.win_show(win_tag, "Commands:")
|
||||||
|
prof.win_show(win_tag, " /jenkins help - Show this help")
|
||||||
|
prof.win_show(win_tag, " /jenkins jobs - List all jobs")
|
||||||
|
prof.win_show(win_tag, " /jenkins failing - List all failing jobs")
|
||||||
|
prof.win_show(win_tag, " /jenkins passing - List all passing jobs")
|
||||||
|
prof.win_show(win_tag, " /jenkins unstable - List all unstable jobs")
|
||||||
|
prof.win_show(win_tag, " /jenkins build [job] - Trigger build for job")
|
||||||
|
prof.win_show(win_tag, " /jenkins open [job] - Open job in browser")
|
||||||
|
prof.win_show(win_tag, " /jenkins log [job] - Show the latest build log")
|
||||||
|
prof.win_show(win_tag, " /jenkins remind on|off - Enable/disable reminder notifications")
|
||||||
|
prof.win_show(win_tag, " /jenkins notify on|off - Enable/disable build notifications")
|
||||||
|
prof.win_show(win_tag, " /jenkins settings - Show current settings")
|
||||||
|
|
||||||
|
|
||||||
|
def _settings():
|
||||||
|
prof.win_show(win_tag, "Jenkins settings:")
|
||||||
|
prof.win_show(win_tag, " Jenkins URL : " + jenkins_url)
|
||||||
|
prof.win_show(win_tag, " Jenkins poll interval : " + str(jenkins_poll_interval) + " seconds")
|
||||||
|
prof.win_show(win_tag, " Profanity update interval : " + str(prof_cb_interval) + " seconds")
|
||||||
|
prof.win_show(win_tag, " Reminder interval : " + str(prof_remind_interval) + " seconds")
|
||||||
|
prof.win_show(win_tag, " Notifications enabled : " + str(enable_notify))
|
||||||
|
prof.win_show(win_tag, " Reminders enabled : " + str(enable_remind))
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_remove(jobname, state):
|
||||||
|
if jobname in last_state[state]:
|
||||||
|
last_state[state].remove(jobname)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_state(jobname, state):
|
||||||
|
if not jobname in last_state[state]:
|
||||||
|
_safe_remove(jobname, STATE_SUCCESS)
|
||||||
|
_safe_remove(jobname, STATE_UNSTABLE)
|
||||||
|
_safe_remove(jobname, STATE_FAILURE)
|
||||||
|
_safe_remove(jobname, STATE_QUEUED)
|
||||||
|
_safe_remove(jobname, STATE_RUNNING)
|
||||||
|
_safe_remove(jobname, STATE_NOBUILDS)
|
||||||
|
_safe_remove(jobname, STATE_UNKNOWN)
|
||||||
|
last_state[state].append(jobname)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _open_job_url(url):
|
||||||
|
savout = os.dup(1)
|
||||||
|
saverr = os.dup(2)
|
||||||
|
os.close(1)
|
||||||
|
os.close(2)
|
||||||
|
os.open(os.devnull, os.O_RDWR)
|
||||||
|
try:
|
||||||
|
webbrowser.open(url, new=2)
|
||||||
|
finally:
|
||||||
|
os.dup2(savout, 1)
|
||||||
|
os.dup2(saverr, 2)
|
||||||
|
|
||||||
|
|
||||||
|
class JobList():
|
||||||
|
def __init__(self):
|
||||||
|
self.jobs = []
|
||||||
|
|
||||||
|
def add_job(self, name, build_number, state):
|
||||||
|
self.jobs.append((name, build_number, state))
|
||||||
|
|
||||||
|
def get_jobs(self, in_state=None):
|
||||||
|
if not in_state:
|
||||||
|
return self.jobs
|
||||||
|
else:
|
||||||
|
result = []
|
||||||
|
for name, build_number, state in self.jobs:
|
||||||
|
if state == in_state:
|
||||||
|
result.append((name, build_number, state))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_job(self, jobname):
|
||||||
|
for name, build_number, state in self.jobs:
|
||||||
|
if name == jobname:
|
||||||
|
return name, build_number
|
||||||
|
return None
|
||||||
|
|
||||||
|
def contains_job(self, jobname):
|
||||||
|
for name, build_number, state in self.jobs:
|
||||||
|
if name == jobname:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def num_in_state(self, given_state):
|
||||||
|
count = 0
|
||||||
|
for name, build_number, state in self.jobs:
|
||||||
|
if state == given_state:
|
||||||
|
count = count + 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
class JobUpdates():
|
||||||
|
def __init__(self):
|
||||||
|
self.states = {}
|
||||||
|
self.states[STATE_FAILURE] = []
|
||||||
|
self.states[STATE_SUCCESS] = []
|
||||||
|
self.states[STATE_UNSTABLE] = []
|
||||||
|
self.states[STATE_QUEUED] = []
|
||||||
|
self.states[STATE_RUNNING] = []
|
||||||
|
|
||||||
|
def add_update(self, state, name, build_number=None):
|
||||||
|
if build_number:
|
||||||
|
self.states[state].append((name, build_number))
|
||||||
|
else:
|
||||||
|
self.states[state].append(name)
|
||||||
|
|
||||||
|
def get_in_state(self, state):
|
||||||
|
return self.states[state]
|
||||||
|
|
||||||
|
|
||||||
|
def _process_build(name, build, new_job_list, new_changes_list):
|
||||||
|
if build.get_status() == STATE_FAILURE:
|
||||||
|
new_job_list.add_job(name, build.get_number(), STATE_FAILURE)
|
||||||
|
changed = _set_state(name, STATE_FAILURE)
|
||||||
|
if changed:
|
||||||
|
new_changes_list.add_update(STATE_FAILURE, name, build.get_number())
|
||||||
|
elif build.get_status() == STATE_SUCCESS:
|
||||||
|
new_job_list.add_job(name, build.get_number(), STATE_SUCCESS)
|
||||||
|
changed = _set_state(name, STATE_SUCCESS)
|
||||||
|
if changed:
|
||||||
|
new_changes_list.add_update(STATE_SUCCESS, name, build.get_number())
|
||||||
|
elif build.get_status() == STATE_UNSTABLE:
|
||||||
|
new_job_list.add_job(name, build.get_number(), STATE_UNSTABLE)
|
||||||
|
changed = _set_state(name, STATE_UNSTABLE)
|
||||||
|
if changed:
|
||||||
|
new_changes_list.add_update(STATE_UNSTABLE, name, build.get_number())
|
||||||
|
|
||||||
|
|
||||||
|
def _process_queued_or_running(name, job, new_job_list, new_changes_list):
|
||||||
|
if job.is_queued():
|
||||||
|
new_job_list.add_job(name, None, STATE_QUEUED)
|
||||||
|
changed = _set_state(name, STATE_QUEUED)
|
||||||
|
if changed:
|
||||||
|
new_changes_list.add_update(STATE_QUEUED, name)
|
||||||
|
elif job.is_running():
|
||||||
|
new_job_list.add_job(name, None, STATE_RUNNING)
|
||||||
|
changed = _set_state(name, STATE_RUNNING)
|
||||||
|
if changed:
|
||||||
|
new_changes_list.add_update(STATE_RUNNING, name)
|
||||||
|
|
||||||
|
|
||||||
|
def _jenkins_poll():
|
||||||
|
global poll_fail
|
||||||
|
global poll_fail_message
|
||||||
|
global job_list
|
||||||
|
global changes_list
|
||||||
|
|
||||||
|
job_ac = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(jenkins_poll_interval)
|
||||||
|
try:
|
||||||
|
j = Jenkins(jenkins_url, username, password)
|
||||||
|
except Exception, e:
|
||||||
|
poll_fail = True
|
||||||
|
poll_fail_message = str(e)
|
||||||
|
else:
|
||||||
|
poll_fail = False
|
||||||
|
poll_fail_message = None
|
||||||
|
new_job_list = JobList()
|
||||||
|
new_changes_list = JobUpdates()
|
||||||
|
for name, job in j.get_jobs():
|
||||||
|
if not job.is_queued_or_running():
|
||||||
|
build = job.get_last_build_or_none()
|
||||||
|
if build:
|
||||||
|
_process_build(name, build, new_job_list, new_changes_list)
|
||||||
|
else:
|
||||||
|
new_job_list.add_job(name, None, STATE_NOBUILDS)
|
||||||
|
else:
|
||||||
|
_process_queued_or_running(name, job, new_job_list, new_changes_list)
|
||||||
|
if not job_list:
|
||||||
|
job_ac.append(name)
|
||||||
|
|
||||||
|
if not job_list:
|
||||||
|
prof.completer_add("/jenkins build", job_ac)
|
||||||
|
prof.completer_add("/jenkins open", job_ac)
|
||||||
|
prof.completer_add("/jenkins log", job_ac)
|
||||||
|
|
||||||
|
job_list = new_job_list
|
||||||
|
changes_list = new_changes_list
|
||||||
|
|
||||||
|
|
||||||
|
def _prof_callback():
|
||||||
|
global changes_list
|
||||||
|
if poll_fail:
|
||||||
|
if not prof.win_exists(win_tag):
|
||||||
|
prof.win_create(win_tag, _handle_input)
|
||||||
|
prof.win_show(win_tag, "Could not connect to jenkins, see the logs.")
|
||||||
|
if poll_fail_message:
|
||||||
|
prof.log_warning("Jenkins poll failed: " + str(poll_fail_message))
|
||||||
|
else:
|
||||||
|
prof.log_warning("Jenkins poll failed")
|
||||||
|
elif changes_list:
|
||||||
|
for name in changes_list.get_in_state(STATE_QUEUED):
|
||||||
|
if not prof.win_exists(win_tag):
|
||||||
|
prof.win_create(win_tag, _handle_input)
|
||||||
|
prof.win_show_themed(win_tag, None, None, "cyan", name + " " + STATE_QUEUED)
|
||||||
|
for name in changes_list.get_in_state(STATE_RUNNING):
|
||||||
|
if not prof.win_exists(win_tag):
|
||||||
|
prof.win_create(win_tag, _handle_input)
|
||||||
|
prof.win_show_themed(win_tag, None, None, "cyan", name + " " + STATE_RUNNING)
|
||||||
|
for name, build_number in changes_list.get_in_state(STATE_SUCCESS):
|
||||||
|
if not prof.win_exists(win_tag):
|
||||||
|
prof.win_create(win_tag, _handle_input)
|
||||||
|
prof.win_show_themed(win_tag, None, None, "green", name + " #" + str(build_number) + " " + STATE_SUCCESS)
|
||||||
|
if enable_notify:
|
||||||
|
prof.notify(name + " " + STATE_SUCCESS, 5000, "Jenkins")
|
||||||
|
for name, build_number in changes_list.get_in_state(STATE_UNSTABLE):
|
||||||
|
if not prof.win_exists(win_tag):
|
||||||
|
prof.win_create(win_tag, _handle_input)
|
||||||
|
prof.win_show_themed(win_tag, None, None, "yellow", name + " #" + str(build_number) + " " + STATE_UNSTABLE)
|
||||||
|
if enable_notify:
|
||||||
|
prof.notify(name + " " + STATE_UNSTABLE, 5000, "Jenkins")
|
||||||
|
for name, build_number in changes_list.get_in_state(STATE_FAILURE):
|
||||||
|
if not prof.win_exists(win_tag):
|
||||||
|
prof.win_create(win_tag, _handle_input)
|
||||||
|
prof.win_show_themed(win_tag, None, None, "red", name + " #" + str(build_number) + " " + STATE_FAILURE)
|
||||||
|
if enable_notify:
|
||||||
|
prof.notify(name + " " + STATE_FAILURE, 5000, "Jenkins")
|
||||||
|
|
||||||
|
changes_list = None
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_input(win, line):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _list_jobs(jobs):
|
||||||
|
for name, build_number, state in jobs:
|
||||||
|
if state == STATE_SUCCESS:
|
||||||
|
prof.win_show_themed(win_tag, None, None, "green", " " + name + " #" + str(build_number) + " " + STATE_SUCCESS)
|
||||||
|
elif state == STATE_UNSTABLE:
|
||||||
|
prof.win_show_themed(win_tag, None, None, "yellow", " " + name + " #" + str(build_number) + " " + STATE_UNSTABLE)
|
||||||
|
elif state == STATE_FAILURE:
|
||||||
|
prof.win_show_themed(win_tag, None, None, "red", " " + name + " #" + str(build_number) + " " + STATE_FAILURE)
|
||||||
|
elif state == STATE_NOBUILDS:
|
||||||
|
prof.win_show(win_tag, " " + name + ", no builds")
|
||||||
|
else:
|
||||||
|
prof.win_show_themed(win_tag, None, None, "cyan", " " + name + " " + state)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_job(job):
|
||||||
|
try:
|
||||||
|
request = urllib2.Request(jenkins_url + "/job/" + job + "/build");
|
||||||
|
if username:
|
||||||
|
basicauth = base64.encodestring("%s:%s" %(username, password)).replace("\n", "")
|
||||||
|
request.add_header("Authorization", "Basic %s" %basicauth);
|
||||||
|
request.add_data("")
|
||||||
|
urllib2.urlopen(request)
|
||||||
|
except Exception, e:
|
||||||
|
prof.win_show(win_tag, "Failed to build " + job + ", see the logs.")
|
||||||
|
prof.log_warning("Failed to build " + job + ": " + str(e))
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "Build request sent for " + job)
|
||||||
|
|
||||||
|
|
||||||
|
def _job_log(job):
|
||||||
|
name = job[0]
|
||||||
|
build_no = job[1]
|
||||||
|
if build_no:
|
||||||
|
try:
|
||||||
|
response = urllib2.urlopen(jenkins_url + "/job/" + name + "/" + str(build_no) + "/consoleText")
|
||||||
|
except Exception, e:
|
||||||
|
prof.win_show(win_tag, "Unable to fetch log for " + name + "#" + str(build_no) + ", see the logs.")
|
||||||
|
prof.log_warning("Unable to fetch log for " + name + "#" + str(build_no) + ": " + str(e))
|
||||||
|
else:
|
||||||
|
log_str = "Log for " + name + " #" + str(build_no) + ":\n" + response.read()
|
||||||
|
prof.win_show(win_tag, log_str)
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No build found for " + name)
|
||||||
|
|
||||||
|
|
||||||
|
def _cmd_jenkins(cmd=None, arg=None):
|
||||||
|
global enable_remind
|
||||||
|
global enable_notify
|
||||||
|
|
||||||
|
if not prof.win_exists(win_tag):
|
||||||
|
prof.win_create(win_tag, _handle_input)
|
||||||
|
|
||||||
|
prof.win_focus(win_tag)
|
||||||
|
|
||||||
|
if cmd == "jobs":
|
||||||
|
if job_list and job_list.get_jobs():
|
||||||
|
prof.win_show(win_tag, "Jobs:")
|
||||||
|
_list_jobs(job_list.get_jobs())
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No job data yet.")
|
||||||
|
elif cmd == "failing":
|
||||||
|
if job_list:
|
||||||
|
jobs_in_state = job_list.get_jobs(STATE_FAILURE)
|
||||||
|
if jobs_in_state:
|
||||||
|
prof.win_show(win_tag, "Failing jobs:")
|
||||||
|
_list_jobs(jobs_in_state)
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No failing jobs.")
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No job data yet.")
|
||||||
|
elif cmd == "passing":
|
||||||
|
if job_list:
|
||||||
|
jobs_in_state = job_list.get_jobs(STATE_SUCCESS)
|
||||||
|
if jobs_in_state:
|
||||||
|
prof.win_show(win_tag, "Passing jobs:")
|
||||||
|
_list_jobs(jobs_in_state)
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No passing jobs.")
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No job data yet.")
|
||||||
|
elif cmd == "unstable":
|
||||||
|
if job_list:
|
||||||
|
jobs_in_state = job_list.get_jobs(STATE_UNSTABLE)
|
||||||
|
if jobs_in_state:
|
||||||
|
prof.win_show(win_tag, "Unstable jobs:")
|
||||||
|
_list_jobs(jobs_in_state)
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No unstable jobs.")
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No job data yet.")
|
||||||
|
elif cmd == "build":
|
||||||
|
if not arg:
|
||||||
|
prof.win_show(win_tag, "You must supply a job argument.")
|
||||||
|
elif job_list and job_list.contains_job(arg):
|
||||||
|
_build_job(arg)
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No such job: " + arg)
|
||||||
|
elif cmd == "open":
|
||||||
|
if not arg:
|
||||||
|
prof.win_show(win_tag, "You must supply a job argument.")
|
||||||
|
elif job_list and job_list.contains_job(arg):
|
||||||
|
_open_job_url(jenkins_url + "/job/" + arg)
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No such job: " + arg)
|
||||||
|
elif cmd == "remind":
|
||||||
|
if not arg:
|
||||||
|
prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
|
||||||
|
elif arg == "on":
|
||||||
|
enable_remind = True
|
||||||
|
prof.win_show(win_tag, "Reminder notifications enabled.")
|
||||||
|
elif arg == "off":
|
||||||
|
enable_remind = False
|
||||||
|
prof.win_show(win_tag, "Reminder notifications disabled.")
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
|
||||||
|
elif cmd == "notify":
|
||||||
|
if not arg:
|
||||||
|
prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
|
||||||
|
elif arg == "on":
|
||||||
|
enable_notify = True
|
||||||
|
prof.win_show(win_tag, "Build notifications enabled.")
|
||||||
|
elif arg == "off":
|
||||||
|
enable_notify = False
|
||||||
|
prof.win_show(win_tag, "Build notifications disabled.")
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
|
||||||
|
elif cmd == "settings":
|
||||||
|
_settings()
|
||||||
|
elif cmd == "help":
|
||||||
|
_help()
|
||||||
|
elif cmd == "log":
|
||||||
|
if not arg:
|
||||||
|
prof.win_show(win_tag, "You must specify a job.")
|
||||||
|
else:
|
||||||
|
job = job_list.get_job(arg)
|
||||||
|
if job:
|
||||||
|
_job_log(job)
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "No job found: " + arg)
|
||||||
|
else:
|
||||||
|
prof.win_show(win_tag, "Unknown command.")
|
||||||
|
|
||||||
|
|
||||||
|
def _remind():
|
||||||
|
if enable_remind and job_list:
|
||||||
|
notify_string = ""
|
||||||
|
failing = job_list.num_in_state(STATE_FAILURE)
|
||||||
|
unstable = job_list.num_in_state(STATE_UNSTABLE)
|
||||||
|
|
||||||
|
if failing == 1:
|
||||||
|
notify_string = notify_string + "1 failing build"
|
||||||
|
if failing > 1:
|
||||||
|
notify_string = notify_string + str(failing) + " failing builds"
|
||||||
|
|
||||||
|
if failing > 0 and unstable > 0:
|
||||||
|
notify_string = notify_string + "\n"
|
||||||
|
|
||||||
|
if unstable == 1:
|
||||||
|
notify_string = notify_string + "1 unstable build"
|
||||||
|
if unstable > 1:
|
||||||
|
notify_string = notify_string + str(unstable) + " unstable builds"
|
||||||
|
|
||||||
|
if not notify_string == "":
|
||||||
|
prof.notify(notify_string, 5000, "Jenkins")
|
||||||
|
|
||||||
|
|
||||||
|
def prof_init(version, status, account_name, fulljid):
|
||||||
|
last_state[STATE_SUCCESS] = []
|
||||||
|
last_state[STATE_UNSTABLE] = []
|
||||||
|
last_state[STATE_FAILURE] = []
|
||||||
|
last_state[STATE_QUEUED] = []
|
||||||
|
last_state[STATE_RUNNING] = []
|
||||||
|
last_state[STATE_NOBUILDS] = []
|
||||||
|
last_state[STATE_UNKNOWN] = []
|
||||||
|
|
||||||
|
jenkins_t = threading.Thread(target=_jenkins_poll)
|
||||||
|
jenkins_t.daemon = True;
|
||||||
|
jenkins_t.start()
|
||||||
|
|
||||||
|
prof.register_timed(_prof_callback, prof_cb_interval)
|
||||||
|
prof.register_timed(_remind, prof_remind_interval)
|
||||||
|
|
||||||
|
prof.completer_add(
|
||||||
|
"/jenkins", [
|
||||||
|
"help",
|
||||||
|
"jobs",
|
||||||
|
"failing",
|
||||||
|
"passing",
|
||||||
|
"unstable",
|
||||||
|
"build",
|
||||||
|
"open",
|
||||||
|
"log",
|
||||||
|
"remind",
|
||||||
|
"notify",
|
||||||
|
"settings"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
prof.completer_add(
|
||||||
|
"/jenkins remind", [
|
||||||
|
"on",
|
||||||
|
"off"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
prof.completer_add(
|
||||||
|
"/jenkins notify", [
|
||||||
|
"on",
|
||||||
|
"off"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
synopsis = [
|
||||||
|
"/jenkins jobs|failing|passing|unstable",
|
||||||
|
"/jenkins build <job>",
|
||||||
|
"/jenkins open <job>",
|
||||||
|
"/jenkins log <job>",
|
||||||
|
"/jenkins remind on|off",
|
||||||
|
"/jenkins notify on|off",
|
||||||
|
"/jenkins settings"
|
||||||
|
]
|
||||||
|
description = "Monitor, run and view Jenkins jobs."
|
||||||
|
args = [
|
||||||
|
[ "jobs", "List all jobs" ],
|
||||||
|
[ "failing", "List all failing jobs" ],
|
||||||
|
[ "passing", "List all passing jobs" ],
|
||||||
|
[ "unstable", "List all unstable jobs" ],
|
||||||
|
[ "build <job>", "Trigger build for job" ],
|
||||||
|
[ "open <job>", "Open job in browser" ],
|
||||||
|
[ "log <job>", "Show the latest build log for job" ],
|
||||||
|
[ "remind on|off", "Enable/disable reminder notifications" ],
|
||||||
|
[ "notify on|off", "Enable/disable build notifications" ],
|
||||||
|
[ "settings", "Show current settings" ]
|
||||||
|
]
|
||||||
|
examples = [
|
||||||
|
"/kenkins failing",
|
||||||
|
"/kenkins build stabber-build",
|
||||||
|
"/kenkins remind off"
|
||||||
|
]
|
||||||
|
|
||||||
|
prof.register_command("/jenkins", 0, 2, synopsis, description, args, examples, _cmd_jenkins)
|
||||||
|
|
||||||
|
|
||||||
|
def prof_on_start():
|
||||||
|
prof.win_create(win_tag, _handle_input)
|
||||||
|
prof.win_show(win_tag, "Jenkins plugin started.")
|
||||||
276
src/ai.py
276
src/ai.py
@@ -1,276 +0,0 @@
|
|||||||
"""AI Chat Plugin for CProof XMPP Client
|
|
||||||
|
|
||||||
This plugin enables interaction with AI models via a dedicated chat window using the
|
|
||||||
LiteLLM library, supporting providers like xAI (Grok) and OpenAI. Commands allow setting
|
|
||||||
default models, API tokens, starting chats, clearing windows, and correcting messages.
|
|
||||||
|
|
||||||
- Requires the `litellm` library (`pip install litellm`).
|
|
||||||
- Configure API tokens with zero-data-retention agreements for maximum privacy.
|
|
||||||
- Use models like xAI's Grok for minimal data retention.
|
|
||||||
- Chat history is not persisted locally beyond the active session.
|
|
||||||
|
|
||||||
See Also:
|
|
||||||
- LiteLLM documentation: https://docs.litellm.ai/docs/
|
|
||||||
- xAI API: https://x.ai/api
|
|
||||||
"""
|
|
||||||
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
import litellm
|
|
||||||
import prof
|
|
||||||
|
|
||||||
from typing import Callable, Dict, List, Optional, Tuple
|
|
||||||
|
|
||||||
# Global settings
|
|
||||||
DEFAULT_MODEL_KEY = "default_model" # Changed to avoid hardcoding model
|
|
||||||
TOKEN_KEY = "ai_tokens"
|
|
||||||
CHAT_WIN_ID: Optional[int] = None
|
|
||||||
# Global message history: {win_id: [{"role": "user"|"assistant", "content": str}, ...]}
|
|
||||||
CHAT_HISTORY: Dict[str, List[Dict[str, str]]] = {}
|
|
||||||
# Output file for safe display
|
|
||||||
OUTPUT_FILE = "/tmp/ai_output.txt"
|
|
||||||
|
|
||||||
FILE_LOCK = threading.Lock()
|
|
||||||
|
|
||||||
# Privacy settings for LiteLLM
|
|
||||||
litellm.drop_params = True
|
|
||||||
litellm.set_verbose = False
|
|
||||||
|
|
||||||
# Disable printing in the console to avoid breaking profanity's outline
|
|
||||||
litellm.suppress_debug_info = True
|
|
||||||
|
|
||||||
|
|
||||||
def _get_tokens() -> Dict[str, str]:
|
|
||||||
"""Retrieve tokens from settings as a dictionary."""
|
|
||||||
tokens: Dict[str, str] = {}
|
|
||||||
token_strings = prof.settings_string_list_get("ai_plugin", TOKEN_KEY)
|
|
||||||
if not token_strings:
|
|
||||||
return tokens
|
|
||||||
for ts in token_strings:
|
|
||||||
if ":" in ts:
|
|
||||||
company, token = ts.split(":", 1)
|
|
||||||
tokens[company] = token
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
|
|
||||||
def _save_tokens(tokens: Dict[str, str]) -> None:
|
|
||||||
"""Save tokens to settings as a list of 'company:token' strings."""
|
|
||||||
prof.settings_string_list_clear("ai_plugin", TOKEN_KEY)
|
|
||||||
for company, token in tokens.items():
|
|
||||||
prof.settings_string_list_add("ai_plugin", TOKEN_KEY, f"{company}:{token}")
|
|
||||||
|
|
||||||
|
|
||||||
def display_settings() -> None:
|
|
||||||
"""Show the current default model and registered tokens."""
|
|
||||||
model = get_default_model()
|
|
||||||
tokens = _get_tokens()
|
|
||||||
token_info = ", ".join([f"{company}: {token[:4]}..." for company, token in tokens.items()])
|
|
||||||
prof.cons_show(f"AI Settings: Default Model: {model}, Tokens: {token_info or 'None set'}")
|
|
||||||
|
|
||||||
|
|
||||||
def set_model(model) -> None:
|
|
||||||
"""Set the default model to be used for AI chats."""
|
|
||||||
set_default_model(model)
|
|
||||||
prof.cons_show(f"Default model set to: {model}")
|
|
||||||
|
|
||||||
def run_completion(window_id: str, model: str, history: list, outfile: str, tokens: dict, file_lock) -> None:
|
|
||||||
"""Run AI completion and write response to file."""
|
|
||||||
try:
|
|
||||||
response = litellm.completion(
|
|
||||||
model=model,
|
|
||||||
messages=history,
|
|
||||||
api_key=tokens.get(model.split("/")[0], None),
|
|
||||||
).choices[0].message.content
|
|
||||||
with file_lock:
|
|
||||||
with open(outfile, "a") as f:
|
|
||||||
json.dump({"win_id": window_id, "content": f"AI: {response}"}, f)
|
|
||||||
f.write("\n")
|
|
||||||
except Exception as e:
|
|
||||||
with file_lock:
|
|
||||||
with open(outfile, "a") as f:
|
|
||||||
json.dump({"win_id": window_id, "content": f"Error: {str(e)}"}, f)
|
|
||||||
f.write("\n")
|
|
||||||
|
|
||||||
def handler(win_id: str, message: str) -> None:
|
|
||||||
"""Process messages in a chat window using the model from the window title.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
win_id: Identifier for the chat window, used to extract the model name.
|
|
||||||
message: The user's message to process.
|
|
||||||
"""
|
|
||||||
model = win_id.split(" - ", 2)[1]
|
|
||||||
prof.win_show(win_id, f"Me: {message}")
|
|
||||||
|
|
||||||
# Create a new history list for this call to avoid modifying shared state
|
|
||||||
current_history = CHAT_HISTORY.get(win_id, []) + [{"role": "user", "content": message}]
|
|
||||||
|
|
||||||
thread = threading.Thread(target=run_completion, args=(win_id, model, current_history, OUTPUT_FILE, _get_tokens(), FILE_LOCK))
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def process_queued_outputs() -> None:
|
|
||||||
"""Process outputs from the file using prof.win_show."""
|
|
||||||
if os.path.exists(OUTPUT_FILE):
|
|
||||||
with open(OUTPUT_FILE, "r") as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
outputs = []
|
|
||||||
for line in lines:
|
|
||||||
if line.strip():
|
|
||||||
try:
|
|
||||||
data = json.loads(line)
|
|
||||||
outputs.append((data["win_id"], data["content"]))
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
for win_id, content in outputs:
|
|
||||||
prof.win_show(win_id, content)
|
|
||||||
open(OUTPUT_FILE, "w").close() # truncate the file
|
|
||||||
|
|
||||||
|
|
||||||
def create_chat_window(model: str) -> str:
|
|
||||||
"""Create a new window for AI chat with the specified model."""
|
|
||||||
win_id = f"AI Chat - {model} - {int(time.time())}"
|
|
||||||
prof.win_create(win_id, handler)
|
|
||||||
prof.win_show(win_id, f"Chat started with {model}. Type a message to interact.")
|
|
||||||
prof.win_focus(win_id)
|
|
||||||
return win_id
|
|
||||||
|
|
||||||
|
|
||||||
def start_chat(model: Optional[str] = None) -> None:
|
|
||||||
"""Open a new chat window with the specified or default model."""
|
|
||||||
model = model if model else get_default_model()
|
|
||||||
create_chat_window(model)
|
|
||||||
prof.cons_show(f"Started AI chat with model: {model}")
|
|
||||||
|
|
||||||
|
|
||||||
def set_token(company: str, token: str) -> None:
|
|
||||||
"""Store an API token for a specific company."""
|
|
||||||
tokens = _get_tokens()
|
|
||||||
tokens[company] = token
|
|
||||||
_save_tokens(tokens)
|
|
||||||
prof.cons_show(f"Token set for {company}")
|
|
||||||
|
|
||||||
|
|
||||||
def clear_chat() -> None:
|
|
||||||
"""Notify that the current chat window is cleared."""
|
|
||||||
prof.cons_show("Sorry, current API doesn't support cleaning windows")
|
|
||||||
return
|
|
||||||
win_id = prof.get_current_win()
|
|
||||||
if win_id:
|
|
||||||
prof.win_show(win_id, "Chat cleared.")
|
|
||||||
else:
|
|
||||||
prof.cons_show("No active chat window.")
|
|
||||||
|
|
||||||
|
|
||||||
def correct_message(corrected_text: str) -> None:
|
|
||||||
"""Replace the latest user message in the current window's history and get AI response."""
|
|
||||||
prof.cons_show("Sorry, current API doesn't support correcting messages and getting current window")
|
|
||||||
return
|
|
||||||
# work in progress (get_current_win doesn't work)
|
|
||||||
win_id = prof.get_current_win()
|
|
||||||
if not win_id:
|
|
||||||
prof.cons_show("No active chat window. Use /ai start <model> first.")
|
|
||||||
return
|
|
||||||
title = prof.get_win_title(win_id)
|
|
||||||
if not title or not title.startswith("AI Chat - "):
|
|
||||||
prof.cons_show("Invalid chat window. Use /ai start <model> to create one.")
|
|
||||||
return
|
|
||||||
model = title.split(" - ", 2)[1]
|
|
||||||
history = CHAT_HISTORY.get(win_id, [])
|
|
||||||
user_messages = [msg for msg in history if msg["role"] == "user"]
|
|
||||||
if not user_messages:
|
|
||||||
prof.cons_show("No user messages in this chat to correct.")
|
|
||||||
return
|
|
||||||
# Replace the latest user message
|
|
||||||
for msg in history[::-1]:
|
|
||||||
if msg["role"] == "user":
|
|
||||||
msg["content"] = corrected_text
|
|
||||||
break
|
|
||||||
handler(win_id, corrected_text)
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_model() -> str:
|
|
||||||
"""Retrieve the default model from settings, defaulting to gpt-5.0."""
|
|
||||||
return prof.settings_string_get("ai_plugin", DEFAULT_MODEL_KEY, "openai/gpt-5.0")
|
|
||||||
|
|
||||||
|
|
||||||
def set_default_model(model: str) -> None:
|
|
||||||
"""Save the default model to settings."""
|
|
||||||
prof.settings_string_set("ai_plugin", DEFAULT_MODEL_KEY, model)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _cmd_ai(*args: Tuple[Optional[str], ...]) -> None:
|
|
||||||
"""Handle /ai commands for interacting with AI models.
|
|
||||||
|
|
||||||
Synopsis:
|
|
||||||
/ai
|
|
||||||
/ai set model <model>
|
|
||||||
/ai start [<model>]
|
|
||||||
/ai set token <company> <token>
|
|
||||||
/ai clear
|
|
||||||
/ai correct <message>
|
|
||||||
"""
|
|
||||||
if not args:
|
|
||||||
display_settings()
|
|
||||||
return
|
|
||||||
if args[0] == "set" and len(args) >= 2:
|
|
||||||
if args[1] == "model" and len(args) == 3:
|
|
||||||
set_model(args[2])
|
|
||||||
elif args[1] == "token" and len(args) == 3:
|
|
||||||
try:
|
|
||||||
company, token = args[2].split(" ", 1)
|
|
||||||
set_token(company, token)
|
|
||||||
except ValueError:
|
|
||||||
prof.cons_show("Invalid format, use: /ai set token <company> <token>")
|
|
||||||
else:
|
|
||||||
prof.cons_show("Invalid command, use: /ai set model|token")
|
|
||||||
elif args[0] == "start" and len(args) <= 3:
|
|
||||||
start_chat(args[1] if len(args) == 2 else None)
|
|
||||||
elif args[0] == "clear" and len(args) == 1:
|
|
||||||
clear_chat()
|
|
||||||
elif args[0] == "correct" and len(args) == 3:
|
|
||||||
correct_message(args[2])
|
|
||||||
else:
|
|
||||||
prof.cons_bad_cmd_usage("/ai")
|
|
||||||
|
|
||||||
|
|
||||||
def prof_init(version, status, account_name, fulljid):
|
|
||||||
"""Initialize the AI chat plugin and register commands."""
|
|
||||||
|
|
||||||
synopsis = [
|
|
||||||
"/ai",
|
|
||||||
"/ai set model <model>",
|
|
||||||
"/ai start [<model>]",
|
|
||||||
"/ai set token <company> <token>",
|
|
||||||
"/ai clear",
|
|
||||||
"/ai correct <message>",
|
|
||||||
]
|
|
||||||
description = """Interact with AI models via a chat interface using LiteLLM.
|
|
||||||
You can see the list of available models here: https://models.litellm.ai/"""
|
|
||||||
args = [
|
|
||||||
["", "Display current AI plugin settings"],
|
|
||||||
["set model <model>", "Set the default AI model (e.g., gpt-3.5-turbo)"],
|
|
||||||
["start [<model>]", "Start a new AI chat with the specified or default model"],
|
|
||||||
["set token <company> <token>", "Set an API token for a specific company"],
|
|
||||||
["clear", "Clear the current AI chat window"],
|
|
||||||
["correct <message>", "Correct a message using the current AI model"],
|
|
||||||
]
|
|
||||||
examples = [
|
|
||||||
"/ai",
|
|
||||||
"/ai set token openai sk-xxx",
|
|
||||||
"/ai set model gpt-4",
|
|
||||||
"/ai start xai/grok-4",
|
|
||||||
"/ai clear",
|
|
||||||
'/ai correct I has a error',
|
|
||||||
]
|
|
||||||
|
|
||||||
prof.register_command("/ai", 0, 3, synopsis, description, args, examples, _cmd_ai)
|
|
||||||
prof.completer_add("/ai", ["set", "start", "clear", "correct"])
|
|
||||||
prof.completer_add("/ai set", ["model", "token"])
|
|
||||||
prof.completer_add("/ai set model", ["openai/gpt-5", "xai/grok-3-mini"])
|
|
||||||
prof.completer_add("/ai start", ["xai/grok-4", "openai/gpt-4o"])
|
|
||||||
prof.completer_add("/ai set token", ["openai", "xai"])
|
|
||||||
|
|
||||||
prof.register_timed(process_queued_outputs, 1) # 1s interval to process AI message output
|
|
||||||
549
src/antispam.py
549
src/antispam.py
@@ -1,549 +0,0 @@
|
|||||||
"""
|
|
||||||
Antispam plugin for CProof.
|
|
||||||
|
|
||||||
Asks a question from new contacts to verify their humanity.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import math
|
|
||||||
import prof
|
|
||||||
import re
|
|
||||||
import datetime
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
# Constants
|
|
||||||
_antispam_defaults = {
|
|
||||||
'msg': 'Please, solve the captcha',
|
|
||||||
'question': 'Where a bullet must be lodged in order for rifle to shoot?',
|
|
||||||
'answer': 'Chamber',
|
|
||||||
'donemsg': 'You passed antispam. Congratulations!',
|
|
||||||
'blockmsg': 'You\'ve been blocked. Try again {} minutes later',
|
|
||||||
'errormsg': 'Wrong answer. Please, try again. The question is',
|
|
||||||
'otrmsg': 'Please, turn off the OTR and then send the answer.',
|
|
||||||
'unbanmsg': 'Your ban has been lifted. You can try again.',
|
|
||||||
'maxtries': 5,
|
|
||||||
'blocktime': 5,
|
|
||||||
'debug': 'off',
|
|
||||||
'dry': 'off'
|
|
||||||
}
|
|
||||||
|
|
||||||
_time_format = "%d.%m.%Y %H:%M:%S"
|
|
||||||
|
|
||||||
_roster = []
|
|
||||||
|
|
||||||
_page_limit = 10
|
|
||||||
|
|
||||||
_egg_counter = 0
|
|
||||||
|
|
||||||
_as_settings_strings = ['msg', 'question', 'answer', 'donemsg', 'blockmsg', 'otrmsg', 'errormsg', 'unbanmsg', 'debug', 'dry']
|
|
||||||
_as_settings_ints = {'maxtries': {'min': 2, 'max': 1000}, 'blocktime': {'min': -1, 'max': 100000}}
|
|
||||||
_as_settings = [*_as_settings_strings, *_as_settings_ints]
|
|
||||||
|
|
||||||
|
|
||||||
# Handlers
|
|
||||||
|
|
||||||
def prof_init(version, status, account_name, fulljid):
|
|
||||||
synopsis = [
|
|
||||||
"/antispam on|off",
|
|
||||||
"/antispam settings",
|
|
||||||
*[f"/antispam set {x} <msg>" for x in _as_settings_strings],
|
|
||||||
"/antispam set maxtries <tries>",
|
|
||||||
"/antispam set blocktime <minutes>",
|
|
||||||
"/antispam counter [<page|clear>]",
|
|
||||||
"/antispam history [<page|clear>]",
|
|
||||||
"/antispam blocklist [<page>|add|remove <jid or part of jid>|clear]",
|
|
||||||
"/antispam whitelist [<page>|add|remove <jid or part of jid>|clear]",
|
|
||||||
]
|
|
||||||
description = "Prevents spam messages by asking new contacts questions."
|
|
||||||
args = [
|
|
||||||
[ "set msg <msg>", "Set captcha message" ],
|
|
||||||
[ "set question <msg>", "Set custom captcha question" ],
|
|
||||||
[ "set answer <msg>", "Set answer to captcha question" ],
|
|
||||||
[ "set donemsg <msg>", "Set congratulation message for passing antispam" ],
|
|
||||||
[ "set blockmsg <msg>", "Set message that user is blocked. Use \"{}\" to show amounts of minutes before unlock. Example: You are banned. Unban in {} minutes." ],
|
|
||||||
[ "set otrmsg <msg>", "Set answer in case if unauthorized user tries to initiate OTR session." ],
|
|
||||||
[ "set errormsg <msg>", "Set prefix to answer in case if wrong answer given." ],
|
|
||||||
[ "set unbanmsg <msg>", "Set message given when temporary ban is finished. Set to \"None\" to prevent sending message." ],
|
|
||||||
[ "set maxtries <tries>", "Set maximum amount of tries before ban. Set -1 to make unlimited." ],
|
|
||||||
[ "set blocktime <minutes>", "Set time to block user for. Set -1 to make ban unlimited." ],
|
|
||||||
[ "set debug <on|off>", "Set debug mode on|off." ],
|
|
||||||
[ "set dry <on|off>", "Set dry mode on|off. In dry mode antispam doesn't block messages, mode is needed for debug use." ],
|
|
||||||
[ "counter [<page>|clear]", "Show or clear wrong answer counter" ],
|
|
||||||
[ "history [<page>|clear]", "Show or clear history" ],
|
|
||||||
[ "blocklist [add|remove <jid or part of jid>|clear]", "Show banned users, or add|remove user from ban" ],
|
|
||||||
[ "whitelist [add|remove <jid or part of jid>|clear]", "Whitelist users. Whitelist have priority over bans." ],
|
|
||||||
]
|
|
||||||
examples = [
|
|
||||||
"/antispam on",
|
|
||||||
"/antispam set msg Please, solve the captcha.",
|
|
||||||
"/antispam set question 2 + 2? (1984)",
|
|
||||||
"/antispam set answer 5",
|
|
||||||
"/antispam set donemsg You passed antispam. Now your messages are delivered.",
|
|
||||||
"/antispam set blockmsg You've been blocked by antispam. Please, try again later in {} minute(s).",
|
|
||||||
"/antispam set maxtries 5",
|
|
||||||
"/antispam set blocktime 69",
|
|
||||||
"/antispam blocklist clear",
|
|
||||||
"/antispam history 42",
|
|
||||||
]
|
|
||||||
|
|
||||||
prof.register_command("/antispam", 1, 40, synopsis, description, args, examples, _cmd_antispam)
|
|
||||||
|
|
||||||
prof.register_command("/as", 1, 40, _prep_alias(synopsis), description, args, _prep_alias(examples), _cmd_antispam)
|
|
||||||
|
|
||||||
prof.completer_add("/antispam",
|
|
||||||
[ "set", "history", "blocklist", "whitelist" ]
|
|
||||||
)
|
|
||||||
|
|
||||||
prof.completer_add("/antispam set",
|
|
||||||
_as_settings
|
|
||||||
)
|
|
||||||
prof.completer_add("/antispam history",
|
|
||||||
[ 'clear' ]
|
|
||||||
)
|
|
||||||
|
|
||||||
prof.completer_add("/antispam blocklist",
|
|
||||||
[ 'add', 'remove', 'clear' ]
|
|
||||||
)
|
|
||||||
|
|
||||||
prof.completer_add("/antispam whitelist",
|
|
||||||
[ 'add', 'remove', 'clear' ]
|
|
||||||
)
|
|
||||||
|
|
||||||
prof.register_timed(_update_banned_list, 60)
|
|
||||||
|
|
||||||
_force_update_roster((fulljid.split('/')[0] if fulljid else ''))
|
|
||||||
|
|
||||||
prof.cons_show("[AntiSpam] plugin started.")
|
|
||||||
if _is_dry_mode():
|
|
||||||
prof.cons_show("[AntiSpam] WARNING: dry-mode is on, no messages are going to be blocked.")
|
|
||||||
|
|
||||||
def prof_on_message_stanza_send(stanza):
|
|
||||||
body = _parse_tag_content(stanza, 'body')
|
|
||||||
if body and (to := _parse_attr(stanza, 'to').split('/', 1)[0]):
|
|
||||||
if not _is_authorized(to):
|
|
||||||
prof.settings_string_list_add("antispam", "authorized", to)
|
|
||||||
return stanza
|
|
||||||
|
|
||||||
def prof_on_presence_stanza_send(stanza):
|
|
||||||
if _parse_attr(stanza, 'type') == 'subscribe' and (to := _parse_attr(stanza, 'to').split('/', 1)[0]):
|
|
||||||
if not _is_authorized(to):
|
|
||||||
prof.settings_string_list_add("antispam", "authorized", to)
|
|
||||||
return stanza
|
|
||||||
|
|
||||||
|
|
||||||
def prof_on_message_stanza_receive(stanza) -> bool:
|
|
||||||
return _antispam_check_wrapper(stanza, 'msg')
|
|
||||||
|
|
||||||
def prof_on_presence_stanza_receive(stanza) -> bool:
|
|
||||||
return _antispam_check_wrapper(stanza, 'presence')
|
|
||||||
|
|
||||||
def prof_on_iq_stanza_receive(stanza) -> bool:
|
|
||||||
if 'jabber:iq:roster' in stanza and 'type="result"' in stanza:
|
|
||||||
_update_roster(stanza)
|
|
||||||
return _antispam_check_wrapper(stanza, 'iq')
|
|
||||||
|
|
||||||
# Custom functions
|
|
||||||
|
|
||||||
def _cmd_antispam(*args):
|
|
||||||
if not len(args):
|
|
||||||
_show_cmd_error()
|
|
||||||
if args[0] == "on":
|
|
||||||
prof.settings_boolean_set("antispam", "enabled", True)
|
|
||||||
prof.cons_show("Antispam is enabled.")
|
|
||||||
return
|
|
||||||
if args[0] == "off":
|
|
||||||
prof.settings_boolean_set("antispam", "enabled", False)
|
|
||||||
prof.cons_show("Antispam is disabled.")
|
|
||||||
return
|
|
||||||
if args[0] == "set":
|
|
||||||
if len(args) < 3:
|
|
||||||
_show_cmd_error('More arguments required')
|
|
||||||
return
|
|
||||||
key = args[1]
|
|
||||||
if key not in _as_settings:
|
|
||||||
_show_cmd_error(f'Wrong setting. You can use only {_as_settings}')
|
|
||||||
return
|
|
||||||
if args[2] == "default":
|
|
||||||
_set_default_values(key)
|
|
||||||
return
|
|
||||||
if _set_values(key, args[2:]):
|
|
||||||
prof.cons_show(f'Antispam setting {key} was successfully changed to "{" ".join(args[2:])}"')
|
|
||||||
else:
|
|
||||||
_show_cmd_error("Something went wrong, setting wasn't changed")
|
|
||||||
return
|
|
||||||
if args[0] == "settings":
|
|
||||||
prof.cons_show("===== Antispam settings =====")
|
|
||||||
for key in _as_settings:
|
|
||||||
value = _get_string_setting(key) if key in _as_settings_strings else _get_int_setting(key)
|
|
||||||
fvalue = f'"{value}"' if key in _as_settings_strings else value
|
|
||||||
prof.cons_show(f'{key} is {fvalue}')
|
|
||||||
return
|
|
||||||
if args[0] in ["counter", "history", "authorized"]:
|
|
||||||
return _cmd_show_or_clear(args)
|
|
||||||
if args[0] == "blocklist":
|
|
||||||
return _cmd_as_blocklist(args)
|
|
||||||
if args[0] == "whitelist":
|
|
||||||
return _cmd_as_whitelist(args)
|
|
||||||
if args[0] == "egg":
|
|
||||||
global _egg_counter
|
|
||||||
if _egg_counter == 10:
|
|
||||||
prof.cons_show(f"Do you know that it's an antispam plugin and not an egg farm? No more eggs for you!")
|
|
||||||
return
|
|
||||||
_egg_counter += 1
|
|
||||||
prof.cons_show(f"Egg number {_egg_counter} has been planted.")
|
|
||||||
if _egg_counter == 5:
|
|
||||||
prof.cons_show(f"What do you need so many eggs for?")
|
|
||||||
return
|
|
||||||
if args[0] == "roster":
|
|
||||||
prof.cons_show("\n".join(_roster))
|
|
||||||
return
|
|
||||||
prof.cons_bad_cmd_usage('/antispam')
|
|
||||||
|
|
||||||
def _cmd_show_or_clear(args):
|
|
||||||
if len(args) == 1:
|
|
||||||
return _show_page(args[0])
|
|
||||||
if len(args) == 2:
|
|
||||||
if args[1] == "clear":
|
|
||||||
if prof.settings_string_list_clear("antispam", args[0]):
|
|
||||||
prof.cons_show(f'{args[0].capitalize()} has been cleared.')
|
|
||||||
else:
|
|
||||||
prof.cons_show(f'{args[0].capitalize()} is already empty.')
|
|
||||||
return
|
|
||||||
return _show_page(args[0], args[1])
|
|
||||||
prof.cons_bad_cmd_usage('/antispam')
|
|
||||||
|
|
||||||
def _cmd_as_blocklist(args):
|
|
||||||
if len(args) < 3:
|
|
||||||
return _cmd_show_or_clear(args)
|
|
||||||
if args[1] == "add":
|
|
||||||
for jid in args[2:]:
|
|
||||||
ban_time = _ban_user(jid, full_jid=False, time_limited=False)
|
|
||||||
prof.cons_show(f'JID "{jid}" has been banned {ban_time}.')
|
|
||||||
elif args[1] == "remove":
|
|
||||||
for jid in args[2:]:
|
|
||||||
if _unban_user(jid):
|
|
||||||
prof.cons_show(f'JID "{jid}" has been unbanned.')
|
|
||||||
else:
|
|
||||||
prof.cons_show(f'JID "{jid}" has not been found in the blocklist.')
|
|
||||||
|
|
||||||
def _cmd_as_whitelist(args):
|
|
||||||
if len(args) < 3:
|
|
||||||
return _cmd_show_or_clear(args)
|
|
||||||
if args[1] == "add":
|
|
||||||
for jid in args[2:]:
|
|
||||||
prof.settings_string_list_add("antispam", "whitelist", jid)
|
|
||||||
prof.cons_show(f'JID "{jid}" has been whitelisted.')
|
|
||||||
elif args[1] == "remove":
|
|
||||||
for jid in args[2:]:
|
|
||||||
if prof.settings_string_list_remove("antispam", "whitelist", jid):
|
|
||||||
prof.cons_show(f'JID "{jid}" has been removed from the whitelist.')
|
|
||||||
else:
|
|
||||||
prof.cons_show(f'JID "{jid}" has not been found in the whitelist.')
|
|
||||||
|
|
||||||
def _show_cmd_error(error="", user_mistake=True) -> None:
|
|
||||||
"""Used to shows str:error when user typed wrong input"""
|
|
||||||
if user_mistake:
|
|
||||||
prof.cons_bad_cmd_usage('/antispam')
|
|
||||||
if error:
|
|
||||||
prof.cons_show(error)
|
|
||||||
|
|
||||||
|
|
||||||
def _set_default_values(key) -> bool:
|
|
||||||
default_value = _antispam_defaults[key]
|
|
||||||
if (key in _as_settings_strings):
|
|
||||||
prof.settings_string_set("antispam", key, default_value)
|
|
||||||
elif (key in _as_settings_ints):
|
|
||||||
prof.settings_int_set("antispam", key, default_value)
|
|
||||||
prof.cons_show(f'Antispam setting {key} was successfully set to its default ({default_value}).')
|
|
||||||
|
|
||||||
def _set_values(key, value) -> bool:
|
|
||||||
"""Returns Bool True if values has been set, False is value is invalid"""
|
|
||||||
value = ' '.join(value)
|
|
||||||
if key in _as_settings_strings:
|
|
||||||
prof.settings_string_set("antispam", key, value)
|
|
||||||
return True
|
|
||||||
if key in _as_settings_ints:
|
|
||||||
min_val, max_val = _as_settings_ints[key]['min'], _as_settings_ints[key]['max']
|
|
||||||
if not _represents_int_in_range(value, min_val, max_val):
|
|
||||||
prof.cons_show(f'Value of {key} must be an integer between {min_val} and {max_val}!')
|
|
||||||
return False
|
|
||||||
prof.settings_int_set("antispam", key, int(value))
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _parse_tag_content(stanza, tag):
|
|
||||||
tmp = re.search(f"<{tag}.*?>([\S\s]*?)</{tag}>", stanza) # don't blame me, it works :D
|
|
||||||
try:
|
|
||||||
return tmp.group(1) if tmp else ""
|
|
||||||
except IndexError as e:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _parse_attr(stanza, attribute) -> str:
|
|
||||||
"""Parse attribute from the first tag"""
|
|
||||||
tmp = re.search(f'^\s*<[^>]+{attribute}="([^"]+)"', stanza)
|
|
||||||
try:
|
|
||||||
return tmp.group(1) if tmp else ""
|
|
||||||
except IndexError as e:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _parse_sender(stanza) -> str:
|
|
||||||
"""Returns sender from stanza or empty string if not found"""
|
|
||||||
if "<forwarded" in stanza:
|
|
||||||
stanza = _parse_tag_content(stanza, "forwarded")
|
|
||||||
return _parse_attr(stanza, "from").split('/', 1)[0]
|
|
||||||
|
|
||||||
def _parse_id(stanza) -> str:
|
|
||||||
"""Returns id from stanza or empty string if not found"""
|
|
||||||
return _parse_attr(stanza, "id")
|
|
||||||
|
|
||||||
def _is_dry_mode() -> bool:
|
|
||||||
return _get_string_setting("dry") == "on"
|
|
||||||
|
|
||||||
def _antispam_check_wrapper(stanza, stanza_type) -> bool:
|
|
||||||
result = _antispam_check(stanza, stanza_type)
|
|
||||||
dry_mode = _is_dry_mode()
|
|
||||||
if not result:
|
|
||||||
if _get_string_setting("debug") == "on" or dry_mode:
|
|
||||||
prof.cons_show(f"[AntiSpam{' (DryMode)' if dry_mode else ''}] Blocked stanza: {stanza}")
|
|
||||||
else:
|
|
||||||
prof.log_debug(f"[AntiSpam] Blocked stanza: {stanza}")
|
|
||||||
return result or _is_dry_mode()
|
|
||||||
|
|
||||||
def _antispam_check(stanza, stanza_type) -> bool:
|
|
||||||
"""Returns Bool True if message is not spam, False if message should be blocked (spam/unverified sender)"""
|
|
||||||
sender = _parse_sender(stanza)
|
|
||||||
stanza_id = _parse_id(stanza)
|
|
||||||
if not sender and "from=" in stanza:
|
|
||||||
prof.log_warning(f"In the following stanza From is present, but not catched: \n{stanza}")
|
|
||||||
if not _is_activated():
|
|
||||||
return True
|
|
||||||
if _is_whitelisted(sender) or _is_in_roster(sender):
|
|
||||||
return True
|
|
||||||
if _is_banned(sender):
|
|
||||||
return False
|
|
||||||
if _is_authorized(sender):
|
|
||||||
return True
|
|
||||||
if not _roster:
|
|
||||||
prof.log_debug(f"[AntiSpam] Passed message because roster was empty. Stanza: {stanza}")
|
|
||||||
return True
|
|
||||||
if stanza_type == "presence":
|
|
||||||
if re.search('^\s*<presence.+type="subscribe".+>', stanza):
|
|
||||||
_send_stanza(f'<presence type="unsubscribed" to="{sender}" />')
|
|
||||||
_send_stanza(f'<message type="chat" to="{sender}"><subject>{_get_string_setting("msg")}</subject><body>{_get_string_setting("question")}</body></message>')
|
|
||||||
_counter_inc(sender)
|
|
||||||
elif stanza_type == "iq":
|
|
||||||
if re.search('^\s*<iq[^>]+type="set"', stanza):
|
|
||||||
to = f'to="{sender}" ' if sender else ''
|
|
||||||
_send_stanza(f'<iq type="error" id="{stanza_id}" {to}/>')
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
elif stanza_type == "msg":
|
|
||||||
if _parse_attr(stanza, "type") == "error":
|
|
||||||
prof.settings_string_list_add("antispam", "history", f"{_get_time()}|{sender}|{_parse_tag_content(stanza, 'body')}")
|
|
||||||
return False
|
|
||||||
if not _is_correct_answer(stanza):
|
|
||||||
if (is_otr_requested := "OTRv2" in stanza):
|
|
||||||
_send_stanza(f'<message type="chat" to="{sender}"><body>{_get_string_setting("otrmsg")}</body></message>')
|
|
||||||
prof.settings_string_list_add("antispam", "history", f"{_get_time()}|{sender}|{_parse_tag_content(stanza, 'body')}")
|
|
||||||
wrong_tries = _get_counter(sender)
|
|
||||||
subject = f'<subject>{_get_string_setting("msg")}</subject>' if wrong_tries == 0 else ''
|
|
||||||
wrong_answer = _get_string_setting("errormsg")+'\n' if wrong_tries > 0 and not is_otr_requested else ''
|
|
||||||
_send_stanza(f'<message type="chat" to="{sender}">{subject}<body>{wrong_answer}{_get_string_setting("question")}</body></message>')
|
|
||||||
_counter_inc(sender)
|
|
||||||
return False
|
|
||||||
prof.settings_string_list_add("antispam", "authorized", sender)
|
|
||||||
_send_stanza(f'<message type="chat" to="{sender}"><body>{_get_string_setting("donemsg")}</body></message>')
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _force_update_roster(myjid=None) -> None:
|
|
||||||
global _roster
|
|
||||||
if not _roster and myjid:
|
|
||||||
_roster = [myjid]
|
|
||||||
prof.send_stanza(f'<iq id="{uuid4()}" type="get"><query xmlns="jabber:iq:roster"/>\</iq>')
|
|
||||||
return
|
|
||||||
|
|
||||||
def _update_roster(stanza) -> None:
|
|
||||||
global _roster
|
|
||||||
_roster = [
|
|
||||||
*re.findall('<iq[^>]+to="([^"/]+)["/]', stanza),
|
|
||||||
*[x for x,y in re.findall('<item[^>]+jid="([^"]+?)"[^>]+subscription="([^"]+?)"', stanza) if y == "both" or y == "to"],
|
|
||||||
*[x for y,x in re.findall('<item[^>]+subscription="([^"]+?)"[^>]+jid="([^"]+?)"', stanza) if y == "both" or y == "to"],
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _is_activated() -> bool:
|
|
||||||
return prof.settings_boolean_get("antispam", "enabled", True)
|
|
||||||
|
|
||||||
def _update_banned_list() -> None:
|
|
||||||
ban_list = prof.settings_string_list_get("antispam", "blocklist") or []
|
|
||||||
for banned_user in ban_list:
|
|
||||||
if not (tmp := _unpack_banned_user(banned_user)):
|
|
||||||
continue
|
|
||||||
ban_time, ban_type, jid = tmp
|
|
||||||
if ban_time != "Forever" and not _is_future_time(ban_time):
|
|
||||||
unbanmsg = _get_string_setting("unbanmsg")
|
|
||||||
if unbanmsg != "None":
|
|
||||||
_send_stanza(f'<message type="chat" to="{jid}"><body>{unbanmsg}</body></message>')
|
|
||||||
prof.settings_string_list_remove("antispam", "blocklist", banned_user)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _unpack_banned_user(banned_user):
|
|
||||||
"""Returns None or Tuple of banned user info in (time_banned, ban_type, jid) format"""
|
|
||||||
tmp = banned_user.split("|", 2)
|
|
||||||
if len(tmp) != 3:
|
|
||||||
prof.cons_show(f"Problem on banned user unpacking: \"{banned_user}\". Deleting...")
|
|
||||||
prof.settings_string_list_remove("antispam", "blocklist", banned_user)
|
|
||||||
return
|
|
||||||
return tmp
|
|
||||||
|
|
||||||
def _is_in_roster(jid) -> bool:
|
|
||||||
return jid in _roster
|
|
||||||
|
|
||||||
def _is_authorized(jid) -> bool:
|
|
||||||
return jid in (prof.settings_string_list_get("antispam", "authorized") or [])
|
|
||||||
|
|
||||||
def _is_banned(jid) -> bool:
|
|
||||||
ban_list = prof.settings_string_list_get("antispam", "blocklist") or []
|
|
||||||
for banned_user in ban_list:
|
|
||||||
if not (tmp := _unpack_banned_user(banned_user)):
|
|
||||||
continue
|
|
||||||
ban_time, ban_type, bjid = tmp
|
|
||||||
if ban_type == "F": # On full JID ban (e.g. "user@example.com")
|
|
||||||
if jid == bjid and (ban_time == "Forever" or _is_future_time(ban_time)):
|
|
||||||
return True
|
|
||||||
else: # On partial JID ban (e.g. "@example.com", "spammer")
|
|
||||||
if bjid in jid and (ban_time == "Forever" or _is_future_time(ban_time)):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _is_whitelisted(jid) -> bool:
|
|
||||||
wl_users = (prof.settings_string_list_get("antispam", "whitelist") or [])
|
|
||||||
return any(wj in jid for wj in wl_users)
|
|
||||||
|
|
||||||
def _get_validate_count(c):
|
|
||||||
tmp = c.split('|', 1)
|
|
||||||
if len(tmp) != 2:
|
|
||||||
prof.log_warning(f'Invalid counter entry: "{c}". Removing...')
|
|
||||||
prof.settings_string_list_remove("antispam", "counter", c)
|
|
||||||
return
|
|
||||||
return int(tmp[0]), tmp[1]
|
|
||||||
|
|
||||||
def _counter_inc(jid) -> None:
|
|
||||||
counter = prof.settings_string_list_get("antispam", "counter") or []
|
|
||||||
for c in counter:
|
|
||||||
if not (tmp := _get_validate_count(c)):
|
|
||||||
continue
|
|
||||||
count, cjid = tmp
|
|
||||||
if jid == cjid:
|
|
||||||
prof.settings_string_list_remove("antispam", "counter", c)
|
|
||||||
prof.settings_string_list_add("antispam", "counter", f"{count+1}|{jid}")
|
|
||||||
if count % _get_int_setting("maxtries") == 0:
|
|
||||||
ban_time = _ban_user(jid)
|
|
||||||
blockmsg = _get_string_setting("blockmsg").replace('{}', str(ban_time))
|
|
||||||
_send_stanza(f'<message type="chat" to="{jid}"><body>{blockmsg}</body></message>')
|
|
||||||
return
|
|
||||||
prof.settings_string_list_add("antispam", "counter", f"1|{jid}")
|
|
||||||
|
|
||||||
|
|
||||||
def _get_counter(jid) -> int:
|
|
||||||
counter = prof.settings_string_list_get("antispam", "counter") or []
|
|
||||||
for c in counter:
|
|
||||||
if not (tmp := _get_validate_count(c)):
|
|
||||||
continue
|
|
||||||
count, cjid = tmp
|
|
||||||
if jid == cjid:
|
|
||||||
return count
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def _show_page(table, page_num=1) -> None:
|
|
||||||
"""Shows a page from table N (table is fetched from string_list setting with name) to plugin user through cons_show."""
|
|
||||||
if not _represents_int_in_range(page_num, 1):
|
|
||||||
prof.cons_show("Use correct page number. It must be integer from 1 to 10000.")
|
|
||||||
return
|
|
||||||
page_num = int(page_num)
|
|
||||||
page = page_num-1
|
|
||||||
table_content = prof.settings_string_list_get("antispam", table) or []
|
|
||||||
table_size = len(table_content)
|
|
||||||
total_pages = math.ceil(table_size/_page_limit)
|
|
||||||
if not table_content:
|
|
||||||
return prof.cons_show(f"Nothing to display, {table} is empty")
|
|
||||||
if page_num > total_pages:
|
|
||||||
return prof.cons_show(f"Page {page_num} does not exist. There are only {total_pages} pages")
|
|
||||||
page_content = table_content[page*_page_limit:page_num*_page_limit]
|
|
||||||
if not page_content:
|
|
||||||
return prof.cons_show(f"Nothing to display, {table} on Page {page_num} is empty")
|
|
||||||
for x in page_content:
|
|
||||||
formatting = (lambda x: x)
|
|
||||||
prof.cons_show(formatting(x))
|
|
||||||
prof.cons_show(f"Page {page_num}/{total_pages}. Currently {table_size} entries.")
|
|
||||||
|
|
||||||
def _ban_user(jid, full_jid=True, time_limited=True, custom_time_limit=None):
|
|
||||||
"""Adds user to ban list"""
|
|
||||||
ban_type = "F" if full_jid else "P"
|
|
||||||
if time_limited and (ban_time := custom_time_limit if custom_time_limit else _get_int_setting("blocktime")) != -1:
|
|
||||||
ban_time_formatted = (datetime.datetime.now() + datetime.timedelta(minutes=ban_time)).strftime("%d.%m.%Y %H:%M:%S")
|
|
||||||
_unban_user(jid)
|
|
||||||
prof.settings_string_list_add("antispam", "blocklist", f"{ban_time_formatted}|{ban_type}|{jid}")
|
|
||||||
return ban_time
|
|
||||||
prof.settings_string_list_add("antispam", "blocklist", f"Forever|{ban_type}|{jid}")
|
|
||||||
return "forever"
|
|
||||||
|
|
||||||
def _unban_user(jid):
|
|
||||||
"""Removes user from ban list. True on success, otherwise false."""
|
|
||||||
ban_list = prof.settings_string_list_get("antispam", "blocklist") or []
|
|
||||||
for banned_user in ban_list:
|
|
||||||
if not (tmp := _unpack_banned_user(banned_user)):
|
|
||||||
continue
|
|
||||||
ban_time, ban_type, bjid = tmp
|
|
||||||
if bjid == jid:
|
|
||||||
return prof.settings_string_list_remove("antispam", "blocklist", banned_user)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# Settings utils
|
|
||||||
|
|
||||||
def _get_string_setting(setting) -> str:
|
|
||||||
return prof.settings_string_get("antispam", setting, _antispam_defaults[setting])
|
|
||||||
|
|
||||||
def _get_int_setting(setting) -> int:
|
|
||||||
return prof.settings_int_get("antispam", setting, _antispam_defaults[setting])
|
|
||||||
|
|
||||||
# Utils
|
|
||||||
|
|
||||||
def _prep_alias(replace_list):
|
|
||||||
return [x.replace("/antispam", "/as") for x in replace_list]
|
|
||||||
|
|
||||||
def _is_correct_answer(stanza):
|
|
||||||
correct_answer = _get_string_setting('answer')
|
|
||||||
answer = _parse_tag_content(stanza, "body")
|
|
||||||
if not answer:
|
|
||||||
prof.log_warning(f'Invalid mesage stanza. Ignoring user input. Stanza: {stanza}')
|
|
||||||
return False
|
|
||||||
return answer.strip().lower() == correct_answer.strip().lower()
|
|
||||||
|
|
||||||
def _is_future_time(time) -> bool:
|
|
||||||
"""Checks formatted time and returns True if time from parameter is in the future"""
|
|
||||||
try:
|
|
||||||
ptime = datetime.datetime.strptime(time, _time_format)
|
|
||||||
except ValueError as e:
|
|
||||||
prof.log_error(f"Exception raised when time was compared (input time: {time})\n{e}")
|
|
||||||
return True
|
|
||||||
return ptime > datetime.datetime.now()
|
|
||||||
|
|
||||||
def _get_time():
|
|
||||||
"""Returns current time formatted"""
|
|
||||||
return datetime.datetime.now().strftime(_time_format)
|
|
||||||
|
|
||||||
def _represents_int_in_range(s, num_min=-1, num_max=10000) -> bool:
|
|
||||||
"""Returns True if passed number is integer and it is in provided range (from -1 to 10000 by default)"""
|
|
||||||
try:
|
|
||||||
num = int(s)
|
|
||||||
return num >= num_min and num <= num_max
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _send_stanza(stanza):
|
|
||||||
if not _is_dry_mode():
|
|
||||||
return prof.send_stanza(stanza)
|
|
||||||
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
"""
|
|
||||||
autocorrector - Replace common typos
|
|
||||||
|
|
||||||
My typing style is such a mess. But I invest more time in correcting it
|
|
||||||
programmatically than training myself to type better. Maybe thats the
|
|
||||||
problem? Anyway, here is a plugin for all keyboard messies like me.
|
|
||||||
"""
|
|
||||||
import prof
|
|
||||||
|
|
||||||
def prof_pre_chat_message_send(barejid, message):
|
|
||||||
enabled = prof.settings_string_get("autocorrector", "enabled", "on")
|
|
||||||
if enabled == "off":
|
|
||||||
return message
|
|
||||||
else:
|
|
||||||
replacements = prof.settings_string_list_get("autocorrector","replacements")
|
|
||||||
for replacement in replacements:
|
|
||||||
repl=replacement.split(':')
|
|
||||||
message=message.replace(repl[0],repl[1])
|
|
||||||
return message
|
|
||||||
|
|
||||||
def prof_pre_priv_message_send(barejid, nick, message):
|
|
||||||
enabled = prof.settings_string_get("autocorrector", "enabled", "on")
|
|
||||||
if enabled == "off":
|
|
||||||
return message
|
|
||||||
else:
|
|
||||||
replacements = prof.settings_string_list_get("autocorrector","replacements")
|
|
||||||
for replacement in replacements:
|
|
||||||
repl=replacement.split(':')
|
|
||||||
message=message.replace(repl[0],repl[1])
|
|
||||||
return message
|
|
||||||
|
|
||||||
def prof_pre_room_message_send(barejid, message):
|
|
||||||
enabled = prof.settings_string_get("autocorrector", "enabled", "on")
|
|
||||||
if enabled == "off":
|
|
||||||
return message
|
|
||||||
else:
|
|
||||||
replacements = prof.settings_string_list_get("autocorrector","replacements")
|
|
||||||
for replacement in replacements:
|
|
||||||
repl=replacement.split(':')
|
|
||||||
message=message.replace(repl[0],repl[1])
|
|
||||||
return message
|
|
||||||
|
|
||||||
def _cmd_autocorrector(arg1=None, arg2=None):
|
|
||||||
if arg1 == "on":
|
|
||||||
prof.settings_string_set("autocorrector", "enabled", "on")
|
|
||||||
prof.cons_show("autocorrector plugin enabled")
|
|
||||||
elif arg1 == "off":
|
|
||||||
prof.settings_string_set("autocorrector", "enabled", "off")
|
|
||||||
prof.cons_show("autocorrector plugin disabled")
|
|
||||||
elif arg1 == "clearall":
|
|
||||||
prof.settings_string_list_clear("autocorrector","replacements")
|
|
||||||
prof.cons_show("autocorrector plugin replacement list cleared.")
|
|
||||||
elif arg1 == "showlist":
|
|
||||||
replacements = prof.settings_string_list_get("autocorrector","replacements")
|
|
||||||
prof.cons_show("Replacement string list:")
|
|
||||||
for replacement in replacements:
|
|
||||||
prof.cons_show(replacement)
|
|
||||||
elif arg1 == "add":
|
|
||||||
if arg2 == None:
|
|
||||||
prof.cons_bad_cmd_usage("/autocorrector")
|
|
||||||
else:
|
|
||||||
if len(arg2.split(':')) == 2:
|
|
||||||
prof.settings_string_list_add("autocorrector","replacements",arg2)
|
|
||||||
prof.cons_show("Replacement String added.")
|
|
||||||
else:
|
|
||||||
prof.cons_show("Invalid replacement String. Please use the following format to specify replacements: pattern:replacement.")
|
|
||||||
elif arg1 == "rm":
|
|
||||||
if arg2 == None:
|
|
||||||
prof.cons_bad_cmd_usage("/autocorrector")
|
|
||||||
else:
|
|
||||||
if len(arg2.split(':')) == 2:
|
|
||||||
prof.settings_string_list_remove("autocorrector","replacements",arg2)
|
|
||||||
prof.cons_show("Replacement String removed.")
|
|
||||||
else:
|
|
||||||
prof.cons_show("Invalid replacement String. Please use the following format to specify replacements: pattern:replacement.")
|
|
||||||
else:
|
|
||||||
enabled = prof.settings_string_get("autocorrector", "enabled", "on")
|
|
||||||
if enabled == "off":
|
|
||||||
prof.cons_show("autocorrector inactive.")
|
|
||||||
else:
|
|
||||||
prof.cons_show("autocorrector active.")
|
|
||||||
|
|
||||||
def prof_init(version, status, account_name, fulljid):
|
|
||||||
synopsis = [
|
|
||||||
"/autocorrector on|off",
|
|
||||||
"/autocorrector showlist",
|
|
||||||
"/autocorrector clearall",
|
|
||||||
"/autocorrector add"
|
|
||||||
"/autocorrector rm"
|
|
||||||
]
|
|
||||||
description = "Messy typist autocorrector. Replaces common typos in your outgoing messages."
|
|
||||||
args = [
|
|
||||||
[ "on|off", "Enable/disable correction for all windows" ],
|
|
||||||
[ "showlist", "show typolist." ],
|
|
||||||
[ "add", "Add entry to typolist. Use pattern:replacement as format." ],
|
|
||||||
[ "rm", "Remove entry from typolist. Use pattern:replacement as format." ],
|
|
||||||
[ "clearall", "Remove all entries from typolist" ]
|
|
||||||
]
|
|
||||||
examples = [
|
|
||||||
"/autocorrector on",
|
|
||||||
"/autocorrector add tpyo:typo",
|
|
||||||
"/autocorrector add tset:test",
|
|
||||||
"/autocorrector rm tset:test",
|
|
||||||
"/autocorrector showlist",
|
|
||||||
]
|
|
||||||
|
|
||||||
prof.settings_string_set("autocorrector", "enabled", "on")
|
|
||||||
prof.register_command("/autocorrector", 0, 2, synopsis, description, args, examples, _cmd_autocorrector)
|
|
||||||
prof.completer_add("/autocorrector", [ "on", "off", "clearall", "add", "rm", "showlist" ])
|
|
||||||
|
|
||||||
7
src/c/moj/.gitignore
vendored
7
src/c/moj/.gitignore
vendored
@@ -1,7 +0,0 @@
|
|||||||
moj
|
|
||||||
moj.exe
|
|
||||||
*.o
|
|
||||||
*.so
|
|
||||||
gen/lookup.c
|
|
||||||
gen/emoji.json
|
|
||||||
gen/emoji.csv
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2018, Adrian "vifino" Pistol
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# moj makefile
|
|
||||||
CC ?= cc
|
|
||||||
|
|
||||||
MOJILIST=https://raw.githubusercontent.com/milesj/emojibase/master/packages/data/en/raw.json
|
|
||||||
|
|
||||||
# Do not touch.
|
|
||||||
all: moj libmoj.so
|
|
||||||
|
|
||||||
# Generate lookup lists
|
|
||||||
gen/emoji.json:
|
|
||||||
mkdir -p gen
|
|
||||||
wget -O $@ ${MOJILIST}
|
|
||||||
|
|
||||||
gen/emoji.csv: gen/emoji.json
|
|
||||||
jq -r '.[] | select(.emoji) | [.emoji] + .shortcodes | @csv' -- \
|
|
||||||
gen/emoji.json | \
|
|
||||||
sed 's/^"//;s/"$$//' | awk -f src/process.awk > $@
|
|
||||||
|
|
||||||
gen/emoji.gperf: src/gen_gperf.sh gen/emoji.csv
|
|
||||||
./src/gen_gperf.sh > ./gen/emoji.gperf
|
|
||||||
|
|
||||||
gen/emoji_list.h: src/gen_list.sh gen/emoji.csv
|
|
||||||
./src/gen_list.sh > $@
|
|
||||||
|
|
||||||
gen/lookup.c: gen/emoji.gperf
|
|
||||||
gperf --global-table --readonly-tables \
|
|
||||||
--struct-type --includes \
|
|
||||||
--compare-lengths --compare-strncmp \
|
|
||||||
--ignore-case -Q emoji_spool \
|
|
||||||
-H emoji_name_hash -N emoji_lookup_ref -m 3 \
|
|
||||||
--output-file=$@ $^
|
|
||||||
|
|
||||||
# Compile our code.
|
|
||||||
src/moj.o: gen/lookup.c gen/emoji_list.h src/moj.c
|
|
||||||
$(CC) -c ${CFLAGS} ${CPPFLAGS} -o $@ src/moj.c
|
|
||||||
|
|
||||||
libmoj.so: src/moj.o
|
|
||||||
$(CC) -shared -fPIC ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $<
|
|
||||||
|
|
||||||
moj: src/main.c src/moj.o
|
|
||||||
$(CC) ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $^
|
|
||||||
|
|
||||||
# Profanity plugin.
|
|
||||||
profanity_emoji.so: src/profanity_emoji.c src/moj.o
|
|
||||||
$(CC) -shared -fPIC ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $^
|
|
||||||
|
|
||||||
profanity: profanity_emoji.so
|
|
||||||
|
|
||||||
# Install
|
|
||||||
install: moj
|
|
||||||
install -d $(DESTDIR)/$(PREFIX)/bin
|
|
||||||
install moj $(DESTDIR)/$(PREFIX)/bin
|
|
||||||
|
|
||||||
install-devel: libmoj.so
|
|
||||||
install -d $(DESTDIR)/$(PREFIX)/lib
|
|
||||||
install libmoj.so $(DESTDIR)/$(PREFIX)/lib
|
|
||||||
install -d $(DESTDIR)/$(PREFIX)/include
|
|
||||||
cp src/moj.h $(DESTDIR)/$(PREFIX)/include/libmoj.h
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
rm -f $(DESTDIR)/$(PREFIX)/bin/moj
|
|
||||||
uninstall-devel:
|
|
||||||
rm -f $(DESTDIR)/$(PREFIX)/lib/libmoj.so $(DESTDIR)/$(PREFIX)/include/libmoj.h
|
|
||||||
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
clean:
|
|
||||||
rm -f moj gen/lookup.c src/*.o profanity_emoji.so
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# moj
|
|
||||||
'moj is for those of us that want something more minimal than emojify.
|
|
||||||
|
|
||||||
Reasons to use moj instead of emojify:
|
|
||||||
- only 3 letters(!), huge reduction compared to 7 of emojify.
|
|
||||||
- written in C, compile time hash table generation, no interpreted language.
|
|
||||||
- VERY FAST. How does 0.002s to generate the average commit messages of this project sound? This is on a ThinkPad T61. A potato.
|
|
||||||
- data not mixed with code, only generated data/glue and handwritten code. (seperate files)
|
|
||||||
|
|
||||||
# Requirements
|
|
||||||
- Make (compile time)
|
|
||||||
- C99 compiler(?). (compile time)
|
|
||||||
- gperf (compile time)
|
|
||||||
|
|
||||||
No runtime deps.
|
|
||||||
|
|
||||||
To regen the emoji list you also need:
|
|
||||||
- wget
|
|
||||||
- jq
|
|
||||||
- awk
|
|
||||||
|
|
||||||
# Credits
|
|
||||||
[milesj/emojibase](https://github.com/milesj/emojibase) for the JSON Emoji list. Many thanks
|
|
||||||
|
|
||||||
vifino for doing everything else.
|
|
||||||
|
|
||||||
# License
|
|
||||||
ISC.
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Generator for the hash list.
|
|
||||||
# This is unquestionably magic.
|
|
||||||
set -e
|
|
||||||
cat <<HEADER
|
|
||||||
%{
|
|
||||||
// THIS FILE IS GENERATED BY src/gen_list.sh.
|
|
||||||
// DO NOT TOUCH HERE.
|
|
||||||
%}
|
|
||||||
struct emoji_ref { char* name; unsigned int ref; };
|
|
||||||
%%
|
|
||||||
HEADER
|
|
||||||
|
|
||||||
cat gen/emoji.csv | sed 's/"//g' | \
|
|
||||||
awk -F',' '{ count++; for (i = 2; i <= NF; i++) printf "%s, %i\n", $i, count;}'
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Generator for the actual emoji list.
|
|
||||||
set -e
|
|
||||||
|
|
||||||
EMOJIS=$(wc -l < gen/emoji.csv)
|
|
||||||
|
|
||||||
cat <<HEADER
|
|
||||||
// THIS FILE IS GENERATED BY src/gen_list.sh.
|
|
||||||
// DO NOT TOUCH HERE.
|
|
||||||
|
|
||||||
#define NUM_EMOJI $EMOJIS
|
|
||||||
static const char* emoji_list[$EMOJIS] = {
|
|
||||||
HEADER
|
|
||||||
|
|
||||||
gen() {
|
|
||||||
printf "\t\"%s\",\n" $1
|
|
||||||
}
|
|
||||||
|
|
||||||
cat gen/emoji.csv | sed 's/,/ /g' | while read -r input; do
|
|
||||||
gen $(eval "echo $input")
|
|
||||||
done
|
|
||||||
|
|
||||||
cat <<FOOTER
|
|
||||||
};
|
|
||||||
FOOTER
|
|
||||||
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
// moj.
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include "moj.h"
|
|
||||||
|
|
||||||
#define MODE_NORMAL 0
|
|
||||||
#define MODE_LOOKUP 1
|
|
||||||
#define MODE_USELIBRARY 2
|
|
||||||
|
|
||||||
void usage(char* name, int fail) {
|
|
||||||
FILE* f = stdout;
|
|
||||||
if (fail) f = stderr;
|
|
||||||
fprintf(f, "Usage: %s [-s sep] [-e] <:emoji: text>\n"
|
|
||||||
" -e: lookup mode - interpret arguments as emoji names\n"
|
|
||||||
" -s <sep>: use custom seperator\n", name);
|
|
||||||
exit(fail);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
if (argc < 2) {
|
|
||||||
usage(argv[0], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse opts
|
|
||||||
int opt;
|
|
||||||
int mode = MODE_NORMAL;
|
|
||||||
char* sep = NULL;
|
|
||||||
while ((opt = getopt(argc, argv, "hels:")) != -1) {
|
|
||||||
switch (opt) {
|
|
||||||
case 'h':
|
|
||||||
usage(argv[0], 0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case '?':
|
|
||||||
usage(argv[0], 1);
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
mode = MODE_LOOKUP;
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
sep = optarg;
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
mode = MODE_USELIBRARY;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (optind >= argc)
|
|
||||||
usage(argv[0], 1);
|
|
||||||
|
|
||||||
int argi;
|
|
||||||
switch (mode) {
|
|
||||||
case MODE_LOOKUP:
|
|
||||||
for (argi = optind; argi < argc; argi++) {
|
|
||||||
char* found = moj_lookup(argv[argi], strlen(argv[argi]));
|
|
||||||
if (found) {
|
|
||||||
printf("%s%s", found, sep ? sep : "\n");
|
|
||||||
} else {
|
|
||||||
printf("Couldn't find %s. :(\n", argv[argi]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sep) printf("\n");
|
|
||||||
break;
|
|
||||||
case MODE_NORMAL:
|
|
||||||
for (argi = optind; argi < argc; argi++) {
|
|
||||||
char* s = argv[argi];
|
|
||||||
char c;
|
|
||||||
int ci;
|
|
||||||
int len = strlen(s);
|
|
||||||
int start = 0;
|
|
||||||
int attention = 0;
|
|
||||||
for (ci = 0; ci <= len; ci++) {
|
|
||||||
c = s[ci];
|
|
||||||
|
|
||||||
if (attention) { // in :
|
|
||||||
if (c == ' ') {
|
|
||||||
attention = 0;
|
|
||||||
s[ci] = 0;
|
|
||||||
fprintf(stdout, "%s ", s + start);
|
|
||||||
} else if (c == ':') {
|
|
||||||
// check if start to ci - start -1 is a valid emoji.
|
|
||||||
s[ci] = 0;
|
|
||||||
char* found = moj_lookup(s + start + 1, ci - start - 1);
|
|
||||||
if (found) {
|
|
||||||
fprintf(stdout, "%s", found);
|
|
||||||
} else {
|
|
||||||
// not an emoji? oops.
|
|
||||||
fprintf(stdout, "%s:", s + start);
|
|
||||||
start = ci;
|
|
||||||
// the next might be one, though..
|
|
||||||
}
|
|
||||||
attention = 0;
|
|
||||||
}
|
|
||||||
} else if (c == ':' && ci < len) {
|
|
||||||
attention = 1;
|
|
||||||
start = ci;
|
|
||||||
} else {
|
|
||||||
putc(c, stdout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (attention && start < len)
|
|
||||||
fprintf(stdout, "%s", s + start + 1);
|
|
||||||
|
|
||||||
if (argi < (argc - 1))
|
|
||||||
fputs(sep ? sep : " ", stdout);
|
|
||||||
}
|
|
||||||
fprintf(stdout, "\n");
|
|
||||||
break;
|
|
||||||
case MODE_USELIBRARY:
|
|
||||||
for (argi = optind; argi < argc; argi++) {
|
|
||||||
char* rep = moj_replace(argv[argi], strlen(argv[argi]));
|
|
||||||
printf("%s\n", rep);
|
|
||||||
free(rep);
|
|
||||||
}
|
|
||||||
break;;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
// moj: Small Emoji library
|
|
||||||
// Based on our generated things.
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "../gen/lookup.c"
|
|
||||||
#include "../gen/emoji_list.h"
|
|
||||||
|
|
||||||
const char* moj_lookup(const char* name, size_t len) {
|
|
||||||
const struct emoji_ref *found = emoji_lookup_ref(name, len);
|
|
||||||
if (found) {
|
|
||||||
return emoji_list[found->ref - 1];
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* moj_replace(const char* text, size_t len) {
|
|
||||||
if (!len)
|
|
||||||
return strdup(text);
|
|
||||||
|
|
||||||
char c;
|
|
||||||
// count size delta to alloc a string just the right size.
|
|
||||||
int size_delta = 0;
|
|
||||||
|
|
||||||
size_t ci;
|
|
||||||
size_t start = 0;
|
|
||||||
size_t strend;
|
|
||||||
int attention = 0;
|
|
||||||
for (ci = 0; ci <= len; ci++) {
|
|
||||||
c = text[ci];
|
|
||||||
|
|
||||||
if (attention) { // in :
|
|
||||||
if (c == ' ') {
|
|
||||||
attention = 0;
|
|
||||||
} else if (c == ':') {
|
|
||||||
// check if start to ci - start -1 is a valid emoji.
|
|
||||||
size_t elen = ci - start - 1;
|
|
||||||
const char* found = moj_lookup(text + start + 1, elen);
|
|
||||||
if (found) {
|
|
||||||
size_delta += elen - strlen(found);
|
|
||||||
} else {
|
|
||||||
start = ci;
|
|
||||||
}
|
|
||||||
attention = 0;
|
|
||||||
}
|
|
||||||
} else if (c == ':' && ci < len) {
|
|
||||||
attention = 1;
|
|
||||||
start = ci;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocate buffer the right size.
|
|
||||||
size_t realsz = len + size_delta;
|
|
||||||
char* buf = malloc(realsz + 1);
|
|
||||||
if (!buf)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
start = 0;
|
|
||||||
attention = 0;
|
|
||||||
size_t offset = 0;
|
|
||||||
for (ci = 0; ci < len; ci++) {
|
|
||||||
c = text[ci];
|
|
||||||
|
|
||||||
if (attention) { // in :
|
|
||||||
if (c == ' ') {
|
|
||||||
attention = 0;
|
|
||||||
memcpy(buf + offset, text + start, ci - start + 1);
|
|
||||||
offset += ci - start + 1;
|
|
||||||
} else if (c == ':') {
|
|
||||||
// check if start to ci - start -1 is a valid emoji.
|
|
||||||
const char* found = moj_lookup(text + start + 1, ci - start - 1);
|
|
||||||
if (found) {
|
|
||||||
offset += sprintf(buf + offset, "%s", found);
|
|
||||||
} else {
|
|
||||||
// not an emoji? oops.
|
|
||||||
memcpy(buf + offset, text + start, ci - start + 1);
|
|
||||||
offset += ci - start + 1;
|
|
||||||
start = ci;
|
|
||||||
// the next might be one, though..
|
|
||||||
}
|
|
||||||
attention = 0;
|
|
||||||
}
|
|
||||||
} else if (c == ':' && ci < len) {
|
|
||||||
attention = 1;
|
|
||||||
start = ci;
|
|
||||||
} else {
|
|
||||||
buf[offset] = c;
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (attention) {
|
|
||||||
memcpy(buf + offset, text + start, ci - start);
|
|
||||||
offset += ci - start;
|
|
||||||
}
|
|
||||||
buf[realsz] = 0;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// moj: a small emoji library and cli.
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
char* moj_lookup(const char* name, size_t len);
|
|
||||||
char* moj_replace(const char* name, size_t len);
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#!/usr/bin/awk -f
|
|
||||||
# This file takes in our CSV, emits simpler stuff.
|
|
||||||
# It also fixes up some things.
|
|
||||||
|
|
||||||
# Bunch of convenience functions.
|
|
||||||
function didsee(name) {
|
|
||||||
if (name in seen)
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasnew() {
|
|
||||||
for (i = 2; i <= NF; i++)
|
|
||||||
if (!didsee($i))
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function newname(name) {
|
|
||||||
if (didsee(name))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
seen[name] = 1;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function char(str, idx) {
|
|
||||||
return substr(str, idx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function prefixed(name, prefixes) {
|
|
||||||
for (prefix in prefixes)
|
|
||||||
if (index(name, prefixes[prefix]) == 1)
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function inarray(name, array) {
|
|
||||||
for (i in array)
|
|
||||||
if (array[i] == name)
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function noing(name) {
|
|
||||||
# yep thats the name.
|
|
||||||
len = length(name);
|
|
||||||
if (len > 5 && substr(name, len - 2, 3) == "ing" && !prefixed(name, persons) && !inarray(name, exemptions)) {
|
|
||||||
if (char(name, len - 4) == char(name, len - 3)) {
|
|
||||||
# double letter combo, gg, nn, etc..
|
|
||||||
short = substr(name, 0, len - 4);
|
|
||||||
} else {
|
|
||||||
short = substr(name, 0, len - 3);
|
|
||||||
}
|
|
||||||
if (newname(short))
|
|
||||||
printf ",%s", short;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BEGIN {
|
|
||||||
FS="\",\""
|
|
||||||
|
|
||||||
# I hate this.
|
|
||||||
persons[1] = "person_";
|
|
||||||
persons[2] = "women_";
|
|
||||||
persons[3] = "woman_"
|
|
||||||
persons[4] = "men_";
|
|
||||||
persons[5] = "man_";
|
|
||||||
|
|
||||||
# I hate this even more.
|
|
||||||
exemptions[1] = "bowing";
|
|
||||||
exemptions[2] = "golfing";
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
if (hasnew())
|
|
||||||
printf "%s", $1;
|
|
||||||
|
|
||||||
for (i = 2; i <= NF; i++) {
|
|
||||||
if (newname($i)) {
|
|
||||||
printf ",%s", $i;
|
|
||||||
noing($i);
|
|
||||||
if (index($i, "person_") == 1) {
|
|
||||||
replaced = substr($i, 8);
|
|
||||||
if (newname(replaced)) {
|
|
||||||
printf ",%s", replaced;
|
|
||||||
}
|
|
||||||
noing(replaced);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf "\n";
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// moj: CProof plugin
|
|
||||||
// Intercepts text and replaces :emoji:
|
|
||||||
|
|
||||||
#include <profapi.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "moj.h"
|
|
||||||
|
|
||||||
char* prof_pre_chat_message_send(const char* const barejid, const char* message) {
|
|
||||||
return moj_replace(message, strlen(message)); // yep that is it.
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
"""
|
|
||||||
Converts smileys in a text to their Unicode character representations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import prof
|
|
||||||
|
|
||||||
EMOTICON_UNICODE_DICT = {':)': '😃', ':(': '☹️', ':D': '😃', ':O': '😲', ';)': '😉', ':-)': '😊', ':-D': '😁', ':-(': '😞',
|
|
||||||
';-)': '😜', '<3': '❤️', ':P': '😛', ':|': '😐', ':/': '😕', ':*': '😘', ':]': '😃', ':[': '😢',
|
|
||||||
':-|': '😐', ':\\': '😕', ':3': '😺', 'O:)': '😇', '>:(': '😠', ':poop:': '💩', ':fire:': '🔥',
|
|
||||||
':heart:': '❤️', ':thumbsup:': '👍', ':star:': '⭐', ':beer:': '🍺', ':pizza:': '🍕', ':sunglasses:': '😎',
|
|
||||||
':ok_hand:': '👌', ':heart_eyes:': '😍'}
|
|
||||||
|
|
||||||
|
|
||||||
def replace_emoticons(text, emoticon_dict):
|
|
||||||
for emoticon, unicode_char in emoticon_dict.items():
|
|
||||||
text = text.replace(emoticon, unicode_char)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def convert_emoticons(input_str):
|
|
||||||
return replace_emoticons(input_str, EMOTICON_UNICODE_DICT)
|
|
||||||
|
|
||||||
|
|
||||||
def prof_pre_chat_message_display(barejid, resource, message):
|
|
||||||
return convert_emoticons(message)
|
|
||||||
|
|
||||||
|
|
||||||
def prof_pre_room_message_display(barejid, nick, message):
|
|
||||||
return convert_emoticons(message)
|
|
||||||
|
|
||||||
|
|
||||||
def prof_pre_priv_message_display(barejid, nick, message):
|
|
||||||
return convert_emoticons(message)
|
|
||||||
167
src/history.py
167
src/history.py
@@ -1,167 +0,0 @@
|
|||||||
"""
|
|
||||||
CProof plugin to read full chat history with custom editor using /hh command.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import prof
|
|
||||||
import re
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
_editor = ""
|
|
||||||
_current_user = ""
|
|
||||||
_logs_dir = ""
|
|
||||||
_db_file = ""
|
|
||||||
_win = "History"
|
|
||||||
_plugin_name = __file__.split('/')[-1] if __file__ else "history.py"
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_win_input():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _create_win(win):
|
|
||||||
if not prof.win_exists(win):
|
|
||||||
prof.win_create(win, _handle_win_input)
|
|
||||||
|
|
||||||
|
|
||||||
def _show_error(error):
|
|
||||||
error_msg = f"[History Reader] Error happened: {error}"
|
|
||||||
prof.cons_alert()
|
|
||||||
prof.cons_show(error_msg)
|
|
||||||
prof.log_error(error_msg)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def _str_sanitize(text):
|
|
||||||
return re.sub('[^a-zA-Z0-9\._]', '', text.replace("@", "_at_"))
|
|
||||||
|
|
||||||
|
|
||||||
def _cmd_editor(*args):
|
|
||||||
global _editor
|
|
||||||
_db_connection = sqlite3.connect(f"{_db_file}")
|
|
||||||
_db_connection.text_factory = bytes
|
|
||||||
_cur = _db_connection.cursor()
|
|
||||||
msg_buffer = []
|
|
||||||
if args and args[0] == "set":
|
|
||||||
if len(args) < 2:
|
|
||||||
prof.cons_show("Please, use this format: \"/hh set <editor>\"")
|
|
||||||
return
|
|
||||||
_editor = args[1]
|
|
||||||
prof.settings_string_set("history", "editor", _editor)
|
|
||||||
prof.cons_show(
|
|
||||||
f"New editor set up successfully. New editor: {_editor}")
|
|
||||||
return
|
|
||||||
if args and args[0] == "--no-repeat":
|
|
||||||
prof.cons_show("[History Reader] Trying operation again...")
|
|
||||||
if not _current_user:
|
|
||||||
if args and args[0] == "--no-repeat":
|
|
||||||
_show_error("Can't fetch current user.")
|
|
||||||
return
|
|
||||||
prof.send_line(f"/plugins reload {_plugin_name}")
|
|
||||||
prof.send_line("/hh --no-repeat")
|
|
||||||
return
|
|
||||||
if not _editor:
|
|
||||||
prof.cons_show(
|
|
||||||
"Please, set up editor using /hh set. E.g. /hh set /usr/bin/vim")
|
|
||||||
return
|
|
||||||
if not (recipient := prof.get_current_recipient()):
|
|
||||||
prof.cons_show("Please, use this command in a chat window")
|
|
||||||
return
|
|
||||||
|
|
||||||
tmpfpath = _logs_dir / f"{_str_sanitize(recipient)}.log"
|
|
||||||
res = _cur.execute("""SELECT timestamp, from_jid, message FROM `chatlogs`
|
|
||||||
WHERE ((`from_jid` = :jid AND `to_jid` = :myjid)
|
|
||||||
OR (`from_jid` = :myjid AND `to_jid` = :jid))
|
|
||||||
ORDER BY id""", {"jid": recipient, "myjid": _current_user})
|
|
||||||
|
|
||||||
for bmsg in res.fetchall():
|
|
||||||
try:
|
|
||||||
msg = [x.decode("UTF-8", errors="backslashreplace") for x in bmsg]
|
|
||||||
except Exception as e:
|
|
||||||
msg = [x.decode("UTF-8", errors="replace") for x in bmsg]
|
|
||||||
sender = "me" if msg[1] == _current_user else msg[1]
|
|
||||||
msg_buffer.append(f"{msg[0]} - {sender}: {msg[2]}")
|
|
||||||
|
|
||||||
tmpfpath.write_text('\n'.join(msg_buffer),
|
|
||||||
encoding="UTF-8", errors="replace")
|
|
||||||
|
|
||||||
pid = os.fork()
|
|
||||||
if pid == 0:
|
|
||||||
os.execlp(_editor, _editor, str(tmpfpath))
|
|
||||||
else:
|
|
||||||
if pid == -1:
|
|
||||||
return
|
|
||||||
os.waitpid(pid, 0)
|
|
||||||
prof.send_line("/statusbar show name")
|
|
||||||
try:
|
|
||||||
tmpfpath.unlink()
|
|
||||||
except Exception as e:
|
|
||||||
prof.log_error(
|
|
||||||
f"[History Reader] Error on file deletion (path: {tmpfpath}): {e}")
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def prof_on_connect(account_name, fulljid):
|
|
||||||
prof.log_debug(
|
|
||||||
f"[History Reader] prof_on_connect called with this JID: {fulljid}")
|
|
||||||
_init(fulljid)
|
|
||||||
|
|
||||||
|
|
||||||
def prof_on_disconnect(account_name, fulljid):
|
|
||||||
prof.log_debug(
|
|
||||||
f"[History Reader] prof_on_disconnect called with this JID: {fulljid}")
|
|
||||||
global _current_user
|
|
||||||
_current_user = ""
|
|
||||||
|
|
||||||
|
|
||||||
def prof_init(version, status, account_name, fulljid):
|
|
||||||
prof.log_debug(
|
|
||||||
f"[History Reader] prof_init called with this JID: {fulljid}")
|
|
||||||
_init(fulljid)
|
|
||||||
synopsis = ["/hh"]
|
|
||||||
description = "Open an editor and check user's history."
|
|
||||||
args = [
|
|
||||||
["set", "Set custom editor."]
|
|
||||||
]
|
|
||||||
examples = [
|
|
||||||
"/hh",
|
|
||||||
"/hh set /usr/bin/vim",
|
|
||||||
"/hh set gtk-open"
|
|
||||||
]
|
|
||||||
prof.register_command("/hh", 0, 2, synopsis,
|
|
||||||
description, args, examples, _cmd_editor)
|
|
||||||
prof.completer_add("/hh", ["set"])
|
|
||||||
prof.filepath_completer_add("/hh set")
|
|
||||||
|
|
||||||
|
|
||||||
def _init(fulljid):
|
|
||||||
global _current_user, _editor, _logs_dir, _db_file
|
|
||||||
prof.log_debug(
|
|
||||||
f"[History Reader] Initialization started with this JID: {fulljid}")
|
|
||||||
_current_user = fulljid and fulljid.split('/')[0]
|
|
||||||
if not _current_user:
|
|
||||||
prof.log_debug(
|
|
||||||
"[History Reader] Can't fetch current user, aborting initialization...")
|
|
||||||
return
|
|
||||||
_cproof_home = Path(os.getenv('XDG_DATA_HOME')
|
|
||||||
or "~/.local/share").expanduser() / "profanity"
|
|
||||||
_logs_dir = _cproof_home / "chatlogs" / _str_sanitize(_current_user)
|
|
||||||
db_dir = _cproof_home / "database" / _str_sanitize(_current_user)
|
|
||||||
if not _logs_dir.is_dir():
|
|
||||||
_show_error(f"Can't open logs directory. Path: {_logs_dir}")
|
|
||||||
return
|
|
||||||
if not db_dir.is_dir():
|
|
||||||
_show_error(f"Can't open DB directory. Path: {db_dir}")
|
|
||||||
return
|
|
||||||
_db_file = db_dir / "chatlog.db"
|
|
||||||
if not _db_file.is_file():
|
|
||||||
_show_error(f"Log db is not present. Path: {_db_file}")
|
|
||||||
return
|
|
||||||
_editor = prof.settings_string_get("history", "editor", "")
|
|
||||||
if not _editor:
|
|
||||||
prof.cons_show("Please, set up editor using /hh set.")
|
|
||||||
else:
|
|
||||||
prof.cons_show(
|
|
||||||
f"[History Reader] Successfully started. Editor: {_editor}")
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
# vim: set ts=2 sw=2 sts=2 et:
|
|
||||||
"""
|
|
||||||
Removes "unwanted" HTML tags from incoming messages and unescapes html.
|
|
||||||
|
|
||||||
Author: Diego Blanco <diego.blanco@treitos.com>
|
|
||||||
|
|
||||||
Some XMPP clients like Adium or Pidgin send HTML tags like <font>, <b>, <u>, etc
|
|
||||||
to format text, specially when using OTR. This plugin removes them and also
|
|
||||||
unescapes HTML like <, >, 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)
|
|
||||||
137
src/notifycmd.py
137
src/notifycmd.py
@@ -1,137 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Executes a command when a message is received
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Oliver Schmidhauser
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import prof
|
|
||||||
from subprocess import Popen
|
|
||||||
import time
|
|
||||||
from sys import platform
|
|
||||||
|
|
||||||
def secure(string):
|
|
||||||
string=string.replace('\\','\\\\')
|
|
||||||
string=string.replace("\"","\\\"")
|
|
||||||
string=string.replace("$","\$")
|
|
||||||
string=string.replace("`","\`")
|
|
||||||
return string
|
|
||||||
|
|
||||||
def notifycmd(sender,message):
|
|
||||||
command = prof.settings_string_get("notifycmd", "command", "")
|
|
||||||
# replace markers with complex strings first to avoid "%%mittens"=>"(%%)mittens"=>"(%m)ittens"=>"<message>ittens" but rather do "(%%)mittens"=>"%mittens"
|
|
||||||
command = command.replace("%%","{percentreplace}")
|
|
||||||
command = command.replace("%s","${senderreplace}")
|
|
||||||
command = command.replace("%m","${messagereplace}")
|
|
||||||
|
|
||||||
|
|
||||||
command = command.replace("{percentreplace}","%")
|
|
||||||
command = "set -f;senderreplace=\"{}\";messagereplace=\"{}\";{}".format(secure(sender),secure(message),command)
|
|
||||||
p = Popen(['sh', '-c', command])
|
|
||||||
|
|
||||||
def prof_post_chat_message_display(barejid, resource, message):
|
|
||||||
enabled = prof.settings_string_get("notifycmd", "enabled", "on")
|
|
||||||
current_recipient = prof.get_current_recipient()
|
|
||||||
if enabled == "on" or (enabled == "active" and current_recipient == barejid):
|
|
||||||
notifycmd(barejid, message)
|
|
||||||
return message
|
|
||||||
|
|
||||||
|
|
||||||
def prof_post_room_message_display(barejid, nick, message):
|
|
||||||
enabled = prof.settings_string_get("notifycmd", "enabled", "on")
|
|
||||||
rooms = prof.settings_string_get("notifycmd", "rooms", "mention")
|
|
||||||
current_muc = prof.get_current_muc()
|
|
||||||
mynick = prof.get_room_nick(barejid)
|
|
||||||
# Don't notify for ones own messages
|
|
||||||
if nick != mynick:
|
|
||||||
if rooms == "on":
|
|
||||||
if enabled == "on":
|
|
||||||
notifycmd(nick + " in " + barejid, message)
|
|
||||||
elif enabled == "active" and current_muc == barejid:
|
|
||||||
notifycmd(nick, message)
|
|
||||||
elif rooms == "mention":
|
|
||||||
if mynick in message:
|
|
||||||
if enabled == "on":
|
|
||||||
notifycmd(nick, message)
|
|
||||||
elif enabled == "active" and current_muc == barejid:
|
|
||||||
notifycmd(nick, message)
|
|
||||||
return message
|
|
||||||
|
|
||||||
|
|
||||||
def prof_post_priv_message_display(barejid, nick, message):
|
|
||||||
if enabled:
|
|
||||||
notifycmd(nick,message)
|
|
||||||
|
|
||||||
return message
|
|
||||||
|
|
||||||
|
|
||||||
def _cmd_notifycmd(arg1=None, arg2=None):
|
|
||||||
if arg1 == "on":
|
|
||||||
prof.settings_string_set("notifycmd", "enabled", "on")
|
|
||||||
prof.cons_show("Notifycmd plugin enabled")
|
|
||||||
elif arg1 == "off":
|
|
||||||
prof.settings_string_set("notifycmd", "enabled", "off")
|
|
||||||
prof.cons_show("Notifycmd plugin disabled")
|
|
||||||
elif arg1 == "active":
|
|
||||||
prof.settings_string_set("notifycmd", "enabled", "active")
|
|
||||||
prof.cons_show("Notifycmd plugin enabled for active window only")
|
|
||||||
elif arg1 == "command":
|
|
||||||
if arg2 == None:
|
|
||||||
prof.cons_bad_cmd_usage("/notifycmd")
|
|
||||||
else:
|
|
||||||
prof.settings_string_set("notifycmd", "command", arg2)
|
|
||||||
prof.cons_show("notifycmd plugin command set to: " + arg2)
|
|
||||||
elif arg1 == "rooms":
|
|
||||||
if arg2 == None:
|
|
||||||
prof.cons_bad_cmd_usage("/notifycmd")
|
|
||||||
else:
|
|
||||||
prof.settings_string_set("notifycmd", "rooms", arg2)
|
|
||||||
prof.cons_show("notifycmd plugin notifications for rooms set to: " + arg2)
|
|
||||||
else:
|
|
||||||
enabled = prof.settings_string_get("notifycmd", "enabled", "on")
|
|
||||||
rooms = prof.settings_string_get("notifycmd", "rooms", "mention")
|
|
||||||
command = prof.settings_string_get("notifycmd", "command", "")
|
|
||||||
prof.cons_show("Notifycmd plugin settings:")
|
|
||||||
prof.cons_show("enabled : " + enabled)
|
|
||||||
prof.cons_show("command : " + command)
|
|
||||||
prof.cons_show("rooms : " + rooms)
|
|
||||||
|
|
||||||
def prof_init(version, status, account_name, fulljid):
|
|
||||||
synopsis = [
|
|
||||||
"/notifycmd on|off|active",
|
|
||||||
"/notifycmd command <command>",
|
|
||||||
"/notifycmd rooms on|off|mention"
|
|
||||||
]
|
|
||||||
description = "Executes a command when a message is received"
|
|
||||||
args = [
|
|
||||||
[ "on|off", "Enable/disable notifycmd for all windows" ],
|
|
||||||
[ "active", "Enable notifycmd for active window only" ],
|
|
||||||
[ "command <args>", "Set command to execute. Replaces %s with sender, %m with message and %% with literal %." ],
|
|
||||||
[ "rooms <args>", "Setting for multi-user rooms. Set to 'on', 'off' or 'mention'. If set to mention it will only run it your nick was mentioned." ]
|
|
||||||
]
|
|
||||||
examples = []
|
|
||||||
|
|
||||||
prof.register_command("/notifycmd", 0, 2, synopsis, description, args, examples, _cmd_notifycmd)
|
|
||||||
prof.completer_add("/notifycmd", [ "on", "off","active","command","rooms" ])
|
|
||||||
prof.completer_add("/notifycmd rooms", [ "on", "off", "mention" ])
|
|
||||||
144
src/sanitize.py
144
src/sanitize.py
@@ -1,144 +0,0 @@
|
|||||||
"""
|
|
||||||
Sanitize incoming messages from Adium clients.
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Nd2(SO4)3
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from io import StringIO
|
|
||||||
from lxml import etree
|
|
||||||
from lxml.etree import _ElementUnicodeResult, _ElementStringResult
|
|
||||||
import prof
|
|
||||||
|
|
||||||
# ROADMAP
|
|
||||||
#
|
|
||||||
# Version 2:
|
|
||||||
# "/sanitise on|off": global auto detect setting
|
|
||||||
# "/sanitise <jid> on|off|<mode>": setting per JID: can be autodetect, off,
|
|
||||||
# or a special "mode" (if there are any).
|
|
||||||
|
|
||||||
iq_count = 1
|
|
||||||
|
|
||||||
# Stores client information. JID/str~>Client/str.
|
|
||||||
clients = {}
|
|
||||||
|
|
||||||
|
|
||||||
def prof_on_presence_stanza_receive(stanza):
|
|
||||||
"""Send a software version request when a contact comes online."""
|
|
||||||
full_jid = etree.fromstring(stanza).get("from")
|
|
||||||
if full_jid:
|
|
||||||
send_version_request(full_jid)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def send_version_request(full_jid):
|
|
||||||
global iq_count
|
|
||||||
|
|
||||||
iq = etree.Element(
|
|
||||||
"iq", {"type": "get", "to": full_jid, "id": "sanitiser_" + str(iq_count)}
|
|
||||||
)
|
|
||||||
etree.SubElement(iq, "query", {"xmlns": "jabber:iq:version"})
|
|
||||||
|
|
||||||
iq_count = iq_count + 1
|
|
||||||
prof.send_stanza(etree.tostring(iq))
|
|
||||||
|
|
||||||
|
|
||||||
def prof_on_iq_stanza_receive(stanza):
|
|
||||||
"""Listen for software version responses and save results in the
|
|
||||||
'clients' store.
|
|
||||||
"""
|
|
||||||
iq = etree.fromstring(stanza)
|
|
||||||
id = iq.get("id")
|
|
||||||
if not id:
|
|
||||||
return True
|
|
||||||
if not id.startswith("sanitiser_"):
|
|
||||||
return True
|
|
||||||
full_jid = iq.get("from")
|
|
||||||
if not full_jid:
|
|
||||||
return False
|
|
||||||
|
|
||||||
query = iq.find("{jabber:iq:version}query")
|
|
||||||
if query is None:
|
|
||||||
return False
|
|
||||||
client = query.find("{jabber:iq:version}name")
|
|
||||||
if client is not None:
|
|
||||||
clients[full_jid] = client.text
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def prof_pre_chat_message_display(jid, resource, message):
|
|
||||||
"""Sanitises chat room messages if necessary."""
|
|
||||||
full_jid = "%s/%s" % (jid, resource)
|
|
||||||
if full_jid not in clients:
|
|
||||||
return message
|
|
||||||
return get_sanitiser(clients[full_jid])(message)
|
|
||||||
|
|
||||||
|
|
||||||
# Sanitiser functions:
|
|
||||||
|
|
||||||
|
|
||||||
def get_sanitiser(client):
|
|
||||||
"""Returns appropriate sanitiser function for the given
|
|
||||||
Jabber client (string).
|
|
||||||
"""
|
|
||||||
if client.startswith("Adium"):
|
|
||||||
return sanitise
|
|
||||||
else:
|
|
||||||
return lambda msg: msg
|
|
||||||
|
|
||||||
|
|
||||||
def sanitise(message):
|
|
||||||
"""Cleans message from HTML. Raises exceptions on problems."""
|
|
||||||
parser = etree.HTMLParser()
|
|
||||||
tree = etree.parse(StringIO(unicode(message)), parser)
|
|
||||||
elems = tree.xpath("(//br|//a|//*[not(self::a)]/text())")
|
|
||||||
return "".join(map(substitude, elems))
|
|
||||||
|
|
||||||
|
|
||||||
def try_sanitise(message):
|
|
||||||
"""Same as 'sanitise' but returns unmodified message if it encounters
|
|
||||||
a problem, instead of throwing an exception.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return sanitise(message)
|
|
||||||
except:
|
|
||||||
return message
|
|
||||||
|
|
||||||
|
|
||||||
def substitude(elem):
|
|
||||||
"""Substitudes an lxml element with a string representation."""
|
|
||||||
if type(elem) in (_ElementUnicodeResult, _ElementStringResult):
|
|
||||||
return str(elem)
|
|
||||||
else:
|
|
||||||
if elem.tag == "br":
|
|
||||||
return "\n"
|
|
||||||
elif elem.tag == "a":
|
|
||||||
text = elem.text or ""
|
|
||||||
href = elem.get("href", "")
|
|
||||||
if not text and not href:
|
|
||||||
return ""
|
|
||||||
elif text == href:
|
|
||||||
return href
|
|
||||||
else:
|
|
||||||
return "%s (%s)" % (text, href)
|
|
||||||
else:
|
|
||||||
raise Exception("unhandled tag <%s>" % elem.tag)
|
|
||||||
128
src/shortcuts.py
128
src/shortcuts.py
@@ -1,128 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Manage Message Shortcuts
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2016 René Calles
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import prof
|
|
||||||
|
|
||||||
ENABLED = True
|
|
||||||
SHORTCUTS_DICTIONARY = {u'shrug': u'¯\_(ツ)_/¯',
|
|
||||||
u'yay': u'\o/'}
|
|
||||||
|
|
||||||
plugin_win = "Shortcuts"
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_win_input(win, line):
|
|
||||||
prof.win_show(win, line)
|
|
||||||
|
|
||||||
|
|
||||||
def create_win():
|
|
||||||
if prof.win_exists(plugin_win) == False:
|
|
||||||
prof.win_create(plugin_win, _handle_win_input)
|
|
||||||
|
|
||||||
|
|
||||||
def _substitute(message):
|
|
||||||
if ENABLED:
|
|
||||||
shortcuts = re.findall(r'(?:^|\s)(:\w+:)(?=\s|$)', message)
|
|
||||||
for s in shortcuts:
|
|
||||||
shortcut_text = SHORTCUTS_DICTIONARY.get(s.strip()[1:-1])
|
|
||||||
if shortcut_text:
|
|
||||||
message = message.replace(s.strip(), shortcut_text)
|
|
||||||
|
|
||||||
return message
|
|
||||||
|
|
||||||
|
|
||||||
def save(key, value):
|
|
||||||
global SHORTCUTS_DICTIONARY
|
|
||||||
SHORTCUTS_DICTIONARY[key] = value
|
|
||||||
|
|
||||||
|
|
||||||
def _list_shortcuts():
|
|
||||||
global SHORTCUTS_DICTIONARY
|
|
||||||
# ordered_dict = sorted(DICTIONARY.items(), key=lambda t: t[0])
|
|
||||||
prof.cons_show(u'Shortcuts: ')
|
|
||||||
for key, value in SHORTCUTS_DICTIONARY.iteritems():
|
|
||||||
prof.cons_show(u':{0}: => {1}'.format(key, value))
|
|
||||||
|
|
||||||
|
|
||||||
def _cmd_shortcuts(arg1=None, arg2=None, arg3=None):
|
|
||||||
global ENABLED
|
|
||||||
|
|
||||||
if arg1 == None:
|
|
||||||
prof.cons_show("Shortcuts Plugin is {}.".format("ON" if ENABLED else "OFF"))
|
|
||||||
elif arg1 == "on":
|
|
||||||
ENABLED = True
|
|
||||||
elif arg1 == "off":
|
|
||||||
ENABLED = False
|
|
||||||
elif arg1 == "list":
|
|
||||||
_list_shortcuts()
|
|
||||||
elif arg1 == "set":
|
|
||||||
if arg2 == None:
|
|
||||||
prof.cons_bad_cmd_usage("/shortcuts")
|
|
||||||
else:
|
|
||||||
save(arg2, arg3)
|
|
||||||
else:
|
|
||||||
prof.cons_bad_cmd_usage("/shortcuts")
|
|
||||||
|
|
||||||
|
|
||||||
def prof_pre_chat_message_send(jid, message):
|
|
||||||
return _substitute(message)
|
|
||||||
|
|
||||||
|
|
||||||
def prof_pre_room_message_send(room, message):
|
|
||||||
return _substitute(message)
|
|
||||||
|
|
||||||
|
|
||||||
def prof_pre_priv_message_send(room, nick, message):
|
|
||||||
return _substitute(message)
|
|
||||||
|
|
||||||
|
|
||||||
def prof_init(version, status):
|
|
||||||
synopsis = [
|
|
||||||
"/shortcuts",
|
|
||||||
"/shortcuts on|off",
|
|
||||||
"/shortcuts set <name> <text>"
|
|
||||||
]
|
|
||||||
description = "Manage shortcuts for emoticons or other stuff"
|
|
||||||
args = [
|
|
||||||
[ "on|off", "Enable/disable shortcuts" ],
|
|
||||||
[ "set <name> <text>", "Save shortcut" ],
|
|
||||||
[ "list", "List Shortcuts Dictionary"]
|
|
||||||
]
|
|
||||||
examples = [
|
|
||||||
"/shortcuts",
|
|
||||||
"/shortcuts on",
|
|
||||||
"/shortcuts set lol \"Laughing out loud\""
|
|
||||||
]
|
|
||||||
|
|
||||||
prof.register_command("/shortcuts", 0, 3, synopsis, description, args, examples, _cmd_shortcuts)
|
|
||||||
prof.register_ac("/shortcuts", [ "on", "off", "set" ])
|
|
||||||
|
|
||||||
shortcut_keys = [":{}:".format(k) for k in SHORTCUTS_DICTIONARY.keys()]
|
|
||||||
# prof.cons_show(", ".join(shortcut_keys))
|
|
||||||
prof.register_ac(" ", shortcut_keys)
|
|
||||||
313
src/sounds.py
313
src/sounds.py
@@ -1,313 +0,0 @@
|
|||||||
"""
|
|
||||||
Play sounds on receiving messages.
|
|
||||||
|
|
||||||
Default settings require mpg123 to be installed on the host system:
|
|
||||||
sudo apt-get install mpg123
|
|
||||||
brew install mpg123
|
|
||||||
"""
|
|
||||||
|
|
||||||
from functools import lru_cache
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
import prof
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
_last_played = {}
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=16)
|
|
||||||
def get_expanded_path(path_str: str) -> Tuple[Path, str]:
|
|
||||||
path = Path(path_str).expanduser()
|
|
||||||
return path, str(path)
|
|
||||||
|
|
||||||
|
|
||||||
def _play_sound_file(soundfile: str):
|
|
||||||
soundfile_path, soundfile_str = get_expanded_path(soundfile)
|
|
||||||
|
|
||||||
if not soundfile_path.exists():
|
|
||||||
prof.cons_show(f"Sound file not found: {soundfile_str}")
|
|
||||||
return
|
|
||||||
|
|
||||||
now = time.monotonic()
|
|
||||||
if _last_played.get(soundfile_str, 0) + 0.5 > now:
|
|
||||||
return # skip if played too recently
|
|
||||||
_last_played[soundfile_str] = now
|
|
||||||
|
|
||||||
audioplayer = prof.settings_string_get("sounds", "audioplayer", "mpg123 -q")
|
|
||||||
subprocess.Popen([*audioplayer.split(), soundfile_str], start_new_session=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _play_sound(kind: str = "chat"):
|
|
||||||
soundfile = prof.settings_string_get("sounds", kind, None)
|
|
||||||
if soundfile:
|
|
||||||
_play_sound_file(soundfile)
|
|
||||||
|
|
||||||
|
|
||||||
def _cmd_sounds(arg1=None, arg2=None, arg3=None):
|
|
||||||
if not arg1:
|
|
||||||
prof.cons_show("")
|
|
||||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
|
||||||
chatsound = prof.settings_string_get("sounds", "chat", None)
|
|
||||||
roomsound = prof.settings_string_get("sounds", "room", None)
|
|
||||||
privatesound = prof.settings_string_get("sounds", "private", None)
|
|
||||||
roomlist = prof.settings_string_list_get("sounds", "rooms")
|
|
||||||
audioplayer = prof.settings_string_get("sounds", "audioplayer", "mpg123 -q")
|
|
||||||
|
|
||||||
if chatsound or roomsound or privatesound:
|
|
||||||
if enabled:
|
|
||||||
prof.cons_show("Sounds: ON")
|
|
||||||
else:
|
|
||||||
prof.cons_show("Sounds: OFF")
|
|
||||||
|
|
||||||
prof.cons_show("Audio Player: " + audioplayer)
|
|
||||||
if chatsound:
|
|
||||||
prof.cons_show(" Chat : " + chatsound)
|
|
||||||
if roomsound:
|
|
||||||
prof.cons_show(" Room : " + roomsound)
|
|
||||||
if roomlist and len(roomlist) > 0:
|
|
||||||
for room in roomlist:
|
|
||||||
prof.cons_show(" " + room)
|
|
||||||
else:
|
|
||||||
prof.cons_show(" All rooms")
|
|
||||||
if privatesound:
|
|
||||||
prof.cons_show(" Private : " + privatesound)
|
|
||||||
|
|
||||||
else:
|
|
||||||
prof.cons_show("No sounds set.")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
if arg1 == "on":
|
|
||||||
prof.settings_boolean_set("sounds", "enabled", True)
|
|
||||||
prof.cons_show("Sounds enabled")
|
|
||||||
return
|
|
||||||
|
|
||||||
if arg1 == "off":
|
|
||||||
prof.settings_boolean_set("sounds", "enabled", False)
|
|
||||||
prof.cons_show("Sounds disabled")
|
|
||||||
return
|
|
||||||
|
|
||||||
if arg1 == "test":
|
|
||||||
if arg2 not in (
|
|
||||||
"chat",
|
|
||||||
"private",
|
|
||||||
"room",
|
|
||||||
):
|
|
||||||
prof.cons_bad_cmd_usage("/sounds")
|
|
||||||
return
|
|
||||||
|
|
||||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
|
||||||
if not enabled:
|
|
||||||
prof.cons_show("Sounds disabled")
|
|
||||||
return
|
|
||||||
|
|
||||||
soundfile = prof.settings_string_get("sounds", arg2, None)
|
|
||||||
if not soundfile:
|
|
||||||
prof.cons_show("No sound file for " + arg2)
|
|
||||||
return
|
|
||||||
|
|
||||||
_play_sound(kind=arg2)
|
|
||||||
return
|
|
||||||
|
|
||||||
if arg1 == "set":
|
|
||||||
if arg2 == "chat":
|
|
||||||
prof.settings_string_set("sounds", "chat", arg3)
|
|
||||||
prof.cons_show("Set chat sound: " + arg3)
|
|
||||||
elif arg2 == "room":
|
|
||||||
prof.settings_string_set("sounds", "room", arg3)
|
|
||||||
prof.cons_show("Set room sound: " + arg3)
|
|
||||||
elif arg2 == "private":
|
|
||||||
prof.settings_string_set("sounds", "private", arg3)
|
|
||||||
prof.cons_show("Set private sound: " + arg3)
|
|
||||||
elif arg2 == "audioplayer":
|
|
||||||
prof.settings_string_set("sounds", "audioplayer", arg3)
|
|
||||||
prof.cons_show("Set audio player: " + arg3)
|
|
||||||
else:
|
|
||||||
prof.cons_bad_cmd_usage("/sounds")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
if arg1 == "reset":
|
|
||||||
if arg2 == "audioplayer":
|
|
||||||
prof.settings_string_set("sounds", "audioplayer", "mpg123 -q")
|
|
||||||
prof.cons_show("Reset audio player to default.")
|
|
||||||
else:
|
|
||||||
prof.cons_bad_cmd_usage("/sounds")
|
|
||||||
|
|
||||||
if arg1 == "clear":
|
|
||||||
if arg2 == "chat":
|
|
||||||
prof.settings_string_set("sounds", "chat", "")
|
|
||||||
prof.cons_show("Removed chat sound.")
|
|
||||||
elif arg2 == "room":
|
|
||||||
prof.settings_string_set("sounds", "room", "")
|
|
||||||
prof.cons_show("Removed room sound.")
|
|
||||||
elif arg2 == "private":
|
|
||||||
prof.settings_string_set("sounds", "private", "")
|
|
||||||
prof.cons_show("Removed private sound.")
|
|
||||||
else:
|
|
||||||
prof.cons_bad_cmd_usage("/sounds")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
if arg1 == "rooms":
|
|
||||||
if arg2 == "add":
|
|
||||||
if not arg3:
|
|
||||||
prof.cons_bad_cmd_usage("/sounds")
|
|
||||||
else:
|
|
||||||
prof.settings_string_list_add("sounds", "rooms", arg3)
|
|
||||||
prof.cons_show("Sounds enabled for room: " + arg3)
|
|
||||||
elif arg2 == "remove":
|
|
||||||
if not arg3:
|
|
||||||
prof.cons_bad_cmd_usage("/sounds")
|
|
||||||
else:
|
|
||||||
prof.settings_string_list_remove("sounds", "rooms", arg3)
|
|
||||||
roomlist = prof.settings_string_list_get("sounds", "rooms")
|
|
||||||
if roomlist and len(roomlist) > 0:
|
|
||||||
prof.cons_show("Sounds disabled for room: " + arg3)
|
|
||||||
else:
|
|
||||||
prof.cons_show("Empty room list for sounds, playing in all rooms.")
|
|
||||||
elif arg2 == "clear":
|
|
||||||
prof.settings_string_list_clear("sounds", "rooms")
|
|
||||||
prof.cons_show("Cleared sounds room list, playing in all rooms.")
|
|
||||||
else:
|
|
||||||
prof.cons_bad_cmd_usage("/sounds")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
if arg1 == "mute":
|
|
||||||
if not arg2:
|
|
||||||
prof.cons_bad_cmd_usage("/sounds mute <jid>")
|
|
||||||
else:
|
|
||||||
prof.settings_string_list_add("sounds", "muted", arg2)
|
|
||||||
prof.cons_show("Muted JID: " + arg2)
|
|
||||||
return
|
|
||||||
|
|
||||||
if arg1 == "unmute":
|
|
||||||
if not arg2:
|
|
||||||
prof.cons_bad_cmd_usage("/sounds unmute <jid>")
|
|
||||||
else:
|
|
||||||
prof.settings_string_list_remove("sounds", "muted", arg2)
|
|
||||||
prof.cons_show("Unmuted JID: " + arg2)
|
|
||||||
return
|
|
||||||
|
|
||||||
prof.cons_bad_cmd_usage("/sounds")
|
|
||||||
|
|
||||||
|
|
||||||
def prof_init(version, status, account_name, fulljid):
|
|
||||||
synopsis = [
|
|
||||||
"/sounds",
|
|
||||||
"/sounds on|off",
|
|
||||||
"/sounds set audioplayer <cmd>",
|
|
||||||
"/sounds reset audioplayer",
|
|
||||||
"/sounds set chat <file>",
|
|
||||||
"/sounds set room <file>",
|
|
||||||
"/sounds set private <file>",
|
|
||||||
"/sounds test private|room|chat",
|
|
||||||
"/sounds clear chat",
|
|
||||||
"/sounds clear room",
|
|
||||||
"/sounds clear private",
|
|
||||||
"/sounds rooms add <roomjid>",
|
|
||||||
]
|
|
||||||
description = "Play sounds on receiving messages. Executing the command without arguments displays current settings."
|
|
||||||
args = [
|
|
||||||
["on|off", "Enable or disable playing sounds."],
|
|
||||||
[
|
|
||||||
"set audioplayer <cmd>",
|
|
||||||
"Commmand line of audio player to use for playing sound files. Defaults to mpg123.",
|
|
||||||
],
|
|
||||||
["reset audioplayer", "Resets the audio player to the default mpg123."],
|
|
||||||
["set chat <file>", "Path to sound file to play on chat messages."],
|
|
||||||
["set room <file>", "Path to sound file to play on room messages."],
|
|
||||||
["set private <file>", "Path to sound file to play on private room messages."],
|
|
||||||
["test private|room|chat", "Test the configured audio player and sound file."],
|
|
||||||
["clear chat", "Remove the sound for chat messages."],
|
|
||||||
["clear room", "Remove the sound for room messages."],
|
|
||||||
["clear private", "Remove the sound for private messages."],
|
|
||||||
[
|
|
||||||
"rooms add <roomjid>",
|
|
||||||
"Add the room to the list that will play the room sound.",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"rooms remove <roomjid>",
|
|
||||||
"Remove the room from the list that will play the room sound.",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"rooms clear",
|
|
||||||
"Clear the room list, all rooms will play sounds on new messages.",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
examples = [
|
|
||||||
"/sounds set audioplayer paplay",
|
|
||||||
"/sounds set chat ~/sounds/woof.mp3",
|
|
||||||
"/sounds set room ~/sounds/meow.mp3",
|
|
||||||
"/sounds set private ~/sounds/shhh.mp3",
|
|
||||||
"/sounds test private",
|
|
||||||
"/sounds remove private",
|
|
||||||
"/sounds rooms add myroom@conference.server.org",
|
|
||||||
"/sounds on",
|
|
||||||
]
|
|
||||||
|
|
||||||
prof.register_command(
|
|
||||||
"/sounds", 0, 3, synopsis, description, args, examples, _cmd_sounds
|
|
||||||
)
|
|
||||||
|
|
||||||
prof.completer_add(
|
|
||||||
"/sounds", ["set", "clear", "on", "off", "rooms", "reset", "mute", "unmute"]
|
|
||||||
)
|
|
||||||
prof.completer_add("/sounds set", ["chat", "room", "private", "audioplayer"])
|
|
||||||
prof.completer_add("/sounds reset", ["audioplayer"])
|
|
||||||
prof.completer_add("/sounds test", ["chat", "room", "private"])
|
|
||||||
prof.completer_add("/sounds clear", ["chat", "room", "private"])
|
|
||||||
prof.completer_add("/sounds rooms", ["add", "remove", "clear"])
|
|
||||||
prof.filepath_completer_add("/sounds set chat")
|
|
||||||
prof.filepath_completer_add("/sounds set room")
|
|
||||||
prof.filepath_completer_add("/sounds set private")
|
|
||||||
|
|
||||||
|
|
||||||
def prof_post_chat_message_display(barejid, resource, message):
|
|
||||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
|
||||||
if not enabled:
|
|
||||||
return
|
|
||||||
|
|
||||||
muted = prof.settings_string_list_get("sounds", "muted")
|
|
||||||
if muted and barejid in muted:
|
|
||||||
return
|
|
||||||
|
|
||||||
_play_sound(kind="chat")
|
|
||||||
|
|
||||||
|
|
||||||
def prof_post_room_message_display(barejid, nick, message):
|
|
||||||
my_nick = prof.get_room_nick(barejid)
|
|
||||||
if not my_nick:
|
|
||||||
return
|
|
||||||
|
|
||||||
if my_nick == nick:
|
|
||||||
return
|
|
||||||
|
|
||||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
|
||||||
if not enabled:
|
|
||||||
return
|
|
||||||
|
|
||||||
roomlist = prof.settings_string_list_get("sounds", "rooms")
|
|
||||||
if roomlist and (barejid not in roomlist):
|
|
||||||
return
|
|
||||||
|
|
||||||
muted = prof.settings_string_list_get("sounds", "muted")
|
|
||||||
if muted and barejid in muted:
|
|
||||||
return
|
|
||||||
|
|
||||||
_play_sound(soundfile, kind="room")
|
|
||||||
|
|
||||||
|
|
||||||
def prof_post_priv_message_display(barejid, nick, message):
|
|
||||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
|
||||||
if not enabled:
|
|
||||||
return
|
|
||||||
|
|
||||||
muted = prof.settings_string_list_get("sounds", "muted")
|
|
||||||
if muted and barejid in muted:
|
|
||||||
return
|
|
||||||
|
|
||||||
_play_sound(kind="private")
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Print something in ASCII text.
|
Print something in ASCII text.
|
||||||
|
|
||||||
Running from the console will print the text to the console.
|
Running from the console will print the text to the console.
|
||||||
Running in a chat or room window will send the ASCII text
|
Running in a chat or room window will send the ASCII text
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ def prof_init(version, status, account_name, fulljid):
|
|||||||
[ "<url>", "URL to open in the browser" ]
|
[ "<url>", "URL to open in the browser" ]
|
||||||
]
|
]
|
||||||
examples = [
|
examples = [
|
||||||
"/browser http://jabber.space/"
|
"/browser http://www.profanity.im"
|
||||||
]
|
]
|
||||||
|
|
||||||
prof.register_command("/browser", 0, 1, synopsis, description, args, examples, _cmd_browser)
|
prof.register_command("/browser", 0, 1, synopsis, description, args, examples, _cmd_browser)
|
||||||
30
stable/emoticons.py
Normal file
30
stable/emoticons.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""
|
||||||
|
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)
|
||||||
@@ -5,6 +5,7 @@ Requires scrot installed for screenshot (on linux)
|
|||||||
sudo apt-get install scrot
|
sudo apt-get install scrot
|
||||||
|
|
||||||
Uses built in screencapture on OSX
|
Uses built in screencapture on OSX
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import prof
|
import prof
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Simple plugin to display the PID of the CProof process and its parent
|
Simple plugin to display the PID of the Profanity process and its parent
|
||||||
|
|
||||||
Theme example in ~/.local/share/profanity/plugin_themes
|
Theme example in ~/.local/share/profanity/plugin_themes
|
||||||
|
|
||||||
@@ -9,16 +9,15 @@ On OSX will use the built in 'say' command.
|
|||||||
|
|
||||||
import prof
|
import prof
|
||||||
import os
|
import os
|
||||||
import shlex
|
|
||||||
from sys import platform
|
from sys import platform
|
||||||
|
|
||||||
def say(message):
|
def say(message):
|
||||||
args = prof.settings_string_get("say", "args", "")
|
args = prof.settings_string_get("say", "args", "")
|
||||||
|
|
||||||
if platform == "darwin":
|
if platform == "darwin":
|
||||||
os.system("say " + args + " " + shlex.quote(message) + " 2>/dev/null")
|
os.system("say " + args + " '" + message + "' 2>/dev/null")
|
||||||
elif platform == "linux" or platform == "linux2":
|
elif platform == "linux" or platform == "linux2":
|
||||||
os.system("echo " + shlex.quote(message) + " | espeak " + args + " 2>/dev/null")
|
os.system("echo '" + message + "' | espeak " + args + " 2>/dev/null")
|
||||||
|
|
||||||
|
|
||||||
def prof_post_chat_message_display(barejid, resource, message):
|
def prof_post_chat_message_display(barejid, resource, message):
|
||||||
208
stable/sounds.py
Normal file
208
stable/sounds.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
"""
|
||||||
|
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" ])
|
||||||
|
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
|
||||||
|
|
||||||
|
soundfile = prof.settings_string_get("sounds", "chat", None)
|
||||||
|
if not soundfile:
|
||||||
|
return
|
||||||
|
|
||||||
|
_play_sound(soundfile)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ def _handle_send(command=None):
|
|||||||
result = _get_result(command)
|
result = _get_result(command)
|
||||||
newline = prof.settings_boolean_get("system", "newline", True)
|
newline = prof.settings_boolean_get("system", "newline", True)
|
||||||
if len(result.splitlines()) > 1 and newline:
|
if len(result.splitlines()) > 1 and newline:
|
||||||
prof.send_line(u'\u000A' + result.decode('utf-8', errors='ignore'))
|
prof.send_line(u'\u000A' + result)
|
||||||
else:
|
else:
|
||||||
prof.send_line(result)
|
prof.send_line(result)
|
||||||
|
|
||||||
161
tests/RubyTest.rb
Normal file
161
tests/RubyTest.rb
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
module RubyTest
|
||||||
|
|
||||||
|
def self.prof_init(version, status)
|
||||||
|
Prof::cons_show("RubyTest: init, " + version + ", " + status)
|
||||||
|
Prof::register_command("/ruby", 0, 1, "/ruby [arg]", "RubyTest", "RubyTest", cmd_ruby)
|
||||||
|
Prof::register_command("/rb_upper", 1, 1, "/rb_upper string", "RubyTest", "RubyTest", cmd_upper)
|
||||||
|
Prof::register_command("/rb_notify", 0, 0, "/rb_notify", "RubyTest", "RubyTest", cmd_notify)
|
||||||
|
Prof::register_command("/rb_vercheck", 0, 0, "/rb_vercheck", "RubyTest", "RubyTest", cmd_vercheck)
|
||||||
|
Prof::register_ac("/rb_complete", [ "aaaa", "bbbb", "bcbcbc" ])
|
||||||
|
Prof::register_ac("/rb_complete aaaa", [ "one", "two", "three", "four" ])
|
||||||
|
Prof::register_ac("/rb_complete bcbcbc", [ "james", "jim", "jane", "bob" ])
|
||||||
|
Prof::register_command("/rb_complete", 0, 2, "/rb_complete [arg1] [arg2]", "RubyTest", "RubyTest", cmd_ac)
|
||||||
|
Prof::register_timed(timer_test, 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_on_start
|
||||||
|
Prof::cons_show("RubyTest: prof_on_start")
|
||||||
|
Prof::log_debug("RubyTest: logged debug");
|
||||||
|
Prof::log_info("RubyTest: logged info");
|
||||||
|
Prof::log_warning("RubyTest: logged warning");
|
||||||
|
Prof::log_error("RubyTest: logged error");
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_on_shutdown
|
||||||
|
Prof::log_info("RubyTest: prof_on_shutdown");
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_on_connect(account_name, fulljid)
|
||||||
|
Prof::cons_show("RubyTest: prof_on_connect, " + account_name + ", " + fulljid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_on_disconnect(account_name, fulljid)
|
||||||
|
Prof::cons_show("RubyTest: prof_on_disconnect, " + account_name + ", " + fulljid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_pre_chat_message_display(jid, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_pre_chat_message_display, " + jid + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
return message + "[RB_pre_chat_message_display]"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_post_chat_message_display(jid, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_post_chat_message_display, " + jid + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_pre_chat_message_send(jid, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_pre_chat_message_send, " + jid + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
return message + "[RB_pre_chat_message_send]"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_post_chat_message_send(jid, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_post_chat_message_send, " + jid + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_pre_room_message_display(room, nick, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_pre_room_message_display, " + room + ", " + nick + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
return message + "[RB_pre_room_message_display]"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_post_room_message_display(room, nick, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_post_room_message_display, " + room + ", " + nick + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_pre_room_message_send(room, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_pre_room_message_send, " + room + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
return message + "[RB_pre_room_message_send]"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_post_room_message_send(room, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_post_room_message_send, " + room + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_pre_priv_message_display(room, nick, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_pre_priv_message_display, " + room + ", " + nick + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
return message + "[RB_pre_priv_message_display]"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_post_priv_message_display(room, nick, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_post_priv_message_display, " + room + ", " + nick + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_pre_priv_message_send(room, nick, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_pre_priv_message_send, " + room + ", " + nick + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
return message + "[RB_pre_priv_message_send]"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_post_priv_message_send(room, nick, message)
|
||||||
|
Prof::cons_show("RubyTest: prof_post_priv_message_send, " + room + ", " + nick + ", " + message)
|
||||||
|
Prof::cons_alert
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cmd_ruby
|
||||||
|
return Proc.new { | msg |
|
||||||
|
if msg
|
||||||
|
Prof::cons_show("RubyTest: /ruby command called, arg = " + msg)
|
||||||
|
else
|
||||||
|
Prof::cons_show("RubyTest: /ruby command called with no arg")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cmd_ac
|
||||||
|
return Proc.new { | arg1, arg2 |
|
||||||
|
Prof::cons_show("RubyTest: /rb_complete called")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cmd_notify
|
||||||
|
return Proc.new {
|
||||||
|
Prof::notify("RubyTest: notify", 2000, "Plugins")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cmd_vercheck
|
||||||
|
return Proc.new {
|
||||||
|
Prof::send_line("/vercheck")
|
||||||
|
Prof::cons_show("RubyTest: sent \"/vercheck\" command")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.timer_test
|
||||||
|
return Proc.new {
|
||||||
|
Prof::cons_show("RubyTest: timer fired.")
|
||||||
|
Prof::cons_alert
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cmd_upper
|
||||||
|
return Proc.new { | line |
|
||||||
|
win_tag = "Ruby Plygin"
|
||||||
|
if (Prof::win_exists(win_tag) == false)
|
||||||
|
Prof::win_create(win_tag, handle_upper)
|
||||||
|
end
|
||||||
|
Prof::win_focus(win_tag)
|
||||||
|
if (line)
|
||||||
|
handle_upper.call(win_tag, line)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.handle_upper
|
||||||
|
return Proc.new { | win, line |
|
||||||
|
upper = line.upcase
|
||||||
|
Prof::win_show(win, upper)
|
||||||
|
Prof::win_show_red(win, upper + " red")
|
||||||
|
Prof::win_show_yellow(win, upper + " yellow")
|
||||||
|
Prof::win_show_green(win, upper + " green")
|
||||||
|
Prof::win_show_cyan(win, upper + " cyan")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
23
tests/RubyThread.rb
Normal file
23
tests/RubyThread.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
module RubyThread
|
||||||
|
@counter = 0
|
||||||
|
|
||||||
|
def self.inc_counter()
|
||||||
|
while true
|
||||||
|
sleep(1)
|
||||||
|
@counter = @counter + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cmd_rcount()
|
||||||
|
return Proc.new {
|
||||||
|
instance_eval {
|
||||||
|
Prof::cons_show("RCounter: " + @counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.prof_init(version, status)
|
||||||
|
Thread.new { self.inc_counter() }
|
||||||
|
Prof::register_command("/rcount", 0, 0, "/rcount", "Ruby threaded example", "Ruby threaded example", cmd_rcount)
|
||||||
|
end
|
||||||
|
end
|
||||||
116
tests/luatest.lua
Normal file
116
tests/luatest.lua
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
local luatest = {}
|
||||||
|
|
||||||
|
local function cmd_lua(msg)
|
||||||
|
if msg then
|
||||||
|
prof_cons_show("luatest: /lua command called, arg = " .. msg)
|
||||||
|
else
|
||||||
|
prof_cons_show("luatest: /lua command called with no arg")
|
||||||
|
end
|
||||||
|
prof_cons_alert()
|
||||||
|
prof_notify("luatest: notify", 8000, "Plugins")
|
||||||
|
prof_send_line("/account list")
|
||||||
|
prof_cons_show("luatest: sent \"/account list\" command")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function timer_test()
|
||||||
|
prof_cons_show("luatest: timer fired.")
|
||||||
|
recipient = prof_get_current_recipient()
|
||||||
|
if recipient then
|
||||||
|
prof_cons_show(" current recipient = " .. recipient)
|
||||||
|
end
|
||||||
|
prof_cons_alert()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handle_bracket(win, line)
|
||||||
|
prof_win_show(win, "(" .. line .. ")")
|
||||||
|
prof_win_show_red(win, "Red")
|
||||||
|
prof_win_show_yellow(win, "Yellow")
|
||||||
|
prof_win_show_green(win, "Green")
|
||||||
|
prof_win_show_cyan(win, "Cyan")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cmd_bracket(line)
|
||||||
|
win_tag = "Parenthesise echo";
|
||||||
|
if prof_win_exists(win_tag) == false then
|
||||||
|
prof_win_create(win_tag, handle_bracket);
|
||||||
|
end
|
||||||
|
|
||||||
|
prof_win_focus(win_tag);
|
||||||
|
|
||||||
|
if line then
|
||||||
|
handle_bracket(win_tag, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_init(version, status)
|
||||||
|
prof_cons_show("luatest: init, " .. version .. ", " .. status)
|
||||||
|
|
||||||
|
prof_register_command("/lua", 0, 1, "/lua", "luatest", "luatest", cmd_lua)
|
||||||
|
prof_register_command("/bracket", 0, 1, "/bracket", "Parenthesise input string", "Parenthesise input string", cmd_bracket);
|
||||||
|
|
||||||
|
prof_register_ac("/lcomplete", { "football", "footy", "tennis" })
|
||||||
|
prof_register_ac("/lcomplete football", { "goal", "out", "try", "given" })
|
||||||
|
prof_register_ac("/lcomplete tennis", { "test", "prod", "dev", "stage" })
|
||||||
|
prof_register_command("/lcomplete", 0, 2, "/lcomplete", "Lua completion", "Lua completion", cmd_bracket)
|
||||||
|
|
||||||
|
prof_register_timed(timer_test, 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_start()
|
||||||
|
prof_cons_show("luatest: on_start")
|
||||||
|
prof_log_debug("luatest: logged debug");
|
||||||
|
prof_log_info("luatest: logged info");
|
||||||
|
prof_log_warning("luatest: logged warning");
|
||||||
|
prof_log_error("luatest: logged error");
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_connect(account_name, fulljid)
|
||||||
|
prof_cons_show("luatest: on_connect, " .. account_name .. ", " .. fulljid)
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_disconnect(account_name, fulljid)
|
||||||
|
prof_cons_show("luatest: on_disconnect, " .. account_name .. ", " .. fulljid)
|
||||||
|
prof_log_info("luatest: on_disconnect, " .. account_name .. ", " .. fulljid)
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_message_received(jid, message)
|
||||||
|
prof_cons_show("luatest: on_message_received, " .. jid .. ", " .. message)
|
||||||
|
prof_cons_alert()
|
||||||
|
return message .. "[LUA]"
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_room_message_received(room, nick, message)
|
||||||
|
prof_cons_show("luatest: on_room_message_received, " .. room .. ", " .. nick .. ", " .. message)
|
||||||
|
prof_cons_alert()
|
||||||
|
return message .. "[LUA]"
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_private_message_received(room, nick, message)
|
||||||
|
prof_cons_show("luatest: on_private_message_received, " .. room .. ", " .. nick .. ", " .. message)
|
||||||
|
prof_cons_alert()
|
||||||
|
return message .. "[LUA]"
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_message_send(jid, message)
|
||||||
|
prof_cons_show("luatest: on_message_send, " .. jid .. ", " .. message)
|
||||||
|
prof_cons_alert()
|
||||||
|
return message .. "[LUA]"
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_private_message_send(room, nick, message)
|
||||||
|
prof_cons_show("luatest: on_private_message_send, " .. room .. ", " .. nick .. ", " .. message)
|
||||||
|
prof_cons_alert()
|
||||||
|
return message .. "[LUA]"
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_room_message_send(room, message)
|
||||||
|
prof_cons_show("luatest: on_room_message_send, " .. room .. ", " .. message)
|
||||||
|
prof_cons_alert()
|
||||||
|
return message .. "[LUA]"
|
||||||
|
end
|
||||||
|
|
||||||
|
function luatest.prof_on_shutdown()
|
||||||
|
prof_log_info("luatest: on_shutdown")
|
||||||
|
end
|
||||||
|
|
||||||
|
return luatest
|
||||||
704
tests/python-test.py
Normal file
704
tests/python-test.py
Normal file
@@ -0,0 +1,704 @@
|
|||||||
|
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)
|
||||||
|
nick = prof.get_room_nick(room)
|
||||||
|
if nick:
|
||||||
|
prof.win_focus(plugin_win)
|
||||||
|
prof.win_show(plugin_win, "called -> prof_get_room_nick('" + room + "'): " + nick)
|
||||||
|
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>",
|
||||||
|
"/python-test file"
|
||||||
|
]
|
||||||
|
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." ],
|
||||||
|
[ "file", "Complete a file path." ]
|
||||||
|
]
|
||||||
|
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",
|
||||||
|
"file"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
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.filepath_completer_add("/python-test file")
|
||||||
|
|
||||||
|
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)
|
||||||
|
return 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)
|
||||||
|
return 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)
|
||||||
|
return 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)
|
||||||
18
tests/test-c-plugin/Makefile
Normal file
18
tests/test-c-plugin/Makefile
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
LINK_FLAGS := -fno-common -flat_namespace -bundle -undefined suppress
|
||||||
|
else
|
||||||
|
LINK_FLAGS := -shared -fpic
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: test-c-plugin.so
|
||||||
|
|
||||||
|
%.so:%.o
|
||||||
|
$(CC) $(LINK_FLAGS) -lprofanity -o $@ $^
|
||||||
|
|
||||||
|
%.o:%.c
|
||||||
|
$(CC) $(INCLUDE) -D_GNU_SOURCE -D_BSD_SOURCE -fpic -g3 -O0 -std=c99 \
|
||||||
|
-pedantic -c -o $@ $<
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) test-c-plugin.so
|
||||||
1051
tests/test-c-plugin/test-c-plugin.c
Normal file
1051
tests/test-c-plugin/test-c-plugin.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user