mirror of
https://github.com/Mikaela/Limnoria.git
synced 2025-08-13 08:27:22 +02:00
Compare commits
No commits in common. "gh-pages" and "v0.83.4.1+limnoria1" have entirely different histories.
gh-pages
...
v0.83.4.1+
@ -1,8 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -1 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
test export-ignore
|
||||
sandbox export-ignore
|
||||
.git* export-ignore
|
||||
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
||||
* @Mikaela
|
5
.github/renovate.json5
vendored
5
.github/renovate.json5
vendored
@ -1,5 +0,0 @@
|
||||
/** @format */
|
||||
|
||||
{
|
||||
extends: ["local>Mikaela/shell-things:.renovate-shared"],
|
||||
}
|
25
.github/workflows/html5validator.yml.disabled
vendored
25
.github/workflows/html5validator.yml.disabled
vendored
@ -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
|
39
.gitignore
vendored
39
.gitignore
vendored
@ -1,32 +1,15 @@
|
||||
*.mo
|
||||
*.py[cdo]
|
||||
backup
|
||||
build
|
||||
test-data
|
||||
test-conf
|
||||
test-logs
|
||||
*.pyc
|
||||
docs/_build
|
||||
docs/plugins
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.*.swp
|
||||
.swp
|
||||
MANIFEST
|
||||
backup/
|
||||
build/
|
||||
debian/compat
|
||||
debian/files
|
||||
*.mo
|
||||
debian/limnoria*
|
||||
debian/python-module-stampdir/
|
||||
dist/
|
||||
docs/_build/
|
||||
docs/plugins/
|
||||
limnoria.egg-info/
|
||||
merge.sh
|
||||
nano.save
|
||||
push.sh
|
||||
py3k/
|
||||
src/version.py
|
||||
supybot.egg-info/
|
||||
test-conf/
|
||||
test-data/
|
||||
test-logs/
|
||||
src/version.py
|
||||
_site
|
||||
.sass-cache
|
||||
vendor/
|
||||
.bundle
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
|
@ -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]
|
@ -1,5 +0,0 @@
|
||||
_includes/
|
||||
_layouts/
|
||||
_sass/
|
||||
css/
|
||||
feed.xml
|
@ -1 +0,0 @@
|
||||
{}
|
@ -1 +0,0 @@
|
||||
3.4.2
|
@ -1,4 +0,0 @@
|
||||
# @format
|
||||
|
||||
language: ruby
|
||||
script: "bundle exec jekyll build"
|
12
ACKS
Normal file
12
ACKS
Normal file
@ -0,0 +1,12 @@
|
||||
* johhnyace, who gave me the modem that helped me tremendously early on
|
||||
in development.
|
||||
|
||||
* sweede, for hosting the "main" supybot for awhile.
|
||||
|
||||
* bwp, who rewrote the Http.weather command (ham site), and also
|
||||
hosted the canonical supybot in #supybot on OFTC and Freenode for
|
||||
quite some time.
|
||||
|
||||
* HostPC.com, for hosting the current canonical "supybot" and for
|
||||
graciously providing DNS services and email.
|
||||
|
8
Gemfile
8
Gemfile
@ -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"
|
286
Gemfile.lock
286
Gemfile.lock
@ -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
|
83
INSTALL
Normal file
83
INSTALL
Normal file
@ -0,0 +1,83 @@
|
||||
Common
|
||||
|
||||
First things first: Supybot *requires* at least Python 2.4. There
|
||||
ain't no getting around it. You can get it from http://www.python.org/.
|
||||
|
||||
Recommended Software
|
||||
|
||||
PySQLite -- Version 1.x
|
||||
|
||||
Twisted -- Version 1.2.0 or greater
|
||||
|
||||
For more information and help on how to use Supybot, checkout
|
||||
the documents under docs/ (especially GETTING_STARTED and
|
||||
CONFIGURATION).
|
||||
|
||||
So what do you do? That depends on which operating system you're
|
||||
running. We've split this document up to address the different
|
||||
methods, so find the section for your operating system and continue
|
||||
from there.
|
||||
|
||||
UNIX/Linux/BSD
|
||||
|
||||
If you're installing Python using your distributor's packages, you may
|
||||
need a python-dev package installed, too. If you don't have a
|
||||
'/usr/lib/python2.x/distutils' directory or
|
||||
'/usr/lib/python2.x/config/Makefile' (assuming '/usr/lib/python2.x' is
|
||||
where your Python libs are installed), then you will need a python-dev
|
||||
package.
|
||||
|
||||
After you extract Supybot and cd into the supybot directory just
|
||||
created, you'll want to run (as root) 'python setup.py install'. This
|
||||
will install Supybot globally. If you need to install locally for
|
||||
whatever reason, see the notes at the end of this section. You'll then
|
||||
have several new programs installed where Python scripts are normally
|
||||
installed on your system ('/usr/bin' or '/usr/local/bin' are common on
|
||||
UNIX systems). The two that might be of particular interest to you, the
|
||||
new user, are 'supybot' and 'supybot-wizard'. The former, 'supybot', is
|
||||
the script to run an actual bot; the latter, 'supybot-wizard', is an
|
||||
in-depth wizard that provides a nice user interface for creating a
|
||||
registry file for your bot.
|
||||
|
||||
Local Install
|
||||
|
||||
You can install Supybot in a local directory by using the '--prefix'
|
||||
option when running 'setup.py'. E.g., 'python setup.py install
|
||||
--prefix=$HOME' to install into your home directory. You'll now have
|
||||
a $HOME/bin directory containing Supybot programs ('supybot',
|
||||
'supybot-wizard', etc.) and a $HOME/lib directory containing the
|
||||
Supybot libraries. It is also recommended that you setup a proper
|
||||
PYTHONPATH environment variable in your shell's init file.
|
||||
|
||||
bash -- 'export PYTHONPATH=$HOME/lib/python2.x/site-packages'
|
||||
|
||||
(t)csh -- 'setenv PYTHONPATH $HOME/lib/python2.x/site-packages'
|
||||
|
||||
Windows
|
||||
|
||||
**Note**: If you are using an IPV6 connection, you will not be able
|
||||
to run Supybot under Windows (unless Python has fixed things). Current
|
||||
versions of Python for Windows are *not* built with IPV6 support. This
|
||||
isn't expected to be fixed until Python 2.4, at the earliest.
|
||||
|
||||
Now that you have Python installed, open up a command prompt. The
|
||||
easiest way to do this is to open the run dialog (Programs -> run) and
|
||||
type "cmd" (for Windows 2000/XP/2003) or "command" (for Windows 9x). In
|
||||
order to reduce the amount of typing you need to do, I suggest adding
|
||||
Python's directory to your path. If you installed Python using the
|
||||
default settings, you would then do the following in the command prompt
|
||||
(otherwise change the path to match your settings)::
|
||||
|
||||
set PATH=C:\Python2x\;%PATH%
|
||||
|
||||
You should now be able to type 'python' to start the Python
|
||||
interpreter. Exit by pressing CTRL-Z and then Return. Now that that's
|
||||
setup, you'll want to cd into the directory that was created when you
|
||||
unzipped Supybot; I'll assume you unzipped it to 'C:\Supybot' for these
|
||||
instructions. From 'C:\Supybot', run 'python setup.py install'. This
|
||||
will install Supybot under 'C:\Python2x\'. You will now have several new
|
||||
programs installed in 'C:\Python2x\Scripts\'. The two that might be of
|
||||
particular interest to you, the new user, are 'supybot' and 'supybot-wizard'.
|
||||
The former, 'supybot', is the script to run an actual bot; the latter,
|
||||
'supybot-wizard', is an in-depth wizard that provides a nice user interface for
|
||||
creating a registry file for your bot.
|
28
LICENSE
Normal file
28
LICENSE
Normal 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.
|
36
Makefile
Normal file
36
Makefile
Normal file
@ -0,0 +1,36 @@
|
||||
PYTHON=`which python`
|
||||
DESTDIR=/
|
||||
BUILDIR=$(CURDIR)/debian/limnoria
|
||||
PROJECT=limnoria
|
||||
VERSION=0.83.4.1+limnoria1
|
||||
|
||||
all:
|
||||
@echo "make source - Create source package"
|
||||
@echo "make install - Install on local system"
|
||||
@echo "make buildrpm - Generate a rpm package"
|
||||
@echo "make builddeb - Generate a deb package"
|
||||
@echo "make clean - Get rid of scratch and byte files"
|
||||
|
||||
source:
|
||||
$(PYTHON) setup.py sdist $(COMPILE)
|
||||
|
||||
install:
|
||||
$(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE)
|
||||
|
||||
buildrpm:
|
||||
$(PYTHON) setup.py bdist_rpm --post-install=rpm/postinstall --pre-uninstall=rpm/preuninstall
|
||||
|
||||
builddeb:
|
||||
# build the source package in the parent directory
|
||||
# then rename it to project_version.orig.tar.gz
|
||||
$(PYTHON) setup.py sdist $(COMPILE) --dist-dir=../ --prune
|
||||
rename -f 's/$(PROJECT)-(.*)\.tar\.gz/$(PROJECT)_$$1\.orig\.tar\.gz/' ../*
|
||||
# build the package
|
||||
dpkg-buildpackage -i -I -rfakeroot
|
||||
|
||||
clean:
|
||||
$(PYTHON) setup.py clean
|
||||
$(MAKE) -f $(CURDIR)/debian/rules clean
|
||||
rm -rf build/ MANIFEST
|
||||
find . -name '*.pyc' -delete
|
||||
|
29
README
Normal file
29
README
Normal file
@ -0,0 +1,29 @@
|
||||
EVERYONE:
|
||||
---------
|
||||
Read LICENSE. It's a 3-clause BSD license, but you should read it
|
||||
anyway.
|
||||
|
||||
USERS:
|
||||
------
|
||||
If you're upgrading, read RELNOTES. There is also much documentation
|
||||
at http://supybot.com/ for your perusal. Please read it; we took the
|
||||
time to write it, you should take the time to read it.
|
||||
|
||||
If you have any trouble, feel free to swing by #supybot on
|
||||
irc.freenode.net or irc.oftc.net (we have a Supybot there relaying,
|
||||
so either network works) 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 Sourceforge.net so we can improve the bot!
|
||||
|
||||
WINDOWS USERS:
|
||||
--------------
|
||||
The wizards (supybot-wizard, supybot-newplugin, and
|
||||
supybot-adduser) are all installed to your Python directory's
|
||||
\Scripts. What that *probably* means is that you'll run them like
|
||||
this: C:\Python2x\python C:\Python2x\Scripts\supybot-wizard
|
||||
|
||||
DEVELOPERS:
|
||||
-----------
|
||||
We likewise have tons of developer documentation at
|
||||
http://supybot.com/ for your learning adventures. Have fun :)
|
11
README.md
11
README.md
@ -1,11 +0,0 @@
|
||||
<!-- @format -->
|
||||
|
||||
# Mikaela's fork of Limnoria.
|
||||
|
||||
There are mainly two branches. This one which you are looking at, gh-pages which
|
||||
is the source of <https://supybot.mikaela.info/>.
|
||||
|
||||
**testing** which will be synced with [ProgVal/Limnoria] when needed. It is used
|
||||
as base for my changes which will be pull requested.
|
||||
|
||||
[ProgVal/Limnoria]: https://github.com/ProgVal/Limnoria.git
|
433
RELNOTES
Normal file
433
RELNOTES
Normal file
@ -0,0 +1,433 @@
|
||||
Version 0.83.5
|
||||
|
||||
The minimum supported Python version has been bumped to 2.4.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
@ -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?
|
||||
|
||||
---
|
145
Supybot.markdown
145
Supybot.markdown
@ -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)
|
38
_config.yml
38
_config.yml
@ -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
|
@ -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>
|
@ -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>
|
210
_sass/_base.scss
210
_sass/_base.scss
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
145
assets/main.scss
145
assets/main.scss
@ -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;
|
||||
}
|
||||
}
|
5
debian/changelog
vendored
Normal file
5
debian/changelog
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
limnoria (0.83.4.1+limnoria1) unstable; urgency=low
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Valentin Lorentz <progval@gmail.com> Sun, 21 Aug 2011 20:58:51 +0200
|
23
debian/control
vendored
Normal file
23
debian/control
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
Source: limnoria
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Valentin Lorentz <progval@gmail.com>
|
||||
Build-Depends: debhelper (>=3.9.2), python-support (>= 0.6), cdbs (>= 0.4.49), python (>=2.6), python-setuptools
|
||||
XS-Python-Version: >=2.6
|
||||
Standards-Version: 3.9.2
|
||||
|
||||
Package: limnoria
|
||||
Architecture: all
|
||||
Depends: python (>= 2.6), python-support (>= 0.90.0), ${misc:Depends}
|
||||
Recommends: python-simplejson, python-feedparser, python-sqlite3
|
||||
Suggests: python-twisted-core, python-twisted-names, python-dictclient, python-dateutil
|
||||
Conflicts: supybot
|
||||
Provides: supybot
|
||||
Section: net
|
||||
Priority: optional
|
||||
Homepage: https://github.com/ProgVal/Limnoria
|
||||
Description: Fork of the robust and user-friendly Python IRC bot Supybot.
|
||||
It provides several enhancements, such as internationalization and
|
||||
embedded HTTP server available to plugins. All plugins written for
|
||||
Supybot are still compatible with Limnoria.
|
||||
|
45
debian/copyright
vendored
Normal file
45
debian/copyright
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
Upstream Author:
|
||||
|
||||
Jeremiah Fincher and others
|
||||
|
||||
Files: *
|
||||
Copyright:
|
||||
2002-2011 Jeremiah Fincher and others
|
||||
License: BSD
|
||||
|
||||
Files: debian/*
|
||||
Copyright:
|
||||
2002-2009, James Vega
|
||||
2011, Valentin Lorentz
|
||||
License: BSD
|
||||
|
||||
License: BSD
|
||||
|
||||
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.
|
3
debian/postinst
vendored
Executable file
3
debian/postinst
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
#DEBHELPER#
|
3
debian/prerm
vendored
Executable file
3
debian/prerm
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
#DEBHELPER#
|
11
debian/rules
vendored
Executable file
11
debian/rules
vendored
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
DEB_PYTHON_SYSTEM := pysupport
|
||||
|
||||
include /usr/share/cdbs/1/rules/debhelper.mk
|
||||
include /usr/share/cdbs/1/class/python-distutils.mk
|
||||
|
||||
clean::
|
||||
rm -rf build build-stamp configure-stamp build/ MANIFEST
|
||||
dh_clean
|
342
docs/ADVANCED_PLUGIN_CONFIG.rst
Normal file
342
docs/ADVANCED_PLUGIN_CONFIG.rst
Normal file
@ -0,0 +1,342 @@
|
||||
Advanced Plugin Config
|
||||
----------------------
|
||||
This tutorial covers some of the more advanced plugin config features available
|
||||
to Supybot plugin authors.
|
||||
|
||||
What's This Tutorial For?
|
||||
=========================
|
||||
Brief overview of what this tutorial covers and the target audience.
|
||||
|
||||
Want to know the crazy advanced features available to you, the Supybot plugin
|
||||
author? Well, this is the tutorial for you. This article assumes you've read
|
||||
the Supybot plugin author tutorial since all the basics of plugin config are
|
||||
handled there first.
|
||||
|
||||
In this tutorial we'll cover:
|
||||
|
||||
* Using the configure function more effectively by using the functions
|
||||
provided in supybot.questions
|
||||
* Creating config variable groups and config variables underneath those
|
||||
groups.
|
||||
* The built-in config variable types ("registry types") for use with config
|
||||
variables
|
||||
* Creating custom registry types to handle config variable values more
|
||||
effectively
|
||||
|
||||
Using 'configure' effectively
|
||||
=============================
|
||||
How to use 'configure' effectively using the functions from
|
||||
'supybot.questions'
|
||||
|
||||
In the original Supybot plugin author tutorial you'll note that we gloss over
|
||||
the configure portion of the config.py file for the sake of keeping the
|
||||
tutorial to a reasonable length. Well, now we're going to cover it in more
|
||||
detail.
|
||||
|
||||
The supybot.questions module is a nice little module coded specifically to help
|
||||
clean up the configure section of every plugin's config.py. The boilerplate
|
||||
config.py code imports the four most useful functions from that module:
|
||||
|
||||
* "expect" is a very general prompting mechanism which can specify certain
|
||||
inputs that it will accept and also specify a default response. It takes
|
||||
the following arguments:
|
||||
- prompt: The text to be displayed
|
||||
- possibilities: The list of possible responses (can be the empty
|
||||
list, [])
|
||||
- default (optional): Defaults to None. Specifies the default value
|
||||
to use if the user enters in no input.
|
||||
- acceptEmpty (optional): Defaults to False. Specifies whether or not
|
||||
to accept no input as an answer.
|
||||
|
||||
* "anything" is basically a special case of expect which takes anything
|
||||
(including no input) and has no default value specified. It takes only
|
||||
one argument:
|
||||
- prompt: The text to be displayed
|
||||
|
||||
* "something" is also a special case of expect, requiring some input and
|
||||
allowing an optional default. It takes the following arguments:
|
||||
- prompt: The text to be displayed
|
||||
- default (optional): Defaults to None. The default value to use if
|
||||
the user doesn't input anything.
|
||||
|
||||
* "yn" is for "yes or no" questions and basically forces the user to input
|
||||
a "y" for yes, or "n" for no. It takes the following arguments:
|
||||
- prompt: The text to be displayed
|
||||
- default (optional): Defaults to None. Default value to use if the
|
||||
user doesn't input anything.
|
||||
|
||||
All of these functions, with the exception of "yn", return whatever string
|
||||
results as the answer whether it be input from the user or specified as the
|
||||
default when the user inputs nothing. The "yn" function returns True for "yes"
|
||||
answers and False for "no" answers.
|
||||
|
||||
For the most part, the latter three should be sufficient, but we expose expect
|
||||
to anyone who needs a more specialized configuration.
|
||||
|
||||
Let's go through a quick example configure that covers all four of these
|
||||
functions. First I'll give you the code, and then we'll go through it,
|
||||
discussing each usage of a supybot.questions function just to make sure you
|
||||
realize what the code is actually doing. Here it is:
|
||||
|
||||
def configure(advanced):
|
||||
# This will be called by supybot to configure this module. advanced is
|
||||
# a bool that specifies whether the user identified himself 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
|
||||
WorldDom = conf.registerPlugin('WorldDom', True)
|
||||
if yn("""The WorldDom plugin allows for total world domination
|
||||
with simple commands. Would you like these commands to
|
||||
be enabled for everyone?""", default=False):
|
||||
WorldDom.globalWorldDominationRequires.setValue("")
|
||||
else:
|
||||
cap = something("""What capability would you like to require for
|
||||
this command to be used?""", default="Admin")
|
||||
WorldDom.globalWorldDominationRequires.setValue(cap)
|
||||
dir = expect("""What direction would you like to attack from in
|
||||
your quest for world domination?""",
|
||||
["north", "south", "east", "west", "ABOVE"],
|
||||
default="ABOVE")
|
||||
WorldDom.attackDirection.setValue(dir)
|
||||
|
||||
As you can see, this is the WorldDom plugin, which I am currently working on.
|
||||
The first thing our configure function checks is to see whether or not the bot
|
||||
owner would like the world domination commands in this plugin to be available
|
||||
to everyone. If they say yes, we set the globalWorldDominationRequires
|
||||
configuration variable to the empty string, signifying that no specific
|
||||
capabilities are necessary. If they say no, we prompt them for a specific
|
||||
capability to check for, defaulting to the "Admin" capability. Here they can
|
||||
create their own custom capability to grant to folks which this plugin will
|
||||
check for if they want, but luckily for the bot owner they don't really have to
|
||||
do this since Supybot's capabilities system can be flexed to take care of this.
|
||||
|
||||
Lastly, we check to find out what direction they want to attack from as they
|
||||
venture towards world domination. I prefer "death from above!", so I made that
|
||||
the default response, but the more boring cardinal directions are available as
|
||||
choices as well.
|
||||
|
||||
Using Config Groups
|
||||
===================
|
||||
A brief overview of how to use config groups to organize config variables
|
||||
|
||||
Supybot's Hierarchical Configuration
|
||||
|
||||
Supybot's configuration is inherently hierarchical, as you've probably already
|
||||
figured out in your use of the bot. Naturally, it makes sense to allow plugin
|
||||
authors to create their own hierarchies to organize their configuration
|
||||
variables for plugins that have a lot of plugin options. If you've taken a look
|
||||
at the plugins that Supybot comes with, you've probably noticed that several of
|
||||
them take advantage of this. In this section of this tutorial we'll go over how
|
||||
to make your own config hierarchy for your plugin.
|
||||
|
||||
Here's the brilliant part about Supybot config values which makes hierarchical
|
||||
structuring all that much easier - values are groups. That is, any config value
|
||||
you may already defined in your plugins can already be treated as a group, you
|
||||
simply need to know how to add items to that group.
|
||||
|
||||
Now, if you want to just create a group that doesn't have an inherent value you
|
||||
can do that as well, but you'd be surprised at how rarely you have to do that.
|
||||
In fact if you look at most of the plugins that Supybot comes with, you'll only
|
||||
find that we do this in a handful of spots yet we use the "values as groups"
|
||||
feature quite a bit.
|
||||
|
||||
Creating a Config Group
|
||||
=======================
|
||||
|
||||
As stated before, config variables themselves are groups, so you can create a
|
||||
group simply by creating a configuration variable:
|
||||
|
||||
conf.registerGlobalValue(WorldDom, 'globalWorldDominationRequires',
|
||||
registry.String('', """Determines the capability required to access the
|
||||
world domination commands in this plugin."""))
|
||||
|
||||
As you probably know by now this creates the config variable
|
||||
supybot.plugins.WorldDom.globalWorldDominationRequires which you can access/set
|
||||
using the Config plugin directly on the running bot. What you may not have
|
||||
known prior to this tutorial is that that variable is also a group.
|
||||
Specifically, it is now the WorldDom.globalWorldDominationRequires group, and
|
||||
we can add config variables to it! Unfortunately, this particular bit of
|
||||
configuration doesn't really require anything underneath it, so let's create a
|
||||
new group which does using the "create only a group, not a value" command.
|
||||
|
||||
Let's create a configurable list of targets for different types of attacks
|
||||
(land, sea, air, etc.). We'll call the group attackTargets. Here's how you
|
||||
create just a config group alone with no value assigned:
|
||||
|
||||
conf.registerGroup(WorldDom, 'attackTargets')
|
||||
|
||||
The first argument is just the group under which you want to create your new
|
||||
group (and we got WorldDom from conf.registerPlugin which was in our
|
||||
boilerplate code from the plugin creation wizard). The second argument is, of
|
||||
course, the group name. So now we have WorldDom.attackTargets (or, fully,
|
||||
supybot.plugins.WorldDom.attackTargets).
|
||||
|
||||
Adding Values to a Group
|
||||
========================
|
||||
|
||||
Actually, you've already done this several times, just never to a custom group
|
||||
of your own. You've always added config values to your plugin's config group.
|
||||
With that in mind, the only slight modification needed is to simply point to
|
||||
the new group:
|
||||
|
||||
conf.registerGlobalValue(WorldDom.attackTargets, 'air',
|
||||
registry.SpaceSeparatedListOfStrings('', """Contains the list of air
|
||||
targets."""))
|
||||
|
||||
And now we have a nice list of air targets! You'll notice that the first
|
||||
argument is WorldDom.attackTargets, our new group. Make sure that the
|
||||
conf.registerGroup call is made before this one or else you'll get a nasty
|
||||
AttributeError.
|
||||
|
||||
The Built-in Registry Types
|
||||
===========================
|
||||
A rundown of all of the built-in registry types available for use with config
|
||||
variables.
|
||||
|
||||
The "registry" module defines the following config variable types for your use
|
||||
(I'll include the 'registry.' on each one since that's how you'll refer to it in
|
||||
code most often). Most of them are fairly self-explanatory, so excuse the
|
||||
boring descriptions:
|
||||
|
||||
* registry.Boolean - A simple true or false value. Also accepts the
|
||||
following for true: "true", "on" "enable", "enabled", "1", and the
|
||||
following for false: "false", "off", "disable", "disabled", "0",
|
||||
|
||||
* registry.Integer - Accepts any integer value, positive or negative.
|
||||
|
||||
* registry.NonNegativeInteger - Will hold any non-negative integer value.
|
||||
|
||||
* registry.PositiveInteger - Same as above, except that it doesn't accept 0
|
||||
as a value.
|
||||
|
||||
* registry.Float - Accepts any floating point number.
|
||||
|
||||
* registry.PositiveFloat - Accepts any positive floating point number.
|
||||
|
||||
* registry.Probability - Accepts any floating point number between 0 and 1
|
||||
(inclusive, meaning 0 and 1 are also valid).
|
||||
|
||||
* registry.String - Accepts any string that is not a valid Python command
|
||||
|
||||
* registry.NormalizedString - Accepts any string (with the same exception
|
||||
above) but will normalize sequential whitespace to a single space..
|
||||
|
||||
* registry.StringSurroundedBySpaces - Accepts any string but assures that
|
||||
it has a space preceding and following it. Useful for configuring a
|
||||
string that goes in the middle of a response.
|
||||
|
||||
* registry.StringWithSpaceOnRight - Also accepts any string but assures
|
||||
that it has a space after it. Useful for configuring a string that
|
||||
begins a response.
|
||||
|
||||
* registry.Regexp - Accepts only valid (Perl or Python) regular expressions
|
||||
|
||||
* registry.SpaceSeparatedListOfStrings - Accepts a space-separated list of
|
||||
strings.
|
||||
|
||||
There are a few other built-in registry types that are available but are not
|
||||
usable in their current state, only by creating custom registry types, which
|
||||
we'll go over in the next section.
|
||||
|
||||
Custom Registry Types
|
||||
=====================
|
||||
How to create and use your own custom registry types for use in customizing
|
||||
plugin config variables.
|
||||
|
||||
Why Create Custom Registry Types?
|
||||
|
||||
For most configuration, the provided types in the registry module are
|
||||
sufficient. However, for some configuration variables it's not only convenient
|
||||
to use custom registry types, it's actually recommended. Customizing registry
|
||||
types allows for tighter restrictions on the values that get set and for
|
||||
greater error-checking than is possible with the provided types.
|
||||
|
||||
What Defines a Registry Type?
|
||||
|
||||
First and foremost, it needs to subclass one of the existing registry types
|
||||
from the registry module, whether it be one of the ones in the previous section
|
||||
or one of the other classes in registry specifically designed to be subclassed.
|
||||
|
||||
Also it defines a number of other nice things: a custom error message for your
|
||||
type, customized value-setting (transforming the data you get into something
|
||||
else if wanted), etc.
|
||||
|
||||
Creating Your First Custom Registry Type
|
||||
|
||||
As stated above, priority number one is that you subclass one of the types in
|
||||
the registry module. Basically, you just subclass one of those and then
|
||||
customize whatever you want. Then you can use it all you want in your own
|
||||
plugins. We'll do a quick example to demonstrate.
|
||||
|
||||
We already have registry.Integer and registry.PositiveInteger, but let's say we
|
||||
want to accept only negative integers. We can create our own NegativeInteger
|
||||
registry type like so:
|
||||
|
||||
class NegativeInteger(registry.Integer):
|
||||
"""Value must be a negative integer."""
|
||||
def setValue(self, v):
|
||||
if v >= 0:
|
||||
self.error()
|
||||
registry.Integer.setValue(self, v)
|
||||
|
||||
All we need to do is define a new error message for our custom registry type
|
||||
(specified by the docstring for the class), and customize the setValue
|
||||
function. Note that all you have to do when you want to signify that you've
|
||||
gotten an invalid value is to call self.error(). Finally, we call the parent
|
||||
class's setValue to actually set the value.
|
||||
|
||||
What Else Can I Customize?
|
||||
|
||||
Well, the error string and the setValue function are the most useful things
|
||||
that are available for customization, but there are other things. For examples,
|
||||
look at the actual built-in registry types defined in registry.py (in the src
|
||||
directory distributed with the bot).
|
||||
|
||||
What Subclasses Can I Use?
|
||||
|
||||
Chances are one of the built-in types in the previous section will be
|
||||
sufficient, but there are a few others of note which deserve mention:
|
||||
|
||||
* registry.Value - Provides all the core functionality of registry types
|
||||
(including acting as a group for other config variables to reside
|
||||
underneath), but nothing more.
|
||||
|
||||
* registry.OnlySomeStrings - Allows you to specify only a certain set of
|
||||
strings as valid values. Simply override validStrings in the inheriting
|
||||
class and you're ready to go.
|
||||
|
||||
* registry.SeparatedListOf - The generic class which is the parent class to
|
||||
registry.SpaceSeparatedListOfStrings. Allows you to customize four
|
||||
things: the type of sequence it is (list, set, tuple, etc.), what each
|
||||
item must be (String, Boolean, etc.), what separates each item in the
|
||||
sequence (using custom splitter/joiner functions), and whether or not
|
||||
the sequence is to be sorted. Look at the definitions of
|
||||
registry.SpaceSeparatedListOfStrings and
|
||||
registry.CommaSeparatedListOfStrings at the bottom of registry.py for
|
||||
more information. Also, there will be an example using this in the
|
||||
section below.
|
||||
|
||||
Using My Custom Registry Type
|
||||
|
||||
Using your new registry type is relatively straightforward. Instead of using
|
||||
whatever registry built-in you might have used before, now use your own custom
|
||||
class. Let's say we define a registry type to handle a comma-separated list of
|
||||
probabilities:
|
||||
|
||||
class CommaSeparatedListOfProbabilities(registry.SeparatedListOf):
|
||||
Value = registry.Probability
|
||||
def splitter(self, s):
|
||||
return re.split(r'\s*,\s*', s)
|
||||
joiner = ', '.join
|
||||
|
||||
Now, to use that type we simply have to specify it whenever we create a config
|
||||
variable using it:
|
||||
|
||||
conf.registerGlobalValue(SomePlugin, 'someConfVar',
|
||||
CommaSeparatedListOfProbabilities('0.0, 1.0', """Holds the list of
|
||||
probabilities for whatever."""))
|
||||
|
||||
Note that we initialize it just the same as we do any other registry type, with
|
||||
two arguments: the default value, and then the description of the config
|
||||
variable.
|
||||
|
283
docs/ADVANCED_PLUGIN_TESTING.rst
Normal file
283
docs/ADVANCED_PLUGIN_TESTING.rst
Normal file
@ -0,0 +1,283 @@
|
||||
Advanced Plugin Testing
|
||||
-----------------------
|
||||
The complete guide to writing tests for your plugins.
|
||||
|
||||
Why Write Tests?
|
||||
================
|
||||
Why should I write tests for my plugin? Here's why.
|
||||
|
||||
For those of you asking "Why should I write tests for my plugin? I tried it
|
||||
out, and it works!", read on. For those of you who already realize that
|
||||
Testing is Good (TM), skip to the next section.
|
||||
|
||||
Here are a few quick reasons why to test your Supybot plugins.
|
||||
|
||||
* When/if we rewrite or change certain features in Supybot, tests make
|
||||
sure your plugin will work with these changes. It's much easier to run
|
||||
supybot-test MyPlugin after upgrading the code and before even reloading
|
||||
the bot with the new code than it is to load the bot with new code and
|
||||
then load the plugin only to realize certain things don't work. You may
|
||||
even ultimately decide you want to stick with an older version for a while
|
||||
as you patch your custom plugin. This way you don't have to rush a patch
|
||||
while restless users complain since you're now using a newer version that
|
||||
doesn't have the plugin they really like.
|
||||
|
||||
* Running the automated tests takes a few seconds, testing plugins in IRC
|
||||
on a live bot generally takes quite a bit longer. We make it so that
|
||||
writing tests generally doesn't take much time, so a small initial
|
||||
investment adds up to lots of long-term gains.
|
||||
|
||||
* If you want your plugin to be included in any of our releases (the core
|
||||
Supybot if you think it's worthy, or our supybot-plugins package), it has
|
||||
to have tests. Period.
|
||||
|
||||
For a bigger list of why to write unit tests, check out this article:
|
||||
|
||||
http://www.onjava.com/pub/a/onjava/2003/04/02/javaxpckbk.html
|
||||
|
||||
and also check out what the Extreme Programming folks have to say about unit
|
||||
tests:
|
||||
|
||||
http://www.extremeprogramming.org/rules/unittests.html
|
||||
|
||||
Plugin Tests
|
||||
============
|
||||
How to write tests for commands in your plugins.
|
||||
|
||||
Introduction
|
||||
|
||||
This tutorial assumes you've read through the plugin author tutorial, and that
|
||||
you used supybot-plugin-create to create your plugin (as everyone should). So,
|
||||
you should already have all the necessary imports and all that boilerplate
|
||||
stuff in test.py already, and you have already seen what a basic plugin test
|
||||
looks like from the plugin author tutorial. Now we'll go into more depth about
|
||||
what plugin tests are available to Supybot plugin authors.
|
||||
|
||||
Plugin Test Case Classes
|
||||
|
||||
Supybot comes with two plugin test case classes, PluginTestCase and
|
||||
ChannelPluginTestCase. The former is used when it doesn't matter whether or
|
||||
not the commands are issued in a channel, and the latter is used for when it
|
||||
does. For the most part their API is the same, so unless there's a distinction
|
||||
between the two we'll treat them as one and the same when discussing their
|
||||
functionality.
|
||||
|
||||
The Most Basic Plugin Test Case
|
||||
|
||||
At the most basic level, a plugin test case requires three things:
|
||||
|
||||
* the class declaration (subclassing PluginTestCase or
|
||||
ChannelPluginTestCase)
|
||||
* a list of plugins that need to be loaded for these tests (does not
|
||||
include Owner, Misc, or Config, those are always automatically loaded) -
|
||||
often this is just the name of the plugin that you are writing tests for
|
||||
* some test methods
|
||||
|
||||
Here's what the most basic plugin test case class looks like (for a plugin
|
||||
named MyPlugin):
|
||||
|
||||
class MyPluginTestCase(PluginTestCase):
|
||||
plugins = ('MyPlugin',)
|
||||
|
||||
def testSomething(self):
|
||||
# assertions and such go here
|
||||
|
||||
Your plugin test case should be named TestCase as you see above, though it
|
||||
doesn't necessarily have to be named that way (supybot-plugin-create puts that
|
||||
in place for you anyway). As you can see we elected to subclass PluginTestCase
|
||||
because this hypothetical plugin apparently doesn't do anything
|
||||
channel-specific.
|
||||
|
||||
As you probably noticed, the plugins attribute of the class is where the list
|
||||
of necessary plugins goes, and in this case just contains the plugin that we
|
||||
are testing. This will be the case for probably the majority of plugins. A lot
|
||||
of the time test writers will use a bot function that performs some function
|
||||
that they don't want to write code for and they will just use command nesting
|
||||
to feed the bot what they need by using that plugin's functionality. If you
|
||||
choose to do this, only do so with core bot plugins as this makes distribution
|
||||
of your plugin simpler. After all, we want people to be able to run your
|
||||
plugin tests without having to have all of your plugins!
|
||||
|
||||
One last thing to note before moving along is that each of the test methods
|
||||
should describe what they are testing. If you want to test that your plugin
|
||||
only responds to registered users, don't be afraid to name your test method
|
||||
testOnlyRespondingToRegisteredUsers or testNotRespondingToUnregisteredUsers.
|
||||
You may have noticed some rather long and seemingly unwieldy test method names
|
||||
in our code, but that's okay because they help us know exactly what's failing
|
||||
when we run our tests. With an ambiguously named test method we may have to
|
||||
crack open test.py after running the tests just to see what it is that failed.
|
||||
For this reason you should also test only one thing per test method. Don't
|
||||
write a test method named testFoobarAndBaz. Just write two test methods,
|
||||
testFoobar and testBaz. Also, it is important to note that test methods must
|
||||
begin with test and that any method within the class that does begin with test
|
||||
will be run as a test by the supybot-test program. If you want to write
|
||||
utility functions in your test class that's fine, but don't name them
|
||||
something that begins with test or they will be executed as tests.
|
||||
|
||||
Including Extra Setup
|
||||
|
||||
Some tests you write may require a little bit of setup. For the most part it's
|
||||
okay just to include that in the individual test method itself, but if you're
|
||||
duplicating a lot of setup code across all or most of your test methods it's
|
||||
best to use the setUp method to perform whatever needs to be done prior to
|
||||
each test method.
|
||||
|
||||
The setUp method is inherited from the whichever plugin test case class you
|
||||
chose for your tests, and you can add whatever functionality you want to it.
|
||||
Note the important distinction, however: you should be adding to it and not
|
||||
overriding it. Just define setUp in your own plugin test case class and it
|
||||
will be run before all the test methods are invoked.
|
||||
|
||||
Let's do a quick example of one. Let's write a setUp method which registers a
|
||||
test user for our test bot:
|
||||
|
||||
def setUp(self):
|
||||
ChannelPluginTestCase.setUp(self) # important!!
|
||||
# Create a valid user to use
|
||||
self.prefix = 'foo!bar@baz'
|
||||
self.feedMsg('register tester moo', to=self.nick, frm=self.prefix))
|
||||
m = self.getMsg() # Response to registration.
|
||||
|
||||
Now notice how the first line calls the parent class's setUp method first?
|
||||
This must be done first. Otherwise several problems are likely to arise. For
|
||||
one, you wouldn't have an irc object at self.irc that we use later on nor
|
||||
would self.nick be set.
|
||||
|
||||
As for the rest of the method, you'll notice a few things that are available
|
||||
to the plugin test author. self.prefix refers to the hostmask of the
|
||||
hypothetical test user which will be "talking" to the bot, issuing commands.
|
||||
We set it to some generically fake hostmask, and then we use feedMsg to send
|
||||
a private message (using the bot's nick, accessible via self.nick) to the bot
|
||||
registering the username "tester" with the password "moo". We have to do it
|
||||
this way (rather than what you'll find out is the standard way of issuing
|
||||
commands to the bot in test cases a little later) because registration must be
|
||||
done in private. And lastly, since feedMsg doesn't dequeue any messages from
|
||||
the bot after being fed a message, we perform a getMsg to get the response.
|
||||
You're not expected to know all this yet, but do take note of it since using
|
||||
these methods in test-writing is not uncommon. These utility methods as well as
|
||||
all of the available assertions are covered in the next section.
|
||||
|
||||
So, now in any of the test methods we write, we'll be able to count on the
|
||||
fact that there will be a registered user "tester" with a password of "moo",
|
||||
and since we changed our prefix by altering self.prefix and registered after
|
||||
doing so, we are now identified as this user for all messages we send unless
|
||||
we specify that they are coming from some other prefix.
|
||||
|
||||
The Opposite of Setting-up: Tearing Down
|
||||
|
||||
If you did some things in your setUp that you want to clean up after, then
|
||||
this code belongs in the tearDown method of your test case class. It's
|
||||
essentially the same as setUp except that you probably want to wait to invoke
|
||||
the parent class's tearDown until after you've done all of your tearing down.
|
||||
But do note that you do still have to invoke the parent class's tearDown
|
||||
method if you decide to add in your own tear-down stuff.
|
||||
|
||||
Setting Config Variables for Testing
|
||||
|
||||
Before we delve into all of the fun assertions we can use in our test methods
|
||||
it's worth noting that each plugin test case can set custom values for any
|
||||
Supybot config variable they want rather easily. Much like how we can simply
|
||||
list the plugins we want loaded for our tests in the plugins attribute of our
|
||||
test case class, we can set config variables by creating a mapping of
|
||||
variables to values with the config attribute.
|
||||
|
||||
So if, for example, we wanted to disable nested commands within our plugin
|
||||
testing for some reason, we could just do this:
|
||||
|
||||
class MyPluginTestCase(PluginTestCase):
|
||||
config = {'supybot.commands.nested': False}
|
||||
|
||||
def testThisThing(self):
|
||||
# stuff
|
||||
|
||||
And now you can be assured that supybot.commands.nested is going to be off for
|
||||
all of your test methods in this test case class.
|
||||
|
||||
Plugin Test Methods
|
||||
===================
|
||||
The full list of test methods and how to use them.
|
||||
|
||||
Introduction
|
||||
|
||||
You know how to make plugin test case classes and you know how to do just
|
||||
about everything with them except to actually test stuff. Well, listed below
|
||||
are all of the assertions used in tests. If you're unfamiliar with what an
|
||||
assertion is in code testing, it is basically a requirement of something that
|
||||
must be true in order for that test to pass. It's a necessary condition. If
|
||||
any assertion within a test method fails the entire test method fails and it
|
||||
goes on to the next one.
|
||||
|
||||
Assertions
|
||||
|
||||
All of these are methods of the plugin test classes themselves and hence are
|
||||
accessed by using self.assertWhatever in your test methods. These are sorted
|
||||
in order of relative usefulness.
|
||||
|
||||
* assertResponse(query, expectedResponse) - Feeds query to the bot as a
|
||||
message and checks to make sure the response is expectedResponse. The
|
||||
test fails if they do not match (note that prefixed nicks in the
|
||||
response do not need to be included in the expectedResponse).
|
||||
|
||||
* assertError(query) - Feeds query to the bot and expects an error in
|
||||
return. Fails if the bot doesn't return an error.
|
||||
|
||||
* assertNotError(query) - The opposite of assertError. It doesn't matter
|
||||
what the response to query is, as long as it isn't an error. If it is
|
||||
not an error, this test passes, otherwise it fails.
|
||||
|
||||
* assertRegexp(query, regexp, flags=re.I) - Feeds query to the bot and
|
||||
expects something matching the regexp (no m// required) in regexp with
|
||||
the supplied flags. Fails if the regexp does not match the bot's
|
||||
response.
|
||||
|
||||
* assertNotRegexp(query, regexp, flags=re.I) - The opposite of
|
||||
assertRegexp. Fails if the bot's output matches regexp with the
|
||||
supplied flags.
|
||||
|
||||
* assertHelp(query) - Expects query to return the help for that command.
|
||||
Fails if the command help is not triggered.
|
||||
|
||||
* assertAction(query, expectedResponse=None) - Feeds query to the bot and
|
||||
expects an action in response, specifically expectedResponse if it is
|
||||
supplied. Otherwise, the test passes for any action response.
|
||||
|
||||
* assertActionRegexp(query, regexp, flags=re.I) - Basically like
|
||||
assertRegexp but carries the extra requirement that the response must
|
||||
be an action or the test will fail.
|
||||
|
||||
Utilities
|
||||
|
||||
* feedMsg(query, to=None, frm=None) - Simply feeds query to whoever is
|
||||
specified in to or to the bot itself if no one is specified. Can also
|
||||
optionally specify the hostmask of the sender with the frm keyword.
|
||||
Does not actually perform any assertions.
|
||||
|
||||
* getMsg(query) - Feeds query to the bot and gets the response.
|
||||
|
||||
Other Tests
|
||||
===========
|
||||
If you had to write helper code for a plugin and want to test it, here's
|
||||
how.
|
||||
|
||||
Previously we've only discussed how to test stuff in the plugin that is
|
||||
intended for IRC. Well, we realize that some Supybot plugins will require
|
||||
utility code that doesn't necessarily require all of the overhead of setting
|
||||
up IRC stuff, and so we provide a more lightweight test case class,
|
||||
SupyTestCase, which is a very very light wrapper around unittest.TestCase
|
||||
(from the standard unittest module) that basically just provides a little
|
||||
extra logging. This test case class is what you should use for writing those
|
||||
test cases which test things that are independent of IRC.
|
||||
|
||||
For example, in the MoobotFactoids plugin there is a large chunk of utility
|
||||
code dedicating to parsing out random choices within a factoid using a class
|
||||
called OptionList. So, we wrote the OptionListTestCase as a SupyTestCase for
|
||||
the MoobotFactoids plugin. The setup for test methods is basically the same as
|
||||
before, only you don't have to define plugins since this is independent of
|
||||
IRC.
|
||||
|
||||
You still have the choice of using setUp and tearDown if you wish, since those
|
||||
are inherited from unittest.TestCase. But, the same rules about calling the
|
||||
setUp or tearDown method from the parent class still apply.
|
||||
|
||||
With all this in hand, now you can write great tests for your Supybot plugins!
|
||||
|
136
docs/CAPABILITIES.rst
Normal file
136
docs/CAPABILITIES.rst
Normal file
@ -0,0 +1,136 @@
|
||||
============
|
||||
Capabilities
|
||||
============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Ok, some explanation of the capabilities system is probably in order. With
|
||||
most IRC bots (including the ones I've written myself prior to this one) "what
|
||||
a user can do" is set in one of two ways. On the *really* simple bots, each
|
||||
user has a numeric "level" and commands check to see if a user has a "high
|
||||
enough level" to perform some operation. On bots that are slightly more
|
||||
complicated, users have a list of "flags" whose meanings are hardcoded, and the
|
||||
bot checks to see if a user possesses the necessary flag before performing some
|
||||
operation. Both methods, IMO, are rather arbitrary, and force the user and the
|
||||
programmer to be unduly confined to less expressive constructs.
|
||||
|
||||
This bot is different. Every user has a set of "capabilities" that is
|
||||
consulted every time they give the bot a command. Commands, rather than
|
||||
checking for a user level of 100, or checking if the user has an 'o' flag, are
|
||||
instead able to check if a user has the 'owner' capability. At this point such
|
||||
a difference might not seem revolutionary, but at least we can already tell
|
||||
that this method is self-documenting, and easier for users and developers to
|
||||
understand what's truly going on.
|
||||
|
||||
User Capabilities
|
||||
-----------------
|
||||
What the heck can these capabilities DO?
|
||||
|
||||
If that was all, well, the capability system would be *cool*, but not many
|
||||
people would say it was *awesome*. But it **is** awesome! Several things are
|
||||
happening behind the scenes that make it awesome, and these are things that
|
||||
couldn't happen if the bot was using numeric userlevels or single-character
|
||||
flags. First, whenever a user issues the bot a command, the command dispatcher
|
||||
checks to make sure the user doesn't have the "anticapability" for that
|
||||
command. An anticapability is a capability that, instead of saying "what a
|
||||
user can do", says what a user *cannot* do. It's formed rather simply by
|
||||
adding a dash ('-') to the beginning of a capability; 'rot13' is a capability,
|
||||
and '-rot13' is an anticapability.
|
||||
|
||||
Anyway, when a user issues the bot a command, perhaps 'calc' or 'help', the bot
|
||||
first checks to make sure the user doesn't have the '-calc' or the '-help'
|
||||
(anti)capabilities before even considering responding to the user. So commands
|
||||
can be turned on or off on a *per user* basis, offering fine-grained control
|
||||
not often (if at all!) seen in other bots. This can be further refined by
|
||||
limiting the (anti)capability to a command in a specific plugin or even an
|
||||
entire plugin. For example, the rot13 command is in the Filter plugin. If a
|
||||
user should be able to use another rot13 command, but not the one in the Format
|
||||
plugin, they would simply need to be given '-Format.rot13' anticapability.
|
||||
Similarly, if a user were to be banned from using the Filter plugin altogether,
|
||||
they would simply need to be given the '-Filter' anticapability.
|
||||
|
||||
Channel Capabilities
|
||||
--------------------
|
||||
What if #linux wants completely different capabilities from #windows?
|
||||
|
||||
But that's not all! The capabilities system also supports *channel*
|
||||
capabilities, which are capabilities that only apply to a specific channel;
|
||||
they're of the form '#channel,capability'. Whenever a user issues a command to
|
||||
the bot in a channel, the command dispatcher also checks to make sure the user
|
||||
doesn't have the anticapability for that command *in that channel*, and if the
|
||||
user does, the bot won't respond to the user in the channel. Thus now, in
|
||||
addition to having the ability to turn individual commands on or off for an
|
||||
individual user, we can now turn commands on or off for an individual user on
|
||||
an individual channel!
|
||||
|
||||
So when a user 'foo' sends a command 'bar' to the bot on channel '#baz', first
|
||||
the bot checks to see if the user has the anticapability for the command by
|
||||
itself, '-bar'. If so, it errors right then and there, telling the user that
|
||||
he lacks the 'bar' capability. If the user doesn't have that anticapability,
|
||||
then the bot checks to see if the user issued the command over a channel, and
|
||||
if so, checks to see if the user has the antichannelcapability for that
|
||||
command, '#baz,-bar'. If so, again, he tells the user that he lacks the 'bar'
|
||||
capability. If neither of these anticapabilities are present, then the bot
|
||||
just responds to the user like normal.
|
||||
|
||||
Default Capabilities
|
||||
--------------------
|
||||
So what capabilities am I dealing with already?
|
||||
|
||||
There are several default capabilities the bot uses. The most important of
|
||||
these is the 'owner' capability. This capability allows the person having it
|
||||
to use *any* command. It's best to keep this capability reserved to people who
|
||||
actually have access to the shell the bot is running on. It's so important, in
|
||||
fact, that the bot will not allow you to add it with a command--you'll have you
|
||||
edit the users file directly to give it to someone.
|
||||
|
||||
There is also the 'admin' capability for non-owners that are highly trusted to
|
||||
administer the bot appropriately. They can do things such as change the bot's
|
||||
nick, cause the bot to ignore a given user, make the bot join or part channels,
|
||||
etc. They generally cannot do administration related to channels, which is
|
||||
reserved for people with the next capability.
|
||||
|
||||
People who are to administer channels with the bot should have the
|
||||
'#channel,op' capability--whatever channel they are to administrate, they
|
||||
should have that channel capability for 'op'. For example, since I want
|
||||
inkedmn to be an administrator in #supybot, I'll give him the '#supybot,op'
|
||||
capability. This is in addition to his 'admin' capability, since the 'admin'
|
||||
capability doesn't give the person having it control over channels.
|
||||
'#channel,op' is used for such things as giving/receiving ops, kickbanning
|
||||
people, lobotomizing the bot, ignoring users in the channel, and managing the
|
||||
channel capabilities. The '#channel,op' capability is also basically the
|
||||
equivalent of the 'owner' capability for capabilities involving
|
||||
#channel--basically anyone with the #channel,op capability is considered to
|
||||
have all positive capabilities and no negative capabilities for #channel.
|
||||
|
||||
One other globally important capability exists: 'trusted'. This is a command
|
||||
that basically says "This user can be trusted not to try and crash the bot." It
|
||||
allows users to call commands like 'icalc' in the 'Math' plugin, which can
|
||||
cause the bot to begin a calculation that could potentially never return (a
|
||||
calculation like '10**10**10**10'). Another command that requires the 'trusted'
|
||||
capability is the 're' command in the 'Utilities' plugin, which (due to the
|
||||
regular expression implementation in Python (and any other language that uses
|
||||
NFA regular expressions, like Perl or Ruby or Lua or ...) which can allow a
|
||||
regular expression to take exponential time to process). Consider what would
|
||||
happen if someone gave the bot the command 're [format join "" s/./ [dict go]
|
||||
/] [dict go]' It would basically replace every character in the output of
|
||||
'dict go' (14,896 characters!) with the entire output of 'dict go', resulting
|
||||
in 221MB of memory allocated! And that's not even the worst example!
|
||||
|
||||
Final Word
|
||||
----------
|
||||
|
||||
From a programmer's perspective, capabilties are flexible and easy to use. Any
|
||||
command can check if a user has any capability, even ones not thought of when
|
||||
the bot was originally written. Plugins can easily add their own
|
||||
capabilities--it's as easy as just checking for a capability and documenting
|
||||
somewhere that a user needs that capability to do something.
|
||||
|
||||
From an user's perspective, capabilities remove a lot of the mystery and
|
||||
esotery of bot control, in addition to giving a bot owner absolutely
|
||||
finegrained control over what users are allowed to do with the bot.
|
||||
Additionally, defaults can be set by the bot owner for both individual channels
|
||||
and for the bot as a whole, letting an end-user set the policy he wants the bot
|
||||
to follow for users that haven't yet registered in his user database. It's
|
||||
really a revolution!
|
195
docs/CONFIGURATION.rst
Normal file
195
docs/CONFIGURATION.rst
Normal file
@ -0,0 +1,195 @@
|
||||
=============
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
So you've got your Supybot up and running and there are some things you
|
||||
don't like about it. Fortunately for you, chances are that these things
|
||||
are configurable, and this document is here to tell you how to configure
|
||||
them.
|
||||
|
||||
Configuration of Supybot is handled via the `Config` plugin, which
|
||||
controls runtime access to Supybot's registry (the configuration file
|
||||
generated by the 'supybot-wizard' program you ran). The `Config` plugin
|
||||
provides a way to get or set variables, to list the available variables,
|
||||
and even to get help for certain variables. Take a moment now to read
|
||||
the help for each of those commands: ``config``, ``list``, and ``help``.
|
||||
If you don't know how to get help on those commands, take a look at the
|
||||
GETTING_STARTED document.
|
||||
|
||||
Configuration Registry
|
||||
----------------------
|
||||
Now, if you're used to the Windows registry, don't worry, Supybot's
|
||||
registry is completely different. For one, it's completely plain text.
|
||||
There's no binary database sensitive to corruption, it's not necessary
|
||||
to use another program to edit it--all you need is a simple text editor.
|
||||
But there is at least one good idea in Windows' registry: hierarchical
|
||||
configuration.
|
||||
|
||||
Supybot's configuration variables are organized in a hierarchy:
|
||||
variables having to do with the way Supybot makes replies all start with
|
||||
`supybot.reply`; variables having to do with the way a plugin works all
|
||||
start with `supybot.plugins.Plugin` (where 'Plugin' is the name of the
|
||||
plugin in question). This hierarchy is nice because it means the user
|
||||
isn't inundated with hundreds of unrelated and unsorted configuration
|
||||
variables.
|
||||
|
||||
Some of the more important configuration values are located directly
|
||||
under the base group, `supybot`. Things like the bot's nick, its ident,
|
||||
etc. Along with these config values are a few subgroups that contain
|
||||
other values. Some of the more prominent subgroups are: `plugins`
|
||||
(where all the plugin-specific configuration is held), `reply` (where
|
||||
variables affecting the way a Supybot makes its replies resides),
|
||||
`replies` (where all the specific standard replies are kept), and
|
||||
`directories` (where all the directories a Supybot uses are defined).
|
||||
There are other subgroups as well, but these are the ones we'll use in
|
||||
our example.
|
||||
|
||||
Configuration Groups
|
||||
--------------------
|
||||
Using the `Config` plugin, you can list values in a subgroup and get or
|
||||
set any of the values anywhere in the configuration hierarchy. For
|
||||
example, let's say you wanted to see what configuration values were
|
||||
under the `supybot` (the base group) hierarchy. You would simply issue
|
||||
this command::
|
||||
|
||||
<jemfinch|lambda> @config list supybot
|
||||
<supybot> jemfinch|lambda: @abuse, @capabilities, @commands,
|
||||
@databases, @debug, @directories, @drivers, @log, @networks,
|
||||
@nick, @plugins, @protocols, @replies, @reply,
|
||||
alwaysJoinOnInvite, channels, defaultIgnore,
|
||||
defaultSocketTimeout, externalIP, flush,
|
||||
followIdentificationThroughNickChanges, ident, pidFile,
|
||||
snarfThrottle, upkeepInterval, and user
|
||||
|
||||
These are all the configuration groups and values which are under the
|
||||
base `supybot` group. Actually, their full names would each have a
|
||||
'supybot.' prepended to them, but it is omitted in the listing in order
|
||||
to shorten the output. The first entries in the output are the groups
|
||||
(distinguished by the '@' symbol in front of them), and the rest are the
|
||||
configuration values. The '@' symbol (like the '#' symbol we'll discuss
|
||||
later) is simply a visual cue and is not actually part of the name.
|
||||
|
||||
Configuration Values
|
||||
--------------------
|
||||
Okay, now that you've used the Config plugin to list configuration
|
||||
variables, it's time that we start looking at individual variables and
|
||||
their values.
|
||||
|
||||
The first (and perhaps most important) thing you should know about each
|
||||
configuration variable is that they all have an associated help string
|
||||
to tell you what they represent. So the first command we'll cover is
|
||||
``config help``. To see the help string for any value or group, simply
|
||||
use the ``config help`` command. For example, to see what this
|
||||
`supybot.snarfThrottle` configuration variable is all about, we'd do
|
||||
this::
|
||||
|
||||
<jemfinch|lambda> @config help supybot.snarfThrottle
|
||||
<supybot> jemfinch|lambda: A floating point number of seconds to
|
||||
throttle snarfed URLs, in order to prevent loops between two
|
||||
bots snarfing the same URLs and having the snarfed URL in
|
||||
the output of the snarf message. (Current value: 10.0)
|
||||
|
||||
Pretty simple, eh?
|
||||
|
||||
Now if you're curious what the current value of a configuration variable
|
||||
is, you'll use the ``config`` command with one argument, the name of the
|
||||
variable you want to see the value of::
|
||||
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: '@'
|
||||
|
||||
To set this value, just stick an extra argument after the name::
|
||||
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars @$
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
|
||||
Now check this out::
|
||||
|
||||
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: '@$'
|
||||
|
||||
Note that we used '$' as our prefix character, and that the value of the
|
||||
configuration variable changed. If I were to use the ``flush`` command
|
||||
now, this change would be flushed to the registry file on disk (this
|
||||
would also happen if I made the bot quit, or pressed Ctrl-C in the
|
||||
terminal which the bot was running). Instead, I'll revert the change::
|
||||
|
||||
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars @
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> $note that this makes no response.
|
||||
|
||||
Default Values
|
||||
--------------
|
||||
If you're ever curious what the default for a given configuration
|
||||
variable is, use the ``config default`` command::
|
||||
|
||||
<jemfinch|lambda> @config default supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: ''
|
||||
|
||||
Thus, to reset a configuration variable to its default value, you can
|
||||
simply say::
|
||||
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars [config
|
||||
default supybot.reply.whenAddressedBy.chars]
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> @note that this does nothing
|
||||
|
||||
Simple, eh?
|
||||
|
||||
Searching the Registry
|
||||
----------------------
|
||||
Now, let's say you want to find all configuration variables that might
|
||||
be even remotely related to opping. For that, you'll want the ``config
|
||||
search`` command. Check this out::
|
||||
|
||||
<jemfinch|lamda> @config search op
|
||||
<supybot> jemfinch|lambda: supybot.plugins.Enforcer.autoOp,
|
||||
supybot.plugins.Enforcer.autoHalfop,
|
||||
supybot.plugins.Enforcer.takeRevenge.onOps,
|
||||
supybot.plugins.Enforcer.cycleToGetOps,
|
||||
supybot.plugins.Topic, supybot.plugins.Topic.public,
|
||||
supybot.plugins.Topic.separator,
|
||||
supybot.plugins.Topic.format,
|
||||
supybot.plugins.Topic.recognizeTopiclen,
|
||||
supybot.plugins.Topic.default,
|
||||
supybot.plugins.Topic.undo.max,
|
||||
supybot.plugins.Relay.topicSync
|
||||
|
||||
Sure, it showed all the topic-related stuff in there, but it also showed
|
||||
you all the op-related stuff, too. Do note, however, that you can only
|
||||
see configuration variables for plugins that are currently loaded or
|
||||
that you loaded in the past; if you've never loaded a plugin there's no
|
||||
way for the bot to know what configuration variables it registers.
|
||||
|
||||
Channel-Specific Configuration
|
||||
------------------------------
|
||||
Many configuration variables can be specific to individual channels.
|
||||
The `Config` plugin provides an easy way to configure something for a
|
||||
specific channel; for instance, in order to set the prefix chars for a
|
||||
specific channel, do this in that channel::
|
||||
|
||||
<jemfinch|lambda> @config channel supybot.reply.whenAddressedBy.chars !
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
|
||||
That'll set the prefix chars in the channel from which the message was
|
||||
sent to '!'. Voila, channel-specific values! Also, note that when
|
||||
using the `Config` plugin's ``list`` command, channel-specific values are
|
||||
preceeded by a '#' character to indicate such (similar to how '@' is
|
||||
used to indicate a group of values).
|
||||
|
||||
Editing the Configuration Values by Hand
|
||||
----------------------------------------
|
||||
Some people might like editing their registry file directly rather than
|
||||
manipulating all these things through the bot. For those people, we
|
||||
offer the ``config reload`` command, which reloads both registry
|
||||
configuration and user/channel/ignore database configuration.
|
||||
|
||||
Just edit the interesting files and then give the bot the ``config
|
||||
reload`` command and it'll work as expected. Do note, however, that
|
||||
Supybot flushes his configuration files and database to disk every hour
|
||||
or so, and if this happens after you've edited your configuration files
|
||||
but before you reload your changes, you could lose the changes you made.
|
||||
To prevent this, set the `supybot.flush` value to 'Off' while editing
|
||||
the files, and no automatic flushing will occur.
|
201
docs/FAQ.rst
Normal file
201
docs/FAQ.rst
Normal file
@ -0,0 +1,201 @@
|
||||
==========================
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
How do I make my Supybot connect to multiple servers?
|
||||
|
||||
Just use the `connect` command in the `Network` plugin.
|
||||
|
||||
Why does my bot not recognize me or tell me that I don't have the
|
||||
'owner' capability?
|
||||
|
||||
Because you've not given it anything to recognize you from!
|
||||
|
||||
You'll need to identify with the bot (``help identify`` to see how
|
||||
that works) or add your hostmask to your user record (``help hostmask
|
||||
add`` to see how that works) for it to know that you're you.
|
||||
|
||||
You may wish to note that addhostmask can accept a password; rather
|
||||
than identify, you can send the command::
|
||||
|
||||
hostmask add myOwnerUser [hostmask] myOwnerUserPassword
|
||||
|
||||
and the bot will add your current hostmask to your owner user (of
|
||||
course, you should change myOwnerUser and myOwnerUserPassword
|
||||
appropriately for your bot).
|
||||
|
||||
What is a hostmask?
|
||||
|
||||
Each user on IRC is uniquely identified by a string which we call a
|
||||
`hostmask`. The IRC RFC refers to it as a prefix. Either way, it
|
||||
consists of a nick, a user, and a host, in the form
|
||||
``nick!user@host``. If your Supybot complains that something you've
|
||||
given to it isn't a hostmask, make sure that you have those three
|
||||
components and that they're joined in the appropriate manner.
|
||||
|
||||
My bot can't handle nicks with brackets in them!
|
||||
|
||||
It always complains about something not being a valid command, or
|
||||
about spurious or missing right brackets, etc.
|
||||
|
||||
You should quote arguments (using double quotes, like this:
|
||||
``"foo[bar]"``) that have brackets in them that you don't wish to be
|
||||
evaluated as nested commands. Otherwise, you can turn off nested
|
||||
commands by setting `supybot.commands.nested` to False, or change the
|
||||
brackets that nest commands, by setting
|
||||
`supybot.commands.nested.brackets` to some other value (like ``<>``,
|
||||
which can't occur in IRC nicks).
|
||||
|
||||
I added an alias, but it doesn't work!
|
||||
|
||||
Take a look at ``help <alias you added>``. If the alias the bot has
|
||||
listed doesn't match what you're giving it, chances are you need to
|
||||
quote your alias in order for the brackets not to be evaluated. For
|
||||
instance, if you're adding an alias to give you a link to your
|
||||
homepage, you need to say::
|
||||
|
||||
alias add mylink "format concat http://my.host.com/ [urlquote $1]"
|
||||
|
||||
and not::
|
||||
|
||||
alias add mylink format concat http://my.host.com/ [urlquote $1]
|
||||
|
||||
The first version works; the second version will always return the
|
||||
same url.
|
||||
|
||||
What does 'lobotomized' mean?
|
||||
|
||||
I see this word in commands and in my `channels.conf`, but I don't
|
||||
know what it means. What does Supybot mean when it says "lobotomized"?
|
||||
|
||||
A lobotomy is an operation that removes the frontal lobe of the brain,
|
||||
the part that does most of a person's thinking. To "lobotomize" a bot
|
||||
is to tell it to stop thinking--thus, a lobotomized bot will not
|
||||
respond to anything said by anyone other than its owner in whichever
|
||||
channels it is lobotomized.
|
||||
|
||||
The term is certainly suboptimal, but remains in use because it was
|
||||
historically used by certain other IRC bots, and we wanted to ease the
|
||||
transition to Supybot from those bots by reusing as much terminology
|
||||
as possible.
|
||||
|
||||
Is there a way to load all the plugins Supybot has?
|
||||
|
||||
No, there isn't. Even if there were, some plugins conflict with other
|
||||
plugins, so it wouldn't make much sense to load them. For instance,
|
||||
what would a bot do with `Factoids`, `MoobotFactoids`, and `Infobot`
|
||||
all loaded? Probably just annoy people :)
|
||||
|
||||
If you want to know more about the plugins that are available, check
|
||||
out our `plugin index`_ at our `website`_.
|
||||
|
||||
Is there a command that can tell me what capability another command
|
||||
requires?
|
||||
|
||||
No, there isn't, and there probably never will be.
|
||||
|
||||
Commands have the flexibility to check any capabilities they wish to
|
||||
check; while this flexibility is useful, it also makes it hard to
|
||||
guess what capability a certain command requires. We could make a
|
||||
solution that would work in a large majority of cases, but it wouldn't
|
||||
(and couldn't!) be absolutely correct in all circumstances, and since
|
||||
we're anal and we hate doing things halfway, we probably won't ever
|
||||
add this partial solution.
|
||||
|
||||
Why doesn't `Karma` seem to work for me?
|
||||
|
||||
`Karma`, by default, doesn't acknowledge karma updates. If you check
|
||||
the karma of whatever you increased/decreased, you'll note that your
|
||||
increment or decrement still took place. If you'd rather `Karma`
|
||||
acknowledge karma updates, change the `supybot.plugins.Karma.response`
|
||||
configuration variable to "On".
|
||||
|
||||
Why won't Supybot respond to private messages?
|
||||
|
||||
The most likely cause is that you are running your bot on the Freenode
|
||||
network. Around Sept. 2005, Freenode added a user mode which
|
||||
registered user could set that `blocks`_ private messages from
|
||||
unregistered users. So, the reason you aren't seeing a response from
|
||||
your Supybot is:
|
||||
|
||||
* Your Supybot is not registered with NickServ, you are registered,
|
||||
and you have set the +E user mode for yourself.
|
||||
|
||||
* or you have registered your Supybot with NickServ, you aren't
|
||||
registered, and your Supybot has the +E user mode set.
|
||||
|
||||
Can users with the "admin" capability change configuration?
|
||||
|
||||
Currently, no. Feel free to make your case to us as to why a certain
|
||||
configuration variable should only require the `admin` capability
|
||||
instead of the `owner` capability, and if we agree with you, we'll
|
||||
change it for the next release.
|
||||
|
||||
How can I make my Supybot log my IRC channel?
|
||||
|
||||
To log all the channels your Supybot is in, simply load the
|
||||
`ChannelLogger` plugin, which is included in the main distribution.
|
||||
|
||||
How do I find out channel modes?
|
||||
|
||||
I want to know who's an op in a certain channel, or who's voiced, or
|
||||
what the modes on the channel are. How do I do that?
|
||||
|
||||
Everything you need is kept in a `ChannelState` object in an
|
||||
`IrcState` object in the `Irc` object your plugin is given. To see
|
||||
the ops in a given channel, for instance, you would do this::
|
||||
|
||||
irc.state.channels['#channel'].ops
|
||||
|
||||
To see a dictionary mapping mode chars to values (if any), you would
|
||||
do this::
|
||||
|
||||
irc.state.channels['#channel'].modes
|
||||
|
||||
From there, things should be self-evident.
|
||||
|
||||
Can Supybot connect through a proxy server?
|
||||
|
||||
Supybot is not designed to be allowed to connect to an IRC server via
|
||||
a proxy server, however there are transparent proxy server helpers
|
||||
like tsocks_ that are designed to proxy-enable all network
|
||||
applications, and Supybot does work with these.
|
||||
|
||||
Why can't Supybot find the plugin I want to load?
|
||||
|
||||
Why does my bot say that 'No plugin "foo" exists.' when I try to load
|
||||
the foo plugin?
|
||||
|
||||
First, make sure you are typing the plugin name correctly. ``@load
|
||||
foo`` is not the same as ``@load Foo`` [#plugindir]_. If that is not
|
||||
the problem,
|
||||
|
||||
.. [#plugindir] Yes, it used to be the same, but then we moved to using
|
||||
directories for plugins instead of a single file. Apparently, that
|
||||
makes a difference to Python.
|
||||
|
||||
I've found a bug, what do I do?
|
||||
|
||||
Submit your bug on `Sourceforge`_ through our `project page`_.
|
||||
|
||||
Is Python installed?
|
||||
|
||||
I run Windows, and I'm not sure if Python is installed on my computer.
|
||||
How can I find out for sure?
|
||||
|
||||
Python isn't commonly installed by default on Windows computers. If
|
||||
you don't see it in your start menu somewhere, it's probably not
|
||||
installed.
|
||||
|
||||
The easiest way to find out if Python is installed is simply to
|
||||
`download it`_ and try to install it. If the installer complains, you
|
||||
probably already have it installed. If it doesn't, well, now you have
|
||||
Python installed.
|
||||
|
||||
.. _plugin index: http://supybot.com/plugins.html
|
||||
.. _website: http://supybot.com/
|
||||
.. _blocks: http://freenode.net/faq.shtml#blockingmessages
|
||||
.. _tsocks: http://tsocks.sourceforge.net
|
||||
.. _Sourceforge: http://sourceforge.net/
|
||||
.. _project page: http://sourceforge.net/projects/supybot
|
||||
.. _download it: http://python.org/download/
|
181
docs/GETTING_STARTED.rst
Normal file
181
docs/GETTING_STARTED.rst
Normal file
@ -0,0 +1,181 @@
|
||||
============================
|
||||
Getting Started with Supybot
|
||||
============================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Ok, so you've decided to try out Supybot. That's great! The more people who
|
||||
use Supybot, the more people can submit bugs and help us to make it the best
|
||||
IRC bot in the world :)
|
||||
|
||||
You should have already read through our install document (if you had to
|
||||
manually install) before reading any further. Now we'll give you a whirlwind
|
||||
tour as to how you can get Supybot setup and use Supybot effectively.
|
||||
|
||||
Initial Setup
|
||||
-------------
|
||||
|
||||
Now that you have Supybot installed, you'll want to get it running. The first
|
||||
thing you'll want to do is run supybot-wizard. Before running supybot-wizard,
|
||||
you should be in the directory in which you want your bot-related files to
|
||||
reside. The wizard will walk you through setting up a base config file for
|
||||
your Supybot. Once you've completed the wizard, you will have a config file
|
||||
called botname.conf. In order to get the bot running, run ``supybot
|
||||
botname.conf``.
|
||||
|
||||
Listing Commands
|
||||
----------------
|
||||
|
||||
Ok, so let's assume your bot connected to the server and joined the channels
|
||||
you told it to join. For now we'll assume you named your bot 'supybot' (you
|
||||
probably didn't, but it'll make it much clearer in the examples that follow to
|
||||
assume that you did). We'll also assume that you told it to join #channel (a
|
||||
nice generic name for a channel, isn't it? :)) So what do you do with this
|
||||
bot that you just made to join your channel? Try this in the channel::
|
||||
|
||||
supybot: list
|
||||
|
||||
Replacing 'supybot' with the actual name you picked for your bot, of course.
|
||||
Your bot should reply with a list of the plugins he currently has loaded. At
|
||||
least `Admin`, `Channel`, `Config`, `Misc`, `Owner`, and `User` should be
|
||||
there; if you used supybot-wizard to create your configuration file you may
|
||||
have many more plugins loaded. The list command can also be used to list the
|
||||
commands in a given plugin::
|
||||
|
||||
supybot: list Misc
|
||||
|
||||
will list all the commands in the `Misc` plugin. If you want to see the help
|
||||
for any command, just use the help command::
|
||||
|
||||
supybot: help help
|
||||
supybot: help list
|
||||
supybot: help load
|
||||
|
||||
Sometimes more than one plugin will have a given command; for instance, the
|
||||
"list" command exists in both the Misc and Config plugins (both loaded by
|
||||
default). List, in this case, defaults to the Misc plugin, but you may want
|
||||
to get the help for the list command in the Config plugin. In that case,
|
||||
you'll want to give your command like this::
|
||||
|
||||
supybot: help config list
|
||||
|
||||
Anytime your bot tells you that a given command is defined in several plugins,
|
||||
you'll want to use this syntax ("plugin command") to disambiguate which
|
||||
plugin's command you wish to call. For instance, if you wanted to call the
|
||||
Config plugin's list command, then you'd need to say::
|
||||
|
||||
supybot: config list
|
||||
|
||||
Rather than just 'list'.
|
||||
|
||||
Making Supybot Recognize You
|
||||
----------------------------
|
||||
|
||||
If you ran the wizard, then it is almost certainly the case that you already
|
||||
added an owner user for yourself. If not, however, you can add one via the
|
||||
handy-dandy 'supybot-adduser' script. You'll want to run it while the bot is
|
||||
not running (otherwise it could overwrite supybot-adduser's changes to your
|
||||
user database before you get a chance to reload them). Just follow the
|
||||
prompts, and when it asks if you want to give the user any capabilities, say
|
||||
yes and then give yourself the 'owner' capability, restart the bot and you'll
|
||||
be ready to load some plugins!
|
||||
|
||||
Now, in order for the bot to recognize you as your owner user, you'll have to
|
||||
identify with the bot. Open up a query window in your irc client ('/query'
|
||||
should do it; if not, just know that you can't identify in a channel because
|
||||
it requires sending your password to the bot). Then type this::
|
||||
|
||||
help identify
|
||||
|
||||
And follow the instructions; the command you send will probably look like
|
||||
this, with 'myowneruser' and 'myuserpassword' replaced::
|
||||
|
||||
identify myowneruser myuserpassword
|
||||
|
||||
The bot will tell you that 'The operation succeeded' if you got the right name
|
||||
and password. Now that you're identified, you can do anything that requires
|
||||
any privilege: that includes all the commands in the Owner and Admin plugins,
|
||||
which you may want to take a look at (using the list and help commands, of
|
||||
course). One command in particular that you might want to use (it's from the
|
||||
User plugin) is the 'hostmask add' command: it lets you add a hostmask to your
|
||||
user record so the bot recognizes you by your hostmask instead of requiring
|
||||
you always to identify with it before it recognizes you. Use the 'help'
|
||||
command to see how this command works. Here's how I often use it::
|
||||
|
||||
hostmask add myuser [hostmask] mypassword
|
||||
|
||||
You may not have seen that '[hostmask]' syntax before. Supybot allows nested
|
||||
commands, which means that any command's output can be nested as an argument
|
||||
to another command. The hostmask command from the Misc plugin returns the
|
||||
hostmask of a given nick, but if given no arguments, it returns the hostmask
|
||||
of the person giving the command. So the command above adds the hostmask I'm
|
||||
currently using to my user's list of recognized hostmasks. I'm only required
|
||||
to give mypassword if I'm not already identified with the bot.
|
||||
|
||||
Loading Plugins
|
||||
---------------
|
||||
|
||||
Let's take a look at loading other plugins. If you didn't use supybot-wizard,
|
||||
though, you might do well to try it before playing around with loading plugins
|
||||
yourself: each plugin has its own configure function that the wizard uses to
|
||||
setup the appropriate registry entries if the plugin requires any.
|
||||
|
||||
If you do want to play around with loading plugins, you're going to need to
|
||||
have the owner capability.
|
||||
|
||||
Remember earlier when I told you to try ``help load``? That's the very command
|
||||
you'll be using. Basically, if you want to load, say, the Games plugin, then
|
||||
``load Games``. Simple, right? If you need a list of the plugins you can load,
|
||||
you'll have to list the directory the plugins are in (using whatever command
|
||||
is appropriate for your operating system, either 'ls' or 'dir').
|
||||
|
||||
Getting More From Your Supybot
|
||||
------------------------------
|
||||
|
||||
Another command you might find yourself needing somewhat often is the 'more'
|
||||
command. The IRC protocol limits messages to 512 bytes, 60 or so of which
|
||||
must be devoted to some bookkeeping. Sometimes, however, Supybot wants to
|
||||
send a message that's longer than that. What it does, then, is break it into
|
||||
"chunks" and send the first one, following it with ``(X more messages)`` where
|
||||
X is how many more chunks there are. To get to these chunks, use the `more`
|
||||
command. One way to try is to look at the default value of
|
||||
`supybot.replies.genericNoCapability` -- it's so long that it'll stretch
|
||||
across two messages::
|
||||
|
||||
<jemfinch|lambda> $config default
|
||||
supybot.replies.genericNoCapability
|
||||
<lambdaman> jemfinch|lambda: You're missing some capability
|
||||
you need. This could be because you actually
|
||||
possess the anti-capability for the capability
|
||||
that's required of you, or because the channel
|
||||
provides that anti-capability by default, or
|
||||
because the global capabilities include that
|
||||
anti-capability. Or, it could be because the
|
||||
channel or the global defaultAllow is set to
|
||||
False, meaning (1 more message)
|
||||
<jemfinch|lambda> $more
|
||||
<lambdaman> jemfinch|lambda: that no commands are allowed
|
||||
unless explicitly in your capabilities. Either
|
||||
way, you can't do what you want to do.
|
||||
|
||||
So basically, the bot keeps, for each person it sees, a list of "chunks" which
|
||||
are "released" one at a time by the `more` command. In fact, you can even get
|
||||
the more chunks for another user: if you want to see another chunk in the last
|
||||
command jemfinch gave, for instance, you would just say `more jemfinch` after
|
||||
which, his "chunks" now belong to you. So, you would just need to say `more`
|
||||
to continue seeing chunks from jemfinch's initial command.
|
||||
|
||||
Final Word
|
||||
----------
|
||||
|
||||
You should now have a solid foundation for using Supybot. You can use the
|
||||
`list` command to see what plugins your bot has loaded and what commands are
|
||||
in those plugins; you can use the 'help' command to see how to use a specific
|
||||
command, and you can use the 'more' command to continue a long response from
|
||||
the bot. With these three commands, you should have a strong basis with which
|
||||
to discover the rest of the features of Supybot!
|
||||
|
||||
Do be sure to read our other documentation and make use of the resources we
|
||||
provide for assistance; this website and, of course, #supybot on
|
||||
irc.freenode.net if you run into any trouble!
|
89
docs/Makefile
Normal file
89
docs/Makefile
Normal file
@ -0,0 +1,89 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Supybot.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Supybot.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
557
docs/PLUGIN_TUTORIAL.rst
Normal file
557
docs/PLUGIN_TUTORIAL.rst
Normal file
@ -0,0 +1,557 @@
|
||||
=================================
|
||||
Writing Your First Supybot Plugin
|
||||
=================================
|
||||
|
||||
Introduction
|
||||
============
|
||||
Ok, so you want to write a plugin for Supybot. Good, then this is the place to
|
||||
be. We're going to start from the top (the highest level, where Supybot code
|
||||
does the most work for you) and move lower after that.
|
||||
|
||||
So have you used Supybot? If not, you need to go use it. This will help you
|
||||
understand crucial things like the way the various commands work and it is
|
||||
essential prior to embarking upon the plugin-development excursion detailed in
|
||||
the following pages. If you haven't used Supybot, come back to this document
|
||||
after you've used it for a while and gotten a feel for it.
|
||||
|
||||
So, now that we know you've used Supybot, we'll start getting into details.
|
||||
We'll go through this tutorial by actually writing a new plugin, named Random
|
||||
with just a few simple commands.
|
||||
|
||||
Caveat: you'll need to have Supybot installed on the machine you
|
||||
intend to develop plugins on. This will not only allow you to test
|
||||
the plugins with a live bot, but it will also provide you with
|
||||
several nice scripts which aid the development of plugins. Most
|
||||
notably, it provides you with the supybot-plugin-create script which
|
||||
we will use in the next section... Creating a minimal plugin This
|
||||
section describes using the 'supybot-plugin-create' script to create
|
||||
a minimal plugin which we will enhance in later sections.
|
||||
|
||||
The recommended way to start writing a plugin is to use the wizard provided,
|
||||
:command:`supybot-plugin-create`. Run this from within your local plugins
|
||||
directory, so we will be able to load the plugin and test it out.
|
||||
|
||||
It's very easy to follow, because basically all you have to do is answer three
|
||||
questions. Here's an example session::
|
||||
|
||||
[ddipaolo@quinn ../python/supybot]% supybot-plugin-create
|
||||
What should the name of the plugin be? Random
|
||||
|
||||
Sometimes you'll want a callback to be threaded. If its methods
|
||||
(command or regexp-based, either one) will take a significant amount
|
||||
of time to run, you'll want to thread them so they don't block the
|
||||
entire bot.
|
||||
|
||||
Does your plugin need to be threaded? [y/n] n
|
||||
|
||||
What is your real name, so I can fill in the copyright and license
|
||||
appropriately? Daniel DiPaolo
|
||||
|
||||
Your new plugin template is in the Random directory.
|
||||
|
||||
It's that simple! Well, that part of making the minimal plugin is that simple.
|
||||
You should now have a directory with a few files in it, so let's take a look at
|
||||
each of those files and see what they're used for.
|
||||
|
||||
README.txt
|
||||
==========
|
||||
In :file:`README.txt` you put exactly what the boilerplate text says to put in
|
||||
there:
|
||||
|
||||
Insert a description of your plugin here, with any notes, etc. about
|
||||
using it.
|
||||
|
||||
A brief overview of exactly what the purpose of the plugin is supposed to do is
|
||||
really all that is needed here. Also, if this plugin requires any third-party
|
||||
Python modules, you should definitely mention those here. You don't have to
|
||||
describe individual commands or anything like that, as those are defined within
|
||||
the plugin code itself as you'll see later. You also don't need to acknowledge
|
||||
any of the developers of the plugin as those too are handled elsewhere.
|
||||
|
||||
For our Random plugin, let's make :file:`README.txt` say this:
|
||||
|
||||
This plugin contains commands relating to random numbers, and
|
||||
includes: a simple random number generator, the ability to pick a
|
||||
random number from within a range, a command for returning a random
|
||||
sampling from a list of items, and a simple dice roller.
|
||||
|
||||
And now you know what's in store for the rest of this tutorial, we'll be
|
||||
writing all of that in one Supybot plugin, and you'll be surprised at just how
|
||||
simple it is!
|
||||
|
||||
__init__.py
|
||||
===========
|
||||
The next file we'll look at is :file:`__init__.py`. If you're familiar with
|
||||
the Python import mechanism, you'll know what this file is for. If you're not,
|
||||
think of it as sort of the "glue" file that pulls all the files in this
|
||||
directory together when you load the plugin. It's also where there are a few
|
||||
administrative items live that you really need to maintain.
|
||||
|
||||
Let's go through the file. For the first 30 lines or so, you'll see the
|
||||
copyright notice that we use for our plugins, only with your name in place (as
|
||||
prompted in :command:`supybot-plugin-create`). Feel free to use whatever
|
||||
license you choose, we don't feel particularly attached to the boilerplate
|
||||
code so it's yours to license as you see fit even if you don't modify it. For
|
||||
our example, we'll leave it as is.
|
||||
|
||||
The plugin docstring immediately follows the copyright notice and it (like
|
||||
:file:`README.txt`) tells you precisely what it should contain:
|
||||
|
||||
Add a description of the plugin (to be presented to the user inside
|
||||
the wizard) here. This should describe *what* the plugin does.
|
||||
|
||||
The "wizard" that it speaks of is the :command:`supybot-wizard` script that is
|
||||
used to create working Supybot config file. I imagine that in meeting the
|
||||
prerequisite of "using a Supybot" first, most readers will have already
|
||||
encountered this script. Basically, if the user selects to look at this plugin
|
||||
from the list of plugins to load, it prints out that description to let the
|
||||
user know what it does, so make sure to be clear on what the purpose of the
|
||||
plugin is. This should be an abbreviated version of what we put in our
|
||||
:file:`README.txt`, so let's put this::
|
||||
|
||||
Provides a number of commands for selecting random things.
|
||||
|
||||
Next in :file:`__init__.py` you see a few imports which are necessary, and
|
||||
then four attributes that you need to modify for your bot and preferably keep
|
||||
up with as you develop it: ``__version__``, ``__author__``,
|
||||
``__contributors__``, ``__url__``.
|
||||
|
||||
``__version__`` is just a version string representing the current working
|
||||
version of the plugin, and can be anything you want. If you use some sort of
|
||||
RCS, this would be a good place to have it automatically increment the version
|
||||
string for any time you edit any of the files in this directory. We'll just
|
||||
make ours "0.1".
|
||||
|
||||
``__author__`` should be an instance of the :class:`supybot.Author` class. A
|
||||
:class:`supybot.Author` is simply created by giving it a full name, a short
|
||||
name (preferably IRC nick), and an e-mail address (all of these are optional,
|
||||
though at least the second one is expected). So, for example, to create my
|
||||
Author user (though I get to cheat and use supybot.authors.strike since I'm a
|
||||
main dev, muahaha), I would do::
|
||||
|
||||
__author__ = supybot.Author('Daniel DiPaolo', 'Strike',
|
||||
'somewhere@someplace.xxx')
|
||||
|
||||
Keep this in mind as we get to the next item...
|
||||
|
||||
``__contributors__`` is a dictionary mapping supybot.Author instances to lists
|
||||
of things they contributed. If someone adds a command named foo to your
|
||||
plugin, the list for that author should be ``["foo"]``, or perhaps even
|
||||
``["added foo command"]``. The main author shouldn't be referenced here, as it
|
||||
is assumed that everything that wasn't contributed by someone else was done by
|
||||
the main author. For now we have no contributors, so we'll leave it blank.
|
||||
|
||||
Lastly, the ``__url__`` attribute should just reference the download URL for
|
||||
the plugin. Since this is just an example, we'll leave this blank.
|
||||
|
||||
The rest of :file:`__init__.py` really shouldn't be touched unless you are
|
||||
using third-party modules in your plugin. If you are, then you need to take
|
||||
special note of the section that looks like this::
|
||||
|
||||
import config
|
||||
import plugin
|
||||
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!
|
||||
|
||||
As the comment says, this is one place where you need to make sure you import
|
||||
the third-party modules, and that you call :func:`reload` on them as well.
|
||||
That way, if we are reloading a plugin on a running bot it will actually
|
||||
reload the latest code. We aren't using any third-party modules, so we can
|
||||
just leave this bit alone.
|
||||
|
||||
We're almost through the "boring" part and into the guts of writing Supybot
|
||||
plugins, let's take a look at the next file.
|
||||
|
||||
config.py
|
||||
=========
|
||||
:file:`config.py` is, unsurprisingly, where all the configuration stuff
|
||||
related to your plugin goes. If you're not familiar with Supybot's
|
||||
configuration system, I recommend reading the config tutorial before going any
|
||||
further with this section.
|
||||
|
||||
So, let's plow through config.py line-by-line like we did the other files.
|
||||
|
||||
Once again, at the top is the standard copyright notice. Again, change it to
|
||||
how you see fit.
|
||||
|
||||
Then, some standard imports which are necessary.
|
||||
|
||||
Now, the first peculiar thing we get to is the configure function. This
|
||||
function is what is called by the supybot-wizard whenever a plugin is selected
|
||||
to be loaded. Since you've used the bot by now (as stated on the first page of
|
||||
this tutorial as a prerequisite), you've seen what this script does to
|
||||
configure plugins. The wizard allows the bot owner to choose something
|
||||
different from the default plugin config values without having to do it through
|
||||
the bot (which is still not difficult, but not as easy as this). Also, note
|
||||
that the advanced argument allows you to differentiate whether or not the
|
||||
person configuring this plugin considers himself an advanced Supybot user. Our
|
||||
plugin has no advanced features, so we won't be using it.
|
||||
|
||||
So, what exactly do we do in this configure function for our plugin? Well, for
|
||||
the most part we ask questions and we set configuration values. You'll notice
|
||||
the import line with supybot.questions in it. That provides some nice
|
||||
convenience functions which are used to (you guessed it) ask questions. The
|
||||
other line in there is the conf.registerPlugin line which registers our plugin
|
||||
with the config and allows us to create configuration values for the plugin.
|
||||
You should leave these two lines in even if you don't have anything else to put
|
||||
in here. For the vast majority of plugins, you can leave this part as is, so we
|
||||
won't go over how to write plugin configuration functions here (that will be
|
||||
handled in a separate article). Our plugin won't be using much configuration,
|
||||
so we'll leave this as is.
|
||||
|
||||
Next, you'll see a line that looks very similar to the one in the configure
|
||||
function. This line is used not only to register the plugin prior to being
|
||||
called in configure, but also to store a bit of an alias to the plugin's config
|
||||
group to make things shorter later on. So, this line should read::
|
||||
|
||||
Random = conf.registerPlugin('Random')
|
||||
|
||||
Now we get to the part where we define all the configuration groups and
|
||||
variables that our plugin is to have. Again, many plugins won't require any
|
||||
configuration so we won't go over it here, but in a separate article dedicated
|
||||
to sprucing up your config.py for more advanced plugins. Our plugin doesn't
|
||||
require any config variables, so we actually don't need to make any changes to
|
||||
this file at all.
|
||||
|
||||
Configuration of plugins is handled in depth at the Advanced Plugin Config
|
||||
Tutorial
|
||||
|
||||
plugin.py
|
||||
=========
|
||||
Here's the moment you've been waiting for, the overview of plugin.py and how to
|
||||
make our plugin actually do stuff.
|
||||
|
||||
At the top, same as always, is the standard copyright block to be used and
|
||||
abused at your leisure.
|
||||
|
||||
Next, some standard imports. Not all of them are used at the moment, but you
|
||||
probably will use many (if not most) of them, so just let them be. Since
|
||||
we'll be making use of Python's standard 'random' module, you'll need to add
|
||||
the following line to the list of imports::
|
||||
|
||||
import random
|
||||
|
||||
Now, the plugin class itself. What you're given is a skeleton: a simple
|
||||
subclass of callbacks.Plugin for you to start with. The only real content it
|
||||
has is the boilerplate docstring, which you should modify to reflect what the
|
||||
boilerplate text says - it should be useful so that when someone uses the
|
||||
plugin help command to determine how to use this plugin, they'll know what they
|
||||
need to do. Ours will read something like::
|
||||
|
||||
"""This plugin provides a few random number commands and some
|
||||
commands for getting random samples. Use the "seed" command to seed
|
||||
the plugin's random number generator if you like, though it is
|
||||
unnecessary as it gets seeded upon loading of the plugin. The
|
||||
"random" command is most likely what you're looking for, though
|
||||
there are a number of other useful commands in this plugin. Use
|
||||
'list random' to check them out. """
|
||||
|
||||
It's basically a "guide to getting started" for the plugin. Now, to make the
|
||||
plugin do something. First of all, to get any random numbers we're going to
|
||||
need a random number generator (RNG). Pretty much everything in our plugin is
|
||||
going to use it, so we'll define it in the constructor of our plugin, __init__.
|
||||
Here we'll also seed it with the current time (standard practice for RNGs).
|
||||
Here's what our __init__ looks like::
|
||||
|
||||
def __init__(self, irc):
|
||||
self.__parent = super(Random, self)
|
||||
self.__parent.__init__(irc)
|
||||
self.rng = random.Random() # create our rng
|
||||
self.rng.seed() # automatically seeds with current time
|
||||
|
||||
Now, the first two lines may look a little daunting, but it's just
|
||||
administrative stuff required if you want to use a custom __init__. If we
|
||||
didn't want to do so, we wouldn't have to, but it's not uncommon so I decided
|
||||
to use an example plugin that did. For the most part you can just copy/paste
|
||||
those lines into any plugin you override the __init__ for and just change them
|
||||
to use the plugin name that you are working on instead.
|
||||
|
||||
So, now we have a RNG in our plugin, let's write a command to get a random
|
||||
number. We'll start with a simple command named random that just returns a
|
||||
random number from our RNG and takes no arguments. Here's what that looks
|
||||
like::
|
||||
|
||||
def random(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Returns the next random number from the random number generator.
|
||||
"""
|
||||
irc.reply(str(self.rng.random()))
|
||||
random = wrap(random)
|
||||
|
||||
And that's it. Now here are the important points.
|
||||
|
||||
First and foremost, all plugin commands must have all-lowercase function
|
||||
names. If they aren't all lowercase they won't show up in a plugin's list of
|
||||
commands (nor will they be useable in general). If you look through a plugin
|
||||
and see a function that's not in all lowercase, it is not a plugin command.
|
||||
Chances are it is a helper function of some sort, and in fact using capital
|
||||
letters is a good way of assuring that you don't accidentally expose helper
|
||||
functions to users as commands.
|
||||
|
||||
You'll note the arguments to this class method are (self, irc, msg, args). This
|
||||
is what the argument list for all methods that are to be used as commands must
|
||||
start with. If you wanted additional arguments, you'd append them onto the end,
|
||||
but since we take no arguments we just stop there. I'll explain this in more
|
||||
detail with our next command, but it is very important that all plugin commands
|
||||
are class methods that start with those four arguments exactly as named.
|
||||
|
||||
Next, in the docstring there are two major components. First, the very first
|
||||
line dictates the argument list to be displayed when someone calls the help
|
||||
command for this command (i.e., help random). Then you leave a blank line and
|
||||
start the actual help string for the function. Don't worry about the fact that
|
||||
it's tabbed in or anything like that, as the help command normalizes it to
|
||||
make it look nice. This part should be fairly brief but sufficient to explain
|
||||
the function and what (if any) arguments it requires. Remember that this should
|
||||
fit in one IRC message which is typically around a 450 character limit.
|
||||
|
||||
Then we have the actual code body of the plugin, which consists of a single
|
||||
line: irc.reply(str(self.rng.random())). The irc.reply function issues a reply
|
||||
to wherever the PRIVMSG it received the command from with whatever text is
|
||||
provided. If you're not sure what I mean when I say "wherever the PRIVMSG it
|
||||
received the command from", basically it means: if the command is issued in a
|
||||
channel the response is sent in the channel, and if the command is issued in a
|
||||
private dialog the response is sent in a private dialog. The text we want to
|
||||
display is simply the next number from our RNG (self.rng). We get that number
|
||||
by calling the random function, and then we str it just to make sure it is a
|
||||
nice printable string.
|
||||
|
||||
Lastly, all plugin commands must be 'wrap'ed. What the wrap function does is
|
||||
handle argument parsing for plugin commands in a very nice and very powerful
|
||||
way. With no arguments, we simply need to just wrap it. For more in-depth
|
||||
information on using wrap check out the wrap tutorial (The astute Python
|
||||
programmer may note that this is very much like a decorator, and that's
|
||||
precisely what it is. However, we developed this before decorators existed and
|
||||
haven't changed the syntax due to our earlier requirement to stay compatible
|
||||
with Python 2.3. As we now require Python 2.4 or greater, this may eventually
|
||||
change to support work via decorators.)
|
||||
|
||||
Now let's create a command with some arguments and see how we use those in our
|
||||
plugin commands. Let's allow the user to seed our RNG with their own seed
|
||||
value. We'll call the command seed and take just the seed value as the argument
|
||||
(which we'll require be a floating point value of some sort, though technically
|
||||
it can be any hashable object). Here's what this command looks like::
|
||||
|
||||
def seed(self, irc, msg, args, seed):
|
||||
"""<seed>
|
||||
|
||||
Sets the internal RNG's seed value to <seed>. <seed> must be a
|
||||
floating point number.
|
||||
"""
|
||||
self.rng.seed(seed)
|
||||
irc.replySuccess()
|
||||
seed = wrap(seed, ['float'])
|
||||
|
||||
You'll notice first that argument list now includes an extra argument, seed. If
|
||||
you read the wrap tutorial mentioned above, you should understand how this arg
|
||||
list gets populated with values. Thanks to wrap we don't have to worry about
|
||||
type-checking or value-checking or anything like that. We just specify that it
|
||||
must be a float in the wrap portion and we can use it in the body of the
|
||||
function.
|
||||
|
||||
Of course, we modify the docstring to document this function. Note the syntax
|
||||
on the first line. Arguments go in <> and optional arguments should be
|
||||
surrounded by [] (we'll demonstrate this later as well).
|
||||
|
||||
The body of the function should be fairly straightforward to figure out, but it
|
||||
introduces a new function - irc.replySuccess. This is just a generic "I
|
||||
succeeded" command which responds with whatever the bot owner has configured to
|
||||
be the success response (configured in supybot.replies.success). Note that we
|
||||
don't do any error-checking in the plugin, and that's because we simply don't
|
||||
have to. We are guaranteed that seed will be a float and so the call to our
|
||||
RNG's seed is guaranteed to work.
|
||||
|
||||
Lastly, of course, the wrap call. Again, read the wrap tutorial for fuller
|
||||
coverage of its use, but the basic premise is that the second argument to wrap
|
||||
is a list of converters that handles argument validation and conversion and it
|
||||
then assigns values to each argument in the arg list after the first four
|
||||
(required) arguments. So, our seed argument gets a float, guaranteed.
|
||||
|
||||
With this alone you'd be able to make some pretty usable plugin commands, but
|
||||
we'll go through two more commands to introduce a few more useful ideas. The
|
||||
next command we'll make is a sample command which gets a random sample of items
|
||||
from a list provided by the user::
|
||||
|
||||
def sample(self, irc, msg, args, n, items):
|
||||
"""<number of items> <item1> [<item2> ...]
|
||||
|
||||
Returns a sample of the <number of items> taken from the remaining
|
||||
arguments. Obviously <number of items> must be less than the number
|
||||
of arguments given.
|
||||
"""
|
||||
if n > len(items):
|
||||
irc.error('<number of items> must be less than the number '
|
||||
'of arguments.')
|
||||
return
|
||||
sample = self.rng.sample(items, n)
|
||||
sample.sort()
|
||||
irc.reply(utils.str.commaAndify(sample))
|
||||
sample = wrap(sample, ['int', many('anything')])
|
||||
|
||||
This plugin command introduces a few new things, but the general structure
|
||||
should look fairly familiar by now. You may wonder why we only have two extra
|
||||
arguments when obviously this plugin can accept any number of arguments. Well,
|
||||
using wrap we collect all of the remaining arguments after the first one into
|
||||
the items argument. If you haven't caught on yet, wrap is really cool and
|
||||
extremely useful.
|
||||
|
||||
Next of course is the updated docstring. Note the use of [] to denote the
|
||||
optional items after the first item.
|
||||
|
||||
The body of the plugin should be relatively easy to read. First we check and
|
||||
make sure that n (the number of items the user wants to sample) is not larger
|
||||
than the actual number of items they gave. If it does, we call irc.error with
|
||||
the error message you see. irc.error is kind of like irc.replySuccess only it
|
||||
gives an error message using the configured error format (in
|
||||
supybot.replies.error). Otherwise, we use the sample function from our RNG to
|
||||
get a sample, then we sort it, and we reply with the 'utils.str.commaAndify'ed
|
||||
version. The utils.str.commaAndify function basically takes a list of strings
|
||||
and turns it into "item1, item2, item3, item4, and item5" for an arbitrary
|
||||
length. More details on using the utils module can be found in the utils
|
||||
tutorial.
|
||||
|
||||
Now for the last command that we will add to our plugin.py. This last command
|
||||
will allow the bot users to roll an arbitrary n-sided die, with as many sides
|
||||
as they so choose. Here's the code for this command::
|
||||
|
||||
def diceroll(self, irc, msg, args, n):
|
||||
"""[<number of sides>]
|
||||
|
||||
Rolls a die with <number of sides> sides. The default number of sides
|
||||
is 6.
|
||||
"""
|
||||
s = 'rolls a %s' % self.rng.randrange(1, n)
|
||||
irc.reply(s, action=True)
|
||||
diceroll = wrap(diceroll, [additional(('int', 'number of sides'), 6)])
|
||||
|
||||
The only new thing learned here really is that the irc.reply method accepts an
|
||||
optional argument action, which if set to True makes the reply an action
|
||||
instead. So instead of just crudely responding with the number, instead you
|
||||
should see something like * supybot rolls a 5. You'll also note that it uses a
|
||||
more advanced wrap line than we have used to this point, but to learn more
|
||||
about wrap, you should refer to the wrap tutorial
|
||||
|
||||
And now that we're done adding plugin commands you should see the boilerplate
|
||||
stuff at the bottom, which just consists of::
|
||||
|
||||
Class = Random
|
||||
|
||||
And also some vim modeline stuff. Leave these as is, and we're finally done
|
||||
with plugin.py!
|
||||
|
||||
test.py
|
||||
=======
|
||||
Now that we've gotten our plugin written, we want to make sure it works. Sure,
|
||||
an easy way to do a somewhat quick check is to start up a bot, load the plugin,
|
||||
and run a few commands on it. If all goes well there, everything's probably
|
||||
okay. But, we can do better than "probably okay". This is where written plugin
|
||||
tests come in. We can write tests that not only assure that the plugin loads
|
||||
and runs the commands fine, but also that it produces the expected output for
|
||||
given inputs. And not only that, we can use the nifty supybot-test script to
|
||||
test the plugin without even having to have a network connection to connect to
|
||||
IRC with and most certainly without running a local IRC server.
|
||||
|
||||
The boilerplate code for test.py is a good start. It imports everything you
|
||||
need and sets up RandomTestCase which will contain all of our tests. Now we
|
||||
just need to write some test methods. I'll be moving fairly quickly here just
|
||||
going over very basic concepts and glossing over details, but the full plugin
|
||||
test authoring tutorial has much more detail to it and is recommended reading
|
||||
after finishing this tutorial.
|
||||
|
||||
Since we have four commands we should have at least four test methods in our
|
||||
test case class. Typically you name the test methods that simply checks that a
|
||||
given command works by just appending the command name to test. So, we'll have
|
||||
testRandom, testSeed, testSample, and testDiceRoll. Any other methods you want
|
||||
to add are more free-form and should describe what you're testing (don't be
|
||||
afraid to use long names).
|
||||
|
||||
First we'll write the testRandom method::
|
||||
|
||||
def testRandom(self):
|
||||
# difficult to test, let's just make sure it works
|
||||
self.assertNotError('random')
|
||||
|
||||
Since we can't predict what the output of our random number generator is going
|
||||
to be, it's hard to specify a response we want. So instead, we just make sure
|
||||
we don't get an error by calling the random command, and that's about all we
|
||||
can do.
|
||||
|
||||
Next, testSeed. In this method we're just going to check that the command
|
||||
itself functions. In another test method later on we will check and make sure
|
||||
that the seed produces reproducible random numbers like we would hope it would,
|
||||
but for now we just test it like we did random in 'testRandom'::
|
||||
|
||||
def testSeed(self):
|
||||
# just make sure it works
|
||||
self.assertNotError('seed 20')
|
||||
|
||||
Now for testSample. Since this one takes more arguments it makes sense that we
|
||||
test more scenarios in this one. Also this time we have to make sure that we
|
||||
hit the error that we coded in there given the right conditions::
|
||||
|
||||
def testSample(self):
|
||||
self.assertError('sample 20 foo')
|
||||
self.assertResponse('sample 1 foo', 'foo')
|
||||
self.assertRegexp('sample 2 foo bar', '... and ...')
|
||||
self.assertRegexp('sample 3 foo bar baz', '..., ..., and ...')
|
||||
|
||||
So first we check and make sure trying to take a 20-element sample of a
|
||||
1-element list gives us an error. Next we just check and make sure we get the
|
||||
right number of elements and that they are formatted correctly when we give 1,
|
||||
2, or 3 element lists.
|
||||
|
||||
And for the last of our basic "check to see that it works" functions,
|
||||
testDiceRoll::
|
||||
|
||||
def testDiceRoll(self):
|
||||
self.assertActionRegexp('diceroll', 'rolls a \d')
|
||||
|
||||
We know that diceroll should return an action, and that with no arguments it
|
||||
should roll a single-digit number. And that's about all we can test reliably
|
||||
here, so that's all we do.
|
||||
|
||||
Lastly, we wanted to check and make sure that seeding the RNG with seed
|
||||
actually took effect like it's supposed to. So, we write another test method::
|
||||
|
||||
def testSeedActuallySeeds(self):
|
||||
# now to make sure things work repeatably
|
||||
self.assertNotError('seed 20')
|
||||
m1 = self.getMsg('random')
|
||||
self.assertNotError('seed 20')
|
||||
m2 = self.getMsg('random')
|
||||
self.failUnlessEqual(m1, m2)
|
||||
m3 = self.getMsg('random')
|
||||
self.failIfEqual(m2, m3)
|
||||
|
||||
So we seed the RNG with 20, store the message, and then seed it at 20 again. We
|
||||
grab that message, and unless they are the same number when we compare the two,
|
||||
we fail. And then just to make sure our RNG is producing random numbers, we get
|
||||
another random number and make sure it is distinct from the prior one.
|
||||
|
||||
Conclusion
|
||||
==========
|
||||
You are now very well-prepared to write Supybot plugins. Now for a few words of
|
||||
wisdom with regards to Supybot plugin-writing.
|
||||
|
||||
* Read other people's plugins, especially the included plugins and ones by
|
||||
the core developers. We (the Supybot dev team) can't possibly document
|
||||
all the awesome things that Supybot plugins can do, but we try.
|
||||
Nevertheless there are some really cool things that can be done that
|
||||
aren't very well-documented.
|
||||
|
||||
* Hack new functionality into existing plugins first if writing a new
|
||||
plugin is too daunting.
|
||||
|
||||
* Come ask us questions in #supybot on Freenode or OFTC. Going back to the
|
||||
first point above, the developers themselves can help you even more than
|
||||
the docs can (though we prefer you read the docs first).
|
||||
|
||||
* Share your plugins with the world and make Supybot all that more
|
||||
attractive for other users so they will want to write their plugins for
|
||||
Supybot as well.
|
||||
|
||||
* Read, read, read all the documentation.
|
||||
|
||||
* And of course, have fun writing your plugins.
|
213
docs/STYLE.rst
Normal file
213
docs/STYLE.rst
Normal file
@ -0,0 +1,213 @@
|
||||
================
|
||||
Style Guidelines
|
||||
================
|
||||
|
||||
**Note:** Code not following these style guidelines fastidiously is likely
|
||||
(*very* likely) not to be accepted into the Supybot core.
|
||||
|
||||
* Read :pep:`8` (Guido's Style Guide) and know that we use almost all the
|
||||
same style guidelines.
|
||||
|
||||
* Maximum line length is 79 characters. 78 is a safer bet, though.
|
||||
This is **NON-NEGOTIABLE**. Your code will not be accepted while you are
|
||||
violating this guidline.
|
||||
|
||||
* Identation is 4 spaces per level. No tabs. This also is
|
||||
**NON-NEGOTIABLE**. Your code, again, will *never* be accepted while you
|
||||
have literal tabs in it.
|
||||
|
||||
* Single quotes are used for all string literals that aren't docstrings.
|
||||
They're just easier to type.
|
||||
|
||||
* Triple double quotes (``"""``) are always used for docstrings.
|
||||
|
||||
* Raw strings (``r''`` or ``r""``) should be used for regular expressions.
|
||||
|
||||
* Spaces go around all operators (except around ``=`` in default arguments to
|
||||
functions) and after all commas (unless doing so keeps a line within the 79
|
||||
character limit).
|
||||
|
||||
* Functions calls should look like ``foo(bar(baz(x), y))``. They should
|
||||
not look like ``foo (bar (baz (x), y))``, or like ``foo(bar(baz(x), y) )``
|
||||
or like anything else. I hate extraneous spaces.
|
||||
|
||||
* Class names are StudlyCaps. Method and function names are camelCaps
|
||||
(StudlyCaps with an initial lowercase letter). If variable and attribute
|
||||
names can maintain readability without being camelCaps, then they should be
|
||||
entirely in lowercase, otherwise they should also use camelCaps. Plugin
|
||||
names are StudlyCaps.
|
||||
|
||||
* Imports should always happen at the top of the module, one import per line
|
||||
(so if imports need to be added or removed later, it can be done easily).
|
||||
|
||||
* Unless absolutely required by some external force, imports should be ordered
|
||||
by the string length of the module imported. I just think it looks
|
||||
prettier.
|
||||
|
||||
* A blank line should be between all consecutive method declarations in a
|
||||
class definition. Two blank lines should be between all consecutive class
|
||||
definitions in a file. Comments are even better than blank lines for
|
||||
separating classes.
|
||||
|
||||
* Database filenames should generally begin with the name of the plugin and
|
||||
the extension should be 'db'. plugins.DBHandler does this already.
|
||||
|
||||
* Whenever creating a file descriptor or socket, keep a reference around and
|
||||
be sure to close it. There should be no code like this::
|
||||
|
||||
s = urllib2.urlopen('url').read()
|
||||
|
||||
Instead, do this::
|
||||
|
||||
fd = urllib2.urlopen('url')
|
||||
try:
|
||||
s = fd.read()
|
||||
finally:
|
||||
fd.close()
|
||||
|
||||
This is to be sure the bot doesn't leak file descriptors.
|
||||
|
||||
* All plugin files should include a docstring decsribing what the plugin does.
|
||||
This docstring will be returned when the user is configuring the plugin.
|
||||
All plugin classes should also include a docstring describing how to do
|
||||
things with the plugin; this docstring will be returned when the user
|
||||
requests help on a plugin name.
|
||||
|
||||
* Method docstrings in classes deriving from callbacks.Privmsg should include
|
||||
an argument list as their first line, and after that a blank line followed
|
||||
by a longer description of what the command does. The argument list is used
|
||||
by the ``syntax`` command, and the longer description is used by the
|
||||
``help`` command.
|
||||
|
||||
* Whenever joining more than two strings, use string interpolation, not
|
||||
addition::
|
||||
|
||||
s = x + y + z # Bad.
|
||||
s = '%s%s%s' % (x, y, z) # Good.
|
||||
s = ''.join([x, y, z]) # Best, but not as general.
|
||||
|
||||
This has to do with efficiency; the intermediate string x+y is made (and
|
||||
thus copied) before x+y+z is made, so it's less efficient. People who use
|
||||
string concatenation in a for loop will be swiftly kicked in the head.
|
||||
|
||||
* When writing strings that have formatting characters in them, don't use
|
||||
anything but ``%s`` unless you absolutely must. In particular, ``%d`` should never
|
||||
be used, it's less general than ``%s`` and serves no useful purpose. If you got
|
||||
the ``%d`` wrong, you'll get an exception that says, "foo instance can't be
|
||||
converted to an integer." But if you use ``%s``, you'll get to see your nice
|
||||
little foo instance, if it doesn't convert to a string cleanly, and if it
|
||||
does convert cleanly, you'll get to see what you expect to see. Basically,
|
||||
``%d`` just sucks.
|
||||
|
||||
* As a corrolary to the above, note that sometimes ``%f`` is used, but on when
|
||||
floats need to be formatted, e.g., ``%.2f``.
|
||||
|
||||
* Use the log module to its fullest; when you need to print some values to
|
||||
debug, use self.log.debug to do so, and leave those statements in the code
|
||||
(commented out) so they can later be re-enabled. Remember that once code is
|
||||
buggy, it tends to have more bugs, and you'll probably need those print
|
||||
statements again.
|
||||
|
||||
* While on the topic of logs, note that we do not use % (i.e., str.__mod__)
|
||||
with logged strings; we simple pass the format parameters as additional
|
||||
arguments. The reason is simple: the logging module supports it, and it's
|
||||
cleaner (fewer tokens/glyphs) to read.
|
||||
|
||||
* While still on the topic of logs, it's also important to pick the
|
||||
appropriate log level for given information.
|
||||
|
||||
* DEBUG: Appropriate to tell a programmer *how* we're doing something
|
||||
(i.e., debugging printfs, basically). If you're trying to figure out why
|
||||
your code doesn't work, DEBUG is the new printf -- use that, and leave the
|
||||
statements in your code.
|
||||
|
||||
* INFO: Appropriate to tell a user *what* we're doing, when what we're
|
||||
doing isn't important for the user to pay attention to. A user who likes
|
||||
to keep up with things should enjoy watching our logging at the INFO
|
||||
level; it shouldn't be too low-level, but it should give enough
|
||||
information that it keeps him relatively interested at peak times.
|
||||
|
||||
* WARNING: Appropriate to tell a user when we're doing something that he
|
||||
really ought to pay attention to. Users should see WARNING and think,
|
||||
"Hmm, should I tell the Supybot developers about this?" Later, he should
|
||||
decide not to, but it should give the user a moment to pause and think
|
||||
about what's actually happening with his bot.
|
||||
|
||||
* ERROR: Appropriate to tell a user when something has gone wrong.
|
||||
Uncaught exceptions are ERRORs. Conditions that we absolutely want to
|
||||
hear about should be errors. Things that should *scare* the user should
|
||||
be errors.
|
||||
|
||||
* CRITICAL: Not really appropriate. I can think of no absolutely critical
|
||||
issue yet encountered in Supybot; the only possible thing I can imagine is
|
||||
to notify the user that the partition on which Supybot is running has
|
||||
filled up. That would be a CRITICAL condition, but it would also be hard
|
||||
to log :)
|
||||
|
||||
|
||||
* All plugins should have test cases written for them. Even if it doesn't
|
||||
actually test anything but just exists, it's good to have the test there so
|
||||
there's a place to add more tests later (and so we can be sure that all
|
||||
plugins are adequately documented; PluginTestCase checks that every command
|
||||
has documentation)
|
||||
|
||||
* All uses of eval() that expect to get integrated in Supybot must be approved
|
||||
by jemfinch, no exceptions. Chances are, it won't be accepted. Have you
|
||||
looked at utils.safeEval?
|
||||
|
||||
* SQL table names should be all-lowercase and include underscores to separate
|
||||
words. This is because SQL itself is case-insensitive. This doesn't
|
||||
change, however the fact that variable/member names should be camel case.
|
||||
|
||||
* SQL statements in code should put SQL words in ALL CAPS::
|
||||
|
||||
"""SELECT quote FROM quotes ORDER BY random() LIMIT 1"""
|
||||
|
||||
This makes SQL significantly easier to read.
|
||||
|
||||
* Common variable names
|
||||
|
||||
- L => an arbitrary list.
|
||||
|
||||
- t => an arbitrary tuple.
|
||||
|
||||
- x => an arbitrary float.
|
||||
|
||||
- s => an arbitrary string.
|
||||
|
||||
- f => an arbitrary function.
|
||||
|
||||
- p => an arbitrary predicate.
|
||||
|
||||
- i,n => an arbitrary integer.
|
||||
|
||||
- cb => an arbitrary callback.
|
||||
|
||||
- db => a database handle.
|
||||
|
||||
- fd => a file-like object.
|
||||
|
||||
- msg => an ircmsgs.IrcMsg object.
|
||||
|
||||
- irc => an irclib.Irc object (or proxy)
|
||||
|
||||
- nick => a string that is an IRC nick.
|
||||
|
||||
- channel => a string that is an IRC channel.
|
||||
|
||||
- hostmask => a string that is a user's IRC prefix.
|
||||
|
||||
When the semantic functionality (that is, the "meaning" of a variable is
|
||||
obvious from context), one of these names should be used. This just makes it
|
||||
easier for people reading our code to know what a variable represents
|
||||
without scouring the surrounding code.
|
||||
|
||||
* Multiple variable assignments should always be surrounded with parentheses
|
||||
-- i.e., if you're using the partition function, then your assignment
|
||||
statement should look like::
|
||||
|
||||
(good, bad) = partition(p, L)
|
||||
|
||||
The parentheses make it obvious that you're doing a multiple assignment, and
|
||||
that's important because I hate reading code and wondering where a variable
|
||||
came from.
|
379
docs/USING_UTILS.rst
Normal file
379
docs/USING_UTILS.rst
Normal file
@ -0,0 +1,379 @@
|
||||
============================
|
||||
Using Supybot's utils module
|
||||
============================
|
||||
Supybot provides a wealth of utilities for plugin writers in the supybot.utils
|
||||
module, this tutorial describes these utilities and shows you how to use them.
|
||||
|
||||
str.py
|
||||
======
|
||||
The Format Function
|
||||
-------------------
|
||||
|
||||
The supybot.utils.str module provides a bunch of utility functions for
|
||||
handling string values. This section contains a quick rundown of all of the
|
||||
functions available, along with descriptions of the arguments they take. First
|
||||
and foremost is the format function, which provides a lot of capability in
|
||||
just one function that uses string-formatting style to accomplish a lot. So
|
||||
much so that it gets its own section in this tutorial. All other functions
|
||||
will be in other sections. format takes several arguments - first, the format
|
||||
string (using the format characters described below), and then after that,
|
||||
each individual item to be formatted. Do not attempt to use the % operator to
|
||||
do the formatting because that will fall back on the normal string formatting
|
||||
operator. The format function uses the following string formatting characters.
|
||||
|
||||
* % - literal ``%``
|
||||
* i - integer
|
||||
* s - string
|
||||
* f - float
|
||||
* r - repr
|
||||
* b - form of the verb ``to be`` (takes an int)
|
||||
* h - form of the verb ``to have`` (takes an int)
|
||||
* L - commaAndify (takes a list of strings or a tuple of ([strings], and))
|
||||
* p - pluralize (takes a string)
|
||||
* q - quoted (takes a string)
|
||||
* n - n items (takes a 2-tuple of (n, item) or a 3-tuple of (n, between, item))
|
||||
* t - time, formatted (takes an int)
|
||||
* u - url, wrapped in braces
|
||||
|
||||
Here are a few examples to help elaborate on the above descriptions::
|
||||
|
||||
>>> format("Error %q has been reported %n. For more information, see %u.",
|
||||
"AttributeError", (5, "time"), "http://supybot.com")
|
||||
|
||||
'Error "AttributeError" has been reported 5 times. For more information,
|
||||
see <http://supybot.com>.'
|
||||
|
||||
>>> i = 4
|
||||
>>> format("There %b %n at this time. You are only allowed %n at any given
|
||||
time", i, (i, "active", "thread"), (5, "active", "thread"))
|
||||
'There are 4 active threads at this time. You are only allowed 5 active
|
||||
threads at any given time'
|
||||
|
||||
>>> i = 1
|
||||
>>> format("There %b %n at this time. You are only allowed %n at any given
|
||||
time", i, (i, "active", "thread"), (5, "active", "thread"))
|
||||
'There is 1 active thread at this time. You are only allowed 5 active
|
||||
threads at any given time'
|
||||
|
||||
>>> ops = ["foo", "bar", "baz"]
|
||||
>>> format("The following %n %h the %s capability: %L", (len(ops), "user"),
|
||||
len(ops), "op", ops)
|
||||
'The following 3 users have the op capability: foo, bar, and baz'
|
||||
|
||||
As you can see, you can combine all sorts of combinations of formatting
|
||||
strings into one. In fact, that was the major motivation behind format. We
|
||||
have specific functions that you can use individually for each of those
|
||||
formatting types, but it became much easier just to use special formatting
|
||||
chars and the format function than concatenating a bunch of strings that were
|
||||
the result of other utils.str functions.
|
||||
|
||||
The Other Functions
|
||||
-------------------
|
||||
|
||||
These are the functions that can't be handled by format. They are sorted in
|
||||
what I perceive to be the general order of usefulness (and I'm leaving the
|
||||
ones covered by format for the next section).
|
||||
|
||||
* ellipsisify(s, n) - Returns a shortened version of a string. Produces up to
|
||||
the first n chars at the nearest word boundary.
|
||||
|
||||
- s: the string to be shortened
|
||||
- n: the number of characters to shorten it to
|
||||
|
||||
* perlReToPythonRe(s) - Converts a Perl-style regexp (e.g., "/abcd/i" or
|
||||
"m/abcd/i") to an actual Python regexp (an re object)
|
||||
|
||||
- s: the regexp string
|
||||
|
||||
* perlReToReplacer(s) - converts a perl-style replacement regexp (eg,
|
||||
"s/foo/bar/g") to a Python function that performs such a replacement
|
||||
|
||||
- s: the regexp string
|
||||
|
||||
* dqrepr(s) - Returns a repr() of s guaranteed to be in double quotes.
|
||||
(Double Quote Repr)
|
||||
|
||||
- s: the string to be double-quote repr()'ed
|
||||
|
||||
* toBool(s) - Determines whether or not a string means True or False and
|
||||
returns the appropriate boolean value. True is any of "true", "on",
|
||||
"enable", "enabled", or "1". False is any of "false", "off", "disable",
|
||||
"disabled", or "0".
|
||||
|
||||
- s: the string to determine the boolean value for
|
||||
|
||||
* rsplit(s, sep=None, maxsplit=-1) - functionally the same as str.split in the
|
||||
Python standard library except splitting from the right instead of the left.
|
||||
Python 2.4 has str.rsplit (which this function defers to for those versions
|
||||
>= 2.4), but Python 2.3 did not.
|
||||
|
||||
- s: the string to be split
|
||||
- sep: the separator to split on, defaults to whitespace
|
||||
- maxsplit: the maximum number of splits to perform, -1 splits all possible
|
||||
splits.
|
||||
|
||||
* normalizeWhitespace(s) - reduces all multi-spaces in a string to a single
|
||||
space
|
||||
|
||||
- s: the string to normalize
|
||||
|
||||
* depluralize(s) - the opposite of pluralize
|
||||
|
||||
- s: the string to depluralize
|
||||
|
||||
* unCommaThe(s) - Takes a string of the form "foo, the" and turns it into "the
|
||||
foo"
|
||||
|
||||
- s: string, the
|
||||
|
||||
* distance(s, t) - computes the levenshtein distance (or "edit distance")
|
||||
between two strings
|
||||
|
||||
- s: the first string
|
||||
- t: the second string
|
||||
|
||||
* soundex(s, length=4) - computes the soundex for a given string
|
||||
|
||||
- s: the string to compute the soundex for
|
||||
- length: the length of the soundex to generate
|
||||
|
||||
* matchCase(s1, s2) - Matches the case of the first string in the second
|
||||
string.
|
||||
|
||||
- s1: the first string
|
||||
- s2: the string which will be made to match the case of the first
|
||||
|
||||
The Commands Format Already Covers
|
||||
----------------------------------
|
||||
|
||||
These commands aren't necessary because you can achieve them more easily by
|
||||
using the format command, but they exist if you decide you want to use them
|
||||
anyway though it is greatly discouraged for general use.
|
||||
|
||||
* commaAndify(seq, comma=",", And="and") - transforms a list of items into a
|
||||
comma separated list with an "and" preceding the last element. For example,
|
||||
["foo", "bar", "baz"] becomes "foo, bar, and baz". Is smart enough to
|
||||
convert two-element lists to just "item1 and item2" as well.
|
||||
|
||||
- seq: the sequence of items (don't have to be strings, but need to be
|
||||
'str()'-able)
|
||||
- comma: the character to use to separate the list
|
||||
- And: the word to use before the last element
|
||||
|
||||
* pluralize(s) - Returns the plural of a string. Put any exceptions to the
|
||||
general English rules of pluralization in the plurals dictionary in
|
||||
supybot.utils.str.
|
||||
|
||||
- s: the string to pluralize
|
||||
|
||||
* nItems(n, item, between=None) - returns a string that describes a given
|
||||
number of an item (with any string between the actual number and the item
|
||||
itself), handles pluralization with the pluralize function above. Note that
|
||||
the arguments here are in a different order since between is optional.
|
||||
|
||||
- n: the number of items
|
||||
- item: the type of item
|
||||
- between: the optional string that goes between the number and the type of
|
||||
item
|
||||
|
||||
* quoted(s) - Returns the string surrounded by double-quotes.
|
||||
|
||||
- s: the string to quote
|
||||
|
||||
* be(i) - Returns the proper form of the verb "to be" based on the number
|
||||
provided (be(1) is "is", be(anything else) is "are")
|
||||
|
||||
- i: the number of things that "be"
|
||||
|
||||
* has(i) - Returns the proper form of the verb "to have" based on the number
|
||||
provided (has(1) is "has", has(anything else) is "have")
|
||||
|
||||
- i: the number of things that "has"
|
||||
|
||||
structures.py
|
||||
=============
|
||||
Intro
|
||||
-----
|
||||
|
||||
This module provides a number of useful data structures that aren't found in
|
||||
the standard Python library. For the most part they were created as needed for
|
||||
the bot and plugins themselves, but they were created in such a way as to be
|
||||
of general use for anyone who needs a data structure that performs a like
|
||||
duty. As usual in this document, I'll try and order these in order of
|
||||
usefulness, starting with the most useful.
|
||||
|
||||
The queue classes
|
||||
-----------------
|
||||
|
||||
The structures module provides two general-purpose queue classes for you to
|
||||
use. The "queue" class is a robust full-featured queue that scales up to
|
||||
larger sized queues. The "smallqueue" class is for queues that will contain
|
||||
fewer (less than 1000 or so) items. Both offer the same common interface,
|
||||
which consists of:
|
||||
|
||||
* a constructor which will optionally accept a sequence to start the queue off
|
||||
with
|
||||
* enqueue(item) - adds an item to the back of the queue
|
||||
* dequeue() - removes (and returns) the item from the front of the queue
|
||||
* peek() - returns the item from the front of the queue without removing it
|
||||
* reset() - empties the queue entirely
|
||||
|
||||
In addition to these general-use queue classes, there are two other more
|
||||
specialized queue classes as well. The first is the "TimeoutQueue" which holds
|
||||
a queue of items until they reach a certain age and then they are removed from
|
||||
the queue. It features the following:
|
||||
|
||||
* TimeoutQueue(timeout, queue=None) - you must specify the timeout (in
|
||||
seconds) in the constructor. Note that you can also optionally pass it a
|
||||
queue which uses any implementation you wish to use whether it be one of the
|
||||
above (queue or smallqueue) or if it's some custom queue you create that
|
||||
implements the same interface. If you don't pass it a queue instance to use,
|
||||
it will build its own using smallqueue.
|
||||
|
||||
- reset(), enqueue(item), dequeue() - all same as above queue classes
|
||||
- setTimeout(secs) - allows you to change the timeout value
|
||||
|
||||
And for the final queue class, there's the "MaxLengthQueue" class. As you may
|
||||
have guessed, it's a queue that is capped at a certain specified length. It
|
||||
features the following:
|
||||
|
||||
* MaxLengthQueue(length, seq=()) - the constructor naturally requires that you
|
||||
set the max length and it allows you to optionally pass in a sequence to be
|
||||
used as the starting queue. The underlying implementation is actually the
|
||||
queue from before.
|
||||
|
||||
- enqueue(item) - adds an item onto the back of the queue and if it would
|
||||
push it over the max length, it dequeues the item on the front (it does
|
||||
not return this item to you)
|
||||
- all the standard methods from the queue class are inherited for this class
|
||||
|
||||
The Other Structures
|
||||
--------------------
|
||||
|
||||
The most useful of the other structures is actually very similar to the
|
||||
"MaxLengthQueue". It's the "RingBuffer", which is essentially a MaxLengthQueue
|
||||
which fills up to its maximum size and then circularly replaces the old
|
||||
contents as new entries are added instead of dequeuing. It features the
|
||||
following:
|
||||
|
||||
* RingBuffer(size, seq=()) - as with the MaxLengthQueue you specify the size
|
||||
of the RingBuffer and optionally give it a sequence.
|
||||
|
||||
- append(item) - adds item to the end of the buffer, pushing out an item
|
||||
from the front if necessary
|
||||
- reset() - empties out the buffer entirely
|
||||
- resize(i) - shrinks/expands the RingBuffer to the size provided
|
||||
- extend(seq) - append the items from the provided sequence onto the end of
|
||||
the RingBuffer
|
||||
|
||||
The next data structure is the TwoWayDictionary, which as the name implies is
|
||||
a dictionary in which key-value pairs have mappings going both directions. It
|
||||
features the following:
|
||||
|
||||
* TwoWayDictionary(seq=(), \**kwargs) - Takes an optional sequence of (key,
|
||||
value) pairs as well as any key=value pairs specified in the constructor as
|
||||
initial values for the two-way dict.
|
||||
|
||||
- other than that, no extra features that a normal Python dict doesn't
|
||||
already offer with the exception that any (key, val) pair added to the
|
||||
dict is also added as (val, key) as well, so the mapping goes both ways.
|
||||
Elements are still accessed the same way you always do with Python
|
||||
'dict's.
|
||||
|
||||
There is also a MultiSet class available, but it's very unlikely that it will
|
||||
serve your purpose, so I won't go into it here. The curious coder can go check
|
||||
the source and see what it's all about if they wish (it's only used once in our
|
||||
code, in the Relay plugin).
|
||||
|
||||
web.py
|
||||
======
|
||||
The web portion of Supybot's utils module is mainly used for retrieving data
|
||||
from websites but it also has some utility functions pertaining to HTML and
|
||||
email text as well. The functions in web are listed below, once again in order
|
||||
of usefulness.
|
||||
|
||||
* getUrl(url, size=None, headers=None) - gets the data at the URL provided and
|
||||
returns it as one large string
|
||||
|
||||
- url: the location of the data to be retrieved or a urllib2.Request object
|
||||
to be used in the retrieval
|
||||
- size: the maximum number of bytes to retrieve, defaults to None, meaning
|
||||
that it is to try to retrieve all data
|
||||
- headers: a dictionary mapping header types to header data
|
||||
|
||||
* getUrlFd(url, headers=None) - returns a file-like object for a url
|
||||
|
||||
- url: the location of the data to be retrieved or a urllib2.Request object
|
||||
to be used in the retrieval
|
||||
- headers: a dictionary mapping header types to header data
|
||||
|
||||
* htmlToText(s, tagReplace=" ") - strips out all tags in a string of HTML,
|
||||
replacing them with the specified character
|
||||
|
||||
- s: the HTML text to strip the tags out of
|
||||
- tagReplace: the string to replace tags with
|
||||
|
||||
* strError(e) - pretty-printer for web exceptions, returns a descriptive
|
||||
string given a web-related exception
|
||||
|
||||
- e: the exception to pretty-print
|
||||
|
||||
* mungeEmail(s) - a naive e-mail obfuscation function, replaces "@" with "AT"
|
||||
and "." with "DOT"
|
||||
|
||||
- s: the e-mail address to obfuscate
|
||||
|
||||
* getDomain(url) - returns the domain of a URL
|
||||
- url: the URL in question
|
||||
|
||||
The Best of the Rest
|
||||
====================
|
||||
Intro
|
||||
-----
|
||||
|
||||
Rather than document each of the remaining portions of the supybot.utils
|
||||
module, I've elected to just pick out the choice bits from specific parts and
|
||||
document those instead. Here they are, broken out by module name.
|
||||
|
||||
supybot.utils.file - file utilities
|
||||
-----------------------------------
|
||||
|
||||
* touch(filename) - updates the access time of a file by opening it for
|
||||
writing and immediately closing it
|
||||
|
||||
* mktemp(suffix="") - creates a decent random string, suitable for a temporary
|
||||
filename with the given suffix, if provided
|
||||
|
||||
* the AtomicFile class - used for files that need to be atomically written,
|
||||
i.e., if there's a failure the original file remains unmodified. For more
|
||||
info consult file.py in src/utils
|
||||
|
||||
supybot.utils.gen - general utilities
|
||||
-------------------------------------
|
||||
|
||||
* timeElapsed(elapsed, [lots of optional args]) - given the number of seconds
|
||||
elapsed, returns a string with the English description of the amount of time
|
||||
passed, consult gen.py in src/utils for the exact argument list and
|
||||
documentation if you feel you could use this function.
|
||||
|
||||
* exnToString(e) - improved exception-to-string function. Provides nicer
|
||||
output than a simple str(e).
|
||||
|
||||
* InsensitivePreservingDict class - a dict class that is case-insensitive when
|
||||
accessing keys
|
||||
|
||||
supybot.utils.iter - iterable utilities
|
||||
---------------------------------------
|
||||
|
||||
* len(iterable) - returns the length of a given iterable
|
||||
|
||||
* groupby(key, iterable) - equivalent to the itertools.groupby function
|
||||
available as of Python 2.4. Provided for backwards compatibility.
|
||||
|
||||
* any(p, iterable) - Returns true if any element in the iterable satisfies the
|
||||
predicate p
|
||||
|
||||
* all(p, iterable) - Returns true if all elements in the iterable satisfy the
|
||||
predicate p
|
||||
|
||||
* choice(iterable) - Returns a random element from the iterable
|
482
docs/USING_WRAP.rst
Normal file
482
docs/USING_WRAP.rst
Normal file
@ -0,0 +1,482 @@
|
||||
Using commands.wrap to parse your command's arguments.
|
||||
------------------------------------------------------
|
||||
This document illustrates how to use the new 'wrap' function present in Supybot
|
||||
0.80 to handle argument parsing and validation for your plugin's commands.
|
||||
|
||||
Introduction
|
||||
============
|
||||
To plugin developers for older (pre-0.80) versions of Supybot, one of the more
|
||||
annoying aspects of writing commands was handling the arguments that were
|
||||
passed in. In fact, many commands often had to duplicate parsing and
|
||||
verification code, resulting in lots of duplicated code for not a whole lot of
|
||||
action. So, instead of forcing plugin writers to come up with their own ways of
|
||||
cleaning it up, we wrote up the wrap function to handle all of it.
|
||||
|
||||
It allows a much simpler and more flexible way of checking things than before
|
||||
and it doesn't require that you know the bot internals to do things like check
|
||||
and see if a user exists, or check if a command name exists and whatnot.
|
||||
|
||||
If you are a plugin author this document is absolutely required reading, as it
|
||||
will massively ease the task of writing commands.
|
||||
|
||||
Using Wrap
|
||||
==========
|
||||
First off, to get the wrap function, it is recommended (strongly) that you use
|
||||
the following import line::
|
||||
|
||||
from supybot.commands import *
|
||||
|
||||
This will allow you to access the wrap command (and it allows you to do it
|
||||
without the commands prefix). Note that this line is added to the imports of
|
||||
plugin templates generated by the supybot-plugin-create script.
|
||||
|
||||
Let's write a quickie command that uses wrap to get a feel for how it makes our
|
||||
lives better. Let's write a command that repeats a string of text a given
|
||||
number of times. So you could say "repeat 3 foo" and it would say "foofoofoo".
|
||||
Not a very useful command, but it will serve our purpose just fine. Here's how
|
||||
it would be done without wrap::
|
||||
|
||||
def repeat(self, irc, msg, args):
|
||||
"""<num> <text>
|
||||
|
||||
Repeats <text> <num> times.
|
||||
"""
|
||||
(num, text) = privmsg.getArgs(args, required=2)
|
||||
try:
|
||||
num = int(num)
|
||||
except ValueError:
|
||||
raise callbacks.ArgumentError
|
||||
irc.reply(num * text)
|
||||
|
||||
Note that all of the argument validation and parsing takes up 5 of the 6 lines
|
||||
(and you should have seen it before we had privmsg.getArgs!). Now, here's what
|
||||
our command will look like with wrap applied::
|
||||
|
||||
def repeat(self, irc, msg, args, num, text):
|
||||
"""<num> <text>
|
||||
|
||||
Repeats <text> <num> times.
|
||||
"""
|
||||
irc.reply(text * num)
|
||||
repeat = wrap(repeat, ['int', 'text'])
|
||||
|
||||
Pretty short, eh? With wrap all of the argument parsing and validation is
|
||||
handled for us and we get the arguments we want, formatted how we want them,
|
||||
and converted into whatever types we want them to be - all in one simple
|
||||
function call that is used to wrap the function! So now the code inside each
|
||||
command really deals with how to execute the command and not how to deal with
|
||||
the input.
|
||||
|
||||
So, now that you see the benefits of wrap, let's figure out what stuff we have
|
||||
to do to use it.
|
||||
|
||||
Syntax Changes
|
||||
==============
|
||||
There are two syntax changes to the old style that are implemented. First, the
|
||||
definition of the command function must be changed. The basic syntax for the
|
||||
new definition is::
|
||||
|
||||
def commandname(self, irc, msg, args, <arg1>, <arg2>, ...):
|
||||
|
||||
Where arg1 and arg2 (up through as many as you want) are the variables that
|
||||
will store the parsed arguments. "Now where do these parsed arguments come
|
||||
from?" you ask. Well, that's where the second syntax change comes in. The
|
||||
second syntax change is the actual use of the wrap function itself to decorate
|
||||
our command names. The basic decoration syntax is::
|
||||
|
||||
commandname = wrap(commandname, [converter1, converter2, ...])
|
||||
|
||||
.. note::
|
||||
|
||||
This should go on the line immediately following the body of the command's
|
||||
definition, so it can easily be located (and it obviously must go after the
|
||||
command's definition so that commandname is defined).
|
||||
|
||||
Each of the converters in the above listing should be one of the converters in
|
||||
commands.py (I will describe each of them in detail later.) The converters are
|
||||
applied in order to the arguments given to the command, generally taking
|
||||
arguments off of the front of the argument list as they go. Note that each of
|
||||
the arguments is actually a string containing the NAME of the converter to use
|
||||
and not a reference to the actual converter itself. This way we can have
|
||||
converters with names like int and not have to worry about polluting the
|
||||
builtin namespace by overriding the builtin int.
|
||||
|
||||
As you will find out when you look through the list of converters below, some
|
||||
of the converters actually take arguments. The syntax for supplying them (since
|
||||
we aren't actually calling the converters, but simply specifying them), is to
|
||||
wrap the converter name and args list into a tuple. For example::
|
||||
|
||||
commandname = wrap(commandname, [(converterWithArgs, arg1, arg2),
|
||||
converterWithoutArgs1, converterWithoutArgs2])
|
||||
|
||||
For the most part you won't need to use an argument with the converters you use
|
||||
either because the defaults are satisfactory or because it doesn't even take
|
||||
any.
|
||||
|
||||
Customizing Wrap
|
||||
================
|
||||
Converters alone are a pretty powerful tool, but for even more advanced (yet
|
||||
simpler!) argument handling you may want to use contexts. Contexts describe how
|
||||
the converters are applied to the arguments, while the converters themselves
|
||||
do the actual parsing and validation.
|
||||
|
||||
For example, one of the contexts is "optional". By using this context, you're
|
||||
saying that a given argument is not required, and if the supplied converter
|
||||
doesn't find anything it likes, we should use some default. Yet another
|
||||
example is the "reverse" context. This context tells the supplied converter to
|
||||
look at the last argument and work backwards instead of the normal
|
||||
first-to-last way of looking at arguments.
|
||||
|
||||
So, that should give you a feel for the role that contexts play. They are not
|
||||
by any means necessary to use wrap. All of the stuff we've done to this point
|
||||
will work as-is. However, contexts let you do some very powerful things in very
|
||||
easy ways, and are a good thing to know how to use.
|
||||
|
||||
Now, how do you use them? Well, they are in the global namespace of
|
||||
src/commands.py, so your previous import line will import them all; you can
|
||||
call them just as you call wrap. In fact, the way you use them is you simply
|
||||
call the context function you want to use, with the converter (and its
|
||||
arguments) as arguments. It's quite simple. Here's an example::
|
||||
|
||||
commandname = wrap(commandname, [optional('int'), many('something')])
|
||||
|
||||
In this example, our command is looking for an optional integer argument first.
|
||||
Then, after that, any number of arguments which can be anything (as long as
|
||||
they are something, of course).
|
||||
|
||||
Do note, however, that the type of the arguments that are returned can be
|
||||
changed if you apply a context to it. So, optional("int") may very well return
|
||||
None as well as something that passes the "int" converter, because after all
|
||||
it's an optional argument and if it is None, that signifies that nothing was
|
||||
there. Also, for another example, many("something") doesn't return the same
|
||||
thing that just "something" would return, but rather a list of "something"s.
|
||||
|
||||
Converter List
|
||||
==============
|
||||
Below is a list of all the available converters to use with wrap. If the
|
||||
converter accepts any arguments, they are listed after it and if they are
|
||||
optional, the default value is shown.
|
||||
|
||||
* id, kind="integer"
|
||||
|
||||
- Returns something that looks like an integer ID number. Takes an optional
|
||||
"kind" argument for you to state what kind of ID you are looking for,
|
||||
though this doesn't affect the integrity-checking. Basically requires that
|
||||
the argument be an integer, does no other integrity-checking, and provides
|
||||
a nice error message with the kind in it.
|
||||
|
||||
* ip
|
||||
|
||||
- Checks and makes sure the argument looks like a valid IP and then returns
|
||||
it.
|
||||
|
||||
* int, type="integer", p=None
|
||||
|
||||
- Gets an integer. The "type" text can be used to customize the error message
|
||||
received when the argument is not an integer. "p" is an optional predicate
|
||||
to test the integer with. If p(i) fails (where i is the integer arg parsed
|
||||
out of the argument string), the arg will not be accepted.
|
||||
|
||||
* index
|
||||
|
||||
- Basically ("int", "index"), but with a twist. This will take a 1-based
|
||||
index and turn it into a 0-based index (which is more useful in code). It
|
||||
doesn't transform 0, and it maintains negative indices as is (note that it
|
||||
does allow them!).
|
||||
|
||||
* color
|
||||
|
||||
- Accepts arguments that describe a text color code (e.g., "black", "light
|
||||
blue") and returns the mIRC color code for that color. (Note that many
|
||||
other IRC clients support the mIRC color code scheme, not just mIRC)
|
||||
|
||||
* now
|
||||
|
||||
- Simply returns the current timestamp as an arg, does not reference or
|
||||
modify the argument list.
|
||||
|
||||
* url
|
||||
|
||||
- Checks for a valid URL.
|
||||
|
||||
* httpUrl
|
||||
|
||||
- Checks for a valid HTTP URL.
|
||||
|
||||
* long, type="long"
|
||||
|
||||
- Basically the same as int minus the predicate, except that it converts the
|
||||
argument to a long integer regardless of the size of the int.
|
||||
|
||||
* float, type="floating point number"
|
||||
|
||||
- Basically the same as int minus the predicate, except that it converts the
|
||||
argument to a float.
|
||||
|
||||
* nonInt, type="non-integer value"
|
||||
|
||||
- Accepts everything but integers, and returns them unchanged. The "type"
|
||||
value, as always, can be used to customize the error message that is
|
||||
displayed upon failure.
|
||||
|
||||
* positiveInt
|
||||
|
||||
- Accepts only positive integers.
|
||||
|
||||
* nonNegativeInt
|
||||
|
||||
- Accepts only non-negative integers.
|
||||
|
||||
* letter
|
||||
|
||||
- Looks for a single letter. (Technically, it looks for any one-element
|
||||
sequence).
|
||||
|
||||
* haveOp, action="do that"
|
||||
|
||||
- Simply requires that the bot have ops in the channel that the command is
|
||||
called in. The action parameter completes the error message: "I need to be
|
||||
opped to ...".
|
||||
|
||||
* expiry
|
||||
|
||||
- Takes a number of seconds and adds it to the current time to create an
|
||||
expiration timestamp.
|
||||
|
||||
* literal, literals, errmsg=None
|
||||
|
||||
- Takes a required sequence or string (literals) and any argument that
|
||||
uniquely matches the starting substring of one of the literals is
|
||||
transformed into the full literal. For example, with ``("literal", ("bar",
|
||||
"baz", "qux"))``, you'd get "bar" for "bar", "baz" for "baz", and "qux"
|
||||
for any of "q", "qu", or "qux". "b" and "ba" would raise errors because
|
||||
they don't uniquely identify one of the literals in the list. You can
|
||||
override errmsg to provide a specific (full) error message, otherwise the
|
||||
default argument error message is displayed.
|
||||
|
||||
* to
|
||||
|
||||
- Returns the string "to" if the arg is any form of "to" (case-insensitive).
|
||||
|
||||
* nick
|
||||
|
||||
- Checks that the arg is a valid nick on the current IRC server.
|
||||
|
||||
* seenNick
|
||||
|
||||
- Checks that the arg is a nick that the bot has seen (NOTE: this is limited
|
||||
by the size of the history buffer that the bot has).
|
||||
|
||||
* channel
|
||||
|
||||
- Gets a channel to use the command in. If the channel isn't supplied, uses
|
||||
the channel the message was sent in. If using a different channel, does
|
||||
sanity-checking to make sure the channel exists on the current IRC network.
|
||||
|
||||
* inChannel
|
||||
|
||||
- Requires that the command be called from within any channel that the bot
|
||||
is currently in or with one of those channels used as an argument to the
|
||||
command.
|
||||
|
||||
* onlyInChannel
|
||||
|
||||
- Requires that the command be called from within any channel that the bot
|
||||
is currently in.
|
||||
|
||||
* nickInChannel
|
||||
|
||||
- Requires that the argument be a nick that is in the current channel, and
|
||||
returns that nick.
|
||||
|
||||
* networkIrc, errorIfNoMatch=False
|
||||
|
||||
- Returns the IRC object of the specified IRC network. If one isn't
|
||||
specified, the IRC object of the IRC network the command was called on is
|
||||
returned.
|
||||
|
||||
* callerInGivenChannel
|
||||
|
||||
- Takes the given argument as a channel and makes sure that the caller is in
|
||||
that channel.
|
||||
|
||||
* plugin, require=True
|
||||
|
||||
- Returns the plugin specified by the arg or None. If require is True, an
|
||||
error is raised if the plugin cannot be retrieved.
|
||||
|
||||
* boolean
|
||||
|
||||
- Converts the text string to a boolean value. Acceptable true values are:
|
||||
"1", "true", "on", "enable", or "enabled" (case-insensitive). Acceptable
|
||||
false values are: "0", false", "off", "disable", or "disabled"
|
||||
(case-insensitive).
|
||||
|
||||
* lowered
|
||||
|
||||
- Returns the argument lowered (NOTE: it is lowered according to IRC
|
||||
conventions, which does strange mapping with some punctuation characters).
|
||||
|
||||
* anything
|
||||
|
||||
- Returns anything as is.
|
||||
|
||||
* something, errorMsg=None, p=None
|
||||
|
||||
- Takes anything but the empty string. errorMsg can be used to customize the
|
||||
error message. p is any predicate function that can be used to test the
|
||||
validity of the input.
|
||||
|
||||
* filename
|
||||
|
||||
- Used to get a filename argument.
|
||||
|
||||
* commandName
|
||||
|
||||
- Returns the canonical command name version of the given string (ie, the
|
||||
string is lowercased and dashes and underscores are removed).
|
||||
|
||||
* text
|
||||
|
||||
- Takes the rest of the arguments as one big string. Note that this differs
|
||||
from the "anything" context in that it clobbers the arg string when it's
|
||||
done. Using any converters after this is most likely incorrect.
|
||||
|
||||
* glob
|
||||
|
||||
- Gets a glob string. Basically, if there are no wildcards (``*``, ``?``) in
|
||||
the argument, returns ``*string*``, making a glob string that matches
|
||||
anything containing the given argument.
|
||||
|
||||
* somethingWithoutSpaces
|
||||
|
||||
- Same as something, only with the exception of disallowing spaces of course.
|
||||
|
||||
* capability
|
||||
|
||||
- Used to retrieve an argument that describes a capability.
|
||||
|
||||
* channelDb
|
||||
|
||||
- Sets the channel appropriately in order to get to the databases for that
|
||||
channel (handles whether or not a given channel uses channel-specific
|
||||
databases and whatnot).
|
||||
|
||||
* hostmask
|
||||
|
||||
- Returns the hostmask of any provided nick or hostmask argument.
|
||||
|
||||
* banmask
|
||||
|
||||
- Returns a generic banmask of the provided nick or hostmask argument.
|
||||
|
||||
* user
|
||||
|
||||
- Requires that the caller be a registered user.
|
||||
|
||||
* matches, regexp, errmsg
|
||||
|
||||
- Searches the args with the given regexp and returns the matches. If no
|
||||
match is found, errmsg is given.
|
||||
|
||||
* public
|
||||
|
||||
- Requires that the command be sent in a channel instead of a private
|
||||
message.
|
||||
|
||||
* private
|
||||
|
||||
- Requires that the command be sent in a private message instead of a
|
||||
channel.
|
||||
|
||||
* otherUser
|
||||
|
||||
- Returns the user specified by the username or hostmask in the argument.
|
||||
|
||||
* regexpMatcher
|
||||
|
||||
- Gets a matching regexp argument (m// or //).
|
||||
|
||||
* validChannel
|
||||
|
||||
- Gets a channel argument once it makes sure it's a valid channel.
|
||||
|
||||
* regexpReplacer
|
||||
|
||||
- Gets a replacing regexp argument (s//).
|
||||
|
||||
* owner
|
||||
|
||||
- Requires that the command caller has the "owner" capability.
|
||||
|
||||
* admin
|
||||
|
||||
- Requires that the command caller has the "admin" capability.
|
||||
|
||||
* checkCapability, capability
|
||||
|
||||
- Checks to make sure that the caller has the specified capability.
|
||||
|
||||
"checkChannelCapability", capability
|
||||
Checks to make sure that the caller has the specified capability on the
|
||||
channel the command is called in.
|
||||
|
||||
Contexts List
|
||||
=============
|
||||
What contexts are available for me to use?
|
||||
|
||||
The list of available contexts is below. Unless specified otherwise, it can be
|
||||
assumed that the type returned by the context itself matches the type of the
|
||||
converter it is applied to.
|
||||
|
||||
any
|
||||
Looks for any number of arguments matching the supplied converter. Will
|
||||
return a sequence of converted arguments or None.
|
||||
|
||||
many
|
||||
Looks for multiple arguments matching the supplied converter. Expects at
|
||||
least one to work, otherwise it will fail. Will return the sequence of
|
||||
converted arguments.
|
||||
|
||||
optional
|
||||
Look for an argument that satisfies the supplied converter, but if it's not
|
||||
the type I'm expecting or there are no arguments for us to check, then use
|
||||
the default value. Will return the converted argument as is or None.
|
||||
|
||||
additional
|
||||
Look for an argument that satisfies the supplied converter, making sure
|
||||
that it's the right type. If there aren't any arguments to check, then use
|
||||
the default value. Will return the converted argument as is or None.
|
||||
|
||||
rest
|
||||
Treat the rest of the arguments as one big string, and then convert. If the
|
||||
conversion is unsuccessful, restores the arguments.
|
||||
|
||||
getopts
|
||||
Handles --option style arguments. Each option should be a key in a
|
||||
dictionary that maps to the name of the converter that is to be used on
|
||||
that argument. To make the option take no argument, use "" as the converter
|
||||
name in the dictionary. For no conversion, use None as the converter name
|
||||
in the dictionary.
|
||||
|
||||
first
|
||||
Tries each of the supplied converters in order and returns the result of
|
||||
the first successfully applied converter.
|
||||
|
||||
reverse
|
||||
Reverse the argument list, apply the converters, and then reverse the
|
||||
argument list back.
|
||||
|
||||
commalist
|
||||
Looks for a comma separated list of arguments that match the supplied
|
||||
converter. Returns a list of the successfully converted arguments. If any
|
||||
of the arguments fail, this whole context fails.
|
||||
|
||||
|
||||
Final Word
|
||||
==========
|
||||
|
||||
Now that you know how to use wrap, and you have a list of converters and
|
||||
contexts you can use, your task of writing clean, simple, and safe plugin code
|
||||
should become much easier. Enjoy!
|
||||
|
195
docs/conf.py
Normal file
195
docs/conf.py
Normal file
@ -0,0 +1,195 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Supybot documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Feb 27 12:42:30 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('../src'))
|
||||
#sys.path.append(os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Supybot'
|
||||
copyright = u'2010, Jeremiah Fincher and James Vega'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.83.4.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.83.4.1+git+fr3'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
modindex_common_prefix = ['supybot']
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Supybotdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Supybot.tex', u'Supybot Documentation',
|
||||
u'Jeremiah Fincher and James Vega', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
24
docs/index.rst
Normal file
24
docs/index.rst
Normal file
@ -0,0 +1,24 @@
|
||||
.. Supybot documentation master file, created by
|
||||
sphinx-quickstart on Sat Feb 27 12:42:30 2010.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to Supybot's documentation!
|
||||
===================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 0
|
||||
:glob:
|
||||
|
||||
*
|
||||
plugins
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
42
docs/man/supybot-adduser.1
Normal file
42
docs/man/supybot-adduser.1
Normal 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 Vega
|
||||
<jamessan at supybot 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
docs/man/supybot-botchk.1
Normal file
54
docs/man/supybot-botchk.1
Normal 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 Vega
|
||||
<jamessan at supybot dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD-style license.
|
43
docs/man/supybot-plugin-create.1
Normal file
43
docs/man/supybot-plugin-create.1
Normal 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 Vega
|
||||
<jamessan at supybot 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
docs/man/supybot-plugin-doc.1
Normal file
48
docs/man/supybot-plugin-doc.1
Normal 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 Vega
|
||||
<jamessan at supybot 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
docs/man/supybot-test.1
Normal file
51
docs/man/supybot-test.1
Normal 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 Vega
|
||||
<jamessan at supybot 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
docs/man/supybot-wizard.1
Normal file
42
docs/man/supybot-wizard.1
Normal 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 Vega
|
||||
<jamessan at supybot 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
docs/man/supybot.1
Normal file
66
docs/man/supybot.1
Normal 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 Vega
|
||||
<jamessan at supybot dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD-style license.
|
30
feed.xml
30
feed.xml
@ -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>
|
@ -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
locale/__init__.py
Normal file
0
locale/__init__.py
Normal file
1109
locale/fr.po
Normal file
1109
locale/fr.po
Normal file
File diff suppressed because it is too large
Load Diff
100
locale/fr.py
Normal file
100
locale/fr.py
Normal 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'
|
1355
locale/it.po
Normal file
1355
locale/it.po
Normal file
File diff suppressed because it is too large
Load Diff
1104
locale/messages.pot
Normal file
1104
locale/messages.pot
Normal file
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@ -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"
|
||||
}
|
56
plugins/Admin/__init__.py
Normal file
56
plugins/Admin/__init__.py
Normal file
@ -0,0 +1,56 @@
|
||||
###
|
||||
# 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
|
||||
|
||||
# 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__ = {}
|
||||
|
||||
import config
|
||||
import plugin
|
||||
reload(plugin) # In case we're being reloaded.
|
||||
|
||||
if world.testing:
|
||||
import test
|
||||
|
||||
Class = plugin.Class
|
||||
configure = config.configure
|
50
plugins/Admin/config.py
Normal file
50
plugins/Admin/config.py
Normal file
@ -0,0 +1,50 @@
|
||||
###
|
||||
# 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 himself 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')
|
||||
# This is where your configuration variables (if any) should go. For example:
|
||||
# conf.registerGlobalValue(Admin, 'someConfigVariableName',
|
||||
# registry.Boolean(False, """Help for someConfigVariableName."""))
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
226
plugins/Admin/locale/fi.po
Normal file
226
plugins/Admin/locale/fi.po
Normal file
@ -0,0 +1,226 @@
|
||||
# Admin plugin in Limnoria.
|
||||
# Copyright (C) 2011 Limnoria
|
||||
# Mika Suomalainen <mika.henrik.mainio@hotmail.com>, 2011.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Finnish translation of Admin plugin in Supybot\n"
|
||||
"POT-Creation-Date: 2010-10-16 10:43+CEST\n"
|
||||
"PO-Revision-Date: 2011-08-13 23:31+0200\n"
|
||||
"Last-Translator: Mika Suomalainen <mika.henrik.mainio@hotmail.com>\n"
|
||||
"Language-Team: \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: Finnish\n"
|
||||
"X-Poedit-Country: FINLAND\n"
|
||||
|
||||
#: plugin.py:54
|
||||
msgid "Nick/channel temporarily unavailable."
|
||||
msgstr "Nimimerkki/kanava on väliaikaisesti saavuttamattomissa."
|
||||
|
||||
#: plugin.py:72
|
||||
msgid "Cannot join %s, it's full."
|
||||
msgstr "Ei voida liittyä kanavalle %s, se on täynnä."
|
||||
|
||||
#: plugin.py:80
|
||||
msgid "Cannot join %s, I was not invited."
|
||||
msgstr "Ei voi liittyä kanavalle %s, minua ei ole kutsuttu."
|
||||
|
||||
#: plugin.py:88
|
||||
msgid "Cannot join %s, it's banned me."
|
||||
msgstr "Ei voi liittyä kanavalle %s, se on antanut minulle porttikiellon."
|
||||
|
||||
#: plugin.py:96
|
||||
msgid "Cannot join %s, my keyword was wrong."
|
||||
msgstr "En voi liittyä kanavalle %s, minun avainsana oli väärä."
|
||||
|
||||
#: plugin.py:104
|
||||
msgid "Cannot join %s, I'm not identified with the NickServ."
|
||||
msgstr "En voi liittyä kanavalle %s, koska en ole tunnistautunut NickServille."
|
||||
|
||||
#: plugin.py:134
|
||||
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:147
|
||||
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:156
|
||||
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:166
|
||||
msgid "I'm not currently in any channels."
|
||||
msgstr "En juuri nyt ole millään kanavalla."
|
||||
|
||||
#: plugin.py:172
|
||||
msgid "My connection is restricted, I can't change nicks."
|
||||
msgstr "Minun yhteyteni on rajoitettu. En voi vaihtaa nimimerkkiä."
|
||||
|
||||
#: plugin.py:179
|
||||
msgid "Someone else is already using that nick."
|
||||
msgstr "Joku muu käyttää jo tuota nimimerkkiä."
|
||||
|
||||
#: plugin.py:186
|
||||
msgid "That nick is currently banned."
|
||||
msgstr "Tuolla nimimerkillä on tällähetkellä porttikielto."
|
||||
|
||||
#: plugin.py:193
|
||||
msgid "I can't change nicks, the server said %q."
|
||||
msgstr "Minä en voi vaihtaa nimimerkkiä, koska palvelin sanoi %q"
|
||||
|
||||
#: plugin.py:207
|
||||
msgid ""
|
||||
"[<nick>]\n"
|
||||
"\n"
|
||||
" Changes the bot's nick to <nick>. If no nick is given, returns the\n"
|
||||
" bot's current nick.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"[<nimimerkki>]\n"
|
||||
"\n"
|
||||
" Vaihtaa botin nimimerkin <nimimerkiksi>. Jos nimimerkkiä ei ole annettu, palauttaa\n"
|
||||
" botin nykyisen nimimerkin.\n"
|
||||
" "
|
||||
|
||||
#: plugin.py:222
|
||||
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:240
|
||||
msgid "I'm not in %s."
|
||||
msgstr "Minä en ole kanavalla %s."
|
||||
|
||||
#: plugin.py:252
|
||||
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:272
|
||||
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:283
|
||||
msgid "You can't add capabilities you don't have."
|
||||
msgstr "Et voi lisätä valtuuksia, joita sinulla ei ole."
|
||||
|
||||
#: plugin.py:288
|
||||
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:300
|
||||
msgid "That user doesn't have that capability."
|
||||
msgstr "Tuolla käyttäjällä ei tuota valtuutta."
|
||||
|
||||
#: plugin.py:302
|
||||
msgid "You can't remove capabilities you don't have."
|
||||
msgstr "Sinä et voi poistaa valtuuksia, joita sinulla ei ole."
|
||||
|
||||
#: plugin.py:310
|
||||
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 ignoren <hostmaskiin> tai hostmaskiin,\n"
|
||||
" joka on tällä hetkellä yhdistetty <nimimerkkiin>. <Vanhentuminen> on vaihtoehtoinen paremetri,\n"
|
||||
" joka määrittää (\"sekuntieissa\") joiden jälkeen ignore poistetaan; jos\n"
|
||||
" sitä ei ole annettu, ignore ei vanhene koskaan automaattisesti.\n"
|
||||
" "
|
||||
|
||||
#: plugin.py:323
|
||||
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 ignoren <hostmaskista> tai\n"
|
||||
" hostmaskista joka on tällä hetkellä yhdistetty <nimimerkkiin>.\n"
|
||||
" "
|
||||
|
||||
#: plugin.py:332
|
||||
msgid "%s wasn't in the ignores database."
|
||||
msgstr "%s ei ollut ignore tietokannassa."
|
||||
|
||||
#: plugin.py:337
|
||||
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 ignoressa.\n"
|
||||
" "
|
||||
|
||||
#: plugin.py:345
|
||||
msgid "I'm not currently globally ignoring anyone."
|
||||
msgstr "Minä en tällä hetkellä pidä ketään globaalissa ignoressa."
|
||||
|
202
plugins/Admin/locale/fr.po
Normal file
202
plugins/Admin/locale/fr.po
Normal file
@ -0,0 +1,202 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot-fr\n"
|
||||
"POT-Creation-Date: 2010-10-16 10:43+CEST\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Valentin Lorentz <progval@gmail.com>\n"
|
||||
"Language-Team: ProgVal <progval@gmail.com>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: French\n"
|
||||
"X-Poedit-Country: France\n"
|
||||
"X-Poedit-SourceCharset: Ascii\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, it's banned me."
|
||||
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
|
||||
msgid "Cannot join %s, I'm not identified with the NickServ."
|
||||
msgstr "Ne peut joindre %s, je ne suis pas identifié auprès de NickServ."
|
||||
|
||||
#: plugin.py:134
|
||||
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:147
|
||||
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:156
|
||||
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:166
|
||||
msgid "I'm not currently in any channels."
|
||||
msgstr "Je ne suis actuellement sur aucun canal."
|
||||
|
||||
#: plugin.py:172
|
||||
msgid "My connection is restricted, I can't change nicks."
|
||||
msgstr "Ma connexion est restreinte, je ne peux changer de nick."
|
||||
|
||||
#: plugin.py:179
|
||||
msgid "Someone else is already using that nick."
|
||||
msgstr "Quelqu'un d'autre utilise déjà ce nick."
|
||||
|
||||
#: plugin.py:186
|
||||
msgid "That nick is currently banned."
|
||||
msgstr "Ce nick est banni."
|
||||
|
||||
#: plugin.py:193
|
||||
msgid "I can't change nicks, the server said %q."
|
||||
msgstr "Je ne peux changer de nick, le serveur a dit %q."
|
||||
|
||||
#: plugin.py:207
|
||||
msgid ""
|
||||
"[<nick>]\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"
|
||||
"Change le nick du bot à <nick>. Si aucun nick n'est donné, retourne le nick actuel du bot."
|
||||
|
||||
#: plugin.py:222
|
||||
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:240
|
||||
msgid "I'm not in %s."
|
||||
msgstr "Je ne suis pas sur %s."
|
||||
|
||||
#: plugin.py:252
|
||||
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:272
|
||||
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:283
|
||||
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:288
|
||||
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:300
|
||||
msgid "That user doesn't have that capability."
|
||||
msgstr "Cet utilisateur n'a pas cette capacité."
|
||||
|
||||
#: plugin.py:302
|
||||
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:310
|
||||
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:323
|
||||
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:332
|
||||
msgid "%s wasn't in the ignores database."
|
||||
msgstr "%s n'étais pas dans ma base de données d'ignorance."
|
||||
|
||||
#: plugin.py:337
|
||||
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:345
|
||||
msgid "I'm not currently globally ignoring anyone."
|
||||
msgstr "Je n'ignore actuellement personne globalement."
|
||||
|
229
plugins/Admin/locale/it.po
Normal file
229
plugins/Admin/locale/it.po
Normal file
@ -0,0 +1,229 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot-fr\n"
|
||||
"POT-Creation-Date: 2011-02-26 09:49+CET\n"
|
||||
"PO-Revision-Date: 2011-08-09 23:57+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:54
|
||||
#, docstring
|
||||
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, it's banned me."
|
||||
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
|
||||
msgid "Cannot join %s, I'm not identified with the NickServ."
|
||||
msgstr "Non posso entrare in %s, non sono identificato con NickServ."
|
||||
|
||||
#: plugin.py:134
|
||||
#, 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 ""
|
||||
"<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:147
|
||||
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:156
|
||||
#, 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 ""
|
||||
"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:166
|
||||
msgid "I'm not currently in any channels."
|
||||
msgstr "Al momento non sono in nessun canale."
|
||||
|
||||
#: plugin.py:172
|
||||
msgid "My connection is restricted, I can't change nicks."
|
||||
msgstr "La mia connessione è limitata, non posso cambiare nick."
|
||||
|
||||
#: plugin.py:179
|
||||
msgid "Someone else is already using that nick."
|
||||
msgstr "Qualcun altro sta utilizzando questo nick."
|
||||
|
||||
#: plugin.py:186
|
||||
msgid "That nick is currently banned."
|
||||
msgstr "Il nick è attualmente bannato."
|
||||
|
||||
#: plugin.py:193
|
||||
msgid "I can't change nicks, the server said %q."
|
||||
msgstr "Non posso cambiare nick, il server ha detto %q."
|
||||
|
||||
#: plugin.py:207
|
||||
#, docstring
|
||||
msgid ""
|
||||
"[<nick>]\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:222
|
||||
#, 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 ""
|
||||
"[<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:240
|
||||
msgid "I'm not in %s."
|
||||
msgstr "Non sono in %s."
|
||||
|
||||
#: plugin.py:252
|
||||
#, 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 ""
|
||||
"<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:272
|
||||
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:283
|
||||
msgid "You can't add capabilities you don't have."
|
||||
msgstr "Non puoi aggiungere capacità che non hai."
|
||||
|
||||
#: plugin.py:288
|
||||
#, 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 ""
|
||||
"<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:300
|
||||
msgid "That user doesn't have that capability."
|
||||
msgstr "Questo utente non ha tale capacità."
|
||||
|
||||
#: plugin.py:302
|
||||
msgid "You can't remove capabilities you don't have."
|
||||
msgstr "Non puoi rimuovere capacità che non hai."
|
||||
|
||||
#: plugin.py:310
|
||||
#, 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> [<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:323
|
||||
#, 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"
|
||||
" Rimuove l'ignore persistente su <hostmask> o l'attuale hostmask associata a <nick>.\n"
|
||||
" "
|
||||
|
||||
#: plugin.py:332
|
||||
msgid "%s wasn't in the ignores database."
|
||||
msgstr "%s non è nel mio database degli ignorati."
|
||||
|
||||
#: plugin.py:337
|
||||
#, docstring
|
||||
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:345
|
||||
msgid "I'm not currently globally ignoring anyone."
|
||||
msgstr "Al momento non sto ignorando nessuno."
|
||||
|
187
plugins/Admin/messages.pot
Normal file
187
plugins/Admin/messages.pot
Normal file
@ -0,0 +1,187 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2011-02-26 09:49+CET\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:54
|
||||
#, docstring
|
||||
msgid "Nick/channel temporarily unavailable."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:72
|
||||
msgid "Cannot join %s, it's full."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:80
|
||||
msgid "Cannot join %s, I was not invited."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:88
|
||||
msgid "Cannot join %s, it's banned me."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:96
|
||||
msgid "Cannot join %s, my keyword was wrong."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:104
|
||||
msgid "Cannot join %s, I'm not identified with the NickServ."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:134
|
||||
#, 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:147
|
||||
msgid "I'm already too close to maximum number of channels for this network."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:156
|
||||
#, 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:166
|
||||
msgid "I'm not currently in any channels."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:172
|
||||
msgid "My connection is restricted, I can't change nicks."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:179
|
||||
msgid "Someone else is already using that nick."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:186
|
||||
msgid "That nick is currently banned."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:193
|
||||
msgid "I can't change nicks, the server said %q."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:207
|
||||
#, docstring
|
||||
msgid ""
|
||||
"[<nick>]\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:222
|
||||
#, 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:240
|
||||
msgid "I'm not in %s."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:252
|
||||
#, 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:272
|
||||
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:283
|
||||
msgid "You can't add capabilities you don't have."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:288
|
||||
#, 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:300
|
||||
msgid "That user doesn't have that capability."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:302
|
||||
msgid "You can't remove capabilities you don't have."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:310
|
||||
#, 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:323
|
||||
#, 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:332
|
||||
msgid "%s wasn't in the ignores database."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:337
|
||||
#, docstring
|
||||
msgid ""
|
||||
"takes no arguments\n"
|
||||
"\n"
|
||||
" Lists the hostmasks that the bot is ignoring.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:345
|
||||
msgid "I'm not currently globally ignoring anyone."
|
||||
msgstr ""
|
||||
|
351
plugins/Admin/plugin.py
Normal file
351
plugins/Admin/plugin.py
Normal file
@ -0,0 +1,351 @@
|
||||
###
|
||||
# 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):
|
||||
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]
|
||||
if irc.isChannel(target): # We don't care about nicks.
|
||||
t = time.time() + 30
|
||||
# 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)
|
||||
|
||||
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, it\'s banned me.') % 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 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 the '
|
||||
'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() 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.maxint)
|
||||
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. Must be given in private, in order
|
||||
to protect the secrecy of secret channels.
|
||||
"""
|
||||
L = irc.state.channels.keys()
|
||||
if L:
|
||||
utils.sortBy(ircutils.toLower, L)
|
||||
irc.reply(format('%L', L))
|
||||
else:
|
||||
irc.reply(_('I\'m not currently in any channels.'))
|
||||
channels = wrap(channels, ['private'])
|
||||
|
||||
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(_('That nick is currently banned.'))
|
||||
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):
|
||||
"""[<nick>]
|
||||
|
||||
Changes the bot's nick to <nick>. If no nick is given, returns the
|
||||
bot's current nick.
|
||||
"""
|
||||
if nick:
|
||||
conf.supybot.nick.setValue(nick)
|
||||
irc.queueMsg(ircmsgs.nick(nick))
|
||||
self.pendingNickChanges[irc.getRealIrc()] = irc
|
||||
else:
|
||||
irc.reply(irc.nick)
|
||||
nick = wrap(nick, [additional('nick')])
|
||||
|
||||
@internationalizeDocstring
|
||||
def part(self, irc, msg, args, channel, reason):
|
||||
"""[<channel>] [<reason>]
|
||||
|
||||
Tells the bot to part the list of channels you give it. <channel> is
|
||||
only necessary if you want the bot to part a channel other than the
|
||||
current channel. If <reason> is specified, use it as the part
|
||||
message.
|
||||
"""
|
||||
if channel is None:
|
||||
if irc.isChannel(msg.args[0]):
|
||||
channel = msg.args[0]
|
||||
else:
|
||||
irc.error(Raise=True)
|
||||
try:
|
||||
network = conf.supybot.networks.get(irc.network)
|
||||
network.channels().remove(channel)
|
||||
except KeyError:
|
||||
pass
|
||||
if channel not in irc.state.channels:
|
||||
irc.error(_('I\'m not in %s.') % channel, Raise=True)
|
||||
irc.queueMsg(ircmsgs.part(channel, reason or msg.nick))
|
||||
if msg.nick in irc.state.channels[channel].users:
|
||||
irc.noReply()
|
||||
else:
|
||||
irc.replySuccess()
|
||||
part = wrap(part, [optional('validChannel'), additional('text')])
|
||||
|
||||
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 his 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', (map(repr,ircdb.ignores.hostmasks))))
|
||||
else:
|
||||
irc.reply(_('I\'m not currently globally ignoring anyone.'))
|
||||
list = wrap(list)
|
||||
|
||||
|
||||
Class = Admin
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
127
plugins/Admin/test.py
Normal file
127
plugins/Admin/test.py
Normal file
@ -0,0 +1,127 @@
|
||||
###
|
||||
# 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, '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 testPart(self):
|
||||
def getAfterJoinMessages():
|
||||
m = self.irc.takeMsg()
|
||||
self.assertEqual(m.command, 'MODE')
|
||||
m = self.irc.takeMsg()
|
||||
self.assertEqual(m.command, 'WHO')
|
||||
self.assertError('part #foo')
|
||||
self.assertRegexp('part #foo', 'not in')
|
||||
self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix))
|
||||
getAfterJoinMessages()
|
||||
m = self.getMsg('part #foo')
|
||||
self.assertEqual(m.command, 'PART')
|
||||
self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix))
|
||||
getAfterJoinMessages()
|
||||
m = self.getMsg('part #foo reason')
|
||||
self.assertEqual(m.command, 'PART')
|
||||
self.assertEqual(m.args[0], '#foo')
|
||||
self.assertEqual(m.args[1], 'reason')
|
||||
|
||||
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.nick.setValue(original)
|
||||
|
||||
def testAddCapabilityOwner(self):
|
||||
self.assertError('admin capability add %s owner' % self.nick)
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
||||
|
23
plugins/Alias/README.txt
Normal file
23
plugins/Alias/README.txt
Normal file
@ -0,0 +1,23 @@
|
||||
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.
|
61
plugins/Alias/__init__.py
Normal file
61
plugins/Alias/__init__.py
Normal file
@ -0,0 +1,61 @@
|
||||
###
|
||||
# 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.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
# This is a dictionary mapping supybot.Author instances to lists of
|
||||
# contributions.
|
||||
__contributors__ = {}
|
||||
|
||||
import config
|
||||
import plugin
|
||||
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 # for the tests.
|
||||
|
||||
if world.testing:
|
||||
import test
|
||||
|
||||
Class = plugin.Class
|
||||
configure = config.configure
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
46
plugins/Alias/config.py
Normal file
46
plugins/Alias/config.py
Normal file
@ -0,0 +1,46 @@
|
||||
###
|
||||
# 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 himself 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')
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
117
plugins/Alias/locale/fi.po
Normal file
117
plugins/Alias/locale/fi.po
Normal file
@ -0,0 +1,117 @@
|
||||
# Alias plugin in Limnoria.
|
||||
# Copyright (C) 2011 Limnoria
|
||||
# Mika Suomalainen <mika.henrik.mainio@hotmail.com>, 2011.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot Alias plugin\n"
|
||||
"POT-Creation-Date: 2011-02-26 09:49+CET\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Mika Suomalainen <mika.henrik.mainio@hotmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Finnish\n"
|
||||
"X-Poedit-Country: FINLAND\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 ""
|
||||
"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:164
|
||||
msgid " at least"
|
||||
msgstr "vähintään"
|
||||
|
||||
#: plugin.py:165
|
||||
msgid ""
|
||||
"<an alias,%s %n>\n"
|
||||
"\n"
|
||||
"Alias for %q."
|
||||
msgstr ""
|
||||
"<alias,%s %n>\n"
|
||||
"\n"
|
||||
"Alias %q:lle."
|
||||
|
||||
#: plugin.py:166
|
||||
msgid "argument"
|
||||
msgstr "parametri"
|
||||
|
||||
#: plugin.py:220
|
||||
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:229
|
||||
#: plugin.py:243
|
||||
msgid "There is no such alias."
|
||||
msgstr "Tuollaista aliasta ei ole."
|
||||
|
||||
#: plugin.py:234
|
||||
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:254
|
||||
msgid "That name isn't valid. Try %q instead."
|
||||
msgstr "Tuo nimi ei ole kelvollinen. Yritä sen sijaan %q:ta."
|
||||
|
||||
#: plugin.py:292
|
||||
msgid ""
|
||||
"<name> <alias>\n"
|
||||
"\n"
|
||||
" Defines an alias <name> that executes <alias>. The <alias>\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 <aliaksen>. <Aliaksen>\n"
|
||||
" pitäisi olla tavallinen \"komento parametri [sisäkkäinen komento parametrit]\"\n"
|
||||
" parametrejä aliakselle; ne täytetään ensinmäinen, toinen, jne.\n"
|
||||
" Parametrit. $1, $2, jne. ovat vaadittuja parametrejä. @1, @2,\n"
|
||||
" jne. ovat vapaaehtoisia parametrejä. $* tarkoittaa yksinkertaisesti \"kaikki\n"
|
||||
" jäljellä olevat parametrit,\" ja johon ei voida yhdistää vaihtoehtoisia parametrejä.\n"
|
||||
" "
|
||||
|
||||
#: plugin.py:315
|
||||
msgid ""
|
||||
"<name>\n"
|
||||
"\n"
|
||||
" Removes the given alias, if unlocked.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<nimi>\n"
|
||||
"\n"
|
||||
" Poistaa annetun aliaksen jos se ei ole lukittu.\n"
|
||||
" "
|
||||
|
103
plugins/Alias/locale/fr.po
Normal file
103
plugins/Alias/locale/fr.po
Normal file
@ -0,0 +1,103 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot-fr\n"
|
||||
"POT-Creation-Date: 2011-02-26 09:49+CET\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Valentin Lorentz <progval@gmail.com>\n"
|
||||
"Language-Team: Supybot-fr <progval@gmail.com>\n"
|
||||
"Language: \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"
|
||||
|
||||
#: 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 ""
|
||||
"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:164
|
||||
msgid " at least"
|
||||
msgstr "au moins"
|
||||
|
||||
#: plugin.py:165
|
||||
msgid ""
|
||||
"<an alias,%s %n>\n"
|
||||
"\n"
|
||||
"Alias for %q."
|
||||
msgstr ""
|
||||
"<un alias,%s %n>\n"
|
||||
"\n"
|
||||
"Alias pour %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"
|
||||
"Vérouille un alias pour que personne d'autre ne puisse le changer."
|
||||
|
||||
#: plugin.py:229
|
||||
#: plugin.py:243
|
||||
msgid "There is no such alias."
|
||||
msgstr "Cet alias n'existe pas."
|
||||
|
||||
#: plugin.py:234
|
||||
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:254
|
||||
msgid "That name isn't valid. Try %q instead."
|
||||
msgstr "Ce nom n'est pas valide. Essayez plutôt %q."
|
||||
|
||||
#: plugin.py:292
|
||||
msgid ""
|
||||
"<name> <alias>\n"
|
||||
"\n"
|
||||
" Defines an alias <name> that executes <alias>. The <alias>\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:315
|
||||
msgid ""
|
||||
"<name>\n"
|
||||
"\n"
|
||||
" Removes the given alias, if unlocked.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<nom>\n"
|
||||
"\n"
|
||||
"Supprime l'alias donné, si il n'est pas vérouillé."
|
||||
|
116
plugins/Alias/locale/it.po
Normal file
116
plugins/Alias/locale/it.po
Normal file
@ -0,0 +1,116 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot-fr\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> <alias>\n"
|
||||
"\n"
|
||||
" Defines an alias <name> that executes <alias>. The <alias>\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> <alias>\n"
|
||||
"\n"
|
||||
" Definisce un <nome> che esegue <alias>. <alias> 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"
|
||||
" "
|
||||
|
91
plugins/Alias/messages.pot
Normal file
91
plugins/Alias/messages.pot
Normal file
@ -0,0 +1,91 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2011-02-26 09:49+CET\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: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 ""
|
||||
|
||||
#: plugin.py:164
|
||||
msgid " at least"
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:165
|
||||
msgid ""
|
||||
"<an alias,%s %n>\n"
|
||||
"\n"
|
||||
"Alias for %q."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:166
|
||||
msgid "argument"
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:220
|
||||
#, docstring
|
||||
msgid ""
|
||||
"<alias>\n"
|
||||
"\n"
|
||||
" Locks an alias so that no one else can change it.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:229 plugin.py:243
|
||||
msgid "There is no such alias."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:234
|
||||
#, docstring
|
||||
msgid ""
|
||||
"<alias>\n"
|
||||
"\n"
|
||||
" Unlocks an alias so that people can define new aliases over it.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:254
|
||||
msgid "That name isn't valid. Try %q instead."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:292
|
||||
#, docstring
|
||||
msgid ""
|
||||
"<name> <alias>\n"
|
||||
"\n"
|
||||
" Defines an alias <name> that executes <alias>. The <alias>\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:315
|
||||
#, docstring
|
||||
msgid ""
|
||||
"<name>\n"
|
||||
"\n"
|
||||
" Removes the given alias, if unlocked.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
330
plugins/Alias/plugin.py
Normal file
330
plugins/Alias/plugin.py
Normal file
@ -0,0 +1,330 @@
|
||||
###
|
||||
# Copyright (c) 2002-2004, Jeremiah Fincher
|
||||
# Copyright (c) 2009-2010, James Vega
|
||||
# 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 new
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
from supybot.commands import *
|
||||
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(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 ircutils.isChannel(args[0]):
|
||||
if conf.supybot.reply.requireChannelCommandsToBeSentInChannel():
|
||||
if args[0] != msg.args[0]:
|
||||
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 ircutils.isChannel(msg.args[0]):
|
||||
return msg.args[0]
|
||||
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
|
||||
|
||||
class RecursiveAlias(AliasError):
|
||||
pass
|
||||
|
||||
dollarRe = re.compile(r'\$(\d+)')
|
||||
def findBiggestDollar(alias):
|
||||
dollars = dollarRe.findall(alias)
|
||||
dollars = 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 = map(int, ats)
|
||||
ats.sort()
|
||||
if ats:
|
||||
return ats[-1]
|
||||
else:
|
||||
return 0
|
||||
|
||||
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(msg, args)
|
||||
alias = alias.replace('$channel', channel)
|
||||
tokens = callbacks.tokenize(alias)
|
||||
if biggestDollar or biggestAt:
|
||||
args = getArgs(args, required=biggestDollar, optional=biggestAt,
|
||||
wildcard=wildcard)
|
||||
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)
|
||||
self.Proxy(irc, msg, tokens)
|
||||
flexargs = ''
|
||||
if biggestDollar and (wildcard or biggestAt):
|
||||
flexargs = _(' at least')
|
||||
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):
|
||||
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
|
||||
for (name, alias) in registry._cache.iteritems():
|
||||
name = name.lower()
|
||||
if name.startswith('supybot.plugins.alias.aliases.'):
|
||||
name = name[len('supybot.plugins.alias.aliases.'):]
|
||||
if '.' in name:
|
||||
continue
|
||||
conf.registerGlobalValue(group, name, registry.String('', ''))
|
||||
conf.registerGlobalValue(group.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 (alias, (command, locked, _)) in self.aliases.items():
|
||||
try:
|
||||
self.addAlias(irc, alias, command, locked)
|
||||
except Exception, 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]
|
||||
|
||||
@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.aliases[name][1] = True
|
||||
conf.supybot.plugins.Alias.aliases.get(name).locked.setValue(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.aliases[name][1] = False
|
||||
conf.supybot.plugins.Alias.aliases.get(name).locked.setValue(False)
|
||||
irc.replySuccess()
|
||||
else:
|
||||
irc.error(_('There is no such alias.'))
|
||||
unlock = wrap(unlock, [('checkCapability', 'admin'), 'commandName'])
|
||||
|
||||
_invalidCharsRe = re.compile(r'[\[\]\s]')
|
||||
def addAlias(self, irc, name, alias, lock=False):
|
||||
if self._invalidCharsRe.search(name):
|
||||
raise AliasError, 'Names cannot contain spaces or square brackets.'
|
||||
if '|' in name:
|
||||
raise AliasError, 'Names cannot contain pipes.'
|
||||
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)
|
||||
try:
|
||||
f = makeNewAlias(name, alias)
|
||||
f = new.instancemethod(f, self, Alias)
|
||||
except RecursiveAlias:
|
||||
raise AliasError, 'You can\'t define a recursive alias.'
|
||||
aliasGroup = self.registryValue('aliases', value=False)
|
||||
if name in self.aliases:
|
||||
# We gotta remove it so its value gets updated.
|
||||
aliasGroup.unregister(name)
|
||||
conf.registerGlobalValue(aliasGroup, name, registry.String(alias, ''))
|
||||
conf.registerGlobalValue(aliasGroup.get(name), '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]
|
||||
conf.supybot.plugins.Alias.aliases.unregister(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> <alias>
|
||||
|
||||
Defines an alias <name> that executes <alias>. The <alias>
|
||||
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, 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, e:
|
||||
irc.error(str(e))
|
||||
remove = wrap(remove, ['commandName'])
|
||||
|
||||
|
||||
Class = Alias
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
129
plugins/Alias/test.py
Normal file
129
plugins/Alias/test.py
Normal file
@ -0,0 +1,129 @@
|
||||
###
|
||||
# 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.plugin as plugin
|
||||
|
||||
Alias = plugin.loadPluginModule('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")
|
||||
|
||||
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.failIf('foobar' in cb.aliases)
|
||||
self.assertError('foobar')
|
||||
|
||||
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 testSimpleAliasWithoutArgsImpliesDollarStar(self):
|
||||
self.assertNotError('alias add exo echo')
|
||||
self.assertResponse('exo foo bar baz', 'foo bar baz')
|
||||
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
4
plugins/Anonymous/README.txt
Normal file
4
plugins/Anonymous/README.txt
Normal file
@ -0,0 +1,4 @@
|
||||
Anonymous allows you to send messages anonymously as the bot. If
|
||||
supybot.plugins.Anonymous.allowPrivateTarget is True, you can send messages in query too.
|
||||
|
||||
One usage example is to identify the bot with NickServ if it fails to identify for some reason.
|
61
plugins/Anonymous/__init__.py
Normal file
61
plugins/Anonymous/__init__.py
Normal file
@ -0,0 +1,61 @@
|
||||
###
|
||||
# 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%%"
|
||||
|
||||
# XXX Replace this with an appropriate author or supybot.Author instance.
|
||||
__author__ = supybot.authors.strike
|
||||
|
||||
# This is a dictionary mapping supybot.Author instances to lists of
|
||||
# contributions.
|
||||
__contributors__ = {}
|
||||
|
||||
import config
|
||||
import plugin
|
||||
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:
|
||||
import test
|
||||
|
||||
Class = plugin.Class
|
||||
configure = config.configure
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
65
plugins/Anonymous/config.py
Normal file
65
plugins/Anonymous/config.py
Normal file
@ -0,0 +1,65 @@
|
||||
###
|
||||
# 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 himself 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.registerGlobalValue(conf.supybot.plugins.Anonymous, 'requireRegistration',
|
||||
registry.Boolean(True, _("""Determines whether the bot should require
|
||||
people trying to use this plugin to be registered.""")))
|
||||
conf.registerGlobalValue(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 require
|
||||
targets of the "say" command to be public (i.e., channels). If this is
|
||||
True, the bot will allow people to use the "say" command to send private
|
||||
messages to other users.""")))
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
121
plugins/Anonymous/locale/fi.po
Normal file
121
plugins/Anonymous/locale/fi.po
Normal file
@ -0,0 +1,121 @@
|
||||
# Anonymous plugin in Limnoria.
|
||||
# Copyright (C) 2011 Limnoria
|
||||
# Mika Suomalainen <mika.henrik.mainio@hotmail.com>, 2011.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot Anonymous\n"
|
||||
"POT-Creation-Date: 2011-06-09 18:26+CEST\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Mika Suomalainen <mika.henrik.mainio@hotmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Finnish\n"
|
||||
"X-Poedit-Country: FINLAND\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
|
||||
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 ""
|
||||
"Määrittää vaatiiko botti \n"
|
||||
"\"say\" komennon kohteiden olevan julkisia (esim., kanavia). Jos tämä on\n"
|
||||
" True, botti sallii ihmisten käyttää \"say\" komentoa lähettääkseen yksityisviestejä \n"
|
||||
" toisille käyttäjille."
|
||||
|
||||
#: 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 ""
|
||||
"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:64
|
||||
msgid "You must be in %s to %q in there."
|
||||
msgstr "Sinun täytyy olla kanavalla %s %q sinne."
|
||||
|
||||
#: plugin.py:68
|
||||
msgid "I'm lobotomized in %s."
|
||||
msgstr "Minut on lobotomoitu kanavalla %s."
|
||||
|
||||
#: plugin.py:71
|
||||
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:74
|
||||
msgid "%q cannot be used to send private messages."
|
||||
msgstr "%q:ta ei voi käyttää yksityisviestien lähettämiseen."
|
||||
|
||||
#: 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 ""
|
||||
"<kanava|nimimerkki> <teksti>\n"
|
||||
"\n"
|
||||
" Lähettää <tekstin> <kanavalle|nimimerkille>. <Nimimerkille> voi lähettää vain jos\n"
|
||||
" supybot.plugins.Anonymous.allowPrivateTarget on True.\n"
|
||||
" "
|
||||
|
||||
#: plugin.py:94
|
||||
msgid ""
|
||||
"<channel> <action>\n"
|
||||
"\n"
|
||||
" Performs <action> in <channel>.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<kanava> <toiminto>\n"
|
||||
"\n"
|
||||
"Suorittaa <toiminnon> <kanavalla>.\n"
|
||||
" "
|
||||
|
96
plugins/Anonymous/locale/fr.po
Normal file
96
plugins/Anonymous/locale/fr.po
Normal file
@ -0,0 +1,96 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot-fr\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: Supybot-fr <progval@gmail.com>\n"
|
||||
"Language: \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 /cs register ##fschfsch 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>."
|
||||
|
97
plugins/Anonymous/locale/hu.po
Normal file
97
plugins/Anonymous/locale/hu.po
Normal file
@ -0,0 +1,97 @@
|
||||
# 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"
|
||||
"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."
|
||||
|
118
plugins/Anonymous/locale/it.po
Normal file
118
plugins/Anonymous/locale/it.po
Normal file
@ -0,0 +1,118 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot-fr\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"
|
||||
" "
|
||||
|
95
plugins/Anonymous/messages.pot
Normal file
95
plugins/Anonymous/messages.pot
Normal file
@ -0,0 +1,95 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2011-06-09 18:26+CEST\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 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 ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: plugin.py:64
|
||||
msgid "You must be in %s to %q in there."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:68
|
||||
msgid "I'm lobotomized in %s."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:71
|
||||
msgid "That channel has set its capabilities so as to disallow the use of this plugin."
|
||||
msgstr ""
|
||||
|
||||
#: plugin.py:74
|
||||
msgid "%q cannot be used to send private messages."
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: plugin.py:94
|
||||
#, docstring
|
||||
msgid ""
|
||||
"<channel> <action>\n"
|
||||
"\n"
|
||||
" Performs <action> in <channel>.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
109
plugins/Anonymous/plugin.py
Normal file
109
plugins/Anonymous/plugin.py
Normal file
@ -0,0 +1,109 @@
|
||||
###
|
||||
# Copyright (c) 2005, Daniel DiPaolo
|
||||
# Copyright (c) 2010, James Vega
|
||||
# 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.ircdb as ircdb
|
||||
import supybot.utils as utils
|
||||
from supybot.commands import *
|
||||
import supybot.ircmsgs as ircmsgs
|
||||
import supybot.callbacks as callbacks
|
||||
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'):
|
||||
try:
|
||||
foo = ircdb.users.getUser(msg.prefix)
|
||||
except KeyError:
|
||||
irc.errorNotRegistered(Raise=True)
|
||||
capability = self.registryValue('requireCapability')
|
||||
if capability:
|
||||
if not ircdb.checkCapability(msg.prefix, capability):
|
||||
irc.errorNoCapability(capability, Raise=True)
|
||||
if irc.isChannel(target):
|
||||
if self.registryValue('requirePresenceInChannel', target) 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 action == 'say' and not self.registryValue('allowPrivateTarget'):
|
||||
irc.error(format(_('%q cannot be used to send private messages.'),
|
||||
action),
|
||||
Raise=True)
|
||||
|
||||
@internationalizeDocstring
|
||||
def say(self, irc, msg, args, target, text):
|
||||
"""<channel|nick> <text>
|
||||
|
||||
Sends <text> to <channel|nick>. Can only send to <nick> if
|
||||
supybot.plugins.Anonymous.allowPrivateTarget is True.
|
||||
"""
|
||||
self._preCheck(irc, msg, target, 'say')
|
||||
self.log.info('Saying %q to %s due to %s.',
|
||||
text, target, msg.prefix)
|
||||
irc.queueMsg(ircmsgs.privmsg(target, text))
|
||||
irc.noReply()
|
||||
say = wrap(say, [first('nick', 'inChannel'), 'text'])
|
||||
|
||||
@internationalizeDocstring
|
||||
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.queueMsg(ircmsgs.action(channel, text))
|
||||
irc.noReply()
|
||||
do = wrap(do, ['inChannel', 'text'])
|
||||
Anonymous = internationalizeDocstring(Anonymous)
|
||||
|
||||
Class = Anonymous
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
63
plugins/Anonymous/test.py
Normal file
63
plugins/Anonymous/test.py
Normal file
@ -0,0 +1,63 @@
|
||||
###
|
||||
# Copyright (c) 2005, Daniel DiPaolo
|
||||
# Copyright (c) 2010, James Vega
|
||||
# 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 AnonymousTestCase(ChannelPluginTestCase):
|
||||
plugins = ('Anonymous',)
|
||||
def testSay(self):
|
||||
self.assertError('anonymous say %s I love you!' % self.channel)
|
||||
self.assertError('anonymous say %s I love you!' % self.nick)
|
||||
origreg = conf.supybot.plugins.Anonymous.requireRegistration()
|
||||
origpriv = conf.supybot.plugins.Anonymous.allowPrivateTarget()
|
||||
try:
|
||||
conf.supybot.plugins.Anonymous.requireRegistration.setValue(False)
|
||||
m = self.assertNotError('anonymous say %s foo!' % self.channel)
|
||||
self.failUnless(m.args[1] == 'foo!')
|
||||
conf.supybot.plugins.Anonymous.allowPrivateTarget.setValue(True)
|
||||
m = self.assertNotError('anonymous say %s foo!' % self.nick)
|
||||
self.failUnless(m.args[1] == 'foo!')
|
||||
finally:
|
||||
conf.supybot.plugins.Anonymous.requireRegistration.setValue(origreg)
|
||||
conf.supybot.plugins.Anonymous.allowPrivateTarget.setValue(origpriv)
|
||||
|
||||
def testAction(self):
|
||||
m = self.assertError('anonymous do %s loves you!' % self.channel)
|
||||
try:
|
||||
orig = conf.supybot.plugins.Anonymous.requireRegistration()
|
||||
conf.supybot.plugins.Anonymous.requireRegistration.setValue(False)
|
||||
m = self.assertNotError('anonymous do %s loves you!'%self.channel)
|
||||
self.assertEqual(m.args, ircmsgs.action(self.channel,
|
||||
'loves you!').args)
|
||||
finally:
|
||||
conf.supybot.plugins.Anonymous.requireRegistration.setValue(orig)
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
3
plugins/AutoMode/README.txt
Normal file
3
plugins/AutoMode/README.txt
Normal file
@ -0,0 +1,3 @@
|
||||
This plugin automaticly voices/halfops/ops users with #channel,<voice/halfop/op> capability
|
||||
when they join to the channel.
|
||||
It will also ban automaticly everyone who is in channel ban list ( @channel ban list ).
|
62
plugins/AutoMode/__init__.py
Normal file
62
plugins/AutoMode/__init__.py
Normal file
@ -0,0 +1,62 @@
|
||||
###
|
||||
# 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
|
||||
|
||||
# This is a dictionary mapping supybot.Author instances to lists of
|
||||
# contributions.
|
||||
__contributors__ = {}
|
||||
|
||||
import config
|
||||
import plugin
|
||||
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:
|
||||
import test
|
||||
|
||||
Class = plugin.Class
|
||||
configure = config.configure
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
76
plugins/AutoMode/config.py
Normal file
76
plugins/AutoMode/config.py
Normal file
@ -0,0 +1,76 @@
|
||||
###
|
||||
# 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 himself 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(True, _("""Determines whether this plugin will automode
|
||||
owners even if they don't have op/halfop/voice/whatever capability.""")))
|
||||
conf.registerChannelValue(AutoMode, 'fallthrough',
|
||||
registry.Boolean(False, _("""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(True, _("""Determines whether the bot will automatically
|
||||
op people with the <channel>,op capability when they join the channel.
|
||||
""")))
|
||||
conf.registerChannelValue(AutoMode, 'halfop',
|
||||
registry.Boolean(True, _("""Determines whether the bot will automatically
|
||||
halfop people with the <channel>,halfop capability when they join the
|
||||
channel.""")))
|
||||
conf.registerChannelValue(AutoMode, 'voice',
|
||||
registry.Boolean(True, _("""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.""")))
|
||||
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
90
plugins/AutoMode/locale/fi.po
Normal file
90
plugins/AutoMode/locale/fi.po
Normal file
@ -0,0 +1,90 @@
|
||||
# AutoMode plugin in Limnoria.
|
||||
# Copyright (C) 2011 Limnoria
|
||||
# Mika Suomalainen <mika.henrik.mainio@hotmail.com>, 2011.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot AutoMode\n"
|
||||
"POT-Creation-Date: 2011-06-27 12:02+CEST\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Mika Suomalainen <mika.henrik.mainio@hotmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Finnish\n"
|
||||
"X-Poedit-Country: FINLAND\n"
|
||||
|
||||
#: config.py:46
|
||||
msgid ""
|
||||
"Determines whether this plugin is enabled.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"Määrittää onko tämä lisäosa käytössä.\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 ""
|
||||
"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 \"fall\n"
|
||||
" through\" to halfop/voicing when auto-opping is turned off but\n"
|
||||
" auto-halfopping/voicing are turned on."
|
||||
msgstr ""
|
||||
"Määrittää \"siirtyykö\" botti\n"
|
||||
" halfoppaamiseen/voicen antamiseen kun automaatti-oppaaminen on poistettu käytöstä, mutta\n"
|
||||
" automaattinen-halfoppaaminen/voicen antaminen on käytössä."
|
||||
|
||||
#: 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 ""
|
||||
"Määrittää oppaako botti\n"
|
||||
" ihmiset #<kanava>,op valtuudella automaattisesti, kun he liittyvät kanavalle.\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 ""
|
||||
"Määrittää halfoppaako botti automaattisesti, kun\n"
|
||||
" ihmiset #<kanava>,halfop valtuudella, kun he liittyvät \n"
|
||||
" kanavalle."
|
||||
|
||||
#: 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 ""
|
||||
"Määrittää antaako botti automaattisesti äänen\n"
|
||||
" ihmisille, joilla on #<kanava>,voice valtuus kun he liittyvät\n"
|
||||
" kanavalle."
|
||||
|
||||
#: config.py:68
|
||||
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:71
|
||||
msgid ""
|
||||
"Determines how many seconds the bot\n"
|
||||
" will automatically ban a person when banning."
|
||||
msgstr ""
|
||||
"Määrittää kuinka moneksi sekuntiksi botti\n"
|
||||
"antaa porttikiellon henkilöle, kun ollaan antamassa porttikieltoja."
|
||||
|
67
plugins/AutoMode/locale/fr.po
Normal file
67
plugins/AutoMode/locale/fr.po
Normal file
@ -0,0 +1,67 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot-fr\n"
|
||||
"POT-Creation-Date: 2011-06-27 12:02+CEST\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Valentin Lorentz <progval@gmail.com>\n"
|
||||
"Language-Team: Supybot-fr <progval@gmail.com>\n"
|
||||
"Language: \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: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 \"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:56
|
||||
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:60
|
||||
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:64
|
||||
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:68
|
||||
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:71
|
||||
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."
|
||||
|
85
plugins/AutoMode/locale/it.po
Normal file
85
plugins/AutoMode/locale/it.po
Normal file
@ -0,0 +1,85 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Supybot-fr\n"
|
||||
"POT-Creation-Date: 2011-02-26 09:49+CET\n"
|
||||
"PO-Revision-Date: 2011-08-10 00: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 applica a un utente."
|
||||
|
69
plugins/AutoMode/messages.pot
Normal file
69
plugins/AutoMode/messages.pot
Normal file
@ -0,0 +1,69 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2011-06-27 12:02+CEST\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 \"fall\n"
|
||||
" through\" to halfop/voicing when auto-opping is turned off but\n"
|
||||
" auto-halfopping/voicing are turned on."
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: config.py:68
|
||||
msgid ""
|
||||
"Determines whether the bot will automatically\n"
|
||||
" ban people who join the channel and are on the banlist."
|
||||
msgstr ""
|
||||
|
||||
#: config.py:71
|
||||
msgid ""
|
||||
"Determines how many seconds the bot\n"
|
||||
" will automatically ban a person when banning."
|
||||
msgstr ""
|
||||
|
94
plugins/AutoMode/plugin.py
Normal file
94
plugins/AutoMode/plugin.py
Normal file
@ -0,0 +1,94 @@
|
||||
###
|
||||
# Copyright (c) 2004, Jeremiah Fincher
|
||||
# Copyright (c) 2009, James Vega
|
||||
# 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 time
|
||||
|
||||
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):
|
||||
def doJoin(self, irc, msg):
|
||||
channel = msg.args[0]
|
||||
if ircutils.strEqual(irc.nick, msg.nick):
|
||||
return
|
||||
if not self.registryValue('enable', channel):
|
||||
return
|
||||
fallthrough = self.registryValue('fallthrough', channel)
|
||||
def do(type):
|
||||
cap = ircdb.makeChannelCapability(channel, type)
|
||||
if ircdb.checkCapability(msg.prefix, cap,
|
||||
ignoreOwner=not self.registryValue('owner')):
|
||||
if self.registryValue(type, channel):
|
||||
self.log.info('Sending auto-%s of %s in %s.',
|
||||
type, msg.prefix, channel)
|
||||
msgmaker = getattr(ircmsgs, type)
|
||||
irc.queueMsg(msgmaker(channel, msg.nick))
|
||||
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, refusing to fall '
|
||||
'through.', msg.prefix, cap, type, channel)
|
||||
raise Continue
|
||||
try:
|
||||
do('op')
|
||||
if 'h' in irc.state.supported['prefix']:
|
||||
do('halfop')
|
||||
do('voice')
|
||||
except Continue:
|
||||
return
|
||||
c = ircdb.channels.getChannel(channel)
|
||||
if c.checkBan(msg.prefix) and self.registryValue('ban', channel):
|
||||
period = self.registryValue('ban.period', channel)
|
||||
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))
|
||||
|
||||
|
||||
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
Loading…
x
Reference in New Issue
Block a user