Compare commits

...

No commits in common. "gh-pages" and "master-2021-04-01" have entirely different histories.

773 changed files with 141341 additions and 1515 deletions

View File

@ -1,8 +0,0 @@
root = true
[*]
end_of_line = lf
trim_trailing_whitespace = false
insert_final_newline = true
charset = utf-8
indent_style = space

3
.gitattributes vendored
View File

@ -1 +1,2 @@
* text=auto eol=lf
sandbox export-ignore
.git* export-ignore

1
.github/CODEOWNERS vendored
View File

@ -1 +0,0 @@
* @Mikaela

View File

@ -1,5 +0,0 @@
/** @format */
{
extends: ["local>Mikaela/shell-things:.renovate-shared"],
}

View File

@ -1,25 +0,0 @@
name: HTML5 Validator
on:
push:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # Required will all actions
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Jekyll build
run: |
bundle exec jekyll build --drafts --profile
- name: Checks HTML5 validity
uses: Cyb3r-Jak3/html5validator-action@v7.2.0
with:
root: _site/
blacklist: n r or ir

17
.gitignore vendored
View File

@ -24,9 +24,14 @@ test-conf/
test-data/
test-logs/
src/version.py
_site
.sass-cache
vendor/
.bundle
node_modules/
pnpm-lock.yaml
INSTALL
README.txt
conf/
data/
logs/
*.conf
*.conf.bak
# Intellij PyCharm / IDEA related files
*.iml
.idea

27
.mailmap Normal file
View File

@ -0,0 +1,27 @@
<jamessan@users.sourceforge.net> <vega.james@gmail.com>
<jemfinch@users.sourceforge.net> <jemfinch@finchers.us>
<jemfinch@users.sourceforge.net> <jfincher@monoid.(none)>
<mikaela.suomalainen@outlook.com> <Mkaysi@users.noreply.github.com>
<nyuszika7h@openmailbox.org> <liemininyuszika@gmail.com>
<nyuszika7h@openmailbox.org> <litemininyuszika@gmail.com>
<nyuszika7h@openmailbox.org> <nyuszika7h@cadoth.co>
<nyuszika7h@openmailbox.org> <nyuszika7h@cadoth.net>
<nyuszika7h@openmailbox.org> <nyuszika7h@gmail.com>
<nyuszika7h@openmailbox.org> <nyuszika7h@outlook.com>
<progval@progval.net> <progval+github@progval.net>
<progval@progval.net> <progval@gmail.com>
<shadowninja@minetest.net> <ShadowNinja@users.noreply.github.com>
Daniel Folkinshteyn <nanotube@users.sourceforge.net> Daniel F <dfolkins@a90.(none)>
Daniel Folkinshteyn <nanotube@users.sourceforge.net> nanotube <nanotube@users.sf.net>
James Lu <james@overdrivenetworks.com> <GLolol1@hotmail.com>
James Lu <james@overdrivenetworks.com> <glolol1@hotmail.com>
James Lu <james@overdrivenetworks.com> <glolol@overdrive.pw>
James Lu <james@overdrivenetworks.com> <GLolol@overdrivenetworks.com>
James McCoy <jamessan@users.sourceforge.net>
Ken Spencer <ken@electrocode.net> Iota Spencer <iota@electrocode.net>
Mikaela Suomalainen <mikaela.suomalainen@outlook.com> Mika Suomalainen <mika.henrik.mainio@hotmail.com>
Mikaela Suomalainen <mikaela.suomalainen@outlook.com> Mika Suomalainen <mkaysi@outlook.com>
Mikaela Suomalainen <mikaela.suomalainen@outlook.com> Mika Suomalainen <mkaysi@users.sourceforge.net>
Mikaela Suomalainen <mikaela.suomalainen@outlook.com> Mika Suomalainen <s.mika95@gmail.com>
Mikaela Suomalainen <mikaela.suomalainen@outlook.com> Mikaela Suomalainen <mkaysi@outlook.com>
Tannn3r <tannn3r@gmail.com> <tanman8r@gmail.com>

View File

@ -1,83 +0,0 @@
# @format
# SPDX-FileCopyrightText: 2023 Aminda Suomalainen <suomalainen+git@mikaela.info>
#
# SPDX-License-Identifier: CC0-1.0
# See https://pre-commit.com for more information
# See https://pre-commit.ci for more information
ci:
# I don't need so many duplicated notifications on the same thing as I keep
# autoupdating manually too. Besides it just creates extra branch I never
# touch.
# https://github.com/pre-commit-ci/issues/issues/83
autoupdate_schedule: quarterly
skip: [pnpm-install-dev, prettier]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
args: ["--markdown-linebreak-ext", "md,markdown"]
exclude_types: [svg, tsv]
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-json
- id: check-merge-conflict
- id: check-shebang-scripts-are-executable
- id: destroyed-symlinks
- id: detect-private-key
- id: fix-byte-order-marker
- id: check-merge-conflict
- id: mixed-line-ending
args: [--fix=auto]
- id: pretty-format-json
args: [--autofix, --no-ensure-ascii]
- repo: https://github.com/pre-commit-ci/pre-commit-ci-config
rev: v1.6.1
hooks:
- id: check-pre-commit-ci-config
- repo: https://github.com/thlorenz/doctoc
rev: v2.2.0
hooks:
- id: doctoc
args: [--update-only, --notitle]
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.0
hooks:
- id: check-dependabot
- id: check-github-workflows
- id: check-gitlab-ci
# - repo: https://github.com/fsfe/reuse-tool
# rev: v3.0.2
# hooks:
# - id: reuse
- repo: local
hooks:
- id: pnpm-install-dev
name: Install pnpm dev dependencies
entry: corepack pnpm install -D
language: system
always_run: true
#verbose: true
pass_filenames: false
- id: prettier
name: prettier
entry: corepack pnpm exec prettier --cache --ignore-unknown --write
language: system
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: "3.2.1"
hooks:
- id: editorconfig-checker
alias: ec
args: [-disable-max-line-length]

View File

@ -1,5 +0,0 @@
_includes/
_layouts/
_sass/
css/
feed.xml

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
3.4.2

View File

@ -1,4 +1,54 @@
# @format
language: python
sudo: true
install:
- if [ "$WITH_OPT_DEPS" = "true" ] ; then pip install -vr requirements.txt pytest; fi
- git clone https://github.com/ProgVal/irctest.git
- echo "y" | pip uninstall limnoria || true
# command to run tests, e.g. python setup.py test
script:
- echo $TRAVIS_PYTHON_VERSION
- python setup.py install
- supybot-test test -v --plugins-dir=./plugins/ --no-network
- if [ "$WITH_OPT_DEPS" = "true" ] -a [[ "$TRAVIS_PYTHON_VERSION" =~ ^3\.[4-9] ]] ; then cd irctest; pytest --controllers irctest.controllers.limnoria; fi
notifications:
email: false
matrix:
include:
- python: "3.4"
env: WITH_OPT_DEPS=false
dist: trusty
- python: "3.5"
env: WITH_OPT_DEPS=false
dist: trusty
- python: "3.6"
env: WITH_OPT_DEPS=false
dist: trusty
language: ruby
script: "bundle exec jekyll build"
- python: "3.7"
env: WITH_OPT_DEPS=false
dist: xenial
- python: "3.7"
env: WITH_OPT_DEPS=true
dist: xenial
- python: "3.8"
env: WITH_OPT_DEPS=true
dist: xenial
- python: "3.9"
env: WITH_OPT_DEPS=true
dist: xenial
- python: "nightly"
env: WITH_OPT_DEPS=true
dist: xenial
- python: "pypy3"
env: WITH_OPT_DEPS=false
dist: trusty
- python: "nightly"
env: WITH_OPT_DEPS=true
dist: xenial
allow_failures:
- python: "pypy3"
env: WITH_OPT_DEPS=true
dist: xenial

1
CNAME
View File

@ -1 +0,0 @@
supybot.mikaela.info

37
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,37 @@
# Contributing to Limnoria
## Guidelines
Follow the [Style Guidelines].
When adding a string that will be shown on IRC, always internationalize
it (wrap it in a call to `_()`).
When making a trivial change to an internationalized string that does not
affect the meaning of the string (typo fix, etc.), please update the
`msgid` entry in localization file. It helps preserve the translation
without the translator having to review it.
Last rule: you shouldn't add a mandatory dependency. Limnoria does not
come with any (besides Python), so please try to keep all dependencies
optional.
[Style Guidelines]:https://limnoria.readthedocs.io/en/latest/develop/style.html
## Sending patches
When you send a pull request, **send it to the testing branch**.
It will be merged to master when it's considered to be stable enough to be
supported.
Don't fear that you spam Limnoria by sending many pull requests. According
to @ProgVal, it's easier for them to accept pull requests than to
cherry-pick everything manually.
Having at least one test case in any non-trivial pull-request
is very appreciated.
See also [Contributing to Limnoria] at [Limnoria documentation].
[Contributing to Limnoria]:https://limnoria.readthedocs.io/en/latest/contribute/index.html
[Limnoria documentation]:https://limnoria.readthedocs.io/

2022
ChangeLog Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
source "https://rubygems.org"
ruby file: ".ruby-version"
# For now this is a GitHub Pages hosted website.
# Ref: https://github.com/Mikaela/mikaela.github.io/issues/153
gem 'github-pages', group: :jekyll_plugins
gem 'jekyll-seo-tag'
# Required for `bundle exec jekyll serve`
gem "webrick"

View File

@ -1,286 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (8.0.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
colorator (1.1.0)
commonmarker (0.23.11)
concurrent-ruby (1.3.5)
connection_pool (2.5.0)
csv (3.3.3)
dnsruby (1.72.4)
base64 (~> 0.2.0)
logger (~> 1.6.5)
simpleidn (~> 0.2.1)
drb (2.2.1)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
ethon (0.16.0)
ffi (>= 1.15.0)
eventmachine (1.2.7)
execjs (2.10.0)
faraday (2.13.0)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
ffi (1.17.1-x86_64-linux-gnu)
forwardable-extended (2.6.0)
gemoji (4.1.0)
github-pages (232)
github-pages-health-check (= 1.18.2)
jekyll (= 3.10.0)
jekyll-avatar (= 0.8.0)
jekyll-coffeescript (= 1.2.2)
jekyll-commonmark-ghpages (= 0.5.1)
jekyll-default-layout (= 0.1.5)
jekyll-feed (= 0.17.0)
jekyll-gist (= 1.5.0)
jekyll-github-metadata (= 2.16.1)
jekyll-include-cache (= 0.2.1)
jekyll-mentions (= 1.6.0)
jekyll-optional-front-matter (= 0.3.2)
jekyll-paginate (= 1.1.0)
jekyll-readme-index (= 0.3.0)
jekyll-redirect-from (= 0.16.0)
jekyll-relative-links (= 0.6.1)
jekyll-remote-theme (= 0.4.3)
jekyll-sass-converter (= 1.5.2)
jekyll-seo-tag (= 2.8.0)
jekyll-sitemap (= 1.4.0)
jekyll-swiss (= 1.0.0)
jekyll-theme-architect (= 0.2.0)
jekyll-theme-cayman (= 0.2.0)
jekyll-theme-dinky (= 0.2.0)
jekyll-theme-hacker (= 0.2.0)
jekyll-theme-leap-day (= 0.2.0)
jekyll-theme-merlot (= 0.2.0)
jekyll-theme-midnight (= 0.2.0)
jekyll-theme-minimal (= 0.2.0)
jekyll-theme-modernist (= 0.2.0)
jekyll-theme-primer (= 0.6.0)
jekyll-theme-slate (= 0.2.0)
jekyll-theme-tactile (= 0.2.0)
jekyll-theme-time-machine (= 0.2.0)
jekyll-titles-from-headings (= 0.5.3)
jemoji (= 0.13.0)
kramdown (= 2.4.0)
kramdown-parser-gfm (= 1.1.0)
liquid (= 4.0.4)
mercenary (~> 0.3)
minima (= 2.5.1)
nokogiri (>= 1.16.2, < 2.0)
rouge (= 3.30.0)
terminal-table (~> 1.4)
webrick (~> 1.8)
github-pages-health-check (1.18.2)
addressable (~> 2.3)
dnsruby (~> 1.60)
octokit (>= 4, < 8)
public_suffix (>= 3.0, < 6.0)
typhoeus (~> 1.3)
html-pipeline (2.14.3)
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.8.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
jekyll (3.10.0)
addressable (~> 2.4)
colorator (~> 1.0)
csv (~> 3.0)
em-websocket (~> 0.5)
i18n (>= 0.7, < 2)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 2.0)
kramdown (>= 1.17, < 3)
liquid (~> 4.0)
mercenary (~> 0.3.3)
pathutil (~> 0.9)
rouge (>= 1.7, < 4)
safe_yaml (~> 1.0)
webrick (>= 1.0)
jekyll-avatar (0.8.0)
jekyll (>= 3.0, < 5.0)
jekyll-coffeescript (1.2.2)
coffee-script (~> 2.2)
coffee-script-source (~> 1.12)
jekyll-commonmark (1.4.0)
commonmarker (~> 0.22)
jekyll-commonmark-ghpages (0.5.1)
commonmarker (>= 0.23.7, < 1.1.0)
jekyll (>= 3.9, < 4.0)
jekyll-commonmark (~> 1.4.0)
rouge (>= 2.0, < 5.0)
jekyll-default-layout (0.1.5)
jekyll (>= 3.0, < 5.0)
jekyll-feed (0.17.0)
jekyll (>= 3.7, < 5.0)
jekyll-gist (1.5.0)
octokit (~> 4.2)
jekyll-github-metadata (2.16.1)
jekyll (>= 3.4, < 5.0)
octokit (>= 4, < 7, != 4.4.0)
jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0)
jekyll-mentions (1.6.0)
html-pipeline (~> 2.3)
jekyll (>= 3.7, < 5.0)
jekyll-optional-front-matter (0.3.2)
jekyll (>= 3.0, < 5.0)
jekyll-paginate (1.1.0)
jekyll-readme-index (0.3.0)
jekyll (>= 3.0, < 5.0)
jekyll-redirect-from (0.16.0)
jekyll (>= 3.3, < 5.0)
jekyll-relative-links (0.6.1)
jekyll (>= 3.3, < 5.0)
jekyll-remote-theme (0.4.3)
addressable (~> 2.0)
jekyll (>= 3.5, < 5.0)
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
rubyzip (>= 1.3.0, < 3.0)
jekyll-sass-converter (1.5.2)
sass (~> 3.4)
jekyll-seo-tag (2.8.0)
jekyll (>= 3.8, < 5.0)
jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0)
jekyll-swiss (1.0.0)
jekyll-theme-architect (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-cayman (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-dinky (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-hacker (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-leap-day (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-merlot (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-midnight (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-minimal (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-modernist (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-primer (0.6.0)
jekyll (> 3.5, < 5.0)
jekyll-github-metadata (~> 2.9)
jekyll-seo-tag (~> 2.0)
jekyll-theme-slate (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-tactile (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-time-machine (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-titles-from-headings (0.5.3)
jekyll (>= 3.3, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
jemoji (0.13.0)
gemoji (>= 3, < 5)
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
json (2.10.2)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.6)
mercenary (0.3.6)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.25.5)
net-http (0.6.0)
uri
nokogiri (1.18.7-x86_64-linux-gnu)
racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (5.1.1)
racc (1.8.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.4.1)
rouge (3.30.0)
rubyzip (2.4.1)
safe_yaml (1.0.5)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
securerandom (0.4.1)
simpleidn (0.2.3)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (1.8.0)
uri (1.0.3)
webrick (1.9.1)
PLATFORMS
x86_64-linux
DEPENDENCIES
github-pages
jekyll-seo-tag
webrick
RUBY VERSION
ruby 3.4.2p28
BUNDLED WITH
2.6.2

28
LICENSE.md Normal file
View File

@ -0,0 +1,28 @@
Copyright (c) 2002-2009 Jeremiah Fincher and others
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions, and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions, and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the author of this software nor the name of
contributors to this software may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Portions of the included source code are copyright by its original author(s)
and remain subject to its associated license.

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
include LICENSE.md
include README.md
include Makefile
include .travis.yml
include requirements.txt
include test/*.py

42
Makefile Normal file
View File

@ -0,0 +1,42 @@
PYTHON=`which python3`
DESTDIR=/
PROJECT=limnoria
all:
@echo "make source - Create source package"
@echo "make install - Install on local system"
@echo "make buildrpm - Generate a rpm package"
@echo "make builddeb_py2 - Generate a deb package for Python 2"
@echo "make builddeb_py3 - Generate a deb package for Python 3"
@echo "make clean - Get rid of scratch and byte files"
test:
PATH=./scripts/:${PATH} PYTHONPATH=. $(PYTHON) ./scripts/supybot-test test --plugins-dir=plugins/
source:
$(PYTHON) setup.py sdist $(COMPILE)
install:
$(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE)
buildrpm:
$(PYTHON) setup.py bdist_rpm
builddeb_py2:
cp debian/control.py2 debian/control
debuild -us -uc
rm debian/control
builddeb_py3:
cp debian/control.py3 debian/control
debuild -us -uc
rm debian/control
clean:
$(PYTHON) setup.py clean
$(MAKE) -f $(CURDIR)/debian/rules clean
rm -rf build/ MANIFEST
find . -name '*.pyc' -delete
rm debian/control
.PHONY: test

View File

@ -1,11 +1,56 @@
<!-- @format -->
Supybot is a robust (it doesn't crash), user friendly (it's easy to
configure) and programmer friendly (plugins are *extremely* easy to
write) Python IRC bot. It aims to be an adequate replacement for most
existing IRC bots. It includes a very flexible and powerful ACL system
for controlling access to commands, as well as more than 50 builtin plugins
providing around 400 actual commands.
# Mikaela's fork of Limnoria.
Limnoria is the project which continues development of Supybot since 2010.
There are mainly two branches. This one which you are looking at, gh-pages which
is the source of <https://supybot.mikaela.info/>.
# Build status
**testing** which will be synced with [ProgVal/Limnoria] when needed. It is used
as base for my changes which will be pull requested.
Master branch: [![Build Status (master branch)](https://travis-ci.org/ProgVal/Limnoria.png?branch=master)](https://travis-ci.org/ProgVal/Limnoria)
Testing branch: [![Build Status (testing branch)](https://travis-ci.org/ProgVal/Limnoria.png?branch=testing)](https://travis-ci.org/ProgVal/Limnoria)
Limnoria supports CPython 3.4 to 3.9, CPython nightly, and Pypy 3.
# Support
## Documentation
If this is your first install, there is an [install guide](https://docs.limnoria.net/en/latest/use/install.html).
You will probably be pointed to it if you ask on IRC how to install
Limnoria.
TL;DR version:
```
sudo apt-get install python3 python3-pip python3-wheel
pip3 install --user limnoria
# You might need to add $HOME/.local/bin to your PATH
supybot-wizard
```
There is extensive documentation at [docs.limnoria.net] and at
[Gribble wiki]. We took the time to write it; you should take the time to
read it.
[docs.limnoria.net]:https://docs.limnoria.net/
[Gribble wiki]:https://sourceforge.net/p/gribble/wiki/Main_Page/
## IRC channels
### In English
If you have any trouble, feel free to swing by [#limnoria](ircs://chat.freenode.net:6697/#limnoria) on
[freenode](https://freenode.net/) and ask questions. We'll be happy to help
wherever we can. And by all means, if you find anything hard to
understand or think you know of a better way to do something,
*please* post it on the [issue tracker] so we can improve the bot!
[issue tracker]:https://github.com/ProgVal/Limnoria/issues
### In Other languages
Only in French at the moment, located at [#supybot-fr on freenode](ircs://chat.freenode.net:6697/#supybot-fr).
[ProgVal/Limnoria]: https://github.com/ProgVal/Limnoria.git

435
RELNOTES Normal file
View File

@ -0,0 +1,435 @@
Version 0.83.5
The minimum supported Python version has been bumped to 2.6.
utils.str.perlVariableSubstitute is deprecated in favor of using Python's
string.Template directly. perlVariableSubstitute will be removed in a future
release.
Factoids' config variable supybot.plugins.Factoids.factoidPrefix has been
replaced by supybot.plugins.Factoids.format, which allows the user to
determine exactly how replies to Factoid queries are formatted.
supybot-botchk no longer runs supybot inside an instance of /bin/sh.
Version 0.83.4.1
Simple bug-fix release for a couple changes that were introduced last minute
before the previous release.
No incompatibilities.
Version 0.83.4
This release contains fixes for Python2.6 compability as well as a re-written
Google plugin which uses the AJAX API. The re-written plugin gained a
translate command and no longer needs an API key.
ChannelLogger has a new config variable, supybot.plugins.ChannelLogger.enable,
which can be used on a per-channel basis to determine whether that channel is
logged.
The RSS announce command has been restructured into the commands "announce
list", "announce remove", and "announce add" in order to simplify the
command's use.
The supybot.directories.plugins config variable now determines its global
directory dynamically instead of adding the directory to the value. This
means values like '/usr/lib/python2.5/site-packages/supybot/plugins' can be
removed from the variable. This should help ease transitioning a Supybot
config from one Python release to another.
Incompatibilities:
supybot.plugins.Google.safeSearch has been renamed to
supybot.plugins.Google.searchFilter
supybot.plugins.Channel.banmask has been removed in favor of a new
supybot.protocols.irc.banmask config variable. This general config variable
is used by the various commands which would need to know what style of banmask
to use.
Version 0.83.3
Overdue bug fix and Python2.5-compatible release. No significant changes to
worry about from the user perspective.
Version 0.83.2
Mainly bug fix release. The most noticeable change being a value of
'default' for supybot.drivers.module will use Socket instead of Twisted.
Version 0.83.1
No incompatibilities, just fixing an important bug with plugin loading.
Version 0.83.0
This far overdue release contains mostly bug-fixes.
Incompatibilities:
Changed the prefixName keyword argument (which appears in various places
in callbacks.py and the reply-related irc methods) to prefixNick.
Version 0.83.0rc3
This release candidate contains mostly bug-fixes.
Incompatibilities:
utils.changeFunctionName was moved to utils.python.changeFunctionName
Version 0.83.0rc2
This release candidate contains a few bug-fixes and some plugins we
forgot in the last RC. Otherwise, everything is compatible.
Version 0.83.0rc1
There have been some fairly significant changes since our last release.
This means that you should uninstall your previous version before
installing this version.
First, plugins are now a directory of files rather than a single file.
This makes it much easier for an individual plugin to supply any
3rd-party modules it may depend on and resolves some bugs we had with
reloading plugins. supybot-plugin-create (nee supybot-newplugin) has
been updated to reflect this. A side effect of using a directory-based
plugin is that @load/@reload are now case-sensitive. "@load foo" is not
the same as "@load Foo".
As part of the conversion to the new plugin format, some plugins were
broken up into more focused plugins. Also, not all of the plugins that
we used to ship are part of this release. Some we moved to the
supybot-plugins package and some others (which would be candidates for
supybot-plugins) have yet to be converted to the new format.
Second, we've updated the scripts that ship with Supybot. As noted in
the previous section, supybot-newplugin is now named
supybot-plugin-create. We've also added the following scripts:
supybot-botchk - Handy script for starting the bot and keeping it
running. Ideal for having cron automatically start the
bot.
supybot-plugin-doc - Generates documentation for the specified
plugin(s). Currently, the documentation is
generated using Structured TeXt so that it can
easily be uploaded to our website.
supybot-plugin-package - The beginning of a script to make a plugin
package which can be uploaded to our website
for general consumption.
supybot-test - Runs a plugin's test suite.
Third, we've broken supybot.utils into focused sub-modules. There's no
longer a supybot.fix module and we now have the following modules:
supybot.utils.file - utilities for dealing with files (e.g. the old
supybot.utils.transactionalFile is now
supybot.utils.file.AtomicFile)
supybot.utils.iter - utilities for dealing with iterables (all, any,
partition, groupBy, choice, etc)
supybot.utils.gen - general purpose utilities which are imported into
the supybot.utils namespace
supybot.utils.net - utilities for dealing with the network
supybot.utils.python - utilities for dealing with Python
supybot.utils.seq - utilities for dealing with sequences
supybot.utils.str - utilities for dealing with strings (including our
new format() function)
supybot.utils.structures - general purpose structures used in Supybot
supybot.utils.web - utilities for dealing with the web (used to be
supybot.webutils)
Fourth, we've added source-nested plugins (using the class
callbacks.Commands). This allows you to group similar commands
together. Some examples are:
Channel.{ban add,ban list,ban remove}
User.{hostmask add,hostmask list,hostmask remove}
Fifth, we've removed the privmsgs module. All of the functionality
that was offered in that module is now available by using commands.wrap.
Use of this is documented at:
http://supybot.com/documentation/help/tutorial/wrap
Sixth, we've renamed some plugin-related API changes. Some classes had
their names changed. The old names are still available for
backwards-compatibility.
callbacks.Privmsg -> callbacks.Plugin
callbacks.PrivmsgCommandAndRegexp -> callbacks.PluginRegexp
callbacks.IrcObjectProxy -> callbacks.NestedCommandsIrcProxy
callbacks.PrivmsgRegexp was removed since its functionality is covered
by setting using PluginRegexp.
Also, the prototype for a plugin's __init__ method changed:
def __init__(self): -> def __init__(self, irc):
Remember to pass the irc object on when you call the parent class's
__init__ method.
Version 0.80.0
We *finally* hit 0.80.0! This release is completely compatible with
the last release candidate.
An update to Babelfish may cause an error message to be displayed in
the console when the bot is first run. The error message should be
gone when the bot is restarted.
We also have a new community website at http://www.supybot.com/ where
our users can submit their own plugins, view/download other people's
plugins and discuss all things Supybot-related.
Version 0.80.0rc3
Another bugfix release. This one was pretty important as it actually
makes supybot.database.plugins.channelSpecific work properly.
Version 0.80.0rc2
supybot.databases.plugins.channelSpecific.channel was renamed to
supybot.databases.plugins.channelSpecific.link.
supybot.databases.plugins.channelSpecific.link.allow was added, which
determines whether a channel will allow other channels to link to its
database.
Infobot is no longer deprecated and the following changes were made to
its config variables:
supybot.plugins.Infobot.answerUnaddressedQuestions was renamed to
supybot.plugins.Infobot.unaddressed.answerQuestions.
supybot.plugins.Infobot.snarfUnaddressedDefinitions was renamed to
supybot.plugins.Infobot.unaddressed.snarfDefinitions.
supybot.plugins.Infobot.unaddressed.replyExistingFactoid was added to
determine whether the bot will reply when someone attempts to create a
duplicate factoid.
Version 0.80.0pre6
Another bugfix release. No incompatibilities known. The only
registry change is that supybot.databases.users.hash has been
removed.
Version 0.80.0pre5
Completely bugfix release. No incompatibilies known.
Version 0.80.0pre4
Mainly a bug fix release. This will likely be the last release before
0.80.0final, but we're gonna let it stew for a couple weeks to attempt
to catch any lingering bugs.
ansycoreDrivers is now deprecated in favor of socketDrivers or
twistedDrivers.
supybot.databases.plugins.channelSpecific.channel is now a channelValue
so that you can link specific channels together (instead of all channels
being linked together).
For those of you that use eval and/or exec, they have been removed from
the Owner plugin and are now in sandbox/Debug.py (which you'll have to
grab from CVS).
Version 0.80.0pre3
The database format for the Note plugin has changed to a flatfile
format; use tools/noteConvert.py to convert it to the new format.
Ditto that for the URL database.
FunDB is deprecated and will be removed at the next major release;
use tools/fundbConvert.py to convert your old FunDB databases to Lart
and Praise databases.
If you had turned off supybot.databases.plugins.channelSpecific, your
non-channel-specific database files had gone directly into your data/
directory. We had some problems with poor interactions between that
configuration variable and channel capabilities, though, so we
changed the implementation so that non-channel-specific databases are
considered databases of a single (configurable) channel (defaulting
to "#"). This will also help others who are converting from
channel-specific to non-channel-specific databases, but for you
who've already made the switch, you'll need to move your database
files again, from data/ to data/# (or whatever channel you might
change that variable to).
supybot.channels doesn't exist anymore; now the only list of channels
to join is per-network, in supybot.networks.<network>.channels.
We weren't serializing supybot.replies.* properly in older versions.
Now we are, but the old, improperly serialized versions won't work
properly. Remove from your configuration file all variables
beginning with "supybot.replies" before you start the bot.
The URL database has been changed again, but it will use a different
filename so you shouldn't run into conflicts, just a newly-empty
database.
We upgraded the SOAP stuff in others; you may do well to do a
setup.py install --clean this time around.
Version 0.80.0pre2
Many more bugs have been fixed. A few more plugins have been updated
to use our new-style database abstraction. If it seems like your
databases are suddenly empty, look for a new database file named
Plugin.dbtype.db. We've also added a few more configuration variables.
Version 0.80.0pre1
Tons of bugs fixed, many features and plugins added. Everything
should be entirely compatible; many more configuration variables have
been added.
Version 0.79.9999
Some more bugs fixed, added a few features and a couple configuration
variabless. This should hopefully be the last release before 0.80.0,
which will finally bring us to pure Beta status.
Version 0.79.999
Some bugs fixed, but the ones that were fixed were pretty big. This
is, of course, completely compatible with the last release.
Version 0.79.99
Many bugs fixed, thanks to the users who reported them. We're
getting asymptotically closer to 0.80.0 -- maybe this'll be the last
one, maybe we'll have to release an 0.79.999 -- either way, we're
getting close :) Check out the ChangeLog for the fixes and a few new
features.
Version 0.79.9
We've changed so much stuff in this release that we've given up on
users upgrading their configuration files for the new release. So
do a clean install (python2.3 setup.py install --clean), run the
wizard again, and kick some butt.
(It's rumored that you can save most of your old configuration by
appending your new configuration at the end of your old configuration
and running supybot with that new configuration file. This, of
course, comes with no warranty or guarantee of utility -- try it if
you want, but backup your original configuration file!)
Version 0.77.2
This is a drop-in replacement for 0.77.1, with two exceptions. The
configuration variable formerly known as
"supybot.plugins.Services.password" is now known as
"supybot.plugins.Services.NickServ.password", due to the fact that
there might be different passwords for NickServ and ChanServ (and
ChanServ passwords are per-channel, whereas NickServ passwords are
global). If you're using the Services plugin, you'll need to make
this change in order to continue identifying with services. The
configuration variable formerly known as
"supybot.plugins.Babelfish.disabledLanguages" is now known as
"supybot.plugins.Babelfish.languages". The configuration variable now
accepts the languages that *will* be translated as opposed to ones
that are *not* translated.
Tests and the developer sandbox are not longer delivered with our
release tarballs. If you're a developer and you want these, you
should either check out CVS or download one of our weekly CVS
snapshots, available at http://supybot.sourceforge.net/snapshots/ .
Version 0.77.1
This is a drop-in replacement for 0.77.0 -- no incompatibilities, to
out knowledge. Simply install over your old installation and restart
your bot :)
Version 0.77.0
Setup.py will automatically remove your old installations for you, no
need to worry about that yourself.
Configuration has been *entirely* redone. Read the new
GETTING_STARTED document to see how to work with configuration
variables now. Your old botscripts from earlier versions *will not*
work with the new configuration method. We'd appreciate it if you'd
rerun the wizard in order for us to find any bugs that remain in it
before we officially declare ourselves Beta. Note also that because
of the new configuration method, the interface for plugins' configure
function has changed: there are no longer any onStart or afterConnect
arguments, so all configuration should be performed via the registry.
Channel capabilities have been changed; rather than being
#channel.capability, they're now #channel,capability. It's a bit
uglier, we know, but dots can be valid in channel names, and we
needed the dot for handling plugin.command capabilities.
tools/ircdbConvert.py should update this for you.
The on-disk format of the user/channel databases has changed to be far
more readable. A conversion utility is included, as mentioned before:
tools/ircdbConvert.py. Run this with no arguments to see the
directions for using it.
Uh, we were just kidding about the upgrade script in 0.76.0 :) It'll
be a little while longer. We do have several little upgrade scripts,
though.
Version 0.76.1
Almost entirely bugfixes, just some minor (and some less minor) bugs
that need to get in before we really start hacking on the next
version. Should be *entirely* compatible with 0.76.0.
Version 0.76.0
Major bugfix release. A great number of bugs fixed. This is the last
release without an upgrade script.
The only hiccup in the upgrade from 0.75.0 should be that you'll need
to update your botscript to reflect the removal of the debug module.
We'd rather you use supybot-wizard to generate a new botscript, of
course, but if you insist on modifying your existing botscript, take a
look at
<http://cvs.sourceforge.net/viewcvs.py/supybot/supybot/src/template.py?r1=1.20&r2=1.21>
to see what you need to do.
Version 0.75.0
Don't forget to reinstall (i.e., run "python setup.py install" as
root). Sometimes it even does good to remove the old installation;
$PYTHON/site-packages/supybot can be removed with no problems
whatsoever.
You will need to re-run supybot-wizard and generate a new botscript.
The Infobot plugin has been removed from this release; it's not ready
for prime time. If you're interested in getting it running (i.e., you
want full Infobot compatibility and aren't satisfied with either
MoobotFactoids or Factoids) then swing over to #supybot and we can
discuss the tests. We simply don't know enough about Infobot to make
sure our Infobot plugin is an exact replica, and need someone's help
with making the changes necessary for that.

View File

@ -1,71 +0,0 @@
---
layout: page
title: Ignoring RelayBot
permalink: /Relaybot.html
---
<!-- @format -->
RelayBot is the bot which relays between #supybot,#limnoria at a couple of
networks (TODO/FIXME, which ones?). It is currently using the
[LinkRelay](https://github.com/ProgVal/Supybot-plugins/tree/master/LinkRelay)
plugin to do this.
It's sometimes considered as annoyance as it has lately mostly spammed with join
(part messages aren't working, because of a bug (2014-06-23)) messages of people
who usually say nothing and this is why this page is here to tell how to ignore
it on various client.
We(who? I?) encourage you to ignore only notices from RelayBot instead of
everything as there are people whom should be heard at OFTC (mainly main Supybot
developer). (TODO/FIXME: is this the case in 2021?)
Related links:
- [LinkRelay plugin](https://github.com/ProgVal/Supybot-plugins/tree/master/LinkRelay)
- [Feature request for smart filtering of joins/quits/parts](https://github.com/ProgVal/Supybot-plugins/issues/66)
- [Feature request for RELAYMSG for more native look&feel](https://github.com/ProgVal/Supybot-plugins/issues/338)
Hostmask of RelayBot on Libera.Chat 2021-06-06:
- `RelayBot!~limnoria@helium.progval.net`
- This is absolute hostmask, also known as NUH (`nick!user@host`)
- `RelayBot*!*@helium.progval.net`
- This is recommended hostmask as it matches RelayBot even if it cannot use
it's primary nickname or networks cannot connect to it's identd.
## HexChat
From the "Window" menu you can find "Ignore list". Click "Add" and add one of
the hostmasks mentioned above (the lower is recommended).
Uncheck the other checkboxes than "Notice" and you can close the window and you
won't see spamming.
## KVIRC
I am not primarily KVIRC user and I cannot say anything else than right click
RelayBot and select something that matches only RelayBot.
**WARNING: KVIRC makes it very easy to also ignore pinkieval which you don't
want to do as they are author of Limnoria and help people often!**
## Linkinus
According to another person, there is a GUI where you can easily ignore notices
from specific hostmask.
## WeeChat
`/filter add relaybotnotices * irc_notice+nick_RelayBot *`
This creates a new filter with the name "relaybotnotices" which filters all
notices from the nickname "RelayBot".
---
This page is very likely missing many IRC clients. Could you
[open an issue](https://github.com/mikaela/limnoria/issues) about how to do this
with your IRC client that isn't mentioned here?
---

View File

@ -1,145 +0,0 @@
---
layout: page
title: Security issues
permalink: /Supybot.html
---
<!-- @format -->
Supybot git repository was declared dead on 2018-05-10 and archived on GitHub.
[v0.84.0 was the last release at that time](https://github.com/Supybot/Supybot/releases/tag/v0.84.0).
0.83.4.1 used to be a very common release available through several Linux
distributions for years and thus I made this page, which I guess is now
available more of for historical reasons.
**_WARNING: most of the content originates from 2014!_**
## The issues of 0.83.4.1.
### 1. Anyone can crash it and computer where it's running on
And this is very easy. Just run the command
`!misc last --regexp m/(.*\w){512}/`
where ! is the prefix character.
Misc is loaded by default and cannot be unloaded without modifying the config.
- [Limnoria issue #157](https://github.com/ProgVal/Limnoria/issues/157)
- Fixing commits:
[3526d5d](https://github.com/ProgVal/Limnoria/commit/3526d5dabf587457a43af8bee8d4db21986e8222)
&
[e11dc28](https://github.com/ProgVal/Limnoria/commit/e11dc28025de877b1b6cf059013eef88337b7e44)
- [Ubuntu bug #996947](https://bugs.launchpad.net/ubuntu/+source/supybot/+bug/996947)
- [Debian bug #672214](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=672214)
### 2. The previous wasn't the only way to do this
Everyone can also make the bot count an equation, which brings it and the host
computer down.
For example:
`!math calc factorial(999999)`
This requires Math plugin which comes with Supybot, but isn't load by default.
- [Limnoria issue #354](https://github.com/ProgVal/Limnoria/issues/354)
- Fixing commit:
[695078e](https://github.com/ProgVal/Limnoria/commit/695078edeb91e5ff1eec728fedf0e0c27b55c505)
- [Ubuntu bug #996950](https://bugs.launchpad.net/ubuntu/+source/supybot/+bug/996950)
- [Debian bug 672215](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=672215)
### 3. Anyone can access network services via the bot.
I don't have example command for this, but it happens by nesting "format cut"
and "misc tell".
What does this mean? Anyone can tell the bot to ghost someone else on same
account, take over a channel by telling the bot to give flags (if it has correct
flags), change password of the account and everything else what you do with
network services.
- _This was only reported at IRC and I am unable to find issue report or fixing
commit. ~~Mikaela on 2015-01-04._
### 4. Web page with special characters in \<title\> can be used to send DCC/CTCP commands.
This doesn't mean only things like CTCP actions (also known as /me), but known
problems with old routers ( `FF ? DCC SEND “ff???f??????????????” 0 0 0` ) which
make them reconnect to the internet.
Usage:
- `!web title <malicious.page.here>`
- `!web fetch <malicious.page.here>`
_This was only reported at IRC and I am unable to find issue report or fixing
commit. ~~Mikaela on 2015-01-04._
### 5. Web Titlte/Fetch can be used for DoS
They are vulnerable to queries to servers which have custom headers which can
lead to DoS.
_This was only reported at IRC and I am unable to find issue report or fixing
commit. ~~Mikaela on 2015-01-04._
### 6. QuoteGrabs grab command also works in PM
and can grab private content such as `user register` or `user identify` or with
the case of owner possibly NickServ passwords and others not so nice things.
- _It appears this issue was only reported at IRC._
- Fixing commit:
[a3346343679f3bdf8c77d9efb5a2097e215d51df](https://github.com/ProgVal/Limnoria/commit/a3346343679f3bdf8c77d9efb5a2097e215d51df)
### Are these issues publicly known?
**Of course they are.** Issue reports are below the actual issues.
The first issue has been also used to take down some of
[Ubuntu IRC bots](https://wiki.ubuntu.com/IRC/Bots) several times. At least
UbotX (I don't remember the number) and meetingology.
Some of these issues are fixed in git repository, but most people aren't using
it. If you wish to start using it, please scroll down to installation
instructions lower this page even though [Limnoria] and [gribble] are more
recommended.
### How to avoid them?
You can add anticapability for these commands using `owner defaultcapability`,
but that is only a temporary solution. There can also be other issues.
There are also two active Supybot forks, known as [Limnoria] and [Gribble],
which are actively developed and have fixed these issues. If you want permanent
solution, you should install either of them.
## Possibly interesting links
- [Comparsion of commit activity between Limnoria, Gribble and Supybot](https://www.openhub.net/p/compare?project_0=Limnoria&project_1=Gribble%3A+Support+Bottie&project_2=Supybot).
- [Gribble's modifications to stock Supybot](https://sourceforge.net/p/gribble/wiki/Gribble_Project_Git_Repository/)
- [Limnoria's modifications to Gribble.](https://github.com/ProgVal/Limnoria/wiki/LGC)
- Features of Gribble are fully merged to Limnoria.
Your current botname.conf is **100% compatible with forks**.
[Join Supybot channels on LiberaChat!](ircs://irc.libera.chat:6697/#supybot,#gribble,#limnoria)
[Limnoria]: https://github.com/ProgVal/Limnoria
[Gribble]: http://github.com/nanotube/supybot_fixes
## Installing forks
_This section has been removed in order to not duplicate
[Limnoria's documentation.](http://doc.supybot.aperio.fr/en/latest/use/install.html)_
---
Do you know issue that isn't mentioned here? If it's not already reported,
please report it
on [Limnoria's issue tracker.](https://github.com/ProgVal/Limnoria/issues) If
it's known, but just not reported here,
[please feel free to add it.](https://github.com/Mikaela/limnoria/edit/gh-pages/Supybot.markdown)

View File

@ -1,38 +0,0 @@
# @format
theme: minima
title: Mikaela's Supybot site
tagline: Things official documentation may not tell you
author:
name: "Aminda Suomalainen"
url: "https://aminda.eu/"
description: > # this means to ignore newlines until "baseurl:"
Mikaela's Supybot site where nowadays the only content is security issues of
stock Supybot.
baseurl: "" # the subpath of your site, e.g. /blog/
url: "https://supybot.mikaela.info/" # the base hostname & protocol for your site
github_username: Mikaela
lang: en
timezone: Etc/UTC
encoding: utf-8
plugins:
# - jekyll-mentions
- jekyll-redirect-from
- jekyll-sitemap
- jekyll-seo-tag
sitemap:
file: "/sitemap.xml"
include: [robots.txt]
robots: nofollow, noai
icon: https://github.com/ProgVal/Supybot-website/raw/master/static/logo.png
markdown: kramdown
kramdown:
parse_block_html: true
#webmaster_verifications:
#google:
#bing:
defaults:
- scope:
path: "*"
values:
image: https://github.com/ProgVal/Supybot-website/raw/master/static/logo.png

View File

@ -1,32 +0,0 @@
<footer class="site-footer h-card">
<data class="u-url" href="{{ "/" | relative_url }}"></data>
<div class="wrapper">
<h2 class="footer-heading">{{ site.title | escape }}</h2>
<div class="footer-col-wrapper">
<div class="footer-col footer-col-1">
<ul class="contact-list">
<li class="p-name">
<a rel="me prefetch" href="{{ site.author.url }}">{{ site.author.name }}</a><br>
{{ site.title | escape }}
</li>
{%- if site.email -%}
<li><a class="u-email" href="mailto:{{ site.email }}">{{ site.email }}</a></li>
{%- endif -%}
</ul>
</div>
<div class="footer-col footer-col-2">
{%- include social.html -%}
</div>
<div class="footer-col footer-col-3">
<p>{{- site.description | escape -}}</p>
</div>
</div>
</div>
</footer>

View File

@ -1,15 +0,0 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!---->
{%- seo -%}
<!---->
<meta name="robots" content="{% if page.robots %}{{ page.robots }} {% else %}{{ site.robots }} {% endif %}">
<link rel="icon" href="https://github.com/ProgVal/Supybot-website/raw/master/static/logo.png">
<meta name="color-scheme" content="dark light">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#ffb700">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fdfdfd">
<link rel="stylesheet" href="{{ "/assets/main.css" | prepend: site.baseurl }}">
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}">
</head>

View File

@ -1,210 +0,0 @@
:root {
color-scheme: dark light;
}
/**
* Reset some basic elements
*/
body, h1, h2, h3, h4, h5, h6,
p, blockquote, pre, hr,
dl, dd, ol, ul, figure {
margin: 0;
padding: 0;
}
/**
* Basic styling
*/
body {
font-family: $base-font-family;
font-size: $base-font-size;
line-height: $base-line-height;
font-weight: 300;
color: $text-color;
background-color: $background-color;
-webkit-text-size-adjust: 100%;
}
/**
* Set `margin-bottom` to maintain vertical rhythm
*/
h1, h2, h3, h4, h5, h6,
p, blockquote, pre,
ul, ol, dl, figure,
%vertical-rhythm {
margin-bottom: $spacing-unit / 2;
}
/**
* Images
*/
img {
max-width: 100%;
vertical-align: middle;
}
/**
* Figures
*/
figure > img {
display: block;
}
figcaption {
font-size: $small-font-size;
}
/**
* Lists
*/
ul, ol {
margin-left: $spacing-unit;
}
li {
> ul,
> ol {
margin-bottom: 0;
}
}
/**
* Headings
*/
h1, h2, h3, h4, h5, h6 {
font-weight: 300;
}
/**
* Links
*/
a {
color: $brand-color;
//text-decoration: none;
text-decoration: underline;
&:visited {
//color: darken($brand-color, 15%);
}
&:hover {
color: $text-color;
//text-decoration: underline;
}
}
/**
* Blockquotes
*/
blockquote {
color: $grey-color;
border-left: 4px solid $grey-color-light;
padding-left: $spacing-unit / 2;
font-size: 18px;
letter-spacing: -1px;
font-style: italic;
> :last-child {
margin-bottom: 0;
}
}
/**
* Code formatting
*/
pre,
code {
font-family: $monospace-font-family;
font-size: 15px;
border: 1px solid; //$grey-color-light;
border-radius: 3px;
background-color: revert; //#eef;
}
code {
padding: 1px 5px;
}
pre {
padding: 8px 12px;
overflow-x: scroll;
> code {
border: 0;
padding-right: 0;
padding-left: 0;
}
}
/**
* Wrapper
*/
.wrapper {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
margin-right: auto;
margin-left: auto;
padding-right: $spacing-unit;
padding-left: $spacing-unit;
@extend %clearfix;
@include media-query($on-laptop) {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
max-width: calc(#{$content-width} - (#{$spacing-unit}));
padding-right: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
}
}
/**
* Clearfix
*/
%clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
/**
* Icons
*/
.icon {
> svg {
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle;
path {
fill: $grey-color;
}
}
}

View File

@ -1,236 +0,0 @@
/**
* Site header
*/
.site-header {
border-top: 5px solid $grey-color-dark;
border-bottom: 1px solid $grey-color-light;
min-height: 56px;
// Positioning context for the mobile navigation icon
position: relative;
}
.site-title {
font-size: 26px;
line-height: 56px;
letter-spacing: -1px;
margin-bottom: 0;
float: left;
&,
&:visited {
color: $grey-color-dark;
}
}
.site-nav {
float: right;
line-height: 56px;
.menu-icon {
display: none;
}
.page-link {
color: $text-color;
line-height: $base-line-height;
// Gaps between nav items, but not on the first one
&:not(:first-child) {
margin-left: 20px;
}
}
@include media-query($on-palm) {
position: absolute;
top: 9px;
right: 30px;
background-color: $background-color;
border: 1px solid $grey-color-light;
border-radius: 5px;
text-align: right;
.menu-icon {
display: block;
float: right;
width: 36px;
height: 26px;
line-height: 0;
padding-top: 10px;
text-align: center;
> svg {
width: 18px;
height: 15px;
path {
fill: $grey-color-dark;
}
}
}
.trigger {
clear: both;
display: none;
}
&:hover .trigger {
display: block;
padding-bottom: 5px;
}
.page-link {
display: block;
padding: 5px 10px;
}
}
}
/**
* Site footer
*/
.site-footer {
border-top: 1px solid $grey-color-light;
padding: $spacing-unit 0;
}
.footer-heading {
font-size: 18px;
margin-bottom: $spacing-unit / 2;
}
.contact-list,
.social-media-list {
list-style: none;
margin-left: 0;
}
.footer-col-wrapper {
font-size: 15px;
color: $grey-color;
margin-left: -$spacing-unit / 2;
@extend %clearfix;
}
.footer-col {
float: left;
margin-bottom: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
}
.footer-col-1 {
width: -webkit-calc(35% - (#{$spacing-unit} / 2));
width: calc(35% - (#{$spacing-unit} / 2));
}
.footer-col-2 {
width: -webkit-calc(20% - (#{$spacing-unit} / 2));
width: calc(20% - (#{$spacing-unit} / 2));
}
.footer-col-3 {
width: -webkit-calc(45% - (#{$spacing-unit} / 2));
width: calc(45% - (#{$spacing-unit} / 2));
}
@include media-query($on-laptop) {
.footer-col-1,
.footer-col-2 {
width: -webkit-calc(50% - (#{$spacing-unit} / 2));
width: calc(50% - (#{$spacing-unit} / 2));
}
.footer-col-3 {
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
width: calc(100% - (#{$spacing-unit} / 2));
}
}
@include media-query($on-palm) {
.footer-col {
float: none;
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
width: calc(100% - (#{$spacing-unit} / 2));
}
}
/**
* Page content
*/
.page-content {
padding: $spacing-unit 0;
}
.page-heading {
font-size: 20px;
}
.post-list {
margin-left: 0;
list-style: none;
> li {
margin-bottom: $spacing-unit;
}
}
.post-meta {
font-size: $small-font-size;
color: $grey-color;
}
.post-link {
display: block;
font-size: 24px;
}
/**
* Posts
*/
.post-header {
margin-bottom: $spacing-unit;
}
.post-title {
font-size: 42px;
letter-spacing: -1px;
line-height: 1;
@include media-query($on-laptop) {
font-size: 36px;
}
}
.post-content {
margin-bottom: $spacing-unit;
h2 {
font-size: 32px;
@include media-query($on-laptop) {
font-size: 28px;
}
}
h3 {
font-size: 26px;
@include media-query($on-laptop) {
font-size: 22px;
}
}
h4 {
font-size: 20px;
@include media-query($on-laptop) {
font-size: 18px;
}
}
}

View File

@ -1,67 +0,0 @@
/**
* Syntax highlighting styles
*/
.highlight {
background: #fff;
@extend %vertical-rhythm;
.c { color: #998; font-style: italic } // Comment
.err { color: #a61717; background-color: #e3d2d2 } // Error
.k { font-weight: bold } // Keyword
.o { font-weight: bold } // Operator
.cm { color: #998; font-style: italic } // Comment.Multiline
.cp { color: #999; font-weight: bold } // Comment.Preproc
.c1 { color: #998; font-style: italic } // Comment.Single
.cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special
.gd { color: #000; background-color: #fdd } // Generic.Deleted
.gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific
.ge { font-style: italic } // Generic.Emph
.gr { color: #a00 } // Generic.Error
.gh { color: #999 } // Generic.Heading
.gi { color: #000; background-color: #dfd } // Generic.Inserted
.gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific
.go { color: #888 } // Generic.Output
.gp { color: #555 } // Generic.Prompt
.gs { font-weight: bold } // Generic.Strong
.gu { color: #aaa } // Generic.Subheading
.gt { color: #a00 } // Generic.Traceback
.kc { font-weight: bold } // Keyword.Constant
.kd { font-weight: bold } // Keyword.Declaration
.kp { font-weight: bold } // Keyword.Pseudo
.kr { font-weight: bold } // Keyword.Reserved
.kt { color: #458; font-weight: bold } // Keyword.Type
.m { color: #099 } // Literal.Number
.s { color: #d14 } // Literal.String
.na { color: #008080 } // Name.Attribute
.nb { color: #0086B3 } // Name.Builtin
.nc { color: #458; font-weight: bold } // Name.Class
.no { color: #008080 } // Name.Constant
.ni { color: #800080 } // Name.Entity
.ne { color: #900; font-weight: bold } // Name.Exception
.nf { color: #900; font-weight: bold } // Name.Function
.nn { color: #555 } // Name.Namespace
.nt { color: #000080 } // Name.Tag
.nv { color: #008080 } // Name.Variable
.ow { font-weight: bold } // Operator.Word
.w { color: #bbb } // Text.Whitespace
.mf { color: #099 } // Literal.Number.Float
.mh { color: #099 } // Literal.Number.Hex
.mi { color: #099 } // Literal.Number.Integer
.mo { color: #099 } // Literal.Number.Oct
.sb { color: #d14 } // Literal.String.Backtick
.sc { color: #d14 } // Literal.String.Char
.sd { color: #d14 } // Literal.String.Doc
.s2 { color: #d14 } // Literal.String.Double
.se { color: #d14 } // Literal.String.Escape
.sh { color: #d14 } // Literal.String.Heredoc
.si { color: #d14 } // Literal.String.Interpol
.sx { color: #d14 } // Literal.String.Other
.sr { color: #009926 } // Literal.String.Regex
.s1 { color: #d14 } // Literal.String.Single
.ss { color: #990073 } // Literal.String.Symbol
.bp { color: #999 } // Name.Builtin.Pseudo
.vc { color: #008080 } // Name.Variable.Class
.vg { color: #008080 } // Name.Variable.Global
.vi { color: #008080 } // Name.Variable.Instance
.il { color: #099 } // Literal.Number.Integer.Long
}

View File

@ -1,145 +0,0 @@
---
# front-matter
---
@charset "utf-8";
// Font specifications. I keep changing my mind on what are the most pleasant
// fonts to my eyes, so I won't bother commenting them here.'
$serif-font-family:
ui-serif, "Roboto Serif", "Noto Serif", Tinos, serif, "Noto Emoji",
"Noto Color Emoji", "Segoe UI Emoji", emoji;
$sans-serif-font-family:
"Inclusive Sans", ui-sans-serif, "Roboto Flex", "Segoe UI Variable", Roboto,
"Noto Sans", Arimo, sans-serif, "Noto Emoji", "Noto Color Emoji",
"Segoe UI Emoji", emoji;
$monospace-font-family:
"Comic Shanns Mono", ui-monospace, "Roboto Mono", "Segoe UI Mono",
"Noto Mono", Cousine, monospace, "Noto Emoji", "Noto Color Emoji",
"Segoe UI Emoji", emoji;
// Must be in the end under threat of undefined variable error.
$base-font-family: $sans-serif-font-family;
@font-face {
font-family: "Inclusive Sans";
src: url("https://raw.githubusercontent.com/LivKing/Inclusive-Sans/refs/heads/main/fonts/webfonts/InclusiveSans[wght].woff2")
format("woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Inclusive Sans Italic";
src: url("https://raw.githubusercontent.com/LivKing/Inclusive-Sans/refs/heads/main/fonts/webfonts/InclusiveSans-Italic[wght].woff2")
format("woff2");
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: "Comic Shanns Mono";
src: url("https://raw.githubusercontent.com/jesusmgg/comic-shanns-mono/refs/heads/master/fonts/ComicShannsMono-Regular.otf")
format("opentype");
}
@import "{{ site.theme }}";
:root {
color-scheme: dark light !important;
}
* {
// box-sizing: border-box !important;
color: revert !important;
background-color: revert !important;
//margin: auto !important;
// line-height: 1.2 !important;
// A4 paper
//max-width: 210mm !important;
font-size: revert;
//padding: auto !important;
overflow-wrap: break-word !important;
hyphens: auto !important;
// Experimental trick to make all emojis text if supported.
//font-variant-emoji: text;
/* WCAG minimum suggestions */
margin-bottom: 2 !important;
line-height: 1.5 !important;
letter-spacing: 0.12 !important;
word-spacing: 0.16 !important;
}
a {
text-decoration: underline !important;
}
a.site-title {
font-family: $serif-font-family;
}
// Monospace preferred for code
code,
pre {
font-family: $monospace-font-family !important;
}
// So it will not look bigger than normal text
code {
font-size: 0.8em !important;
}
// Sans-Serif for headings to constrast with aminda.eu
// h2,
// h3,
// h4,
// h5,
// h6 {
// font-family: $sans-serif-font-family !important;
// }
h2.footer-heading {
font-family: $sans-serif-font-family !important;
}
// The introduction on top
#bio {
text-align: center;
font-style: italic;
font-family: ui-cursive, $sans-serif-font-family;
}
img {
border-radius: 50% !important;
display: block;
margin-left: auto !important;
margin-right: auto !important;
@media (min-width: 395px) {
display: float !important;
float: right !important;
}
}
ul.linklist {
list-style: none inside;
font-family: $monospace-font-family;
//font-variant: small-caps;
}
// Customize the dark theme to be more me
@media (prefers-color-scheme: dark) {
* {
color: #ffb700 !important;
border-color: #ffb700 !important;
background-color: #000000 !important;
}
.site-nav {
color-scheme: only dark !important;
color: #ffb700 !important;
background-color: #000000 !important;
color: #ffb700 !important;
}
// I don't want links to be restored to amber'
a {
color: revert !important;
}
}

View File

@ -1,30 +0,0 @@
---
layout: null
---
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ site.title | xml_escape }}</title>
<description>{{ site.description | xml_escape }}</description>
<link>{{ site.url }}{{ site.baseurl }}/</link>
<atom:link href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" rel="self" type="application/rss+xml"/>
<pubDate>{{ site.time | date_to_rfc822 }}</pubDate>
<lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>
<generator>Jekyll v{{ jekyll.version }}</generator>
{% for post in site.posts limit:10 %}
<item>
<title>{{ post.title | xml_escape }}</title>
<description>{{ post.content | xml_escape }}</description>
<pubDate>{{ post.date | date_to_rfc822 }}</pubDate>
<link>{{ post.url | prepend: site.baseurl | prepend: site.url }}</link>
<guid isPermaLink="true">{{ post.url | prepend: site.baseurl | prepend: site.url }}</guid>
{% for tag in post.tags %}
<category>{{ tag | xml_escape }}</category>
{% endfor %}
{% for cat in post.categories %}
<category>{{ cat | xml_escape }}</category>
{% endfor %}
</item>
{% endfor %}
</channel>
</rss>

View File

@ -1,26 +0,0 @@
---
layout: default
---
<!-- @format -->
**_WARNING: most of the content on this site originates from 2014!_**
Welcome to Mikaela's Supybot pages.
This site isn't official and won't help with most of issues. In case you are
looking for the official sites, they are here:
- [Limnoria's website](https://limnoria.net/)
- [Limnoria official documentation](https://docs.limnoria.net/)
- [Supybook](https://hoxu.github.io/supybook/devel/)
- [Gribble Wiki](https://sourceforge.net/p/gribble/wiki/Main_Page/)
I also have
[something in my gist repo](https://gitea.blesmrt.net/mikaela/gist/src/branch/master/irc/limnoria/)
at 2021-06-11 16:07 UTC
[opinionated titlefetching instructions](https://gitea.blesmrt.net/mikaela/gist/src/branch/master/irc/limnoria/titlefetching.md).
If you cannot find what you are looking for from them, please come to IRC and
ask. The Support channels are
[#supybot,#limnoria on irc.libera.chat](ircs://irc.libera.chat:6697/%23supybot%2c%23limnoria)

0
locales/__init__.py Normal file
View File

1137
locales/de.po Normal file

File diff suppressed because it is too large Load Diff

2002
locales/fi.po Normal file

File diff suppressed because it is too large Load Diff

2046
locales/fr.po Normal file

File diff suppressed because it is too large Load Diff

100
locales/fr.py Normal file
View File

@ -0,0 +1,100 @@
# -*- encoding: utf8 -*-
###
# Copyright (c) 2010, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
Supybot utility functions localization in French.
"""
def pluralize(s):
"""Returns the plural of s.
"""
lowered = s.lower()
if lowered.endswith('ou') and \
lowered in ['bijou', 'caillou', 'chou', 'genou', 'hibou', 'joujou',
'pou']:
return s + 'x'
elif lowered.endswith('al') and \
lowered not in ['bal', 'carnaval', 'chacal', 'festival', 'récital',
'régal', 'cal', 'étal', 'aval', 'caracal', 'val', 'choral',
'corral', 'galgal', 'gayal']:
return s[0:-2] + 'aux'
elif lowered.endswith('ail') and \
lowered not in ['bail', 'corail', 'émail', 'soupirail', 'travail',
'ventail', 'vitrail', 'aspirail', 'fermail']:
return s[0:-3] + 'aux'
elif lowered.endswith('eau'):
return s + 'x'
elif lowered == 'pare-feu':
return s
elif lowered.endswith('eu') and \
lowered not in ['bleu', 'pneu', 'émeu', 'enfeu']:
# Note: when 'lieu' is a fish, it has a 's' ; else, it has a 'x'
return s + 'x'
else:
return s + 's'
def depluralize(s):
"""Returns the singular of s."""
lowered = s.lower()
if lowered.endswith('aux') and \
lowered in ['baux', 'coraux', 'émaux', 'soupiraux', 'travaux',
'ventaux', 'vitraux', 'aspiraux', 'fermaux']:
return s[0:-3] + 'ail'
elif lowered.endswith('aux'):
return s[0:-3] + 'al'
else:
return s[0:-1]
def ordinal(i):
"""Returns i + the ordinal indicator for the number.
Example: ordinal(3) => '3ème'
"""
i = int(i)
if i == 1:
return '1er'
else:
return '%sème' % i
def be(i):
"""Returns the form of the verb 'être' based on the number i."""
# Note: this function is used only for the third person
if i == 1:
return 'est'
else:
return 'sont'
def has(i):
"""Returns the form of the verb 'avoir' based on the number i."""
# Note: this function is used only for the third person
if i == 1:
return 'a'
else:
return 'ont'

1405
locales/it.po Normal file

File diff suppressed because it is too large Load Diff

1457
locales/messages.pot Normal file

File diff suppressed because it is too large Load Diff

42
man/supybot-adduser.1 Normal file
View File

@ -0,0 +1,42 @@
.\" Process this file with
.\" groff -man -Tascii supybot-adduser.1
.\"
.TH SUPYBOT-ADDUSER 1 "APRIL 2005"
.SH NAME
supybot-adduser \- Adds a user to a Supybot users.conf file
.SH SYNOPSIS
.B supybot-adduser
.RI [ options ] " users.conf
.SH DESCRIPTION
.B supybot-adduser
adds a user to the specified users.conf file.
.SH OPTIONS
.TP
.B \-\^\-version
Show version of program.
.TP
.BR \-h ", " \-\^\-help
Show summary of options.
.TP
.BR \-u " NAME" "\fR,\fP \-\^\-username=" NAME
Specifies the username to use for the new user.
.TP
.BR \-p " PASSWORD" "\fR,\fP \-\^\-password=" PASSWORD
Specifies the password to use for the new user.
.TP
.BR \-c " CAPABILITY" "\fR,\fP \-\^\-capability=" CAPABILITY
Capability the user should have; this option may be given
multiple times.
.SH "SEE ALSO"
.IR python (1),
.IR supybot (1),
.IR supybot-test (1),
.IR supybot-botchk (1),
.IR supybot-wizard (1),
.IR supybot-plugin-doc (1),
.IR supybot-plugin-create (1)
.SH AUTHOR
This manual page was originally written by James McCoy
<vega dot james at gmail dot com>. Permission is granted to copy,
distribute and/or modify this document under the terms of the Supybot
license, a BSD-style license.

54
man/supybot-botchk.1 Normal file
View File

@ -0,0 +1,54 @@
.\" Process this file with
.\" groff -man -Tascii supybot-botchk.1
.\"
.TH SUPYBOT-BOTCHK 1 "APRIL 2005"
.SH NAME
supybot-botchk \- A script to start Supybot if it's not already running.
.SH SYNOPSIS
.B supybot-botchk
.RI [ options ]
.SH DESCRIPTION
.B supybot-botchk
is a script that will start Supybot if it detects that one is not currently
running. This can be useful for scheduling
.IR supybot (1)
to run via
.IR cron (8).
.SH OPTIONS
.TP
.BR \-h ", " \-\^\-help
Show summary of options.
.TP
.BR \-v ", " \-\^\-verbose
Use verbose output when running the script.
.TP
.BI \-\^\-botdir= BOTDIR
Determines which directory the bot be started in.
.TP
.BI \-\^\-pidfile= PIDFILE
Specifies the name of the pidfile to look for. This should be relative
to the given botdir.
.TP
.BI \-\^\-supybot= SUPYBOT
Specifies the location of
.IR supybot (1).
If this is not given, it is assumed that
.IR supybot (1)
is in the user's $PATH.
.TP
.BI \-\^\-conffile= CONFFILE
Specifies the path to the bot's configuration file. This will be used
when (re)starting the bot.
.SH "SEE ALSO"
.IR python (1),
.IR supybot (1),
.IR supybot-test (1),
.IR supybot-wizard (1),
.IR supybot-adduser (1),
.IR supybot-plugin-doc (1),
.IR supybot-plugin-create (1)
.SH AUTHOR
This manual page was originally written by James McCoy
<vega dot james at gmail dot com>. Permission is granted to copy,
distribute and/or modify this document under the terms of the Supybot
license, a BSD-style license.

View File

@ -0,0 +1,43 @@
.\" Process this file with
.\" groff -man -Tascii supybot-plugin-create.1
.\"
.TH SUPYBOT-PLUGIN-CREATE 1 "APRIL 2005"
.SH NAME
supybot-plugin-create \- A wizard for creating Supybot plugins
.SH SYNOPSIS
.B supybot-plugin-create
.RI [ options ]
.SH DESCRIPTION
.B supybot-plugin-create
is a wizard that creates a template python source file for a new
.IR supybot (1)
plugin.
.SH OPTIONS
.TP
.B \-\^\-version
Show version of program.
.TP
.BR \-h ", " \-\^\-help
Show summary of options.
.TP
.BI \-n " NAME" "\fR,\fP \-\^\-name=" NAME
Sets the name for the plugin.
.TP
.BR \-t ", " \-\^\-thread
Makes the plugin threaded.
.TP
.BI \-\^\-real\-name= REALNAME
Specify what real name the copyright is assigned to.
.SH "SEE ALSO"
.IR python (1),
.IR supybot (1),
.IR supybot-test (1),
.IR supybot-botchk (1),
.IR supybot-wizard (1),
.IR supybot-adduser (1),
.IR supybot-plugin-doc (1)
.SH AUTHOR
This manual page was originally written by James McCoy
<vega dot james at gmail dot com>. Permission is granted to copy,
distribute and/or modify this document under the terms of the Supybot
license, a BSD-style license.

48
man/supybot-plugin-doc.1 Normal file
View File

@ -0,0 +1,48 @@
.\" Process this file with
.\" groff -man -Tascii supybot-plugin-doc.1
.\"
.TH SUPYBOT-PLUGIN-DOC 1 "May 2009"
.SH NAME
supybot-plugin-doc \- Generates the documentation for a Supybot plugin
.SH SYNOPSIS
.B supybot-plugin-doc
.RI [ options ]
.SH DESCRIPTION
.B supybot-plugin-doc
is used to generate documentation (StructuredText or reStructuredText format)
for a
.IR supybot (1)
plugin.
.SH OPTIONS
.TP
.B \-\^\-version
Show version of program.
.TP
.BR \-h ", " \-\^\-help
Show summary of options.
.TP
.BR \-c ", " \-\^\-clean
Clean the various data/conf/log directories after generating the docs.
.TP
.BR \-o ", " \-\^\-output\-dir= \fIOUTPUTDIR
Specifies the directory in which to write the documentation for the plugin.
.TP
.BR \-f ", " \-\^\-format= \fIFORMAT
Specifies which output format to use. Choices are 'rst' or 'stx'.
.TP
.BI \-\^\-plugins\-dir= PLUGINSDIRS
Looks in the given directory for plugins and generates documentation for all of
them.
.SH "SEE ALSO"
.IR python (1),
.IR supybot (1),
.IR supybot-test (1),
.IR supybot-botchk (1),
.IR supybot-wizard (1),
.IR supybot-adduser (1),
.IR supybot-plugin-create (1)
.SH AUTHOR
This manual page was originally written by James McCoy
<vega dot james at gmail dot com>. Permission is granted to copy,
distribute and/or modify this document under the terms of the Supybot
license, a BSD-style license.

51
man/supybot-test.1 Normal file
View File

@ -0,0 +1,51 @@
.\" Process this file with
.\" groff -man -Tascii supybot-test.1
.\"
.TH SUPYBOT-TEST 1 "OCTOBER 2005"
.SH NAME
supybot-test \- Runs the test suite for a Supybot plugin
.SH SYNOPSIS
.B supybot-test
.RI [ options ] " plugins
.SH DESCRIPTION
.B supybot-test
Runs the test suite for a Supybot plugin
.SH OPTIONS
.TP
.B \-\^\-version
Show version of program.
.TP
.BR \-h ", " \-\^\-help
Show summary of options.
.TP
.BR \-c ", " \-\^\-clean
Cleans the various data/conf/logs directories before running tests.
.TP
.BR \-t " TIMEOUT" "\fR,\fP \-\^\-timeout=" TIMEOUT
Specifies the timeout for tests to return responses.
.TP
.BR \-v ", " \-\^\-verbose
Sets the verbose flag, logging extra information about each test that runs.
.TP
.BR \-\^\-no\-network
Prevents the network-based tests from being run.
.TP
.BR \-\^\-trace
Traces all calls made. Unless you're really in a pinch, you probably
shouldn't do this; it results in copious amounts of output.
.TP
.BR "\fR,\fP \-\^\-plugins\-dir=" PLUGINSDIR
Looks in the given directory for plugins and loads the tests for all of them.
.SH "SEE ALSO"
.IR python (1),
.IR supybot (1),
.IR supybot-botchk (1),
.IR supybot-wizard (1),
.IR supybot-adduser (1),
.IR supybot-plugin-doc (1),
.IR supybot-plugin-create (1)
.SH AUTHOR
This manual page was originally written by James McCoy
<vega dot james at gmail dot com>. Permission is granted to copy,
distribute and/or modify this document under the terms of the Supybot
license, a BSD-style license.

42
man/supybot-wizard.1 Normal file
View File

@ -0,0 +1,42 @@
.\" Process this file with
.\" groff -man -Tascii supybot-wizard.1
.\"
.TH SUPYBOT-WIZARD 1 "SEPTEMBER 2004"
.SH NAME
supybot-wizard \- A wizard for creating Supybot configuration files
.SH SYNOPSIS
.B supybot-wizard
.RI [ options ]
.SH DESCRIPTION
.B supybot-wizard
is an in-depth wizard that provides a nice user interface for creating
configuration files for
.IR supybot (1).
.SH OPTIONS
.TP
.B \-\^\-version
Show version of program.
.TP
.BR \-h ", " \-\^\-help
Show summary of options.
.TP
.B \-\^\-allow\-root
Determines whether the wizard will be allowed to run as root. You do not
want this. Do not do it. Even if you think you want it, you do not.
.TP
.B \-\^\-no\-network
Determines whether the wizard will be allowed to run without a network
connection.
.SH "SEE ALSO"
.IR python (1),
.IR supybot (1),
.IR supybot-test (1),
.IR supybot-botchk (1),
.IR supybot-adduser (1),
.IR supybot-plugin-doc (1),
.IR supybot-plugin-create (1)
.SH AUTHOR
This manual page was originally written by James McCoy
<vega dot james at gmail dot com>. Permission is granted to copy,
distribute and/or modify this document under the terms of the Supybot
license, a BSD-style license.

66
man/supybot.1 Normal file
View File

@ -0,0 +1,66 @@
.\" Process this file with
.\" groff -man -Tascii supybot.1
.\"
.TH SUPYBOT 1 "JULY 2009"
.SH NAME
supybot - A robust and user friendly Python IRC bot
.SH SYNOPSIS
.B supybot
.RI [ options ] " configFile
.SH DESCRIPTION
.B Supybot
is a robust, user-friendly, and programmer-friendly Python IRC bot.
It aims to be an adequate replacement for most existing IRC bots. It
includes a very flexible and powerful ACL system for controlling access
to commands, as well as more than 50 builtin plugins providing around
400 actual commands.
.SH OPTIONS
.TP
.B \-\^\-version
Show version of program.
.TP
.BR \-h ", " \-\^\-help
Show summary of options.
.TP
.BR \-P ", " \-\^\-profile
Enable profiling.
.TP
.BI \-n " NICK" "\fR,\fP \-\^\-nick=" NICK
Nick the bot should use.
.TP
.BI \-u " USER" "\fR,\fP \-\^\-user=" USER
Full username the bot should use.
.TP
.BI \-i " IDENT" "\fR,\fP \-\^\-ident=" IDENT
Ident the bot should use.
.TP
.BR \-d ", " \-\^\-daemon
Determines whether the bot will daemonize. This is a no-op on
non-POSIX systems.
.TP
.B \-\^\-allow\-default\-owner
Determines whether the bot will allow its defaultCapabilities not to
include "\-owner", thus giving all users the owner capability by
default. This is dumb, hence we require a command-line option to
enable it.
.TP
.B \-\^\-allow\-root
Determines whether the bot will be allowed to run as root. You do not
want this. Do not do it. Even if you think you want it, you do not.
.TP
.B \-\^\-debug
Determines whether some extra debugging stuff will be logged by this
script.
.SH "SEE ALSO"
.IR python (1),
.IR supybot-test (1),
.IR supybot-botchk (1),
.IR supybot-wizard (1),
.IR supybot-adduser (1),
.IR supybot-plugin-doc (1),
.IR supybot-plugin-create (1)
.SH AUTHOR
This manual page was originally written by James McCoy
<vega dot james at gmail dot com>. Permission is granted to copy,
distribute and/or modify this document under the terms of the Supybot
license, a BSD-style license.

View File

@ -1,14 +0,0 @@
{
"devDependencies": {
"@aminda/global-prettier-config": "2025.15.0",
"@prettier/plugin-ruby": "4.0.4",
"@prettier/plugin-xml": "3.4.1",
"corepack": "latest",
"prettier": "3.5.3",
"prettier-plugin-nginx": "1.0.3",
"prettier-plugin-sh": "0.17.1",
"prettier-plugin-toml": "2.0.4"
},
"packageManager": "pnpm@10.8.0+sha512.0e82714d1b5b43c74610193cb20734897c1d00de89d0e18420aebc5977fa13d780a9cb05734624e81ebd81cc876cd464794850641c48b9544326b5622ca29971",
"prettier": "@aminda/global-prettier-config"
}

58
plugins/Admin/__init__.py Normal file
View File

@ -0,0 +1,58 @@
###
# Copyright (c) 2004-2005, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
These are commands useful for administrating the bot; they all require their
caller to have the 'admin' capability. This plugin is loaded by default.
"""
import supybot
import supybot.world as world
__author__ = supybot.authors.jemfinch
__maintainer__ = supybot.authors.limnoria_core
# Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you\'re keeping the plugin in CVS or some similar system.
__version__ = "%%VERSION%%"
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
from . import config
from . import plugin
from importlib import reload
reload(plugin) # In case we're being reloaded.
if world.testing:
from . import test
Class = plugin.Class
configure = config.configure

46
plugins/Admin/config.py Normal file
View File

@ -0,0 +1,46 @@
###
# Copyright (c) 2004-2005, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.conf as conf
import supybot.registry as registry
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Admin')
def configure(advanced):
# This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified themself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Admin', True)
Admin = conf.registerPlugin('Admin')
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

241
plugins/Admin/locales/de.po Normal file
View File

@ -0,0 +1,241 @@
msgid ""
msgstr ""
"Project-Id-Version: Supybot\n"
"POT-Creation-Date: 2014-08-17 13:46+CEST\n"
"PO-Revision-Date: 2011-10-31 13:37+0100\n"
"Last-Translator: Florian Besser <fbesser@gmail.com>\n"
"Language-Team: German <fbesser@gmail.com>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Language: German\n"
"X-Poedit-Country: Germany\n"
#: plugin.py:54
msgid "Nick/channel temporarily unavailable."
msgstr "Nick/Kanal temporär nicht verfügbar."
#: plugin.py:72
msgid "Cannot join %s, it's full."
msgstr "Kann %s nicht beitreten, der Kanal ist voll."
#: plugin.py:80
msgid "Cannot join %s, I was not invited."
msgstr "Kann %s nicht beitreten, ich wurde nicht eingeladen."
#: plugin.py:88
msgid "Cannot join %s, I am banned."
msgstr "Ich kann %s nicht betreten, ich bin gebannt."
#: plugin.py:96
msgid "Cannot join %s, my keyword was wrong."
msgstr "Ich kann %s nicht beitreten, mein Schlüsselwort ist falsch."
#: plugin.py:104 plugin.py:113
msgid "Cannot join %s, I'm not identified with NickServ."
msgstr "Ich kann %s nicht betreten, ich bin nicht mit NickServ identifiziert."
#: plugin.py:143
msgid ""
"<channel> [<key>]\n"
"\n"
" Tell the bot to join the given channel. If <key> is given, it is "
"used\n"
" when attempting to join the channel.\n"
" "
msgstr ""
"<Kanal> [<Schlüssel>]\n"
"\n"
"Sagt dem Bot dem angegeben Kanal beizutreten. Falls <Schlüssel> angegeben "
"wird, wird dieser benutzt um zu versuchen den Kanal zu betreten."
#: plugin.py:156
msgid "I'm already too close to maximum number of channels for this network."
msgstr "Ich bin schon zu nah an den maximalen Kanälen für dieses Netzwerk."
#: plugin.py:165
msgid ""
"takes no arguments\n"
"\n"
" Returns the channels the bot is on. Must be given in private, in "
"order\n"
" to protect the secrecy of secret channels.\n"
" "
msgstr ""
"hat keine Argumenten\n"
"\n"
"Gibt die Kanäle aus in denen der Bot sich befindet. Dieser Befehl muss "
"privat gegeben werden, um das Geheimnis der geheimen Kanale zu wahren."
#: plugin.py:175
msgid "I'm not currently in any channels."
msgstr "Momentan bin ich in keinen Kanälen."
#: plugin.py:181
msgid "My connection is restricted, I can't change nicks."
msgstr "Meine Verbindung ist begrenzt, I kann meinen Nick nicht wechseln."
#: plugin.py:188
msgid "Someone else is already using that nick."
msgstr "Jemand anderes benutzt diesen Nick schon."
#: plugin.py:195
#, fuzzy
msgid "I can't change nick, I'm currently banned in %s."
msgstr "Ich kann meinen Nick nicht ändern, der Server sagte %q."
#: plugin.py:203
msgid "I can't change nicks, the server said %q."
msgstr "Ich kann meinen Nick nicht ändern, der Server sagte %q."
#: plugin.py:217
#, fuzzy
msgid ""
"[<nick>] [<network>]\n"
"\n"
" Changes the bot's nick to <nick>. If no nick is given, returns the\n"
" bot's current nick.\n"
" "
msgstr ""
"[<Nick>]\n"
"\n"
"Ändert den Nick des Bots zu <Nick>. Falls <Nick> nicht angegeben wird, wird "
"der momentane Botnick zurückgegeben."
#: plugin.py:234
msgid ""
"[<channel>] [<reason>]\n"
"\n"
" Tells the bot to part the list of channels you give it. <channel> "
"is\n"
" only necessary if you want the bot to part a channel other than the\n"
" current channel. If <reason> is specified, use it as the part\n"
" message.\n"
" "
msgstr ""
"[<Kanal>] [<Grund>]\n"
"\n"
"Sagt dem Bot die Liste von angebenen Kanälen zu verlassen. <Kanal> ist nur "
"notwendig, falls der Bot einen anderen Kanal als den Momentanen verlassen "
"soll. Falls <Grund> angegeben wird, wird dies als Verlassensnachricht "
"verwendet."
#: plugin.py:252
msgid "I'm not in %s."
msgstr "Ich bin nicht in %s."
#: plugin.py:264
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Gives the user specified by <name> (or the user to whom "
"<hostmask>\n"
" currently maps) the specified capability <capability>\n"
" "
msgstr ""
"<Name|Hostmaske> <Fähigkeit>\n"
"\n"
"Gibt dem angebenen Benutzer <Name> (oder dem auf den die <Hostmaske> "
"zutrifft) die angegebene Fähigkeit."
#: plugin.py:284
msgid ""
"The \"owner\" capability can't be added in the bot. Use the supybot-adduser "
"program (or edit the users.conf file yourself) to add an owner capability."
msgstr ""
"Die \"owner\" Fähigkeit kann nicht über den Bot hinzugefügt werden. Benutze "
"das supybot-adduser Programm (oder verändere users.conf per Hand) um die "
"Besitzer Fähigkeit hinzuzufügen."
#: plugin.py:295
msgid "You can't add capabilities you don't have."
msgstr "Du kannst keine Fähigkeiten hinzufügen, die du nicht hast."
#: plugin.py:300
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Takes from the user specified by <name> (or the user to whom\n"
" <hostmask> currently maps) the specified capability "
"<capability>\n"
" "
msgstr ""
"<Name|Hostmaske> <Fähigkeit>\n"
"\n"
"Nimmt dem Benutzer der durch <Name> (oder dem Benutzer auf den momentan "
"<Hostmaske> zeigt) angeben wird die angegeben Fähigkeit <Fähigkeit>."
#: plugin.py:312
msgid "That user doesn't have that capability."
msgstr "Der Benutzer hat diese Fähigkeit nicht."
#: plugin.py:314
msgid "You can't remove capabilities you don't have."
msgstr "Du kannst keine Fähigkeiten entfernen, die du nicht hast."
#: plugin.py:322
msgid ""
"<hostmask|nick> [<expires>]\n"
"\n"
" This will set a persistent ignore on <hostmask> or the hostmask\n"
" currently associated with <nick>. <expires> is an optional "
"argument\n"
" specifying when (in \"seconds from now\") the ignore will "
"expire; if\n"
" it isn't given, the ignore will never automatically expire.\n"
" "
msgstr ""
"<Hostmaske|Nick> [<Ablaufzeitpunkt>]\n"
"\n"
"Es wird eine beständige Ignorierung auf <Hostmaske> oder auf die Hostmaske "
"die momentan mit <Nick> verbunden wird gesetzt. <Ablaufzeitpunkt> ist "
"optional, das legt fest wann die Ignorierung abläuft;falls dies nicht "
"angegeben wird, wird die Ignorierung niemals ablaufen."
#: plugin.py:335
msgid ""
"<hostmask|nick>\n"
"\n"
" This will remove the persistent ignore on <hostmask> or the\n"
" hostmask currently associated with <nick>.\n"
" "
msgstr ""
"<Hostmaske|Nick>\n"
"\n"
"Wird die beständige Ignorierung, von <Hostmaske> oder der Hostmaske die "
"momentan mit dem <Nick> verbunden wird, aufheben."
#: plugin.py:344
msgid "%s wasn't in the ignores database."
msgstr "%s war nicht in der Datenbank für Ignorierungen."
#: plugin.py:349
msgid ""
"takes no arguments\n"
"\n"
" Lists the hostmasks that the bot is ignoring.\n"
" "
msgstr ""
"hat keine Argumente\n"
"\n"
"Listet die Hostmasken auf, die der Bot ignoriert."
#: plugin.py:357
msgid "I'm not currently globally ignoring anyone."
msgstr "Momentan ignoriere ich niemanden global."
#: plugin.py:361
msgid ""
"takes no arguments\n"
"\n"
" Clears the current send queue for this network.\n"
" "
msgstr ""
"hat keine Argumente\n"
"\n"
"Leert die momentane Sendenwarteschlange für dieses Netzwerk."
#~ msgid "That nick is currently banned."
#~ msgstr "Dieser Nick ist momentan gebannt."

276
plugins/Admin/locales/es.po Normal file
View File

@ -0,0 +1,276 @@
# Spanish translation for limnoria
# Copyright (c) 2015 Limnoria Contributors 2015
# This file is distributed under the same license as the Limnoria package.
# Aaron Farias <timido@ubuntu.com>, 2015.
#
msgid ""
msgstr ""
"Project-Id-Version: limnoria\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2014-12-24 15:42+0000\n"
"PO-Revision-Date: 2015-01-01 16:35+0000\n"
"Last-Translator: Aaron Farias <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2015-01-01 16:48+0000\n"
"X-Generator: Launchpad (build 17286)\n"
#: plugin.py:46
#, docstring
msgid ""
"This plugin provides access to administrative commands, such as\n"
" adding capabilities, managing ignore lists, and joining channels.\n"
" This is a core Supybot plugin that should not be removed!"
msgstr ""
"Este plugin proporciona acceso a los comandos de administración, tales como\n"
"la adición de capacidades, la gestión ignoran listas y canales de unión.\n"
"Se trata de un complemento del núcleo Supybot que no se debe quitar!"
#: plugin.py:57
#, docstring
msgid "Nick/channel temporarily unavailable."
msgstr "Nick/canal disponible temporalmente."
#: plugin.py:75
msgid "Cannot join %s, it's full."
msgstr "No puede unirse a %s, está lleno."
#: plugin.py:83
msgid "Cannot join %s, I was not invited."
msgstr "No puede unirse a %s, no me invitaron."
#: plugin.py:91
msgid "Cannot join %s, I am banned."
msgstr "No pudo unirse a %s, estoy baneado."
#: plugin.py:99
msgid "Cannot join %s, my keyword was wrong."
msgstr "No puede unirse a %s, mi palabra estaba mal."
#: plugin.py:107 plugin.py:116
msgid "Cannot join %s, I'm not identified with NickServ."
msgstr "No puede unirse a %s, yo no estoy identificado con NickServ."
#: plugin.py:146
#, docstring
msgid ""
"<channel> [<key>]\n"
"\n"
" Tell the bot to join the given channel. If <key> is given, it is "
"used\n"
" when attempting to join the channel.\n"
" "
msgstr ""
"<Canal> [<key>]\n"
"\n"
"Dígale al bot para unirse al canal dado. Si <key> se da, que se utiliza\n"
"al intentar entrar al canal.\n"
" "
#: plugin.py:159
msgid "I'm already too close to maximum number of channels for this network."
msgstr "Ya estoy demasiado cerca de número máximo de canales para esta red."
#: plugin.py:168
#, docstring
msgid ""
"takes no arguments\n"
"\n"
" Returns the channels the bot is on. Must be given in private, in "
"order\n"
" to protect the secrecy of secret channels.\n"
" "
msgstr ""
"no tiene argumentos\n"
"\n"
"Devuelve los canales del bot está encendido. Debe ser dado en privado, con "
"el fin\n"
"para proteger el secreto de canales secretos.\n"
" "
#: plugin.py:178
msgid "I'm not currently in any channels."
msgstr "No estoy actualmente en ningún canal."
#: plugin.py:184
msgid "My connection is restricted, I can't change nicks."
msgstr "Mi conexión es restringida, no puedo cambiar nicks."
#: plugin.py:191
msgid "Someone else is already using that nick."
msgstr "Alguien más ya está utilizando ese nick."
#: plugin.py:198
msgid "I can't change nick, I'm currently banned in %s."
msgstr "No puedo cambiar de nick, actualmente Estoy prohibido en %s."
#: plugin.py:206
msgid "I can't change nicks, the server said %q."
msgstr "No puedo cambiar de nicks, dijo el servidor %q."
#: plugin.py:220
#, docstring
msgid ""
"[<nick>] [<network>]\n"
"\n"
" Changes the bot's nick to <nick>. If no nick is given, returns the\n"
" bot's current nick.\n"
" "
msgstr ""
"[<nick>] [<red>]\n"
"\n"
"Cambia el nick del bot a <nick>. Si no se da nick, devuelve el\n"
"actual nick del bot.\n"
" "
#: plugin.py:237
#, docstring
msgid ""
"[<channel>] [<reason>]\n"
"\n"
" Tells the bot to part the list of channels you give it. <channel> "
"is\n"
" only necessary if you want the bot to part a channel other than the\n"
" current channel. If <reason> is specified, use it as the part\n"
" message.\n"
" "
msgstr ""
"[<canal>] [<razón>]\n"
"\n"
"Le dice al bot a parte de la lista de canales que le des. <canal> es\n"
"sólo es necesario si desea que el bot a desprenderse de un canal que no sea "
"el\n"
"canal actual. Si <razón> se especifica, lo utilizan como parte\n"
"mensaje.\n"
" "
#: plugin.py:255
msgid "I'm not in %s."
msgstr "No estoy en %s."
#: plugin.py:267
#, docstring
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Gives the user specified by <name> (or the user to whom "
"<hostmask>\n"
" currently maps) the specified capability <capability>\n"
" "
msgstr ""
"<nombre | hostmask> <capacidad>\n"
"\n"
"Le da al usuario especificado por <nombre> (o el usuario a quien <hostmask>\n"
"Actualmente los mapas) la capacidad especificada <capacidad>\n"
" "
#: plugin.py:287
msgid ""
"The \"owner\" capability can't be added in the bot. Use the supybot-adduser "
"program (or edit the users.conf file yourself) to add an owner capability."
msgstr ""
"La capacidad de \"propietario\" no se puede añadir en el bot. Utilice el "
"programa de usuario Supybot-add (o editar el users.conf archivo usted mismo) "
"para añadir una capacidad de propietario."
#: plugin.py:298
msgid "You can't add capabilities you don't have."
msgstr "No se puede agregar capacidades que usted no tiene."
#: plugin.py:303
#, docstring
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Takes from the user specified by <name> (or the user to whom\n"
" <hostmask> currently maps) the specified capability "
"<capability>\n"
" "
msgstr ""
"<Nombre | hostmask> <capacidad>\n"
"\n"
"Toma del usuario especificado por <nombre> (o el usuario a quien\n"
"<Hostmask> Actualmente los mapas) la capacidad especificada <capacidad>\n"
" "
#: plugin.py:315
msgid "That user doesn't have that capability."
msgstr "Ese usuario no tiene esa capacidad."
#: plugin.py:317
msgid "You can't remove capabilities you don't have."
msgstr "No se puede quitar capacidades que usted no tiene."
#: plugin.py:325
#, docstring
msgid ""
"<hostmask|nick> [<expires>]\n"
"\n"
" This will set a persistent ignore on <hostmask> or the hostmask\n"
" currently associated with <nick>. <expires> is an optional "
"argument\n"
" specifying when (in \"seconds from now\") the ignore will "
"expire; if\n"
" it isn't given, the ignore will never automatically expire.\n"
" "
msgstr ""
"<Hostmask | nick> [<expira>]\n"
"\n"
"Esto establecerá una persistente ignoran en <hostmask> o la hostmask\n"
"actualmente asociados con <nick>. <Expira> es un argumento opcional\n"
"especificar cuándo (en \"segundo a partir de ahora\") expirará el ignorar; "
"si\n"
"no se le da, el ignorar nunca expirará automáticamente.\n"
" "
#: plugin.py:338
#, docstring
msgid ""
"<hostmask|nick>\n"
"\n"
" This will remove the persistent ignore on <hostmask> or the\n"
" hostmask currently associated with <nick>.\n"
" "
msgstr ""
"<Hostmask | nick>\n"
"\n"
"Esto eliminará la persistente ignoran en <hostmask> o la\n"
"hostmask actualmente asociada con <nick>.\n"
" "
#: plugin.py:347
msgid "%s wasn't in the ignores database."
msgstr "%S no estaba en la base de datos ignorados."
#: plugin.py:352
#, docstring
msgid ""
"takes no arguments\n"
"\n"
" Lists the hostmasks that the bot is ignoring.\n"
" "
msgstr ""
"no tiene argumentos\n"
"\n"
"Enumera los sus hosts que el bot está ignorando.\n"
" "
#: plugin.py:360
msgid "I'm not currently globally ignoring anyone."
msgstr "No estoy actualmente haciendo caso omiso a nivel mundial a nadie."
#: plugin.py:364
#, docstring
msgid ""
"takes no arguments\n"
"\n"
" Clears the current send queue for this network.\n"
" "
msgstr ""
"no tiene argumentos\n"
"\n"
"Borra la cola de envío actual de esta red.\n"
" "

272
plugins/Admin/locales/fi.po Normal file
View File

@ -0,0 +1,272 @@
# Admin plugin in Limnoria.
# Copyright (C) 2011 Limnoria
# Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011.
#
msgid ""
msgstr ""
"Project-Id-Version: Finnish translation of Admin plugin in Supybot\n"
"POT-Creation-Date: 2014-12-20 11:59+EET\n"
"PO-Revision-Date: 2014-12-20 12:19+0200\n"
"Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n"
"Language-Team: \n"
"Language: fi_FI\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 1.6.10\n"
#: plugin.py:46
msgid ""
"This plugin provides access to administrative commands, such as\n"
" adding capabilities, managing ignore lists, and joining channels.\n"
" This is a core Supybot plugin that should not be removed!"
msgstr ""
"Tämä plugin antaa pääsyn ylläpitäviin komentoihin, kuten valtuuksien "
"lisäämiseen,\n"
" ignore-listojen hallintaan ja kanaville liittymiseen. Tämä on ydin Supybot-"
"plugin, jota ei pitäisi poistaa!s"
#: plugin.py:57
msgid "Nick/channel temporarily unavailable."
msgstr "Nimimerkki/kanava on väliaikaisesti saavuttamattomissa."
#: plugin.py:75
msgid "Cannot join %s, it's full."
msgstr "Ei voida liittyä kanavalle %s, se on täynnä."
#: plugin.py:83
msgid "Cannot join %s, I was not invited."
msgstr "Ei voi liittyä kanavalle %s, minua ei ole kutsuttu."
#: plugin.py:91
msgid "Cannot join %s, I am banned."
msgstr "Ei voi liittyä kanavalle %s, se on antanut minulle porttikiellon."
#: plugin.py:99
msgid "Cannot join %s, my keyword was wrong."
msgstr "En voi liittyä kanavalle %s, minun avainsana oli väärä."
#: plugin.py:107 plugin.py:116
msgid "Cannot join %s, I'm not identified with NickServ."
msgstr "En voi liittyä kanavalle %s, koska en ole tunnistautunut NickServille."
#: plugin.py:146
msgid ""
"<channel> [<key>]\n"
"\n"
" Tell the bot to join the given channel. If <key> is given, it is "
"used\n"
" when attempting to join the channel.\n"
" "
msgstr ""
"<kanava> [<avain>]\n"
"\n"
" Käskee botin liittyä annetulle kanavalle. Jos <avain> on annettu, "
"sitä käytetään\n"
" yrittäessä liittyä kanavalle.\n"
" "
#: plugin.py:159
msgid "I'm already too close to maximum number of channels for this network."
msgstr "Minä olen jo liian lähellä kanavien maksimimäärää tässä verkossa."
#: plugin.py:168
msgid ""
"takes no arguments\n"
"\n"
" Returns the channels the bot is on. Must be given in private, in "
"order\n"
" to protect the secrecy of secret channels.\n"
" "
msgstr ""
"Ei ota parametrejä\n"
"\n"
" Palauttaa listan kanavista, joilla botti on. Täytyy antaa "
"yksityisviestillä salaistenkanavien\n"
" salaisuuden suojelemiseksi.\n"
" "
#: plugin.py:178
msgid "I'm not currently in any channels."
msgstr "En juuri nyt ole millään kanavalla."
#: plugin.py:184
msgid "My connection is restricted, I can't change nicks."
msgstr "Minun yhteyteni on rajoitettu. En voi vaihtaa nimimerkkiä."
#: plugin.py:191
msgid "Someone else is already using that nick."
msgstr "Joku muu käyttää jo tuota nimimerkkiä."
#: plugin.py:198
#, fuzzy
msgid "I can't change nick, I'm currently banned in %s."
msgstr "Minä en voi vaihtaa nimimerkkiä, koska palvelin sanoi %q"
#: plugin.py:206
msgid "I can't change nicks, the server said %q."
msgstr "Minä en voi vaihtaa nimimerkkiä, koska palvelin sanoi %q"
#: plugin.py:220
msgid ""
"[<nick>] [<network>]\n"
"\n"
" Changes the bot's nick to <nick>. If no nick is given, returns the\n"
" bot's current nick.\n"
" "
msgstr ""
"[<nimimerkki>] [<verkko>]\n"
"\n"
" Vaihtaa botin nimimerkin <nimimerkiksi>. Mikäli nimimerkkiä ei anneta, "
"botin nykyinen\n"
" nimimerkki palautetaan.\n"
" "
#: plugin.py:237
msgid ""
"[<channel>] [<reason>]\n"
"\n"
" Tells the bot to part the list of channels you give it. <channel> "
"is\n"
" only necessary if you want the bot to part a channel other than the\n"
" current channel. If <reason> is specified, use it as the part\n"
" message.\n"
" "
msgstr ""
"[<kanava>] [<syy>]\n"
"\n"
" Käskee botin poistua kanavilta, jotka annat sille. <Kanava> on\n"
" vaadittu jos haluat botin poistuvat muulta, kuin \n"
" nykyiseltä kanavalta. Jos <syy> on määritetty, sitä käytetään\n"
" poistumisviestissä.\n"
" "
#: plugin.py:255
msgid "I'm not in %s."
msgstr "Minä en ole kanavalla %s."
#: plugin.py:267
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Gives the user specified by <name> (or the user to whom "
"<hostmask>\n"
" currently maps) the specified capability <capability>\n"
" "
msgstr ""
"<nimi|hostmask> <valtuus>\n"
"\n"
" Antaa <nimen> määrittämälle käyttäjälle (tai käyttäjälle jonka "
"<hostmask>\n"
" ilmoittaa) määritetyn valtuuden <valtuus>\n"
" "
#: plugin.py:287
msgid ""
"The \"owner\" capability can't be added in the bot. Use the supybot-adduser "
"program (or edit the users.conf file yourself) to add an owner capability."
msgstr ""
"\"Owner\" valtuutta ei voi lisätä botissa. Käytä supybot-adduser ohjelmaa "
"(tai muokkaa users.conf tiedostoa itse) lisätäksesi owner valtuuden."
#: plugin.py:298
msgid "You can't add capabilities you don't have."
msgstr "Et voi lisätä valtuuksia, joita sinulla ei ole."
#: plugin.py:303
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Takes from the user specified by <name> (or the user to whom\n"
" <hostmask> currently maps) the specified capability "
"<capability>\n"
" "
msgstr ""
"<nimi|hostmask> <valtuus>\n"
"\n"
" Ottaa <nimen> määrittämältä käyttäjältä (tai käyttäjältä "
"johon\n"
" <hostmask> sopii) määritetyn valtuuden <valtuus>\n"
" "
#: plugin.py:315
msgid "That user doesn't have that capability."
msgstr "Tuolla käyttäjällä ei tuota valtuutta."
#: plugin.py:317
msgid "You can't remove capabilities you don't have."
msgstr "Sinä et voi poistaa valtuuksia, joita sinulla ei ole."
#: plugin.py:325
msgid ""
"<hostmask|nick> [<expires>]\n"
"\n"
" This will set a persistent ignore on <hostmask> or the hostmask\n"
" currently associated with <nick>. <expires> is an optional "
"argument\n"
" specifying when (in \"seconds from now\") the ignore will "
"expire; if\n"
" it isn't given, the ignore will never automatically expire.\n"
" "
msgstr ""
"<hostmask|nimimerkki> [<vanhentuu>]\n"
"\n"
" Tämä asettaa pysyvän huomiotta jättämisen <hostmaskiin> tai "
"hostmaskiin,\n"
" joka on tällä hetkellä yhdistetty <nimimerkkiin>. <Vanhentuminen> on "
"vaihtoehtoinen paremetri,\n"
" joka määrittää (\"sekuntit\") joiden jälkeen huomiotta jättäminen "
"poistetaan; jos\n"
" sitä ei ole annettu, huomiotta jättäminen ei vanhene ikinä "
"automaattisesti.\n"
" "
#: plugin.py:338
msgid ""
"<hostmask|nick>\n"
"\n"
" This will remove the persistent ignore on <hostmask> or the\n"
" hostmask currently associated with <nick>.\n"
" "
msgstr ""
"<hostmask|nimimerkki>\n"
"\n"
" Tämä poistaa pysyvän huomiotta jättämisen <hostmaskista> tai\n"
" hostmaskista joka on tällä hetkellä yhdistetty <nimimerkkiin>.\n"
" "
#: plugin.py:347
msgid "%s wasn't in the ignores database."
msgstr "%s ei ollut huomiotta jätettävien tietokannassa."
#: plugin.py:352
msgid ""
"takes no arguments\n"
"\n"
" Lists the hostmasks that the bot is ignoring.\n"
" "
msgstr ""
"Ei ota parametrejä\n"
"\n"
" Luetteloi hostmaskit jotka ovat botin huomiotta jättämis listalla.\n"
" "
#: plugin.py:360
msgid "I'm not currently globally ignoring anyone."
msgstr "En tällä hetkellä jätä ketään huomioitta globaalisti."
#: plugin.py:364
msgid ""
"takes no arguments\n"
"\n"
" Clears the current send queue for this network.\n"
" "
msgstr ""
"ei ota parametrejä\n"
"\n"
" Tyhjentää nykyisen lähetysjonon tälle verkolle.\n"
" "
#~ msgid "That nick is currently banned."
#~ msgstr "Tuolla nimimerkillä on tällähetkellä porttikielto."

239
plugins/Admin/locales/fr.po Normal file
View File

@ -0,0 +1,239 @@
msgid ""
msgstr ""
"Project-Id-Version: Limnoria\n"
"POT-Creation-Date: 2014-08-17 13:46+CEST\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: ProgVal <progval@gmail.com>\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-SourceCharset: Ascii\n"
"X-Generator: Poedit 1.5.4\n"
#: plugin.py:54
msgid "Nick/channel temporarily unavailable."
msgstr "Nick/canal temporairement indisponible"
#: plugin.py:72
msgid "Cannot join %s, it's full."
msgstr "Ne peut joindre %s, il est plein."
#: plugin.py:80
msgid "Cannot join %s, I was not invited."
msgstr "Ne peut joindre %s, pas invité."
#: plugin.py:88
msgid "Cannot join %s, I am banned."
msgstr "Ne peut joindre %s, j'y suis banni."
#: plugin.py:96
msgid "Cannot join %s, my keyword was wrong."
msgstr "Ne peut joindre %s, mon mot de passe est mauvais."
#: plugin.py:104 plugin.py:113
msgid "Cannot join %s, I'm not identified with NickServ."
msgstr "Ne peut joindre %s, je ne suis pas identifié auprès de NickServ."
#: plugin.py:143
msgid ""
"<channel> [<key>]\n"
"\n"
" Tell the bot to join the given channel. If <key> is given, it is "
"used\n"
" when attempting to join the channel.\n"
" "
msgstr ""
"<canal> [<clef>]\n"
"\n"
"Dit au bot de rejoindre le canal donné. Si la <clef> est donnée, elle est "
"utilisée pour rejoindre le canal."
#: plugin.py:156
msgid "I'm already too close to maximum number of channels for this network."
msgstr "Je suis déjà sur un nombre de canaux trop grand pour ce réseau."
#: plugin.py:165
msgid ""
"takes no arguments\n"
"\n"
" Returns the channels the bot is on. Must be given in private, in "
"order\n"
" to protect the secrecy of secret channels.\n"
" "
msgstr ""
"Ne prend pas d'argument \n"
"\n"
"Retourne les canaux sur lesquels le bot est. Doit être en privé, dans le but "
"d'éviter que les canaux secrets ne soient divulgués."
#: plugin.py:175
msgid "I'm not currently in any channels."
msgstr "Je ne suis actuellement sur aucun canal."
#: plugin.py:181
msgid "My connection is restricted, I can't change nicks."
msgstr "Ma connexion est restreinte, je ne peux changer de nick."
#: plugin.py:188
msgid "Someone else is already using that nick."
msgstr "Quelqu'un d'autre utilise déjà ce nick."
#: plugin.py:195
#, fuzzy
msgid "I can't change nick, I'm currently banned in %s."
msgstr "Je ne peux changer de nick, le serveur a dit %q."
#: plugin.py:203
msgid "I can't change nicks, the server said %q."
msgstr "Je ne peux changer de nick, le serveur a dit %q."
#: plugin.py:217
msgid ""
"[<nick>] [<network>]\n"
"\n"
" Changes the bot's nick to <nick>. If no nick is given, returns the\n"
" bot's current nick.\n"
" "
msgstr ""
"[<nick>] [<réseau>]\n"
"\n"
"Change le nick du bot à <nick>. Si aucun nick n'est donné, retourne le nick "
"actuel du bot."
#: plugin.py:234
msgid ""
"[<channel>] [<reason>]\n"
"\n"
" Tells the bot to part the list of channels you give it. <channel> "
"is\n"
" only necessary if you want the bot to part a channel other than the\n"
" current channel. If <reason> is specified, use it as the part\n"
" message.\n"
" "
msgstr ""
"[<canal>] [<raison>]\n"
"\n"
"Dit au bot de partir de la liste de canaux que vous avez donnée. <canal> "
"n'est nécessaire que si vous voulez que le bot parte d'un autre canal que "
"l'actuel. Si la <raison> est spécifiée, elle est utilisée comme message de "
"départ."
#: plugin.py:252
msgid "I'm not in %s."
msgstr "Je ne suis pas sur %s."
#: plugin.py:264
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Gives the user specified by <name> (or the user to whom "
"<hostmask>\n"
" currently maps) the specified capability <capability>\n"
" "
msgstr ""
"<nom|masque d'hôte> <capacité>\n"
"\n"
"Donne la <capacité> à l'utilisateur spécifié par <nom> (ou l'utilisateur à "
"qui correspond <masque d'hôte>)."
#: plugin.py:284
msgid ""
"The \"owner\" capability can't be added in the bot. Use the supybot-adduser "
"program (or edit the users.conf file yourself) to add an owner capability."
msgstr ""
"La capacité \"owner\" ne peut être ajoutée via le bot. Utilisez le programme "
"supybot-adduser (ou éditez le fichier users.conf vous-même) pour ajouter la "
"capacité owner."
#: plugin.py:295
msgid "You can't add capabilities you don't have."
msgstr "Vous ne pouvez ajouter des capacités que vous n'avez pas."
#: plugin.py:300
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Takes from the user specified by <name> (or the user to whom\n"
" <hostmask> currently maps) the specified capability "
"<capability>\n"
" "
msgstr ""
"<nom|masque d'hôte> <capacité>\n"
"\n"
"Retire la <capacité> à l'utilisateur spécifié par le <nom> (ou celui à qui "
"correspond le <masque d'hôte>)."
#: plugin.py:312
msgid "That user doesn't have that capability."
msgstr "Cet utilisateur n'a pas cette capacité."
#: plugin.py:314
msgid "You can't remove capabilities you don't have."
msgstr "Vous ne pouvez retirer des capacités que vous n'avez pas."
#: plugin.py:322
msgid ""
"<hostmask|nick> [<expires>]\n"
"\n"
" This will set a persistent ignore on <hostmask> or the hostmask\n"
" currently associated with <nick>. <expires> is an optional "
"argument\n"
" specifying when (in \"seconds from now\") the ignore will "
"expire; if\n"
" it isn't given, the ignore will never automatically expire.\n"
" "
msgstr ""
"<masque d'hôte|nick> [<expiration>]\n"
"\n"
"Ajoute un masque d'ignorance persistant sur le <masque d'hôte>, ou sur le "
"masque d'hôte de <nick>. <expiration> est un argument optionnel spécifiant "
"quand (en \"secondes à partir de maintenant\") l'ignorance expirera ; si "
"elle n'est pas donnée, l'ignorance n'expirera jamais."
#: plugin.py:335
msgid ""
"<hostmask|nick>\n"
"\n"
" This will remove the persistent ignore on <hostmask> or the\n"
" hostmask currently associated with <nick>.\n"
" "
msgstr ""
"<masque d'hôte|nick>\n"
"\n"
"Ceci retirera le masque d'ignorance persistant sur le <masque d'hôte>, ou "
"sur le masque d'hôte associé au <nick>."
#: plugin.py:344
msgid "%s wasn't in the ignores database."
msgstr "%s n'étais pas dans ma base de données d'ignorance."
#: plugin.py:349
msgid ""
"takes no arguments\n"
"\n"
" Lists the hostmasks that the bot is ignoring.\n"
" "
msgstr ""
"Ne prend pas d'argument\n"
"\n"
"Liste les masques d'hôte que le bot ignore."
#: plugin.py:357
msgid "I'm not currently globally ignoring anyone."
msgstr "Je n'ignore actuellement personne globalement."
#: plugin.py:361
msgid ""
"takes no arguments\n"
"\n"
" Clears the current send queue for this network.\n"
" "
msgstr ""
"Ne prend pas d'argument\n"
"\n"
"Vide la queue en attente pour ce réseau."
#~ msgid "That nick is currently banned."
#~ msgstr "Ce nick est banni."

257
plugins/Admin/locales/it.po Normal file
View File

@ -0,0 +1,257 @@
msgid ""
msgstr ""
"Project-Id-Version: Limnoria\n"
"POT-Creation-Date: 2014-08-17 13:46+CEST\n"
"PO-Revision-Date: 2012-03-15 20:25+0100\n"
"Last-Translator: skizzhg <skizzhg@gmx.com>\n"
"Language-Team: Italian <skizzhg@gmx.com>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: plugin.py:54
msgid "Nick/channel temporarily unavailable."
msgstr "Nick/canale temporaneamente non disponibile."
#: plugin.py:72
msgid "Cannot join %s, it's full."
msgstr "Non posso entrare in %s, è pieno."
#: plugin.py:80
msgid "Cannot join %s, I was not invited."
msgstr "Non posso entrare in %s, non sono stato invitato."
#: plugin.py:88
msgid "Cannot join %s, I am banned."
msgstr "Non posso entrare in %s, sono stato bannato."
#: plugin.py:96
msgid "Cannot join %s, my keyword was wrong."
msgstr "Non posso entrare in %s, la password era sbagliata."
#: plugin.py:104 plugin.py:113
msgid "Cannot join %s, I'm not identified with NickServ."
msgstr "Non posso entrare in %s, non sono identificato con NickServ."
#: plugin.py:143
msgid ""
"<channel> [<key>]\n"
"\n"
" Tell the bot to join the given channel. If <key> is given, it is "
"used\n"
" when attempting to join the channel.\n"
" "
msgstr ""
"<canale> [<password>]\n"
"\n"
" Dice al bot di entrare nel canale specificato. Se <password> è "
"fornita,\n"
" viene usata quando si tenta di entrare nel canale.\n"
" "
#: plugin.py:156
msgid "I'm already too close to maximum number of channels for this network."
msgstr "Sono già troppo vicino al numero massimo di canali per questa rete."
#: plugin.py:165
msgid ""
"takes no arguments\n"
"\n"
" Returns the channels the bot is on. Must be given in private, in "
"order\n"
" to protect the secrecy of secret channels.\n"
" "
msgstr ""
"non necessita argomenti\n"
"\n"
" Restituisce i canali dove è presente il bot. Deve essere richiesto "
"in\n"
" privato per preservare la segretezza dei canali privati.\n"
" "
#: plugin.py:175
msgid "I'm not currently in any channels."
msgstr "Al momento non sono in nessun canale."
#: plugin.py:181
msgid "My connection is restricted, I can't change nicks."
msgstr "La mia connessione è limitata, non posso cambiare nick."
#: plugin.py:188
msgid "Someone else is already using that nick."
msgstr "Qualcun altro sta utilizzando questo nick."
#: plugin.py:195
#, fuzzy
msgid "I can't change nick, I'm currently banned in %s."
msgstr "Non posso cambiare nick, il server ha detto %q."
#: plugin.py:203
msgid "I can't change nicks, the server said %q."
msgstr "Non posso cambiare nick, il server ha detto %q."
#: plugin.py:217
#, fuzzy
msgid ""
"[<nick>] [<network>]\n"
"\n"
" Changes the bot's nick to <nick>. If no nick is given, returns the\n"
" bot's current nick.\n"
" "
msgstr ""
"[<nick>]\n"
"\n"
" Cambia il nick del bot in <nick>. Se non ne viene fornito uno, "
"restituisce\n"
" quello attuale.\n"
" "
#: plugin.py:234
msgid ""
"[<channel>] [<reason>]\n"
"\n"
" Tells the bot to part the list of channels you give it. <channel> "
"is\n"
" only necessary if you want the bot to part a channel other than the\n"
" current channel. If <reason> is specified, use it as the part\n"
" message.\n"
" "
msgstr ""
"[<canale>] [<motivo>]\n"
"\n"
" Fornisce al bot l'elenco dei canali da cui uscire. <canale> è "
"necessario\n"
" solo se si vuole far uscire il bot da un canale diverso da quello "
"attuale.\n"
" Se <motivo> viene specificato, verrà usato come messaggio di "
"uscita.\n"
" "
#: plugin.py:252
msgid "I'm not in %s."
msgstr "Non sono in %s."
#: plugin.py:264
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Gives the user specified by <name> (or the user to whom "
"<hostmask>\n"
" currently maps) the specified capability <capability>\n"
" "
msgstr ""
"<nome|hostmask> <capacità>\n"
"\n"
" Dà all'utente specificato da <nome> (o quello a cui corrisponde\n"
" <hostmask> attualmente) la <capacità> specificata.\n"
" "
#: plugin.py:284
msgid ""
"The \"owner\" capability can't be added in the bot. Use the supybot-adduser "
"program (or edit the users.conf file yourself) to add an owner capability."
msgstr ""
"La capacità \"owner\" non può essere aggiunta al bot. Utilizzare il "
"programma supybot-adduser (o modificare il file users.conf) per aggiungerla."
#: plugin.py:295
msgid "You can't add capabilities you don't have."
msgstr "Non puoi aggiungere capacità che non hai."
#: plugin.py:300
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Takes from the user specified by <name> (or the user to whom\n"
" <hostmask> currently maps) the specified capability "
"<capability>\n"
" "
msgstr ""
"<nome|hostmask> <capacità>\n"
"\n"
" Rimuove l'utente specificato da <nome> (o quello a cui "
"corrisponde\n"
" <hostmask> attualmente) la <capacità> specificata\n"
" "
#: plugin.py:312
msgid "That user doesn't have that capability."
msgstr "Questo utente non ha tale capacità."
#: plugin.py:314
msgid "You can't remove capabilities you don't have."
msgstr "Non puoi rimuovere capacità che non hai."
#: plugin.py:322
msgid ""
"<hostmask|nick> [<expires>]\n"
"\n"
" This will set a persistent ignore on <hostmask> or the hostmask\n"
" currently associated with <nick>. <expires> is an optional "
"argument\n"
" specifying when (in \"seconds from now\") the ignore will "
"expire; if\n"
" it isn't given, the ignore will never automatically expire.\n"
" "
msgstr ""
"<hostmask|nick> [<scadenza>]\n"
"\n"
" Imposta un ignore permanente su <hostmask> o l'hostmask "
"attualmente\n"
" associata a <nick>. <scadenza> è un argomento opzionale per "
"specificare\n"
" quando (in \"secondi a partire da subito\") scadrà l'ignore; se "
"non fornito,\n"
" questo non scadrà mai.\n"
" "
#: plugin.py:335
msgid ""
"<hostmask|nick>\n"
"\n"
" This will remove the persistent ignore on <hostmask> or the\n"
" hostmask currently associated with <nick>.\n"
" "
msgstr ""
"<hostmask|nick>\n"
"\n"
" Rimuove l'ignore persistente su <hostmask> o l'attuale hostmask "
"associata a <nick>.\n"
" "
#: plugin.py:344
msgid "%s wasn't in the ignores database."
msgstr "%s non è nel mio database degli ignorati."
#: plugin.py:349
msgid ""
"takes no arguments\n"
"\n"
" Lists the hostmasks that the bot is ignoring.\n"
" "
msgstr ""
"Non necessita argomenti\n"
"\n"
" Elenca le hostmask che il bot sta ignorando.\n"
" "
#: plugin.py:357
msgid "I'm not currently globally ignoring anyone."
msgstr "Al momento non sto ignorando nessuno."
#: plugin.py:361
msgid ""
"takes no arguments\n"
"\n"
" Clears the current send queue for this network.\n"
" "
msgstr ""
"non necessita argomenti\n"
"\n"
" Pulisce l'attuale coda dei messaggi da inviare (interrompe il flood) "
"per questa rete.\n"
" "
#~ msgid "That nick is currently banned."
#~ msgstr "Il nick è attualmente bannato."

204
plugins/Admin/messages.pot Normal file
View File

@ -0,0 +1,204 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2014-12-20 11:59+EET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
#: plugin.py:46
#, docstring
msgid ""
"This plugin provides access to administrative commands, such as\n"
" adding capabilities, managing ignore lists, and joining channels.\n"
" This is a core Supybot plugin that should not be removed!"
msgstr ""
#: plugin.py:57
#, docstring
msgid "Nick/channel temporarily unavailable."
msgstr ""
#: plugin.py:75
msgid "Cannot join %s, it's full."
msgstr ""
#: plugin.py:83
msgid "Cannot join %s, I was not invited."
msgstr ""
#: plugin.py:91
msgid "Cannot join %s, I am banned."
msgstr ""
#: plugin.py:99
msgid "Cannot join %s, my keyword was wrong."
msgstr ""
#: plugin.py:107 plugin.py:116
msgid "Cannot join %s, I'm not identified with NickServ."
msgstr ""
#: plugin.py:146
#, docstring
msgid ""
"<channel> [<key>]\n"
"\n"
" Tell the bot to join the given channel. If <key> is given, it is used\n"
" when attempting to join the channel.\n"
" "
msgstr ""
#: plugin.py:159
msgid "I'm already too close to maximum number of channels for this network."
msgstr ""
#: plugin.py:168
#, docstring
msgid ""
"takes no arguments\n"
"\n"
" Returns the channels the bot is on. Must be given in private, in order\n"
" to protect the secrecy of secret channels.\n"
" "
msgstr ""
#: plugin.py:178
msgid "I'm not currently in any channels."
msgstr ""
#: plugin.py:184
msgid "My connection is restricted, I can't change nicks."
msgstr ""
#: plugin.py:191
msgid "Someone else is already using that nick."
msgstr ""
#: plugin.py:198
msgid "I can't change nick, I'm currently banned in %s."
msgstr ""
#: plugin.py:206
msgid "I can't change nicks, the server said %q."
msgstr ""
#: plugin.py:220
#, docstring
msgid ""
"[<nick>] [<network>]\n"
"\n"
" Changes the bot's nick to <nick>. If no nick is given, returns the\n"
" bot's current nick.\n"
" "
msgstr ""
#: plugin.py:237
#, docstring
msgid ""
"[<channel>] [<reason>]\n"
"\n"
" Tells the bot to part the list of channels you give it. <channel> is\n"
" only necessary if you want the bot to part a channel other than the\n"
" current channel. If <reason> is specified, use it as the part\n"
" message.\n"
" "
msgstr ""
#: plugin.py:255
msgid "I'm not in %s."
msgstr ""
#: plugin.py:267
#, docstring
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Gives the user specified by <name> (or the user to whom <hostmask>\n"
" currently maps) the specified capability <capability>\n"
" "
msgstr ""
#: plugin.py:287
msgid "The \"owner\" capability can't be added in the bot. Use the supybot-adduser program (or edit the users.conf file yourself) to add an owner capability."
msgstr ""
#: plugin.py:298
msgid "You can't add capabilities you don't have."
msgstr ""
#: plugin.py:303
#, docstring
msgid ""
"<name|hostmask> <capability>\n"
"\n"
" Takes from the user specified by <name> (or the user to whom\n"
" <hostmask> currently maps) the specified capability <capability>\n"
" "
msgstr ""
#: plugin.py:315
msgid "That user doesn't have that capability."
msgstr ""
#: plugin.py:317
msgid "You can't remove capabilities you don't have."
msgstr ""
#: plugin.py:325
#, docstring
msgid ""
"<hostmask|nick> [<expires>]\n"
"\n"
" This will set a persistent ignore on <hostmask> or the hostmask\n"
" currently associated with <nick>. <expires> is an optional argument\n"
" specifying when (in \"seconds from now\") the ignore will expire; if\n"
" it isn't given, the ignore will never automatically expire.\n"
" "
msgstr ""
#: plugin.py:338
#, docstring
msgid ""
"<hostmask|nick>\n"
"\n"
" This will remove the persistent ignore on <hostmask> or the\n"
" hostmask currently associated with <nick>.\n"
" "
msgstr ""
#: plugin.py:347
msgid "%s wasn't in the ignores database."
msgstr ""
#: plugin.py:352
#, docstring
msgid ""
"takes no arguments\n"
"\n"
" Lists the hostmasks that the bot is ignoring.\n"
" "
msgstr ""
#: plugin.py:360
msgid "I'm not currently globally ignoring anyone."
msgstr ""
#: plugin.py:364
#, docstring
msgid ""
"takes no arguments\n"
"\n"
" Clears the current send queue for this network.\n"
" "
msgstr ""

367
plugins/Admin/plugin.py Normal file
View File

@ -0,0 +1,367 @@
###
# Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2010, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import sys
import time
import supybot.conf as conf
import supybot.ircdb as ircdb
import supybot.utils as utils
from supybot.commands import *
import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
import supybot.schedule as schedule
import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Admin')
class Admin(callbacks.Plugin):
"""This plugin provides access to administrative commands, such as
adding capabilities, managing ignore lists, and joining channels.
This is a core Supybot plugin that should not be removed!"""
def __init__(self, irc):
self.__parent = super(Admin, self)
self.__parent.__init__(irc)
self.joins = {}
self.pendingNickChanges = {}
@internationalizeDocstring
def do437(self, irc, msg):
"""Nick/channel temporarily unavailable."""
target = msg.args[0]
t = time.time() + 30
if irc.isChannel(target):
# Let's schedule a rejoin.
networkGroup = conf.supybot.networks.get(irc.network)
def rejoin():
irc.queueMsg(networkGroup.channels.join(target))
# We don't need to schedule something because we'll get another
# 437 when we try to join later.
schedule.addEvent(rejoin, t)
self.log.info('Scheduling a rejoin to %s at %s; '
'Channel temporarily unavailable.', target, t)
else:
irc = self.pendingNickChanges.get(irc, None)
if irc is not None:
def nick():
irc.queueMsg(ircmsgs.nick(target))
schedule.addEvent(nick, t)
self.log.info('Scheduling a nick change to %s at %s; '
'Nick temporarily unavailable.', target, t)
else:
self.log.debug('Got 437 without Admin.nick being called.')
def do471(self, irc, msg):
try:
channel = msg.args[1]
(irc, msg) = self.joins.pop(channel)
irc.error(_('Cannot join %s, it\'s full.') % channel)
except KeyError:
self.log.debug('Got 471 without Admin.join being called.')
def do473(self, irc, msg):
try:
channel = msg.args[1]
(irc, msg) = self.joins.pop(channel)
irc.error(_('Cannot join %s, I was not invited.') % channel)
except KeyError:
self.log.debug('Got 473 without Admin.join being called.')
def do474(self, irc, msg):
try:
channel = msg.args[1]
(irc, msg) = self.joins.pop(channel)
irc.error(_('Cannot join %s, I am banned.') % channel)
except KeyError:
self.log.debug('Got 474 without Admin.join being called.')
def do475(self, irc, msg):
try:
channel = msg.args[1]
(irc, msg) = self.joins.pop(channel)
irc.error(_('Cannot join %s, my keyword was wrong.') % channel)
except KeyError:
self.log.debug('Got 475 without Admin.join being called.')
def do477(self, irc, msg):
try:
channel = msg.args[1]
(irc,msg) = self.joins.pop(channel)
irc.error(_('Cannot join %s, I\'m not identified with '
'NickServ.') % channel)
except KeyError:
self.log.debug('Got 477 without Admin.join being called.')
def do515(self, irc, msg):
try:
channel = msg.args[1]
(irc, msg) = self.joins.pop(channel)
irc.error(_('Cannot join %s, I\'m not identified with '
'NickServ.') % channel)
except KeyError:
self.log.debug('Got 515 without Admin.join being called.')
def doJoin(self, irc, msg):
if msg.prefix == irc.prefix:
try:
del self.joins[msg.args[0]]
except KeyError:
s = 'Joined a channel without Admin.join being called.'
self.log.debug(s)
def doInvite(self, irc, msg):
channel = msg.args[1]
if channel not in irc.state.channels:
if conf.supybot.alwaysJoinOnInvite.get(channel)() or \
ircdb.checkCapability(msg.prefix, 'admin'):
self.log.info('Invited to %s by %s.', channel, msg.prefix)
networkGroup = conf.supybot.networks.get(irc.network)
irc.queueMsg(networkGroup.channels.join(channel))
conf.supybot.networks.get(irc.network).channels().add(channel)
else:
self.log.warning('Invited to %s by %s, but '
'supybot.alwaysJoinOnInvite was False and '
'the user lacked the "admin" capability.',
channel, msg.prefix)
@internationalizeDocstring
def join(self, irc, msg, args, channel, key):
"""<channel> [<key>]
Tell the bot to join the given channel. If <key> is given, it is used
when attempting to join the channel.
"""
if not irc.isChannel(channel):
irc.errorInvalid(_('channel'), channel, Raise=True)
networkGroup = conf.supybot.networks.get(irc.network)
networkGroup.channels().add(channel)
if key:
networkGroup.channels.key.get(channel).setValue(key)
maxchannels = irc.state.supported.get('maxchannels', sys.maxsize)
if len(irc.state.channels) + 1 > maxchannels:
irc.error(_('I\'m already too close to maximum number of '
'channels for this network.'), Raise=True)
irc.queueMsg(networkGroup.channels.join(channel))
irc.noReply()
self.joins[channel] = (irc, msg)
join = wrap(join, ['validChannel', additional('something')])
@internationalizeDocstring
def channels(self, irc, msg, args):
"""takes no arguments
Returns the channels the bot is on.
"""
L = irc.state.channels.keys()
if L:
utils.sortBy(ircutils.toLower, L)
irc.reply(format('%L', L), private=True)
else:
irc.reply(_('I\'m not currently in any channels.'))
channels = wrap(channels)
def do484(self, irc, msg):
irc = self.pendingNickChanges.get(irc, None)
if irc is not None:
irc.error(_('My connection is restricted, I can\'t change nicks.'))
else:
self.log.debug('Got 484 without Admin.nick being called.')
def do433(self, irc, msg):
irc = self.pendingNickChanges.get(irc, None)
if irc is not None:
irc.error(_('Someone else is already using that nick.'))
else:
self.log.debug('Got 433 without Admin.nick being called.')
def do435(self, irc, msg):
irc = self.pendingNickChanges.get(irc, None)
if irc is not None:
irc.error(_('I can\'t change nick, I\'m currently banned in %s.') %
msg.args[2])
else:
self.log.debug('Got 435 without Admin.nick being called.')
def do438(self, irc, msg):
irc = self.pendingNickChanges.get(irc, None)
if irc is not None:
irc.error(format(_('I can\'t change nicks, the server said %q.'),
msg.args[2]), private=True)
else:
self.log.debug('Got 438 without Admin.nick being called.')
def doNick(self, irc, msg):
if msg.nick == irc.nick or msg.args[0] == irc.nick:
try:
del self.pendingNickChanges[irc]
except KeyError:
self.log.debug('Got NICK without Admin.nick being called.')
@internationalizeDocstring
def nick(self, irc, msg, args, nick, network):
"""[<nick>] [<network>]
Changes the bot's nick to <nick>. If no nick is given, returns the
bot's current nick.
"""
network = network or irc.network
if nick:
group = getattr(conf.supybot.networks, network)
group.nick.setValue(nick)
irc.queueMsg(ircmsgs.nick(nick))
self.pendingNickChanges[irc.getRealIrc()] = irc
else:
irc.reply(irc.nick)
nick = wrap(nick, [additional('nick'), additional('something')])
class capability(callbacks.Commands):
@internationalizeDocstring
def add(self, irc, msg, args, user, capability):
"""<name|hostmask> <capability>
Gives the user specified by <name> (or the user to whom <hostmask>
currently maps) the specified capability <capability>
"""
# Ok, the concepts that are important with capabilities:
#
### 1) No user should be able to elevate their privilege to owner.
### 2) Admin users are *not* superior to #channel.ops, and don't
### have God-like powers over channels.
### 3) We assume that Admin users are two things: non-malicious and
### and greedy for power. So they'll try to elevate their
### privilege to owner, but they won't try to crash the bot for
### no reason.
# Thus, the owner capability can't be given in the bot. Admin
# users can only give out capabilities they have themselves (which
# will depend on supybot.capabilities and its child default) but
# generally means they can't mess with channel capabilities.
if ircutils.strEqual(capability, 'owner'):
irc.error(_('The "owner" capability can\'t be added in the '
'bot. Use the supybot-adduser program (or edit the '
'users.conf file yourself) to add an owner '
'capability.'))
return
if ircdb.isAntiCapability(capability) or \
ircdb.checkCapability(msg.prefix, capability):
user.addCapability(capability)
ircdb.users.setUser(user)
irc.replySuccess()
else:
irc.error(_('You can\'t add capabilities you don\'t have.'))
add = wrap(add, ['otherUser', 'lowered'])
@internationalizeDocstring
def remove(self, irc, msg, args, user, capability):
"""<name|hostmask> <capability>
Takes from the user specified by <name> (or the user to whom
<hostmask> currently maps) the specified capability <capability>
"""
if ircdb.checkCapability(msg.prefix, capability) or \
ircdb.isAntiCapability(capability):
try:
user.removeCapability(capability)
ircdb.users.setUser(user)
irc.replySuccess()
except KeyError:
irc.error(_('That user doesn\'t have that capability.'))
else:
s = _('You can\'t remove capabilities you don\'t have.')
irc.error(s)
remove = wrap(remove, ['otherUser','lowered'])
class ignore(callbacks.Commands):
@internationalizeDocstring
def add(self, irc, msg, args, hostmask, expires):
"""<hostmask|nick> [<expires>]
This will set a persistent ignore on <hostmask> or the hostmask
currently associated with <nick>. <expires> is an optional argument
specifying when (in "seconds from now") the ignore will expire; if
it isn't given, the ignore will never automatically expire.
"""
ircdb.ignores.add(hostmask, expires)
irc.replySuccess()
add = wrap(add, ['hostmask', additional('expiry', 0)])
@internationalizeDocstring
def remove(self, irc, msg, args, hostmask):
"""<hostmask|nick>
This will remove the persistent ignore on <hostmask> or the
hostmask currently associated with <nick>.
"""
try:
ircdb.ignores.remove(hostmask)
irc.replySuccess()
except KeyError:
irc.error(_('%s wasn\'t in the ignores database.') % hostmask)
remove = wrap(remove, ['hostmask'])
@internationalizeDocstring
def list(self, irc, msg, args):
"""takes no arguments
Lists the hostmasks that the bot is ignoring.
"""
# XXX Add the expirations.
if ircdb.ignores.hostmasks:
irc.reply(format('%L', (list(map(repr,ircdb.ignores.hostmasks)))))
else:
irc.reply(_('I\'m not currently globally ignoring anyone.'))
list = wrap(list)
def clearq(self, irc, msg, args):
"""takes no arguments
Clears the current send queue for this network.
"""
irc.queue.reset()
irc.replySuccess()
clearq = wrap(clearq)
def acmd(self, irc, msg, args, commandAndArgs):
"""<command> [<arg> ...]
Perform <command> (with associated <arg>s on all channels on current network."""
for channel in irc.state.channels:
msg.args[0] = channel
self.Proxy(irc, msg, commandAndArgs)
acmd = wrap(acmd, ['admin', many('something')])
Class = Admin
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

134
plugins/Admin/test.py Normal file
View File

@ -0,0 +1,134 @@
###
# Copyright (c) 2002-2005, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from supybot.test import *
class AdminTestCase(PluginTestCase):
plugins = ('Admin',)
def testChannels(self):
def getAfterJoinMessages():
m = self.irc.takeMsg()
self.assertEqual(m.command, 'MODE')
m = self.irc.takeMsg()
self.assertEqual(m.command, 'MODE')
m = self.irc.takeMsg()
self.assertEqual(m.command, 'WHO')
self.assertRegexp('channels', 'not.*in any')
self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix))
getAfterJoinMessages()
self.assertRegexp('channels', '#foo')
self.irc.feedMsg(ircmsgs.join('#bar', prefix=self.prefix))
getAfterJoinMessages()
self.assertRegexp('channels', '#bar and #foo')
self.irc.feedMsg(ircmsgs.join('#Baz', prefix=self.prefix))
getAfterJoinMessages()
self.assertRegexp('channels', '#bar, #Baz, and #foo')
def testIgnoreAddRemove(self):
self.assertNotError('admin ignore add foo!bar@baz')
self.assertError('admin ignore add alsdkfjlasd')
self.assertNotError('admin ignore remove foo!bar@baz')
self.assertError('admin ignore remove foo!bar@baz')
def testIgnoreList(self):
self.assertNotError('admin ignore list')
self.assertNotError('admin ignore add foo!bar@baz')
self.assertNotError('admin ignore list')
self.assertNotError('admin ignore add foo!bar@baz')
self.assertRegexp('admin ignore list', 'foo')
def testCapabilityAdd(self):
self.assertError('capability add foo bar')
u = ircdb.users.newUser()
u.name = 'foo'
ircdb.users.setUser(u)
self.assertNotError('capability add foo bar')
self.assertError('addcapability foo baz')
self.assert_('bar' in u.capabilities)
ircdb.users.delUser(u.id)
def testCapabilityRemove(self):
self.assertError('capability remove foo bar')
u = ircdb.users.newUser()
u.name = 'foo'
ircdb.users.setUser(u)
self.assertNotError('capability add foo bar')
self.assert_('bar' in u.capabilities)
self.assertError('removecapability foo bar')
self.assertNotError('capability remove foo bar')
self.assert_(not 'bar' in u.capabilities)
ircdb.users.delUser(u.id)
def testJoin(self):
m = self.getMsg('join #foo')
self.assertEqual(m.command, 'JOIN')
self.assertEqual(m.args[0], '#foo')
m = self.getMsg('join #foo key')
self.assertEqual(m.command, 'JOIN')
self.assertEqual(m.args[0], '#foo')
self.assertEqual(m.args[1], 'key')
def testNick(self):
original = conf.supybot.nick()
try:
m = self.getMsg('nick foobar')
self.assertEqual(m.command, 'NICK')
self.assertEqual(m.args[0], 'foobar')
finally:
conf.supybot.networks.test.nick.setValue('')
def testAddCapabilityOwner(self):
self.assertError('admin capability add %s owner' % self.nick)
def testJoinOnOwnerInvite(self):
self.irc.feedMsg(ircmsgs.invite(conf.supybot.nick(), '#foo', prefix=self.prefix))
m = self.getMsg(' ')
self.assertEqual(m.command, 'JOIN')
self.assertEqual(m.args[0], '#foo')
def testNoJoinOnUnprivilegedInvite(self):
try:
world.testing = False
self.irc.feedMsg(ircmsgs.invite(conf.supybot.nick(), '#foo', prefix='foo!bar@baz'))
self.assertResponse('somecommand',
'Error: "somecommand" is not a valid command.')
finally:
world.testing = True
def testNoJoinOnUnprivilegedInvite(self):
try:
world.testing = False
self.irc.feedMsg(ircmsgs.invite(conf.supybot.nick(), '#foo\u0009', prefix='foo!bar@baz'))
self.assertResponse('somecommand',
'Error: "somecommand" is not a valid command.')
finally:
world.testing = True
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

74
plugins/Aka/README.md Normal file
View File

@ -0,0 +1,74 @@
This plugin allows the user to create various aliases (known as "Akas",
since Alias is the name of another plugin Aka is based on) to other
commands or combinations of other commands (via nested commands).
It is a good idea to always quote the commands that are being aliased so
that any nested commands are not immediately run.
Basic usage
-----------
### Alias
Add an aka, Alias, which eases the transitioning to Aka from Alias.
First we will load Alias and Aka.
```
<jamessan> @load Alias
<bot> jamessan: The operation succeeded.
<jamessan> @load Aka
<bot> jamessan: The operation succeeded.
```
Then we import the Alias database to Aka in case it exists and unload
Alias.
```
<jamessan> @importaliasdatabase
<bot> jamessan: The operation succeeded.
<jamessan> @unload Alias
<bot> jamessan: The operation succeeded.
```
And now we will finally add the Aka `alias` itself.
```
<jamessan> @aka add "alias" "aka $1 $*"
<bot> jamessan: The operation succeeded.
```
Now you can use Aka as you used Alias before.
### Trout
Add an aka, trout, which expects a word as an argument
```
<jamessan> @aka add trout "reply action slaps $1 with a large trout"
<bot> jamessan: The operation succeeded.
<jamessan> @trout me
* bot slaps me with a large trout
```
This `trout` aka requires the plugin `Reply` to be loaded since it
provides the `action` command.
### LastFM
Add an aka, `lastfm`, which expects a last.fm username and replies with
their most recently played item.
```
@aka add lastfm "rss [format concat http://ws.audioscrobbler.com/1.0/user/ [format concat [web urlquote $1] /recenttracks.rss]]"
```
This `lastfm` aka requires the following plugins to be loaded: `RSS`,
`Format` and `Web`.
`RSS` provides `rss`, `Format` provides `concat` and `Web` provides
`urlquote`.
Note that if the nested commands being aliased hadn't been quoted, then
those commands would have been run immediately, and `@lastfm` would always
reply with the same information, the result of those commands.

70
plugins/Aka/__init__.py Normal file
View File

@ -0,0 +1,70 @@
###
# Copyright (c) 2013, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
This plugin allows the user to create various aliases (known as "Akas", since
Alias is the name of another plugin Aka is based on) to other commands or
combinations of other commands (via nested commands).
"""
import supybot
import supybot.world as world
# Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you're keeping the plugin in CVS or some similar system.
__version__ = ""
__author__ = supybot.authors.progval
__maintainer__ = supybot.authors.limnoria_core
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
# This is a url where the most recent plugin package can be downloaded.
__url__ = '' # 'http://supybot.com/Members/yourname/Aka/download'
from . import config
from . import plugin
from importlib import reload
# In case we're being reloaded.
reload(config)
reload(plugin)
# Add more reloads here if you add third-party modules and want them to be
# reloaded when this plugin is reloaded. Don't forget to import them as well!
if world.testing:
from . import test
Class = plugin.Class
configure = config.configure
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

64
plugins/Aka/config.py Normal file
View File

@ -0,0 +1,64 @@
###
# Copyright (c) 2013, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.conf as conf
import supybot.registry as registry
try:
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization('Aka')
except:
# Placeholder that allows to run the plugin on a bot
# without the i18n module
_ = lambda x:x
def configure(advanced):
# This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified themself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Aka', True)
Aka = conf.registerPlugin('Aka')
# This is where your configuration variables (if any) should go. For example:
# conf.registerGlobalValue(Aka, 'someConfigVariableName',
# registry.Boolean(False, _("""Help for someConfigVariableName.""")))
conf.registerGlobalValue(Aka, 'maximumWordsInName',
registry.Integer(5, _("""The maximum number of words allowed in a
command name. Setting this to an high value may slow down your bot
on long commands.""")))
conf.registerGroup(Aka, 'web')
conf.registerGlobalValue(Aka.web, 'enable',
registry.Boolean(False, _("""Determines whether the Akas will be
browsable through the HTTP server.""")))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -0,0 +1 @@
# Stub so local is a module, used for third-party modules

253
plugins/Aka/locales/fi.po Normal file
View File

@ -0,0 +1,253 @@
# Aka plugin for Limnoria
# Copyright (C) 2014 Limnoria
# Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: Aka plugin for Limnoria\n"
"POT-Creation-Date: 2014-12-20 13:30+EET\n"
"PO-Revision-Date: 2014-12-20 13:57+0200\n"
"Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n"
"Language-Team: \n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 1.6.10\n"
#: config.py:55
msgid ""
"The maximum number of words allowed in a\n"
" command name. Setting this to an high value may slow down your bot\n"
" on long commands."
msgstr ""
"Komennon nimessä sallittujen merkkien enimmäismäärä.\n"
" Korkean arvon asettaminen tähän voi hidastaa bottiasi pitkien komentojen "
"kanssa."
#: plugin.py:140 plugin.py:264 plugin.py:507
msgid "This Aka already exists."
msgstr "Tämä Aka on jo olemassa."
#: plugin.py:169 plugin.py:181 plugin.py:195 plugin.py:291 plugin.py:308
#: plugin.py:325
msgid "This Aka does not exist."
msgstr "Tätä Akaa ei ole olemassa."
#: plugin.py:293
msgid "This Aka is already locked."
msgstr "Tämä Aka on jo lukittu."
#: plugin.py:310
msgid "This Aka is already unlocked."
msgstr "Tämä Aka on jo avattu."
#: plugin.py:372
msgid ""
"Aka is the improved version of the Alias plugin. It stores akas outside\n"
" of the bot.conf, which doesn't have risk of corrupting the bot.conf "
"file\n"
" (this often happens when there are Unicode issues). Aka also\n"
" introduces multi-worded akas."
msgstr ""
"Aka on paranneltu versio Alias pluginista. Se tallentaa akat bot.conf-"
"tiedoston ulkopuolelle, jollla ei ole\n"
" riskiä korruptoida bot.conf tiedostoa (joka tapahtuu usein Unicode-"
"ongelmien kanssa). Aka\n"
" tukee myös useamman akan pituisia akoja."
#: plugin.py:481
msgid "You've attempted more nesting than is currently allowed on this bot."
msgstr ""
"Olet yrittänyt sisällyttää enemmän komentoja, kuin tässä botti sallii juuri "
"nyt."
#: plugin.py:485
msgid " at least"
msgstr "ainakin"
#: plugin.py:494
msgid "Locked by %s at %s"
msgstr "Lukinnut %s aikaan %s"
#: plugin.py:497
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q.%s"
msgstr ""
"<alias,%s %n>\n"
"\n"
"Alias komennolle %q.%s"
#: plugin.py:498
msgid "argument"
msgstr "parametri"
#: plugin.py:504
msgid "You can't overwrite commands in this plugin."
msgstr "Et voi ylikirjoittaa tämän lisä-osan komentoja."
#: plugin.py:509
msgid "This Aka has too many spaces in its name."
msgstr "Tämän Akan nimessä on liian monta välilyöntiä."
#: plugin.py:514
msgid "Can't mix $* and optional args (@1, etc.)"
msgstr ""
"$*:ä ja vapaaehtoisia parametrejä (@1, jne.) ei voida sekoittaa keskenään"
#: plugin.py:516
msgid "There can be only one $* in an alias."
msgstr "Aliaksessa voi olla vain yksi $*."
#: plugin.py:523
msgid "This Aka is locked."
msgstr "Tämä Aka on lukittu."
#: plugin.py:527
#, fuzzy
msgid ""
"[--channel <#channel>] <name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand "
"argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, "
"etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, "
"@2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" arguments that have not replaced $1, $2, etc.\", ie. it will also\n"
" include optional arguments.\n"
" "
msgstr ""
"[--channel <#kanava>] <nimi> <komento>\n"
"\n"
"Määrittää aliaksen <nimi>, joka suorittaa <komennon>. <Komennon>\n"
" pitäisi olla tavallisessa muodossa \"komento parametri [sisällytettykomento "
"parametri]\"\n"
" parametreinä aliakselle; ne täytetään ensimmäisenä, toisena, jne.\n"
" parametreinä. $1, $2, jne. voidaan käyttää vaadittuina parametreinä. @1, "
"@2,\n"
" jne. voidaan käyttää vapaaehtoisina parametreinä. $* tarkoittaa "
"yksinkertaisesti \"kaikki\n"
" jotka eivät ole korvanneet $1, $2, jne.\", esim. se sisältää vapaa-ehtoiset "
"parametrit.\n"
" "
#: plugin.py:541 plugin.py:573 plugin.py:604 plugin.py:636 plugin.py:659
#: plugin.py:682
msgid "%r is not a valid channel."
msgstr "%r ei ole kelvollinen kanava."
#: plugin.py:559
#, fuzzy
msgid ""
"[--channel <#channel>] <name> <command>\n"
"\n"
" Overwrites an existing alias <name> to execute <command> instead. "
"The\n"
" <command> should be in the standard \"command argument "
"[nestedcommand\n"
" argument]\" arguments to the alias; they'll be filled with the "
"first,\n"
" second, etc. arguments. $1, $2, etc. can be used for required\n"
" arguments. @1, @2, etc. can be used for optional arguments. $* "
"simply\n"
" means \"all arguments that have not replaced $1, $2, etc.\", ie. it "
"will\n"
" also include optional arguments.\n"
" "
msgstr ""
"[--kanava <#kanava>] <nimi> <komento>\n"
" Ylikirjoittaa olemassa olevan aliaksen <nimi> suorittamaan <komennon> "
"sensijaan. <Komennon>\n"
" pitäisi olla standardissa \"komento parametri [sisäkkäinen komento\" "
"parametreinä aliakselle; ne täytetään\n"
" ensimmäisillä, toisilla jne. parametreillä. $1, $2, jne. voidaan käyttää "
"vaadittuihin parametreihin. $*\n"
" yksinkertaisesti tarkoittaa \"kaikki parametrin, joita ei ole korvattu $1, "
"$2 jne.\", esimerkiksi. se sisällyttää\n"
" myös kaikki vapaaehtoiset parametrit."
#: plugin.py:596
msgid ""
"[--channel <#channel>] <name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""
"[--channel <#kanava>] <nimi>\n"
"\n"
" Poistaa annetun aliaksen, ellei se ole lukittu.\n"
" "
#: plugin.py:618
msgid ""
"Check if the user has any of the required capabilities to manage\n"
" the regexp database."
msgstr ""
"Tarkistaa onko käyttäjällä vaadittu valtuus säännöllisten lausekkeiden\n"
" tietokannan hallintaan."
#: plugin.py:628
msgid ""
"[--channel <#channel>] <alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
"[--channel <#kanava>] <alias>\n"
"\n"
" Lukitsee aliaksen estäen muita muokkaamasta sitä.\n"
" "
#: plugin.py:651
msgid ""
"[--channel <#channel>] <alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
"[--channel <#kanava>] <alias>\n"
"\n"
" Avaa aliaksen, jotta kaikki voivat määrittää uusia aliaksia sen päälle.\n"
" "
#: plugin.py:674
msgid ""
"[--channel <#channel>] <alias>\n"
"\n"
" This command shows the content of an Aka.\n"
" "
msgstr ""
"<komento> <#kanava> <alias>\n"
"\n"
" Tämä komento näyttää Akan sisällön."
#: plugin.py:694
msgid ""
"takes no arguments\n"
"\n"
" Imports the Alias database into Aka's, and clean the former."
msgstr ""
"ei ota parametrejä\n"
"\n"
" Tuo Aliaksen tietokannan Akaan ja tyhjentää aiemman."
#: plugin.py:699
msgid "Alias plugin is not loaded."
msgstr "Alias lisä-osa ei ole ladattu."
#: plugin.py:709
msgid "Error occured when importing the %n: %L"
msgstr "Virhe komennon %n tuomisessa: %L"
#~ msgid ""
#~ "Add the help for 'plugin help Aka' here\n"
#~ " This should describe *how* to use this plugin."
#~ msgstr ""
#~ "Lisää ohje komentoa 'plugin help Aka' varten tähän.\n"
#~ " Tämän pitäisi kuvata *kuinka* tätä lisä-osaa käytetään."

191
plugins/Aka/messages.pot Normal file
View File

@ -0,0 +1,191 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2014-12-20 13:30+EET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
#: config.py:55
msgid ""
"The maximum number of words allowed in a\n"
" command name. Setting this to an high value may slow down your bot\n"
" on long commands."
msgstr ""
#: plugin.py:140 plugin.py:264 plugin.py:507
msgid "This Aka already exists."
msgstr ""
#: plugin.py:169 plugin.py:181 plugin.py:195 plugin.py:291 plugin.py:308
#: plugin.py:325
msgid "This Aka does not exist"
msgstr ""
#: plugin.py:293
msgid "This Aka is already locked."
msgstr ""
#: plugin.py:310
msgid "This Aka is already unlocked."
msgstr ""
#: plugin.py:372
#, docstring
msgid ""
"Aka is the improved version of the Alias plugin. It stores akas outside\n"
" of the bot.conf, which doesn't have risk of corrupting the bot.conf file\n"
" (this often happens when there are Unicode issues). Aka also\n"
" introduces multi-worded akas."
msgstr ""
#: plugin.py:481
msgid "You've attempted more nesting than is currently allowed on this bot."
msgstr ""
#: plugin.py:485
msgid " at least"
msgstr ""
#: plugin.py:494
msgid "Locked by %s at %s"
msgstr ""
#: plugin.py:497
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q.%s"
msgstr ""
#: plugin.py:498
msgid "argument"
msgstr ""
#: plugin.py:504
msgid "You can't overwrite commands in this plugin."
msgstr ""
#: plugin.py:509
msgid "This Aka has too many spaces in its name."
msgstr ""
#: plugin.py:514
msgid "Can't mix $* and optional args (@1, etc.)"
msgstr ""
#: plugin.py:516
msgid "There can be only one $* in an alias."
msgstr ""
#: plugin.py:523
msgid "This Aka is locked."
msgstr ""
#: plugin.py:527
#, docstring
msgid ""
"[--channel <#channel>] <name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, @2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" arguments that have not replaced $1, $2, etc.\", ie. it will also\n"
" include optional arguments.\n"
" "
msgstr ""
#: plugin.py:541 plugin.py:573 plugin.py:604 plugin.py:636 plugin.py:659
#: plugin.py:682
msgid "%r is not a valid channel."
msgstr ""
#: plugin.py:559
#, docstring
msgid ""
"[--channel <#channel>] <name> <command>\n"
"\n"
" Overwrites an existing alias <name> to execute <command> instead. The\n"
" <command> should be in the standard \"command argument [nestedcommand\n"
" argument]\" arguments to the alias; they'll be filled with the first,\n"
" second, etc. arguments. $1, $2, etc. can be used for required\n"
" arguments. @1, @2, etc. can be used for optional arguments. $* simply\n"
" means \"all arguments that have not replaced $1, $2, etc.\", ie. it will\n"
" also include optional arguments.\n"
" "
msgstr ""
#: plugin.py:596
#, docstring
msgid ""
"[--channel <#channel>] <name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""
#: plugin.py:618
#, docstring
msgid ""
"Check if the user has any of the required capabilities to manage\n"
" the regexp database."
msgstr ""
#: plugin.py:628
#, docstring
msgid ""
"[--channel <#channel>] <alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
#: plugin.py:651
#, docstring
msgid ""
"[--channel <#channel>] <alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
#: plugin.py:674
#, docstring
msgid ""
"[--channel <#channel>] <alias>\n"
"\n"
" This command shows the content of an Aka.\n"
" "
msgstr ""
#: plugin.py:689
msgid "This Aka does not exist."
msgstr ""
#: plugin.py:694
#, docstring
msgid ""
"takes no arguments\n"
"\n"
" Imports the Alias database into Aka's, and clean the former."
msgstr ""
#: plugin.py:699
msgid "Alias plugin is not loaded."
msgstr ""
#: plugin.py:709
msgid "Error occured when importing the %n: %L"
msgstr ""

952
plugins/Aka/plugin.py Normal file
View File

@ -0,0 +1,952 @@
###
# Copyright (c) 2013, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import re
import os
import sys
import datetime
import supybot.conf as conf
import supybot.utils as utils
import supybot.ircdb as ircdb
from supybot.commands import *
import supybot.plugins as plugins
import supybot.utils.minisix as minisix
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.httpserver as httpserver
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization('Aka')
try:
import sqlite3
except ImportError:
sqlite3 = None
try:
import sqlalchemy
import sqlalchemy.ext
import sqlalchemy.ext.declarative
except ImportError:
sqlalchemy = None
if not (sqlite3 or sqlalchemy):
raise callbacks.Error('You have to install python-sqlite3 or '
'python-sqlalchemy in order to load this plugin.')
available_db = {}
class Alias(object):
__slots__ = ('name', 'alias', 'locked', 'locked_by', 'locked_at')
def __init__(self, name, alias):
self.name = name
self.alias = alias
self.locked = False
self.locked_by = None
self.locked_at = None
def __repr__(self):
return "<Alias('%r', '%r')>" % (self.name, self.alias)
if sqlite3:
class SQLiteAkaDB(object):
__slots__ = ('engines', 'filename', 'dbs',)
def __init__(self, filename):
self.engines = ircutils.IrcDict()
self.filename = filename.replace('sqlite3', 'sqlalchemy')
def close(self):
self.dbs.clear()
def get_db(self, channel):
if channel in self.engines:
engine = self.engines[channel]
else:
filename = plugins.makeChannelFilename(self.filename, channel)
exists = os.path.exists(filename)
engine = sqlite3.connect(filename, check_same_thread=False)
if not exists:
cursor = engine.cursor()
cursor.execute("""CREATE TABLE aliases (
id INTEGER NOT NULL,
name VARCHAR NOT NULL,
alias VARCHAR NOT NULL,
locked BOOLEAN NOT NULL,
locked_by VARCHAR,
locked_at DATETIME,
PRIMARY KEY (id),
UNIQUE (name))""")
engine.commit()
self.engines[channel] = engine
assert engine.execute("select 1").fetchone() == (1,)
return engine
def has_aka(self, channel, name):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
db = self.get_db(channel)
return self.get_db(channel).cursor() \
.execute("""SELECT COUNT() as count
FROM aliases WHERE name = ?;""", (name,)) \
.fetchone()[0]
def get_aka_list(self, channel):
cursor = self.get_db(channel).cursor()
cursor.execute("""SELECT name FROM aliases;""")
list_ = cursor.fetchall()
return list_
def get_alias(self, channel, name):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
cursor = self.get_db(channel).cursor()
cursor.execute("""SELECT alias FROM aliases
WHERE name = ?;""", (name,))
r = cursor.fetchone()
if r:
return r[0]
else:
return None
def add_aka(self, channel, name, alias):
name = callbacks.canonicalName(name, preserve_spaces=True)
if self.has_aka(channel, name):
raise AkaError(_('This Aka already exists.'))
if minisix.PY2:
if isinstance(name, str):
name = name.decode('utf8')
if isinstance(alias, str):
alias = alias.decode('utf8')
db = self.get_db(channel)
cursor = db.cursor()
cursor.execute("""INSERT INTO aliases VALUES (
NULL, ?, ?, 0, NULL, NULL);""", (name, alias))
db.commit()
def remove_aka(self, channel, name):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
db = self.get_db(channel)
db.cursor().execute('DELETE FROM aliases WHERE name = ?', (name,))
db.commit()
def lock_aka(self, channel, name, by):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
db = self.get_db(channel)
cursor = db.cursor().execute("""UPDATE aliases
SET locked=1, locked_at=?, locked_by=? WHERE name = ?""",
(datetime.datetime.now(), by, name))
if cursor.rowcount == 0:
raise AkaError(_('This Aka does not exist.'))
db.commit()
def unlock_aka(self, channel, name, by):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
db = self.get_db(channel)
cursor = db.cursor()
cursor.execute("""UPDATE aliases SET locked=0, locked_at=?
WHERE name = ?""", (datetime.datetime.now(), name))
if cursor.rowcount == 0:
raise AkaError(_('This Aka does not exist.'))
db.commit()
def get_aka_lock(self, channel, name):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
cursor = self.get_db(channel).cursor()
cursor.execute("""SELECT locked, locked_by, locked_at
FROM aliases WHERE name = ?;""", (name,))
r = cursor.fetchone()
if r:
return (bool(r[0]), r[1], r[2])
else:
raise AkaError(_('This Aka does not exist.'))
def get_all(self, channel):
cursor = self.get_db(channel).cursor()
cursor.execute("""
SELECT id, name, alias, locked, locked_by, locked_at
FROM aliases;
""")
return cursor.fetchall()
available_db.update({'sqlite3': SQLiteAkaDB})
elif sqlalchemy:
Base = sqlalchemy.ext.declarative.declarative_base()
class SQLAlchemyAlias(Alias, Base):
__slots__ = ()
__tablename__ = 'aliases'
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
name = sqlalchemy.Column(sqlalchemy.String, unique=True, nullable=False)
alias = sqlalchemy.Column(sqlalchemy.String, nullable=False)
locked = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False)
locked_by = sqlalchemy.Column(sqlalchemy.String, nullable=True)
locked_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True)
# TODO: Add table for usage statistics
class SqlAlchemyAkaDB(object):
__slots__ = ('engines', 'filename', 'sqlalchemy', 'dbs')
def __init__(self, filename):
self.engines = ircutils.IrcDict()
self.filename = filename
self.sqlalchemy = sqlalchemy
def close(self):
self.dbs.clear()
def get_db(self, channel):
if channel in self.engines:
engine = self.engines[channel]
else:
filename = plugins.makeChannelFilename(self.filename, channel)
exists = os.path.exists(filename)
engine = sqlalchemy.create_engine('sqlite:///' + filename)
if not exists:
Base.metadata.create_all(engine)
self.engines[channel] = engine
assert engine.execute("select 1").scalar() == 1
Session = sqlalchemy.orm.sessionmaker()
Session.configure(bind=engine)
return Session()
def has_aka(self, channel, name):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
count = self.get_db(channel).query(SQLAlchemyAlias) \
.filter(SQLAlchemyAlias.name == name) \
.count()
return bool(count)
def get_aka_list(self, channel):
list_ = list(self.get_db(channel).query(SQLAlchemyAlias.name))
return list_
def get_alias(self, channel, name):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
try:
return self.get_db(channel).query(SQLAlchemyAlias.alias) \
.filter(SQLAlchemyAlias.name == name).one()[0]
except sqlalchemy.orm.exc.NoResultFound:
return None
def add_aka(self, channel, name, alias):
name = callbacks.canonicalName(name, preserve_spaces=True)
if self.has_aka(channel, name):
raise AkaError(_('This Aka already exists.'))
if minisix.PY2:
if isinstance(name, str):
name = name.decode('utf8')
if isinstance(alias, str):
alias = alias.decode('utf8')
db = self.get_db(channel)
db.add(SQLAlchemyAlias(name, alias))
db.commit()
def remove_aka(self, channel, name):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
db = self.get_db(channel)
db.query(SQLAlchemyAlias).filter(SQLAlchemyAlias.name == name).delete()
db.commit()
def lock_aka(self, channel, name, by):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
db = self.get_db(channel)
try:
aka = db.query(SQLAlchemyAlias) \
.filter(SQLAlchemyAlias.name == name).one()
except sqlalchemy.orm.exc.NoResultFound:
raise AkaError(_('This Aka does not exist.'))
if aka.locked:
raise AkaError(_('This Aka is already locked.'))
aka.locked = True
aka.locked_by = by
aka.locked_at = datetime.datetime.now()
db.commit()
def unlock_aka(self, channel, name, by):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
db = self.get_db(channel)
try:
aka = db.query(SQLAlchemyAlias) \
.filter(SQLAlchemyAlias.name == name).one()
except sqlalchemy.orm.exc.NoResultFound:
raise AkaError(_('This Aka does not exist.'))
if not aka.locked:
raise AkaError(_('This Aka is already unlocked.'))
aka.locked = False
aka.locked_by = by
aka.locked_at = datetime.datetime.now()
db.commit()
def get_aka_lock(self, channel, name):
name = callbacks.canonicalName(name, preserve_spaces=True)
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
try:
return self.get_db(channel) \
.query(SQLAlchemyAlias.locked, SQLAlchemyAlias.locked_by, SQLAlchemyAlias.locked_at)\
.filter(SQLAlchemyAlias.name == name).one()
except sqlalchemy.orm.exc.NoResultFound:
raise AkaError(_('This Aka does not exist.'))
def get_all(self, channel):
akas = self.get_db(channel).query(
SQLAlchemyAlias.id, SQLAlchemyAlias.name, SQLAlchemyAlias.alias,
SQLAlchemyAlias.locked, SQLAlchemyAlias.locked_by, SQLAlchemyAlias.locked_at
).all()
return map(
lambda aka: (aka.name, aka.alias, aka.locked, aka.locked_by, aka.locked_at),
akas
)
available_db.update({'sqlalchemy': SqlAlchemyAkaDB})
def getArgs(args, required=1, optional=0, wildcard=0):
if len(args) < required:
raise callbacks.ArgumentError
if len(args) < required + optional:
ret = list(args) + ([''] * (required + optional - len(args)))
elif len(args) >= required + optional:
if not wildcard:
ret = list(args[:required + optional - 1])
ret.append(' '.join(args[required + optional - 1:]))
else:
ret = list(args)
return ret
class AkaError(Exception):
pass
class RecursiveAlias(AkaError):
pass
dollarRe = re.compile(r'\$(\d+)')
def findBiggestDollar(alias):
dollars = dollarRe.findall(alias)
dollars = list(map(int, dollars))
dollars.sort()
if dollars:
return dollars[-1]
else:
return 0
atRe = re.compile(r'@(\d+)')
def findBiggestAt(alias):
ats = atRe.findall(alias)
ats = list(map(int, ats))
ats.sort()
if ats:
return ats[-1]
else:
return 0
if 'sqlite3' in conf.supybot.databases() and 'sqlite3' in available_db:
AkaDB = SQLiteAkaDB
elif 'sqlalchemy' in conf.supybot.databases() and 'sqlalchemy' in available_db:
log.warning('Aka\'s only enabled database engine is SQLAlchemy, which '
'is deprecated. Please consider adding \'sqlite3\' to '
'supybot.databases (and/or install sqlite3).')
AkaDB = SqlAlchemyAkaDB
else:
raise plugins.NoSuitableDatabase(['sqlite3', 'sqlalchemy'])
class AkaHTTPCallback(httpserver.SupyHTTPServerCallback):
name = 'Aka web interface'
base_template = '''\
<!DOCTYPE html>
<html>
<head>
<title>Aka</title>
<link rel="stylesheet" href="/default.css">
</head>
<body>
<h1>Aka</h1>
%s
</body>
</html>'''
index_template = base_template % '''\
<p>To view the global Akas either click <a href="list/global">here</a> or
enter 'global' in the form below.</p>
<form action="" method="post">
<label for="channel">Channel name:</label>
<input type="text" placeholder="#channel" name="channel" id="channel">
<input type="submit" name="submit" value="view">
</form>'''
list_template = base_template % '''\
<table>
<thead>
<th>Name</th>
<th>Alias</th>
<th>Locked</th>
</thead>
%s
</table>'''
def doGet(self, handler, path, *args, **kwargs):
if path == '/':
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.write(self.index_template)
elif path.startswith('/list/'):
parts = path.split('/')
channel = parts[2] if len(parts) == 3 else 'global'
channel = utils.web.urlunquote(channel)
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
akas = {}
for aka in self._plugin._db.get_all('global'):
akas[aka[1]] = aka
if channel != 'global':
for aka in self._plugin._db.get_all(channel):
akas[aka[1]] = aka
aka_rows = []
for (name, aka) in sorted(akas.items()):
(id, name, alias, locked, locked_by, locked_at) = aka
locked_column = 'False'
if locked:
locked_column = format(
_('By %s at %s'),
locked_by,
locked_at.split('.')[0],
)
aka_rows.append(
format(
"""
<tr>
<td style="white-space: nowrap;"><code>%s</code></td>
<td><code>%s<code></td>
<td style="white-space: nowrap;">%s</td>
</tr>
""",
utils.web.html_escape(name),
utils.web.html_escape(alias),
utils.web.html_escape(locked_column),
)
)
self.write(format(self.list_template, ''.join(aka_rows)))
def doPost(self, handler, path, form, *args, **kwargs):
if path == '/' and 'channel' in form:
self.send_response(303)
self.send_header(
'Location',
format('list/%s', utils.web.urlquote(form['channel'].value))
)
self.end_headers()
else:
self.send_response(400)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.write('Missing field \'channel\'.')
class Aka(callbacks.Plugin):
"""Aka is the improved version of the Alias plugin. It stores akas outside
of the bot.conf, which doesn't have risk of corrupting the bot.conf file
(this often happens when there are Unicode issues). Aka also
introduces multi-worded akas."""
def __init__(self, irc):
self.__parent = super(Aka, self)
self.__parent.__init__(irc)
# "sqlalchemy" is only for backward compatibility
filename = conf.supybot.directories.data.dirize('Aka.sqlalchemy.db')
self._db = AkaDB(filename)
self._http_running = False
conf.supybot.plugins.Aka.web.enable.addCallback(self._httpConfCallback)
if self.registryValue('web.enable'):
self._startHttp()
def die(self):
if self._http_running:
self._stopHttp()
def _httpConfCallback(self):
if self.registryValue('web.enable'):
if not self._http_running:
self._startHttp()
else:
if self._http_running:
self._stopHttp()
def _startHttp(self):
callback = AkaHTTPCallback()
callback._plugin = self
httpserver.hook('aka', callback)
self._http_running = True
def _stopHttp(self):
httpserver.unhook('aka')
self._http_running = False
def isCommandMethod(self, name):
args = name.split(' ')
if '|' in args:
return False
if len(args) > 1 and \
callbacks.canonicalName(args[0]) != self.canonicalName():
for cb in dynamic.irc.callbacks: # including this plugin
if cb.isCommandMethod(' '.join(args[0:-1])):
return False
if minisix.PY2 and isinstance(name, str):
name = name.decode('utf8')
channel = dynamic.channel or 'global'
return self._db.has_aka(channel, name) or \
self._db.has_aka('global', name) or \
self.__parent.isCommandMethod(name)
isCommand = isCommandMethod
def listCommands(self):
commands = ['add', 'remove', 'lock', 'unlock', 'importaliasdatabase',
'show', 'list', 'set', 'search']
return commands
def getCommand(self, args, check_other_plugins=True):
canonicalName = callbacks.canonicalName
# All the code from here to the 'for' loop is copied from callbacks.py
assert args == list(map(canonicalName, args))
first = args[0]
for cb in self.cbs:
if first == cb.canonicalName():
return cb.getCommand(args[1:])
if first == self.canonicalName() and len(args) > 1:
ret = self.getCommand(args[1:], False)
if ret:
return [first] + ret
max_length = self.registryValue('maximumWordsInName')
for i in range(1, min(len(args)+1, max_length)):
if self.isCommandMethod(callbacks.formatCommand(args[0:i])):
return args[0:i]
return []
def getCommandMethod(self, command):
if len(command) == 1 or command[0] == self.canonicalName():
try:
return self.__parent.getCommandMethod(command)
except AttributeError:
pass
name = callbacks.formatCommand(command)
channel = dynamic.channel or 'global'
original = self._db.get_alias(channel, name)
if not original:
original = self._db.get_alias('global', name)
biggestDollar = findBiggestDollar(original)
biggestAt = findBiggestAt(original)
wildcard = '$*' in original
def f(irc, msg, args):
tokens = callbacks.tokenize(original)
if biggestDollar or biggestAt:
args = getArgs(args, required=biggestDollar, optional=biggestAt,
wildcard=wildcard)
remaining_len = conf.supybot.reply.maximumLength()
for (i, arg) in enumerate(args):
if remaining_len < len(arg):
arg = arg[0:remaining_len]
args[i+1:] = []
break
remaining_len -= len(arg)
def regexpReplace(m):
idx = int(m.group(1))
return args[idx-1]
def replace(tokens, replacer):
for (i, token) in enumerate(tokens):
if isinstance(token, list):
replace(token, replacer)
else:
tokens[i] = replacer(token)
replace(tokens, lambda s: dollarRe.sub(regexpReplace, s))
if biggestAt:
assert not wildcard
args = args[biggestDollar:]
replace(tokens, lambda s: atRe.sub(regexpReplace, s))
if wildcard:
assert not biggestAt
# Gotta remove the things that have already been subbed in.
i = biggestDollar
args[:] = args[i:]
def everythingReplace(tokens):
skip = 0
for (i, token) in enumerate(tokens):
if skip:
skip -= 1
continue
if isinstance(token, list):
everythingReplace(token)
if token == '$*':
tokens[i:i+1] = args
skip = len(args)-1 # do not make replacements in
# tokens we just added
elif '$*' in token:
tokens[i] = token.replace('$*', ' '.join(args))
everythingReplace(tokens)
maxNesting = conf.supybot.commands.nested.maximum()
if maxNesting and irc.nested+1 > maxNesting:
irc.error(_('You\'ve attempted more nesting than is '
'currently allowed on this bot.'), Raise=True)
self.Proxy(irc, msg, tokens, nested=irc.nested+1)
if biggestDollar and (wildcard or biggestAt):
flexargs = _(' at least')
else:
flexargs = ''
try:
lock = self._db.get_aka_lock(channel, name)
except AkaError:
lock = self._db.get_aka_lock('global', name)
(locked, locked_by, locked_at) = lock
if locked:
lock = ' ' + _('Locked by %s at %s') % (locked_by, locked_at)
else:
lock = ''
escaped_command = original.replace('\\', '\\\\').replace('"', '\\"')
if channel == 'global':
doc = format(_('<a global alias,%s %n>\n\nAlias for %q.%s'),
flexargs, (biggestDollar, _('argument')),
escaped_command, lock)
else:
doc = format(_('<an alias on %s,%s %n>\n\nAlias for %q.%s'),
channel, flexargs, (biggestDollar, _('argument')),
escaped_command, lock)
f = utils.python.changeFunctionName(f, name, doc)
return f
def _add_aka(self, channel, name, alias):
if self.__parent.isCommandMethod(name):
raise AkaError(_('You can\'t overwrite commands in '
'this plugin.'))
if self._db.has_aka(channel, name):
raise AkaError(_('This Aka already exists.'))
if len(name.split(' ')) > self.registryValue('maximumWordsInName'):
raise AkaError(_('This Aka has too many spaces in its name.'))
biggestDollar = findBiggestDollar(alias)
biggestAt = findBiggestAt(alias)
wildcard = '$*' in alias
if biggestAt and wildcard:
raise AkaError(_('Can\'t mix $* and optional args (@1, etc.)'))
self._db.add_aka(channel, name, alias)
def _remove_aka(self, channel, name, evenIfLocked=False):
if not evenIfLocked:
(locked, by, at) = self._db.get_aka_lock(channel, name)
if locked:
raise AkaError(_('This Aka is locked.'))
self._db.remove_aka(channel, name)
def add(self, irc, msg, args, optlist, name, alias):
"""[--channel <#channel>] <name> <command>
Defines an alias <name> that executes <command>. The <command>
should be in the standard "command argument [nestedcommand argument]"
arguments to the alias; they'll be filled with the first, second, etc.
arguments. $1, $2, etc. can be used for required arguments. @1, @2,
etc. can be used for optional arguments. $* simply means "all
arguments that have not replaced $1, $2, etc.", ie. it will also
include optional arguments.
"""
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not irc.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
if ' ' not in alias:
# If it's a single word, they probably want $*.
alias += ' $*'
try:
self._add_aka(channel, name, alias)
self.log.info('Adding Aka %r for %r (from %s)',
name, alias, msg.prefix)
irc.replySuccess()
except AkaError as e:
irc.error(str(e))
add = wrap(add, [getopts({
'channel': 'somethingWithoutSpaces',
}), 'something', 'text'])
def set(self, irc, msg, args, optlist, name, alias):
"""[--channel <#channel>] <name> <command>
Overwrites an existing alias <name> to execute <command> instead. The
<command> should be in the standard "command argument [nestedcommand
argument]" arguments to the alias; they'll be filled with the first,
second, etc. arguments. $1, $2, etc. can be used for required
arguments. @1, @2, etc. can be used for optional arguments. $* simply
means "all arguments that have not replaced $1, $2, etc.", ie. it will
also include optional arguments.
"""
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not irc.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
try:
self._remove_aka(channel, name)
except AkaError as e:
irc.error(str(e), Raise=True)
if ' ' not in alias:
# If it's a single word, they probably want $*.
alias += ' $*'
try:
self._add_aka(channel, name, alias)
self.log.info('Setting Aka %r to %r (from %s)',
name, alias, msg.prefix)
irc.replySuccess()
except AkaError as e:
irc.error(str(e))
set = wrap(set, [getopts({
'channel': 'somethingWithoutSpaces',
}), 'something', 'text'])
def remove(self, irc, msg, args, optlist, name):
"""[--channel <#channel>] <name>
Removes the given alias, if unlocked.
"""
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not irc.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
try:
self._remove_aka(channel, name)
self.log.info('Removing Aka %r (from %s)', name, msg.prefix)
irc.replySuccess()
except AkaError as e:
irc.error(str(e))
remove = wrap(remove, [getopts({
'channel': 'somethingWithoutSpaces',
}), 'something'])
def _checkManageCapabilities(self, irc, msg, channel):
"""Check if the user has any of the required capabilities to manage
the regexp database."""
if channel != 'global':
capability = ircdb.makeChannelCapability(channel, 'op')
else:
capability = 'admin'
if not ircdb.checkCapability(msg.prefix, capability):
irc.errorNoCapability(capability, Raise=True)
def lock(self, irc, msg, args, optlist, user, name):
"""[--channel <#channel>] <alias>
Locks an alias so that no one else can change it.
"""
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not irc.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
self._checkManageCapabilities(irc, msg, channel)
try:
self._db.lock_aka(channel, name, user.name)
except AkaError as e:
irc.error(str(e))
else:
irc.replySuccess()
lock = wrap(lock, [getopts({
'channel': 'somethingWithoutSpaces',
}), 'user', 'something'])
def unlock(self, irc, msg, args, optlist, user, name):
"""[--channel <#channel>] <alias>
Unlocks an alias so that people can define new aliases over it.
"""
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not irc.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
self._checkManageCapabilities(irc, msg, channel)
try:
self._db.unlock_aka(channel, name, user.name)
except AkaError as e:
irc.error(str(e))
else:
irc.replySuccess()
unlock = wrap(unlock, [getopts({
'channel': 'somethingWithoutSpaces',
}), 'user', 'something'])
def show(self, irc, msg, args, optlist, name):
"""[--channel <#channel>] <alias>
This command shows the content of an Aka.
"""
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not irc.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
command = self._db.get_alias(channel, name)
if command:
irc.reply(command)
else:
irc.error(_('This Aka does not exist.'))
show = wrap(show, [getopts({'channel': 'somethingWithoutSpaces'}),
'text'])
def importaliasdatabase(self, irc, msg, args):
"""takes no arguments
Imports the Alias database into Aka's, and clean the former."""
alias_plugin = irc.getCallback('Alias')
if alias_plugin is None:
irc.error(_('Alias plugin is not loaded.'), Raise=True)
errors = {}
for (name, (command, locked, func)) in \
list(alias_plugin.aliases.items()):
try:
self._add_aka('global', name, command)
except AkaError as e:
errors[name] = e.args[0]
else:
alias_plugin.removeAlias(name, evenIfLocked=True)
if errors:
irc.error(format(_('Error occured when importing the %n: %L'),
(len(errors), 'following', 'command'),
['%s (%s)' % x for x in errors.items()]))
else:
irc.replySuccess()
importaliasdatabase = wrap(importaliasdatabase, ['owner'])
def list(self, irc, msg, args, optlist):
"""[--channel <#channel>] [--keys] [--unlocked|--locked]
Lists all Akas defined for <channel>. If <channel> is not specified,
lists all global Akas. If --keys is given, lists only the Aka names
and not their commands."""
channel = 'global'
filterlocked = filterunlocked = False
for (option, arg) in optlist:
if option == 'channel':
if not irc.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
if option == 'locked':
filterlocked = True
if option == 'unlocked':
filterunlocked = True
aka_list = self._db.get_aka_list(channel)
if filterlocked and filterunlocked:
irc.error(_('--locked and --unlocked are incompatible options.'), Raise=True)
elif filterlocked:
aka_list = [aka for aka in aka_list if
self._db.get_aka_lock(channel, aka)[0]]
elif filterunlocked:
aka_list = [aka for aka in aka_list if not
self._db.get_aka_lock(channel, aka)[0]]
if aka_list:
if 'keys' in dict(optlist):
# Strange, aka_list is a list of one length tuples
s = [k[0] for k in aka_list]
oneToOne = True
else:
aka_values = [self._db.get_alias(channel, aka) for aka in
aka_list]
s = ('{0}: "{1}"'.format(ircutils.bold(k), v) for (k, v) in
zip(aka_list, aka_values))
oneToOne = None
irc.replies(s, oneToOne=oneToOne)
else:
irc.error(_("No Akas found."))
list = wrap(list, [getopts({'channel': 'channel', 'keys': '', 'locked': '',
'unlocked': ''})])
def search(self, irc, msg, args, optlist, query):
"""[--channel <#channel>] <query>
Searches Akas defined for <channel>. If <channel> is not specified,
searches all global Akas."""
query = callbacks.canonicalName(query, preserve_spaces=True)
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not irc.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
aka_list = self._db.get_aka_list(channel)
aka_list = [callbacks.canonicalName(k[0], preserve_spaces=True)
for k in aka_list]
matching = [aka for aka in aka_list if query in aka]
if matching:
irc.replies(matching)
else:
irc.error(_("No matching Akas were found."))
search = wrap(search, [getopts({'channel': 'channel'}), 'text'])
Class = Aka
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

344
plugins/Aka/test.py Normal file
View File

@ -0,0 +1,344 @@
# -*- coding: utf8 -*-
###
# Copyright (c) 2002-2004, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from supybot.test import *
import supybot.conf as conf
import supybot.httpserver as httpserver
import supybot.plugin as plugin
import supybot.registry as registry
from supybot.utils.minisix import u
from . import plugin as Aka
class FunctionsTest(SupyTestCase):
def testFindBiggestDollar(self):
self.assertEqual(Aka.findBiggestDollar(''), 0)
self.assertEqual(Aka.findBiggestDollar('foo'), 0)
self.assertEqual(Aka.findBiggestDollar('$0'), 0)
self.assertEqual(Aka.findBiggestDollar('$1'), 1)
self.assertEqual(Aka.findBiggestDollar('$2'), 2)
self.assertEqual(Aka.findBiggestDollar('$2 $10'), 10)
self.assertEqual(Aka.findBiggestDollar('$3'), 3)
self.assertEqual(Aka.findBiggestDollar('$3 $2 $1'), 3)
self.assertEqual(Aka.findBiggestDollar('foo bar $1'), 1)
self.assertEqual(Aka.findBiggestDollar('foo $2 $1'), 2)
self.assertEqual(Aka.findBiggestDollar('foo $0 $1'), 1)
self.assertEqual(Aka.findBiggestDollar('foo $1 $3'), 3)
self.assertEqual(Aka.findBiggestDollar('$10 bar $1'), 10)
class AkaChannelTestCase(ChannelPluginTestCase):
plugins = ('Aka', 'Conditional', 'Filter', 'Math', 'Utilities',
'Format', 'Reply', 'String')
def testHistsearch(self):
self.assertNotError(
r'aka add histsearch "last --from [cif true '
r'\"echo test\" \"echo test\"] '
r'--regexp [concat \"m/$1/\" [re s/g// \"@2\"]]"')
self.assertResponse('echo foo', 'foo')
self.assertResponse('histsearch .*foo.*', '@echo foo')
def testDoesNotOverwriteCommands(self):
# We don't have dispatcher commands anymore
#self.assertError('aka add aka "echo foo bar baz"')
self.assertError('aka add add "echo foo bar baz"')
self.assertError('aka add remove "echo foo bar baz"')
self.assertError('aka add lock "echo foo bar baz"')
self.assertError('aka add unlock "echo foo bar baz"')
def testAkaHelp(self):
self.assertNotError(r'aka add slashdot "foo \"bar\" baz"')
self.assertRegexp('help slashdot', r'Alias for "foo \\"bar\\" baz".')
self.assertNotError('aka add nonascii echo éé')
self.assertRegexp('help nonascii', r'Alias for "echo éé".')
self.assertNotError('aka remove slashdot')
self.assertNotError('aka add --channel %s slashdot foo' % self.channel)
self.assertRegexp('help aka slashdot', "an alias on %s.*Alias for .*foo"
% self.channel)
self.assertNotError('aka remove --channel %s slashdot' % self.channel)
def testShow(self):
self.assertNotError('aka add foo bar')
self.assertResponse('show foo', 'bar $*')
self.assertNotError('aka add "foo bar" baz')
self.assertResponse('show "foo bar"', 'baz $*')
def testRemove(self):
self.assertNotError('aka add foo echo bar')
self.assertResponse('foo', 'bar')
self.assertNotError('aka remove foo')
self.assertError('foo')
def testDollars(self):
self.assertNotError('aka add rot26 "rot13 [rot13 $1]"')
self.assertResponse('rot26 foobar', 'foobar')
def testMoreDollars(self):
self.assertNotError('aka add rev "echo $3 $2 $1"')
self.assertResponse('rev foo bar baz', 'baz bar foo')
def testAllArgs(self):
self.assertNotError('aka add swap "echo $2 $1 $*"')
self.assertResponse('swap 1 2 3 4 5', '2 1 3 4 5')
self.assertError('aka add foo "echo $1 @1 $*"')
self.assertNotError('aka add moo echo $1 $*')
self.assertError('moo')
self.assertResponse('moo foo', 'foo')
self.assertResponse('moo foo bar', 'foo bar')
self.assertNotError('aka add spam "echo [echo $*]"')
self.assertResponse('spam egg', 'egg')
self.assertResponse('spam egg bacon', 'egg bacon')
self.assertNotError('aka add doublespam "echo [echo $* $*]"')
self.assertResponse('doublespam egg', 'egg egg')
self.assertResponse('doublespam egg bacon', 'egg bacon egg bacon')
def testExpansionBomb(self):
self.assertNotError('aka add bomb "bomb $* $* $* $* $*"')
# if the mitigation doesn't work, this test will eat all memory on the
# system.
self.assertResponse('bomb foo', "Error: You've attempted more nesting "
"than is currently allowed on this bot.")
def testChannel(self):
self.assertNotError('aka add channel echo $channel')
self.assertResponse('aka channel', self.channel)
def testAddRemoveAka(self):
cb = self.irc.getCallback('Aka')
cb._add_aka('global', 'foobar', 'echo sbbone')
cb._db.lock_aka('global', 'foobar', 'evil_admin')
self.assertResponse('foobar', 'sbbone')
self.assertRegexp('aka list', 'foobar')
self.assertRaises(Aka.AkaError, cb._remove_aka, 'global', 'foobar')
cb._remove_aka('global', 'foobar', evenIfLocked=True)
self.assertNotRegexp('aka list', 'foobar')
self.assertError('foobar')
def testOptionalArgs(self):
self.assertNotError('aka add myrepr "repr @1"')
self.assertResponse('myrepr foo', '"foo"')
self.assertResponse('myrepr ""', '""')
def testRequiredAndOptional(self):
self.assertNotError('aka add reqopt "echo req=$1, opt=@1"')
self.assertResponse('reqopt foo bar', 'req=foo, opt=bar')
self.assertResponse('reqopt foo', 'req=foo, opt=')
def testNoExtraSpaces(self):
self.assertNotError('aka add foo "action takes $1\'s money"')
self.assertResponse('foo bar', '\x01ACTION takes bar\'s money\x01')
def testNoExtraQuotes(self):
self.assertNotError('aka add myre "echo s/$1/$2/g"')
self.assertResponse('myre foo bar', 's/foo/bar/g')
def testSimpleAkaWithoutArgsImpliesDollarStar(self):
self.assertNotError('aka add exo echo')
self.assertResponse('exo foo bar baz', 'foo bar baz')
def testChannelPriority(self):
self.assertNotError('aka add spam "echo foo"')
self.assertNotError('aka add --channel %s spam "echo bar"' %
self.channel)
self.assertResponse('spam', 'bar')
self.assertNotError('aka add --channel %s egg "echo baz"' %
self.channel)
self.assertNotError('aka add egg "echo qux"')
self.assertResponse('egg', 'baz')
def testComplicatedNames(self):
self.assertNotError(u('aka add café "echo coffee"'))
self.assertResponse(u('café'), 'coffee')
self.assertNotError('aka add "foo bar" "echo spam"')
self.assertResponse('foo bar', 'spam')
self.assertNotError('aka add "foo" "echo egg"')
self.assertResponse('foo', 'egg')
# You could expect 'spam' here, but in fact, this is dangerous.
# Just imagine this session:
# <evil_user> aka add "echo foo" quit
# <bot> The operation succeeded.
# ...
# <owner> echo foo
# * bot has quit
self.assertResponse('foo bar', 'egg')
def testNoOverride(self):
self.assertNotError('aka add "echo foo" "echo bar"')
self.assertResponse('echo foo', 'foo')
self.assertNotError('aka add foo "echo baz"')
self.assertNotError('aka add "foo bar" "echo qux"')
self.assertResponse('foo bar', 'baz')
def testRecursivity(self):
self.assertNotError('aka add fact '
r'"cif [nceq $1 0] \"echo 1\" '
r'\"calc $1 * [fact [calc $1 - 1]]\""')
self.assertResponse('fact 4', '24')
self.assertRegexp('fact 50', 'more nesting')
def testDollarStarNesting(self):
self.assertResponse('aka add alias aka $*', 'The operation succeeded.')
self.assertResponse('alias add a+ aka add $*', 'The operation succeeded.')
self.assertResponse('a+ spam echo egg', 'The operation succeeded.')
self.assertResponse('spam', 'egg')
def testIgnore(self):
self.assertResponse('aka add test ignore', 'The operation succeeded.')
self.assertNoResponse('test')
class AkaTestCase(PluginTestCase):
plugins = ('Aka', 'Alias', 'User', 'Utilities')
def testMaximumLength(self):
self.assertNotError('aka add "foo bar baz qux quux" "echo test"')
self.assertError('aka add "foo bar baz qux quux corge" "echo test"')
def testAkaLockedHelp(self):
self.assertNotError('register evil_admin foo')
self.assertNotError('aka add slashdot foo')
self.assertRegexp('help aka slashdot', "a global alias.*Alias for .*foo")
self.assertNotRegexp('help aka slashdot', 'Locked by')
self.assertNotError('aka lock slashdot')
self.assertRegexp('help aka slashdot', 'Locked by evil_admin')
self.assertNotError('aka unlock slashdot')
self.assertNotRegexp('help aka slashdot', 'Locked by')
def testAliasImport(self):
self.assertNotError('alias add foo "echo bar"')
self.assertNotError(u('alias add baz "echo café"'))
self.assertNotError('aka add qux "echo quux"')
self.assertResponse('alias foo', 'bar')
self.assertResponse('alias baz', 'café')
self.assertRegexp('aka foo', 'there is no command named')
self.assertResponse('aka qux', 'quux')
self.assertNotError('aka importaliasdatabase')
self.assertRegexp('alias foo', 'there is no command named')
self.assertResponse('aka foo', 'bar')
self.assertResponse('aka baz', 'café')
self.assertResponse('aka qux', 'quux')
self.assertNotError('alias add foo "echo test"')
self.assertNotError('alias add spam "echo egg"')
self.assertNotError('alias lock spam')
self.assertRegexp('aka importaliasdatabase',
r'the 1 following command: foo \(This Aka already exists.\)$')
self.assertResponse('aka foo', 'bar')
self.assertResponse('alias foo', 'test')
self.assertRegexp('alias spam', 'there is no command named')
self.assertResponse('aka spam', 'egg')
def testList(self):
self.assertNotError('aka add foo bar')
self.assertRegexp('aka list', r'foo.*?bar \$\*')
self.assertNotError('aka add "foo bar" baz')
self.assertRegexp('aka list', r'foo.*?bar \$\*.*?foo bar.*?baz \$\*')
def testListLockedUnlocked(self):
self.assertNotError('register tacocat hunter2')
self.assertNotError('aka add foo bar')
self.assertNotError('aka add abcd echo hi')
self.assertNotError('aka lock foo')
self.assertRegexp('aka list --locked', 'foo')
self.assertNotRegexp('aka list --locked', 'abcd')
self.assertNotRegexp('aka list --unlocked', 'foo')
self.assertRegexp('aka list --unlocked', 'abcd')
# Can't look up both.
self.assertError('aka list --locked --unlocked abcd')
def testSearch(self):
self.assertNotError('aka add foo bar')
self.assertNotError('aka add "many words" "much command"')
self.assertRegexp('aka search f', 'foo')
self.assertError('aka search abcdefghijklmnop')
self.assertRegexp('aka search many', 'many words')
# This should be case insensitive too.
self.assertRegexp('aka search MaNY', 'many words')
class AkaWebUITestCase(ChannelHTTPPluginTestCase):
plugins = ('Aka',)
config = {
'servers.http.keepAlive': True,
'plugins.Aka.web.enable': False,
}
def setUp(self):
super(ChannelHTTPPluginTestCase, self).setUp()
httpserver.startServer()
def tearDown(self):
httpserver.stopServer()
super(ChannelHTTPPluginTestCase, self).tearDown()
def testToggleWebEnable(self):
self.assertHTTPResponse('/aka/', 404)
self.assertNotError('config plugins.Aka.web.enable True')
self.assertHTTPResponse('/aka/', 200)
self.assertNotError('config plugins.Aka.web.enable False')
self.assertHTTPResponse('/aka/', 404)
def testGlobalPage(self):
self.assertNotError('config plugins.Aka.web.enable True')
self.assertNotError('aka add foo1 echo 1')
self.assertNotError('aka add --channel #foo foo2 echo 2')
self.assertNotError('aka add --channel #bar foo3 echo 3')
(respCode, body) = self.request('/aka/list/global')
self.assertEqual(respCode, 200)
self.assertIn(b'foo1', body)
self.assertNotIn(b'foo2', body)
self.assertNotIn(b'foo3', body)
def testChannelPage(self):
self.assertNotError('config plugins.Aka.web.enable True')
self.assertNotError('aka add foo1 echo 1')
self.assertNotError('aka add --channel #foo foo2 echo 2')
self.assertNotError('aka add --channel #bar foo3 echo 3')
(respCode, body) = self.request('/aka/list/%23foo')
self.assertEqual(respCode, 200)
self.assertIn(b'foo1', body)
self.assertIn(b'foo2', body)
self.assertNotIn(b'foo3', body)
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

27
plugins/Alias/README.md Normal file
View File

@ -0,0 +1,27 @@
This plugin allows the user to create various aliases to other commands
or combinations of other commands (via nested commands). It is a good
idea to always quote the commands that are being aliased so that any
nested commands are not immediately run.
Basic usage
-----------
Add an alias, `trout`, which expects a word as an argument
```
<jamessan> @alias add trout "action slaps $1 with a large trout"
<bot> jamessan: The operation succeeded.
<jamessan> @trout me
* bot slaps me with a large trout
```
Add an alias, `lastfm`, which expects a last.fm user and replies with
their recently played items.
```
@alias add lastfm "rss [format concat http://ws.audioscrobbler.com/1.0/user/ [format concat [urlquote $1] /recenttracks.rss]]"
```
Note that if the nested commands being aliased hadn't been quoted, then
those commands would have been run immediately, and `@lastfm` would always
reply with the same information, the result of those commands.

64
plugins/Alias/__init__.py Normal file
View File

@ -0,0 +1,64 @@
###
# Copyright (c) 2005, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
Allows aliases for other commands. NOTE THAT IT'S RECOMMENDED TO USE Aka
PLUGIN INSTEAD!
"""
import supybot
import supybot.world as world
# Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you're keeping the plugin in CVS or some similar system.
__version__ = ""
__author__ = supybot.authors.jemfinch
__maintainer__ = supybot.authors.limnoria_core
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
from . import config
from . import plugin
from importlib import reload
reload(plugin) # In case we're being reloaded.
# Add more reloads here if you add third-party modules and want them to be
# reloaded when this plugin is reloaded. Don't forget to import them as well!
from .plugin import findBiggestDollar, AliasError, escapeAlias, unescapeAlias # for the tests.
if world.testing:
from . import test
Class = plugin.Class
configure = config.configure
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

49
plugins/Alias/config.py Normal file
View File

@ -0,0 +1,49 @@
###
# Copyright (c) 2005, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.conf as conf
import supybot.registry as registry
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Alias')
def configure(advanced):
# This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified themself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Alias', True)
Alias = conf.registerPlugin('Alias')
conf.registerGroup(Alias, 'aliases')
conf.registerGroup(Alias, 'escapedaliases')
conf.registerGlobalValue(Alias, 'validName',
registry.String(r'^[^\x00-\x20]+$', _("""Regex which alias names must match in order to be valid""")))
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

104
plugins/Alias/locales/de.po Normal file
View File

@ -0,0 +1,104 @@
msgid ""
msgstr ""
"Project-Id-Version: Supybot\n"
"POT-Creation-Date: 2012-02-15 23:03+CET\n"
"PO-Revision-Date: 2012-04-27 15:36+0200\n"
"Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n"
"Language-Team: German <fbesser@gmail.com>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Language: German\n"
"X-Poedit-Country: GERMANY\n"
#: plugin.py:45
msgid ""
"Returns the channel the msg came over or the channel given in args.\n"
"\n"
" If the channel was given in args, args is modified (the channel is\n"
" removed).\n"
" "
msgstr ""
"Gibt den Kanal aus über den die Nachricht kam oder der Kanal der in den Argumenten gegeben wurde.\n"
"\n"
"Falls der Kanal in den Argumenten angegeben wurde, werden die Argumente bearbeitet (der Kanal wird entfernt."
#: plugin.py:164
msgid " at least"
msgstr "mindestens"
#: plugin.py:165
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q."
msgstr ""
"<ein Alias, %s %n>\n"
"\n"
"Alias für %q."
#: plugin.py:166
msgid "argument"
msgstr "Argument"
#: plugin.py:220
msgid ""
"<alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
"<Alias>\n"
"\n"
"Versperrt ein Alias, sodass er nicht verändert werden kann."
#: plugin.py:229
#: plugin.py:243
msgid "There is no such alias."
msgstr "Es gibt keinen Alias mit diesem Namen."
#: plugin.py:234
msgid ""
"<alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
"<Alias>\n"
"\n"
"Entsperrt den Alias, sodass andere Personen ihn verändern können."
#: plugin.py:254
msgid "That name isn't valid. Try %q instead."
msgstr "Dieser Name ist nicht zulässig. Probiere anstatt %q."
#: plugin.py:292
#, fuzzy
msgid ""
"<name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, @2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" remaining arguments,\" and cannot be combined with optional arguments.\n"
" "
msgstr ""
"<Name> <Alias>\n"
"\n"
"Definiert einen Alias <Name> der <Alias> ausführt. <Alias> sollte in der Standardform \"Befehl Argument [verschachtelter Befehl Argument\" angegeben werden."
#: plugin.py:315
msgid ""
"<name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""
"<Name>\n"
"\n"
"Entfernt den gegeben Alias, falls er nicht gesperrt ist."

151
plugins/Alias/locales/es.po Normal file
View File

@ -0,0 +1,151 @@
# Spanish translation for limnoria
# Copyright (c) 2015 Limnoria contributors 2015
# This file is distributed under the same license as the Limnoria package.
# Aaron Farias <timido@ubuntu.com>, 2015.
#
msgid ""
msgstr ""
"Project-Id-Version: limnoria\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2014-12-24 15:42+0000\n"
"PO-Revision-Date: 2015-01-01 16:43+0000\n"
"Last-Translator: Aaron Farias <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2015-01-01 17:00+0000\n"
"X-Generator: Launchpad (build 17286)\n"
#: plugin.py:46
#, docstring
msgid ""
"Returns the channel the msg came over or the channel given in args.\n"
"\n"
" If the channel was given in args, args is modified (the channel is\n"
" removed).\n"
" "
msgstr ""
"Devuelve el msg del canal vino o el canal dado en args.\n"
"\n"
"Si el canal se le dio en args, args es modificado (el canal está\n"
"eliminado).\n"
" "
#: plugin.py:105
#, docstring
msgid ""
"Encodes [a-z0-9.]+ into [a-z][a-z0-9].\n"
" Format: a<number of escaped chars>a(<index>d)+<word without dots>."
msgstr ""
"Codifica [a-z0-9.] + En [az] [a-z0-9].\n"
"Formato: un <número de caracteres escapados> a (<índice> d) + <palabra sin "
"puntos>."
#: plugin.py:221
msgid " at least"
msgstr " al menos"
#: plugin.py:223 plugin.py:228
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q."
msgstr ""
"<Un alias,%s %n>\n"
"\n"
"Alias para %q."
#: plugin.py:224 plugin.py:229
msgid "argument"
msgstr "argumento"
#: plugin.py:234
#, docstring
msgid ""
"This plugin allows users to define aliases to commands and combinations\n"
" of commands (via nesting)."
msgstr ""
"Este plugin permite a los usuarios definir los alias de comandos y "
"combinaciones\n"
"de comandos (a través de anidación)."
#: plugin.py:299
#, docstring
msgid ""
"<alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
"<Alias>\n"
"\n"
"Bloquea un alias para que nadie más puede cambiarlo.\n"
"\t\tHay saltos de línea aquí. Cada uno\n"
" "
#: plugin.py:308 plugin.py:322
msgid "There is no such alias."
msgstr "No hay tal alias."
#: plugin.py:313
#, docstring
msgid ""
"<alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
"<Alias>\n"
"\n"
"Desbloquea un alias para que las personas puedan definir nuevos alias sobre "
"él.\n"
" "
#: plugin.py:334
msgid "That name isn't valid. Try %q instead."
msgstr "Ese nombre no es válido. Trate% q en vez."
#: plugin.py:379
#, docstring
msgid ""
"<name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand "
"argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, "
"etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, "
"@2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" remaining arguments,\" and cannot be combined with optional "
"arguments.\n"
" "
msgstr ""
"<Nombre> <comando>\n"
"\n"
"Define un alias <nombre> que ejecuta <command>. El <comando>\n"
"debe estar en el estándar \"argumento de comando [argumento "
"nestedcommand]\"\n"
"argumentos al alias; que van a ser llenados con el primero, segundo, etc.\n"
"argumentos. $ 1, $ 2, etc. puede ser usado para los argumentos necesarios. @ "
"1, @ 2,\n"
"etc. puede ser utilizado para argumentos opcionales. $ * Simplemente "
"significa \"todo\n"
"restantes argumentos, \"y no puede ser combinado con argumentos opcionales.\n"
" "
#: plugin.py:402
#, docstring
msgid ""
"<name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""
"<Nombre>\n"
"\n"
"Elimina los alias dados, si desbloqueado.\n"
" "

146
plugins/Alias/locales/fi.po Normal file
View File

@ -0,0 +1,146 @@
# Alias plugin in Limnoria.
# Copyright (C) 2011, 2012 Limnoria
# Mikaela Suomalainen <mkaysi@outlook.com>, 2011, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: Supybot Alias plugin\n"
"POT-Creation-Date: 2014-12-20 13:30+EET\n"
"PO-Revision-Date: 2014-12-20 13:47+0200\n"
"Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n"
"Language-Team: suomi <>\n"
"Language: fi_FI\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"X-Generator: Poedit 1.6.10\n"
#: plugin.py:46
msgid ""
"Returns the channel the msg came over or the channel given in args.\n"
"\n"
" If the channel was given in args, args is modified (the channel is\n"
" removed).\n"
" "
msgstr ""
"Palauttaa kanavan, jolta viesti tuli tai kanavan, joka on annettu "
"parametreissä.\n"
"\n"
" Jos kanava annetaan parametreissä, parametriä muokataan (kanava\n"
" poistetaan).\n"
" "
#: plugin.py:105
#, fuzzy
msgid ""
"Encodes [a-z0-9.]+ into [a-z][a-z0-9].\n"
" Format: a<number of escaped chars>a(<index>d)+<word without dots>."
msgstr ""
"Salaa [a-z0-9.]+ sisään [a-z][a-z0-9].\n"
" Muoto: a<ohitettujen merkkien määrä>a(<index>d)+<sana ilman pisteitä>."
#: plugin.py:221
msgid " at least"
msgstr "vähintään"
#: plugin.py:223 plugin.py:228
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q."
msgstr ""
"<alias,%s %n>\n"
"\n"
"Alias %q:lle."
#: plugin.py:224 plugin.py:229
msgid "argument"
msgstr "parametri"
#: plugin.py:234
#, fuzzy
msgid ""
"This plugin allows users to define aliases to commands and combinations\n"
" of commands (via nesting)."
msgstr ""
"Tämä plugini sallii käyttäjien määrittää aliaksia komennoille ja komentojen "
"yhdistelmille (sisäytyksellä)."
#: plugin.py:299
msgid ""
"<alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
"<alias>\n"
"\n"
" Lukitsee aliaksen, niin ettei kukaan muu voi muuttaa sitä.\n"
" "
#: plugin.py:308 plugin.py:322
msgid "There is no such alias."
msgstr "Tuollaista aliasta ei ole."
#: plugin.py:313
msgid ""
"<alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
"<alias>\n"
"\n"
" Poistaa lukituksen aliaksesta, jotta ihmiset vouvat määrittää uusia "
"aliaksia sen päälle.\n"
" "
#: plugin.py:334
msgid "That name isn't valid. Try %q instead."
msgstr "Tuo nimi ei ole kelvollinen. Yritä sen sijaan %q:ta."
#: plugin.py:379
msgid ""
"<name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand "
"argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, "
"etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, "
"@2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" remaining arguments,\" and cannot be combined with optional "
"arguments.\n"
" "
msgstr ""
"<nimi> <alias>\n"
"\n"
" Määrittää aliaksen <nimi>, joka suorittaa <komennon>. <Aliaksen>\n"
" pitäisi olla tavallinen \"komento parametri [sisäkkäinen komento "
"parametrit]\"\n"
" parametrejä aliakselle; ne täytetään ensimmäinen, toinen, jne.\n"
" Parametrit. $1, $2, jne. ovat vaadittava parametrejä. @1, @2,\n"
" jne. ovat valinnaisia parametrejä. $* tarkoittaa yksinkertaisesti "
"\"kaikki\n"
" jäljellä olevat parametrit,\" ja johon ei voida yhdistää vaihtoehtoisia "
"parametrejä.\n"
" "
#: plugin.py:402
msgid ""
"<name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""
"<nimi>\n"
"\n"
" Poistaa annetun aliaksen jos se ei ole lukittu.\n"
" "
#~ msgid "You've attempted more nesting than is currently allowed on this bot."
#~ msgstr ""
#~ "Yritit sisällyttää useampia komentoja, kuin tämä botti sallii juuri nyt."

124
plugins/Alias/locales/fr.po Normal file
View File

@ -0,0 +1,124 @@
msgid ""
msgstr ""
"Project-Id-Version: Limnoria\n"
"POT-Creation-Date: 2014-01-22 13:38+CET\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: Limnoria <progval@gmail.com>\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-SourceCharset: ASCII\n"
"X-Generator: Poedit 1.5.4\n"
"Language: fr\n"
#: plugin.py:46
msgid ""
"Returns the channel the msg came over or the channel given in args.\n"
"\n"
" If the channel was given in args, args is modified (the channel is\n"
" removed).\n"
" "
msgstr ""
"Retourne le canal duquel vient le message ou le canal donné en argument.\n"
"\n"
"Si le canal était donné en argument, args est modifié (le canal est "
"supprimé)."
#: plugin.py:108
msgid ""
"Encodes [a-z0-9.]+ into [a-z][a-z0-9].\n"
" Format: a<number of escaped chars>a(<index>d)+<word without dots>."
msgstr "."
#: plugin.py:219
msgid "You've attempted more nesting than is currently allowed on this bot."
msgstr ""
"Vous avez essayé dutiliser plus dimbrication que ce qui est actuellement "
"autorisé sur ce bot."
#: plugin.py:224
msgid " at least"
msgstr "au moins"
#: plugin.py:226 plugin.py:231
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q."
msgstr ""
"<un alias,%s %n>\n"
"\n"
"Alias pour %q."
#: plugin.py:227 plugin.py:232
msgid "argument"
msgstr "argument"
#: plugin.py:300
msgid ""
"<alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
"<alias>\n"
"\n"
"Vérouille un alias pour que personne d'autre ne puisse le changer."
#: plugin.py:309 plugin.py:323
msgid "There is no such alias."
msgstr "Cet alias n'existe pas."
#: plugin.py:314
msgid ""
"<alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
"<alias>\n"
"\n"
"Déverrouille un alias de façon à ce que des gens puissent le redéfinir."
#: plugin.py:335
msgid "That name isn't valid. Try %q instead."
msgstr "Ce nom n'est pas valide. Essayez plutôt %q."
#: plugin.py:383
msgid ""
"<name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand "
"argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, "
"etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, "
"@2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" remaining arguments,\" and cannot be combined with optional "
"arguments.\n"
" "
msgstr ""
"<nom> <alias>\n"
"\n"
"Défini un alias <nom> qui exécute <alias>. L'<alias> peut être dans le "
"standard \"commande argument [commandeimbriquee argument]\". Les arguments "
"donnés à l'alias doivent être donnés dans l'ordre. Vous pouvez utiliser $1, "
"$2, etc pour symboliser les arguments obligatoires qui seront donnés à "
"l'alias, et @1, @2, etc pour symboliser ceux optionnels. $* signifie "
"simplement *tous* les arguments restants, et ne peut être combiné avec des "
"arguments optionnels."
#: plugin.py:406
msgid ""
"<name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""
"<nom>\n"
"\n"
"Supprime l'alias donné, si il n'est pas verrouillé."

107
plugins/Alias/locales/hu.po Normal file
View File

@ -0,0 +1,107 @@
# Limnoria Alias plugin.
# Copyright (C) 2011 Limnoria
# nyuszika7h <litemininyuszika@gmail.com>, 2011.
# Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: Limnoria Alias\n"
"POT-Creation-Date: 2012-03-11 20:58+UTC\n"
"PO-Revision-Date: 2012-04-27 15:12+0200\n"
"Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n"
"Language-Team: \n"
"Language: hu_HU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
#: plugin.py:45
msgid ""
"Returns the channel the msg came over or the channel given in args.\n"
"\n"
" If the channel was given in args, args is modified (the channel is\n"
" removed).\n"
" "
msgstr ""
"Kiírja a csatorna nevét, ahonnan az üzenet jött, vagy a paraméterként megadott csatornát.\n"
"\n"
"Ha a csatorna meg volt adva paraméterként, a paraméterek módosulnak (a csatorna eltávolításra kerül)."
#: plugin.py:164
msgid " at least"
msgstr " legalább"
#: plugin.py:165
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q."
msgstr ""
"<egy álnév,%s %n\n"
"\n"
"Álnév %q-ra."
#: plugin.py:166
msgid "argument"
msgstr "paraméter"
#: plugin.py:220
msgid ""
"<alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
"<álnév>\n"
"\n"
"Lezár egy álnevet, hogy senki más ne változtathassa meg."
#: plugin.py:229
#: plugin.py:243
msgid "There is no such alias."
msgstr "Nincs ilyen álnév."
#: plugin.py:234
msgid ""
"<alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
"<álnév>\n"
"\n"
"Feloldja egy álnév lezárását, hogy az emberek új álnevekkel írhassák felül."
#: plugin.py:254
msgid "That name isn't valid. Try %q instead."
msgstr "Az a név nem érvényes. Próbáld meg %q-t inkább."
#: plugin.py:292
msgid ""
"<name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, @2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" remaining arguments,\" and cannot be combined with optional arguments.\n"
" "
msgstr ""
"<név> <álnév>\n"
"\n"
"Meghatároz egy <név> nevű álnevet, amely futtatja <álnév> parancsot. Az <álnév>-nek a szabványos \"parancs paraméter [beágyazottparancs paraméter]\" álnév paraméterei formában kell lennie; ezek ki lesznek töltve az első, második stb. paraméterekkel. $1, $2 stb. használható kötelező paraméterekhez. @1, @2 stb. használható választható paraméterekhez. $* azt jelenti, \"az összes hátralévő paraméter,\" és nem kombinálható választható paraméterekkel."
#: plugin.py:315
msgid ""
"<name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""
"<név>\n"
"\n"
"Eltávolítja a megadott álnevet, ha nincs lezárva."

116
plugins/Alias/locales/it.po Normal file
View File

@ -0,0 +1,116 @@
msgid ""
msgstr ""
"Project-Id-Version: Limnoria\n"
"POT-Creation-Date: 2011-02-26 09:49+CET\n"
"PO-Revision-Date: 2011-06-07 08:23+0200\n"
"Last-Translator: skizzhg <skizzhg@gmx.com>\n"
"Language-Team: Italian <skizzhg@gmx.com>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: plugin.py:45
#, docstring
msgid ""
"Returns the channel the msg came over or the channel given in args.\n"
"\n"
" If the channel was given in args, args is modified (the channel is\n"
" removed).\n"
" "
msgstr ""
"Restituisce il canale da dove proviene il messaggio o il canale fornito come argomento.\n"
"\n"
" Se il canale è stato dato come argomento, quest'ultimo viene modificato (il canale\n"
" viene rimosso).\n"
" "
#: plugin.py:164
msgid " at least"
msgstr " almeno"
#: plugin.py:165
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q."
msgstr ""
"<un alias,%s %n>\n"
"\n"
"Alias per %q."
#: plugin.py:166
msgid "argument"
msgstr "argomento"
#: plugin.py:220
#, docstring
msgid ""
"<alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
"<alias>\n"
"\n"
" Blocca un alias affinché nessun altro possa modificarlo.\n"
" "
#: plugin.py:229 plugin.py:243
msgid "There is no such alias."
msgstr "Non c'è nessun alias."
#: plugin.py:234
#, docstring
msgid ""
"<alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
"<alias>\n"
"\n"
" Sblocca un alias affinché chiunque possa ridefinirne di nuovi.\n"
" "
#: plugin.py:254
msgid "That name isn't valid. Try %q instead."
msgstr "Nome non valido. Prova %q invece."
#: plugin.py:292
#, docstring
msgid ""
"<name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, @2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" remaining arguments,\" and cannot be combined with optional arguments.\n"
" "
msgstr ""
"<nome> <comando>\n"
"\n"
" Definisce un <nome> che esegue <comando>. <comando> deve essere nello\n"
" standard \"comando argomento [comando_nidificato argomento]\"; gli\n"
" argomenti dati devono essere riportati in sequenza. Per gli argomenti\n"
" richiesti è possibile utilizzare $1, $2, ecc., mentre @1, @2, ecc. per\n"
" quelli opzionali. $* significa semplicemente \"tutti gli argomenti\n"
" rimanenti\" e non può essere combinato con quelli opzionali.\n"
" "
#: plugin.py:315
#, docstring
msgid ""
"<name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""
"<nome>\n"
"\n"
" Rimuove l'alias specificato, se questo non è bloccato.\n"
" "

105
plugins/Alias/messages.pot Normal file
View File

@ -0,0 +1,105 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2014-12-20 13:30+EET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
#: plugin.py:46
#, docstring
msgid ""
"Returns the channel the msg came over or the channel given in args.\n"
"\n"
" If the channel was given in args, args is modified (the channel is\n"
" removed).\n"
" "
msgstr ""
#: plugin.py:105
#, docstring
msgid ""
"Encodes [a-z0-9.]+ into [a-z][a-z0-9].\n"
" Format: a<number of escaped chars>a(<index>d)+<word without dots>."
msgstr ""
#: plugin.py:221
msgid " at least"
msgstr ""
#: plugin.py:223 plugin.py:228
msgid ""
"<an alias,%s %n>\n"
"\n"
"Alias for %q."
msgstr ""
#: plugin.py:224 plugin.py:229
msgid "argument"
msgstr ""
#: plugin.py:234
#, docstring
msgid ""
"This plugin allows users to define aliases to commands and combinations\n"
" of commands (via nesting)."
msgstr ""
#: plugin.py:299
#, docstring
msgid ""
"<alias>\n"
"\n"
" Locks an alias so that no one else can change it.\n"
" "
msgstr ""
#: plugin.py:308 plugin.py:322
msgid "There is no such alias."
msgstr ""
#: plugin.py:313
#, docstring
msgid ""
"<alias>\n"
"\n"
" Unlocks an alias so that people can define new aliases over it.\n"
" "
msgstr ""
#: plugin.py:334
msgid "That name isn't valid. Try %q instead."
msgstr ""
#: plugin.py:379
#, docstring
msgid ""
"<name> <command>\n"
"\n"
" Defines an alias <name> that executes <command>. The <command>\n"
" should be in the standard \"command argument [nestedcommand argument]\"\n"
" arguments to the alias; they'll be filled with the first, second, etc.\n"
" arguments. $1, $2, etc. can be used for required arguments. @1, @2,\n"
" etc. can be used for optional arguments. $* simply means \"all\n"
" remaining arguments,\" and cannot be combined with optional arguments.\n"
" "
msgstr ""
#: plugin.py:402
#, docstring
msgid ""
"<name>\n"
"\n"
" Removes the given alias, if unlocked.\n"
" "
msgstr ""

472
plugins/Alias/plugin.py Normal file
View File

@ -0,0 +1,472 @@
###
# Copyright (c) 2002-2004, Jeremiah Fincher
# Copyright (c) 2014, James McCoy
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import re
import sys
import types
import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.utils.minisix as minisix
import supybot.ircutils as ircutils
import supybot.registry as registry
import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Alias')
# Copied from the old privmsgs.py.
def getChannel(irc, msg, args):
"""Returns the channel the msg came over or the channel given in args.
If the channel was given in args, args is modified (the channel is
removed).
"""
if args and msg.channel:
if conf.supybot.reply.requireChannelCommandsToBeSentInChannel():
if args[0] != msg.channel:
s = 'Channel commands must be sent in the channel to which ' \
'they apply; if this is not the behavior you desire, ' \
'ask the bot\'s administrator to change the registry ' \
'variable ' \
'supybot.reply.requireChannelCommandsToBeSentInChannel ' \
'to False.'
raise callbacks.Error(s)
return args.pop(0)
elif msg.channel:
return msg.channel
else:
raise callbacks.Error('Command must be sent in a channel or ' \
'include a channel in its arguments.')
def getArgs(args, required=1, optional=0, wildcard=0):
if len(args) < required:
raise callbacks.ArgumentError
if len(args) < required + optional:
ret = list(args) + ([''] * (required + optional - len(args)))
elif len(args) >= required + optional:
if not wildcard:
ret = list(args[:required + optional - 1])
ret.append(' '.join(args[required + optional - 1:]))
else:
ret = list(args)
return ret
class AliasError(Exception):
pass
dollarRe = re.compile(r'\$(\d+)')
def findBiggestDollar(alias):
dollars = dollarRe.findall(alias)
dollars = list(map(int, dollars))
dollars.sort()
if dollars:
return dollars[-1]
else:
return 0
atRe = re.compile(r'@(\d+)')
def findBiggestAt(alias):
ats = atRe.findall(alias)
ats = list(map(int, ats))
ats.sort()
if ats:
return ats[-1]
else:
return 0
def needsEscaping(alias):
return '.' in alias or '|' in alias
def escapeAlias(alias):
"""Encodes dots and pipes
Format: a<number of escaped chars>a(<index>(d|p))+<word without dots or pipes>."""
prefix = ''
new_alias = ''
prefixes = 0
for index, char in enumerate(alias):
if char == '.':
prefix += '%sd' % index
prefixes += 1
elif char == '|':
prefix += '%sp' % index
prefixes += 1
else:
new_alias += char
pre_prefix = 'a%ia' % prefixes
return pre_prefix + prefix + new_alias
def unescapeAlias(alias):
alias = alias[1:] # Strip the leading 'a'
escaped_nb = ''
while alias[0] in '0123456789':
escaped_nb += alias[0]
alias = alias[1:]
alias = alias[1:]
escaped_nb = int(escaped_nb)
escaped_chars = []
while alias[0] in '0123456789':
current_group = ''
while alias[0] in '0123456789':
current_group += alias[0]
alias = alias[1:]
if alias[0] == 'd':
char = '.'
elif alias[0] == 'p':
char = '|'
else:
char = alias[0]
alias = alias[1:]
escaped_chars.append((int(current_group), char))
if len(escaped_chars) == escaped_nb:
break
new_alias = ''
index = 0
for char in alias:
if escaped_chars and index == escaped_chars[0][0]:
new_alias += escaped_chars[0][1]
escaped_chars.pop(0)
index += 1
new_alias += char
index += 1
return new_alias
def makeNewAlias(name, alias):
original = alias
biggestDollar = findBiggestDollar(original)
biggestAt = findBiggestAt(original)
wildcard = '$*' in original
if biggestAt and wildcard:
raise AliasError('Can\'t mix $* and optional args (@1, etc.)')
if original.count('$*') > 1:
raise AliasError('There can be only one $* in an alias.')
testTokens = callbacks.tokenize(original)
if testTokens and isinstance(testTokens[0], list):
raise AliasError('Commands may not be the result of nesting.')
def f(self, irc, msg, args):
alias = original.replace('$nick', msg.nick)
if '$channel' in original:
channel = getChannel(irc, msg, args)
alias = alias.replace('$channel', channel)
tokens = callbacks.tokenize(alias)
if biggestDollar or biggestAt:
args = getArgs(args, required=biggestDollar, optional=biggestAt,
wildcard=wildcard)
max_len = conf.supybot.reply.maximumLength()
args = list([x[:max_len] for x in args])
def regexpReplace(m):
idx = int(m.group(1))
return args[idx-1]
def replace(tokens, replacer):
for (i, token) in enumerate(tokens):
if isinstance(token, list):
replace(token, replacer)
else:
tokens[i] = replacer(token)
replace(tokens, lambda s: dollarRe.sub(regexpReplace, s))
if biggestAt:
assert not wildcard
args = args[biggestDollar:]
replace(tokens, lambda s: atRe.sub(regexpReplace, s))
if wildcard:
assert not biggestAt
# Gotta remove the things that have already been subbed in.
i = biggestDollar
while i:
args.pop(0)
i -= 1
def everythingReplace(tokens):
for (i, token) in enumerate(tokens):
if isinstance(token, list):
if everythingReplace(token):
return
if token == '$*':
tokens[i:i+1] = args
return True
elif '$*' in token:
tokens[i] = token.replace('$*', ' '.join(args))
return True
return False
everythingReplace(tokens)
# Limit memory use by constraining the size of the message being passed
# in to the alias. Also tracking nesting to avoid endless recursion.
maxLength = conf.supybot.reply.maximumLength()
tokens = [t[:maxLength] for t in tokens]
self.Proxy(irc, msg, tokens, nested=irc.nested + 1)
flexargs = ''
if biggestDollar and (wildcard or biggestAt):
flexargs = _(' at least')
try:
doc = format(_('<an alias,%s %n>\n\nAlias for %q.'),
flexargs, (biggestDollar, _('argument')), alias)
except UnicodeDecodeError:
if minisix.PY2:
alias = alias.decode('utf8')
doc = format(_('<an alias,%s %n>\n\nAlias for %q.'),
flexargs, (biggestDollar, _('argument')), alias)
f = utils.python.changeFunctionName(f, name, doc)
return f
class Alias(callbacks.Plugin):
"""This plugin allows users to define aliases to commands and combinations
of commands (via nesting)."""
def __init__(self, irc):
self.__parent = super(Alias, self)
self.__parent.__init__(irc)
# Schema: {alias: [command, locked, commandMethod]}
self.aliases = {}
# XXX This should go. aliases should be a space separate list, etc.
group = conf.supybot.plugins.Alias.aliases
group2 = conf.supybot.plugins.Alias.escapedaliases
prefixLen = len(registry.split('supybot.plugins.alias.aliases'))
for (name, alias) in registry._cache.items():
name = name.lower()
nameSplit = registry.split(name)
if len(nameSplit) > prefixLen+1:
continue
if name.startswith('supybot.plugins.alias.aliases.'):
name = nameSplit[-1]
conf.registerGlobalValue(group, name, registry.String('', ''))
conf.registerGlobalValue(group.get(name), 'locked',
registry.Boolean(False, ''))
elif name.startswith('supybot.plugins.alias.escapedaliases.'):
name = nameSplit[-1]
conf.registerGlobalValue(group2, name,
registry.String('', ''))
conf.registerGlobalValue(group2.get(name),
'locked', registry.Boolean(False, ''))
for (name, value) in group.getValues(fullNames=False):
name = name.lower() # Just in case.
command = value()
locked = value.locked()
self.aliases[name] = [command, locked, None]
for (name, value) in group2.getValues(fullNames=False):
name = name.lower() # Just in case.
command = value()
locked = value.locked()
self.aliases[unescapeAlias(name)] = [command, locked, None]
for (alias, (command, locked, _)) in self.aliases.copy().items():
try:
self.addAlias(irc, alias, command, locked)
except Exception as e:
self.log.exception('Exception when trying to add alias %s. '
'Removing from the Alias database.', alias)
del self.aliases[alias]
def isCommandMethod(self, name):
if not self.__parent.isCommandMethod(name):
if name in self.aliases:
return True
else:
return False
else:
return True
def listCommands(self):
return self.__parent.listCommands(self.aliases.keys())
def getCommandMethod(self, command):
try:
return self.__parent.getCommandMethod(command)
except AttributeError:
return self.aliases[command[0]][2]
def aliasRegistryGroup(self, name):
if needsEscaping(name):
return self.registryValue('escapedaliases', value=False)
else:
return self.registryValue('aliases', value=False)
def aliasRegistryNode(self, name):
group = self.aliasRegistryGroup(name)
if needsEscaping(name):
return group.get(escapeAlias(name))
else:
return group.get(name)
def aliasRegistryRemove(self, name):
group = self.aliasRegistryGroup(name)
if needsEscaping(name):
group.unregister(escapeAlias(name))
else:
group.unregister(name)
def setLocked(self, name, value):
self.aliases[name][1] = value
self.aliasRegistryNode(name).locked.setValue(value)
def isValidName(self, name):
if not re.search(self.registryValue('validName'), name):
return False
if not registry.isValidRegistryName(name):
return False
return True
@internationalizeDocstring
def lock(self, irc, msg, args, name):
"""<alias>
Locks an alias so that no one else can change it.
"""
if name in self.aliases and self.isCommandMethod(name):
self.setLocked(name, True)
irc.replySuccess()
else:
irc.error(_('There is no such alias.'))
lock = wrap(lock, [('checkCapability', 'admin'), 'commandName'])
@internationalizeDocstring
def unlock(self, irc, msg, args, name):
"""<alias>
Unlocks an alias so that people can define new aliases over it.
"""
if name in self.aliases and self.isCommandMethod(name):
self.setLocked(name, False)
irc.replySuccess()
else:
irc.error(_('There is no such alias.'))
unlock = wrap(unlock, [('checkCapability', 'admin'), 'commandName'])
def addAlias(self, irc, name, alias, lock=False):
if not self.isValidName(name):
raise AliasError('Invalid alias name.')
realName = callbacks.canonicalName(name)
if name != realName:
s = format(_('That name isn\'t valid. Try %q instead.'), realName)
raise AliasError(s)
name = realName
if self.isCommandMethod(name):
if realName not in self.aliases:
s = 'You can\'t overwrite commands in this plugin.'
raise AliasError(s)
if name in self.aliases:
(currentAlias, locked, _) = self.aliases[name]
if locked and currentAlias != alias:
raise AliasError(format('Alias %q is locked.', name))
f = makeNewAlias(name, alias)
f = types.MethodType(f, self)
if name in self.aliases:
# We gotta remove it so its value gets updated.
self.aliasRegistryRemove(name)
aliasGroup = self.aliasRegistryGroup(name)
if needsEscaping(name):
confname = escapeAlias(name)
else:
confname = name
conf.registerGlobalValue(aliasGroup, confname,
registry.String(alias, ''))
conf.registerGlobalValue(aliasGroup.get(confname), 'locked',
registry.Boolean(lock, ''))
self.aliases[name] = [alias, lock, f]
def removeAlias(self, name, evenIfLocked=False):
name = callbacks.canonicalName(name)
if name in self.aliases and self.isCommandMethod(name):
if evenIfLocked or not self.aliases[name][1]:
del self.aliases[name]
self.aliasRegistryRemove(name)
else:
raise AliasError('That alias is locked.')
else:
raise AliasError('There is no such alias.')
@internationalizeDocstring
def add(self, irc, msg, args, name, alias):
"""<name> <command>
Defines an alias <name> that executes <command>. The <command>
should be in the standard "command argument [nestedcommand argument]"
arguments to the alias; they'll be filled with the first, second, etc.
arguments. $1, $2, etc. can be used for required arguments. @1, @2,
etc. can be used for optional arguments. $* simply means "all
remaining arguments," and cannot be combined with optional arguments.
"""
if ' ' not in alias:
# If it's a single word, they probably want $*.
alias += ' $*'
try:
self.addAlias(irc, name, alias)
self.log.info('Adding alias %q for %q (from %s)',
name, alias, msg.prefix)
irc.replySuccess()
except AliasError as e:
irc.error(str(e))
add = wrap(add, ['commandName', 'text'])
@internationalizeDocstring
def remove(self, irc, msg, args, name):
"""<name>
Removes the given alias, if unlocked.
"""
try:
self.removeAlias(name)
self.log.info('Removing alias %q (from %s)', name, msg.prefix)
irc.replySuccess()
except AliasError as e:
irc.error(str(e))
remove = wrap(remove, ['commandName'])
@internationalizeDocstring
def list(self, irc, msg, args, optlist):
"""[--locked|--unlocked]
Lists alias names of a particular type, defaults to all aliases if no
--locked or --unlocked option is given.
"""
optlist = dict(optlist)
if len(optlist)>1:
irc.error(_('Cannot specify --locked and --unlocked simultaneously'))
return
aliases = []
for name in self.aliases.keys():
if self.isCommandMethod(name):
if 'locked' in optlist:
if self.aliases[name][1]: aliases.append(name)
elif 'unlocked' in optlist:
if not self.aliases[name][1]: aliases.append(name)
else:
aliases.append(name)
if aliases:
aliases.sort()
irc.reply(format('%L', aliases))
else:
if len(optlist):
irc.reply(_('There are no aliases of that type.'))
else:
irc.reply(_('There are no aliases.'))
list = wrap(list, [getopts({'locked':'', 'unlocked':''})])
Class = Alias
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

184
plugins/Alias/test.py Normal file
View File

@ -0,0 +1,184 @@
# -*- coding: utf8 -*-
###
# Copyright (c) 2002-2004, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from supybot.test import *
import supybot.conf as conf
import supybot.plugin as plugin
import supybot.registry as registry
from supybot.utils.minisix import u
from . import plugin as Alias
class FunctionsTest(SupyTestCase):
def testFindBiggestDollar(self):
self.assertEqual(Alias.findBiggestDollar(''), 0)
self.assertEqual(Alias.findBiggestDollar('foo'), 0)
self.assertEqual(Alias.findBiggestDollar('$0'), 0)
self.assertEqual(Alias.findBiggestDollar('$1'), 1)
self.assertEqual(Alias.findBiggestDollar('$2'), 2)
self.assertEqual(Alias.findBiggestDollar('$2 $10'), 10)
self.assertEqual(Alias.findBiggestDollar('$3'), 3)
self.assertEqual(Alias.findBiggestDollar('$3 $2 $1'), 3)
self.assertEqual(Alias.findBiggestDollar('foo bar $1'), 1)
self.assertEqual(Alias.findBiggestDollar('foo $2 $1'), 2)
self.assertEqual(Alias.findBiggestDollar('foo $0 $1'), 1)
self.assertEqual(Alias.findBiggestDollar('foo $1 $3'), 3)
self.assertEqual(Alias.findBiggestDollar('$10 bar $1'), 10)
class AliasTestCase(ChannelPluginTestCase):
plugins = ('Alias', 'Filter', 'Utilities', 'Format', 'Reply')
def testNoAliasWithNestedCommandName(self):
self.assertError('alias add foo "[bar] baz"')
def testDoesNotOverwriteCommands(self):
# We don't have dispatcher commands anymore
#self.assertError('alias add alias "echo foo bar baz"')
self.assertError('alias add add "echo foo bar baz"')
self.assertError('alias add remove "echo foo bar baz"')
self.assertError('alias add lock "echo foo bar baz"')
self.assertError('alias add unlock "echo foo bar baz"')
def testAliasHelp(self):
self.assertNotError('alias add slashdot foo')
self.assertRegexp('help slashdot', "Alias for .*foo")
self.assertNotError('alias add nonascii echo éé')
self.assertRegexp('help nonascii', "Alias for .*echo éé")
def testRemove(self):
self.assertNotError('alias add foo echo bar')
self.assertResponse('foo', 'bar')
self.assertNotError('alias remove foo')
self.assertError('foo')
def testDollars(self):
self.assertNotError('alias add rot26 "rot13 [rot13 $1]"')
self.assertResponse('rot26 foobar', 'foobar')
def testMoreDollars(self):
self.assertNotError('alias add rev "echo $3 $2 $1"')
self.assertResponse('rev foo bar baz', 'baz bar foo')
def testAllArgs(self):
self.assertNotError('alias add swap "echo $2 $1 $*"')
self.assertResponse('swap 1 2 3 4 5', '2 1 3 4 5')
self.assertError('alias add foo "echo $1 @1 $*"')
self.assertNotError('alias add moo echo $1 $*')
self.assertError('moo')
self.assertResponse('moo foo', 'foo')
self.assertResponse('moo foo bar', 'foo bar')
def testChannel(self):
self.assertNotError('alias add channel echo $channel')
self.assertResponse('alias channel', self.channel)
def testNick(self):
self.assertNotError('alias add sendingnick "rot13 [rot13 $nick]"')
self.assertResponse('sendingnick', self.nick)
def testAddRemoveAlias(self):
cb = self.irc.getCallback('Alias')
cb.addAlias(self.irc, 'foobar', 'echo sbbone', lock=True)
self.assertResponse('foobar', 'sbbone')
self.assertRaises(Alias.AliasError, cb.removeAlias, 'foobar')
cb.removeAlias('foobar', evenIfLocked=True)
self.assertFalse('foobar' in cb.aliases)
self.assertError('foobar')
self.assertRegexp('alias add abc\x07 ignore', 'Error.*Invalid')
def testOptionalArgs(self):
self.assertNotError('alias add myrepr "repr @1"')
self.assertResponse('myrepr foo', '"foo"')
self.assertResponse('myrepr ""', '""')
def testNoExtraSpaces(self):
self.assertNotError('alias add foo "action takes $1\'s money"')
self.assertResponse('foo bar', '\x01ACTION takes bar\'s money\x01')
def testNoExtraQuotes(self):
self.assertNotError('alias add myre "echo s/$1/$2/g"')
self.assertResponse('myre foo bar', 's/foo/bar/g')
def testUnicode(self):
self.assertNotError(u('alias add \u200b echo foo'))
self.assertResponse(u('\u200b'), 'foo')
self.assertNotError('alias add café echo bar')
self.assertResponse('café', 'bar')
def testSimpleAliasWithoutArgsImpliesDollarStar(self):
self.assertNotError('alias add exo echo')
self.assertResponse('exo foo bar baz', 'foo bar baz')
class EscapedAliasTestCase(ChannelPluginTestCase):
plugins = ('Alias', 'Utilities')
def setUp(self):
registry._cache.update(
{'supybot.plugins.Alias.escapedaliases.a1a3dfoobar': 'echo baz',
'supybot.plugins.Alias.escapedaliases.a1a3dfoobar.locked': 'False'})
super(EscapedAliasTestCase, self).setUp()
def testReadDatabase(self):
self.assertResponse('foo.bar', 'baz')
def testAdd(self):
self.assertNotError('alias add spam.egg echo hi')
self.assertResponse('spam.egg', 'hi')
self.assertNotError('alias add spam|egg echo hey')
self.assertResponse('spam|egg', 'hey')
self.assertNotError('alias remove spam.egg')
self.assertError('spam.egg')
self.assertNotError('spam|egg')
self.assertNotError('alias remove spam|egg')
self.assertError('spam.egg')
self.assertError('spam|egg')
def testWriteDatabase(self):
self.assertNotError('alias add fooo.spam echo egg')
self.assertResponse('fooo.spam', 'egg')
self.assertTrue(hasattr(conf.supybot.plugins.Alias.escapedaliases,
'a1a4dfooospam'))
self.assertEqual(conf.supybot.plugins.Alias.escapedaliases.a1a4dfooospam(),
'echo egg')
self.assertNotError('alias add foo.spam.egg echo supybot')
self.assertResponse('foo.spam.egg', 'supybot')
self.assertTrue(hasattr(conf.supybot.plugins.Alias.escapedaliases,
'a2a3d8dfoospamegg'))
self.assertEqual(conf.supybot.plugins.Alias.escapedaliases.a2a3d8dfoospamegg(),
'echo supybot')
self.assertEqual(Alias.unescapeAlias('a2a3d8dfoospamegg'),
'foo.spam.egg')
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -0,0 +1,27 @@
Anonymous allows you to send messages anonymously as the bot. If
`supybot.plugins.Anonymous.allowPrivateTarget` is `True`, you can send
messages in query too.
## Usage examples
### Proving that you are the owner.
When you ask for cloak/vhost for your bot, the network operators will
often ask you to prove that you own the bot. You can do this for example
with the following method:
```
@load Anonymous
@config plugins.anonymous.requirecapability owner
@config plugins.anonymous.allowprivatetarget True
@anonymous say <operator nick> Hi, my owner is <your nick> :)
```
This
* Loads the plugin.
* Makes the plugin require that you are the owner
* If anyone could send private messages as the bot, they could also
access network services.
* Allows sending private messages
* Sends message `Hi, my owner is <your nick> :)` to `operator nick`.
* Note that you won't see the messages that are sent to the bot.

View File

@ -0,0 +1,62 @@
###
# Copyright (c) 2005, Daniel DiPaolo
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
Allows folks to talk through the bot anonymously.
"""
import supybot
import supybot.world as world
# Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you're keeping the plugin in CVS or some similar system.
__version__ = "%%VERSION%%"
__author__ = supybot.authors.strike
__maintainer__ = supybot.authors.limnoria_core
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
from . import config
from . import plugin
from importlib import reload
reload(plugin) # In case we're being reloaded.
# Add more reloads here if you add third-party modules and want them to be
# reloaded when this plugin is reloaded. Don't forget to import them as well!
if world.testing:
from . import test
Class = plugin.Class
configure = config.configure
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -0,0 +1,64 @@
###
# Copyright (c) 2005, Daniel DiPaolo
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.conf as conf
import supybot.registry as registry
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Anonymous')
def configure(advanced):
# This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified themself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Anonymous', True)
Anonymous = conf.registerPlugin('Anonymous')
# This is where your configuration variables (if any) should go. For example:
# conf.registerGlobalValue(Anonymous, 'someConfigVariableName',
# registry.Boolean(False, """Help for someConfigVariableName."""))
conf.registerChannelValue(conf.supybot.plugins.Anonymous,
'requirePresenceInChannel', registry.Boolean(True, _("""Determines whether
the bot should require people trying to use this plugin to be in the
channel they wish to anonymously send to.""")))
conf.registerChannelValue(conf.supybot.plugins.Anonymous, 'requireRegistration',
registry.Boolean(True, _("""Determines whether the bot should require
people trying to use this plugin to be registered.""")))
conf.registerChannelValue(conf.supybot.plugins.Anonymous, 'requireCapability',
registry.String('', _("""Determines what capability (if any) the bot should
require people trying to use this plugin to have.""")))
conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'allowPrivateTarget',
registry.Boolean(False, _("""Determines whether the bot will allow the
"tell" command to be used. If true, the bot will allow the "tell"
command to send private messages to other users.""")))
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -0,0 +1,95 @@
msgid ""
msgstr ""
"Project-Id-Version: Supybot\n"
"POT-Creation-Date: 2011-06-09 18:26+CEST\n"
"PO-Revision-Date: 2011-10-28 12:55+0100\n"
"Last-Translator: Florian Besser <fbesser@gmail.com>\n"
"Language-Team: German <fbesser@gmail.com>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Language: de\n"
#: config.py:49
msgid ""
"Determines whether\n"
" the bot should require people trying to use this plugin to be in the\n"
" channel they wish to anonymously send to."
msgstr "Legt fest ob Leute im Kanal sein müssen, an den anonym senden wollen."
#: config.py:53
msgid ""
"Determines whether the bot should require\n"
" people trying to use this plugin to be registered."
msgstr "Legt fest ob Nutzer registiert sein müssen um dieses Plugin zu benutze."
#: config.py:56
msgid ""
"Determines what capability (if any) the bot should\n"
" require people trying to use this plugin to have."
msgstr "Legt fest welche Fähgikeiten (falls überhaupt) der Bot verlangt von Personen die versuchen dieses Plugin zu benutzen."
#: config.py:59
msgid ""
"Determines whether the bot will require \n"
" targets of the \"say\" command to be public (i.e., channels). If this is\n"
" True, the bot will allow people to use the \"say\" command to send private\n"
" messages to other users."
msgstr "Legt fest ob der Bot verlangt, dass Ziele des Befehls \"say\" öffentlich sind (z.B. Kanäle). Falls das auf True gesetzt ist, wird ber Bot Nutzern erlauben den Befehl \"say\" zu nutzen um anderen Nutzern private Nachrichten zu senden."
#: plugin.py:40
msgid ""
"This plugin allows users to act through the bot anonymously. The 'do'\n"
" command has the bot perform an anonymous action in a given channel, and\n"
" the 'say' command allows other people to speak through the bot. Since\n"
" this can be fairly well abused, you might want to set\n"
" supybot.plugins.Anonymous.requireCapability so only users with that\n"
" capability can use this plugin. For extra security, you can require that\n"
" the user be *in* the channel they are trying to address anonymously with\n"
" supybot.plugins.Anonymous.requirePresenceInChannel, or you can require\n"
" that the user be registered by setting\n"
" supybot.plugins.Anonymous.requireRegistration.\n"
" "
msgstr "Das Plugin erlaubt Nutzern durch den Bot anonym zu bleiben. Der 'do' Befehl lässt den Bot eine anonyme Aktion in einem Kanal ausführen und der 'say' Befehl lässt Nutzer durch den Bot sprechen. Da das Ganze natürlich leicht missbraucht werden kann, willst du vielleicht supybot.plugins.Anonymous.requireCapability setzen, sodass nur Nutzer mit dieser Fähigkeit das Plugin benutzen können. Für etwas mehr Sicherheit kannst du mit supybot.plugins.Anonymous.requirePresenceInChannel verlangen, dass der Nutzer in dem Kanal sein muss in dem er anonym senden will oder du kannst verlangen, dass der Nutzer registriert sein muss indem du supybot.plugins.Anonymous.requireRegistration setzt."
#: plugin.py:64
msgid "You must be in %s to %q in there."
msgstr "Du musst in %s sein um %q dort auszuführen."
#: plugin.py:68
msgid "I'm lobotomized in %s."
msgstr "Ich bin hirnamputiert in %s."
#: plugin.py:71
msgid "That channel has set its capabilities so as to disallow the use of this plugin."
msgstr "Für den Kanal sind die Fähigkeiten so gesetzt, dass sie das benutzen dieses Plugins nicht erlauben."
#: plugin.py:74
msgid "%q cannot be used to send private messages."
msgstr "%q kann nicht verwendet werden um private Nachrichten zu versenden."
#: plugin.py:80
msgid ""
"<channel|nick> <text>\n"
"\n"
" Sends <text> to <channel|nick>. Can only send to <nick> if\n"
" supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
" "
msgstr ""
"<Kanal|Nick> <text>\n"
"\n"
"Sendet <Text> an <Kanal|Nick>. Kann nur an <Nick> senden wenn supybot.plugins.Anonymous.allowPrivateTarget auf True gesetzt ist."
#: plugin.py:94
msgid ""
"<channel> <action>\n"
"\n"
" Performs <action> in <channel>.\n"
" "
msgstr ""
"<Kanal> <Aktion>\n"
"\n"
"Führt die <Aktion> im <Kanal> aus."

View File

@ -0,0 +1,156 @@
# Spanish translation for limnoria
# Copyright (c) 2015 Limnoria contributors 2015
# This file is distributed under the same license as the Limnoria package.
# Aaron Farias <timido@ubuntu.com>, 2015.
#
msgid ""
msgstr ""
"Project-Id-Version: limnoria\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2014-12-24 15:42+0000\n"
"PO-Revision-Date: 2015-01-01 16:56+0000\n"
"Last-Translator: Aaron Farias <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2015-01-01 17:04+0000\n"
"X-Generator: Launchpad (build 17286)\n"
#: config.py:49
msgid ""
"Determines whether\n"
" the bot should require people trying to use this plugin to be in the\n"
" channel they wish to anonymously send to."
msgstr ""
"Determina si\n"
"el robot debe requerir la gente tratando de utilizar este plugin para poder "
"estar en el\n"
"canal que desean enviar anónimamente."
#: config.py:53
msgid ""
"Determines whether the bot should require\n"
" people trying to use this plugin to be registered."
msgstr ""
"Determina si el bot debe exigir\n"
"personas que tratan de utilizar este plugin para ser registrados."
#: config.py:56
msgid ""
"Determines what capability (if any) the bot should\n"
" require people trying to use this plugin to have."
msgstr ""
"Determina lo que la capacidad (si lo hay) que el bot debe\n"
"requieren que las personas tratando de usar este plugin para poder tener."
#: config.py:59
msgid ""
"Determines whether the bot will allow the\n"
" \"tell\" command to be used. If true, the bot will allow the \"tell\"\n"
" command to send private messages to other users."
msgstr ""
"Determina si el bot permitirá al\n"
"Comando \"decirle\" a utilizar. De ser cierto, el bot le permitirá al "
"\"decir\"\n"
"comando para enviar mensajes privados a otros usuarios."
#: plugin.py:41
#, docstring
msgid ""
"This plugin allows users to act through the bot anonymously. The 'do'\n"
" command has the bot perform an anonymous action in a given channel, and\n"
" the 'say' command allows other people to speak through the bot. Since\n"
" this can be fairly well abused, you might want to set\n"
" supybot.plugins.Anonymous.requireCapability so only users with that\n"
" capability can use this plugin. For extra security, you can require "
"that\n"
" the user be *in* the channel they are trying to address anonymously "
"with\n"
" supybot.plugins.Anonymous.requirePresenceInChannel, or you can require\n"
" that the user be registered by setting\n"
" supybot.plugins.Anonymous.requireRegistration.\n"
" "
msgstr ""
"Este plugin permite a los usuarios actúan a través de la bot anónima. El "
"\"hacer\"\n"
"comando tiene el robot realice una acción anónimo en un canal determinado, "
"y\n"
"el 'dicen' comando permite que otras personas hablan a través de la bot. "
"Desde\n"
"esto puede ser bastante maltratado, es posible que desee establecer\n"
"supybot.plugins.Anonymous.requireCapability tan sólo los usuarios con que\n"
"capacidad puede utilizar este plugin. Para mayor seguridad, se puede "
"requerir que\n"
"el usuario ser * en * el canal que están tratando de hacer frente de forma "
"anónima con\n"
"supybot.plugins.Anonymous.requirePresenceInChannel, o puede requerir\n"
"que el usuario se ha registrado mediante el establecimiento de\n"
"supybot.plugins.Anonymous.requireRegistration.\n"
" "
#: plugin.py:65
msgid "You must be in %s to %q in there."
msgstr "Usted debe estar en% s para% q allí."
#: plugin.py:69
msgid "I'm lobotomized in %s."
msgstr "Estoy lobotomizado en%s."
#: plugin.py:72
msgid ""
"That channel has set its capabilities so as to disallow the use of this "
"plugin."
msgstr ""
"Ese canal ha puesto sus capacidades a fin de no permitir el uso de este "
"plugin."
#: plugin.py:75
msgid ""
"This command is disabled (supybot.plugins.Anonymous.allowPrivateTarget is "
"False)."
msgstr ""
"Este comando está desactivado (supybot.plugins.Anonymous.allowPrivateTarget "
"es falso)."
#: plugin.py:80
#, docstring
msgid ""
"<channel> <text>\n"
"\n"
" Sends <text> to <channel>.\n"
" "
msgstr ""
"<Canal> <texto>\n"
"\n"
"Envía <texto> al <canal>.\n"
" "
#: plugin.py:92
#, docstring
msgid ""
"<nick> <text>\n"
"\n"
" Sends <text> to <nick>. Can only be used if\n"
" supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
" "
msgstr ""
"<Nick> <texto>\n"
"\n"
"Envía <texto> a <nick>. Sólo puede utilizarse si\n"
"supybot.plugins.Anonymous.allowPrivateTarget es True.\n"
" "
#: plugin.py:106
#, docstring
msgid ""
"<channel> <action>\n"
"\n"
" Performs <action> in <channel>.\n"
" "
msgstr ""
"<Canal> <acción>\n"
"\n"
"Realiza <action> en <channel>.\n"
" "

View File

@ -0,0 +1,160 @@
# Anonymous plugin in Limnoria.
# Copyright (C) 2011-2014 Limnoria
# Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011-2014.
#
msgid ""
msgstr ""
"Project-Id-Version: Supybot Anonymous\n"
"POT-Creation-Date: 2014-12-20 11:59+EET\n"
"PO-Revision-Date: \n"
"Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n"
"Language-Team: \n"
"Language: fi_FI\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.6.10\n"
#: config.py:49
msgid ""
"Determines whether\n"
" the bot should require people trying to use this plugin to be in the\n"
" channel they wish to anonymously send to."
msgstr ""
"Määrittää vaatiiko \n"
"botti ihmisten, jotka yrittävät käyttää tätä lisäosaa, olemaan\n"
"kanavalla, jonne he tahtovat lähettää viestin tuntemattomasti."
#: config.py:53
msgid ""
"Determines whether the bot should require\n"
" people trying to use this plugin to be registered."
msgstr ""
"Määrittää vaatiiko botti\n"
"ihmisiä, jotka yrittävät käyttää tätä lisäosaa, olemaan rekisteröityneitä."
#: config.py:56
msgid ""
"Determines what capability (if any) the bot should\n"
" require people trying to use this plugin to have."
msgstr ""
"Määrittää minkä valtuuden (jos minkään) botti vaatii\n"
" ihmisiltä, jotka yrittävät käyttää tätä lisäosaa."
#: config.py:59
#, fuzzy
msgid ""
"Determines whether the bot will allow the\n"
" \"tell\" command to be used. If true, the bot will allow the \"tell\"\n"
" command to send private messages to other users."
msgstr ""
"Määrittää salliiko botti \"tell\"-komennon käytön. Jos tämä on True, botti "
"sallii \"tell\"-komennon\n"
" lähettävän yksityisviestejä toisille käyttäjille."
#: plugin.py:41
msgid ""
"This plugin allows users to act through the bot anonymously. The 'do'\n"
" command has the bot perform an anonymous action in a given channel, and\n"
" the 'say' command allows other people to speak through the bot. Since\n"
" this can be fairly well abused, you might want to set\n"
" supybot.plugins.Anonymous.requireCapability so only users with that\n"
" capability can use this plugin. For extra security, you can require "
"that\n"
" the user be *in* the channel they are trying to address anonymously "
"with\n"
" supybot.plugins.Anonymous.requirePresenceInChannel, or you can require\n"
" that the user be registered by setting\n"
" supybot.plugins.Anonymous.requireRegistration.\n"
" "
msgstr ""
"Tämä lisäosa sallii käyttäjien toimia botin kautta tuntemattomasti.\n"
" Komento 'do' sallii botin tehdä tuntemattoman toiminnon annetulla "
"kanavalla ja\n"
" komento 'say' sallii toisten ihmisten puhua botin läpi. Koska\n"
" tätä voidaan väärinkäyttää helposti voit tahtoa asettaa asetuksen\n"
" supybot.plugins.Anonymous.requireCapability niin, että vain käyttäjät "
"tuolla\n"
" valtuudella voivat käyttää tätä lisäosaa. Lisäturvallisuuden vuoksi voit "
"vaatia, että käyttäjien täytyy *olla* kanavilla joita he yrittävät puhutella "
"tuntemattomasti asetuksella supybot.plugins.Anonymous."
"requirePresenceInChannel, tai sinä voit vaatia,\n"
" että tuo käyttäjä on rekisteröitynyt asetuksella\n"
" supybot.plugins.Anonymous.requireRegistration"
#: plugin.py:65
msgid "You must be in %s to %q in there."
msgstr "Sinun täytyy olla kanavalla %s %q sinne."
#: plugin.py:69
msgid "I'm lobotomized in %s."
msgstr "Minut on lobotomoitu kanavalla %s."
#: plugin.py:72
msgid ""
"That channel has set its capabilities so as to disallow the use of this "
"plugin."
msgstr "Tuo kanava on asettanut valtuudet kieltämään tämän Pluginin käytön."
#: plugin.py:75
msgid ""
"This command is disabled (supybot.plugins.Anonymous.allowPrivateTarget is "
"False)."
msgstr ""
"Tämä komento on poistettu käytöstä (supybot.plugins.Anonymous."
"allowPrivateTarget on False)."
#: plugin.py:80
msgid ""
"<channel> <text>\n"
"\n"
" Sends <text> to <channel>.\n"
" "
msgstr ""
"<kanava> <teksti>\n"
" Lähettää <tesktin> <kanavalle>."
#: plugin.py:92
msgid ""
"<nick> <text>\n"
"\n"
" Sends <text> to <nick>. Can only be used if\n"
" supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
" "
msgstr ""
"<kanava> <teksti>\n"
"\n"
" Lähettää <tekstin> <nimimerkille>. <Nimimerkille> voidaan lähettää\n"
" viestejä vain asetusarvon supybot.plugins.Anonymous.allowPrivateTarget\n"
" ollessa True.\n"
" "
#: plugin.py:106
msgid ""
"<channel> <action>\n"
"\n"
" Performs <action> in <channel>.\n"
" "
msgstr ""
"<kanava> <toiminto>\n"
"\n"
"Suorittaa <toiminnon> <kanavalla>.\n"
" "
#~ msgid "%q cannot be used to send private messages."
#~ msgstr "%q:ta ei voi käyttää yksityisviestien lähettämiseen."
#~ msgid ""
#~ "<channel> <text>\n"
#~ "\n"
#~ " Sends <text> to <channel>. Can only send to <nick> if\n"
#~ " supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
#~ " "
#~ msgstr ""
#~ "<kanava> <teksti>\n"
#~ "\n"
#~ " Lähettää <tekstin> <kanavalle>. <Nimimerkille> voidaan lähettää "
#~ "viestejä vain \n"
#~ " asetusarvon supybot.plugins.Anonymous.allowPrivateTarget ollessa "
#~ "True.\n"
#~ " "

View File

@ -0,0 +1,96 @@
msgid ""
msgstr ""
"Project-Id-Version: Limnoria\n"
"POT-Creation-Date: 2011-06-09 18:26+CEST\n"
"PO-Revision-Date: \n"
"Last-Translator: Valentin Lorentz <progval@gmail.com>\n"
"Language-Team: Limnoria <progval@gmail.com>\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-Language: Français\n"
"X-Poedit-Country: France\n"
"X-Poedit-SourceCharset: ASCII\n"
#: config.py:49
msgid ""
"Determines whether\n"
" the bot should require people trying to use this plugin to be in the\n"
" channel they wish to anonymously send to."
msgstr "Détermine si le bot requiérera que les gens soient sur le canal pour y envoyer des messages anonymement."
#: config.py:53
msgid ""
"Determines whether the bot should require\n"
" people trying to use this plugin to be registered."
msgstr "Détermine si le bot requiérera que les personnes utilisant ce plugin soient enregistrées."
#: config.py:56
msgid ""
"Determines what capability (if any) the bot should\n"
" require people trying to use this plugin to have."
msgstr "Détermine quelle capacité (s'il y en a une) le bot requiéra que les gens utilisant ce plugin aient."
#: config.py:59
msgid ""
"Determines whether the bot will require \n"
" targets of the \"say\" command to be public (i.e., channels). If this is\n"
" True, the bot will allow people to use the \"say\" command to send private\n"
" messages to other users."
msgstr "Détermine si le bot requiérera que les cibles de la commande \"say\" soient publiques (c'est à dire des canaux). Si c'est True, le bot autorisera les personnes à utiliser la commande \"say\" pour envoyer des messages à d'autres utilisateurs en privé."
#: plugin.py:40
msgid ""
"This plugin allows users to act through the bot anonymously. The 'do'\n"
" command has the bot perform an anonymous action in a given channel, and\n"
" the 'say' command allows other people to speak through the bot. Since\n"
" this can be fairly well abused, you might want to set\n"
" supybot.plugins.Anonymous.requireCapability so only users with that\n"
" capability can use this plugin. For extra security, you can require that\n"
" the user be *in* the channel they are trying to address anonymously with\n"
" supybot.plugins.Anonymous.requirePresenceInChannel, or you can require\n"
" that the user be registered by setting\n"
" supybot.plugins.Anonymous.requireRegistration.\n"
" "
msgstr ""
#: plugin.py:64
msgid "You must be in %s to %q in there."
msgstr "Vous devez être sur %s pour y utiliser %q."
#: plugin.py:68
msgid "I'm lobotomized in %s."
msgstr "Je suis lobotomisé sur %s."
#: plugin.py:71
msgid "That channel has set its capabilities so as to disallow the use of this plugin."
msgstr "Ce canal a définit ses capacités de façon à désactiver l'utilisation de ce plugin."
#: plugin.py:74
msgid "%q cannot be used to send private messages."
msgstr "%q ne peut pas être utilisé pour envoyer des messages privés."
#: plugin.py:80
msgid ""
"<channel|nick> <text>\n"
"\n"
" Sends <text> to <channel|nick>. Can only send to <nick> if\n"
" supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
" "
msgstr ""
"<canal|nick> <text>\n"
"\n"
"Envoie le <texte> au <canal|nick>. Vous ne pouvez envoyer à <nick> que si supybot.plugins.Anonymous.allowPrivateTarget vaut True."
#: plugin.py:94
msgid ""
"<channel> <action>\n"
"\n"
" Performs <action> in <channel>.\n"
" "
msgstr ""
"<canal> <action>\n"
"\n"
"Effectue l'<action> sur le <canal>."

View File

@ -0,0 +1,98 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: Limnoria Anonymous\n"
"POT-Creation-Date: 2011-06-09 18:26+CEST\n"
"PO-Revision-Date: 2011-07-21 17:32+0100\n"
"Last-Translator: nyuszika7h <litemininyuszika@gmail.com>\n"
"Language-Team: \n"
"Language: hu_HU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
#: config.py:49
msgid ""
"Determines whether\n"
" the bot should require people trying to use this plugin to be in the\n"
" channel they wish to anonymously send to."
msgstr "Meghatározza, hogy a bot megkövetelje-e, hogy a bővítményt használni kívánó emberek abban a csatornában legyenek, ahová névtelenül szeretnének küldeni."
#: config.py:53
msgid ""
"Determines whether the bot should require\n"
" people trying to use this plugin to be registered."
msgstr "Meghatározza, hogy a bot megkövetelje-e, hogy a bővítményt használni kívánó emberek regisztrálva legyenek."
#: config.py:56
msgid ""
"Determines what capability (if any) the bot should\n"
" require people trying to use this plugin to have."
msgstr "Meghatározza, hogy milyen képesség (ha van ilyen) legyen szükséges a bővítmény használatához."
#: config.py:59
msgid ""
"Determines whether the bot will require \n"
" targets of the \"say\" command to be public (i.e., channels). If this is\n"
" True, the bot will allow people to use the \"say\" command to send private\n"
" messages to other users."
msgstr "Meghatározza, hogy a bot megkövetelje-e, hogy a \"say\" parancs célja publikus legyen (pl., csatornák). Ha ez True, a bot megengedi az embereknek, hogy a \"say\" paranccsal privát üzenetet küldjenek másoknak."
#: plugin.py:40
msgid ""
"This plugin allows users to act through the bot anonymously. The 'do'\n"
" command has the bot perform an anonymous action in a given channel, and\n"
" the 'say' command allows other people to speak through the bot. Since\n"
" this can be fairly well abused, you might want to set\n"
" supybot.plugins.Anonymous.requireCapability so only users with that\n"
" capability can use this plugin. For extra security, you can require that\n"
" the user be *in* the channel they are trying to address anonymously with\n"
" supybot.plugins.Anonymous.requirePresenceInChannel, or you can require\n"
" that the user be registered by setting\n"
" supybot.plugins.Anonymous.requireRegistration.\n"
" "
msgstr "Ez a bővítmény megengedi a felhasználóknak, hogy névtelenül cselekedjenek a boton keresztül. A 'do' parancs hatására a bot végrehajt egy névtelen tevékenységet egy megadott csatornában, és a 'say' parancs megengedi másoknak, hogy a boton keresztül beszéljenek. Mivel ezzel elég könnyen vissza lehet élni, érdemes beállítani a supybot.plugins.Anonymous.requireCapability-t, hogy csak az adott képességgel rendelkező felhasználók használhassák ezt a bővítményt. Extra biztonságért a supybot.plugins.Anonymous.requirePresenceInChannel-lel megkövetelheted, hogy a felhasználó abban a csatornában legyen, amelyet névtelenül szeretnének megcímezni, vagy a supybot.plugins.Anonymous.requireRegistration-nel megkövetelheted, hogy a felhasználó regisztálva legyen."
#: plugin.py:64
msgid "You must be in %s to %q in there."
msgstr "%s-ban kell lenned, hogy a %q parancsot használd ott."
#: plugin.py:68
msgid "I'm lobotomized in %s."
msgstr "Némítva vagyok %s-ban."
#: plugin.py:71
msgid "That channel has set its capabilities so as to disallow the use of this plugin."
msgstr "A megadott csatornában nem használhatod a bővítményt a csatorna képességei miatt."
#: plugin.py:74
msgid "%q cannot be used to send private messages."
msgstr "A %q parancs nem használható privát üzenetek küldésére."
#: plugin.py:80
msgid ""
"<channel|nick> <text>\n"
"\n"
" Sends <text> to <channel|nick>. Can only send to <nick> if\n"
" supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
" "
msgstr ""
"<csatorna|név> <szöveg>\n"
"\n"
"Elküldi <szöveg>-et <csatorna|név>-nek. Csak akkor küldhet <név>-nek, ha a supybot.plugins.Anonymous.allowPrivateTarget True."
#: plugin.py:94
msgid ""
"<channel> <action>\n"
"\n"
" Performs <action> in <channel>.\n"
" "
msgstr ""
"<csatorna> <tevékenység>\n"
"\n"
"Végrehajtja <tevékenység>-et <csatorna>-ban."

View File

@ -0,0 +1,118 @@
msgid ""
msgstr ""
"Project-Id-Version: Limnoria\n"
"POT-Creation-Date: 2011-02-26 09:49+CET\n"
"PO-Revision-Date: 2011-08-10 00:13+0200\n"
"Last-Translator: skizzhg <skizzhg@gmx.com>\n"
"Language-Team: Italian <skizzhg@gmx.com>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: config.py:49
msgid ""
"Determines whether\n"
" the bot should require people trying to use this plugin to be in the\n"
" channel they wish to anonymously send to."
msgstr ""
"Determina se il bot richieda che gli utenti siano in canale\n"
" per inviare messaggi in forma anonima."
#: config.py:53
msgid ""
"Determines whether the bot should require\n"
" people trying to use this plugin to be registered."
msgstr ""
"Determina se il bot richieda che gli utenti siano registrati\n"
" per usare il plugin."
#: config.py:56
msgid ""
"Determines what capability (if any) the bot should\n"
" require people trying to use this plugin to have."
msgstr ""
"Determina quali capacità (eventuali) debbano\n"
" avere gli utenti per utilizzare questo plugin."
#: config.py:59
msgid ""
"Determines whether the bot will require \n"
" targets of the \"say\" command to be public (i.e., channels). If this is\n"
" True, the bot will allow people to use the \"say\" command to send private\n"
" messages to other users."
msgstr ""
"Determina se il bot richiederà che le destinazioni del comando \"say\" siano\n"
" pubbliche (ovvero i canali). Se impostato a True, il bot permetterà\n"
" di usare il comando \"say\" per inviare messaggi privati ad altri utenti."
#: plugin.py:40
#, docstring
msgid ""
"This plugin allows users to act through the bot anonymously. The 'do'\n"
" command has the bot perform an anonymous action in a given channel, and\n"
" the 'say' command allows other people to speak through the bot. Since\n"
" this can be fairly well abused, you might want to set\n"
" supybot.plugins.Anonymous.requireCapability so only users with that\n"
" capability can use this plugin. For extra security, you can require that\n"
" the user be *in* the channel they are trying to address anonymously with\n"
" supybot.plugins.Anonymous.requirePresenceInChannel, or you can require\n"
" that the user be registered by setting\n"
" supybot.plugins.Anonymous.requireRegistration.\n"
" "
msgstr ""
"Questo plugin permette agli utenti di agire attraverso il bot in modo anonimo.\n"
" Il comando \"do\" esegue un'azione anonima in un dato canale, mentre \"say\"\n"
" permette di parlare tramite il bot. Giacché si può facilmente abusarne, è\n"
" possibile impostare supybot.plugins.Anonymous.requireCapability in modo che\n"
" solo gli utenti con determinate capacità possano usare il plugin. Per una\n"
" maggiore sicurezza è ppossibile richiedere con supybot.plugins.Anonymous.requirePresenceInChannel\n"
" che l'utente intenzionato a parlare anonimamente sia in canale; o anche, tramite\n"
" supybot.plugins.Anonymous.requireRegistration, che l'utente sia registrato."
" "
#: plugin.py:64
msgid "You must be in %s to %q in there."
msgstr "Devi essere in %s per %q."
#: plugin.py:68
msgid "I'm lobotomized in %s."
msgstr "In %s sono lobotomizzato."
#: plugin.py:71
msgid "That channel has set its capabilities so as to disallow the use of this plugin."
msgstr "Questo canale ha le capacità impostate in modo da impedire l'utilizzo di questo plugin."
#: plugin.py:74
msgid "%q cannot be used to send private messages."
msgstr "%q non può essere usato per inviare messaggi privati."
#: plugin.py:80
#, docstring
msgid ""
"<channel|nick> <text>\n"
"\n"
" Sends <text> to <channel|nick>. Can only send to <nick> if\n"
" supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
" "
msgstr ""
"<canale|nick> <testo>\n"
"\n"
" Invia <testo> a <canale|nick>. Può solo inviare a <nick> se\n"
" supybot.plugins.Anonymous.allowPrivateTarget è impostato a True.\n"
" "
#: plugin.py:94
#, docstring
msgid ""
"<channel> <action>\n"
"\n"
" Performs <action> in <channel>.\n"
" "
msgstr ""
"<canale> <azione>\n"
"\n"
" Esegue <azione> in <canale>.\n"
" "

View File

@ -0,0 +1,103 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2014-12-20 11:59+EET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
#: config.py:49
msgid ""
"Determines whether\n"
" the bot should require people trying to use this plugin to be in the\n"
" channel they wish to anonymously send to."
msgstr ""
#: config.py:53
msgid ""
"Determines whether the bot should require\n"
" people trying to use this plugin to be registered."
msgstr ""
#: config.py:56
msgid ""
"Determines what capability (if any) the bot should\n"
" require people trying to use this plugin to have."
msgstr ""
#: config.py:59
msgid ""
"Determines whether the bot will allow the\n"
" \"tell\" command to be used. If true, the bot will allow the \"tell\"\n"
" command to send private messages to other users."
msgstr ""
#: plugin.py:41
#, docstring
msgid ""
"This plugin allows users to act through the bot anonymously. The 'do'\n"
" command has the bot perform an anonymous action in a given channel, and\n"
" the 'say' command allows other people to speak through the bot. Since\n"
" this can be fairly well abused, you might want to set\n"
" supybot.plugins.Anonymous.requireCapability so only users with that\n"
" capability can use this plugin. For extra security, you can require that\n"
" the user be *in* the channel they are trying to address anonymously with\n"
" supybot.plugins.Anonymous.requirePresenceInChannel, or you can require\n"
" that the user be registered by setting\n"
" supybot.plugins.Anonymous.requireRegistration.\n"
" "
msgstr ""
#: plugin.py:65
msgid "You must be in %s to %q in there."
msgstr ""
#: plugin.py:69
msgid "I'm lobotomized in %s."
msgstr ""
#: plugin.py:72
msgid "That channel has set its capabilities so as to disallow the use of this plugin."
msgstr ""
#: plugin.py:75
msgid "This command is disabled (supybot.plugins.Anonymous.allowPrivateTarget is False)."
msgstr ""
#: plugin.py:80
#, docstring
msgid ""
"<channel> <text>\n"
"\n"
" Sends <text> to <channel>.\n"
" "
msgstr ""
#: plugin.py:92
#, docstring
msgid ""
"<nick> <text>\n"
"\n"
" Sends <text> to <nick>. Can only be used if\n"
" supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
" "
msgstr ""
#: plugin.py:106
#, docstring
msgid ""
"<channel> <action>\n"
"\n"
" Performs <action> in <channel>.\n"
" "
msgstr ""

188
plugins/Anonymous/plugin.py Normal file
View File

@ -0,0 +1,188 @@
###
# Copyright (c) 2005, Daniel DiPaolo
# Copyright (c) 2014, James McCoy
# Copyright (c) 2021, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import functools
import supybot.conf as conf
import supybot.ircdb as ircdb
import supybot.utils as utils
from supybot.commands import *
import supybot.ircmsgs as ircmsgs
import supybot.callbacks as callbacks
import supybot.ircutils as ircutils
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Anonymous')
class Anonymous(callbacks.Plugin):
"""This plugin allows users to act through the bot anonymously. The 'do'
command has the bot perform an anonymous action in a given channel, and
the 'say' command allows other people to speak through the bot. Since
this can be fairly well abused, you might want to set
supybot.plugins.Anonymous.requireCapability so only users with that
capability can use this plugin. For extra security, you can require that
the user be *in* the channel they are trying to address anonymously with
supybot.plugins.Anonymous.requirePresenceInChannel, or you can require
that the user be registered by setting
supybot.plugins.Anonymous.requireRegistration.
"""
def _preCheck(self, irc, msg, target, action):
if self.registryValue('requireRegistration', target, irc.network):
try:
foo = ircdb.users.getUser(msg.prefix)
except KeyError:
irc.errorNotRegistered(Raise=True)
capability = self.registryValue('requireCapability',
target, irc.network)
if capability:
if not ircdb.checkCapability(msg.prefix, capability):
irc.errorNoCapability(capability, Raise=True)
if action != 'tell':
require_presence = self.registryValue('requirePresenceInChannel',
target, irc.network)
if require_presence and \
msg.nick not in irc.state.channels[target].users:
irc.error(format(_('You must be in %s to %q in there.'),
target, action), Raise=True)
c = ircdb.channels.getChannel(target)
if c.lobotomized:
irc.error(format(_('I\'m lobotomized in %s.'), target),
Raise=True)
if not c._checkCapability(self.name()):
irc.error(_('That channel has set its capabilities so as to '
'disallow the use of this plugin.'), Raise=True)
elif not self.registryValue('allowPrivateTarget'):
irc.error(_('This command is disabled (supybot.plugins.Anonymous.'
'allowPrivateTarget is False).'), Raise=True)
def say(self, irc, msg, args, target, text):
"""<channel> <text>
Sends <text> to <channel>.
"""
self._preCheck(irc, msg, target, 'say')
self.log.info('Saying %q in %s due to %s.',
text, target, msg.prefix)
irc.queueMsg(ircmsgs.privmsg(target, text))
irc.noReply()
say = wrap(say, ['inChannel', 'text'])
def tell(self, irc, msg, args, target, text):
"""<nick> <text>
Sends <text> to <nick>. Can only be used if
supybot.plugins.Anonymous.allowPrivateTarget is True.
"""
self._preCheck(irc, msg, target, 'tell')
self.log.info('Telling %q to %s due to %s.',
text, target, msg.prefix)
irc.queueMsg(ircmsgs.privmsg(target, text))
irc.noReply()
tell = wrap(tell, ['nick', 'text'])
def do(self, irc, msg, args, channel, text):
"""<channel> <action>
Performs <action> in <channel>.
"""
self._preCheck(irc, msg, channel, 'do')
self.log.info('Performing %q in %s due to %s.',
text, channel, msg.prefix)
irc.reply(text, action=True, to=channel)
do = wrap(do, ['inChannel', 'text'])
def react(self, irc, msg, args, channel, reaction, nick):
"""<channel> <reaction> <nick>
Sends the <reaction> to <nick>'s last message.
<reaction> is typically a smiley or an emoji.
This may not be supported on the current network, as this
command depends on IRCv3 features.
This is also not supported if
supybot.protocols.irc.experimentalExtensions disabled
(don't enable it unless you know what you are doing).
"""
self._preCheck(irc, msg, channel, 'react')
if not conf.supybot.protocols.irc.experimentalExtensions():
irc.error(_('Unable to react, '
'supybot.protocols.irc.experimentalExtensions is '
'disabled.'), Raise=True)
if not 'message-tags' in irc.state.capabilities_ack:
irc.error(_('Unable to react, the network does not support '
'message-tags.'), Raise=True)
if irc.state.getClientTagDenied('draft/reply') \
or irc.state.getClientTagDenied('draft/react'):
irc.error(_('Unable to react, the network does not allow '
'draft/reply and/or draft/react.'), Raise=True)
iterable = filter(functools.partial(self._validLastMsg, irc),
reversed(irc.state.history))
for react_to_msg in iterable:
if react_to_msg.nick == nick:
break
else:
irc.error(_('I couldn\'t find a message from %s in '
'my history of %s messages.')
% (nick, len(irc.state.history)),
Raise=True)
react_to_msgid = react_to_msg.server_tags.get('msgid')
if not react_to_msgid:
irc.error(_('Unable to react, %s\'s last message does not have '
'a message id.') % nick, Raise=True)
self.log.info('Reacting with %q in %s due to %s.',
reaction, channel, msg.prefix)
reaction_msg = ircmsgs.IrcMsg(command='TAGMSG', args=(channel,),
server_tags={'+draft/reply': react_to_msgid,
'+draft/react': reaction})
irc.queueMsg(reaction_msg)
react = wrap(
react, ['inChannel', 'somethingWithoutSpaces', 'nickInChannel'])
def _validLastMsg(self, irc, msg):
return msg.prefix and \
msg.command == 'PRIVMSG' and \
msg.channel
Anonymous = internationalizeDocstring(Anonymous)
Class = Anonymous
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

148
plugins/Anonymous/test.py Normal file
View File

@ -0,0 +1,148 @@
###
# Copyright (c) 2005, Daniel DiPaolo
# Copyright (c) 2014, James McCoy
# Copyright (c) 2021, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.conf as conf
from supybot.test import *
class AnonymousTestCase(ChannelPluginTestCase):
plugins = ('Anonymous',)
def testSay(self):
self.assertError('anonymous say %s I love you!' % self.channel)
with conf.supybot.plugins.Anonymous.requireRegistration.context(False):
m = self.assertNotError('anonymous say %s foo!' % self.channel)
self.assertTrue(m.args[1] == 'foo!')
def testTell(self):
self.assertError('anonymous tell %s I love you!' % self.nick)
with conf.supybot.plugins.Anonymous.requireRegistration.context(False):
self.assertError('anonymous tell %s foo!' % self.channel)
with conf.supybot.plugins.Anonymous.allowPrivateTarget.context(True):
m = self.assertNotError('anonymous tell %s foo!' % self.nick)
self.assertTrue(m.args[1] == 'foo!')
def testAction(self):
m = self.assertError('anonymous do %s loves you!' % self.channel)
with conf.supybot.plugins.Anonymous.requireRegistration.context(False):
m = self.assertNotError('anonymous do %s loves you!'%self.channel)
self.assertEqual(m.args, ircmsgs.action(self.channel,
'loves you!').args)
def testReact(self):
with self.subTest('nick not in channel'):
self.assertRegexp('anonymous react :) blah',
'blah is not in %s' % self.channel)
self.irc.feedMsg(ircmsgs.IrcMsg(
':blah!foo@example JOIN %s' % self.channel))
with self.subTest('require registration'):
self.assertRegexp('anonymous react :) blah',
'must be registered')
self.assertIsNone(self.irc.takeMsg())
with conf.supybot.plugins.Anonymous.requireRegistration.context(False):
with self.subTest('experimental extensions disabled'):
self.assertRegexp('anonymous react :) blah',
'protocols.irc.experimentalExtensions is disabled')
self.assertIsNone(self.irc.takeMsg())
with conf.supybot.plugins.Anonymous.requireRegistration.context(False), \
conf.supybot.protocols.irc.experimentalExtensions.context(True):
with self.subTest('server support missing'):
self.assertRegexp('anonymous react :) blah',
'network does not support message-tags')
self.assertIsNone(self.irc.takeMsg())
self.irc.state.capabilities_ack.add('message-tags')
with self.subTest('no message from the target'):
self.assertRegexp('anonymous react :) blah',
'couldn\'t find a message')
self.assertIsNone(self.irc.takeMsg())
self.irc.feedMsg(ircmsgs.IrcMsg(
':blah!foo@example PRIVMSG %s :hello' % self.channel))
with self.subTest('original message not tagged with msgid'):
self.assertRegexp('anonymous react :) blah',
'not have a message id')
self.assertIsNone(self.irc.takeMsg())
self.irc.feedMsg(ircmsgs.IrcMsg(
'@msgid=123 :blah!foo@example PRIVMSG %s :hello'
% self.channel))
# Works
with self.subTest('canonical working case'):
m = self.getMsg('anonymous react :) blah')
self.assertEqual(m, ircmsgs.IrcMsg(
'@+draft/reply=123;+draft/react=:) TAGMSG %s'
% self.channel))
def testReactClienttagdeny(self):
self.irc.feedMsg(ircmsgs.IrcMsg(
':blah!foo@example JOIN %s' % self.channel))
self.irc.feedMsg(ircmsgs.IrcMsg(
'@msgid=123 :blah!foo@example PRIVMSG %s :hello'
% self.channel))
self.irc.state.capabilities_ack.add('message-tags')
with conf.supybot.plugins.Anonymous.requireRegistration.context(False), \
conf.supybot.protocols.irc.experimentalExtensions.context(True):
# Works
self.irc.state.supported['CLIENTTAGDENY'] = 'foo,bar'
for value in ('draft/reply', 'draft/react', '*,-draft/reply',
'*,draft/react'):
self.irc.state.supported['CLIENTTAGDENY'] = value
with self.subTest('denied by CLIENTTAGDENY=%s' % value):
self.assertRegexp('anonymous react :) blah',
'draft/reply and/or draft/react')
self.assertIsNone(self.irc.takeMsg())
# Works
for value in ('foo,bar', '*,-draft/reply,-draft/react'):
self.irc.state.supported['CLIENTTAGDENY'] = value
with self.subTest('allowed by CLIENTTAGDENY=%s' % value):
m = self.getMsg('anonymous react :) blah')
self.assertEqual(m, ircmsgs.IrcMsg(
'@+draft/reply=123;+draft/react=:) TAGMSG %s'
% self.channel))
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -0,0 +1,15 @@
This plugin sets channel modes on users when they join the channel
depending on the configuration.
If
* `plugins.automode.op` is set to `True`, users with the `#channel,op`
capability are opped when they join.
* `plugins.automode.halfop` is set to `True`, users with the
`#channel,halfop` are halfopped when they join.
* `plugins.automode.voice` is set to `True`, users with the
`#channel,voice` are voiced when they join.
This plugin also kbans people on `@channel ban list`
(`config plugins.automode.ban`) when they join and if moding users with
lower capability is enabled, that is also applied to users with higher
capability (`config plugins.automode.fallthrough).

View File

@ -0,0 +1,64 @@
###
# Copyright (c) 2005, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
Automatically ops, voices, or halfops, or bans people when they join a channel,
according to their capabilities. If you want your bot automatically op users
when they join your channel, this is the plugin to load.
"""
import supybot
import supybot.world as world
# Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you're keeping the plugin in CVS or some similar system.
__version__ = "%%VERSION%%"
__author__ = supybot.authors.jemfinch
__maintainer__ = supybot.authors.limnoria_core
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
from . import config
from . import plugin
from importlib import reload
reload(plugin) # In case we're being reloaded.
# Add more reloads here if you add third-party modules and want them to be
# reloaded when this plugin is reloaded. Don't forget to import them as well!
if world.testing:
from . import test
Class = plugin.Class
configure = config.configure
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -0,0 +1,86 @@
###
# Copyright (c) 2005, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.conf as conf
import supybot.registry as registry
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('AutoMode')
def configure(advanced):
# This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified themself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('AutoMode', True)
AutoMode = conf.registerPlugin('AutoMode')
conf.registerChannelValue(AutoMode, 'enable',
registry.Boolean(True, _("""Determines whether this plugin is enabled.
""")))
conf.registerGlobalValue(AutoMode, 'owner',
registry.Boolean(False, _("""Determines whether this plugin will automode
owners even if they don't have op/halfop/voice/whatever capability.""")))
conf.registerChannelValue(AutoMode, 'alternativeCapabilities',
registry.Boolean(True, _("""Determines whether the bot will
check for 'alternative capabilities' (ie. autoop, autohalfop,
autovoice) in addition to/instead of classic ones.""")))
conf.registerChannelValue(AutoMode, 'fallthrough',
registry.Boolean(True, _("""Determines whether the bot will "fall
through" to halfop/voicing when auto-opping is turned off but
auto-halfopping/voicing are turned on.""")))
conf.registerChannelValue(AutoMode, 'op',
registry.Boolean(False, _("""Determines whether the bot will automatically
op people with the <channel>,op capability when they join the channel.
""")))
conf.registerChannelValue(AutoMode, 'halfop',
registry.Boolean(False, _("""Determines whether the bot will automatically
halfop people with the <channel>,halfop capability when they join the
channel.""")))
conf.registerChannelValue(AutoMode, 'voice',
registry.Boolean(False, _("""Determines whether the bot will automatically
voice people with the <channel>,voice capability when they join the
channel.""")))
conf.registerChannelValue(AutoMode, 'ban',
registry.Boolean(True, _("""Determines whether the bot will automatically
ban people who join the channel and are on the banlist.""")))
conf.registerChannelValue(AutoMode.ban, 'period',
registry.PositiveInteger(86400, _("""Determines how many seconds the bot
will automatically ban a person when banning.""")))
conf.registerChannelValue(AutoMode, 'delay',
registry.Integer(0, _("""Determines how many seconds the bot will wait
before applying a mode. Has no effect on bans.""")))
conf.registerChannelValue(AutoMode, 'extra',
registry.SpaceSeparatedListOfStrings([], _("""Extra modes that will be
applied to a user. Example syntax: user1+o-v user2+v user3-v""")))
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -0,0 +1,75 @@
msgid ""
msgstr ""
"Project-Id-Version: Supybot\n"
"POT-Creation-Date: 2012-04-19 21:37+CEST\n"
"PO-Revision-Date: 2012-04-27 15:38+0200\n"
"Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n"
"Language-Team: German <fbesser@gmail.com>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Language: German\n"
"X-Poedit-Country: GERMANY\n"
#: config.py:46
msgid ""
"Determines whether this plugin is enabled.\n"
" "
msgstr "Legt fest ob das Plugin aktiv ist."
#: config.py:49
msgid ""
"Determines whether this plugin will automode\n"
" owners even if they don't have op/halfop/voice/whatever capability."
msgstr "Legt fest ob das Plugin Besitzern automatisch den Modus zuweist, obwohl sie nicht die op/halfop/voice/wasauchimmer Fähigkeit besitzen."
#: config.py:52
#, fuzzy
msgid ""
"Determines whether the bot will \"fall\n"
" through\" to halfop/voicing when auto-opping is turned off but\n"
" auto-halfopping/voicing are turned on."
msgstr "Legt fest ob der Bot \"zurückfällt\" auf Halboperator/Sprechrechte wenn automatischer Operator abgeschaltet ist, aber automatische Halboperator/Sprecherechte angeschaltet sind."
#: config.py:56
msgid ""
"Determines whether the bot will automatically\n"
" op people with the <channel>,op capability when they join the channel.\n"
" "
msgstr "Legt fest ob der Bot Personen, mit der <Kanal>,op Fähigkeit, automatisch Operatorrechte zuweisen soll, wenn sie den Kanal betreten."
#: config.py:60
msgid ""
"Determines whether the bot will automatically\n"
" halfop people with the <channel>,halfop capability when they join the\n"
" channel."
msgstr "Legt fest ob der Bot Personen, mit der <Kanal>,halfop Fähigkeit, automatisch Halboperatorrechte zuweisen soll, wenn sie den Kanal betreten."
#: config.py:64
msgid ""
"Determines whether the bot will automatically\n"
" voice people with the <channel>,voice capability when they join the\n"
" channel."
msgstr "Legt fest ob der Bot Personen, mit der <Kanal>,voice Fähigkeit, automatisch Sprechrechte zuweisen soll, wenn sie den Kanal betreten."
#: config.py:68
msgid ""
"Determines whether the bot will automatically\n"
" ban people who join the channel and are on the banlist."
msgstr "Legt fest ob der Bot automatisch Personen bannen soll die auf der Banliste stehen."
#: config.py:71
msgid ""
"Determines how many seconds the bot\n"
" will automatically ban a person when banning."
msgstr "Legt fest wieviele Sekunden der Bot Personen automatisch bannt."
#: config.py:75
#, fuzzy
msgid ""
"Determines how many seconds the bot will wait\n"
" before applying a mode. Has no effect on bans."
msgstr "Legt fest wieviele Sekunden der Bot Personen automatisch bannt."

View File

@ -0,0 +1,137 @@
# Spanish translation for Limnoria
# Copyright (c) 2015 Limnoria 2015
# This file is distributed under the same license as the Limnoria package.
# Aaron Farias <timido@ubuntu.com>, 2015.
#
msgid ""
msgstr ""
"Project-Id-Version: limnoria\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2014-12-24 15:43+0000\n"
"PO-Revision-Date: 2015-01-02 18:47+0000\n"
"Last-Translator: Aaron Farias <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2015-01-02 18:48+0000\n"
"X-Generator: Launchpad (build 17286)\n"
#: config.py:46
msgid ""
"Determines whether this plugin is enabled.\n"
" "
msgstr ""
"Determina si este plugin está habilitado.\n"
" "
#: config.py:49
msgid ""
"Determines whether this plugin will automode\n"
" owners even if they don't have op/halfop/voice/whatever capability."
msgstr ""
"Determina si este plugin AutoMode\n"
"propietarios incluso si no tienen lo que sea la capacidad op/halfop/voz."
#: config.py:52
msgid ""
"Determines whether the bot will\n"
" check for 'alternative capabilities' (ie. autoop, autohalfop,\n"
" autovoice) in addition to/instead of classic ones."
msgstr ""
"Determina si el bot\n"
"comprobar 'capacidades alternativas \"(es decir. autoop, autohalfop,\n"
"autovoz) además de/en lugar de los clásicos."
#: config.py:56
msgid ""
"Determines whether the bot will \"fall\n"
" through\" to halfop/voicing when auto-opping is turned off but\n"
" auto-halfopping/voicing are turned on."
msgstr ""
"Determina si el bot \"caerá\n"
"a través \"de HALFOP / sonoridad cuando auto-Op está apagado pero\n"
"auto-halfop a si mismos / sonido están activados."
#: config.py:60
msgid ""
"Determines whether the bot will automatically\n"
" op people with the <channel>,op capability when they join the channel.\n"
" "
msgstr ""
"Determina si el bot automáticamente\n"
"dar op con el <channel>, capacidad de op cuando se unen al canal.\n"
" "
#: config.py:64
msgid ""
"Determines whether the bot will automatically\n"
" halfop people with the <channel>,halfop capability when they join the\n"
" channel."
msgstr ""
"Determina si el bot automáticamente\n"
"personas HALFOP con el <channel>, capacidad halfop cuando se unen a la\n"
"canal."
#: config.py:68
msgid ""
"Determines whether the bot will automatically\n"
" voice people with the <channel>,voice capability when they join the\n"
" channel."
msgstr ""
"Determina si el bot automáticamente\n"
"gente de voz con el <channel>, capacidad de voz cuando se unen a la\n"
"canal."
#: config.py:72
msgid ""
"Determines whether the bot will automatically\n"
" ban people who join the channel and are on the banlist."
msgstr ""
"Determina si el bot automáticamente\n"
"prohibir a la gente que se unen al canal y están en la lista de ban."
#: config.py:75
msgid ""
"Determines how many seconds the bot\n"
" will automatically ban a person when banning."
msgstr ""
"Determina cuántos segundos el bot\n"
"prohibirá automáticamente a una persona cuando la prohibición."
#: config.py:79
msgid ""
"Determines how many seconds the bot will wait\n"
" before applying a mode. Has no effect on bans."
msgstr ""
"Determina cuántos segundos esperará el bot\n"
"antes de aplicar un modo. No tiene ningún efecto sobre las prohibiciones."
#: config.py:83
msgid ""
"Extra modes that will be\n"
" applied to a user. Example syntax: user1+o-v user2+v user3-v"
msgstr ""
"Modos adicionales que serán\n"
" se aplica a un usuario. Ejemplo de sintaxis: usuario1 + o-v + v usuario2 "
"usuario3-v"
#: plugin.py:48
#, docstring
msgid ""
"This plugin, when configured, allows the bot to automatically set modes\n"
" on users when they join."
msgstr ""
"Este plugin, cuando se configura, permite al bot para establecer "
"automáticamente los modos\n"
"en los usuarios cuando se unen."
#: plugin.py:80
#, docstring
msgid ""
"Determines whether or not a mode has already\n"
" been applied."
msgstr ""
"Determina si un modo ya tiene o \n"
"ha aplicado."

View File

@ -0,0 +1,131 @@
# AutoMode plugin in Limnoria.
# Copyright (C) 2011, 2012 Limnoria
# Mikaela Suomalainen <mkaysi@outlook.com>, 2011, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: Supybot AutoMode\n"
"POT-Creation-Date: 2014-12-20 11:29+EET\n"
"PO-Revision-Date: 2014-12-20 11:38+0200\n"
"Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n"
"Language-Team: suomi <>\n"
"Language: fi_FI\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"X-Generator: Poedit 1.6.10\n"
#: config.py:46
msgid ""
"Determines whether this plugin is enabled.\n"
" "
msgstr "Määrittää onko tämä lisäosa käytössä."
#: config.py:49
msgid ""
"Determines whether this plugin will automode\n"
" owners even if they don't have op/halfop/voice/whatever capability."
msgstr ""
"Määrittää vaihtaako botti omistajien tilaa automaattisesti, vaikka heillä ei "
"olisi\n"
" op/halfop/voice/ihansama valtuutta."
#: config.py:52
msgid ""
"Determines whether the bot will\n"
" check for 'alternative capabilities' (ie. autoop, autohalfop,\n"
" autovoice) in addition to/instead of classic ones."
msgstr ""
"Määrittää tarkistaako botti 'vaihtoehtoisten valtuuksien' varalta (esim. "
"autoop,\n"
" autohalfop, autovoice) klassisten lisäksi/sijaan."
#: config.py:56
msgid ""
"Determines whether the bot will \"fall\n"
" through\" to halfop/voicing when auto-opping is turned off but\n"
" auto-halfopping/voicing are turned on."
msgstr ""
"Määrittää \"alentuuko\" botti halfoppaamiseen/voicen antamiseen kun "
"automaattinen\n"
" oprtaattori aseman antaminen on poistettu käytöstä, mutta automaattinen\n"
" puolioperaattori aseman/äänen antaminen on käytössä."
#: config.py:60
msgid ""
"Determines whether the bot will automatically\n"
" op people with the <channel>,op capability when they join the channel.\n"
" "
msgstr ""
"Määrittää oppaako botti\n"
" ihmiset, joilla on #<kanava>,op valtuus automaattisesti, kun he liittyvät "
"kanavalle."
#: config.py:64
msgid ""
"Determines whether the bot will automatically\n"
" halfop people with the <channel>,halfop capability when they join the\n"
" channel."
msgstr ""
"Määrittää antaako botti puolioperaattorin aseman automaattisesti, kun "
"ihmiset, joilla on #<kanava>,halfop valtuus liittyvät kanavalle."
#: config.py:68
msgid ""
"Determines whether the bot will automatically\n"
" voice people with the <channel>,voice capability when they join the\n"
" channel."
msgstr ""
"Määrittää antaako botti automaattisesti äänen ihmisille, joilla on #<kanava>,"
"voice\n"
" valtuus heidän liittyessään kanavalle."
#: config.py:72
msgid ""
"Determines whether the bot will automatically\n"
" ban people who join the channel and are on the banlist."
msgstr ""
"Määrittää antaako botti porttikiellon ihmisille,\n"
" jotka liittyvät kanavalle ja ovat porttikieltolistalla."
#: config.py:75
msgid ""
"Determines how many seconds the bot\n"
" will automatically ban a person when banning."
msgstr ""
"Määrittää kuinka moneksi sekuntiksi botti antaa porttikiellon henkilöle, "
"antaessaan\n"
" porttikieltoa automaattisesti."
#: config.py:79
msgid ""
"Determines how many seconds the bot will wait\n"
" before applying a mode. Has no effect on bans."
msgstr ""
"Määrittää kuinka monta sekuntia botti odottaa ennen, kuin asettaa tilan. "
"Tällä ei ole\n"
" vaikutusta porttikieltoihin."
#: config.py:83
msgid ""
"Extra modes that will be\n"
" applied to a user. Example syntax: user1+o-v user2+v user3-v"
msgstr ""
"Ylimääräiset tilat, jotka asetetaan käyttäjään.\n"
" Esimerkki syntaksi: käyttäjä1+o-v käyttäjä2+v käyttäjä3-v"
#: plugin.py:48
#, fuzzy
msgid ""
"This plugin, when configured, allows the bot to automatically set modes\n"
" on users when they join."
msgstr ""
"Määritettynä tämä plugin voi asettaa tiloja kanavalle liittyjiin "
"automaattisesti."
#: plugin.py:80
msgid ""
"Determines whether or not a mode has already\n"
" been applied."
msgstr "Määrittää asettaako botti tilan vai eikö, jos tila se on jo asetettu."

View File

@ -0,0 +1,112 @@
# Valentin Lorentz <progval@progval.net>, 2012.
msgid ""
msgstr ""
"Project-Id-Version: Limnoria\n"
"POT-Creation-Date: 2014-01-21 19:30+CET\n"
"PO-Revision-Date: 2014-01-21 19:33+0100\n"
"Last-Translator: \n"
"Language-Team: French <kde-i18n-doc@kde.org>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-SourceCharset: ASCII\n"
"X-Generator: Poedit 1.5.4\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: config.py:46
msgid ""
"Determines whether this plugin is enabled.\n"
" "
msgstr "Détermine si ce plugin est activé."
#: config.py:49
msgid ""
"Determines whether this plugin will automode\n"
" owners even if they don't have op/halfop/voice/whatever capability."
msgstr ""
"Détermine si ce plugin mettra des modes automatiques sur les owners, même si "
"ils n'ont pas la capacité op/halfop/voice/..."
#: config.py:52
msgid ""
"Determines whether the bot will\n"
" check for 'alternative capabilities' (ie. autoop, autohalfop,\n"
" autovoice) in addition to/instead of classic ones."
msgstr ""
"Détermine si le bot vérifiera les « capacités alternatives » (ie. autoop, "
"autohalfop, autovoice) en plus/à la place des capacités classiques."
#: config.py:56
msgid ""
"Determines whether the bot will \"fall\n"
" through\" to halfop/voicing when auto-opping is turned off but\n"
" auto-halfopping/voicing are turned on."
msgstr ""
"Détermine si le bot ne halfopera/voicera pas lorsque l'auto-op est "
"désactivé, même si l'auto-halfopvoice est activé."
#: config.py:60
msgid ""
"Determines whether the bot will automatically\n"
" op people with the <channel>,op capability when they join the channel.\n"
" "
msgstr ""
"Détermine si le bot opera automatiquement les gens qui ont la capacité "
"<canal>,op lorsqu'ils rejoignent le canal."
#: config.py:64
msgid ""
"Determines whether the bot will automatically\n"
" halfop people with the <channel>,halfop capability when they join the\n"
" channel."
msgstr ""
"Détermine si le bot halfopera les gens qui ont la capacité <canal>,halfop "
"lorsqu'ils rejoignent le canal."
#: config.py:68
msgid ""
"Determines whether the bot will automatically\n"
" voice people with the <channel>,voice capability when they join the\n"
" channel."
msgstr ""
"Détermine si le bot voicera automatiquement les gens avec la capacité "
"<canal>,voice lorsqu'ils rejoingent le canal."
#: config.py:72
msgid ""
"Determines whether the bot will automatically\n"
" ban people who join the channel and are on the banlist."
msgstr ""
"Détermine si le bot bannira automatiquement les personnes qui rejoignent le "
"canal et qui sont sur la liste de bannissement."
#: config.py:75
msgid ""
"Determines how many seconds the bot\n"
" will automatically ban a person when banning."
msgstr ""
"Détermine combien de secondes durera le bannissement que le bot posera sur "
"une personne."
#: config.py:79
msgid ""
"Determines how many seconds the bot will wait\n"
" before applying a mode. Has no effect on bans."
msgstr ""
"Détermine combien de secondes le bot attendra avant d'appliquer un mode. "
"Cela n'a aucun effet sur les bannissements."
#: config.py:83
msgid ""
"Extra modes that will be\n"
" applied to a user. Example syntax: user1+o-v user2+v user3-v"
msgstr ""
"Des modes supplémentaires à appliquer à un utilisateur. Par exemple : "
"user1+o-v user2+v user3-v"
#: plugin.py:78
msgid ""
"Determines whether or not a mode has already\n"
" been applied."
msgstr "Détermine si un mode a déjà été appliqué ou non."

View File

@ -0,0 +1,93 @@
msgid ""
msgstr ""
"Project-Id-Version: Limnoria\n"
"POT-Creation-Date: 2011-02-26 09:49+CET\n"
"PO-Revision-Date: 2012-04-23 19:29+0200\n"
"Last-Translator: skizzhg <skizzhg@gmx.com>\n"
"Language-Team: Italian <skizzhg@gmx.com>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: config.py:46
msgid ""
"Determines whether this plugin is enabled.\n"
" "
msgstr ""
"Determina se il plugin è abilitato.\n"
" "
#: config.py:49
msgid ""
"Determines whether this plugin will automode\n"
" owners even if they don't have op/halfop/voice/whatever capability."
msgstr ""
"Determina se il plugin darà automaticamente il mode ai proprietari.\n"
" anche se non hanno la capacità op, halfop, voice, ecc..."
#: config.py:52
msgid ""
"Determines whether the bot will \"fall\n"
" through\" to halfop/voicing when auto-opping is turned off but\n"
" auto-halfopping/voicing are turned on."
msgstr ""
"Determina se il the bot non darà l'halfop o il voice\n"
" quando l'op automatico è disabilitato ma l'halfop o il voice\n"
" automatico sono attivati."
#: config.py:56
msgid ""
"Determines whether the bot will automatically\n"
" op people with the <channel>,op capability when they join the channel.\n"
" "
msgstr ""
"Determina se il bot darà automaticamente l'op agli utenti che hanno\n"
" la capacità <canale>,op quando entrano in canale.\n"
" "
#: config.py:60
msgid ""
"Determines whether the bot will automatically\n"
" halfop people with the <channel>,halfop capability when they join the\n"
" channel."
msgstr ""
"Determina se il bot darà automaticamente l'halfop agli utenti che hanno\n"
" la capacità <canale>,halfop quando entrano in canale.\n"
" "
#: config.py:64
msgid ""
"Determines whether the bot will automatically\n"
" voice people with the <channel>,voice capability when they join the\n"
" channel."
msgstr ""
"Determina se il bot darà automaticamente il voice agli utenti che hanno\n"
" la capacità <canale>,voice quando entrano in canale.\n"
" "
#: config.py:68
msgid ""
"Determines whether the bot will automatically\n"
" ban people who join the channel and are on the banlist."
msgstr ""
"Determina se il bot bannerà automaticamente gli utenti che\n"
" entrano in canale e sono nella lista dei ban.\n"
" "
#: config.py:71
msgid ""
"Determines how many seconds the bot\n"
" will automatically ban a person when banning."
msgstr ""
"Determina quanti secondi durerà il ban applicato a un utente."
#: config.py:75
msgid ""
"Determines how many seconds the bot will wait\n"
" before applying a mode. Has no effect on bans."
msgstr ""
"Determina quanti secondi aspetterà il bot prima di applicare un mode.\n"
" Non ha effetto sui ban."

View File

@ -0,0 +1,102 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2014-12-20 11:29+EET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
#: config.py:46
msgid ""
"Determines whether this plugin is enabled.\n"
" "
msgstr ""
#: config.py:49
msgid ""
"Determines whether this plugin will automode\n"
" owners even if they don't have op/halfop/voice/whatever capability."
msgstr ""
#: config.py:52
msgid ""
"Determines whether the bot will\n"
" check for 'alternative capabilities' (ie. autoop, autohalfop,\n"
" autovoice) in addition to/instead of classic ones."
msgstr ""
#: config.py:56
msgid ""
"Determines whether the bot will \"fall\n"
" through\" to halfop/voicing when auto-opping is turned off but\n"
" auto-halfopping/voicing are turned on."
msgstr ""
#: config.py:60
msgid ""
"Determines whether the bot will automatically\n"
" op people with the <channel>,op capability when they join the channel.\n"
" "
msgstr ""
#: config.py:64
msgid ""
"Determines whether the bot will automatically\n"
" halfop people with the <channel>,halfop capability when they join the\n"
" channel."
msgstr ""
#: config.py:68
msgid ""
"Determines whether the bot will automatically\n"
" voice people with the <channel>,voice capability when they join the\n"
" channel."
msgstr ""
#: config.py:72
msgid ""
"Determines whether the bot will automatically\n"
" ban people who join the channel and are on the banlist."
msgstr ""
#: config.py:75
msgid ""
"Determines how many seconds the bot\n"
" will automatically ban a person when banning."
msgstr ""
#: config.py:79
msgid ""
"Determines how many seconds the bot will wait\n"
" before applying a mode. Has no effect on bans."
msgstr ""
#: config.py:83
msgid ""
"Extra modes that will be\n"
" applied to a user. Example syntax: user1+o-v user2+v user3-v"
msgstr ""
#: plugin.py:48
#, docstring
msgid ""
"This plugin, when configured, allows the bot to automatically set modes\n"
" on users when they join."
msgstr ""
#: plugin.py:80
#, docstring
msgid ""
"Determines whether or not a mode has already\n"
" been applied."
msgstr ""

163
plugins/AutoMode/plugin.py Normal file
View File

@ -0,0 +1,163 @@
###
# Copyright (c) 2004, Jeremiah Fincher
# Copyright (c) 2009, James McCoy
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import re
import time
import supybot.log as log
import supybot.conf as conf
import supybot.ircdb as ircdb
import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
import supybot.schedule as schedule
import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('AutoMode')
class Continue(Exception):
pass # Used below, look in the "do" function nested in doJoin.
class AutoMode(callbacks.Plugin):
"""This plugin, when configured, allows the bot to automatically set modes
on users when they join."""
def doJoin(self, irc, msg):
channel = msg.channel
network = irc.network
if ircutils.strEqual(irc.nick, msg.nick):
return
if not self.registryValue('enable', channel, network):
return
fallthrough = self.registryValue('fallthrough', channel, network)
def do(type):
cap = ircdb.makeChannelCapability(channel, type)
cap_auto = ircdb.makeChannelCapability(channel, 'auto'+type)
try:
apply_mode = ircdb.checkCapability(msg.prefix, cap,
ignoreOwner=not self.registryValue('owner'),
ignoreChannelOp=True, ignoreDefaultAllow=True)
except KeyError:
apply_mode = False
if self.registryValue('alternativeCapabilities', channel, network):
try:
override = ircdb.checkCapability(msg.prefix, cap_auto,
ignoreOwner=not self.registryValue('owner'),
ignoreChannelOp=True, ignoreDefaultAllow=True)
except KeyError:
override = False
else:
override = False
if apply_mode or override:
if override or self.registryValue(type, channel, network):
self.log.info('Scheduling auto-%s of %s in %s @ %s.',
type, msg.prefix, channel, network)
def dismiss():
"""Determines whether or not a mode has already
been applied."""
l = getattr(irc.state.channels[channel], type+'s')
return (msg.nick in l)
msgmaker = getattr(ircmsgs, type)
schedule_msg(msgmaker(channel, msg.nick),
dismiss)
raise Continue # Even if fallthrough, let's only do one.
elif not fallthrough:
self.log.debug('%s has %s, but supybot.plugins.AutoMode.%s'
' is not enabled in %s @ %s, refusing to '
'fall through.',
msg.prefix, cap, type, channel, network)
raise Continue
def schedule_msg(msg, dismiss):
def f():
if not dismiss():
irc.queueMsg(msg)
else:
self.log.info('Dismissing auto-mode for %s.', msg.args[2])
delay = self.registryValue('delay', channel, network)
if delay:
schedule.addEvent(f, time.time() + delay)
else:
f()
def extra_modes():
try:
user = ircdb.users.getUser(ircdb.users.getUserId(msg.prefix))
except KeyError:
return
pattern = re.compile(r'-|\+')
for item in self.registryValue('extra', channel, network):
try:
username, modes = pattern.split(item, maxsplit=1)
modes = item[len(username)] + modes
except ValueError: # No - or + in item
log.error(('%r is not a valid item for '
'supybot.plugins.AutoMode.extra') % item)
continue
if username != user.name:
continue
else:
self.log.info('Scheduling auto-modes %s of %s in %s @ %s.',
modes, msg.prefix, channel, network)
modes = [modes] + \
([msg.nick]*len(pattern.sub('', modes)))
schedule_msg(ircmsgs.mode(channel, modes), lambda :False)
break
try:
do('op')
if 'h' in irc.state.supported['prefix']:
do('halfop')
except Continue:
return
finally:
extra_modes()
c = ircdb.channels.getChannel(channel)
if c.checkBan(msg.prefix) and self.registryValue('ban',
channel, network):
period = self.registryValue('ban.period', channel, network)
if period:
def unban():
try:
if msg.prefix in irc.state.channels[channel].bans:
irc.queueMsg(ircmsgs.unban(channel, msg.prefix))
except KeyError:
# We're not in the channel anymore.
pass
schedule.addEvent(unban, time.time()+period)
banmask =conf.supybot.protocols.irc.banmask.makeBanmask(msg.prefix)
irc.queueMsg(ircmsgs.ban(channel, banmask))
irc.queueMsg(ircmsgs.kick(channel, msg.nick))
try:
do('voice')
except Continue:
return
Class = AutoMode
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

Some files were not shown because too many files have changed in this diff Show More