mirror of
https://github.com/Mikaela/Limnoria.git
synced 2025-04-21 07:17:52 +02:00
Compare commits
No commits in common. "gh-pages" and "v0.80.0" have entirely different histories.
3
.cvsignore
Normal file
3
.cvsignore
Normal file
@ -0,0 +1,3 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
@ -1,8 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +0,0 @@
|
||||
* text=auto eol=lf
|
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
|
32
.gitignore
vendored
32
.gitignore
vendored
@ -1,32 +0,0 @@
|
||||
*.mo
|
||||
*.py[cdo]
|
||||
*~
|
||||
.*.swp
|
||||
.swp
|
||||
MANIFEST
|
||||
backup/
|
||||
build/
|
||||
debian/compat
|
||||
debian/files
|
||||
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"
|
7
ACKS
Normal file
7
ACKS
Normal file
@ -0,0 +1,7 @@
|
||||
johhnyace, who gave me the modem that helped me tremendously in development.
|
||||
bwp, who rewrote the Http.weather command, and also hosted the example
|
||||
"supybot" in #supybot on OFTC and Freenode for quite some time.
|
||||
sweede, for hosting the "main" supybot for awhile.
|
||||
HostPC.com, for hosting the current example "supybot" and for graciously
|
||||
providing DNS services and email.
|
||||
|
10
BUGS
Normal file
10
BUGS
Normal file
@ -0,0 +1,10 @@
|
||||
We're sure there are tons of them. When you find them, send them to us and
|
||||
we'll fix them ASAP. We'd love to have a bugless bot someday...
|
||||
|
||||
Incidentally, the way to "send the bugs to us" is via SourceForge:
|
||||
<http://sourceforge.net/tracker/?atid=489447&group_id=58965&func=browse>
|
||||
|
||||
Known bugs that probably won't get fixed:
|
||||
|
||||
BadWords' outFilter filters colors. It's not a high priority to get
|
||||
that fixed.
|
67
DEVS
Normal file
67
DEVS
Normal file
@ -0,0 +1,67 @@
|
||||
These are the developers of Supybot, in approximate order of ____.
|
||||
|
||||
Jeremy Fincher (jemfinch) is a Computer Science (and possibly
|
||||
philosophy) student at The Ohio State University. He spends most of
|
||||
his free time with his girlfriend Meg, but also...well, there's not
|
||||
much also :) He hopes to graduate with good enough grades to go to
|
||||
law school or seminary at some point in the future. He initially
|
||||
wrote the majority of the Supybot framework and standard plugins,
|
||||
though he's been trying to slowly phase himself out of plugin-writing
|
||||
and more into framework-enhancement. Rather than list the specific
|
||||
things he's done, you can just assume that if someone else isn't
|
||||
claiming it, it was probably done by him.
|
||||
|
||||
Daniel DiPaolo (Strike/ddipaolo) is a lazy Texan punk with a job as an IT
|
||||
monkey who spends his free time coding, playing ultimate frisbee, and arguing
|
||||
pointless things on the internet. As far as the bot goes, he's mainly a
|
||||
plugin developer but he has helped here and there with various under-the-hood
|
||||
things and is one of the few people (other than jemfinch) who understands the
|
||||
inner workings of Supybot. His biggest plugin contribution (in terms of sheer
|
||||
lines of code) has been the MoobotFactoids plugin and all the workd involved
|
||||
in getting that plugin to work, but he has also helped with a lot of testing,
|
||||
debugging, and brainstorming. He also wrote the Dunno, News, and Todo plugins
|
||||
and is responsible for a significant amount of code in the Poll, Debian,
|
||||
QuoteGrabs, Karma, and ChannelDB plugins.
|
||||
|
||||
James Vega (jamessan) is an Electrical Engineering/Computer Science student at
|
||||
Northeastern University. He wrote the Sourceforge and Ebay plugins as well as
|
||||
the first incarnation of the Babelfish commands and most of Amazon. He has
|
||||
also performed a significant amount of maintenance and refactoring of plugins
|
||||
in general. Some of the plugins that were affected the most are Debian,
|
||||
FunDB, Gameknot, Http, Note, and Quote. All of the link snarfers, save
|
||||
Bugzilla's, were also written by jamessan. His meddlings have prompted the
|
||||
implementation of Toggleables, which eventually evolved to Configurables and
|
||||
then to the current registry system. As well as being the current webmaster,
|
||||
he also overhauled the tool which is used to generate the site's HTML
|
||||
documentation for Supybot and setup the weekly creation of CVS snapshots.
|
||||
|
||||
Brett Kelly (inkedmn) is a hobbyist (soon to be professional :)) coder
|
||||
from southern California who enjoys collecting tattoos (on his body) and
|
||||
drinking coffee with his wife. He initially wrote the Note plugin as well
|
||||
as several commands in the Http plugin.
|
||||
|
||||
Vincent Foley-Bourgon is a recently-graduated student from Quebec who
|
||||
enjoys anything pointless, unprofitable, and generally useless. Recently
|
||||
returning to Supybot development (after writing the original freshmeat
|
||||
command for the Http plugin) he wrote the entire Hangman infrastructure
|
||||
for the Words plugin.
|
||||
|
||||
Daniel Berlin is a soon to be lawyer with a background in computer science
|
||||
and compilers. He enjoys selling crack to young homeless orphans, and works
|
||||
on Supybot when he's not lawyering or hacking on gcc.
|
||||
|
||||
Keith Jones (kmj) dislikes talking about himself in the third person. He
|
||||
has an MS in Computer Science, and has decided to see how long he can go
|
||||
without using that in any kind of professional capacity. To that end he
|
||||
is currently taking some math classes and applying to math Ph.D programs
|
||||
so some day he can be a professor at a college near a snowy mountain where
|
||||
he will ski every morning. So far, he hasn't done much for the project
|
||||
except squeeze Doug Bell's GPL'd unit conversion code into a supybot plugin.
|
||||
|
||||
Stéphan Kochen (G-LiTe) is a lazy (soon to be) computer science student.
|
||||
He's usually just freelancing and submitting patches here and there when he
|
||||
bumps into a bug that bothers him, but Supybot is one of the first projects
|
||||
he semi-actively tries to work on. ;) His biggest contribution has been the
|
||||
refactoring of the supybot-wizard script to use the registry, though he also
|
||||
likes to track down those nasty obscure bugs which haunt many of our fine
|
||||
applications these days.
|
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
|
110
INSTALL
Normal file
110
INSTALL
Normal file
@ -0,0 +1,110 @@
|
||||
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. First let's start with the parts that are common to all
|
||||
OSes.
|
||||
|
||||
|
||||
###
|
||||
# COMMON:
|
||||
###
|
||||
|
||||
First things first: Supybot *requires* at least Python 2.3. There ain't
|
||||
no getting around it. We do not require any version greater than 2.3,
|
||||
but we will be compatible with any version of Python >= 2.3. If you're
|
||||
a Python developer, you probably know how superior 2.3 is to previous
|
||||
incarnations. If you're not, just think about the difference between a
|
||||
bowl of plain vanilla ice cream and a banana split. Or something like
|
||||
that. Either way, *We're* Python developers and we like banana splits.
|
||||
So, be sure to install python2.3 or greater before continuing. You can
|
||||
get it from http://www.python.org/
|
||||
|
||||
For more information and help on how to use Supybot, checkout
|
||||
the documents under docs/ (especially GETTING_STARTED and CONFIGURATION).
|
||||
Our forums (http://forums.supybot.org/) may also be of use, especially
|
||||
the "Tips and Tricks" topic under "Supybot User Discussion".
|
||||
|
||||
|
||||
###
|
||||
# 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.3/distutils directory (assuming /usr/lib/python2.3 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 addendum near the end of this document.
|
||||
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.
|
||||
|
||||
So after running supybot-wizard, you've got a nice registry file
|
||||
handy. If you're not satisfied with your answers to any of the
|
||||
questions you were asked, feel free to run the program again until
|
||||
you're satisfied with all your answers. Once you're satisfied,
|
||||
though, run the "supybot" program with the registry file you created
|
||||
as an argument. This will start the bot; unless you turned off
|
||||
logging to stdout, you'll see some nice log messages describing what
|
||||
the bot is doing at any particular moment; it may pause for a
|
||||
significant amount of time after saying "Connecting to ..." while the
|
||||
server tries to check its ident.
|
||||
|
||||
|
||||
###
|
||||
# Windows:
|
||||
###
|
||||
|
||||
*** 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=%PATH%;C:\Python23\
|
||||
|
||||
You should now be able to type "python" to start the Python
|
||||
interpreter (CTRL-Z and Return to exit). 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:\Python23\. If you want to install
|
||||
Supybot to a non-default location, see the addendum near the end of
|
||||
this document. You will now have several new programs installed in
|
||||
C:\Python23\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.
|
||||
|
||||
Now you will want to run "python C:\Python23\Scripts\supybot-wizard"
|
||||
to generate a registry file for your bot. So after running
|
||||
supybot-wizard, you've got a nice registry file handy. If you're not
|
||||
satisfied with your answers to any of the questions you were asked,
|
||||
feel free to run the program again until you're satisfied with all
|
||||
your answers. Once you're satisfied, though, run "python
|
||||
C:\Python23\Scripts\supybot botname.conf". This will start the bot;
|
||||
unless you turned off logging to stdout, you'll see some nice log
|
||||
messages describing what the bot is doing at any particular moment; it
|
||||
may pause for a significant amount of time after saying "Connecting
|
||||
to ..." while the server tries to check its ident.
|
||||
|
||||
###
|
||||
# Addenda
|
||||
###
|
||||
Local installs: See this forum post: http://tinyurl.com/2tb37
|
28
LICENSE
Normal file
28
LICENSE
Normal file
@ -0,0 +1,28 @@
|
||||
Copyright (c) 2002-2004 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.
|
38
README
Normal file
38
README
Normal file
@ -0,0 +1,38 @@
|
||||
EVERYONE:
|
||||
---------
|
||||
Read LICENSE. It's a 2-clause BSD license, but you should read it anyway.
|
||||
|
||||
|
||||
USERS:
|
||||
------
|
||||
If you're upgrading, read RELNOTES. If you're new to Supybot,
|
||||
read docs/GETTING_STARTED for an introduction to the bot, and read
|
||||
docs/CAPABILITIES to see how to use capabilities to your greater
|
||||
benefit.
|
||||
|
||||
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:\Python23\python C:\Python23\Scripts\supybot-wizard
|
||||
|
||||
|
||||
DEVELOPERS:
|
||||
-----------
|
||||
Read OVERVIEW to see what the modules are used for. Read PLUGIN-EXAMPLE
|
||||
to see some examples of callbacks and commands written for the bot.
|
||||
Read INTERFACES to see what kinds of objects you'll be dealing with.
|
||||
Read STYLE if you wish to contribute; all contributed code must meet
|
||||
the guidelines set forth there.
|
||||
|
||||
Be sure to run "test/test.py --help" to see what options are available
|
||||
to you when testing. Windows users in particular should be sure to
|
||||
exclude test_Debian.py and test_Unix.py.
|
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
|
257
RELNOTES
Normal file
257
RELNOTES
Normal file
@ -0,0 +1,257 @@
|
||||
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)
|
40
TODO
Normal file
40
TODO
Normal file
@ -0,0 +1,40 @@
|
||||
Roughly in order of precedence (the closer to the front of the file,
|
||||
the more likely it'll be done before next release):
|
||||
|
||||
* We should have a channel-global value in ircdb.IrcChannel for
|
||||
ignoring unregistered users.
|
||||
|
||||
* We need to note when bans expire on a channel and send the unban,
|
||||
that way plugins can use the ircdb ban stuff and not worry about
|
||||
sending or scheduling unbans.
|
||||
|
||||
* We should probably add a "hello" command to make things more
|
||||
compatible with Eggdrop, since we've been replacing many eggdrop
|
||||
bots lately.
|
||||
|
||||
* We should be able to set the log level for plugins individually.
|
||||
|
||||
* Rbot has a "remind" plugin that seems pretty cool, we should get
|
||||
one as well. It might do well as a command in the Later plugin.
|
||||
|
||||
* MozBot has a "list" plugin that seems pretty cool, we should get
|
||||
one as well.
|
||||
|
||||
Problems that involve a lot of tedious minutiae, but really need to
|
||||
be done at some point:
|
||||
|
||||
Hard problems that won't get done until someone really wants to have
|
||||
some fun:
|
||||
|
||||
* Redundant relaying -- having more than one bot handle relaying,
|
||||
where a secondary one will automatically take over if the first
|
||||
becomes incapacitated.
|
||||
|
||||
* Have the bot detect when other bots are in the channel responding
|
||||
to the same prefix character, and do something based on that.
|
||||
|
||||
* Unicode support. Basically, we'd have to have a way to set the
|
||||
encoding for the server, and use unicode throughout internally.
|
||||
The real issue is how much it would affect the current code to
|
||||
switch it over to unicode, and what kind of burden it would put on
|
||||
plugin authors to deal with that issue.
|
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;
|
||||
}
|
||||
}
|
63
debian/changelog
vendored
Normal file
63
debian/changelog
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
supybot (0.79.9999+0.80.0pre2-1) unstable; urgency=high
|
||||
|
||||
* New upstream
|
||||
* Urgency high because it still fixes previous bugs.
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Fri, 17 Sep 2004 11:18:43 -0400
|
||||
|
||||
supybot (0.79.9999-1) unstable; urgency=high
|
||||
|
||||
* New upstream
|
||||
* Urgency high because it still fixes previous bugs.
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Mon, 6 Sep 2004 15:40:13 -0400
|
||||
|
||||
supybot (0.79.999-1) unstable; urgency=high
|
||||
|
||||
* New upstream
|
||||
* Urgency high because of a bug in the Ebay plugin that would hang
|
||||
the bot.
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Tue, 31 Aug 2004 21:58:59 -0400
|
||||
|
||||
supybot (0.79.99-1) unstable; urgency=high
|
||||
|
||||
* New upstream
|
||||
* Urgency high as 0.79.9 had a serious bug in src/callbacks.py
|
||||
* Added a watch file
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Mon, 30 Aug 2004 09:20:48 -0400
|
||||
|
||||
supybot (0.79.9-1) unstable; urgency=high
|
||||
|
||||
* New upstream (Closes: #268311)
|
||||
* New maintainer: James Vega <vega.james@gmail.com>
|
||||
* Urgency high to get the RC fix into Sarge (hopefully)
|
||||
* debian/control:
|
||||
+ Downgrade Recommends: python-sqlite to Suggests: python-sqlite
|
||||
+ Update Build-Depends: cdbs, debhelper (>= 4.1.67)
|
||||
+ Bump Standard-Version to 3.6.1.0, no changes necessary
|
||||
* Convert to cdbs
|
||||
|
||||
-- James Vega <vega.james@gmail.com> Thu, 26 Aug 2004 17:28:03 -0400
|
||||
|
||||
supybot (0.77.2-1) unstable; urgency=low
|
||||
|
||||
* New upstream
|
||||
|
||||
-- Jonathan Hseu <vomjom@debian.org> Sun, 18 Apr 2004 02:58:25 -0500
|
||||
|
||||
supybot (0.77.1-1) unstable; urgency=low
|
||||
|
||||
* New upstream
|
||||
* Add python-dev as a build-dep (closes: Bug#242953)
|
||||
|
||||
-- Jonathan Hseu <vomjom@debian.org> Sat, 10 Apr 2004 13:06:38 -0500
|
||||
|
||||
supybot (0.77.0-1) unstable; urgency=low
|
||||
|
||||
* Initial Release.
|
||||
* Paste LICENSE file into copyright
|
||||
|
||||
-- Jonathan Hseu <vomjom@debian.org> Tue, 10 Mar 2004 00:59:46 -0600
|
||||
|
1
debian/compat
vendored
Normal file
1
debian/compat
vendored
Normal file
@ -0,0 +1 @@
|
||||
4
|
21
debian/control
vendored
Normal file
21
debian/control
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
Source: supybot
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: James Vega <vega.james@gmail.com>
|
||||
Build-Depends: debhelper (>= 4.1.67), cdbs, python (>= 2.3), python-dev (>= 2.3)
|
||||
Standards-Version: 3.6.1.0
|
||||
|
||||
Package: supybot
|
||||
Architecture: all
|
||||
Depends: ${python:Depends}
|
||||
Suggests: python-twisted, python-sqlite
|
||||
Description: robust and user friendly Python IRC bot
|
||||
Supybot is a robust (it doesn't crash), user friendly (it's easy
|
||||
to configure) and programmer friendly (plugins are *extremely*
|
||||
easy to write) Python IRC bot. It aims to be an adequate
|
||||
replacement for most existing IRC bots. It includes a very
|
||||
flexible and powerful ACL system for controlling access to
|
||||
commands, as well as more than 50 builtin plugins providing
|
||||
around 400 actual commands.
|
||||
.
|
||||
Homepage: http://supybot.sourceforge.net/
|
53
debian/copyright
vendored
Normal file
53
debian/copyright
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
This package was debianized by Jonathan Hseu <vomjom@debian.org> on
|
||||
Tue, 3 Feb 2004 21:45:46 -0600.
|
||||
|
||||
It was downloaded from http://supybot.sourceforge.net/
|
||||
|
||||
Upstream Authors:
|
||||
|
||||
/usr/share/doc/supybot/DEVS includes the names and descriptions of the
|
||||
developers.
|
||||
|
||||
Copyright:
|
||||
|
||||
Files located in /usr/lib/python2.3/site-packages/supybot/others/ have their
|
||||
own respective licenses, which are either commented at the top of the file, or
|
||||
are held in the __license__ variable.
|
||||
|
||||
The licenses in that directory are either:
|
||||
BSD-style, GPL, or the Python License
|
||||
The first two can be found in /usr/share/common-licenses/
|
||||
|
||||
The Python License can be found in:
|
||||
/usr/share/doc/python/copyright
|
||||
|
||||
The copyright for all other files are as follows (BSD-style):
|
||||
|
||||
Copyright (c) 2002, 2003, 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 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 remains subject to its associated license.
|
15
debian/docs
vendored
Normal file
15
debian/docs
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
ACKS
|
||||
BUGS
|
||||
DEVS
|
||||
README
|
||||
RELNOTES
|
||||
TODO
|
||||
docs/CAPABILITIES
|
||||
docs/CONFIGURATION
|
||||
docs/FAQ
|
||||
docs/GETTING_STARTED
|
||||
docs/HACKING
|
||||
docs/INTERFACES
|
||||
docs/OVERVIEW
|
||||
docs/PLUGIN-EXAMPLE
|
||||
docs/STYLE
|
6
debian/rules
vendored
Executable file
6
debian/rules
vendored
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/make -f
|
||||
include /usr/share/cdbs/1/rules/debhelper.mk
|
||||
include /usr/share/cdbs/1/class/python-distutils.mk
|
||||
|
||||
DEB_INSTALL_MANPAGES_supybot := docs/man/supybot-adduser.1 docs/man/supybot-newplugin.1\
|
||||
docs/man/supybot.1 docs/man/supybot-wizard.1
|
6
debian/watch
vendored
Normal file
6
debian/watch
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Example watch control file for uscan
|
||||
# Rename this file to "watch" and then you can run the "uscan" command
|
||||
# to check for upstream updates and more.
|
||||
# Site Directory Pattern Version Script
|
||||
version=2
|
||||
http://osdn.dl.sourceforge.net/supybot/Supybot-(\d.\d\d.\d+(.\d+)?)\.tar\.gz
|
118
docs/CAPABILITIES
Normal file
118
docs/CAPABILITIES
Normal file
@ -0,0 +1,118 @@
|
||||
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.
|
||||
|
||||
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 scene 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" 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.
|
||||
|
||||
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 returns right then and
|
||||
there, completely ignoring the fact that the user issued that command
|
||||
to it. 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 returns right then and there
|
||||
and doesn't even think about responding to the bot. If neither of
|
||||
these anticapabilities are present, then the bot just responds to the
|
||||
user like normal.
|
||||
|
||||
From a programming perspective, capabilties are easy to use and
|
||||
flexible. Any command can check if a user has any capability, even
|
||||
ones not thought of when the bot was originally written.
|
||||
Commands/Callbacks can 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 end-user perspective, capabilities remove a lot of the mystery
|
||||
and esotery of bot control, in addition to giving the user absolutely
|
||||
finegrained control over what users are allowed to do with the bot.
|
||||
Additionally, defaults can be set by the end-user 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!
|
||||
|
||||
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.
|
||||
|
||||
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, globally enable/disable commands, cause the
|
||||
bot to ignore a given user, set the prefixchar, report bugs, 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 Math.icalc,
|
||||
which potentially could 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 Utilties.re,
|
||||
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]'
|
||||
|
||||
Other plugins may require different capabilities; the Factoids plugin
|
||||
requires #channel,factoids, the Topic plugin requires #channel,topic,
|
||||
etc.
|
174
docs/CONFIGURATION
Normal file
174
docs/CONFIGURATION
Normal file
@ -0,0 +1,174 @@
|
||||
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, go ahead
|
||||
and read our GETTING_STARTED document before this one.
|
||||
|
||||
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.
|
||||
|
||||
Using the Config plugin, you can list the 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> @capabilities, @commands, @databases, @debug, @directories, @drivers,
|
||||
@log, @networks, @nick, @plugins, @protocols, @replies, @reply,
|
||||
alwaysJoinOnInvite, channels, defaultIgnore, defaultSocketTimeout,
|
||||
externalIP, flush, followIdentificationThroughNickChanges,
|
||||
humanTimestampFormat, 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." appended on to the front of 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.
|
||||
|
||||
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 the bot was running in). 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.
|
||||
|
||||
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?
|
||||
|
||||
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|lambda> @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.maz, and
|
||||
supybot.plugins.Relay.topicSync
|
||||
|
||||
Sure, it showed up 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 you have 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.
|
||||
|
||||
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 databases 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, and no
|
||||
automatic flushing will occur.
|
||||
|
||||
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:
|
||||
|
||||
config channel supybot.reply.whenAddressedBy.chars !
|
||||
|
||||
That'll set the prefix chars in the channel that message is sent in to
|
||||
!. Voila, channel-specific values! Also, note that when using the
|
||||
Config plugin's list command, channel-specific values are preceded by
|
||||
a '#' character to indicate such.
|
||||
|
||||
Anyway, that's about it for configuration. Have fun, and enjoy your
|
||||
configurable bot!
|
25
docs/DocBook/Makefile
Normal file
25
docs/DocBook/Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
JADE=/usr/bin/jade
|
||||
JADETEX=/usr/bin/jadetex
|
||||
DVIPDF=/usr/bin/dvipdfm
|
||||
HTMLSTYLESHEET=/usr/share/sgml/docbook/stylesheet/dsssl/modular/html/docbook.dsl
|
||||
PRINTSTYLESHEET=/usr/share/sgml/docbook/stylesheet/dsssl/modular/print/docbook.dsl
|
||||
|
||||
html: example.sgml capabilities.sgml
|
||||
$(JADE) -t xml -d $(HTMLSTYLESHEET) $<
|
||||
|
||||
example.dvi: example.sgml
|
||||
$(JADE) -t tex -d $(PRINTSTYLESHEET) $<
|
||||
$(JADETEX) $(addsuffix .tex, $(basename $<))
|
||||
|
||||
example.pdf: example.dvi
|
||||
$(DVIPDF) -o $(addsuffix .pdf, $(basename $<)) $<
|
||||
|
||||
capabilities.dvi: capabilities.sgml
|
||||
$(JADE) -t tex -d $(PRINTSTYLESHEET) $<
|
||||
$(JADETEX) $(addsuffix .tex, $(basename $<))
|
||||
|
||||
capabilities.pdf: capabilities.dvi
|
||||
$(DVIPDF) -o $(addsuffix .pdf, $(basename $<)) $<
|
||||
|
||||
clean:
|
||||
rm -f *.html
|
177
docs/DocBook/README.DocBook
Normal file
177
docs/DocBook/README.DocBook
Normal file
@ -0,0 +1,177 @@
|
||||
The Official Supybot DocBook Metadocumentation
|
||||
(or, How Does One SGML File Turn Into All Those Document Formats?)
|
||||
|
||||
Okay, though this isn't the case yet, ideally all of Supybot's documentation
|
||||
can and will be done using DocBook and DocBook-related tools as well as a few
|
||||
custom extensions that I've written.
|
||||
|
||||
- How does DocBook work?
|
||||
First things first, you have to understand sort of how DocBook works in order
|
||||
to figure out how our documentation gets generated from just one file. What
|
||||
DocBook is, basically, is just a DTD (Document Type Definition). What that
|
||||
means is that it simply specifies how a document can be structured and still be
|
||||
considered a valid document by placing restrictions on what elements go where.
|
||||
It's a popular DTD because it is structured very well and it's not only fairly
|
||||
generic, but it also has nice elements that make documenting things (such as
|
||||
supybot) rather easy. It focuses on structure and content instead of
|
||||
presentation which is what makes it nice for writing things which are output
|
||||
format agnostic.
|
||||
|
||||
So, let's say we've written a proper DocBook document, now what? Well, using
|
||||
an output formatting tool and a stylesheet, you create whatever form of output
|
||||
you want. What we use for producing the outptut is jade, and DocBook comes
|
||||
with a few stylesheets that work with that tool to create output formats like
|
||||
HTML and TeX. From the TeX file we produce a DVI (device independent) file
|
||||
with latex, and from that we produce the print formats of our documents, like
|
||||
PDF and Postscript using tools like dvips and dvipdfm.
|
||||
|
||||
- What extra stuff do we do?
|
||||
Well, since our documents all have to do with an IRC bot, there are some very
|
||||
common things that we talk about a lot that we might like to format specially.
|
||||
For example, when we discuss a particular command for the bot we might like to
|
||||
have that text appear slightly different to emphasize the fact that it is
|
||||
special. So, for the commonly used items that weren't already covered by
|
||||
DocBook's DTD, I added elements into a new DTD which just extends DocBook's
|
||||
DTD. So now we have elements like <nick> and <channel> and <botcommand> that
|
||||
we can use for our documentation.
|
||||
|
||||
Of course, now that we have used a DTD with more stuff in it (than DocBook),
|
||||
the stylesheets that DocBook provides won't do any special formatting for those
|
||||
new elements so we have to write new stylesheets as well. Once again I just
|
||||
extended the existing ones with formatting instructions on how to treat the new
|
||||
elements. So with this done, now our HTML and TeX (and whatever else) output
|
||||
will be properly formatted.
|
||||
|
||||
- How do I make my own changes to the DTD and stylesheets?
|
||||
Primarily, you don't :) Ask me (Strike) first about it, and I will generally
|
||||
write them for you or explain a better way of doing things. This is especially
|
||||
true for the DTD, because that must remain consistent everywhere we write/read
|
||||
supybot docs based on it. The stylesheets are more lax and can be modified to
|
||||
produce whatever kind of output you wish.
|
||||
|
||||
So, with that warning/reminder out of the way, here's how to modify each
|
||||
anyway. This doesn't really assume any knowledge of how to write a DTD, nor is
|
||||
it an exhaustive reference on writing one, so don't treat it as such. I'm
|
||||
basically just going to explain how to add extra elements that will play well
|
||||
with the DocBook DTD.
|
||||
|
||||
-- Adding an element to the DTD
|
||||
If you've decided that there's a certain "thing" that's mentioned a lot in the
|
||||
documentation and deserves classification (for potential special formatting),
|
||||
you'll probably want to create a new element (or set of elements) for it. I'll
|
||||
walk you through how I added the "nick" element to our DTD (though many/most of
|
||||
the elements I added follow an identical process).
|
||||
|
||||
The very first thing you need to figure out is: where in my document does this
|
||||
element "fit". That is to say, what elements should/can rightly contain this
|
||||
particular one? In the case of the "nick" element, it's basically always an
|
||||
inline-formatted deal that belongs in paragraphs for the most part. For those
|
||||
of you scratching your heads at that last sentence, perhaps thinking "okay, so
|
||||
how are we supposed to know what is relevant?" I say, "don't worry, I learned
|
||||
by example as well." Basically, I just looked through the DocBook DTD and
|
||||
figured out where things belong. Now, even if you don't know the DocBook DTD
|
||||
front-to-back, you can still peruse it to figure out where your new element
|
||||
belongs. Obviously, you should probably know *some* DocBook to figure out what
|
||||
each element means, but luckily all of our docs have been converted to DocBook
|
||||
and serve as nice examples of the usage of many elements :)
|
||||
|
||||
Now, to figure out where something like "nick" belongs. In many ways, a nick
|
||||
is sort of like a variable name (at least in documentation usage). So, the
|
||||
element I chose to base it off of was "varname". If you have the DocBook DTD
|
||||
installed (as you should if you intend on making extensions to it), the varname
|
||||
element definition is contained in the dbpoolx.mod filename (in Debian, it's
|
||||
under /usr/share/sgml/docbook/dtd/4.2). How did I know this? Well, grep is
|
||||
your friend and mine too, and dbpoolx is the only filename that shows up when
|
||||
grepping for "varname" in the DocBook DTD directory. So, we open up dbpoolx.mod and search for varname. The first thing we find it in looks like this:
|
||||
|
||||
<!ENTITY % tech.char.class
|
||||
"action|application
|
||||
|classname|methodname|interfacename|exceptionname
|
||||
|ooclass|oointerface|ooexception
|
||||
|command|computeroutput
|
||||
|database|email|envar|errorcode|errorname|errortype|errortext|filename
|
||||
|function|guibutton|guiicon|guilabel|guimenu|guimenuitem
|
||||
|guisubmenu|hardware|interface|keycap
|
||||
|keycode|keycombo|keysym|literal|constant|markup|medialabel
|
||||
|menuchoice|mousebutton|option|optional|parameter
|
||||
|prompt|property|replaceable|returnvalue|sgmltag|structfield
|
||||
|structname|symbol|systemitem|token|type|userinput|varname
|
||||
%ebnf.inline.hook;
|
||||
%local.tech.char.class;">
|
||||
|
||||
Hmm, this doesn't look like a definition of varname (to me, but I sort of
|
||||
cheated by having read about DocBook before-hand ;)), but it will be important
|
||||
to remember for later. Let's try and find the element definition for varname
|
||||
(so, basically, let's look for the first line that starts with "<!ELEMENT ").
|
||||
The first line I come up with when I search is:
|
||||
|
||||
<!ELEMENT varname %ho; (%smallcptr.char.mix;)*>
|
||||
|
||||
Rather than write a separate tutorial for interpreting DTDs, I found a good
|
||||
SGML tutorial online that explains everything necessary to help you parse the
|
||||
DocBook DTD to figure out what the varname element really is, as well as to
|
||||
help you learn all the stuff necessary for what we will cover in creating our
|
||||
new nick element. That tutorial is at
|
||||
http://www.w3.org/TR/WD-html40-970708/intro/sgmltut.html#howtodtd (it's for
|
||||
reading the HTML DTD, but it applies to any DTD).
|
||||
|
||||
So, now that we understand how to write/read things for a DTD, we arrive at the
|
||||
time where we can write the actual definition of our "nick" element:
|
||||
|
||||
<!ELEMENT Nick - - ((%smallcptr.char.mix;)+)>
|
||||
|
||||
As we learned in the above tutorial, this means that we are creating an element
|
||||
named "nick", which must have start and end tags, and is defined to contain one
|
||||
or more of whatever is in "smallcptr.char.mix". And rather than hunt through
|
||||
the DocBook DTD to figure out what that is, for now we'll just live with the
|
||||
fact that whatever can go into a DocBook varname can go into our new nick
|
||||
element. If you feel so inclined, feel free to try and define the content
|
||||
model for nick to only include valid nick characters. It's perfectly doable,
|
||||
and I'll probably do it at some point but I haven't yet.
|
||||
|
||||
Since we're extending the DocBook DTD, I also decided that it'd be nice to
|
||||
follow the element creation conventions observed in their DTD, so there are a
|
||||
few more lines associated with our new nick element. All of them are related
|
||||
to the attributes of the element, and allowing for them to be extended by
|
||||
external DTDs (much like we are doing, only we aren't changing attributes of
|
||||
existing elements, just adding our own). The first one is:
|
||||
|
||||
<!ENTITY % local.nick.attrib "">
|
||||
|
||||
This basically defines an empty entity named local.nick.attrib which we will
|
||||
include so that if anyone chooses to extend the nick attributes, all they have
|
||||
to do is redefine local.nick.attrib.
|
||||
|
||||
<!ENTITY % nick.role.attrib "%role.attrib;">
|
||||
|
||||
To tell you the truth, I'm not entirely sure what this is for, but it follows the DocBook convention :)
|
||||
|
||||
<!ATTLIST Nick
|
||||
%common.attrib;
|
||||
%local.nick.attrib;
|
||||
%nick.role.attrib;
|
||||
>
|
||||
|
||||
This is, of course, our attribute list for our nick element. It consists of
|
||||
the two things we just defined as well as common.attrib which contains things
|
||||
like "id" and whatnot which all DocBook elements are expected to have.
|
||||
|
||||
-- Extending the DocBook DTD to recognize new elements
|
||||
So, that's all you need to define your new element. But, we're not done just
|
||||
yet! We're almost there, we just need to make it so that it works with the
|
||||
existing DocBook elements, otherwise it's no good to us. Since we defined our
|
||||
element to esentially be the same as varname, it probably belongs at the same
|
||||
place within the DocBook schema as varname. Do you remember when we had that
|
||||
large entity definition that wasn't what we were looking for at the time though
|
||||
I said it'd be important later? Well, later is now. So, what that line tells
|
||||
us is what class of elements DocBook has varname in, which is
|
||||
"tech.char.class". And thanks to the DocBook convention of defining a
|
||||
local.<classname> entity that we can extend, all we have to do is redefine
|
||||
local.tech.char.class to contain "nick", and we are done.
|
||||
|
||||
You may notice, however, that we don't actually put varname right into the
|
||||
local.tech.char.class entity, but instead we create our own
|
||||
supybot.tech.char.class class of elements that are supybot-specific (and are
|
||||
the equivalent of DocBook's tech.char.class elements) and instead, put all of
|
||||
those into the local.tech.char.class entity. Basically, we just go through one
|
||||
more level of indirection.
|
221
docs/DocBook/capabilities.sgml
Normal file
221
docs/DocBook/capabilities.sgml
Normal file
@ -0,0 +1,221 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot capabilities system explanation</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>18 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>04 Sep 2004</date>
|
||||
<revremark>Update Docbook translation</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<subtitle>
|
||||
Supybot's capabilities overview and comparisons to other bots
|
||||
</subtitle>
|
||||
<para>
|
||||
Ok, some 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 <emphasis>really</emphasis> 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.
|
||||
</para>
|
||||
<para>
|
||||
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 <varname>o</varname>
|
||||
flag, are instead able to check if a user has the
|
||||
<capability>owner</capability> 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.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>What sets supybot's capabilities apart</title>
|
||||
<para>
|
||||
If that was all, well, the capability system would be
|
||||
“cool”, but not many people would say it was
|
||||
“awesome”. But it <emphasis>is</emphasis> awesome!
|
||||
Several things are happening behind the scene 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 <emphasis>cannot</emphasis> do. It's
|
||||
formed rather simply by adding a dash (“-”) to the
|
||||
beginning of a capability; <botcommand>rot13</botcommand> is a
|
||||
capability, and <botcommand>-rot13</botcommand> is an
|
||||
anticapability. Anyway, when a user issues the bot a command,
|
||||
perhaps <botcommand>calc</botcommand> or
|
||||
<botcommand>help</botcommand>, the bot first checks to make sure
|
||||
the user doesn't have the <capability>-calc</capability> or the
|
||||
<capability>-help</capability> capabilities before even
|
||||
considering responding to the user. So commands can be turned on
|
||||
or off on a <emphasis>per user</emphasis> basis, offering
|
||||
finegrained control not often (if at all!) seen in other bots.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Channel capabilities</title>
|
||||
<para>
|
||||
But that's not all! The capabilities system also supports
|
||||
<emphasis>Channel</emphasis> capabilities, which are
|
||||
capabilities that only apply to a specific channel; they're of
|
||||
the form <capability>#channel,capability</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 <emphasis>in that
|
||||
channel</emphasis> 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!
|
||||
</para>
|
||||
<para>
|
||||
So when a user <nick>foo</nick> sends a command
|
||||
<botcommand>bar</botcommand> to the bot on channel
|
||||
<channel>#baz</channel>, first the bot checks to see if the
|
||||
user has the anticapability for the command by itself,
|
||||
<capability>-bar</capability>. If so, it returns right then
|
||||
and there, compltely ignoring the fact that the user issued
|
||||
that command to it. 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,
|
||||
<capability>#baz,-bar</capability>. If so, again, he returns
|
||||
right then and there and doesn't even think about responding
|
||||
to the bot. If neither of these anticapabilities are present,
|
||||
then the bot just responds to the user like normal.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Motivations behind the capabilities system</title>
|
||||
<sect2>
|
||||
<title>A programmer's perspective</title>
|
||||
<para>
|
||||
From a programming perspective, capabilties are easy to use
|
||||
and flexible. Any command can check if a user has any
|
||||
capability, even ones not thought of when the bot was
|
||||
originally written. Commands/Callbacks can 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.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>An end-user's perspective</title>
|
||||
<para>
|
||||
From an end-user perspective, capabilities remove a lot of the
|
||||
mystery and esotery of bot control, in addition to giving the
|
||||
user absolutely finegrained control over what users are
|
||||
allowed to do with the bot. Additionally, defaults can be set
|
||||
by the end-user 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.
|
||||
</para>
|
||||
</sect2>
|
||||
<para>
|
||||
It's really a revolution!
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Hard-coded supybot capabilities</title>
|
||||
<para>
|
||||
There are several default capabilities the bot uses. The most
|
||||
important of these is the <capability>owner</capability>
|
||||
capability. This capability allows the person having it to use
|
||||
<emphasis>any</emphasis> command. It's best to keep this
|
||||
capability reserved to people who actually have access to the
|
||||
shell the bot is running on.
|
||||
</para>
|
||||
<para>
|
||||
There is also the <capability>admin</capability> capability for
|
||||
non-owners that are highly trusted to administer the bot
|
||||
appropriately. They can do things such as change the bot's nick,
|
||||
globally enable/disable commands, cause the bot to ignore a given
|
||||
user, set the prefixchar, report bugs, etc. They generally cannot
|
||||
do administration related to channels, which is reserved for
|
||||
people with the next capability.
|
||||
</para>
|
||||
<para>
|
||||
People who are to administer channels with the bot should have the
|
||||
<capability>#channel,op</capability> capability – whatever
|
||||
channel they are to administrate, they should have that channel
|
||||
capability for <capability>op</capability>. For example, since I
|
||||
want <nick>inkedmn</nick> to be an administrator in
|
||||
<channel>#supybot</channel>, I'll give him the
|
||||
<capability>#supybot,op</capability> capability. This is in
|
||||
addition to his <capability>admin</capability> capability, since
|
||||
the <capability>admin</capability> capability doesn't give the
|
||||
person having it control over channels.
|
||||
<capability>#channel.op</capability> 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 <capability>#channel,op</capability> capability
|
||||
is also basically the equivalent of the owner capability for
|
||||
capabilities involving <channel>#channel</channel> –
|
||||
basically anyone with the <capability>#channel,op</capability>
|
||||
capability is considered to have all positive capabilities and no
|
||||
negative capabilities for <channel>#channel</channel>.
|
||||
</para>
|
||||
<para>
|
||||
One other globally important capability exists:
|
||||
<capability>trusted</capability>. 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
|
||||
<botcommand>Math.icalc</botcommand>, which potentially could 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 <botcommand>Utilties.re</botcommand>, 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
|
||||
the someone gave the bot the command <literal>re [strjoin "" s/./
|
||||
[dict go] /] [dict go]</literal>.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Other capabilities</title>
|
||||
<para>
|
||||
Other plugins may require different capabilities; the
|
||||
<plugin>Factoids</plugin> plugin requires
|
||||
<capability>#channel,factoids</capability>, the <plugin>Topic</plugin>
|
||||
plugin requires <capability>#channel,topic</capability>, etc.
|
||||
</para>
|
||||
</sect1>
|
||||
</article>
|
||||
|
||||
|
316
docs/DocBook/configuration.sgml
Normal file
316
docs/DocBook/configuration.sgml
Normal file
@ -0,0 +1,316 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<author>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot configuration system explanation</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>18 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>26 Feb 2004</date>
|
||||
<revremark>Conversion to Supybot DTD</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.3</revnumber>
|
||||
<date>4 Sep 2004</date>
|
||||
<revremark>Update Docbook translation</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
Configuration of Supybot is handled via the
|
||||
<plugin>Config</plugin> plugin, which controls runtime access to
|
||||
Supybot's registry (the configuration file generated by the
|
||||
<script>supybot-wizard</script> program you ran). The
|
||||
<plugin>Config</plugin> 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: <botcommand>config</botcommand>,
|
||||
<botcommand>list</botcommand>, and
|
||||
<botcommand>help</botcommand>. If you don't know how to get help on
|
||||
those commands, go ahead and read our
|
||||
<filename>GETTING_STARTED</filename> document before this one.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Supybot's registry</title>
|
||||
<para>
|
||||
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
|
||||
<registrygroup>supybot.reply</registrygroup>; variables having to
|
||||
do with the way a plugin works all start with
|
||||
<registrygroup>supybot.plugins.Plugin</registrygroup> (where
|
||||
<plugin>Plugin</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.
|
||||
</para>
|
||||
<para>
|
||||
Some of the more important configuration values are located
|
||||
directly under the base group,
|
||||
<registrygroup>supybot</registrygroup>. 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: <registrygroup>plugins</registrygroup> (where all
|
||||
the plugin-specific configuration is held),
|
||||
<registrygroup>reply</registrygroup> (where variables affecting
|
||||
the way a Supybot makes its replies resides),
|
||||
<registrygroup>replies</registrygroup> (where all the specific
|
||||
standard replies are kept), and
|
||||
<registrygroup>directories</registrygroup> (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.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Config plugin commands</title>
|
||||
<sect3>
|
||||
<title>Listing registry contents</title>
|
||||
<para>
|
||||
Using the <plugin>Config</plugin> plugin, you can list
|
||||
the 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 <registrygroup>supybot</registrygroup> (the base
|
||||
group) hierarchy. You would simply issue this command:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config list supybot
|
||||
<supybot> @capabilities, @commands, @databases, @debug, @directories, @drivers,
|
||||
@log, @networks, @nick, @plugins, @protocols, @replies, @reply,
|
||||
alwaysJoinOnInvite, channels, defaultIgnore, defaultSocketTimeout,
|
||||
externalIP, flush, followIdentificationThroughNickChanges,
|
||||
humanTimestampFormat, ident, pidFile, snarfThrottle, upkeepInterval,
|
||||
and user
|
||||
</ircsession>
|
||||
<para>
|
||||
These are all the configuration groups and values which
|
||||
are under the base <registrygroup>supybot</registrygroup>
|
||||
group. Actually, their full names would each have a
|
||||
“supybot.” appended on to the front of 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.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect2>
|
||||
<title>Supybot's registry</title>
|
||||
<sect3>
|
||||
<title>Dealing with registry values</title>
|
||||
<para>
|
||||
Okay, now that you've used the <plugin>Config</plugin>
|
||||
plugin to list configuration variables, it's time that we
|
||||
start looking at individual variables and their values.
|
||||
</para>
|
||||
<sect4>
|
||||
<title>Built-in help for registry values</title>
|
||||
<para>
|
||||
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 <botcommand>config help</botcommand>. To see the
|
||||
help string for any value or group, simply use the
|
||||
<botcommand>config help</botcommand> command. For
|
||||
example, to see what this
|
||||
<registrygroup>supybot.snarfThrottle</registrygroup>
|
||||
configuration variable is all about, we'd do this:
|
||||
</para>
|
||||
<ircsession>
|
||||
<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)
|
||||
</ircsession>
|
||||
<para>
|
||||
Pretty simple, eh?
|
||||
</para>
|
||||
</sect4>
|
||||
<sect4>
|
||||
<title>Getting/setting registry values</title>
|
||||
<para>
|
||||
Now, if you're curious what the current value of a
|
||||
configuration variable is, you'll use the
|
||||
<botcommand>config</botcommand> command with one
|
||||
argument, the name of the variable you want to see the
|
||||
value of:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: '@'
|
||||
</ircsession>
|
||||
<para>
|
||||
To set this value, just stick an extra argument after
|
||||
the name:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars @$
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
</ircsession>
|
||||
<para>
|
||||
Now, check this out:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: '@$'
|
||||
</ircsession>
|
||||
<para>
|
||||
Note that we used <literal>$</literal> as our prefix
|
||||
character, and that the value of the configuration
|
||||
variable changed. If I were to use the
|
||||
<botcommand>flush</botcommand> 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
|
||||
<keycombo>
|
||||
<keycap>Ctrl</keycap>
|
||||
<keycap>C</keycap>
|
||||
</keycombo>
|
||||
in the terminal the bot was running in). Instead,
|
||||
I'll revert the change:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars @
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> $note that this makes no response.
|
||||
</ircsession>
|
||||
<para>
|
||||
If you're ever curious what the default for a given
|
||||
configuration variable is, use the <botcommand>config
|
||||
default</botcommand> command:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @config default supybot.reply.whenAddressedBy.chars
|
||||
<supybot> jemfinch|lambda: ''
|
||||
</ircsession>
|
||||
<para>
|
||||
Thus, to reset a configuration variable to its default
|
||||
value, you can simply say:
|
||||
</para>
|
||||
<ircsession>
|
||||
<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
|
||||
</ircsession>
|
||||
<para>
|
||||
Simple, eh?
|
||||
</para>
|
||||
</sect4>
|
||||
</sect3>
|
||||
<sect3>
|
||||
<title>Searching the registry</title>
|
||||
<para>
|
||||
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 <botcommand>config
|
||||
search</botcommand> command. Check this out:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> @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.maz, and
|
||||
supybot.plugins.Relay.topicSync
|
||||
</ircsession>
|
||||
<para>
|
||||
Sure, it showed up 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 you have 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.
|
||||
</para>
|
||||
<para>
|
||||
Some people might like editing their registry file
|
||||
directly rather than manipulating all these things through
|
||||
the bot. For those people, we offer the
|
||||
<botcommand>config reload</botcommand> command, which
|
||||
reloads both registry configuration and
|
||||
user/channel/ignore database configuration. Just edit the
|
||||
interesting files and then give the bot the
|
||||
<botcommand>config reload</botcommand> command and it'll
|
||||
work as expected. Do note, however, that Supybot flushes
|
||||
his configuration files and databases 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 <registrygroup>supybot.flush</registrygroup> value to
|
||||
<literal>Off</literal>, and no automatic flushing will
|
||||
occur.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect3>
|
||||
<title>Channel-specific configuration</title>
|
||||
<para>
|
||||
Many configuration variables can be specific to individual
|
||||
channels. The <plugin>Config</plugin> 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:
|
||||
</para>
|
||||
<ircsession>
|
||||
config channel supybot.reply.whenAddressedBy.chars !
|
||||
</ircsession>
|
||||
<para>
|
||||
That'll set the prefix chars in the channel that message
|
||||
is sent in to <literal>!</literal>. Voila,
|
||||
channel-specific values!
|
||||
</para>
|
||||
</sect3>
|
||||
</sect2>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>All done!</title>
|
||||
<para>
|
||||
Anyway, that's about it for configuration. Have fun, and enjoy
|
||||
your configurable bot!
|
||||
</para>
|
||||
</sect1>
|
||||
</article>
|
||||
|
||||
|
359
docs/DocBook/faq.sgml
Normal file
359
docs/DocBook/faq.sgml
Normal file
@ -0,0 +1,359 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
<article class="faq">
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot Frequently Asked Questions</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>18 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>26 Feb 2004</date>
|
||||
<revremark>Changed to Supybot DTD</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<qandaset defaultlabel="qanda">
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Why does my bot not recognize me or tell me that I don't
|
||||
have the <capability>owner</capability> capability?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Because you've not given it anything to recognize you
|
||||
from! You'll need to identify with the bot
|
||||
(<botcommand>help identify</botcommand> to see how that
|
||||
works) or add your hostmask to your user record
|
||||
(<botcommand>help addhostmask</botcommand> to see how that
|
||||
works) for it to know that you're you. You may wish to
|
||||
note that <botcommand>addhostmask</botcommand> can accept
|
||||
a password; rather than identify, you can send the command
|
||||
<botcommand>addhostmask myOwnerUser [hostmask]
|
||||
myOwnerUserPassword</botcommand> and the bot will add your
|
||||
current hostmask to your owner user (of course, you should
|
||||
change <literal>myOwnerUser</literal> and
|
||||
<literal>myOwnerUserPassword</literal> appropriately for
|
||||
your bot).
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
How do I make Supybot op my users?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
First, you'll have to make sure that your users register
|
||||
with the bot. They can do this with the
|
||||
<botcommand>register</botcommand> command. After they do
|
||||
so, you'll want to add the
|
||||
<capability>#channel,op</capability> capability to their
|
||||
user. Use the <botcommand>channel
|
||||
addcapability</botcommand> command to do this. After
|
||||
that, your users should be able to use the
|
||||
<botcommand>op</botcommand> command to get ops.
|
||||
</para>
|
||||
<para>
|
||||
If you want your users to be auto-opped when they join the
|
||||
channel, you'll need to load the <plugin>Enforcer</plugin>
|
||||
plugin and turn its <registrygroup>autoOp</registrygroup>
|
||||
configuration variable on. Use the
|
||||
<botcommand>config</botcommand> command to do so. Here's
|
||||
an example of how to do these steps:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch|lambda> I'm going to make an example session for giving
|
||||
you auto-ops, for our FAQ.
|
||||
<dunk1> ah ok ;]
|
||||
<jemfinch|lambda> First, I need you to register with supybot, using
|
||||
the "register" command (remember to send it in private).
|
||||
<dunk1> done
|
||||
<jemfinch|lambda> what name are you registered under?
|
||||
<dunk1> dunk1
|
||||
<jemfinch|lambda> ok, cool.
|
||||
<jemfinch|lambda> @channel addcapability dunk1 op
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> now use the "op" command to get ops.
|
||||
<dunk1> @op
|
||||
— supybot gives channel operator status to dunk1
|
||||
<dunk1> works!
|
||||
<dunk1> ;]
|
||||
<jemfinch|lambda> @load Enforcer
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> @config channel supybot.plugins.Enforcer.autoOp On
|
||||
<supybot> jemfinch|lambda: The operation succeeded.
|
||||
<jemfinch|lambda> ok, now cycle the channel (part and then rejoin)
|
||||
<– dunk1 (dunker@freebsd.nl) has left #supybot
|
||||
–> dunk1 (dunker@freebsd.nl) has joined #supybot
|
||||
— supybot gives channel operator status to dunk1
|
||||
<jemfinch|lambda> cool, thanks :)
|
||||
</ircsession>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Can users with the <capability>admin</capability>
|
||||
capability change configuration variables?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Currently, no. Since this is the first release of Supybot
|
||||
that uses the registry, we wanted to stay on the
|
||||
conservative side and require the
|
||||
<capability>owner</capability> capability for changing all
|
||||
non-channel-related configuration variables. Feel free to
|
||||
make your case to us as to why a certain configuration
|
||||
variable should only require the
|
||||
<capability>admin</capability> capability instead of the
|
||||
<capability>owner</capability> capability, and if we agree
|
||||
with you, we'll change it for the next release.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Can Supybot do factoids?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Supybot most certainly can! In fact, we offer three
|
||||
full-fledged factoids-related plugins!
|
||||
</para>
|
||||
<para>
|
||||
<plugin>Factoids</plugin> (written by
|
||||
<nick>jemfinch</nick>) is Supybot's original
|
||||
factoids-related plugin. It offers full integration with
|
||||
Supybot's nested commands as well as a complete 1:n key to
|
||||
factoid ratio, with lookup by individual number. Factoids
|
||||
also uses a channel-specific database instead of a global
|
||||
database though that's configurable with the
|
||||
<registrygroup>supybot.databases.plugins.channelSpecific</registrygroup>
|
||||
configuration variable.
|
||||
</para>
|
||||
<para>
|
||||
<plugin>MoobotFactoids</plugin> (written by
|
||||
<nick>Strike</nick>) is much more full-featured, offering
|
||||
users the ability to define factoids in a slightly more
|
||||
user-friendly way, as well as parsing factoids to handle
|
||||
<reply>, <action>, and alternations (defining
|
||||
a factoid “test” as
|
||||
“<reply>(foo|bar|baz)” will make the bot
|
||||
send “foo” or “bar” or
|
||||
“baz” to the channel (without the normal
|
||||
“test is ” at the beginning)). If you're
|
||||
accustomed to Moobot's factoids or Blootbot's factoids,
|
||||
then this is the Factoids plugin for you. Unfortunately,
|
||||
due to the more natural definition syntax (required to be
|
||||
comaptible with Moobot) you can't define Factoids with
|
||||
nested commands; you'll have to evaluate the command first
|
||||
and then copy the result into your factoid definition.
|
||||
</para>
|
||||
<para>
|
||||
<plugin>Infobot</plugin> (written by
|
||||
<nick>jamessan</nick>) is used for Infobot compatibility;
|
||||
if you still want the basic functionality of Infobot, this
|
||||
is the plugin to use.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Can I import my Infobot/Blootbot/Moobot factoids into
|
||||
Supybot?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
As of present, we have no automated way to do so.
|
||||
<nick>Strike</nick> has written a few scripts for
|
||||
importing a Moobot database into MoobotFactoids, however,
|
||||
so you'll want to talk to him about helping you with that.
|
||||
We're certainly happy to help you convert such databases;
|
||||
if you can provide us with such a database exported to a
|
||||
flat file, we can probably do the rest of the work to
|
||||
write a script that imports it into a database for one of
|
||||
our factoids-related plugins.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Do I really have to use separate databases for each
|
||||
channel?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Of course not! We default to separate databases for each
|
||||
channel because, well, that's what <nick>jemfinch</nick>
|
||||
always thought was reasonable. Anyway, if you change the
|
||||
configuration variable
|
||||
<registrygroup>supybot.databases.plugins.channelSpecific</registrygroup>
|
||||
to <literal>False</literal> instead of
|
||||
<literal>True</literal>, for <emphasis>most</emphasis>
|
||||
databases, each channel will share the same database (the
|
||||
exceptions are <plugin>ChannelStats</plugin>,
|
||||
<plugin>Herald</plugin>, <plugin>Seen</plugin>, and
|
||||
<plugin>WordStats</plugin>, which are inherently rather
|
||||
channel-based).
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Karma doesn't seem to work for me.
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
<plugin>Karma</plugin> 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
|
||||
<plugin>Karma</plugin> acknowledge karma updates, change
|
||||
the
|
||||
<registrygroup>supybot.plugins.Karma.response</registrygroup>
|
||||
configuration variable to <literal>On</literal>.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
I added an alias, but it doesn't work!
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Take a look at <botcommand>help <alias you
|
||||
added></botcommand>. If the alias the bot has listed
|
||||
doesn't match what you're giving it, chances re 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:
|
||||
<para>
|
||||
<ircsession>
|
||||
alias add mylink "format concat http://myhost.com/
|
||||
[urlquote $1]"
|
||||
</ircsession>
|
||||
<para>
|
||||
and not:
|
||||
</para>
|
||||
<ircsession>
|
||||
alias add mylink format concat http://myhost.com/
|
||||
[urlquote $1]
|
||||
</ircsession>
|
||||
<para>
|
||||
The first version works; the second version will always
|
||||
return the same url.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
Is there a command that can tell me what capability
|
||||
another command requires?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
Besides, is the error message so bad? If we did have such
|
||||
a command, many users would call the command, see that
|
||||
they could perform it, and then run the command, thus
|
||||
doubling the activity in the channel. Is that something
|
||||
you want?
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
How do I make my Supybot connect to multiple servers?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Just use the <botcommand>connect</botcommand> command in
|
||||
the <plugin>Owner</plugin> plugin. Easy as pie!
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
<qandaentry>
|
||||
<question>
|
||||
<para>
|
||||
I found a bug, what do I do?
|
||||
</para>
|
||||
</question>
|
||||
<answer>
|
||||
<para>
|
||||
Submit it on Sourceforge through our Sourceforge project
|
||||
page:
|
||||
<ulink
|
||||
url="http://sourceforge.net/tracker/?group_id=58965&atid=489447">
|
||||
http://sourceforge.net/tracker/?group_id=58965&atid=489447
|
||||
</ulink>. If Sourceforge happens to be down when you try
|
||||
to submit your bug, then post it in the "Supybot Developer
|
||||
Discussion" forum at our forums at
|
||||
<ulink url="http://forums.supybot.org">
|
||||
http://forums.supybot.org/
|
||||
</ulink>. If that doesn't work, email
|
||||
<email>supybot-bugs@lists.sourceforge.net</email>. If
|
||||
that doesn't work, email
|
||||
<email>jemfinch@supybot.org</email>. If that doesn't
|
||||
work, find yourself some carrier pigeons and … hah!
|
||||
You thought I was serious!
|
||||
</para>
|
||||
<para>
|
||||
Anyway, when you submit your bug, we'll need several
|
||||
things. If the bug involved an uncaught exception, we
|
||||
need the traceback (basically the stuff from
|
||||
“Uncaught exception in …” to the next
|
||||
log entry). We'd also like to see the commands that
|
||||
caused the bug, or happened around the time you saw the
|
||||
bug. If the bug involved a database, we'd love to see the
|
||||
database. Remember, it's always worse to send us too
|
||||
little information in a bug report than too much.
|
||||
</para>
|
||||
</answer>
|
||||
</qandaentry>
|
||||
</qandaset>
|
||||
</article>
|
||||
|
||||
|
297
docs/DocBook/getting_started.sgml
Normal file
297
docs/DocBook/getting_started.sgml
Normal file
@ -0,0 +1,297 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Getting started with Supybot</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>18 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
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 :)
|
||||
</para>
|
||||
<para>
|
||||
First things first: Supybot <emphasis>requires</emphasis> Python
|
||||
2.3. There ain't no getting around it. If you're a Python
|
||||
developer, you probably know how superior 2.3 is to previous
|
||||
incarnations. If you're not, just think about the difference
|
||||
between a bowl of plain vanilla ice cream and a banana split. Or
|
||||
something like that. Either way, <emphasis>we're</emphasis>
|
||||
Python developers and we like banana splits.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Installing the bot and its utilities</title>
|
||||
<para>
|
||||
So what do you do? First thing you'll want to do is run (with
|
||||
root/admin privileges) <application>python setup.py
|
||||
install</application>. This will install Supybot globally. If
|
||||
you need to install locally for whatever reason, see this <ulink
|
||||
url="http://tinyurl.com/2tb37">forum post</ulink> on how to do so.
|
||||
You'll then have several new programs installed where Python
|
||||
scripts are normally installed on your system
|
||||
(<filename>/usr/bin</filename> or
|
||||
<filename>/usr/local/bin</filename> are common on UNIX systems;
|
||||
<filename>C:\Python23\Scripts</filename> is a common place on
|
||||
Windows; and (watch out, this is a long one :))
|
||||
<filename>/System/Library/Frameworks/Python.framework/Versions/2.3/bin</filename>
|
||||
is a common place on MacOS X.). The two that might be of
|
||||
particular interest to you, the new user, are
|
||||
<script>supybot</script> and
|
||||
<script>supybot-wizard</script> The former
|
||||
(<script>supybot</script> is the script to run an actual
|
||||
bot; the latter (<script>supybot-wizard</script> is an
|
||||
in-depth wizard that provides a nice user interface for creating
|
||||
configuration files for your bot. We'd prefer you to the use
|
||||
<script>supybot-wizard</script>, but if you're in a
|
||||
hurry or don't feel like being asked many questions, just run
|
||||
supybot with no arguments and it'll ask you only the questions
|
||||
necessary ")to run a bot.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Firing up the bot for the first time</title>
|
||||
<para>
|
||||
So after running either of those two programs, you've got a nice
|
||||
registry file handy. If you're not satisfied with your answers
|
||||
to any of the questions you were asked, feel free to run the
|
||||
program again until you're satisfied with all your answers. Once
|
||||
you're satisfied, though, run the
|
||||
<script>supybot</script> program with the
|
||||
registry file you created as an argument. This will start the
|
||||
bot; unless you turned off logging to stdout, you'll see some nice
|
||||
log messages describing what the bot is doing at any particular
|
||||
moment; it may pause for a significant amount of time after saying
|
||||
"Connecting to ..." while the server tries to check its ident.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Your first interactions with the bot</title>
|
||||
<para>
|
||||
Ok, so let's assume your bot connected to the server fine and
|
||||
joined the channels you told it to join. For now we'll assume you
|
||||
named your bot <nick>supybot</nick> (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>#channel</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:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: list
|
||||
</ircsession>
|
||||
<para>
|
||||
Replacing <nick>supybot</nick> 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
|
||||
<plugin>Admin</plugin>, <plugin>Channel</plugin>,
|
||||
<plugin>Config</plugin>, <plugin>Misc</plugin>,
|
||||
<plugin>Owner</plugin>, and <plugin>User</plugin> should be
|
||||
there; if you used <script>supybot-wizard</script> to
|
||||
create your configuration file you may have many more plugins
|
||||
loaded. The <botcommand>list</botcommand> command can also be used to
|
||||
list the commands in a given plugin:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: list Misc
|
||||
</ircsession>
|
||||
<para>
|
||||
Will list all the commands in the <plugin>Misc</plugin> plugin.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Accessing the bot's online help</title>
|
||||
<para>
|
||||
If you want to see the help for any command, just use
|
||||
the <botcommand>help</botcommand> command:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: help help
|
||||
supybot: help list
|
||||
supybot: help load
|
||||
</ircsession>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Dealing with ambiguous commands</title>
|
||||
<para>
|
||||
Sometimes more than one plugin will have a given command; for
|
||||
instance, the <botcommand>list</botcommand> command exists in both
|
||||
the <plugin>Misc</plugin> and <plugin>Config</plugin>
|
||||
plugins (both loaded by default). <plugin>List</plugin>, in
|
||||
this case, defaults to the <plugin>Misc</plugin> plugin, but
|
||||
you may want to get the help for the
|
||||
<botcommand>list</botcommand>
|
||||
command in the <plugin>Config</plugin> plugin. In that
|
||||
case, you'll want to give your command like this:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: help config list
|
||||
</ircsession>
|
||||
<para>
|
||||
Anytime your bot tells you that a given command is defined in
|
||||
several plugins, you'll want to use this syntax
|
||||
(<botcommand>plugin command</botcommand>) to disambiguate which
|
||||
plugin's command you wish to call. For instance, if you
|
||||
wanted to call the <plugin>Config</plugin> plugin's
|
||||
<botcommand>list</botcommand> command, then you'd need to say:
|
||||
</para>
|
||||
<ircsession>
|
||||
supybot: config list
|
||||
</ircsession>
|
||||
<para>
|
||||
Rather than just <botcommand>list</botcommand>.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Loading plugins</title>
|
||||
<para>
|
||||
Now that you know how to deal with plugins having commands
|
||||
with the same name, let's take a look at loading other
|
||||
plugins. If you didn't use
|
||||
<script>supybot-wizard</script>, though, you might
|
||||
do well to try it before playing around with loading plugins
|
||||
yourself: each plugin has its own
|
||||
<function>configure</function> function that the wizard uses
|
||||
to setup the appropriate registry entries if the plugin
|
||||
requires any.
|
||||
</para>
|
||||
<sect3>
|
||||
<title>Identifying yourself as the bot owner</title>
|
||||
<para>
|
||||
Now, if you do want to play around with loading plugins,
|
||||
you're going to need to have the
|
||||
<capability>owner</capability>
|
||||
capability. If you ran the wizard, then chances are you
|
||||
already added an owner user for yourself. If not,
|
||||
however, you can add one via the handy-dandy
|
||||
<script>supybot-adduser</script> script. You'll
|
||||
want to run it while the bot is not running (otherwise it
|
||||
could overwrite
|
||||
<script>supybot-adduser</script>'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 <capability>owner</capability> capability
|
||||
(without the quotes), restart the bot and you'll be ready
|
||||
to load some plugins!
|
||||
</para>
|
||||
<para>
|
||||
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:
|
||||
</para>
|
||||
<ircsession>
|
||||
help identify
|
||||
</ircsession>
|
||||
<para>
|
||||
And follow the instructions; the command you send will
|
||||
probably look like this, with your owner user and password
|
||||
replaced:
|
||||
</para>
|
||||
<ircsession>
|
||||
identify myowneruser myuserpassword
|
||||
</ircsession>
|
||||
<para>
|
||||
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 <plugin>Owner</plugin> and <plugin>Admin</plugin>
|
||||
plugins, which you may want to take a look at (using the
|
||||
<botcommand>list</botcommand> and
|
||||
<botcommand>help</botcommand>
|
||||
commands, of course). One command in particular that you
|
||||
might want to use (it's from the <plugin>User</plugin>
|
||||
plugin) is the <botcommand>addhostmask</botcommand> command: it
|
||||
lets you add a hostmask to your user record so the bot
|
||||
recognizes you by your hostmask instead of requiring you
|
||||
to always identify with it before it recognizes you. Use
|
||||
the <botcommand>help</botcommand> command to see how this
|
||||
command works. Here's how I often use it:
|
||||
</para>
|
||||
<ircsession>
|
||||
addhostmask myuser [hostmask] mypassword
|
||||
</ircsession>
|
||||
<para>
|
||||
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
|
||||
<plugin>Misc</plugin> 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
|
||||
<literal>mypassword</literal> if I'm not already
|
||||
identified with the bot.
|
||||
</para>
|
||||
</sect3>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>The <botcommand>more</botcommand> command</title>
|
||||
<para>
|
||||
Another command you might find yourself needing somewhat often
|
||||
is the <botcommand>more</botcommand> 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 listing of
|
||||
configuration groups for the bot (more on this in the
|
||||
CONFIGURATION document) by giving the command "config list
|
||||
supybot". Last I checked, it'll overflow into a second chunk.
|
||||
When you invoke this command, you should see output like:
|
||||
</para>
|
||||
<ircsession>
|
||||
<supybot> nick, ident, user, server, password, channels, prefixChars,
|
||||
defaultCapabilities, defaultAllow, defaultIgnore,
|
||||
humanTimestampFormat, externalIP, bracketSyntax, pipeSyntax,
|
||||
followIdentificationThroughNickChanges, alwaysJoinOnInvite,
|
||||
showSimpleSyntax, maxHistoryLength, nickmods, throttleTime,
|
||||
snarfThrottle, threadAllCommands, pingServer, pingInterval,
|
||||
upkeepInterval, flush, (1 more message)
|
||||
</ircsession>
|
||||
<para>
|
||||
Now, to see the rest of the output, simply give the command
|
||||
<botcommand>more</botcommand>, and it will show you the rest:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch> more
|
||||
<supybot> httpPeekSize, and defaultSocketTimeout
|
||||
</ircsession>
|
||||
</sect2>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>You're ready!</title>
|
||||
<para>
|
||||
You should now have a solid foundation for using Supybot. Be sure
|
||||
to check the help that is built-in to the bot itself if you have
|
||||
any questions, and enjoy using Supybot!
|
||||
</para>
|
||||
</sect1>
|
||||
</article>
|
||||
|
||||
|
585
docs/DocBook/interfaces.sgml
Normal file
585
docs/DocBook/interfaces.sgml
Normal file
@ -0,0 +1,585 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot developer interfaces</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>19 Feb 2004</date>
|
||||
<revremark>Initial Docbook translation</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>26 Feb 2004</date>
|
||||
<revremark>Converted to Supybot DTD</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Available interfaces</title>
|
||||
<para>
|
||||
These are the interfaces for some of the objects you'll deal with
|
||||
if you code for Supybot.
|
||||
</para>
|
||||
<sect2>
|
||||
<title><classname>ircmsgs.IrcMsg</classname>
|
||||
<para>
|
||||
This is the object that represents an IRC message. It has
|
||||
several methods and attributes. The most important thing
|
||||
about this class, however, is that it <emphasis>is</emphasis>
|
||||
hashable, and thus <emphasis>cannot</emphasis> be modified.
|
||||
Do not change any attributes; any code that modifies an IRC
|
||||
message is <emphasis>broken</emphasis> and should not exist.
|
||||
</para>
|
||||
<variablelist>
|
||||
<title>Interesting methods</title>
|
||||
<varlistentry>
|
||||
<term>__init__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
One of the more complex initializers in a class.
|
||||
It can be used in three different ways:
|
||||
</para>
|
||||
<orderedlist numeration="arabic" spacing="normal">
|
||||
<listitem>
|
||||
<para>
|
||||
It can be given a string, as one received
|
||||
from the server, which it will then parse
|
||||
into its separate components and
|
||||
instantiate the class with those
|
||||
components as attributes.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
It can be given a command, some (optional)
|
||||
arguments, and a (optional) prefix, and
|
||||
will instantiate the class with those
|
||||
components as attributes.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
It can be given, in addition to any of the
|
||||
above arguments, a <varname>msg</varname>
|
||||
keyword argument that will use the
|
||||
attributes of msg as defaults. This
|
||||
exists to make it easier to copy messages,
|
||||
since the class is immutable.
|
||||
</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>__str__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This returns the message in a string form suitable
|
||||
for sending to a server.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>__repr__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This returns the message in a form suitable for
|
||||
<function>eval()</function>, assuming the name
|
||||
<varname>IrcMsg</varname> is in your namespace and
|
||||
is bound to this class.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<para>
|
||||
The following attributes are the meat of this class. These
|
||||
are generally what you'll be looking at with
|
||||
<varname>IrcMsg</varname>s.
|
||||
</para>
|
||||
<variablelist>
|
||||
<title>Interesting attributes</title>
|
||||
<varlistentry>
|
||||
<term>command</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This is the command of the
|
||||
<varname>IrcMsg</varname> –
|
||||
<literal>PRIVMSG</literal>,
|
||||
<literal>NOTICE</literal>,
|
||||
<literal>WHOIS</literal>,
|
||||
etc.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>args</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This is a tuple of the arguments to the
|
||||
<varname>IrcMsg</varname>. Some messages have
|
||||
arguments, some don't, depending on what command
|
||||
they are. You are, of course, always assured that
|
||||
<varname>args</varname> exists and is a tuple,
|
||||
though it might be empty.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>prefix</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This is the hostmask of the person/server the
|
||||
message is from. In general, you won't be setting
|
||||
this on your outgoing messages, but incoming
|
||||
messages will always have one. This is the whole
|
||||
hostmask; if the message was received from a
|
||||
server, it'll be the server's hostmask; if the
|
||||
message was received from a user, it'll be the
|
||||
whole user hostmask. In that case, however, it's
|
||||
also parsed out into the
|
||||
<varname>nick</varname>/<varname>user</varname>/<varname>host</varname>
|
||||
attributes, which are probably more useful to
|
||||
check for many purposes.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>nick</term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the message was sent by a user, this will be
|
||||
the nick of the user. If it was sent by a server,
|
||||
this will be the server's name (something like
|
||||
<literal>calvino.freenode.net</literal> or
|
||||
similar).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>user</term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the message was sent by a user, this will be
|
||||
the user string of the user – what they put
|
||||
into their IRC client for their "full name." If
|
||||
it was sent by a server, it'll be the server's
|
||||
name, again.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>host</term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the message was sent by a user, this will be
|
||||
the host portion of their hostmask. If it was
|
||||
sent by a server, it'll be the server's name (yet
|
||||
again :))
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title><classname>irclib.Irc</classname>
|
||||
<para>
|
||||
This is the object to handle everything about IRC except the
|
||||
actual connection to the server itself.
|
||||
(<emphasis>NOTE</emphasis> that the object actually received
|
||||
by commands in subclasses of
|
||||
<classname>callbacks.Privmsg</classname> is an
|
||||
<classname>IrcObjectProxy</classname>, which is described
|
||||
later. It augments the following interface with several
|
||||
methods of its own to help plugin authors.)
|
||||
</para>
|
||||
<variablelist>
|
||||
<title>Interesting methods</title>
|
||||
<varlistentry>
|
||||
<term>queueMsg</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Queues a message for sending to the server. The
|
||||
queue is generally FIFO, but it does prioritize
|
||||
messages based on their command.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>sendMsg</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Queues a message for sending to the server prior
|
||||
to any messages in the normal queue. This is
|
||||
exactly a FIFO queue, no reordering is done at
|
||||
all.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<!--<note>
|
||||
<para>
|
||||
The following two methods are the most important for
|
||||
people writing new <varname>IrcDriver</varname>s.
|
||||
Otherwise, you really don't need to pay attention to
|
||||
them.
|
||||
</para>
|
||||
</note>-->
|
||||
<varlistentry>
|
||||
<term>feedMsg</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Feeds the <varname>Irc</varname> object a message
|
||||
for it handle appropriately, as well as passing it
|
||||
on to callbacks.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>takeMsg</term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the <varname>Irc</varname> object has a message
|
||||
it's ready to send to the server, this will return
|
||||
it. Otherwise, it will return
|
||||
<literal>None</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<!--<note>
|
||||
<para>
|
||||
The next several methods are of far more marginal
|
||||
utility. But someone may need them, so they're
|
||||
documented here.
|
||||
</para>
|
||||
</note>-->
|
||||
<varlistentry>
|
||||
<term>addCallback</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Takes a callback to add to the list of callbacks
|
||||
in the <varname>Irc</varname> object. See the
|
||||
interface for <varname>IrcCallback</varname> for
|
||||
more information.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>getCallback</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Gets a callback by name, if it is in the
|
||||
<varname>Irc</varname> object's list of callbacks.
|
||||
If it it isn't, returns <literal>None</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>removeCallback</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Removes a callback by name. Returns a list of the
|
||||
callbacks removed (since it is technically
|
||||
possible to have multiple callbacks with the same
|
||||
name. This list may be empty.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>__init__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Requires a <varname>nick</varname>. Optional
|
||||
arguments include <varname>user</varname> and
|
||||
<varname>ident</varname>, which default to the
|
||||
nick given, <varname>password</varname>, which
|
||||
defaults to the empty password, and
|
||||
<varname>callbacks</varname>, a list of callbacks
|
||||
(which defaults to nothing, an empty list).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>reset</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Resets the <varname>Irc</varname> object to its
|
||||
original state, as well as sends a
|
||||
<function>reset()</function> to every callbacks.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>die</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Kills the IRC object and all its callbacks.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<variablelist>
|
||||
<title>Interesting attributes</title>
|
||||
<varlistentry>
|
||||
<term>nick</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The current nick of the bot.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>prefix</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The current prefix of the bot.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>server</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The current server the bot is connected to.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>network</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The current network name the bot is connected to.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>afterConnect</term>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>False</literal> until the bot has
|
||||
received a command sent after the connection is
|
||||
finished – 376, 377, or 422.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>state</term>
|
||||
<listitem>
|
||||
<para>
|
||||
An <varname>IrcState</varname> object for this
|
||||
particular connection. See the interface for the
|
||||
<varname>IrcState</varname> object for more
|
||||
information.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title><classname>irclib.IrcCallback</classname></title>
|
||||
<variablelist>
|
||||
<title>Interesting Methods</title>
|
||||
<varlistentry>
|
||||
<term>name</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns the name of the callback. The default
|
||||
implementation simply returns the name of the
|
||||
class.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>__call__</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called by the <varname>Irc</varname> object with
|
||||
itself and the message whenever a message is fed
|
||||
to the <varname>Irc</varname> object. Nothing is
|
||||
done with the return value.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>inFilter</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called by the <varname>Irc</varname> object with
|
||||
itself and the message whenever a message is fed
|
||||
to the <varname>Irc</varname> object. The return
|
||||
value should be an <varname>IrcMsg</varname>
|
||||
object to be passed to the next callback in the
|
||||
<varname>Irc</varname>'s list of callbacks. If
|
||||
<literal>None</literal> is returned, all
|
||||
processing stops. This gives callbacks an
|
||||
oppurtunity to "filter" incoming messages before
|
||||
general callbacks are given them.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>outFilter</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Basically equivalent to
|
||||
<varname>inFilter</varname>, except instead of
|
||||
being called on messages as they enter the
|
||||
<varname>Irc</varname> object, it's called on
|
||||
messages as they leave the <varname>Irc</varname>
|
||||
object.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>die</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called when the parent <varname>Irc</varname> is
|
||||
told to die. This gives callbacks an oppurtunity
|
||||
to close open files, network connections, or
|
||||
databases before they're deleted.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>reset</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called when the parent <varname>Irc</varname> is
|
||||
told to reset (which is generally when
|
||||
reconnecting to the server). Most callbacks don't
|
||||
need to define this.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<variablelist>
|
||||
<title>Interesting attributes</title>
|
||||
<varlistentry>
|
||||
<term>priority</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Determines the priority of the callback in the
|
||||
<varname>Irc</varname> object's list of callbacks.
|
||||
Defaults to <literal>99</literal>; the valid range
|
||||
includes <literal>0</literal> through
|
||||
<literal>sys.maxint-1</literal> (don't use
|
||||
<literal>sys.maxint</literal> itself, that's
|
||||
reserved for the <varname>Misc</varname> plugin).
|
||||
The lower the number, the higher the priority.
|
||||
High priority callbacks are called earlier in the
|
||||
<varname>inFilter</varname> cycle, earlier in the
|
||||
<varname>__call__</varname> cycle, and later in
|
||||
the <varname>outFilter</varname> cycle –
|
||||
basically, they're given the first chances on the
|
||||
way in and the last chances on the way out.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title><classname>callbacks.IrcObjectProxy</classname></title>
|
||||
<para>
|
||||
<classname>IrcObjectProxy</classname> is a proxy for an
|
||||
<classname>irclib.Irc</classname> instance that serves to
|
||||
provide a much fuller interface for handling replies and
|
||||
errors as well as to handle the nesting of commands. This is
|
||||
what you'll be dealing with almost all the time when writing
|
||||
commands; when writing <function>doCommand</function> methods
|
||||
(the kind you read about in the interface description of
|
||||
<classname>irclib.IrcCallback</classname>) you'll be dealing
|
||||
with plain old <classname>irclib.Irc</classname> objects.
|
||||
</para>
|
||||
<variablelist>
|
||||
<title>Interesting methods</title>
|
||||
<varlistentry>
|
||||
<term>reply</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called to reply to the current message with a
|
||||
string that is to be the reply. Uses the
|
||||
<function>queueMsg</function> command discussed in
|
||||
the <classname>irclib.Irc</classname> section.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>replySuccess</term>
|
||||
<term>replyError</term>
|
||||
<listitem>
|
||||
<para>
|
||||
These reply with the configured responses for
|
||||
success and generic error, respectively. If an
|
||||
additional argument is given, it's (intelligently)
|
||||
appended to the generic message to be more
|
||||
specific.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>error</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Called to send an error reply to the current
|
||||
message; not only does the response indicate an
|
||||
error, but commands that error out break the
|
||||
nested-command chain, which is generally useful
|
||||
for not confusing the user :)
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>errorNoCapability</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Like <function>error</function>, except it accepts
|
||||
the capability that's missing and integrates it
|
||||
into the configured error message for such things.
|
||||
Also accepts an additional string for a more
|
||||
descriptive message, if that's what you want.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>errorPossibleBug</term>
|
||||
<term>errorNotRegistered</term>
|
||||
<term>errorNoUser</term>
|
||||
<term>errorRequiresPrivacy</term>
|
||||
<listitem>
|
||||
<para>
|
||||
These methods reply with the appropriate
|
||||
configured error message for the conditions in
|
||||
their names; they all take an additional arguments
|
||||
to be more specific about the conditions they
|
||||
indicate, but this argument is very rarely
|
||||
necessary.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>getRealIrc</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns the actual <classname>Irc</classname>
|
||||
object being proxied for.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
</sect1>
|
||||
</article>
|
||||
|
||||
|
633
docs/DocBook/plugin-example.sgml
Normal file
633
docs/DocBook/plugin-example.sgml
Normal file
@ -0,0 +1,633 @@
|
||||
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||
|
||||
<article>
|
||||
<articleinfo>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Jeremiah</firstname>
|
||||
<surname>Fincher</surname>
|
||||
</author>
|
||||
<editor>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>DiPaolo</surname>
|
||||
<contrib>DocBook translator</contrib>
|
||||
</editor>
|
||||
</authorgroup>
|
||||
<title>Supybot plugin author example</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>0.1</revnumber>
|
||||
<date>13 Sep 2003</date>
|
||||
<revremark>Initial revision</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.2</revnumber>
|
||||
<date>14 Sep 2003</date>
|
||||
<revremark>Converted to DocBook</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.3</revnumber>
|
||||
<date>24 Nov 2003</date>
|
||||
<revremark>
|
||||
Updated to match EXAMPLE included with 0.75.0
|
||||
</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.4</revnumber>
|
||||
<date>26 Feb 2004</date>
|
||||
<revremark>Converted to use Supybot DTD</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>0.5</revnumber>
|
||||
<date>4 Sep 2004</date>
|
||||
<revremark>Updated Docbook translation</revremark>
|
||||
</revision>
|
||||
</revhistory>
|
||||
</articleinfo>
|
||||
<sect1>
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
Ok, so you want to write a callback 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.
|
||||
</para>
|
||||
<para>
|
||||
So have you used supybot? If not, you need to go use it, get a
|
||||
feel for it, see how the various commands work and such.
|
||||
</para>
|
||||
<para>
|
||||
So now that we know you've used supybot, we'll start getting into
|
||||
details.
|
||||
</para>
|
||||
</sect1>
|
||||
<sect1>
|
||||
<title>Creating your own plugin</title>
|
||||
<sect2>
|
||||
<title>
|
||||
Using <script>scripts/newplugin.py</script>
|
||||
</title>
|
||||
<para>
|
||||
First, the easiest way to start writing a module is to use the
|
||||
wizard provided, <script>scripts/newplugin.py</script>.
|
||||
Here's an example session:
|
||||
</para>
|
||||
<screen>
|
||||
functor% scripts/newplugin.py
|
||||
What should the name of the plugin be? Random
|
||||
Supybot offers two major types of plugins: command-based and regexp-
|
||||
based. Command-based plugins are the kind of plugins you've seen most
|
||||
when you've used supybot. They're also the most featureful and
|
||||
easiest to write. Commands can be nested, for instance, whereas
|
||||
regexp-based callbacks can't do nesting. That doesn't mean that
|
||||
you'll never want regexp-based callbacks. They offer a flexibility
|
||||
that command-based callbacks don't offer; however, they don't tie into
|
||||
the whole system as well. If you need to combine a command-based
|
||||
callback with some regexp-based methods, you can do so by subclassing
|
||||
callbacks.PrivmsgCommandAndRegexp and then adding a class-level
|
||||
attribute "regexps" that is a sets.Set of methods that are regexp-
|
||||
based. But you'll have to do that yourself after this wizard is
|
||||
finished :)
|
||||
Do you want a command-based plugin or a regexp-based plugin? [command/
|
||||
regexp] command
|
||||
Sometimes you'll want a callback to be threaded. If its methods
|
||||
(command or regexp-based, either one) will take a signficant 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
|
||||
Your new plugin template is in plugins/Random.py
|
||||
functor%
|
||||
</screen>
|
||||
<para>
|
||||
So that's what it looks like. Now let's look at the source
|
||||
code (if you'd like to look at it in your programming editor,
|
||||
the whole plugin is available as
|
||||
<filename>examples/Random.py</filename>):
|
||||
</para>
|
||||
<programlisting>
|
||||
#!/usr/bin/env python
|
||||
|
||||
###
|
||||
# Copyright (c) 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.
|
||||
###
|
||||
|
||||
"""
|
||||
Add the module docstring here. This will be used by the setup.py script.
|
||||
"""
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = ''
|
||||
|
||||
import supybot.plugins as plugins
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
def configure(onStart, afterConnect, advanced):
|
||||
# This will be called by setup.py 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 questions import expect, anything, something, yn
|
||||
conf.registerPlugin('Random', True)
|
||||
|
||||
class Random(callbacks.Privmsg):
|
||||
pass
|
||||
|
||||
|
||||
Class = Random
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
||||
</programlisting>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Customizing the boilerplate code</title>
|
||||
<para>
|
||||
So a few notes, before we customize it.
|
||||
</para>
|
||||
<para>
|
||||
You'll probably want to change the copyright notice to be your
|
||||
name. It wouldn't stick even if you kept my name, so you
|
||||
might as well :)
|
||||
</para>
|
||||
<para>
|
||||
Describe what you want the plugin to do in the docstring.
|
||||
This is used in <script>scripts/setup.py</script> in
|
||||
order to explain to the user the purpose of the module. It's
|
||||
also returned when someone asks the bot for help for a given
|
||||
module (instead of help for a certain command). We'll change
|
||||
this one to <literal>"Lots of stuff relating to random
|
||||
numbers."</literal>
|
||||
</para>
|
||||
<para>
|
||||
Then there are the imports. The
|
||||
<module>callbacks</module>
|
||||
module is used (the class you're given subclasses
|
||||
<classname>callbacks.Privmsg</classname>) but the
|
||||
<module>privmsgs</module> module isn't used. That's
|
||||
alright; we can almost guarantee you'll use it, so we go ahead
|
||||
and add the import to the template.
|
||||
</para>
|
||||
<para>
|
||||
Then you see a <function>configure</function> function. This
|
||||
the function that's called when users decide to add your
|
||||
module in <script>scripts/setup.py</script>. You'll
|
||||
note that by default it simply registers the plugin to be
|
||||
automatically loaded on startup. For many
|
||||
plugins this is all you need; for more complex plugins, you
|
||||
might need to ask questions and add commands based on the
|
||||
answers.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Digging in: customizing the plugin class</title>
|
||||
<para>
|
||||
Now comes the meat of the plugin: the plugin class.
|
||||
</para>
|
||||
<para>
|
||||
What you're given is a skeleton: a simple subclass of
|
||||
<classname>callbacks.Privmsg</classname> for you to start
|
||||
with. Now let's add a command.
|
||||
</para>
|
||||
<para>
|
||||
I don't know what you know about random number generators, but
|
||||
the short of it is that they start at a certain number (a
|
||||
seed) and they continue (via some somewhat
|
||||
complicated/unpredictable algorithm) from there. This seed
|
||||
(and the rest of the sequence, really) is all nice and
|
||||
packaged up in Python's <module>random</module> module, the
|
||||
<varname>Random</varname> object. So the first thing we're
|
||||
going to have to do is give our plugin a
|
||||
<varname>Random</varname> object.
|
||||
</para>
|
||||
<para>
|
||||
Normally, when we want to give instances of a class an object,
|
||||
we'll do so in the <function>__init__</function> method. And
|
||||
that works great for plugins, too. The one thing you have to
|
||||
be careful of is that you call the superclass
|
||||
<function>__init__</function> method at the end of your own
|
||||
<function>__init__</function>. So to add this
|
||||
<classname>random.Random</classname> object to our plugin, we
|
||||
can replace the <keyword>pass</keyword> statement with
|
||||
this:
|
||||
</para>
|
||||
<programlisting>
|
||||
def __init__(self):
|
||||
self.rng = random.Random()
|
||||
callbacks.Privmsg.__init__(self)
|
||||
</programlisting>
|
||||
<para>
|
||||
(<varname>rng</varname>is an abbreviation for "random number
|
||||
generator," in case you were curious)
|
||||
</para>
|
||||
<para>
|
||||
Do be careful not to give your <function>__init__</function>
|
||||
any arguments (other than <varname>self</varname>, of course).
|
||||
There's no way anything will ever get to them! If you have
|
||||
some sort of initial values you need to get to your plugin
|
||||
before it can do anything interesting, you should get those
|
||||
values from the registry.
|
||||
</para>
|
||||
<para>
|
||||
There's an easier way to get our plugin to have its own rng
|
||||
than to define an <function>__init__</function>. Plugins are
|
||||
unique among classes because we're always certain that there
|
||||
will only be one instance -- supybot doesn't allow us to load
|
||||
multiple instances of a single plugin. So instead of adding
|
||||
the rng in <function>__init__</function>, we can just add it
|
||||
as a attribute to the class itself. Like so (replacing the
|
||||
<function>pass</function> statement again):
|
||||
</para>
|
||||
<programlisting>
|
||||
rng = random.Random()
|
||||
</programlisting>
|
||||
<para>
|
||||
And we save two lines of code and make our code a little more
|
||||
clear :)
|
||||
</para>
|
||||
<para>
|
||||
Now that we have an RNG, we need some way to get random
|
||||
numbers. So first, we'll add a command that simply gets the
|
||||
next random number and gives it back to the user. It takes no
|
||||
arguments, of course (what would you give it?). Here's the
|
||||
command, and I'll follow that with the explanation of what
|
||||
each part means.
|
||||
</para>
|
||||
<programlisting>
|
||||
def random(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Returns the next random number generated by the random number
|
||||
generator.
|
||||
"""
|
||||
irc.reply(str(self.rng.random()))
|
||||
</programlisting>
|
||||
<para>
|
||||
And that's it! Pretty simple, huh? Anyway, you're probably
|
||||
wondering what all that <emphasis>means</emphasis>. We'll
|
||||
start with the <keyword>def</keyword> statement:
|
||||
</para>
|
||||
<programlisting>
|
||||
def random(self, irc, msg, args):
|
||||
</programlisting>
|
||||
<para>
|
||||
What that does is define a command
|
||||
<botcommand>random</botcommand>. You can call it by saying
|
||||
"@random" (or whatever prefix character your specific bot
|
||||
uses). The arguments are a bit less obvious.
|
||||
<varname>self</varname> is self-evident (hah!).
|
||||
<varname>irc</varname> is the <classname>Irc</classname>
|
||||
object passed to the command; <varname>msg</varname> is the
|
||||
original <classname>IrcMsg</classname> object. But you're
|
||||
really not going to have to deal with either of these too much
|
||||
(with the exception of calling <function>irc.reply</function>
|
||||
or <function>irc.error</function>). What you're
|
||||
<emphasis>really</emphasis> interested in is the
|
||||
<varname>args</varname> arg. That is a list of all the
|
||||
arguments passed to your command, pre-parsed and already
|
||||
evaluated (i.e., you never have to worry about nested
|
||||
commands, or handling double quoted strings, or splitting on
|
||||
whitespace – the work has already been done for you).
|
||||
You can read about the <classname>Irc</classname> object in
|
||||
<filename>irclib.py</filename> (you won't find
|
||||
<function>.reply</function> or <function>.error</function>
|
||||
there, though, because you're actually getting an
|
||||
<classname>IrcObjectProxy</classname>, but that's beyond the
|
||||
level we want to describe here :)). You can read about the
|
||||
<varname>msg</varname> object in
|
||||
<filename>ircmsgs.py</filename>. But again, you'll very
|
||||
rarely be using these objects.
|
||||
</para>
|
||||
<para>
|
||||
(In case you're curious, the answer is yes, you
|
||||
<emphasis>must</emphasis> name your arguments <varname>(self,
|
||||
irc, msg, args)</varname>. The names of those arguments is
|
||||
one of the ways that supybot uses to determine which methods
|
||||
in a plugin class are commands and which aren't. And while
|
||||
we're talking about naming restrictions, all your commands
|
||||
should be named in all-lowercase with no underscores. Before
|
||||
calling a command, supybot always converts the command name to
|
||||
lowercase and removes all dashes and underscores. On the
|
||||
other hand, you now know an easy way to make sure a method is
|
||||
never called (even if its arguments are <varname>(self, irc,
|
||||
msg, args)</varname>, however unlikely that may be). Just
|
||||
name it with an underscore or an uppercase letter in it :))
|
||||
</para>
|
||||
<para>
|
||||
You'll also note that the docstring is odd. The wonderful
|
||||
thing about the supybot framework is that it's easy to write
|
||||
complete commands with help and everything: the docstring
|
||||
<emphasis>is</emphasis> the help! Given the above docstring,
|
||||
this is what a supybot does:
|
||||
</para>
|
||||
<ircsession>
|
||||
<jemfinch> @help random
|
||||
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||
next random number from the random number generator.
|
||||
</ircsession>
|
||||
<para>
|
||||
Now on to the actual body of the function:
|
||||
</para>
|
||||
<programlisting>
|
||||
irc.reply(msg, str(self.rng.random()))
|
||||
</programlisting>
|
||||
<para>
|
||||
<function>irc.reply</function> simply takes one simple
|
||||
argument: a string The string is the reply to be sent. Don't
|
||||
worry about length restrictions or anything
|
||||
– if the string you want to send is too big for an IRC
|
||||
message (and oftentimes that turns out to be the case :)) the
|
||||
Supybot framework handles that entirely transparently to you.
|
||||
Do make sure, however, that you give
|
||||
<function>irc.reply</function> a string. It doesn't take
|
||||
anything else (sometimes even unicode fails!). That's why we
|
||||
have "str(self.rng.random())" instead of simply
|
||||
"self.rng.random()" – we had to give
|
||||
<function>irc.reply</function> a string.
|
||||
</para>
|
||||
<para>
|
||||
Anyway, now that we have an RNG, we have a need for seed! Of
|
||||
course, Python gives us a good seed already (it uses the
|
||||
current time as a seed if we don't give it one) but users
|
||||
might want to be able to repeat "random" sequences, so letting
|
||||
them set the seed is a good thing. So we'll add a seed
|
||||
command to give the RNG a specific seed:
|
||||
</para>
|
||||
<programlisting>
|
||||
def seed(self, irc, msg, args):
|
||||
"""<seed>
|
||||
|
||||
Sets the seed of the random number generator. <seed> must be
|
||||
an int or a long.
|
||||
"""
|
||||
seed = privmsgs.getArgs(args)
|
||||
try:
|
||||
seed = long(seed)
|
||||
except ValueError:
|
||||
# It wasn't a valid long!
|
||||
irc.error(msg, '<seed> must be a valid int or long.')
|
||||
return
|
||||
self.rng.seed(seed)
|
||||
irc.replySuccess()
|
||||
</programlisting>
|
||||
<para>
|
||||
So this one's a bit more complicated. But it's still pretty
|
||||
simple. The method name is <botcommand>seed</botcommand> so
|
||||
that'll be the command name. The arguments are the same, the
|
||||
docstring is of the same form, so we don't need to go over
|
||||
that again. The body of the function, however, is
|
||||
significantly different.
|
||||
</para>
|
||||
<para>
|
||||
<function>privmsgs.getArgs</function> is a function you're
|
||||
going to be seeing a lot of when you write plugins for
|
||||
supybot. What it does is basically give you the right number
|
||||
of arguments for your comamnd. In this case, we want one
|
||||
argument. But we might have been given any number of
|
||||
arguments by the user. So
|
||||
<function>privmsgs.getArgs</function> joins them
|
||||
appropriately, leaving us with one single "seed" argument (by
|
||||
default, it returns one argument as a single value; more
|
||||
arguments are returned in a tuple/list). Yes, we could've
|
||||
just said "seed = args[0]" and gotten the first argument, but
|
||||
what if the user didn't pass us an argument at all? Then
|
||||
we've got to catch the <classname>IndexError</classname> from
|
||||
<varname>args[0]</varname> and complain to the user about it.
|
||||
<function>privmsgs.getArgs</function>, on the other hand,
|
||||
handles all that for us. If the user didn't give us enough
|
||||
arguments, it'll reply with the help string for the command,
|
||||
thus saving us the effort.
|
||||
</para>
|
||||
<para>
|
||||
So we have the seed from
|
||||
<function>privmsgs.getArgs</function>. But it's a string.
|
||||
The next three lines is pretty darn obvious: we're just
|
||||
converting the string to a int of some sort. But if it's not,
|
||||
that's when we're going to call
|
||||
<function>irc.error</function>. It has the same interface as
|
||||
we saw before in <function>irc.reply</function>, but it makes
|
||||
sure to remind the user that an error has been encountered
|
||||
(currently, that means it puts <literal>"Error: "</literal> at
|
||||
the beginning of the message). After erroring, we return.
|
||||
It's important to remember this <keyword>return</keyword>
|
||||
here; otherwise, we'll just keep going down through the
|
||||
function and try to use this <varname>seed</varname> variable
|
||||
that never got assigned. A good general rule of thumb is that
|
||||
any time you use <function>irc.error</function>, you'll want
|
||||
to return immediately afterwards.
|
||||
</para>
|
||||
<para>
|
||||
Then we set the seed – that's a simple function on our
|
||||
rng object. Assuming that succeeds (and doesn't raise an
|
||||
exception, which it shouldn't, because we already read the
|
||||
documentation and know that it should work) we reply to say
|
||||
that everything worked fine. That's what
|
||||
<function>irc.replySuccess</function> says. By default, it
|
||||
has the very dry (and appropriately robot-like) "The operation
|
||||
succeeded." but you're perfectly welcome to customize it
|
||||
yourself – the registry was written to be modified!
|
||||
</para>
|
||||
<para>
|
||||
So that's a bit more complicated command. But we still
|
||||
haven't dealt with multiple arguments. Let's do that
|
||||
next.
|
||||
</para>
|
||||
<para>
|
||||
So these random numbers are useful, but they're not the kind
|
||||
of random numbers we usually want in Real Life. In Real Life,
|
||||
we like to tell someone to "pick a number between 1 and 10."
|
||||
So let's write a function that does that. Of course, we won't
|
||||
hardcode the 1 or the 10 into the function, but we'll take
|
||||
them as arguments. First the function:
|
||||
</para>
|
||||
<programlisting>
|
||||
def range(self, irc, msg, args):
|
||||
"""<start> <end>
|
||||
|
||||
Returns a number between <start> and <end>, inclusive (i.e., the number
|
||||
can be either of the endpoints.
|
||||
"""
|
||||
(start, end) = privmsgs.getArgs(args, required=2)
|
||||
try:
|
||||
end = int(end)
|
||||
start = int(start)
|
||||
except ValueError:
|
||||
irc.error(msg, '<start> and <end> must both be integers.')
|
||||
return
|
||||
# .randrange() doesn't include the endpoint, so we use end+1.
|
||||
irc.reply(msg, str(self.rng.randrange(start, end+1)))
|
||||
</programlisting>
|
||||
<para>
|
||||
Pretty simple. This is becoming old hat by now. The only new
|
||||
thing here is the call to
|
||||
<function>privmsgs.getArgs</function>. We have to make sure,
|
||||
since we want two values, to pass a keyword parameter
|
||||
"required" into <function>privmsgs.getArgs</function>. Of
|
||||
course, <function>privmsgs.getArgs</function> handles all the
|
||||
checking for missing arguments and whatnot so we don't have
|
||||
to.
|
||||
</para>
|
||||
<para>
|
||||
The <classname>Random</classname> object we're using offers us
|
||||
a "sample" method that takes a sequence and a number (we'll
|
||||
call it <varname>N</varname>) and returns a list of
|
||||
<varname>N</varname> items taken randomly from the sequence.
|
||||
So I'll show you an example that takes advantage of multiple
|
||||
arguments but doesn't use
|
||||
<function>privmsgs.getArgs</function> (and thus has to handle
|
||||
its own errors if the number of arguments isn't right).
|
||||
Here's the code:
|
||||
</para>
|
||||
<programlisting>
|
||||
def sample(self, irc, msg, args):
|
||||
"""<number of items> [<text> ...]
|
||||
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
n = int(args.pop(0))
|
||||
except IndexError: # raised by .pop(0)
|
||||
raise callbacks.ArgumentError
|
||||
except ValueError:
|
||||
irc.error('<number of items> must be an integer.')
|
||||
return
|
||||
if n > len(args):
|
||||
irc.error('<number of items> must be less than the number '
|
||||
'of arguments.')
|
||||
return
|
||||
sample = self.rng.sample(args, n)
|
||||
irc.reply(utils.commaAndify(map(repr, sample)))
|
||||
</programlisting>
|
||||
<para>
|
||||
Most everything here is familiar. The difference between this
|
||||
and the previous examples is that we're dealing with
|
||||
<varname>args</varname> directly, rather than through
|
||||
<function>getArgs</function>. Since we already have the
|
||||
arguments in a list, it doesn't make any sense to have
|
||||
<function>privmsgs.getArgs</function> smush them all together
|
||||
into a big long string that we'll just have to re-split. But
|
||||
we still want the nice error handling of
|
||||
<function>privmsgs.getArgs</function>. So what do we do? We
|
||||
raise <classname>callbacks.ArgumentError</classname>! That's
|
||||
the secret juju that <function>privmsgs.getArgs</function> is
|
||||
doing; now we're just doing it ourself. Someone up our
|
||||
callchain knows how to handle it so a neat error message is
|
||||
returned. So in this function, if
|
||||
<function>.pop(0)</function> fails, we weren't given enough
|
||||
arguments and thus need to tell the user how to call us.
|
||||
</para>
|
||||
<para>
|
||||
So we have the args, we have the number, we do a simple call
|
||||
to <function>random.sample</function> and then we do this
|
||||
funky <function>utils.commaAndify</function> to it. Yeah, so
|
||||
I was running low on useful names :) Anyway, what it does is
|
||||
take a list of strings and return a string with them joined by
|
||||
a comma, the last one being joined with a comma and "and". So
|
||||
the list ['foo', 'bar', 'baz'] becomes "foo, bar, and baz".
|
||||
It's pretty useful for showing the user lists in a useful
|
||||
form. We map the strings with <function>repr()</function>
|
||||
first just to surround them with quotes.
|
||||
</para>
|
||||
<para>
|
||||
So we have one more example. Yes, I hear your groans, but
|
||||
it's pedagogically useful :) This time we're going to write a
|
||||
command that makes the bot roll a die. It'll take one
|
||||
argument (the number of sides on the die) and will respond
|
||||
with the equivalent of "/me rolls a __" where __ is the number
|
||||
the bot rolled. So here's the code:
|
||||
</para>
|
||||
<programlisting>
|
||||
def diceroll(self, irc, msg, args):
|
||||
"""[<number of sides>]
|
||||
|
||||
Rolls a die with <number of sides> sides. The default number
|
||||
of sides is 6.
|
||||
"""
|
||||
try:
|
||||
n = privmsgs.getArgs(args, required=0, optional=1)
|
||||
if not n:
|
||||
n = 6
|
||||
n = int(n)
|
||||
except ValueError:
|
||||
irc.error(msg, 'Dice have integer numbers of sides. Use one.')
|
||||
return
|
||||
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||
irc.reply(s, action=True)
|
||||
</programlisting>
|
||||
<para>
|
||||
There's a lot of stuff you haven't seen before in there. The
|
||||
most important, though, is the first thing you'll notice
|
||||
that's different: the <function>privmsg.getArgs</function>
|
||||
call. Here we're offering a default argument in case the user
|
||||
is too lazy to supply one (or just wants a nice, standard
|
||||
six-sided die :)) <function>privmsgs.getArgs</function>
|
||||
supports that; we'll just tell it that we don't
|
||||
<emphasis>need</emphasis> any arguments (via
|
||||
<varname>required=0</varname>) and that we <emphasis>might
|
||||
like</emphasis> one argument (<varname>optional=1</varname>).
|
||||
If the user provides an argument, we'll get it -- if they
|
||||
don't, we'll just get an empty string. Hence the "if not n: n
|
||||
= 6", where we provide the default.
|
||||
</para>
|
||||
<para>
|
||||
You'll also note that <function>irc.reply</function> was given
|
||||
a keyword argument here, <varname>action</varname>. This
|
||||
means that the reply is to be made as an action rather than a
|
||||
normal reply.
|
||||
</para>
|
||||
<para>
|
||||
So that's our plugin. 5 commands, each building in
|
||||
complexity. You should now be able to write most anything you
|
||||
want to do in Supybot. Except regexp-based plugins, but
|
||||
that's a story for another day (and those aren't nearly as
|
||||
cool as these command-based callbacks anyway :)). Now we need
|
||||
to flesh it out to make it a full-fledged plugin.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Using the registry in your plugin</title>
|
||||
<para>
|
||||
TODO: Describe the registry and how to write a proper plugin
|
||||
configure function.
|
||||
</para>
|
||||
</sect2>
|
||||
<para>
|
||||
We've written our own plugin from scratch (well, from the
|
||||
boilerplate that we got from
|
||||
<script>scripts/newplugin.py</script> :)) and
|
||||
survived! Now go write more plugins for supybot, and send
|
||||
them to me so I can use them too :)
|
||||
</para>
|
||||
</sect1>
|
||||
</article>
|
46
docs/DocBook/supybot-html.dsl
Normal file
46
docs/DocBook/supybot-html.dsl
Normal file
@ -0,0 +1,46 @@
|
||||
(define %stylesheet% "../stylesheets/supybot.css")
|
||||
|
||||
(element botcommand
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "botcommand"))
|
||||
(process-children)))
|
||||
|
||||
(element plugin
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "plugin"))
|
||||
(process-children)))
|
||||
|
||||
(element flag
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "flag"))
|
||||
(process-children)))
|
||||
|
||||
(element nick
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "nick"))
|
||||
(process-children)))
|
||||
|
||||
(element capability
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "capability"))
|
||||
(process-children)))
|
||||
|
||||
(element registrygroup
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "registrygroup"))
|
||||
(process-children)))
|
||||
|
||||
(element ircsession
|
||||
(make element gi: "pre"
|
||||
attributes: '(("class" "ircsession"))
|
||||
(process-children)))
|
||||
|
||||
(element script
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "script"))
|
||||
(process-children)))
|
||||
|
||||
(element channel
|
||||
(make element gi: "span"
|
||||
attributes: '(("class" "channel"))
|
||||
(process-children)))
|
43
docs/DocBook/supybot-print.dsl
Normal file
43
docs/DocBook/supybot-print.dsl
Normal file
@ -0,0 +1,43 @@
|
||||
(define %mono-font-family% "Courier New")
|
||||
|
||||
(element botcommand
|
||||
(make sequence
|
||||
font-family-name: %mono-font-family%))
|
||||
|
||||
(element plugin
|
||||
(make sequence
|
||||
font-weight: 'bold))
|
||||
|
||||
(element flag
|
||||
(make sequence
|
||||
font-posture: 'italic))
|
||||
|
||||
(element nick
|
||||
(make sequence
|
||||
font-family-name: %mono-font-family%))
|
||||
|
||||
(element capability
|
||||
(make sequence
|
||||
font-weight: 'bold))
|
||||
|
||||
(element registrygroup
|
||||
(make sequence
|
||||
font-weight: 'bold))
|
||||
|
||||
(element ircsession
|
||||
(make paragraph
|
||||
font-family-name: %mono-font-family%
|
||||
space-before: 12pt
|
||||
space-after: 12pt
|
||||
start-indent: 6pt
|
||||
lines: 'asis
|
||||
input-whitespace-treatment: 'preserve))
|
||||
|
||||
(element script
|
||||
(make sequence
|
||||
font-family-name: %mono-font-family%))
|
||||
|
||||
(element channel
|
||||
(make sequence
|
||||
font-weight: 'bold))
|
||||
|
41
docs/DocBook/supybot.css
Normal file
41
docs/DocBook/supybot.css
Normal file
@ -0,0 +1,41 @@
|
||||
.channel {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.botcommand {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.flag {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.nick {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.plugin {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.capability {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.registrygroup {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.ircsession {
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
background-color: #666666;
|
||||
}
|
||||
|
||||
.script {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.channel {
|
||||
font-weight: bold;
|
||||
}
|
23
docs/DocBook/supybot.dsl
Normal file
23
docs/DocBook/supybot.dsl
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE style-sheet PUBLIC "-//James Clark//DTD DSSSL Style Sheet//EN" [
|
||||
<!ENTITY print-ss PUBLIC
|
||||
"-//Norman Walsh//DOCUMENT DocBook Print Stylesheet//EN" CDATA DSSSL>
|
||||
<!ENTITY html-ss PUBLIC
|
||||
"-//Norman Walsh//DOCUMENT DocBook HTML Stylesheet//EN" CDATA DSSSL>
|
||||
<!ENTITY supybot-print SYSTEM "supybot-print.dsl">
|
||||
<!ENTITY supybot-html SYSTEM "supybot-html.dsl">
|
||||
]>
|
||||
|
||||
<style-sheet>
|
||||
<style-specification id="print" use="print-stylesheet">
|
||||
<style-specification-body>
|
||||
&supybot-print;
|
||||
</style-specification-body>
|
||||
</style-specification>
|
||||
<style-specification id="html" use="html-stylesheet">
|
||||
<style-specification-body>
|
||||
&supybot-html;
|
||||
</style-specification-body>
|
||||
</style-specification>
|
||||
<external-specification id="print-stylesheet" document="print-ss">
|
||||
<external-specification id="html-stylesheet" document="html-ss">
|
||||
</style-sheet>
|
139
docs/DocBook/supybot.dtd
Normal file
139
docs/DocBook/supybot.dtd
Normal file
@ -0,0 +1,139 @@
|
||||
<!-- Segregate all of our stuff into its own class for possible extension
|
||||
later and just because I wanted to write my own class :) -->
|
||||
<!ENTITY % local.supybot.tech.char.class "">
|
||||
<!ENTITY % supybot.tech.char.class "BotCommand|Plugin|Flag|Nick|Capability
|
||||
|RegistryGroup|Registry|Script
|
||||
|Channel %local.supybot.tech.char.class;">
|
||||
|
||||
<!-- Stuff that isn't supybot-specific, but it's python-related and no
|
||||
suitable element exists in the DocBook DTD -->
|
||||
<!ENTITY % local.python.tech.char.class "">
|
||||
<!ENTITY % python.tech.char.class "Module|Keyword
|
||||
%local.python.tech.char.class;">
|
||||
|
||||
<!-- Pretty much all of our stuff fits where stuff in the tech.char class
|
||||
goes, so we simply add our stuff using the local extension -->
|
||||
<!ENTITY % local.tech.char.class "|%supybot.tech.char.class;
|
||||
|%python.tech.char.class;">
|
||||
|
||||
<!-- linespecific is the same class as things like screen and programlisting,
|
||||
so it's added here to fit with the DocBook stuff (i.e., so putting an
|
||||
ircsession in where one of those previous two elements would be is a
|
||||
valid operation -->
|
||||
<!ENTITY % local.linespecific.class "|IrcSession">
|
||||
|
||||
<!-- Source the original DocBook DTD -->
|
||||
<!ENTITY % DocBookDTD PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
|
||||
%DocBookDTD;
|
||||
|
||||
<!ELEMENT BotCommand - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.botcommand.attrib "">
|
||||
<!ENTITY % botcommand.role.attrib "%role.attrib;">
|
||||
<!ATTLIST BotCommand
|
||||
%common.attrib;
|
||||
%local.botcommand.attrib;
|
||||
%botcommand.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Plugin - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.plugin.attrib "">
|
||||
<!ENTITY % plugin.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Plugin
|
||||
%common.attrib;
|
||||
%local.plugin.attrib;
|
||||
%plugin.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Flag - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.flag.attrib "">
|
||||
<!ENTITY % flag.role.attrib
|
||||
"
|
||||
flagtype (arg|noarg) #IMPLIED
|
||||
%role.attrib;"
|
||||
>
|
||||
<!ATTLIST Flag
|
||||
%common.attrib;
|
||||
%local.flag.attrib;
|
||||
%flag.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Nick - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.nick.attrib "">
|
||||
<!ENTITY % nick.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Nick
|
||||
%common.attrib;
|
||||
%local.nick.attrib;
|
||||
%nick.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Capability - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.capability.attrib "">
|
||||
<!ENTITY % capability.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Capability
|
||||
%common.attrib;
|
||||
%local.capability.attrib;
|
||||
%capability.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Comment - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.comment.attrib "">
|
||||
<!ENTITY % comment.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Comment
|
||||
%common.attrib;
|
||||
%local.comment.attrib;
|
||||
%comment.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT RegistryGroup - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.registrygroup.attrib "">
|
||||
<!ENTITY % registrygroup.role.attrib "%role.attrib;">
|
||||
<!ATTLIST RegistryGroup
|
||||
%common.attrib;
|
||||
%local.registrygroup.attrib;
|
||||
%registrygroup.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Registry - - ((RegistryGroup|Comment)+)>
|
||||
<!ENTITY % local.registry.attrib "">
|
||||
<!ENTITY % registry.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Registry
|
||||
%common.attrib;
|
||||
%local.registry.attrib;
|
||||
%registry.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT IrcSession - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.ircsession.attrib "">
|
||||
<!ENTITY % ircsession.role.attrib "%role.attrib;">
|
||||
<!ATTLIST IrcSession
|
||||
%common.attrib;
|
||||
%local.ircsession.attrib;
|
||||
%ircsession.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Script - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.script.attrib "">
|
||||
<!ENTITY % script.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Script
|
||||
%common.attrib;
|
||||
%local.script.attrib;
|
||||
%script.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Channel - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.channel.attrib "">
|
||||
<!ENTITY % channel.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Channel
|
||||
%common.attrib;
|
||||
%local.channel.attrib;
|
||||
%channel.role.attrib;
|
||||
>
|
||||
|
||||
<!ELEMENT Module - - ((%smallcptr.char.mix)+)>
|
||||
<!ENTITY % local.module.attrib "">
|
||||
<!ENTITY % module.role.attrib "%role.attrib;">
|
||||
<!ATTLIST Module
|
||||
%common.attrib;
|
||||
%local.module.attrib;
|
||||
%module.role.attrib;
|
||||
>
|
240
docs/FAQ
Normal file
240
docs/FAQ
Normal file
@ -0,0 +1,240 @@
|
||||
Q: Why does my bot not recognize me or tell me that I don't have the
|
||||
"owner" capability?
|
||||
|
||||
A: 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
|
||||
addhostmask" 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 "addhostmask 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).
|
||||
|
||||
|
||||
Q: What's a hostmask?
|
||||
|
||||
A: 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.
|
||||
|
||||
|
||||
Q: How do I make my Supybot op my users?
|
||||
|
||||
A: First, you'll have to make sure that your users register with the
|
||||
bot. They can do this with the "register" command. After they do
|
||||
so, you'll want to add the #channel,op capability to their user.
|
||||
Use the "channel addcapability" command to do this. After that,
|
||||
your users should be able to use the "op" command to get ops.
|
||||
|
||||
If you want your users to be auto-opped when they join the channel,
|
||||
you'll need to load the Enforcer plugin and turn its autoOp
|
||||
configuration variable on. Use the "config" command to do so.
|
||||
Here's an example of how to do these steps:
|
||||
|
||||
<jemfinch> I'm going to make an example session for giving
|
||||
you auto-ops, for our FAQ.
|
||||
<dunk1> ah ok ;]
|
||||
<jemfinch> First, I need you to register with supybot, using
|
||||
the "register" command (remember to send it in
|
||||
private).
|
||||
<dunk1> done
|
||||
<jemfinch> what name are you registered under?
|
||||
<dunk1> dunk1
|
||||
<jemfinch> ok, cool.
|
||||
<jemfinch> @channel addcapability dunk1 op
|
||||
<supybot> jemfinch: The operation succeeded.
|
||||
<jemfinch> now use the "op" command to get ops.
|
||||
<dunk1> @op
|
||||
--- supybot gives channel operator status to dunk1
|
||||
<dunk1> works!
|
||||
<dunk1> ;]
|
||||
<jemfinch> @load Enforcer
|
||||
<supybot> jemfinch: The operation succeeded.
|
||||
<jemfinch> @config channel supybot.plugins.Enforcer.autoOp On
|
||||
<supybot> jemfinch: The operation succeeded.
|
||||
<jemfinch> ok, now cycle the channel (part and then rejoin)
|
||||
<-- dunk1 (dunker@freebsd.nl) has left #supybot
|
||||
--> dunk1 (dunker@freebsd.nl) has joined #supybot
|
||||
--- supybot gives channel operator status to dunk1
|
||||
<jemfinch> cool, thanks :)
|
||||
|
||||
|
||||
Q: Can users with the "admin" capability change configuration
|
||||
variables?
|
||||
|
||||
A: 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.
|
||||
|
||||
|
||||
Q: Can Supybot do factoids?
|
||||
|
||||
A: Supybot most certainly can! In fact, we offer three full-fledged
|
||||
factoids-related plugins!
|
||||
|
||||
Factoids (written by jemfinch) is Supybot's original
|
||||
factoids-related plugin. It offers full integration with Supybot's
|
||||
nested commands as well as a complete 1:n key to factoid ratio,
|
||||
with lookup by individual number. Factoids also uses a
|
||||
channel-specific database instead of a global database, though
|
||||
that's configurable with the
|
||||
supybot.databases.plugins.channelSpecific configuration variable.
|
||||
|
||||
MoobotFactoids (written by Strike) is much more full-featured,
|
||||
offering users the ability to define factoids in a slightly more
|
||||
user-friendly way, as well as parsing factoids to handle <reply>,
|
||||
<action>, and alternations (defining a factoid "test" as
|
||||
"<reply>(foo|bar|baz)" will make the bot send "foo" or "bar" or
|
||||
"baz" to the channel (without the normal "test is " at the
|
||||
beginning)). If you're accustomed to Moobot's factoids or
|
||||
Blootbot's factoids, then this is the Factoids plugin for you.
|
||||
Unfortunately, due to the more natural definition syntax (required
|
||||
to be compatible with Moobot) you can't define Factoids with nested
|
||||
commands; you'll have to evaluate the command first and then copy
|
||||
the result into your factoid definition.
|
||||
|
||||
Infobot (written by jamessan) is used for Infobot compatibility;
|
||||
if you still want the basic functionality of Infobot, this is the
|
||||
plugin to use.
|
||||
|
||||
|
||||
Q: Can I import my Infobot/Blootbot/Moobot factoids into Supybot?
|
||||
|
||||
A: As of present, we have no automated way to do so. Strike has
|
||||
written a few scripts for importing a Moobot database into
|
||||
MoobotFactoids, however, so you'll want to talk to him about
|
||||
helping you with that. We're certainly happy to help you convert
|
||||
such databases; if you can provide us with such a database exported
|
||||
to a flat file, we can probably do the rest of the work to write a
|
||||
script that imports it into a database for one of our
|
||||
factoids-related plugins.
|
||||
|
||||
|
||||
Q: Do I really have to use separate databases for each channel?
|
||||
|
||||
A: Of course not! We default to separate databases for each channel
|
||||
because, well, that's what jemfinch always thought was
|
||||
reasonable. Anyway, if you change the configuration variable
|
||||
supybot.databases.plugins.channelSpecific to False instead of
|
||||
True, for *most* databases, each channel will share the same
|
||||
database (the exceptions are ChannelStats, Herald, Seen, and
|
||||
WordStats, which are inherently rather channel-based).
|
||||
|
||||
|
||||
Q: Karma doesn't seem to work for me.
|
||||
|
||||
A: 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.
|
||||
|
||||
|
||||
Q: I added an alias, but it doesn't work!
|
||||
|
||||
A: 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.
|
||||
|
||||
Q: Is there a command that can tell me what capability another
|
||||
command requires?
|
||||
|
||||
A: 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.
|
||||
|
||||
Besides, is the error message so bad? If we did have such a
|
||||
command, many users would call the command, see that they could
|
||||
perform it, and then run the command, thus doubling the activity
|
||||
in the channel. Is that something you want?
|
||||
|
||||
|
||||
Q: How do I make my Supybot connect to multiple servers?
|
||||
|
||||
A: Just use the "connect" command in the Network plugin. Easy as pie!
|
||||
|
||||
|
||||
Q: 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.
|
||||
|
||||
A: 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).
|
||||
|
||||
|
||||
Q: I've edited my configuration file, but my Supybot doesn't notice
|
||||
the changes! Even if I restart it, it doesn't see them. What's
|
||||
the deal?
|
||||
|
||||
A: Supybot won't reload its configuration files unless you tell it
|
||||
to. In addition, when Supybot exits (and periodically while it
|
||||
runs) it flushes its configuration file to disk. The safest way
|
||||
to avoid problems with configuration file edits is simply to exit
|
||||
the bot before editing the configuration file(s). If you don't
|
||||
wish to do that, however, you can edit the file, save the changes,
|
||||
and tell the bot to reload its configuration, either via the
|
||||
reload command in the Config plugin, or by sending the bot a
|
||||
SIGHUP. There is a brief period in this whole sequence where the
|
||||
bot can flush its configuration to disk after you write your
|
||||
changes, but we even have something to fix that: set the
|
||||
configuration variable supybot.flush to False, and then reload the
|
||||
configuration.
|
||||
|
||||
|
||||
Q: I found a bug, what do I do?
|
||||
|
||||
A: Submit it on Sourceforge through our Sourceforge project page:
|
||||
<http://sourceforge.net/tracker/?group_id=58965&atid=489447>. If
|
||||
Sourceforge happens to be down when you try to submit your bug,
|
||||
then post it in the "Supybot Developer Discussion" forum at our
|
||||
forums at <http://forums.supybot.org/>. If that doesn't work,
|
||||
email supybot-bugs@lists.sourceforge.net. If that doesn't work,
|
||||
email jemfinch@supybot.org. If that doesn't work, find yourself
|
||||
some carrier pigeons and ... hah! You thought I was serious!
|
||||
|
||||
Anyway, when you submit your bug, we'll need several things. If
|
||||
the bug involved an uncaught exception, we need the traceback
|
||||
(basically the stuff from "Uncaught exception in ..." to the next
|
||||
log entry). We'd also like to see the commands that caused the
|
||||
bug, or happened around the time you saw the bug. If the bug
|
||||
involved a database, we'd love to see the database. Remember, it's
|
||||
always worse to send us too little information in a bug report than
|
||||
too much.
|
||||
|
||||
|
||||
Q: Is there a way just to load *all* the plugins Supybot has?
|
||||
|
||||
A: 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.
|
||||
|
71
docs/FIXING-BUGS
Normal file
71
docs/FIXING-BUGS
Normal file
@ -0,0 +1,71 @@
|
||||
These are a list of recurrent bugs in Supybot, and ways to notice
|
||||
them. It just might come in useful for people maintaining code.
|
||||
|
||||
1. Using == or != when you mean ircutils.strEqual. Nicks, prefixes,
|
||||
and channels should be compared for equality not by == or !=, but
|
||||
by ircutils.strEqual. This does a case-normalized check. Don't
|
||||
just use lower() because that doesn't use the rfc1459 casemapping.
|
||||
|
||||
To find:
|
||||
grep == plugins/*.py | egrep "nick|channel"
|
||||
grep "!=" plugins/*.py | egrep "nick|channel"
|
||||
|
||||
2. Using a warning log when it really should be an info log. Users
|
||||
don't like to see warnings. If we have warning logs, they'll
|
||||
complain. So let's try to make them complain as little as
|
||||
possible, and only use warnings when you've encountered a *very*
|
||||
odd situation, or when you need more information before
|
||||
determining if something is correct.
|
||||
|
||||
An example: The Services plugin has two methods, doNickservNotice
|
||||
and doChanservNotice, which doNotice dispatches to appropriately.
|
||||
Now, we can't possibly predict all the possible messages that
|
||||
ChanServ or NickServ might send to us. So I put a default clause
|
||||
in there that just says, "Hey, this is an unexpected message from
|
||||
ChanServ/NickServ: ..." I log this at warning level because I
|
||||
want to know when there's a NickServ/ChanServ message I haven't
|
||||
seen before. This works: the warning makes users report it.
|
||||
|
||||
Another example: We used to log failures in snarfers at the
|
||||
warning level. But do the users really want to be warned when a
|
||||
given "url" isn't valid? No! So now we just make it an info log,
|
||||
so that users who care can still see why a snarfer didn't snarf,
|
||||
but no one complains to us about it.
|
||||
|
||||
To find:
|
||||
grep log.warning plugins/*.py
|
||||
|
||||
3. Spelling errors. They plague almost every large piece of
|
||||
software. Supybot is no different, but we have a weapon against
|
||||
them: find-strings.py. Give find-strings.py a set of files, and
|
||||
it'll extract all the non-raw literal strings from the source code
|
||||
and output them to a file, with the originating file, line number,
|
||||
and string. Spell-checking Supybot is just a matter of taking
|
||||
this 10,000 line file and an hour and running aspell over it,
|
||||
correcting the errors in the original file as you find them.
|
||||
|
||||
To find:
|
||||
find-strings.py src/*.py plugins/*.py scripts/supybot*
|
||||
|
||||
4. Pegging the CPU. It has happened in the past that bugs in Supybot
|
||||
have caused 100% CPU usage. These are insidious, and generally
|
||||
hard to track down. Here are our tools against them; we assuming
|
||||
that the bug is reproducible.
|
||||
|
||||
First, I load the Debug plugin and settrace to a file, then I
|
||||
quickly reproduce the bug, and let the CPU spin for awhile. Then
|
||||
I kill the bot and check the trace file (which should be large).
|
||||
I look for patterns that would indicate an infinite loop of some
|
||||
sort.
|
||||
|
||||
Second, I strace the bot when it's looping. If I see output, it's
|
||||
making syscalls; if I don't see any output, it's not. If it's
|
||||
making syscalls, that might mean it's looping in the network
|
||||
drivers somehow; if not, it's somewhere else.
|
||||
|
||||
Third, I check that no regexps could be causing it. They're
|
||||
notorious for appearing safe, but actually hiding exponential
|
||||
complexity code (the kind of code that strong cryptography is
|
||||
based on).
|
||||
|
||||
After that, I pray harder :)
|
143
docs/GETTING_STARTED
Normal file
143
docs/GETTING_STARTED
Normal file
@ -0,0 +1,143 @@
|
||||
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 :)
|
||||
|
||||
First things first. You should have already read through INSTALL
|
||||
before reading any further.
|
||||
|
||||
Ok, so let's assume your bot connected to the server fine 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".
|
||||
|
||||
Now that you know how to deal with plugins having commands with the
|
||||
same name, 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.
|
||||
|
||||
Now, if you do want to play around with loading plugins, you're going
|
||||
to need to have the owner capability. If you ran the wizard, then
|
||||
chances are 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 (without the quotes), 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 your owner user and password 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 addhostmask command: it lets you add a hostmask to your user
|
||||
record so the bot recognizes you by your hostmask instead of requiring
|
||||
you to always identify with it before it recognizes you. Use the help
|
||||
command to see how this command works. Here's how I often use it:
|
||||
|
||||
addhostmask 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.
|
||||
|
||||
Now, with all that out of the way, we can continue telling you how to
|
||||
load plugins :) 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 Fun plugin, then "load Fun". Simple, right?
|
||||
If you need a list of the plugins you can load, you'll have to either
|
||||
list the directory the plugins are in, or you'll have to check our
|
||||
website -- look for the "Plugin index."
|
||||
|
||||
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.
|
||||
|
||||
You should now have a solid foundation for using Supybot. Be sure to
|
||||
check the help that is built-in to the bot itself if you have any
|
||||
questions, and enjoy using Supybot!
|
48
docs/HACKING
Normal file
48
docs/HACKING
Normal file
@ -0,0 +1,48 @@
|
||||
So, you want to hack on Supybot? Cool! I'm glad -- more developers
|
||||
means more users, and more users means better software (although I
|
||||
suppose more developers means better software even without the
|
||||
addition of more users :))
|
||||
|
||||
Anyway, there are a few things you should know before you submit your
|
||||
code to be accepted into Supybot. The first, and most important
|
||||
thing is that we really do value your contribution. We may say that
|
||||
it's not appropriate for the core distribution and any number of
|
||||
varying reasons, but regardless, we're happy that you're hacking on
|
||||
Supybot and bending it to your will, and we'll be happy to post your
|
||||
patch as long as it applies cleanly.
|
||||
|
||||
The second thing you should know is that, despite the fact that we're
|
||||
happy you want to contribute to Supybot, we're not afraid to piss you
|
||||
off by turning down your code. We won't hesitate to reject code
|
||||
because it's "bad" or because it doesn't fit our style guidelines
|
||||
(read docs/STYLE). We don't really care if it makes you angry or
|
||||
makes you use another IRC bot; we're in the practice of writing good
|
||||
software, not placating whiners. Despite this, we're not entirely
|
||||
heartless, and if you've done something we're interested in, we're
|
||||
willing to work with you and your code until such a time as it's
|
||||
ready to be accepted into the core. But if, at some point, we say,
|
||||
"This needs fixed" and you say, "I refuse to fix it," you can go put
|
||||
your code on the patch tracker; our time together is done. Supybot
|
||||
is #1 here -- we don't care about your feelings, we don't care about
|
||||
jamessan's feelings, we don't care about jemfinch's feelings if it
|
||||
means that the code quality and user experience of Supybot is to
|
||||
suffer.
|
||||
|
||||
Anyway, the normal process is that you'll submit a few patches,
|
||||
jemfinch will review them and tell you what needs to happen for them
|
||||
to be accepted into the core, you'll fix those problems, jemfinch
|
||||
will review them again, that cycle will repeat a few times. When
|
||||
your code is to jemfinch's satisfaction, it'll be integrated into the
|
||||
core. For many people, this is the end of the line. For some
|
||||
others (perhaps you!), you'll continue to write patches for Supybot,
|
||||
and your coding ability and commitment will be obvious through
|
||||
those. If your code quality is consistently high enough that
|
||||
jemfinch (or other Supybot developers) don't have to spend a
|
||||
significant amount of time reviewing your code, you'll be added as a
|
||||
developer on the SF.net project and given commit access to our CVS
|
||||
repository. From then on, you can do what you want, but be aware
|
||||
that the other developers are watching what you do -- if you have a
|
||||
big architecture change, you should probably talk to them before you
|
||||
commit.
|
||||
|
||||
So welcome aboard, and have fun hacking on Supybot!
|
271
docs/INTERFACES
Normal file
271
docs/INTERFACES
Normal file
@ -0,0 +1,271 @@
|
||||
These are the interfaces for some of the objects you'll deal with if
|
||||
you code for Supybot.
|
||||
|
||||
ircmsgs.IrcMsg:
|
||||
This is the object that represents an IRC message. It has
|
||||
several methods and attributes. The most important thing
|
||||
about this class, however, is that it *is* hashable, and thus
|
||||
*cannot* be modified. Do not change any attributes; any code
|
||||
that modifies an IRC message is *broken* and should not
|
||||
exist.
|
||||
|
||||
Interesting Methods:
|
||||
__init__: One of the more complex initializers in
|
||||
a class. It can be used in three different ways:
|
||||
|
||||
1) It can be given a string, as one received from
|
||||
the server, which it will then parse into its
|
||||
separate components and instantiate the class
|
||||
with those components as attributes.
|
||||
|
||||
2) It can be given a command, some (optional)
|
||||
arguments, and a (optional) prefix, and will
|
||||
instantiate the class with those components as
|
||||
attributes.
|
||||
|
||||
3) It can be given, in addition to any of the
|
||||
above arguments, a 'msg' keyword argument that
|
||||
will use the attributes of msg as defaults.
|
||||
This exists to make it easier to copy
|
||||
messages, since the class is immutable.
|
||||
|
||||
__str__: This returns the message in a string form
|
||||
suitable for sending to a server.
|
||||
|
||||
__repr__: This returns the message in a form
|
||||
suitable for eval(), assuming the name "IrcMsg" is
|
||||
in your namespace and is bound to this class.
|
||||
|
||||
Interesting Attributes:
|
||||
This is the meat of this class. These are
|
||||
generally what you'll be looking at with IrcMsgs.
|
||||
|
||||
command: This is the command of the IrcMsg --
|
||||
PRIVMSG, NOTICE, WHOIS, etc.
|
||||
|
||||
args: This is a tuple of the arguments to the
|
||||
IrcMsg. Some messages have arguments, some don't,
|
||||
depending on what command they are. You are, of
|
||||
course, always assured that args exists and is a
|
||||
tuple, though it might be empty.
|
||||
|
||||
prefix: This is the hostmask of the person/server
|
||||
the message is from. In general, you won't be
|
||||
setting this on your outgoing messages, but
|
||||
incoming messages will always have one. This is
|
||||
the whole hostmask; if the message was received
|
||||
from a server, it'll be the server's hostmask; if
|
||||
the message was received from a user, it'll be the
|
||||
whole user hostmask. In that case, however, it's
|
||||
also parsed out into the nick/user/host
|
||||
attributes, which are probably more useful to
|
||||
check for many purposes.
|
||||
|
||||
nick: If the message was sent by a user, this will
|
||||
be the nick of the user. If it was sent by a
|
||||
server, this will be the server's name (something
|
||||
like calvino.freenode.net or similar).
|
||||
|
||||
user: If the message was sent by a user, this will
|
||||
be the user string of the user -- what they put
|
||||
into their IRC client for their "full name." If
|
||||
it was sent by a server, it'll be the server's
|
||||
name, again.
|
||||
|
||||
host: If the message was sent by a user, this will
|
||||
be the host portion of their hostmask. If it was
|
||||
sent by a server, it'll be the server's name (yet
|
||||
again :))
|
||||
|
||||
|
||||
irclib.Irc:
|
||||
This is the object to handle everything about IRC except the
|
||||
actual connection to the server itself. (*NOTE* that the
|
||||
object actually received by commands in subclasses of
|
||||
callbacks.Privmsg is an IrcObjectProxy, which is described
|
||||
later. It augments the following interface with several
|
||||
methods of its own to help plugin authors.)
|
||||
|
||||
Interesting Methods:
|
||||
The two following messages (queueMsg and
|
||||
sendMsg) are the methods by far most commonly
|
||||
called by plugin authors. They're generally
|
||||
the only methods you need to pay attention to
|
||||
if you're writing plugins.
|
||||
|
||||
queueMsg: Queues a message for sending to the
|
||||
server. The queue is generally FIFO, but it
|
||||
does prioritize messages based on their command.
|
||||
|
||||
sendMsg: Queues a message for sending to the
|
||||
server prior to any messages in the normal
|
||||
queue. This is exactly a FIFO queue, no
|
||||
reordering is done at all.
|
||||
|
||||
The following two methods are the most important
|
||||
for people writing new IrcDrivers. Otherwise,
|
||||
you really don't need to pay attention to them.
|
||||
|
||||
feedMsg: Feeds the Irc object a message for it
|
||||
handle appropriately, as well as passing it on
|
||||
to callbacks.
|
||||
|
||||
takeMsg: If the Irc object has a message it's
|
||||
ready to send to the server, this will return
|
||||
it. Otherwise, it will return None.
|
||||
|
||||
The next several methods are of far more marginal
|
||||
utility. But someone may need them, so they're
|
||||
documented here.
|
||||
|
||||
addCallback: Takes a callback to add to the list
|
||||
of callbacks in the Irc object. See the
|
||||
interface for IrcCallback for more information.
|
||||
|
||||
getCallback: Gets a callback by name, if it is
|
||||
in the Irc object's list of callbacks. If it
|
||||
it isn't, returns None.
|
||||
|
||||
removeCallback: Removes a callback by name.
|
||||
Returns a list of the callbacks removed (since
|
||||
it is technically possible to have multiple
|
||||
callbacks with the same name. This list may
|
||||
be empty.
|
||||
|
||||
__init__: Requires a nick. Optional arguments
|
||||
include user and ident, which default to the
|
||||
nick given, password, which defaults to the empty
|
||||
password, and callbacks, a list of callbacks
|
||||
(which defaults to nothing, an empty list).
|
||||
|
||||
reset: Resets the Irc object to its original
|
||||
state, as well as sends a reset() to every
|
||||
callbacks.
|
||||
|
||||
die: Kills the IRC object and all its callbacks.
|
||||
|
||||
Interesting attributes:
|
||||
nick: The current nick of the bot.
|
||||
|
||||
prefix: The current prefix of the bot.
|
||||
|
||||
server: The current server the bot is connected to.
|
||||
|
||||
network: The current network name the bot is connected to.
|
||||
|
||||
afterConnect: False until the bot has received a
|
||||
command sent after the connection is finished --
|
||||
376, 377, or 422.
|
||||
|
||||
state: An IrcState object for this particular
|
||||
connection. See the interface for the IrcState
|
||||
object for more information.
|
||||
|
||||
|
||||
irclib.IrcCallback:
|
||||
Interesting Methods:
|
||||
name: Returns the name of the callback. The
|
||||
default implementation simply returns the name
|
||||
of the class.
|
||||
|
||||
__call__: Called by the Irc object with itself
|
||||
and the message whenever a message is fed to
|
||||
the Irc object. Nothing is done with the return
|
||||
value.
|
||||
|
||||
inFilter: Called by the Irc object with itself
|
||||
and the message whenever a message is fed to
|
||||
the Irc object. The return value should be an
|
||||
IrcMsg object to be passed to the next callback
|
||||
in the Irc's list of callbacks. If None is
|
||||
returned, all processing stops. This gives
|
||||
callbacks an oppurtunity to "filter" incoming
|
||||
messages before general callbacks are given
|
||||
them.
|
||||
|
||||
outFilter: Basically equivalent to inFilter,
|
||||
except instead of being called on messages
|
||||
as they enter the Irc object, it's called on
|
||||
messages as they leave the Irc object.
|
||||
|
||||
die: Called when the parent Irc is told to
|
||||
die. This gives callbacks an oppurtunity to
|
||||
close open files, network connections, or
|
||||
databases before they're deleted.
|
||||
|
||||
reset: Called when the parent Irc is told to
|
||||
reset (which is generally when reconnecting
|
||||
to the server). Most callbacks don't need
|
||||
to define this.
|
||||
|
||||
Interesting attributes:
|
||||
priority: Determines the priority of the
|
||||
callback in the Irc object's list of
|
||||
callbacks. Defaults to 99; the valid range
|
||||
includes 0 through sys.maxint-1 (don't use
|
||||
sys.maxint itself, that's reserved for the
|
||||
Misc plugin). The lower the number, the
|
||||
higher the priority. High priority
|
||||
callbacks are called earlier in the
|
||||
inFilter cycle, earlier in the __call__
|
||||
cycle, and later in the outFilter cycle --
|
||||
basically, they're given the first chances
|
||||
on the way in and the last chances on the
|
||||
way out.
|
||||
|
||||
|
||||
callbacks.IrcObjectProxy:
|
||||
IrcObjectProxy is a proxy for an irclib.Irc instance that
|
||||
serves to provide a much fuller interface for handling
|
||||
replies and errors as well as to handle the nesting of
|
||||
commands. This is what you'll be dealing with almost all the
|
||||
time when writing commands; when writing doCommand methods
|
||||
(the kind you read about in the interface description of
|
||||
irclib.IrcCallback) you'll be dealing with plain old
|
||||
irclib.Irc objects.
|
||||
|
||||
Interesting methods:
|
||||
reply: Called to reply to the current message
|
||||
with a string that is to be the reply.
|
||||
|
||||
replySuccess, replyError: These reply with the
|
||||
configured responses for success and generic
|
||||
error, respectively. If an additional argument
|
||||
is given, it's (intelligently) appended to the
|
||||
generic message to be more specific.
|
||||
|
||||
error: Called to send an error reply to the
|
||||
current message; not only does the response
|
||||
indicate an error, but commands that error out
|
||||
break the nested-command chain, which is
|
||||
generally useful for not confusing the user :)
|
||||
|
||||
errorNoCapability: Like error, except it accepts
|
||||
the capability that's missing and integrates it
|
||||
into the configured error message for such
|
||||
things. Also accepts an additional string for a
|
||||
more descriptive message, if that's what you
|
||||
want.
|
||||
|
||||
errorPossibleBug, errorNotRegistered,
|
||||
errorNoUser, errorRequiresPrivacy: These methods
|
||||
reply with the appropriate configured error
|
||||
message for the conditions in their names; they
|
||||
all take an additional arguments to be more
|
||||
specific about the conditions they indicate, but
|
||||
this argument is very rarely necessary.
|
||||
|
||||
getRealIrc: Returns the actual Irc object being
|
||||
proxied for.
|
||||
|
||||
replies: Sends a collection of messages to a given
|
||||
target, much like reply; except in this case, the user
|
||||
can configure whether the messages will be sent
|
||||
one-by-one or combined into a single message. Thus, the
|
||||
method accepts a "prefixer" argument, which prefixes the
|
||||
messages with a given string (or according to a given
|
||||
function), a "joiner" string (or function) used to join
|
||||
the messages into a single message if necessary, and an
|
||||
onlyPrefixFirst argument which determines whether only
|
||||
the first message will be prefixed when the messages are
|
||||
sent separately (it defaults to False).
|
61
docs/OVERVIEW
Normal file
61
docs/OVERVIEW
Normal file
@ -0,0 +1,61 @@
|
||||
So here's a general *programming* introduction to what the different
|
||||
modules do and what services they provide. It is, however, only an
|
||||
introduction. Read the modules themselves for a much more detailed
|
||||
explanation :)
|
||||
|
||||
fix.py: Stuff that Python should (but doesn't) include by default.
|
||||
|
||||
cdb.py: A constant database library, translated from C (and my O'Caml
|
||||
version) More information available at
|
||||
http://cr.yp.to/cdb.html. Not currently used since we
|
||||
switched to PySQLite.
|
||||
|
||||
ansi.py: Contains different ANSI color sequences.
|
||||
Mostly used by the debug module.
|
||||
|
||||
conf.py: The configuration file for the bot -- it sets a lot of
|
||||
variables that other modules check for when they have
|
||||
questions about what they need to do.
|
||||
|
||||
world.py: Just a dropping off place for some globals that need to be
|
||||
shared among all the modules. It's obviously not used *a
|
||||
lot*, but some things seem to fit better here than anywhere
|
||||
else.
|
||||
|
||||
privmsgs.py: Basic stuff relating to callbacks.Privmsg, the base class
|
||||
for most plugins.
|
||||
|
||||
callbacks.py: A few basic callbacks providing significant
|
||||
functionality. You'll likely be inheriting from Privmsg
|
||||
quite a bit.
|
||||
|
||||
ircdb.py: The users and channels databases are here, as well as the
|
||||
IrcUser and IrcChannel classes. Look here when you want to
|
||||
restrict a command to only certain users.
|
||||
|
||||
irclib.py: Provides the most important class in the irclib, Irc. It
|
||||
represents a connection to an IRC server, but it's entirely
|
||||
separate from the network implementation. It's connected
|
||||
to the network (or whatever else drives it) by a "driver"
|
||||
module which uses its feedMsg/takeMsg functions.
|
||||
|
||||
drivers.py: The baseclass (IrcDriver) for various drivers to drive
|
||||
irclib.Irc classes. Also, the driving mechanism.
|
||||
|
||||
asyncoreDrivers.py: The asyncore-based drivers for use with the bot.
|
||||
|
||||
socketDrivers.py: The plain old socket-based drivers for use with the
|
||||
bot.
|
||||
|
||||
twistedDrivers.py: The Twisted <http://www.twistedmatrix.com/> drivers
|
||||
for use with the bot.
|
||||
|
||||
ircmsgs.py: The IrcMsg class (get to know it :)) and various functions
|
||||
for making the creation of IrcMsgs easier.
|
||||
|
||||
ircutils.py: Various utility functions for Irc -- read the module to
|
||||
see what goodies are there :)
|
||||
|
||||
schedule.py: A schedule driver (which is automatically registered with
|
||||
the drivers module) to run things at a particular time,
|
||||
or at specified periods of time.
|
449
docs/PLUGIN-EXAMPLE
Normal file
449
docs/PLUGIN-EXAMPLE
Normal file
@ -0,0 +1,449 @@
|
||||
*******************************************************************************
|
||||
The plugins/Random.py included with the distribution has been
|
||||
updated to use an argument-parsing module recently added to Supybot
|
||||
but not yet documented. Still, all of the functions used in this
|
||||
tutorial are available, so the code (as shown in this tutorial*
|
||||
should still work.
|
||||
*******************************************************************************
|
||||
|
||||
Ok, so you want to write a callback 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, get a feel
|
||||
for it, see how the various commands work and such.
|
||||
|
||||
So now that we know you've used Supybot, we'll start getting into
|
||||
details.
|
||||
|
||||
First, the easiest way to start writing a module is to use the wizard
|
||||
provided, scripts/newplugin.py. Here's an example session:
|
||||
|
||||
-----
|
||||
functor% supybot-newplugin
|
||||
What should the name of the plugin be? Random
|
||||
Supybot offers two major types of plugins: command-based and regexp-
|
||||
based. Command-based plugins are the kind of plugins you've seen most
|
||||
when you've used supybot. They're also the most featureful and
|
||||
easiest to write. Commands can be nested, for instance, whereas
|
||||
regexp-based callbacks can't do nesting. That doesn't mean that
|
||||
you'll never want regexp-based callbacks. They offer a flexibility
|
||||
that command-based callbacks don't offer; however, they don't tie into
|
||||
the whole system as well. If you need to combine a command-based
|
||||
callback with some regexp-based methods, you can do so by subclassing
|
||||
callbacks.PrivmsgCommandAndRegexp and then adding a class-level
|
||||
attribute "regexps" that is a sets.Set of methods that are regexp-
|
||||
based. But you'll have to do that yourself after this wizard is
|
||||
finished :)
|
||||
Do you want a command-based plugin or a regexp-based plugin? [command/
|
||||
regexp] command
|
||||
Sometimes you'll want a callback to be threaded. If its methods
|
||||
(command or regexp-based, either one) will take a signficant 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
|
||||
Your new plugin template is Random.py
|
||||
functor%
|
||||
-----
|
||||
|
||||
So that's what it looks like. Now let's look at the source code (if
|
||||
you'd like to look at it in your programming editor, the whole plugin
|
||||
is available as examples/Random.py):
|
||||
|
||||
-----
|
||||
#!/usr/bin/env python
|
||||
|
||||
###
|
||||
# Copyright (c) 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.
|
||||
###
|
||||
|
||||
"""
|
||||
Add the module docstring here. This will be used by the setup.py script.
|
||||
"""
|
||||
|
||||
__revision__ = "$Id$"
|
||||
__author__ = ''
|
||||
|
||||
import supybot.plugins as plugins
|
||||
|
||||
import supybot.conf as conf
|
||||
import supybot.utils as utils
|
||||
import supybot.privmsgs as privmsgs
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
def configure(advanced):
|
||||
# This will be called by setup.py 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 questions import expect, anything, something, yn
|
||||
conf.registerPlugin('Random', True)
|
||||
|
||||
class Random(callbacks.Privmsg):
|
||||
pass
|
||||
|
||||
|
||||
Class = Random
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
||||
-----
|
||||
|
||||
So a few notes, before we customize it.
|
||||
|
||||
You'll probably want to change the copyright notice to be your name.
|
||||
It wouldn't stick even if you kept my name, so you might as well :)
|
||||
|
||||
Describe what you want the plugin to do in the docstring. This is
|
||||
used in supybot-wizard in order to explain to the user the purpose of
|
||||
the module. It's also returned when someone asks the bot for help for
|
||||
a given module (instead of help for a certain command). We'll change
|
||||
this one to "Lots of stuff relating to random numbers."
|
||||
|
||||
Then there are the imports. The callbacks module is used (the class
|
||||
you're given subclasses callbacks.Privmsg) but the privmsgs module
|
||||
isn't used. That's alright; we can almost guarantee you'll use it, so
|
||||
we go ahead and add the import to the template.
|
||||
|
||||
Then you see a "configure" function. This is the function that's
|
||||
called when users decide to add your module in supybot-wizard. You'll
|
||||
note that by default it simply registers the plugin to be
|
||||
automatically loaded on startup. For many plugins this is all you
|
||||
need; for more complex plugins, you might need to ask questions and
|
||||
add commands based on the answers.
|
||||
|
||||
Now comes the meat of the plugin: the plugin class.
|
||||
|
||||
What you're given is a skeleton: a simple subclass of
|
||||
callbacks.Privmsg for you to start with. Now let's add a command.
|
||||
|
||||
I don't know what you know about random number generators, but the
|
||||
short of it is that they start at a certain number (a seed) and they
|
||||
continue (via some somewhat complicated/unpredictable algorithm) from
|
||||
there. This seed (and the rest of the sequence, really) is all nice
|
||||
and packaged up in Python's random module, the Random object. So the
|
||||
first thing we're going to have to do is give our plugin a Random
|
||||
object.
|
||||
|
||||
Normally, when we want to give instances of a class an object, we'll
|
||||
do so in the __init__ method. And that works great for plugins, too.
|
||||
The one thing you have to be careful of is that you call the
|
||||
superclass __init__ method at the end of your own __init__. So to add
|
||||
this random.Random object to our plugin, we can replace the "pass"
|
||||
statement with this:
|
||||
|
||||
def __init__(self):
|
||||
self.rng = random.Random()
|
||||
callbacks.Privmsg.__init__(self)
|
||||
|
||||
(rng is an abbreviation for "random number generator," in case you
|
||||
were curious)
|
||||
|
||||
Do be careful not to give your __init__ any arguments (other than
|
||||
self, of course). There's no way anything will ever get to them! If
|
||||
you have some sort of initial values you need to get to your plugin
|
||||
before it can do anything interesting, you should get those values
|
||||
from the registry.
|
||||
|
||||
There's an easier way to get our plugin to have its own rng than to
|
||||
define an __init__. Plugins are unique among classes because we're
|
||||
always certain that there will only be one instance -- supybot doesn't
|
||||
allow us to load multiple instances of a single plugin. So instead of
|
||||
adding the rng in __init__, we can just add it as a attribute to the
|
||||
class itself. Like so (replacing the "pass" statement again):
|
||||
|
||||
rng = random.Random()
|
||||
|
||||
And we save two lines of code and make our code a little more clear :)
|
||||
|
||||
Now that we have an RNG, we need some way to get random numbers. So
|
||||
first, we'll add a command that simply gets the next random number and
|
||||
gives it back to the user. It takes no arguments, of course (what
|
||||
would you give it?). Here's the command, and I'll follow that with
|
||||
the explanation of what each part means.
|
||||
|
||||
def random(self, irc, msg, args):
|
||||
"""takes no arguments
|
||||
|
||||
Returns the next random number generated by the random number
|
||||
generator.
|
||||
"""
|
||||
irc.reply(str(self.rng.random()))
|
||||
|
||||
And that's it! Pretty simple, huh? Anyway, you're probably wondering
|
||||
what all that *means*. We'll start with the def statement:
|
||||
|
||||
def random(self, irc, msg, args):
|
||||
|
||||
What that does is define a command "random". You can call it by
|
||||
saying "@random" (or whatever prefix character your specific bot
|
||||
uses). The arguments are a bit less obvious. Self is self-evident
|
||||
(hah!). irc is the Irc object passed to the command; msg is the
|
||||
original IrcMsg object. But you're really not going to have to deal
|
||||
with either of these too much (with the exception of calling irc.reply
|
||||
or irc.error). What you're *really* interested in is the args arg.
|
||||
That is a list of all the arguments passed to your command, pre-parsed
|
||||
and already evaluated (i.e., you never have to worry about nested
|
||||
commands, or handling double quoted strings, or splitting on
|
||||
whitespace -- the work has already been done for you). You can read
|
||||
about the Irc object in irclib.py (you won't find .reply or .error
|
||||
there, though, because you're actually getting an IrcObjectProxy, but
|
||||
that's beyond the level we want to describe here :)). You can read
|
||||
about the msg object in ircmsgs.py. But again, you'll very rarely be
|
||||
using these objects.
|
||||
|
||||
(In case you're curious, the answer is yes, you *must* name your
|
||||
arguments (self, irc, msg, args). The names of those arguments is one
|
||||
of the ways that supybot uses to determine which methods in a plugin
|
||||
class are commands and which aren't. And while we're talking about
|
||||
naming restrictions, all your commands should be named in
|
||||
all-lowercase with no underscores. Before calling a command, supybot
|
||||
always converts the command name to lowercase and removes all dashes
|
||||
and underscores. On the other hand, you now know an easy way to make
|
||||
sure a method is never called (even if its arguments are (self, irc,
|
||||
msg, args), however unlikely that may be). Just name it with an
|
||||
underscore or an uppercase letter in it :))
|
||||
|
||||
You'll also note that the docstring is odd. The wonderful thing about
|
||||
the supybot framework is that it's easy to write complete commands
|
||||
with help and everything: the docstring *IS* the help! Given the
|
||||
above docstring, this is what a supybot does:
|
||||
|
||||
<jemfinch> @help random
|
||||
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||
next random number from the random number generator.
|
||||
|
||||
Now on to the actual body of the function:
|
||||
|
||||
irc.reply(str(self.rng.random()))
|
||||
|
||||
irc.reply takes one simple argument: a string. The string is the
|
||||
reply to be sent. Don't worry about length restrictions or anything
|
||||
-- if the string you want to send is too big for an IRC message (and
|
||||
oftentimes that turns out to be the case :)) the Supybot framework
|
||||
handles that entirely transparently to you. Do make sure, however,
|
||||
that you give irc.reply a string. It doesn't take anything else
|
||||
(sometimes even unicode fails!). That's why we have
|
||||
"str(self.rng.random())" instead of simply "self.rng.random()" -- we
|
||||
had to give irc.reply a string.
|
||||
|
||||
Anyway, now that we have an RNG, we have a need for seed! Of course,
|
||||
Python gives us a good seed already (it uses the current time as a
|
||||
seed if we don't give it one) but users might want to be able to
|
||||
repeat "random" sequences, so letting them set the seed is a good
|
||||
thing. So we'll add a seed command to give the RNG a specific seed:
|
||||
|
||||
def seed(self, irc, msg, args):
|
||||
"""<seed>
|
||||
|
||||
Sets the seed of the random number generator. <seed> must be
|
||||
an int or a long.
|
||||
"""
|
||||
seed = privmsgs.getArgs(args)
|
||||
try:
|
||||
seed = long(seed)
|
||||
except ValueError:
|
||||
# It wasn't a valid long!
|
||||
irc.error('<seed> must be a valid int or long.')
|
||||
return
|
||||
self.rng.seed(seed)
|
||||
irc.replySuccess()
|
||||
|
||||
So this one's a bit more complicated. But it's still pretty simple.
|
||||
The method name is "seed" so that'll be the command name. The
|
||||
arguments are the same, the docstring is of the same form, so we don't
|
||||
need to go over that again. The body of the function, however, is
|
||||
significantly different.
|
||||
|
||||
privmsgs.getArgs is a function you're going to be seeing a lot of when
|
||||
you write plugins for Supybot. What it does is basically give you the
|
||||
right number of arguments for your comamnd. In this case, we want one
|
||||
argument. But we might have been given any number of arguments by the
|
||||
user. So privmsgs.getArgs joins them appropriately, leaving us with
|
||||
one single "seed" argument (by default, it returns one argument as a
|
||||
single value; more arguments are returned in a tuple/list). Yes, we
|
||||
could've just said "seed = args[0]" and gotten the first argument, but
|
||||
what if the user didn't pass us an argument at all? Then we've got to
|
||||
catch the IndexError from args[0] and complain to the user about it.
|
||||
privmsgs.getArgs, on the other hand, handles all that for us. If the
|
||||
user didn't give us enough arguments, it'll reply with the help string
|
||||
for the command, thus saving us the effort.
|
||||
|
||||
So we have the seed from privmsgs.getArgs. But it's a string. The
|
||||
next three lines are pretty darn obvious: we're just converting the
|
||||
string to a int of some sort. But if it's not, that's when we're
|
||||
going to call irc.error. It has the same interface as we saw before
|
||||
in irc.reply, but it makes sure to remind the user that an error has
|
||||
been encountered (currently, that means it puts "Error: " at the
|
||||
beginning of the message). After erroring, we return. It's important
|
||||
to remember this return here; otherwise, we'll just keep going down
|
||||
through the function and try to use this "seed" variable that never
|
||||
got assigned. A good general rule of thumb is that any time you use
|
||||
irc.error, you'll want to return immediately afterwards.
|
||||
|
||||
Then we set the seed -- that's a simple function on our rng object.
|
||||
Assuming that succeeds (and doesn't raise an exception, which it
|
||||
shouldn't, because we already read the documentation and know that it
|
||||
should work) we reply to say that everything worked fine. That's what
|
||||
irc.replySuccess says. By default, it has the very dry (and
|
||||
appropriately robot-like) "The operation succeeded." but you're
|
||||
perfectly welcome to customize it yourself -- the registry was written to
|
||||
be modified!
|
||||
|
||||
So that's a bit more complicated command. But we still haven't dealt
|
||||
with multiple arguments. Let's do that next.
|
||||
|
||||
So these random numbers are useful, but they're not the kind of random
|
||||
numbers we usually want in Real Life. In Real Life, we like to tell
|
||||
someone to "pick a number between 1 and 10." So let's write a
|
||||
function that does that. Of course, we won't hardcode the 1 or the 10
|
||||
into the function, but we'll take them as arguments. First the
|
||||
function:
|
||||
|
||||
def range(self, irc, msg, args):
|
||||
"""<start> <end>
|
||||
|
||||
Returns a number between <start> and <end>, inclusive (i.e., the number
|
||||
can be either of the endpoints.
|
||||
"""
|
||||
(start, end) = privmsgs.getArgs(args, required=2)
|
||||
try:
|
||||
end = int(end)
|
||||
start = int(start)
|
||||
except ValueError:
|
||||
irc.error('<start> and <end> must both be integers.')
|
||||
return
|
||||
# .randrange() doesn't include the endpoint, so we use end+1.
|
||||
irc.reply(str(self.rng.randrange(start, end+1)))
|
||||
|
||||
Pretty simple. This is becoming old hat by now. The only new thing
|
||||
here is the call to privmsgs.getArgs. We have to make sure, since we
|
||||
want two values, to pass a keyword parameter "required" into
|
||||
privmsgs.getArgs. Of course, privmsgs.getArgs handles all the
|
||||
checking for missing arguments and whatnot so we don't have to.
|
||||
|
||||
The Random object we're using offers us a "sample" method that takes a
|
||||
sequence and a number (we'll call it N) and returns a list of N items
|
||||
taken randomly from the sequence. So I'll show you an example that
|
||||
takes advantage of multiple arguments but doesn't use privmsgs.getArgs
|
||||
(and thus has to handle its own errors if the number of arguments
|
||||
isn't right). Here's the code:
|
||||
|
||||
def sample(self, irc, msg, args):
|
||||
"""<number of items> [<text> ...]
|
||||
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
n = int(args.pop(0))
|
||||
except IndexError: # raised by .pop(0)
|
||||
raise callbacks.ArgumentError
|
||||
except ValueError:
|
||||
irc.error('<number of items> must be an integer.')
|
||||
return
|
||||
if n > len(args):
|
||||
irc.error('<number of items> must be less than the number '
|
||||
'of arguments.')
|
||||
return
|
||||
sample = self.rng.sample(args, n)
|
||||
irc.reply(utils.commaAndify(map(repr, sample)))
|
||||
|
||||
Most everything here is familiar. The difference between this and the
|
||||
previous examples is that we're dealing with args directly, rather
|
||||
than through getArgs. Since we already have the arguments in a list,
|
||||
it doesn't make any sense to have privmsgs.getArgs smush them all
|
||||
together into a big long string that we'll just have to re-split. But
|
||||
we still want the nice error handling of privmsgs.getArgs. So what do
|
||||
we do? We raise callbacks.ArgumentError! That's the secret juju that
|
||||
privmsgs.getArgs is doing; now we're just doing it ourself. Someone
|
||||
up our callchain knows how to handle it so a neat error message is
|
||||
returned. So in this function, if .pop(0) fails, we weren't given
|
||||
enough arguments and thus need to tell the user how to call us.
|
||||
|
||||
So we have the args, we have the number, we do a simple call to
|
||||
random.sample and then we do this funky utils.commaAndify to it.
|
||||
Yeah, so I was running low on useful names :) Anyway, what it does is
|
||||
take a list of strings and return a string with them joined by a
|
||||
comma, the last one being joined with a comma and "and". So the list
|
||||
['foo', 'bar', 'baz'] becomes "foo, bar, and baz". It's pretty useful
|
||||
for showing the user lists in a useful form. We map the strings with
|
||||
repr() first just to surround them with quotes.
|
||||
|
||||
So we have one more example. Yes, I hear your groans, but it's
|
||||
pedagogically useful :) This time we're going to write a command that
|
||||
makes the bot roll a die. It'll take one argument (the number of
|
||||
sides on the die) and will respond with the equivalent of "/me rolls a
|
||||
__" where __ is the number the bot rolled. So here's the code:
|
||||
|
||||
def diceroll(self, irc, msg, args):
|
||||
"""[<number of sides>]
|
||||
|
||||
Rolls a die with <number of sides> sides. The default number
|
||||
of sides is 6.
|
||||
"""
|
||||
try:
|
||||
n = privmsgs.getArgs(args, required=0, optional=1)
|
||||
if not n:
|
||||
n = 6
|
||||
n = int(n)
|
||||
except ValueError:
|
||||
irc.error('Dice have integer numbers of sides. Use one.')
|
||||
return
|
||||
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||
irc.reply(s, action=True)
|
||||
|
||||
There's a lot of stuff you haven't seen before in there. The most
|
||||
important, though, is the first thing you'll notice that's different:
|
||||
the privmsg.getArgs call. Here we're offering a default argument in
|
||||
case the user is too lazy to supply one (or just wants a nice,
|
||||
standard six-sided die :)) privmsgs.getArgs supports that; we'll just
|
||||
tell it that we don't *need* any arguments (via required=0) and that
|
||||
we *might like* one argument (optional=1). If the user provides an
|
||||
argument, we'll get it -- if they don't, we'll just get an empty
|
||||
string. Hence the "if not n: n = 6", where we provide the default.
|
||||
|
||||
You'll also note that irc.reply was given a keyword argument here,
|
||||
"action". This means that the reply is to be made as an action rather
|
||||
than a normal reply.
|
||||
|
||||
So that's our plugin. 5 commands, each building in complexity. You
|
||||
should now be able to write most anything you want to do in Supybot.
|
||||
Except regexp-based plugins, but that's a story for another day (and
|
||||
those aren't nearly as cool as these command-based callbacks anyway
|
||||
:)). Now we need to flesh it out to make it a full-fledged plugin.
|
||||
|
||||
TODO: Describe the registry and how to write a proper plugin configure
|
||||
function.
|
||||
|
||||
We've written our own plugin from scratch (well, from the boilerplate
|
||||
that we got from scripts/newplugin.py :)) and survived! Now go write
|
||||
more plugins for supybot, and send them to me so I can use them too :)
|
188
docs/STYLE
Normal file
188
docs/STYLE
Normal file
@ -0,0 +1,188 @@
|
||||
====================================================================
|
||||
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 this: "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.
|
43
docs/man/supybot-adduser.1
Normal file
43
docs/man/supybot-adduser.1
Normal file
@ -0,0 +1,43 @@
|
||||
.\" Process this file with
|
||||
.\" groff -man -Tascii supybot-adduser.1
|
||||
.\"
|
||||
.TH SUPYBOT\-ADDUSER 1 "SEPTEMBER 2004"
|
||||
.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 \-x ", " \-\^\-hashed
|
||||
Hash encrypt the password.
|
||||
.TP
|
||||
.BR \-n ", " \-\^\-plain
|
||||
Store the password in plain text.
|
||||
.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\-wizard (1),
|
||||
.IR supybot\-newplugin (1)
|
||||
.SH AUTHOR
|
||||
This manual page was originally written by James Vega
|
||||
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD\-style license.
|
41
docs/man/supybot-newplugin.1
Normal file
41
docs/man/supybot-newplugin.1
Normal file
@ -0,0 +1,41 @@
|
||||
.\" Process this file with
|
||||
.\" groff -man -Tascii supybot-newplugin.1
|
||||
.\"
|
||||
.TH SUPYBOT\-NEWPLUGIN 1 "SEPTEMBER 2004"
|
||||
.SH NAME
|
||||
supybot\-newplugin \- A wizard for creating Supybot plugins
|
||||
.SH SYNOPSIS
|
||||
.B supybot\-newplugin
|
||||
.RI [ options ]
|
||||
.SH DESCRIPTION
|
||||
.B
|
||||
supybot\-newplugin
|
||||
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
|
||||
.BR \-r ", " \-\^\-regexp
|
||||
Uses a regexp\-based callback.
|
||||
.TP
|
||||
.BI \-n " NAME" "\fR,\fP \-\^\-name=" NAME
|
||||
Sets the name for the plugin.
|
||||
.TP
|
||||
.BR \-t ", " \-\^\-thread
|
||||
Makes the plugin threaded.
|
||||
.SH "SEE ALSO"
|
||||
.IR python (1),
|
||||
.IR supybot (1),
|
||||
.IR supybot\-wizard (1),
|
||||
.IR supybot\-adduser (1),
|
||||
.SH AUTHOR
|
||||
This manual page was originally written by James Vega
|
||||
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD\-style license.
|
40
docs/man/supybot-wizard.1
Normal file
40
docs/man/supybot-wizard.1
Normal file
@ -0,0 +1,40 @@
|
||||
.\" 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\-adduser (1),
|
||||
.IR supybot\-newplugin (1)
|
||||
.SH AUTHOR
|
||||
This manual page was originally written by James Vega
|
||||
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD\-style license.
|
68
docs/man/supybot.1
Normal file
68
docs/man/supybot.1
Normal file
@ -0,0 +1,68 @@
|
||||
.\" Process this file with
|
||||
.\" groff -man -Tascii supybot.1
|
||||
.\"
|
||||
.TH SUPYBOT 1 "SEPTEMBER 2004"
|
||||
.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
|
||||
.B \-O
|
||||
Optimizes asserts out of the code; \-O0 optimizes asserts and uses
|
||||
.IR psyco .
|
||||
.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\-wizard (1),
|
||||
.IR supybot\-adduser (1),
|
||||
.IR supybot\-newplugin (1)
|
||||
.SH AUTHOR
|
||||
This manual page was originally written by James Vega
|
||||
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||
distribute and/or modify this document under the terms of the Supybot
|
||||
license, a BSD\-style license.
|
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)
|
450
others/BeautifulSoup.py
Normal file
450
others/BeautifulSoup.py
Normal file
@ -0,0 +1,450 @@
|
||||
"""Beautiful Soup
|
||||
Elixir and Tonic
|
||||
"The Screen-Scraper's Friend"
|
||||
|
||||
The BeautifulSoup class turns arbitrarily bad HTML into a tree-like
|
||||
nested tag-soup list of Tag objects and text snippets. A Tag object
|
||||
corresponds to an HTML tag. It knows about the HTML tag's attributes,
|
||||
and contains a representation of everything contained between the
|
||||
original tag and its closing tag (if any). It's easy to extract Tags
|
||||
that meet certain criteria.
|
||||
|
||||
A well-formed HTML document will yield a well-formed data
|
||||
structure. An ill-formed HTML document will yield a correspondingly
|
||||
ill-formed data structure. If your document is only locally
|
||||
well-formed, you can use this to process the well-formed part of it.
|
||||
|
||||
#Example:
|
||||
#--------
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
text = '''<html>
|
||||
<head><title>The Title</title></head>
|
||||
<body>
|
||||
<a class="foo" href="http://www.crummy.com/">Link <i>text (italicized)</i></a>
|
||||
<a href="http://www.foo.com/">Link text 2</a>
|
||||
</body>
|
||||
</html>'''
|
||||
soup = BeautifulSoup()
|
||||
soup.feed(text)
|
||||
print soup("a") #Returns a list of 2 Tag objects, one for each link in
|
||||
#the source
|
||||
print soup.first("a", {'class':'foo'})['href'] #Returns http://www.crummy.com/
|
||||
print soup.first("title").contents[0] #Returns "The title"
|
||||
print soup.first("a", {'href':'http://www.crummy.com/'}).first("i").contents[0]
|
||||
#Returns "text (italicized)"
|
||||
|
||||
#Example of SQL-style attribute wildcards -- all four 'find' calls will
|
||||
#find the link.
|
||||
#----------------------------------------------------------------------
|
||||
soup = BeautifulSoup()
|
||||
soup.feed('''<a href="http://foo.com/">bla</a>''')
|
||||
print soup.fetch('a', {'href': 'http://foo.com/'})
|
||||
print soup.fetch('a', {'href': 'http://%'})
|
||||
print soup.fetch('a', {'href': '%.com/'})
|
||||
print soup.fetch('a', {'href': '%o.c%'})
|
||||
|
||||
#Example with horrible HTML:
|
||||
#---------------------------
|
||||
soup = BeautifulSoup()
|
||||
soup.feed('''<body>
|
||||
Go <a class="that" href="here.html"><i>here</i></a>
|
||||
or <i>go <b><a href="index.html">Home</a>
|
||||
</html>''')
|
||||
print soup.fetch('a') #Returns a list of 2 Tag objects.
|
||||
print soup.first(attrs={'href': 'here.html'})['class'] #Returns "that"
|
||||
print soup.first(attrs={'class': 'that'}).first('i').contents[0] #returns "here"
|
||||
|
||||
This library has no external dependencies. It works with Python 1.5.2
|
||||
and up. If you can install a Python extension, you might want to use
|
||||
the ElementTree Tidy HTML Tree Builder instead:
|
||||
http://www.effbot.org/zone/element-tidylib.htm
|
||||
|
||||
You can use BeautifulSoup on any SGML-like substance, such as XML or a
|
||||
domain-specific language that looks like HTML but has different tag
|
||||
names. For such purposes you may want to use the BeautifulStoneSoup
|
||||
class, which knows nothing at all about HTML per se. I also reserve
|
||||
the right to make the BeautifulSoup parser smarter between releases,
|
||||
so if you want forwards-compatibility without having to think about
|
||||
it, you might want to go with BeautifulStoneSoup.
|
||||
|
||||
Release status:
|
||||
|
||||
(I do a new release whenever I make a change that breaks backwards
|
||||
compatibility.)
|
||||
|
||||
Current release:
|
||||
|
||||
Applied patch from Richie Hindle (richie at entrian dot com) that
|
||||
makes tag.string a shorthand for tag.contents[0].string when the tag
|
||||
has only one string-owning child.
|
||||
|
||||
1.2 "Who for such dainties would not stoop?" (2004/07/08): Applied
|
||||
patch from Ben Last (ben at benlast dot com) that made
|
||||
Tag.renderContents() correctly handle Unicode.
|
||||
|
||||
Made BeautifulStoneSoup even dumber by making it not implicitly
|
||||
close a tag when another tag of the same type is encountered; only
|
||||
when an actual closing tag is encountered. This change courtesy of
|
||||
Fuzzy (mike at pcblokes dot com). BeautifulSoup still works as
|
||||
before.
|
||||
|
||||
1.1 "Swimming in a hot tureen": Added more 'nestable' tags. Changed
|
||||
popping semantics so that when a nestable tag is encountered, tags are
|
||||
popped up to the previously encountered nestable tag (of whatever kind).
|
||||
I will revert this if enough people complain, but it should make
|
||||
more people's lives easier than harder.
|
||||
|
||||
This enhancement was suggested by Anthony Baxter (anthony at
|
||||
interlink dot com dot au).
|
||||
|
||||
1.0 "So rich and green": Initial release.
|
||||
|
||||
Retreived from: http://www.crummy.com/software/BeautifulSoup/
|
||||
"""
|
||||
|
||||
__author__ = "Leonard Richardson (leonardr@segfault.org)"
|
||||
__version__ = "1.1 $Revision$"
|
||||
__date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2004 Leonard Richardson"
|
||||
__license__ = "Python"
|
||||
|
||||
from sgmllib import SGMLParser
|
||||
import string
|
||||
import types
|
||||
|
||||
class PageElement:
|
||||
"""Contains the navigational information for some part of the page
|
||||
(either a tag or a piece of text)"""
|
||||
|
||||
def __init__(self, parent=None, previous=None):
|
||||
self.parent = parent
|
||||
self.previous = previous
|
||||
self.next = None
|
||||
|
||||
class NavigableText(PageElement):
|
||||
|
||||
"""A simple wrapper around a string that keeps track of where in
|
||||
the document the string was found. Doesn't implement all the
|
||||
string methods because I'm lazy. You could have this extend
|
||||
UserString if you were using 2.2."""
|
||||
|
||||
def __init__(self, string, parent=None, previous=None):
|
||||
PageElement.__init__(self, parent, previous)
|
||||
self.string = string
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.string == str(other)
|
||||
|
||||
def __str__(self):
|
||||
return self.string
|
||||
|
||||
def strip(self):
|
||||
return self.string.strip()
|
||||
|
||||
class Tag(PageElement):
|
||||
|
||||
"""Represents a found HTML tag with its attributes and contents."""
|
||||
|
||||
def __init__(self, name, attrs={}, parent=None, previous=None):
|
||||
PageElement.__init__(self, parent, previous)
|
||||
self.name = name
|
||||
self.attrs = attrs
|
||||
self.contents = []
|
||||
self.foundClose = 0
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self._getAttrMap().get(key, default)
|
||||
|
||||
def __call__(self, *args):
|
||||
return apply(self.fetch, args)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._getAttrMap()[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._getAttrMap()
|
||||
self.attrMap[key] = value
|
||||
for i in range(0, len(self.attrs)):
|
||||
if self.attrs[i][0] == key:
|
||||
self.attrs[i] = (key, value)
|
||||
|
||||
def _getAttrMap(self):
|
||||
if not hasattr(self, 'attrMap'):
|
||||
self.attrMap = {}
|
||||
for (key, value) in self.attrs:
|
||||
self.attrMap[key] = value
|
||||
return self.attrMap
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Tag) or self.name != other.name or self.attrs != other.attrs or len(self.contents) != len(other.contents):
|
||||
return 0
|
||||
for i in range(0, len(self.contents)):
|
||||
if self.contents[i] != other.contents[i]:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def __str__(self):
|
||||
attrs = ''
|
||||
if self.attrs:
|
||||
for key, val in self.attrs:
|
||||
attrs = attrs + ' %s="%s"' % (key, val)
|
||||
close = ''
|
||||
closeTag = ''
|
||||
if self.isSelfClosing():
|
||||
close = ' /'
|
||||
elif self.foundClose:
|
||||
closeTag = '</%s>' % self.name
|
||||
s = self.renderContents()
|
||||
if not hasattr(self, 'hideTag'):
|
||||
s = '<%s%s%s>' % (self.name, attrs, close) + s + closeTag
|
||||
return s
|
||||
|
||||
def renderContents(self):
|
||||
s='' #non-Unicode
|
||||
for c in self.contents:
|
||||
try:
|
||||
s = s + str(c)
|
||||
except UnicodeEncodeError:
|
||||
if type(s) <> types.UnicodeType:
|
||||
s = s.decode('utf8') #convert ascii to Unicode
|
||||
#str() should, strictly speaking, not return a Unicode
|
||||
#string, but NavigableText never checks and will return
|
||||
#Unicode data if it was initialised with it.
|
||||
s = s + str(c)
|
||||
return s
|
||||
|
||||
def isSelfClosing(self):
|
||||
return self.name in BeautifulSoup.SELF_CLOSING_TAGS
|
||||
|
||||
def append(self, tag):
|
||||
self.contents.append(tag)
|
||||
|
||||
def first(self, name=None, attrs={}, contents=None, recursive=1):
|
||||
r = None
|
||||
l = self.fetch(name, attrs, contents, recursive)
|
||||
if l:
|
||||
r = l[0]
|
||||
return r
|
||||
|
||||
def fetch(self, name=None, attrs={}, contents=None, recursive=1):
|
||||
"""Extracts Tag objects that match the given criteria. You
|
||||
can specify the name of the Tag, any attributes you want the
|
||||
Tag to have, and what text and Tags you want to see inside the
|
||||
Tag."""
|
||||
if contents and type(contents) != type([]):
|
||||
contents = [contents]
|
||||
results = []
|
||||
for i in self.contents:
|
||||
if isinstance(i, Tag):
|
||||
if not name or i.name == name:
|
||||
match = 1
|
||||
for attr, value in attrs.items():
|
||||
check = i.get(attr)
|
||||
#By default, find the specific value called for.
|
||||
#Use SQL-style wildcards to find substrings, prefix,
|
||||
#suffix, etc.
|
||||
result = (check == value)
|
||||
if check and value:
|
||||
if len(value) > 1 and value[0] == '%' and value[-1] == '%' and value[-2] != '\\':
|
||||
result = (check.find(value[1:-1]) != -1)
|
||||
elif value[0] == '%':
|
||||
print "blah"
|
||||
result = check.rfind(value[1:]) == len(check)-len(value)+1
|
||||
elif value[-1] == '%':
|
||||
result = check.find(value[:-1]) == 0
|
||||
if not result:
|
||||
match = 0
|
||||
break
|
||||
match = match and (not contents or i.contents == contents)
|
||||
if match:
|
||||
results.append(i)
|
||||
if recursive:
|
||||
results.extend(i.fetch(name, attrs, contents, recursive))
|
||||
return results
|
||||
|
||||
class BeautifulSoup(SGMLParser, Tag):
|
||||
|
||||
"""The actual parser. It knows the following facts about HTML, and
|
||||
not much else:
|
||||
|
||||
* Some tags have no closing tag and should be interpreted as being
|
||||
closed as soon as they are encountered.
|
||||
|
||||
* Most tags can't be nested; encountering an open tag when there's
|
||||
already an open tag of that type in the stack means that the
|
||||
previous tag of that type should be implicitly closed. However,
|
||||
some tags can be nested. When a nestable tag is encountered,
|
||||
it's okay to close all unclosed tags up to the last nestable
|
||||
tag. It might not be safe to close any more, so that's all it
|
||||
closes.
|
||||
|
||||
* The text inside some tags (ie. 'script') may contain tags which
|
||||
are not really part of the document and which should be parsed
|
||||
as text, not tags. If you want to parse the text as tags, you can
|
||||
always get it and parse it explicitly."""
|
||||
|
||||
SELF_CLOSING_TAGS = ['br', 'hr', 'input', 'img', 'meta', 'spacer',
|
||||
'link', 'frame']
|
||||
NESTABLE_TAGS = ['font', 'table', 'tr', 'td', 'th', 'tbody', 'p',
|
||||
'div']
|
||||
QUOTE_TAGS = ['script']
|
||||
|
||||
IMPLICITLY_CLOSE_TAGS = 1
|
||||
|
||||
def __init__(self, text=None):
|
||||
Tag.__init__(self, '[document]')
|
||||
SGMLParser.__init__(self)
|
||||
self.quoteStack = []
|
||||
self.hideTag = 1
|
||||
self.reset()
|
||||
if text:
|
||||
self.feed(text)
|
||||
|
||||
def feed(self, text):
|
||||
SGMLParser.feed(self, text)
|
||||
self.endData()
|
||||
|
||||
def reset(self):
|
||||
SGMLParser.reset(self)
|
||||
self.currentData = ''
|
||||
self.currentTag = None
|
||||
self.tagStack = []
|
||||
self.pushTag(self)
|
||||
|
||||
def popTag(self, closedTagName=None):
|
||||
tag = self.tagStack.pop()
|
||||
if closedTagName == tag.name:
|
||||
tag.foundClose = 1
|
||||
|
||||
# Tags with just one string-owning child get the same string
|
||||
# property as the child, so that soup.tag.string is shorthand
|
||||
# for soup.tag.contents[0].string
|
||||
if len(self.currentTag.contents) == 1 and \
|
||||
hasattr(self.currentTag.contents[0], 'string'):
|
||||
self.currentTag.string = self.currentTag.contents[0].string
|
||||
|
||||
#print "Pop", tag.name
|
||||
self.currentTag = self.tagStack[-1]
|
||||
return self.currentTag
|
||||
|
||||
def pushTag(self, tag):
|
||||
#print "Push", tag.name
|
||||
if self.currentTag:
|
||||
self.currentTag.append(tag)
|
||||
self.tagStack.append(tag)
|
||||
self.currentTag = self.tagStack[-1]
|
||||
|
||||
def endData(self):
|
||||
if self.currentData:
|
||||
if not string.strip(self.currentData):
|
||||
if '\n' in self.currentData:
|
||||
self.currentData = '\n'
|
||||
else:
|
||||
self.currentData = ' '
|
||||
o = NavigableText(self.currentData, self.currentTag, self.previous)
|
||||
if self.previous:
|
||||
self.previous.next = o
|
||||
self.previous = o
|
||||
self.currentTag.contents.append(o)
|
||||
self.currentData = ''
|
||||
|
||||
def _popToTag(self, name, closedTag=0):
|
||||
"""Pops the tag stack up to and including the most recent
|
||||
instance of the given tag. If a list of tags is given, will
|
||||
accept any of those tags as an excuse to stop popping, and will
|
||||
*not* pop the tag that caused it to stop popping."""
|
||||
if self.IMPLICITLY_CLOSE_TAGS:
|
||||
closedTag = 1
|
||||
numPops = 0
|
||||
mostRecentTag = None
|
||||
oneTag = (type(name) == types.StringType)
|
||||
for i in range(len(self.tagStack)-1, 0, -1):
|
||||
thisTag = self.tagStack[i].name
|
||||
if (oneTag and thisTag == name) \
|
||||
or (not oneTag and thisTag in name):
|
||||
numPops = len(self.tagStack)-i
|
||||
break
|
||||
if not oneTag:
|
||||
numPops = numPops - 1
|
||||
|
||||
closedTagName = None
|
||||
if closedTag:
|
||||
closedTagName = name
|
||||
|
||||
for i in range(0, numPops):
|
||||
mostRecentTag = self.popTag(closedTagName)
|
||||
return mostRecentTag
|
||||
|
||||
def unknown_starttag(self, name, attrs):
|
||||
if self.quoteStack:
|
||||
#This is not a real tag.
|
||||
#print "<%s> is not real!" % name
|
||||
attrs = map(lambda(x, y): '%s="%s"' % (x, y), attrs)
|
||||
self.handle_data('<%s %s>' % (name, attrs))
|
||||
return
|
||||
self.endData()
|
||||
tag = Tag(name, attrs, self.currentTag, self.previous)
|
||||
if self.previous:
|
||||
self.previous.next = tag
|
||||
self.previous = tag
|
||||
if not name in self.SELF_CLOSING_TAGS:
|
||||
if name in self.NESTABLE_TAGS:
|
||||
self._popToTag(self.NESTABLE_TAGS)
|
||||
else:
|
||||
self._popToTag(name)
|
||||
self.pushTag(tag)
|
||||
if name in self.SELF_CLOSING_TAGS:
|
||||
self.popTag()
|
||||
if name in self.QUOTE_TAGS:
|
||||
#print "Beginning quote (%s)" % name
|
||||
self.quoteStack.append(name)
|
||||
|
||||
def unknown_endtag(self, name):
|
||||
if self.quoteStack and self.quoteStack[-1] != name:
|
||||
#This is not a real end tag.
|
||||
#print "</%s> is not real!" % name
|
||||
self.handle_data('</%s>' % name)
|
||||
return
|
||||
self.endData()
|
||||
self._popToTag(name, 1)
|
||||
if self.quoteStack and self.quoteStack[-1] == name:
|
||||
#print "That's the end of %s!" % self.quoteStack[-1]
|
||||
self.quoteStack.pop()
|
||||
|
||||
def handle_data(self, data):
|
||||
self.currentData = self.currentData + data
|
||||
|
||||
def handle_comment(self, text):
|
||||
"Propagate comments right through."
|
||||
self.handle_data("<!--%s-->" % text)
|
||||
|
||||
def handle_charref(self, ref):
|
||||
"Propagate char refs right through."
|
||||
self.handle_data('&#%s;' % ref)
|
||||
|
||||
def handle_entityref(self, ref):
|
||||
"Propagate entity refs right through."
|
||||
self.handle_data('&%s;' % ref)
|
||||
|
||||
def handle_decl(self, data):
|
||||
"Propagate DOCTYPEs right through."
|
||||
self.handle_data('<!%s>' % data)
|
||||
|
||||
class BeautifulStoneSoup(BeautifulSoup):
|
||||
|
||||
"""A version of BeautifulSoup that doesn't know anything at all
|
||||
about what HTML tags have special behavior. Useful for parsing
|
||||
things that aren't HTML, or when BeautifulSoup makes an assumption
|
||||
counter to what you were expecting."""
|
||||
|
||||
IMPLICITLY_CLOSE_TAGS = 0
|
||||
|
||||
SELF_CLOSING_TAGS = []
|
||||
NESTABLE_TAGS = []
|
||||
QUOTE_TAGS = []
|
85
others/GoogleSOAPFacade.py
Normal file
85
others/GoogleSOAPFacade.py
Normal file
@ -0,0 +1,85 @@
|
||||
"""
|
||||
Facade that hides the differences between the SOAPpy and SOAP.py
|
||||
libraries, so that google.py doesn't have to deal with them.
|
||||
|
||||
@author: Brian Landers <brian@bluecoat93.org>
|
||||
@license: Python
|
||||
@version: 0.5.4
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
__author__ = "Brian Landers <brian@bluecoat93.org>"
|
||||
__version__ = "0.6"
|
||||
__license__ = "Python"
|
||||
|
||||
#
|
||||
# Wrapper around the python 'warnings' facility
|
||||
#
|
||||
def warn( message, level=RuntimeWarning ):
|
||||
warnings.warn( message, level, stacklevel=3 )
|
||||
|
||||
# We can't use older version of SOAPpy, due to bugs that break the Google API
|
||||
minSOAPpyVersion = "0.11.3"
|
||||
|
||||
#
|
||||
# Try loading SOAPpy first. If that fails, fall back to the old SOAP.py
|
||||
#
|
||||
SOAPpy = None
|
||||
try:
|
||||
import SOAPpy
|
||||
from SOAPpy import SOAPProxy, Types
|
||||
|
||||
if LooseVersion( minSOAPpyVersion ) > \
|
||||
LooseVersion( SOAPpy.version.__version__ ):
|
||||
|
||||
warn( "Versions of SOAPpy before %s have known bugs that prevent " +
|
||||
"PyGoogle from functioning." % minSOAPpyVersion )
|
||||
raise ImportError
|
||||
|
||||
except ImportError:
|
||||
warn( "SOAPpy not imported. Trying legacy SOAP.py.",
|
||||
DeprecationWarning )
|
||||
try:
|
||||
import SOAP
|
||||
except ImportError:
|
||||
raise RuntimeError( "Unable to find SOAPpy or SOAP. Can't continue.\n" )
|
||||
|
||||
#
|
||||
# Constants that differ between the modules
|
||||
#
|
||||
if SOAPpy:
|
||||
false = Types.booleanType(0)
|
||||
true = Types.booleanType(1)
|
||||
structType = Types.structType
|
||||
faultType = Types.faultType
|
||||
else:
|
||||
false = SOAP.booleanType(0)
|
||||
true = SOAP.booleanType(1)
|
||||
structType = SOAP.structType
|
||||
faultType = SOAP.faultType
|
||||
|
||||
#
|
||||
# Get a SOAP Proxy object in the correct way for the module we're using
|
||||
#
|
||||
def getProxy( url, namespace, http_proxy ):
|
||||
if SOAPpy:
|
||||
return SOAPProxy( url,
|
||||
namespace = namespace,
|
||||
http_proxy = http_proxy )
|
||||
|
||||
else:
|
||||
return SOAP.SOAPProxy( url,
|
||||
namespace = namespace,
|
||||
http_proxy = http_proxy )
|
||||
|
||||
#
|
||||
# Convert an object to a dictionary in the proper way for the module
|
||||
# we're using for SOAP
|
||||
#
|
||||
def toDict( obj ):
|
||||
if SOAPpy:
|
||||
return obj._asdict()
|
||||
else:
|
||||
return obj._asdict
|
478
others/SOAPpy/Client.py
Normal file
478
others/SOAPpy/Client.py
Normal file
@ -0,0 +1,478 @@
|
||||
"""
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# 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 actzero, inc. nor the names of its contributors 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 REGENTS 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.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
#import xml.sax
|
||||
import urllib
|
||||
from types import *
|
||||
import re
|
||||
import base64
|
||||
|
||||
# SOAPpy modules
|
||||
from Errors import *
|
||||
from Config import Config
|
||||
from Parser import parseSOAPRPC
|
||||
from SOAPBuilder import buildSOAP
|
||||
from Utilities import *
|
||||
from Types import faultType, simplify
|
||||
|
||||
################################################################################
|
||||
# Client
|
||||
################################################################################
|
||||
|
||||
|
||||
def SOAPUserAgent():
|
||||
return "SOAPpy " + __version__ + " (pywebsvcs.sf.net)"
|
||||
|
||||
|
||||
class SOAPAddress:
|
||||
def __init__(self, url, config = Config):
|
||||
proto, uri = urllib.splittype(url)
|
||||
|
||||
# apply some defaults
|
||||
if uri[0:2] != '//':
|
||||
if proto != None:
|
||||
uri = proto + ':' + uri
|
||||
|
||||
uri = '//' + uri
|
||||
proto = 'http'
|
||||
|
||||
host, path = urllib.splithost(uri)
|
||||
|
||||
try:
|
||||
int(host)
|
||||
host = 'localhost:' + host
|
||||
except:
|
||||
pass
|
||||
|
||||
if not path:
|
||||
path = '/'
|
||||
|
||||
if proto not in ('http', 'https', 'httpg'):
|
||||
raise IOError, "unsupported SOAP protocol"
|
||||
if proto == 'httpg' and not config.GSIclient:
|
||||
raise AttributeError, \
|
||||
"GSI client not supported by this Python installation"
|
||||
if proto == 'https' and not config.SSLclient:
|
||||
raise AttributeError, \
|
||||
"SSL client not supported by this Python installation"
|
||||
|
||||
self.user,host = urllib.splituser(host)
|
||||
self.proto = proto
|
||||
self.host = host
|
||||
self.path = path
|
||||
|
||||
def __str__(self):
|
||||
return "%(proto)s://%(host)s%(path)s" % self.__dict__
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class HTTPTransport:
|
||||
def getNS(self, original_namespace, data):
|
||||
"""Extract the (possibly extended) namespace from the returned
|
||||
SOAP message."""
|
||||
|
||||
if type(original_namespace) == StringType:
|
||||
pattern="xmlns:\w+=['\"](" + original_namespace + "[^'\"]*)['\"]"
|
||||
match = re.search(pattern, data)
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
return original_namespace
|
||||
else:
|
||||
return original_namespace
|
||||
|
||||
# Need a Timeout someday?
|
||||
def call(self, addr, data, namespace, soapaction = None, encoding = None,
|
||||
http_proxy = None, config = Config):
|
||||
|
||||
import httplib
|
||||
|
||||
if not isinstance(addr, SOAPAddress):
|
||||
addr = SOAPAddress(addr, config)
|
||||
|
||||
# Build a request
|
||||
if http_proxy:
|
||||
real_addr = http_proxy
|
||||
real_path = addr.proto + "://" + addr.host + addr.path
|
||||
else:
|
||||
real_addr = addr.host
|
||||
real_path = addr.path
|
||||
|
||||
if addr.proto == 'httpg':
|
||||
from pyGlobus.io import GSIHTTP
|
||||
r = GSIHTTP(real_addr, tcpAttr = config.tcpAttr)
|
||||
elif addr.proto == 'https':
|
||||
r = httplib.HTTPS(real_addr)
|
||||
else:
|
||||
r = httplib.HTTP(real_addr)
|
||||
|
||||
r.putrequest("POST", real_path)
|
||||
|
||||
r.putheader("Host", addr.host)
|
||||
r.putheader("User-agent", SOAPUserAgent())
|
||||
t = 'text/xml';
|
||||
if encoding != None:
|
||||
t += '; charset="%s"' % encoding
|
||||
r.putheader("Content-type", t)
|
||||
r.putheader("Content-length", str(len(data)))
|
||||
|
||||
# if user is not a user:passwd format
|
||||
# we'll receive a failure from the server. . .I guess (??)
|
||||
if addr.user != None:
|
||||
val = base64.encodestring(addr.user)
|
||||
r.putheader('Authorization','Basic ' + val.replace('\012',''))
|
||||
|
||||
# This fixes sending either "" or "None"
|
||||
if soapaction == None or len(soapaction) == 0:
|
||||
r.putheader("SOAPAction", "")
|
||||
else:
|
||||
r.putheader("SOAPAction", '"%s"' % soapaction)
|
||||
|
||||
if config.dumpHeadersOut:
|
||||
s = 'Outgoing HTTP headers'
|
||||
debugHeader(s)
|
||||
print "POST %s %s" % (real_path, r._http_vsn_str)
|
||||
print "Host:", addr.host
|
||||
print "User-agent: SOAPpy " + __version__ + " (http://pywebsvcs.sf.net)"
|
||||
print "Content-type:", t
|
||||
print "Content-length:", len(data)
|
||||
print 'SOAPAction: "%s"' % soapaction
|
||||
debugFooter(s)
|
||||
|
||||
r.endheaders()
|
||||
|
||||
if config.dumpSOAPOut:
|
||||
s = 'Outgoing SOAP'
|
||||
debugHeader(s)
|
||||
print data,
|
||||
if data[-1] != '\n':
|
||||
print
|
||||
debugFooter(s)
|
||||
|
||||
# send the payload
|
||||
r.send(data)
|
||||
|
||||
# read response line
|
||||
code, msg, headers = r.getreply()
|
||||
|
||||
content_type = headers.get("content-type","text/xml")
|
||||
content_length = headers.get("Content-length")
|
||||
if content_length == None:
|
||||
# No Content-Length provided; just read the whole socket
|
||||
# This won't work with HTTP/1.1 chunked encoding
|
||||
data = r.getfile().read()
|
||||
message_len = len(data)
|
||||
else:
|
||||
message_len = int(content_length)
|
||||
data = r.getfile().read(message_len)
|
||||
|
||||
if(config.debug):
|
||||
print "code=",code
|
||||
print "msg=", msg
|
||||
print "headers=", headers
|
||||
print "content-type=", content_type
|
||||
print "data=", data
|
||||
|
||||
if config.dumpHeadersIn:
|
||||
s = 'Incoming HTTP headers'
|
||||
debugHeader(s)
|
||||
if headers.headers:
|
||||
print "HTTP/1.? %d %s" % (code, msg)
|
||||
print "\n".join(map (lambda x: x.strip(), headers.headers))
|
||||
else:
|
||||
print "HTTP/0.9 %d %s" % (code, msg)
|
||||
debugFooter(s)
|
||||
|
||||
def startswith(string, val):
|
||||
return string[0:len(val)] == val
|
||||
|
||||
if code == 500 and not \
|
||||
( startswith(content_type, "text/xml") and message_len > 0 ):
|
||||
raise HTTPError(code, msg)
|
||||
|
||||
if config.dumpSOAPIn:
|
||||
s = 'Incoming SOAP'
|
||||
debugHeader(s)
|
||||
print data,
|
||||
if (len(data)>0) and (data[-1] != '\n'):
|
||||
print
|
||||
debugFooter(s)
|
||||
|
||||
if code not in (200, 500):
|
||||
raise HTTPError(code, msg)
|
||||
|
||||
|
||||
# get the new namespace
|
||||
if namespace is None:
|
||||
new_ns = None
|
||||
else:
|
||||
new_ns = self.getNS(namespace, data)
|
||||
|
||||
# return response payload
|
||||
return data, new_ns
|
||||
|
||||
################################################################################
|
||||
# SOAP Proxy
|
||||
################################################################################
|
||||
class SOAPProxy:
|
||||
def __init__(self, proxy, namespace = None, soapaction = None,
|
||||
header = None, methodattrs = None, transport = HTTPTransport,
|
||||
encoding = 'UTF-8', throw_faults = 1, unwrap_results = None,
|
||||
http_proxy=None, config = Config, noroot = 0,
|
||||
simplify_objects=None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
# get default values for unwrap_results and simplify_objects
|
||||
# from config
|
||||
if unwrap_results is None:
|
||||
self.unwrap_results=config.unwrap_results
|
||||
else:
|
||||
self.unwrap_results=unwrap_results
|
||||
|
||||
if simplify_objects is None:
|
||||
self.simplify_objects=config.simplify_objects
|
||||
else:
|
||||
self.simplify_objects=simplify_objects
|
||||
|
||||
self.proxy = SOAPAddress(proxy, config)
|
||||
self.namespace = namespace
|
||||
self.soapaction = soapaction
|
||||
self.header = header
|
||||
self.methodattrs = methodattrs
|
||||
self.transport = transport()
|
||||
self.encoding = encoding
|
||||
self.throw_faults = throw_faults
|
||||
self.http_proxy = http_proxy
|
||||
self.config = config
|
||||
self.noroot = noroot
|
||||
|
||||
# GSI Additions
|
||||
if hasattr(config, "channel_mode") and \
|
||||
hasattr(config, "delegation_mode"):
|
||||
self.channel_mode = config.channel_mode
|
||||
self.delegation_mode = config.delegation_mode
|
||||
#end GSI Additions
|
||||
|
||||
def invoke(self, method, args):
|
||||
return self.__call(method, args, {})
|
||||
|
||||
def __call(self, name, args, kw, ns = None, sa = None, hd = None,
|
||||
ma = None):
|
||||
|
||||
ns = ns or self.namespace
|
||||
ma = ma or self.methodattrs
|
||||
|
||||
if sa: # Get soapaction
|
||||
if type(sa) == TupleType:
|
||||
sa = sa[0]
|
||||
else:
|
||||
if self.soapaction:
|
||||
sa = self.soapaction
|
||||
else:
|
||||
sa = name
|
||||
|
||||
if hd: # Get header
|
||||
if type(hd) == TupleType:
|
||||
hd = hd[0]
|
||||
else:
|
||||
hd = self.header
|
||||
|
||||
hd = hd or self.header
|
||||
|
||||
if ma: # Get methodattrs
|
||||
if type(ma) == TupleType: ma = ma[0]
|
||||
else:
|
||||
ma = self.methodattrs
|
||||
ma = ma or self.methodattrs
|
||||
|
||||
m = buildSOAP(args = args, kw = kw, method = name, namespace = ns,
|
||||
header = hd, methodattrs = ma, encoding = self.encoding,
|
||||
config = self.config, noroot = self.noroot)
|
||||
|
||||
|
||||
call_retry = 0
|
||||
try:
|
||||
|
||||
r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
|
||||
encoding = self.encoding,
|
||||
http_proxy = self.http_proxy,
|
||||
config = self.config)
|
||||
|
||||
except Exception, ex:
|
||||
#
|
||||
# Call failed.
|
||||
#
|
||||
# See if we have a fault handling vector installed in our
|
||||
# config. If we do, invoke it. If it returns a true value,
|
||||
# retry the call.
|
||||
#
|
||||
# In any circumstance other than the fault handler returning
|
||||
# true, reraise the exception. This keeps the semantics of this
|
||||
# code the same as without the faultHandler code.
|
||||
#
|
||||
|
||||
if hasattr(self.config, "faultHandler"):
|
||||
if callable(self.config.faultHandler):
|
||||
call_retry = self.config.faultHandler(self.proxy, ex)
|
||||
if not call_retry:
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
|
||||
if call_retry:
|
||||
r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
|
||||
encoding = self.encoding,
|
||||
http_proxy = self.http_proxy,
|
||||
config = self.config)
|
||||
|
||||
|
||||
p, attrs = parseSOAPRPC(r, attrs = 1)
|
||||
|
||||
try:
|
||||
throw_struct = self.throw_faults and \
|
||||
isinstance (p, faultType)
|
||||
except:
|
||||
throw_struct = 0
|
||||
|
||||
if throw_struct:
|
||||
#print p
|
||||
raise p
|
||||
|
||||
# If unwrap_results=1 and there is only element in the struct,
|
||||
# SOAPProxy will assume that this element is the result
|
||||
# and return it rather than the struct containing it.
|
||||
# Otherwise SOAPproxy will return the struct with all the
|
||||
# elements as attributes.
|
||||
if self.unwrap_results:
|
||||
try:
|
||||
count = 0
|
||||
for i in p.__dict__.keys():
|
||||
if i[0] != "_": # don't count the private stuff
|
||||
count += 1
|
||||
t = getattr(p, i)
|
||||
if count == 1: # Only one piece of data, bubble it up
|
||||
p = t
|
||||
except:
|
||||
pass
|
||||
|
||||
# Automatically simplfy SOAP complex types into the
|
||||
# corresponding python types. (structType --> dict,
|
||||
# arrayType --> array, etc.)
|
||||
if self.simplify_objects:
|
||||
p = simplify(p)
|
||||
|
||||
if self.config.returnAllAttrs:
|
||||
return p, attrs
|
||||
return p
|
||||
|
||||
def _callWithBody(self, body):
|
||||
return self.__call(None, body, {})
|
||||
|
||||
def __getattr__(self, name): # hook to catch method calls
|
||||
if name == '__del__':
|
||||
raise AttributeError, name
|
||||
return self.__Method(self.__call, name, config = self.config)
|
||||
|
||||
# To handle attribute wierdness
|
||||
class __Method:
|
||||
# Some magic to bind a SOAP method to an RPC server.
|
||||
# Supports "nested" methods (e.g. examples.getStateName) -- concept
|
||||
# borrowed from xmlrpc/soaplib -- www.pythonware.com
|
||||
# Altered (improved?) to let you inline namespaces on a per call
|
||||
# basis ala SOAP::LITE -- www.soaplite.com
|
||||
|
||||
def __init__(self, call, name, ns = None, sa = None, hd = None,
|
||||
ma = None, config = Config):
|
||||
|
||||
self.__call = call
|
||||
self.__name = name
|
||||
self.__ns = ns
|
||||
self.__sa = sa
|
||||
self.__hd = hd
|
||||
self.__ma = ma
|
||||
self.__config = config
|
||||
return
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
if self.__name[0] == "_":
|
||||
if self.__name in ["__repr__","__str__"]:
|
||||
return self.__repr__()
|
||||
else:
|
||||
return self.__f_call(*args, **kw)
|
||||
else:
|
||||
return self.__r_call(*args, **kw)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == '__del__':
|
||||
raise AttributeError, name
|
||||
if self.__name[0] == "_":
|
||||
# Don't nest method if it is a directive
|
||||
return self.__class__(self.__call, name, self.__ns,
|
||||
self.__sa, self.__hd, self.__ma)
|
||||
|
||||
return self.__class__(self.__call, "%s.%s" % (self.__name, name),
|
||||
self.__ns, self.__sa, self.__hd, self.__ma)
|
||||
|
||||
def __f_call(self, *args, **kw):
|
||||
if self.__name == "_ns": self.__ns = args
|
||||
elif self.__name == "_sa": self.__sa = args
|
||||
elif self.__name == "_hd": self.__hd = args
|
||||
elif self.__name == "_ma": self.__ma = args
|
||||
return self
|
||||
|
||||
def __r_call(self, *args, **kw):
|
||||
return self.__call(self.__name, args, kw, self.__ns, self.__sa,
|
||||
self.__hd, self.__ma)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s at %d>" % (self.__class__, id(self))
|
202
others/SOAPpy/Config.py
Normal file
202
others/SOAPpy/Config.py
Normal file
@ -0,0 +1,202 @@
|
||||
"""
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# 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 actzero, inc. nor the names of its contributors 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 REGENTS 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.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import copy, socket
|
||||
from types import *
|
||||
|
||||
from NS import NS
|
||||
|
||||
################################################################################
|
||||
# Configuration class
|
||||
################################################################################
|
||||
|
||||
class SOAPConfig:
|
||||
__readonly = ('SSLserver', 'SSLclient', 'GSIserver', 'GSIclient')
|
||||
|
||||
def __init__(self, config = None, **kw):
|
||||
d = self.__dict__
|
||||
|
||||
if config:
|
||||
if not isinstance(config, SOAPConfig):
|
||||
raise AttributeError, \
|
||||
"initializer must be SOAPConfig instance"
|
||||
|
||||
s = config.__dict__
|
||||
|
||||
for k, v in s.items():
|
||||
if k[0] != '_':
|
||||
d[k] = v
|
||||
else:
|
||||
# Setting debug also sets returnFaultInfo,
|
||||
# dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut
|
||||
self.debug = 0
|
||||
self.dumpFaultInfo = 0
|
||||
# Setting namespaceStyle sets typesNamespace, typesNamespaceURI,
|
||||
# schemaNamespace, and schemaNamespaceURI
|
||||
self.namespaceStyle = '1999'
|
||||
self.strictNamespaces = 0
|
||||
self.typed = 1
|
||||
self.buildWithNamespacePrefix = 1
|
||||
self.returnAllAttrs = 0
|
||||
|
||||
# Strict checking of range for floats and doubles
|
||||
self.strict_range = 0
|
||||
|
||||
# Default encoding for dictionary keys
|
||||
self.dict_encoding = 'ascii'
|
||||
|
||||
# New argument name handling mechanism. See
|
||||
# README.MethodParameterNaming for details
|
||||
self.specialArgs = 1
|
||||
|
||||
# If unwrap_results=1 and there is only element in the struct,
|
||||
# SOAPProxy will assume that this element is the result
|
||||
# and return it rather than the struct containing it.
|
||||
# Otherwise SOAPproxy will return the struct with all the
|
||||
# elements as attributes.
|
||||
self.unwrap_results = 1
|
||||
|
||||
# Automatically convert SOAP complex types, and
|
||||
# (recursively) public contents into the corresponding
|
||||
# python types. (Private subobjects have names that start
|
||||
# with '_'.)
|
||||
#
|
||||
# Conversions:
|
||||
# - faultType --> raise python exception
|
||||
# - arrayType --> array
|
||||
# - compoundType --> dictionary
|
||||
#
|
||||
self.simplify_objects = 0
|
||||
|
||||
# Per-class authorization method. If this is set, before
|
||||
# calling a any class method, the specified authorization
|
||||
# method will be called. If it returns 1, the method call
|
||||
# will proceed, otherwise the call will throw with an
|
||||
# authorization error.
|
||||
self.authMethod = None
|
||||
|
||||
# Globus Support if pyGlobus.io available
|
||||
try:
|
||||
from pyGlobus import io;
|
||||
d['GSIserver'] = 1
|
||||
d['GSIclient'] = 1
|
||||
except:
|
||||
d['GSIserver'] = 0
|
||||
d['GSIclient'] = 0
|
||||
|
||||
|
||||
# Server SSL support if M2Crypto.SSL available
|
||||
try:
|
||||
from M2Crypto import SSL
|
||||
d['SSLserver'] = 1
|
||||
except:
|
||||
d['SSLserver'] = 0
|
||||
|
||||
# Client SSL support if socket.ssl available
|
||||
try:
|
||||
from socket import ssl
|
||||
d['SSLclient'] = 1
|
||||
except:
|
||||
d['SSLclient'] = 0
|
||||
|
||||
for k, v in kw.items():
|
||||
if k[0] != '_':
|
||||
setattr(self, k, v)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self.__readonly:
|
||||
raise AttributeError, "readonly configuration setting"
|
||||
|
||||
d = self.__dict__
|
||||
|
||||
if name in ('typesNamespace', 'typesNamespaceURI',
|
||||
'schemaNamespace', 'schemaNamespaceURI'):
|
||||
|
||||
if name[-3:] == 'URI':
|
||||
base, uri = name[:-3], 1
|
||||
else:
|
||||
base, uri = name, 0
|
||||
|
||||
if type(value) == StringType:
|
||||
if NS.NSMAP.has_key(value):
|
||||
n = (value, NS.NSMAP[value])
|
||||
elif NS.NSMAP_R.has_key(value):
|
||||
n = (NS.NSMAP_R[value], value)
|
||||
else:
|
||||
raise AttributeError, "unknown namespace"
|
||||
elif type(value) in (ListType, TupleType):
|
||||
if uri:
|
||||
n = (value[1], value[0])
|
||||
else:
|
||||
n = (value[0], value[1])
|
||||
else:
|
||||
raise AttributeError, "unknown namespace type"
|
||||
|
||||
d[base], d[base + 'URI'] = n
|
||||
|
||||
try:
|
||||
d['namespaceStyle'] = \
|
||||
NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])]
|
||||
except:
|
||||
d['namespaceStyle'] = ''
|
||||
|
||||
elif name == 'namespaceStyle':
|
||||
value = str(value)
|
||||
|
||||
if not NS.STMAP.has_key(value):
|
||||
raise AttributeError, "unknown namespace style"
|
||||
|
||||
d[name] = value
|
||||
n = d['typesNamespace'] = NS.STMAP[value][0]
|
||||
d['typesNamespaceURI'] = NS.NSMAP[n]
|
||||
n = d['schemaNamespace'] = NS.STMAP[value][1]
|
||||
d['schemaNamespaceURI'] = NS.NSMAP[n]
|
||||
|
||||
elif name == 'debug':
|
||||
d[name] = \
|
||||
d['returnFaultInfo'] = \
|
||||
d['dumpHeadersIn'] = \
|
||||
d['dumpHeadersOut'] = \
|
||||
d['dumpSOAPIn'] = \
|
||||
d['dumpSOAPOut'] = value
|
||||
|
||||
else:
|
||||
d[name] = value
|
||||
|
||||
|
||||
Config = SOAPConfig()
|
79
others/SOAPpy/Errors.py
Normal file
79
others/SOAPpy/Errors.py
Normal file
@ -0,0 +1,79 @@
|
||||
"""
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# 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 actzero, inc. nor the names of its contributors 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 REGENTS 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.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import exceptions
|
||||
|
||||
################################################################################
|
||||
# Exceptions
|
||||
################################################################################
|
||||
class Error(exceptions.Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
def __str__(self):
|
||||
return "<Error : %s>" % self.msg
|
||||
__repr__ = __str__
|
||||
def __call__(self):
|
||||
return (msg,)
|
||||
|
||||
class RecursionError(Error):
|
||||
pass
|
||||
|
||||
class UnknownTypeError(Error):
|
||||
pass
|
||||
|
||||
class HTTPError(Error):
|
||||
# indicates an HTTP protocol error
|
||||
def __init__(self, code, msg):
|
||||
self.code = code
|
||||
self.msg = msg
|
||||
def __str__(self):
|
||||
return "<HTTPError %s %s>" % (self.code, self.msg)
|
||||
__repr__ = __str__
|
||||
def __call___(self):
|
||||
return (self.code, self.msg, )
|
||||
|
||||
class UnderflowError(exceptions.ArithmeticError):
|
||||
pass
|
||||
|
143
others/SOAPpy/GSIServer.py
Normal file
143
others/SOAPpy/GSIServer.py
Normal file
@ -0,0 +1,143 @@
|
||||
"""
|
||||
GSIServer - Contributed by Ivan R. Judson <judson@mcs.anl.gov>
|
||||
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# 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 actzero, inc. nor the names of its contributors 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 REGENTS 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.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
#import xml.sax
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import SocketServer
|
||||
from types import *
|
||||
import BaseHTTPServer
|
||||
|
||||
# SOAPpy modules
|
||||
from Parser import parseSOAPRPC
|
||||
from Config import SOAPConfig
|
||||
from Types import faultType, voidType, simplify
|
||||
from NS import NS
|
||||
from SOAPBuilder import buildSOAP
|
||||
from Utilities import debugHeader, debugFooter
|
||||
|
||||
try: from M2Crypto import SSL
|
||||
except: pass
|
||||
|
||||
#####
|
||||
|
||||
from Server import *
|
||||
|
||||
from pyGlobus.io import GSITCPSocketServer, ThreadingGSITCPSocketServer
|
||||
from pyGlobus import ioc
|
||||
|
||||
def GSIConfig():
|
||||
config = SOAPConfig()
|
||||
config.channel_mode = ioc.GLOBUS_IO_SECURE_CHANNEL_MODE_GSI_WRAP
|
||||
config.delegation_mode = ioc.GLOBUS_IO_SECURE_DELEGATION_MODE_FULL_PROXY
|
||||
config.tcpAttr = None
|
||||
config.authMethod = "_authorize"
|
||||
return config
|
||||
|
||||
Config = GSIConfig()
|
||||
|
||||
class GSISOAPServer(GSITCPSocketServer, SOAPServerBase):
|
||||
def __init__(self, addr = ('localhost', 8000),
|
||||
RequestHandler = SOAPRequestHandler, log = 0,
|
||||
encoding = 'UTF-8', config = Config, namespace = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
GSITCPSocketServer.__init__(self, addr, RequestHandler,
|
||||
self.config.channel_mode,
|
||||
self.config.delegation_mode,
|
||||
tcpAttr = self.config.tcpAttr)
|
||||
|
||||
def get_request(self):
|
||||
sock, addr = GSITCPSocketServer.get_request(self)
|
||||
|
||||
return sock, addr
|
||||
|
||||
class ThreadingGSISOAPServer(ThreadingGSITCPSocketServer, SOAPServerBase):
|
||||
|
||||
def __init__(self, addr = ('localhost', 8000),
|
||||
RequestHandler = SOAPRequestHandler, log = 0,
|
||||
encoding = 'UTF-8', config = Config, namespace = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
ThreadingGSITCPSocketServer.__init__(self, addr, RequestHandler,
|
||||
self.config.channel_mode,
|
||||
self.config.delegation_mode,
|
||||
tcpAttr = self.config.tcpAttr)
|
||||
|
||||
def get_request(self):
|
||||
sock, addr = ThreadingGSITCPSocketServer.get_request(self)
|
||||
|
||||
return sock, addr
|
||||
|
104
others/SOAPpy/NS.py
Normal file
104
others/SOAPpy/NS.py
Normal file
@ -0,0 +1,104 @@
|
||||
"""
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# 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 actzero, inc. nor the names of its contributors 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 REGENTS 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 __future__ import nested_scopes
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
##############################################################################
|
||||
# Namespace Class
|
||||
################################################################################
|
||||
def invertDict(dict):
|
||||
d = {}
|
||||
|
||||
for k, v in dict.items():
|
||||
d[v] = k
|
||||
|
||||
return d
|
||||
|
||||
class NS:
|
||||
XML = "http://www.w3.org/XML/1998/namespace"
|
||||
|
||||
ENV = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||
ENC = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||
|
||||
XSD = "http://www.w3.org/1999/XMLSchema"
|
||||
XSD2 = "http://www.w3.org/2000/10/XMLSchema"
|
||||
XSD3 = "http://www.w3.org/2001/XMLSchema"
|
||||
|
||||
XSD_L = [XSD, XSD2, XSD3]
|
||||
EXSD_L= [ENC, XSD, XSD2, XSD3]
|
||||
|
||||
XSI = "http://www.w3.org/1999/XMLSchema-instance"
|
||||
XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance"
|
||||
XSI3 = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
XSI_L = [XSI, XSI2, XSI3]
|
||||
|
||||
URN = "http://soapinterop.org/xsd"
|
||||
|
||||
# For generated messages
|
||||
XML_T = "xml"
|
||||
ENV_T = "SOAP-ENV"
|
||||
ENC_T = "SOAP-ENC"
|
||||
XSD_T = "xsd"
|
||||
XSD2_T= "xsd2"
|
||||
XSD3_T= "xsd3"
|
||||
XSI_T = "xsi"
|
||||
XSI2_T= "xsi2"
|
||||
XSI3_T= "xsi3"
|
||||
URN_T = "urn"
|
||||
|
||||
NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2,
|
||||
XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3,
|
||||
URN_T: URN}
|
||||
NSMAP_R = invertDict(NSMAP)
|
||||
|
||||
STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T),
|
||||
'2001': (XSD3_T, XSI3_T)}
|
||||
STMAP_R = invertDict(STMAP)
|
||||
|
||||
def __init__(self):
|
||||
raise Error, "Don't instantiate this"
|
||||
|
||||
|
||||
|
1024
others/SOAPpy/Parser.py
Normal file
1024
others/SOAPpy/Parser.py
Normal file
File diff suppressed because it is too large
Load Diff
40
others/SOAPpy/SOAP.py
Normal file
40
others/SOAPpy/SOAP.py
Normal file
@ -0,0 +1,40 @@
|
||||
"""This file is here for backward compatibility with versions <= 0.9.9
|
||||
|
||||
Delete when 1.0.0 is released!
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from Client import *
|
||||
from Config import *
|
||||
from Errors import *
|
||||
from NS import *
|
||||
from Parser import *
|
||||
from SOAPBuilder import *
|
||||
from Server import *
|
||||
from Types import *
|
||||
from Utilities import *
|
||||
import wstools
|
||||
import WSDL
|
||||
|
||||
from warnings import warn
|
||||
|
||||
warn("""
|
||||
|
||||
The sub-module SOAPpy.SOAP is deprecated and is only
|
||||
provided for short-term backward compatibility. Objects are now
|
||||
available directly within the SOAPpy module. Thus, instead of
|
||||
|
||||
from SOAPpy import SOAP
|
||||
...
|
||||
SOAP.SOAPProxy(...)
|
||||
|
||||
use
|
||||
|
||||
from SOAPpy import SOAPProxy
|
||||
...
|
||||
SOAPProxy(...)
|
||||
|
||||
instead.
|
||||
""", DeprecationWarning)
|
620
others/SOAPpy/SOAPBuilder.py
Normal file
620
others/SOAPpy/SOAPBuilder.py
Normal file
@ -0,0 +1,620 @@
|
||||
"""
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# 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 actzero, inc. nor the names of its contributors 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 REGENTS 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.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import cgi
|
||||
import copy
|
||||
from wstools.XMLname import toXMLname, fromXMLname
|
||||
import fpconst
|
||||
|
||||
# SOAPpy modules
|
||||
from Config import Config
|
||||
from NS import NS
|
||||
from Types import *
|
||||
|
||||
# Test whether this Python version has Types.BooleanType
|
||||
# If it doesn't have it, then False and True are serialized as integers
|
||||
try:
|
||||
BooleanType
|
||||
pythonHasBooleanType = 1
|
||||
except NameError:
|
||||
pythonHasBooleanType = 0
|
||||
|
||||
################################################################################
|
||||
# SOAP Builder
|
||||
################################################################################
|
||||
class SOAPBuilder:
|
||||
_xml_top = '<?xml version="1.0"?>\n'
|
||||
_xml_enc_top = '<?xml version="1.0" encoding="%s"?>\n'
|
||||
_env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \
|
||||
NS.__dict__
|
||||
_env_bot = '</%(ENV_T)s:Envelope>\n' % NS.__dict__
|
||||
|
||||
# Namespaces potentially defined in the Envelope tag.
|
||||
|
||||
_env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T,
|
||||
NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T,
|
||||
NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T}
|
||||
|
||||
def __init__(self, args = (), kw = {}, method = None, namespace = None,
|
||||
header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8',
|
||||
use_refs = 0, config = Config, noroot = 0):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
self.args = args
|
||||
self.kw = kw
|
||||
self.envelope = envelope
|
||||
self.encoding = encoding
|
||||
self.method = method
|
||||
self.namespace = namespace
|
||||
self.header = header
|
||||
self.methodattrs= methodattrs
|
||||
self.use_refs = use_refs
|
||||
self.config = config
|
||||
self.out = []
|
||||
self.tcounter = 0
|
||||
self.ncounter = 1
|
||||
self.icounter = 1
|
||||
self.envns = {}
|
||||
self.ids = {}
|
||||
self.depth = 0
|
||||
self.multirefs = []
|
||||
self.multis = 0
|
||||
self.body = not isinstance(args, bodyType)
|
||||
self.noroot = noroot
|
||||
|
||||
def build(self):
|
||||
if Config.debug: print "In build."
|
||||
ns_map = {}
|
||||
|
||||
# Cache whether typing is on or not
|
||||
typed = self.config.typed
|
||||
|
||||
if self.header:
|
||||
# Create a header.
|
||||
self.dump(self.header, "Header", typed = typed)
|
||||
self.header = None # Wipe it out so no one is using it.
|
||||
|
||||
if self.body:
|
||||
# Call genns to record that we've used SOAP-ENV.
|
||||
self.depth += 1
|
||||
body_ns = self.genns(ns_map, NS.ENV)[0]
|
||||
self.out.append("<%sBody>\n" % body_ns)
|
||||
|
||||
if self.method:
|
||||
self.depth += 1
|
||||
a = ''
|
||||
if self.methodattrs:
|
||||
for (k, v) in self.methodattrs.items():
|
||||
a += ' %s="%s"' % (k, v)
|
||||
|
||||
if self.namespace: # Use the namespace info handed to us
|
||||
methodns, n = self.genns(ns_map, self.namespace)
|
||||
else:
|
||||
methodns, n = '', ''
|
||||
|
||||
self.out.append('<%s%s%s%s%s>\n' % (
|
||||
methodns, self.method, n, a, self.genroot(ns_map)))
|
||||
|
||||
try:
|
||||
if type(self.args) != TupleType:
|
||||
args = (self.args,)
|
||||
else:
|
||||
args = self.args
|
||||
|
||||
for i in args:
|
||||
self.dump(i, typed = typed, ns_map = ns_map)
|
||||
|
||||
if hasattr(self.config, "argsOrdering") and self.config.argsOrdering.has_key(self.method):
|
||||
for k in self.config.argsOrdering.get(self.method):
|
||||
self.dump(self.kw.get(k), k, typed = typed, ns_map = ns_map)
|
||||
else:
|
||||
for (k, v) in self.kw.items():
|
||||
self.dump(v, k, typed = typed, ns_map = ns_map)
|
||||
|
||||
except RecursionError:
|
||||
if self.use_refs == 0:
|
||||
# restart
|
||||
b = SOAPBuilder(args = self.args, kw = self.kw,
|
||||
method = self.method, namespace = self.namespace,
|
||||
header = self.header, methodattrs = self.methodattrs,
|
||||
envelope = self.envelope, encoding = self.encoding,
|
||||
use_refs = 1, config = self.config)
|
||||
return b.build()
|
||||
raise
|
||||
|
||||
if self.method:
|
||||
self.out.append("</%s%s>\n" % (methodns, self.method))
|
||||
self.depth -= 1
|
||||
|
||||
if self.body:
|
||||
# dump may add to self.multirefs, but the for loop will keep
|
||||
# going until it has used all of self.multirefs, even those
|
||||
# entries added while in the loop.
|
||||
|
||||
self.multis = 1
|
||||
|
||||
for obj, tag in self.multirefs:
|
||||
self.dump(obj, tag, typed = typed, ns_map = ns_map)
|
||||
|
||||
self.out.append("</%sBody>\n" % body_ns)
|
||||
self.depth -= 1
|
||||
|
||||
if self.envelope:
|
||||
e = map (lambda ns: ' xmlns:%s="%s"' % (ns[1], ns[0]),
|
||||
self.envns.items())
|
||||
|
||||
self.out = ['<', self._env_top] + e + ['>\n'] + \
|
||||
self.out + \
|
||||
[self._env_bot]
|
||||
|
||||
if self.encoding != None:
|
||||
self.out.insert(0, self._xml_enc_top % self.encoding)
|
||||
return ''.join(self.out).encode(self.encoding)
|
||||
|
||||
self.out.insert(0, self._xml_top)
|
||||
return ''.join(self.out)
|
||||
|
||||
def gentag(self):
|
||||
if Config.debug: print "In gentag."
|
||||
self.tcounter += 1
|
||||
return "v%d" % self.tcounter
|
||||
|
||||
def genns(self, ns_map, nsURI):
|
||||
if nsURI == None:
|
||||
return ('', '')
|
||||
|
||||
if type(nsURI) == TupleType: # already a tuple
|
||||
if len(nsURI) == 2:
|
||||
ns, nsURI = nsURI
|
||||
else:
|
||||
ns, nsURI = None, nsURI[0]
|
||||
else:
|
||||
ns = None
|
||||
|
||||
if ns_map.has_key(nsURI):
|
||||
return (ns_map[nsURI] + ':', '')
|
||||
|
||||
if self._env_ns.has_key(nsURI):
|
||||
ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI]
|
||||
return (ns + ':', '')
|
||||
|
||||
if not ns:
|
||||
ns = "ns%d" % self.ncounter
|
||||
self.ncounter += 1
|
||||
ns_map[nsURI] = ns
|
||||
if self.config.buildWithNamespacePrefix:
|
||||
return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI))
|
||||
else:
|
||||
return ('', ' xmlns="%s"' % (nsURI))
|
||||
|
||||
def genroot(self, ns_map):
|
||||
if self.noroot:
|
||||
return ''
|
||||
|
||||
if self.depth != 2:
|
||||
return ''
|
||||
|
||||
ns, n = self.genns(ns_map, NS.ENC)
|
||||
return ' %sroot="%d"%s' % (ns, not self.multis, n)
|
||||
|
||||
# checkref checks an element to see if it needs to be encoded as a
|
||||
# multi-reference element or not. If it returns None, the element has
|
||||
# been handled and the caller can continue with subsequent elements.
|
||||
# If it returns a string, the string should be included in the opening
|
||||
# tag of the marshaled element.
|
||||
|
||||
def checkref(self, obj, tag, ns_map):
|
||||
if self.depth < 2:
|
||||
return ''
|
||||
|
||||
if not self.ids.has_key(id(obj)):
|
||||
n = self.ids[id(obj)] = self.icounter
|
||||
self.icounter = n + 1
|
||||
|
||||
if self.use_refs == 0:
|
||||
return ''
|
||||
|
||||
if self.depth == 2:
|
||||
return ' id="i%d"' % n
|
||||
|
||||
self.multirefs.append((obj, tag))
|
||||
else:
|
||||
if self.use_refs == 0:
|
||||
raise RecursionError, "Cannot serialize recursive object"
|
||||
|
||||
n = self.ids[id(obj)]
|
||||
|
||||
if self.multis and self.depth == 2:
|
||||
return ' id="i%d"' % n
|
||||
|
||||
self.out.append('<%s href="#i%d"%s/>\n' %
|
||||
(tag, n, self.genroot(ns_map)))
|
||||
return None
|
||||
|
||||
# dumpers
|
||||
|
||||
def dump(self, obj, tag = None, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump.", "obj=", obj
|
||||
ns_map = ns_map.copy()
|
||||
self.depth += 1
|
||||
|
||||
if type(tag) not in (NoneType, StringType, UnicodeType):
|
||||
raise KeyError, "tag must be a string or None"
|
||||
|
||||
try:
|
||||
meth = getattr(self, "dump_" + type(obj).__name__)
|
||||
except AttributeError:
|
||||
if type(obj) == LongType:
|
||||
obj_type = "integer"
|
||||
elif pythonHasBooleanType and type(obj) == BooleanType:
|
||||
obj_type = "boolean"
|
||||
else:
|
||||
obj_type = type(obj).__name__
|
||||
|
||||
self.out.append(self.dumper(None, obj_type, obj, tag, typed,
|
||||
ns_map, self.genroot(ns_map)))
|
||||
else:
|
||||
meth(obj, tag, typed, ns_map)
|
||||
|
||||
|
||||
self.depth -= 1
|
||||
|
||||
# generic dumper
|
||||
def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {},
|
||||
rootattr = '', id = '',
|
||||
xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s</%(tag)s>\n'):
|
||||
if Config.debug: print "In dumper."
|
||||
|
||||
if nsURI == None:
|
||||
nsURI = self.config.typesNamespaceURI
|
||||
|
||||
tag = tag or self.gentag()
|
||||
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
a = n = t = ''
|
||||
if typed and obj_type:
|
||||
ns, n = self.genns(ns_map, nsURI)
|
||||
ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
|
||||
t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n)
|
||||
|
||||
try: a = obj._marshalAttrs(ns_map, self)
|
||||
except: pass
|
||||
|
||||
try: data = obj._marshalData()
|
||||
except:
|
||||
if (obj_type != "string"): # strings are already encoded
|
||||
data = cgi.escape(str(obj))
|
||||
else:
|
||||
data = obj
|
||||
|
||||
|
||||
return xml % {"tag": tag, "type": t, "data": data, "root": rootattr,
|
||||
"id": id, "attrs": a}
|
||||
|
||||
def dump_float(self, obj, tag, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump_float."
|
||||
tag = tag or self.gentag()
|
||||
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
if Config.strict_range:
|
||||
doubleType(obj)
|
||||
|
||||
if fpconst.isPosInf(obj):
|
||||
obj = "INF"
|
||||
elif fpconst.isNegInf(obj):
|
||||
obj = "-INF"
|
||||
elif fpconst.isNaN(obj):
|
||||
obj = "NaN"
|
||||
else:
|
||||
obj = str(obj)
|
||||
|
||||
# Note: python 'float' is actually a SOAP 'double'.
|
||||
self.out.append(self.dumper(None, "double", obj, tag, typed, ns_map,
|
||||
self.genroot(ns_map)))
|
||||
|
||||
def dump_string(self, obj, tag, typed = 0, ns_map = {}):
|
||||
if Config.debug: print "In dump_string."
|
||||
tag = tag or self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
id = self.checkref(obj, tag, ns_map)
|
||||
if id == None:
|
||||
return
|
||||
|
||||
try: data = obj._marshalData()
|
||||
except: data = obj
|
||||
|
||||
self.out.append(self.dumper(None, "string", cgi.escape(data), tag,
|
||||
typed, ns_map, self.genroot(ns_map), id))
|
||||
|
||||
dump_str = dump_string # For Python 2.2+
|
||||
dump_unicode = dump_string
|
||||
|
||||
def dump_None(self, obj, tag, typed = 0, ns_map = {}):
|
||||
if Config.debug: print "In dump_None."
|
||||
tag = tag or self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
|
||||
|
||||
self.out.append('<%s %snull="1"%s/>\n' %
|
||||
(tag, ns, self.genroot(ns_map)))
|
||||
|
||||
dump_NoneType = dump_None # For Python 2.2+
|
||||
|
||||
def dump_list(self, obj, tag, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump_list.", "obj=", obj
|
||||
tag = tag or self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
if type(obj) == InstanceType:
|
||||
data = obj.data
|
||||
else:
|
||||
data = obj
|
||||
|
||||
id = self.checkref(obj, tag, ns_map)
|
||||
if id == None:
|
||||
return
|
||||
|
||||
try:
|
||||
sample = data[0]
|
||||
empty = 0
|
||||
except:
|
||||
# preserve type if present
|
||||
if getattr(obj,"_typed",None) and getattr(obj,"_type",None):
|
||||
if getattr(obj, "_complexType", None):
|
||||
sample = typedArrayType(typed=obj._type,
|
||||
complexType = obj._complexType)
|
||||
sample._typename = obj._type
|
||||
obj._ns = NS.URN
|
||||
else:
|
||||
sample = typedArrayType(typed=obj._type)
|
||||
else:
|
||||
sample = structType()
|
||||
empty = 1
|
||||
|
||||
# First scan list to see if all are the same type
|
||||
same_type = 1
|
||||
|
||||
if not empty:
|
||||
for i in data[1:]:
|
||||
if type(sample) != type(i) or \
|
||||
(type(sample) == InstanceType and \
|
||||
sample.__class__ != i.__class__):
|
||||
same_type = 0
|
||||
break
|
||||
|
||||
ndecl = ''
|
||||
if same_type:
|
||||
if (isinstance(sample, structType)) or \
|
||||
type(sample) == DictType or \
|
||||
(isinstance(sample, anyType) and \
|
||||
(getattr(sample, "_complexType", None) and \
|
||||
sample._complexType)): # force to urn struct
|
||||
try:
|
||||
tns = obj._ns or NS.URN
|
||||
except:
|
||||
tns = NS.URN
|
||||
|
||||
ns, ndecl = self.genns(ns_map, tns)
|
||||
|
||||
try:
|
||||
typename = sample._typename
|
||||
except:
|
||||
typename = "SOAPStruct"
|
||||
|
||||
t = ns + typename
|
||||
|
||||
elif isinstance(sample, anyType):
|
||||
ns = sample._validNamespaceURI(self.config.typesNamespaceURI,
|
||||
self.config.strictNamespaces)
|
||||
if ns:
|
||||
ns, ndecl = self.genns(ns_map, ns)
|
||||
t = ns + sample._type
|
||||
else:
|
||||
t = 'ur-type'
|
||||
else:
|
||||
typename = type(sample).__name__
|
||||
|
||||
# For Python 2.2+
|
||||
if type(sample) == StringType: typename = 'string'
|
||||
|
||||
# HACK: python 'float' is actually a SOAP 'double'.
|
||||
if typename=="float": typename="double"
|
||||
t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
|
||||
typename
|
||||
|
||||
else:
|
||||
t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
|
||||
"ur-type"
|
||||
|
||||
try: a = obj._marshalAttrs(ns_map, self)
|
||||
except: a = ''
|
||||
|
||||
ens, edecl = self.genns(ns_map, NS.ENC)
|
||||
ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI)
|
||||
|
||||
self.out.append(
|
||||
'<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %
|
||||
(tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl,
|
||||
self.genroot(ns_map), id, a))
|
||||
|
||||
typed = not same_type
|
||||
|
||||
try: elemsname = obj._elemsname
|
||||
except: elemsname = "item"
|
||||
|
||||
for i in data:
|
||||
self.dump(i, elemsname, typed, ns_map)
|
||||
|
||||
self.out.append('</%s>\n' % tag)
|
||||
|
||||
dump_tuple = dump_list
|
||||
|
||||
def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump_dictionary."
|
||||
tag = tag or self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
id = self.checkref(obj, tag, ns_map)
|
||||
if id == None:
|
||||
return
|
||||
|
||||
try: a = obj._marshalAttrs(ns_map, self)
|
||||
except: a = ''
|
||||
|
||||
self.out.append('<%s%s%s%s>\n' %
|
||||
(tag, id, a, self.genroot(ns_map)))
|
||||
|
||||
for (k, v) in obj.items():
|
||||
if k[0] != "_":
|
||||
self.dump(v, k, 1, ns_map)
|
||||
|
||||
self.out.append('</%s>\n' % tag)
|
||||
|
||||
dump_dict = dump_dictionary # For Python 2.2+
|
||||
|
||||
def dump_instance(self, obj, tag, typed = 1, ns_map = {}):
|
||||
if Config.debug: print "In dump_instance.", "obj=", obj, "tag=", tag
|
||||
if not tag:
|
||||
# If it has a name use it.
|
||||
if isinstance(obj, anyType) and obj._name:
|
||||
tag = obj._name
|
||||
else:
|
||||
tag = self.gentag()
|
||||
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||
|
||||
if isinstance(obj, arrayType): # Array
|
||||
self.dump_list(obj, tag, typed, ns_map)
|
||||
return
|
||||
|
||||
if isinstance(obj, faultType): # Fault
|
||||
cns, cdecl = self.genns(ns_map, NS.ENC)
|
||||
vns, vdecl = self.genns(ns_map, NS.ENV)
|
||||
self.out.append('''<%sFault %sroot="1"%s%s>
|
||||
<faultcode>%s</faultcode>
|
||||
<faultstring>%s</faultstring>
|
||||
''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring))
|
||||
if hasattr(obj, "detail"):
|
||||
self.dump(obj.detail, "detail", typed, ns_map)
|
||||
self.out.append("</%sFault>\n" % vns)
|
||||
return
|
||||
|
||||
r = self.genroot(ns_map)
|
||||
|
||||
try: a = obj._marshalAttrs(ns_map, self)
|
||||
except: a = ''
|
||||
|
||||
if isinstance(obj, voidType): # void
|
||||
self.out.append("<%s%s%s></%s>\n" % (tag, a, r, tag))
|
||||
return
|
||||
|
||||
id = self.checkref(obj, tag, ns_map)
|
||||
if id == None:
|
||||
return
|
||||
|
||||
if isinstance(obj, structType):
|
||||
# Check for namespace
|
||||
ndecl = ''
|
||||
ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
|
||||
self.config.strictNamespaces)
|
||||
if ns:
|
||||
ns, ndecl = self.genns(ns_map, ns)
|
||||
tag = ns + tag
|
||||
self.out.append("<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r))
|
||||
|
||||
keylist = obj.__dict__.keys()
|
||||
|
||||
# first write out items with order information
|
||||
if hasattr(obj, '_keyord'):
|
||||
for i in range(len(obj._keyord)):
|
||||
self.dump(obj._aslist(i), obj._keyord[i], 1, ns_map)
|
||||
keylist.remove(obj._keyord[i])
|
||||
|
||||
# now write out the rest
|
||||
for k in keylist:
|
||||
if (k[0] != "_"):
|
||||
self.dump(getattr(obj,k), k, 1, ns_map)
|
||||
|
||||
if isinstance(obj, bodyType):
|
||||
self.multis = 1
|
||||
|
||||
for v, k in self.multirefs:
|
||||
self.dump(v, k, typed = typed, ns_map = ns_map)
|
||||
|
||||
self.out.append('</%s>\n' % tag)
|
||||
|
||||
elif isinstance(obj, anyType):
|
||||
t = ''
|
||||
|
||||
if typed:
|
||||
ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
|
||||
self.config.strictNamespaces)
|
||||
if ns:
|
||||
ons, ondecl = self.genns(ns_map, ns)
|
||||
ins, indecl = self.genns(ns_map,
|
||||
self.config.schemaNamespaceURI)
|
||||
t = ' %stype="%s%s"%s%s' % \
|
||||
(ins, ons, obj._type, ondecl, indecl)
|
||||
|
||||
self.out.append('<%s%s%s%s%s>%s</%s>\n' %
|
||||
(tag, t, id, a, r, obj._marshalData(), tag))
|
||||
|
||||
else: # Some Class
|
||||
self.out.append('<%s%s%s>\n' % (tag, id, r))
|
||||
|
||||
for (k, v) in obj.__dict__.items():
|
||||
if k[0] != "_":
|
||||
self.dump(v, k, 1, ns_map)
|
||||
|
||||
self.out.append('</%s>\n' % tag)
|
||||
|
||||
|
||||
################################################################################
|
||||
# SOAPBuilder's more public interface
|
||||
################################################################################
|
||||
def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None,
|
||||
methodattrs=None,envelope=1,encoding='UTF-8',config=Config,noroot = 0):
|
||||
t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace,
|
||||
header=header, methodattrs=methodattrs,envelope=envelope,
|
||||
encoding=encoding, config=config,noroot=noroot)
|
||||
return t.build()
|
706
others/SOAPpy/Server.py
Normal file
706
others/SOAPpy/Server.py
Normal file
@ -0,0 +1,706 @@
|
||||
"""
|
||||
################################################################################
|
||||
#
|
||||
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||
# Brian Matthews (blm@actzero.com)
|
||||
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||
# Christopher Blunck (blunck@gst.com)
|
||||
#
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# 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 actzero, inc. nor the names of its contributors 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 REGENTS 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.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
#import xml.sax
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import SocketServer
|
||||
from types import *
|
||||
import BaseHTTPServer
|
||||
import thread
|
||||
|
||||
# SOAPpy modules
|
||||
from Parser import parseSOAPRPC
|
||||
from Config import Config
|
||||
from Types import faultType, voidType, simplify
|
||||
from NS import NS
|
||||
from SOAPBuilder import buildSOAP
|
||||
from Utilities import debugHeader, debugFooter
|
||||
|
||||
try: from M2Crypto import SSL
|
||||
except: pass
|
||||
|
||||
ident = '$Id$'
|
||||
|
||||
from version import __version__
|
||||
|
||||
################################################################################
|
||||
# Call context dictionary
|
||||
################################################################################
|
||||
|
||||
_contexts = dict()
|
||||
|
||||
def GetSOAPContext():
|
||||
global _contexts
|
||||
return _contexts[thread.get_ident()]
|
||||
|
||||
################################################################################
|
||||
# Server
|
||||
################################################################################
|
||||
|
||||
# Method Signature class for adding extra info to registered funcs, right now
|
||||
# used just to indicate it should be called with keywords, instead of ordered
|
||||
# params.
|
||||
class MethodSig:
|
||||
def __init__(self, func, keywords=0, context=0):
|
||||
self.func = func
|
||||
self.keywords = keywords
|
||||
self.context = context
|
||||
self.__name__ = func.__name__
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
return apply(self.func,args,kw)
|
||||
|
||||
class SOAPContext:
|
||||
def __init__(self, header, body, attrs, xmldata, connection, httpheaders,
|
||||
soapaction):
|
||||
|
||||
self.header = header
|
||||
self.body = body
|
||||
self.attrs = attrs
|
||||
self.xmldata = xmldata
|
||||
self.connection = connection
|
||||
self.httpheaders= httpheaders
|
||||
self.soapaction = soapaction
|
||||
|
||||
# A class to describe how header messages are handled
|
||||
class HeaderHandler:
|
||||
# Initially fail out if there are any problems.
|
||||
def __init__(self, header, attrs):
|
||||
for i in header.__dict__.keys():
|
||||
if i[0] == "_":
|
||||
continue
|
||||
|
||||
d = getattr(header, i)
|
||||
|
||||
try:
|
||||
fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')])
|
||||
except:
|
||||
fault = 0
|
||||
|
||||
if fault:
|
||||
raise faultType, ("%s:MustUnderstand" % NS.ENV_T,
|
||||
"Required Header Misunderstood",
|
||||
"%s" % i)
|
||||
|
||||
################################################################################
|
||||
# SOAP Server
|
||||
################################################################################
|
||||
class SOAPServerBase:
|
||||
|
||||
def get_request(self):
|
||||
sock, addr = SocketServer.TCPServer.get_request(self)
|
||||
|
||||
if self.ssl_context:
|
||||
sock = SSL.Connection(self.ssl_context, sock)
|
||||
sock._setup_ssl(addr)
|
||||
if sock.accept_ssl() != 1:
|
||||
raise socket.error, "Couldn't accept SSL connection"
|
||||
|
||||
return sock, addr
|
||||
|
||||
def registerObject(self, object, namespace = '', path = ''):
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
self.objmap[namespace] = object
|
||||
|
||||
def registerFunction(self, function, namespace = '', funcName = None,
|
||||
path = ''):
|
||||
if not funcName : funcName = function.__name__
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
if self.funcmap.has_key(namespace):
|
||||
self.funcmap[namespace][funcName] = function
|
||||
else:
|
||||
self.funcmap[namespace] = {funcName : function}
|
||||
|
||||
def registerKWObject(self, object, namespace = '', path = ''):
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
for i in dir(object.__class__):
|
||||
if i[0] != "_" and callable(getattr(object, i)):
|
||||
self.registerKWFunction(getattr(object,i), namespace)
|
||||
|
||||
# convenience - wraps your func for you.
|
||||
def registerKWFunction(self, function, namespace = '', funcName = None,
|
||||
path = ''):
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
self.registerFunction(MethodSig(function,keywords=1), namespace,
|
||||
funcName)
|
||||
|
||||
def unregisterObject(self, object, namespace = '', path = ''):
|
||||
if namespace == '' and path == '': namespace = self.namespace
|
||||
if namespace == '' and path != '':
|
||||
namespace = path.replace("/", ":")
|
||||
if namespace[0] == ":": namespace = namespace[1:]
|
||||
|
||||
del self.objmap[namespace]
|
||||
|
||||
class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
def version_string(self):
|
||||
return '<a href="http://pywebsvcs.sf.net">' + \
|
||||
'SOAPpy ' + __version__ + '</a> (Python ' + \
|
||||
sys.version.split()[0] + ')'
|
||||
|
||||
def date_time_string(self):
|
||||
self.__last_date_time_string = \
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.\
|
||||
date_time_string(self)
|
||||
|
||||
return self.__last_date_time_string
|
||||
|
||||
def do_POST(self):
|
||||
global _contexts
|
||||
|
||||
status = 500
|
||||
try:
|
||||
if self.server.config.dumpHeadersIn:
|
||||
s = 'Incoming HTTP headers'
|
||||
debugHeader(s)
|
||||
print self.raw_requestline.strip()
|
||||
print "\n".join(map (lambda x: x.strip(),
|
||||
self.headers.headers))
|
||||
debugFooter(s)
|
||||
|
||||
data = self.rfile.read(int(self.headers["Content-length"]))
|
||||
|
||||
if self.server.config.dumpSOAPIn:
|
||||
s = 'Incoming SOAP'
|
||||
debugHeader(s)
|
||||
print data,
|
||||
if data[-1] != '\n':
|
||||
print
|
||||
debugFooter(s)
|
||||
|
||||
(r, header, body, attrs) = \
|
||||
parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
|
||||
|
||||
method = r._name
|
||||
args = r._aslist()
|
||||
kw = r._asdict()
|
||||
|
||||
if Config.simplify_objects:
|
||||
args = simplify(args)
|
||||
kw = simplify(kw)
|
||||
|
||||
# Handle mixed named and unnamed arguments by assuming
|
||||
# that all arguments with names of the form "v[0-9]+"
|
||||
# are unnamed and should be passed in numeric order,
|
||||
# other arguments are named and should be passed using
|
||||
# this name.
|
||||
|
||||
# This is a non-standard exension to the SOAP protocol,
|
||||
# but is supported by Apache AXIS.
|
||||
|
||||
# It is enabled by default. To disable, set
|
||||
# Config.specialArgs to False.
|
||||
|
||||
if Config.specialArgs:
|
||||
|
||||
ordered_args = {}
|
||||
named_args = {}
|
||||
|
||||
for (k,v) in kw.items():
|
||||
|
||||
if k[0]=="v":
|
||||
try:
|
||||
i = int(k[1:])
|
||||
ordered_args[i] = v
|
||||
except ValueError:
|
||||
named_args[str(k)] = v
|
||||
|
||||
else:
|
||||
named_args[str(k)] = v
|
||||
|
||||
# We have to decide namespace precedence
|
||||
# I'm happy with the following scenario
|
||||
# if r._ns is specified use it, if not check for
|
||||
# a path, if it's specified convert it and use it as the
|
||||
# namespace. If both are specified, use r._ns.
|
||||
|
||||
ns = r._ns
|
||||
|
||||
if len(self.path) > 1 and not ns:
|
||||
ns = self.path.replace("/", ":")
|
||||
if ns[0] == ":": ns = ns[1:]
|
||||
|
||||
# authorization method
|
||||
a = None
|
||||
|
||||
keylist = ordered_args.keys()
|
||||
keylist.sort()
|
||||
|
||||
# create list in proper order w/o names
|
||||
tmp = map( lambda x: ordered_args[x], keylist)
|
||||
ordered_args = tmp
|
||||
|
||||
#print '<-> Argument Matching Yielded:'
|
||||
#print '<-> Ordered Arguments:' + str(ordered_args)
|
||||
#print '<-> Named Arguments :' + str(named_args)
|
||||
|
||||
resp = ""
|
||||
|
||||
# For fault messages
|
||||
if ns:
|
||||
nsmethod = "%s:%s" % (ns, method)
|
||||
else:
|
||||
nsmethod = method
|
||||
|
||||
try:
|
||||
# First look for registered functions
|
||||
if self.server.funcmap.has_key(ns) and \
|
||||
self.server.funcmap[ns].has_key(method):
|
||||
f = self.server.funcmap[ns][method]
|
||||
|
||||
# look for the authorization method
|
||||
if self.server.config.authMethod != None:
|
||||
authmethod = self.server.config.authMethod
|
||||
if self.server.funcmap.has_key(ns) and \
|
||||
self.server.funcmap[ns].has_key(authmethod):
|
||||
a = self.server.funcmap[ns][authmethod]
|
||||
else:
|
||||
# Now look at registered objects
|
||||
# Check for nested attributes. This works even if
|
||||
# there are none, because the split will return
|
||||
# [method]
|
||||
f = self.server.objmap[ns]
|
||||
|
||||
# Look for the authorization method
|
||||
if self.server.config.authMethod != None:
|
||||
authmethod = self.server.config.authMethod
|
||||
if hasattr(f, authmethod):
|
||||
a = getattr(f, authmethod)
|
||||
|
||||
# then continue looking for the method
|
||||
l = method.split(".")
|
||||
for i in l:
|
||||
f = getattr(f, i)
|
||||
except:
|
||||
info = sys.exc_info()
|
||||
try:
|
||||
resp = buildSOAP(faultType("%s:Client" % NS.ENV_T,
|
||||
"Method Not Found",
|
||||
"%s : %s %s %s" % (nsmethod,
|
||||
info[0],
|
||||
info[1],
|
||||
info[2])),
|
||||
encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
finally:
|
||||
del info
|
||||
status = 500
|
||||
else:
|
||||
try:
|
||||
if header:
|
||||
x = HeaderHandler(header, attrs)
|
||||
|
||||
fr = 1
|
||||
|
||||
# call context book keeping
|
||||
# We're stuffing the method into the soapaction if there
|
||||
# isn't one, someday, we'll set that on the client
|
||||
# and it won't be necessary here
|
||||
# for now we're doing both
|
||||
|
||||
if "SOAPAction".lower() not in self.headers.keys() or \
|
||||
self.headers["SOAPAction"] == "\"\"":
|
||||
self.headers["SOAPAction"] = method
|
||||
|
||||
thread_id = thread.get_ident()
|
||||
_contexts[thread_id] = SOAPContext(header, body,
|
||||
attrs, data,
|
||||
self.connection,
|
||||
self.headers,
|
||||
self.headers["SOAPAction"])
|
||||
|
||||
# Do an authorization check
|
||||
if a != None:
|
||||
if not apply(a, (), {"_SOAPContext" :
|
||||
_contexts[thread_id] }):
|
||||
raise faultType("%s:Server" % NS.ENV_T,
|
||||
"Authorization failed.",
|
||||
"%s" % nsmethod)
|
||||
|
||||
# If it's wrapped, some special action may be needed
|
||||
if isinstance(f, MethodSig):
|
||||
c = None
|
||||
|
||||
if f.context: # retrieve context object
|
||||
c = _contexts[thread_id]
|
||||
|
||||
if Config.specialArgs:
|
||||
if c:
|
||||
named_args["_SOAPContext"] = c
|
||||
fr = apply(f, ordered_args, named_args)
|
||||
elif f.keywords:
|
||||
# This is lame, but have to de-unicode
|
||||
# keywords
|
||||
|
||||
strkw = {}
|
||||
|
||||
for (k, v) in kw.items():
|
||||
strkw[str(k)] = v
|
||||
if c:
|
||||
strkw["_SOAPContext"] = c
|
||||
fr = apply(f, (), strkw)
|
||||
elif c:
|
||||
fr = apply(f, args, {'_SOAPContext':c})
|
||||
else:
|
||||
fr = apply(f, args, {})
|
||||
|
||||
else:
|
||||
if Config.specialArgs:
|
||||
fr = apply(f, ordered_args, named_args)
|
||||
else:
|
||||
fr = apply(f, args, {})
|
||||
|
||||
|
||||
if type(fr) == type(self) and \
|
||||
isinstance(fr, voidType):
|
||||
resp = buildSOAP(kw = {'%sResponse' % method: fr},
|
||||
encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
else:
|
||||
resp = buildSOAP(kw =
|
||||
{'%sResponse' % method: {'Result': fr}},
|
||||
encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
|
||||
# Clean up _contexts
|
||||
if _contexts.has_key(thread_id):
|
||||
del _contexts[thread_id]
|
||||
|
||||
except Exception, e:
|
||||
import traceback
|
||||
info = sys.exc_info()
|
||||
|
||||
try:
|
||||
if self.server.config.dumpFaultInfo:
|
||||
s = 'Method %s exception' % nsmethod
|
||||
debugHeader(s)
|
||||
traceback.print_exception(info[0], info[1],
|
||||
info[2])
|
||||
debugFooter(s)
|
||||
|
||||
if isinstance(e, faultType):
|
||||
f = e
|
||||
else:
|
||||
f = faultType("%s:Server" % NS.ENV_T,
|
||||
"Method Failed",
|
||||
"%s" % nsmethod)
|
||||
|
||||
if self.server.config.returnFaultInfo:
|
||||
f._setDetail("".join(traceback.format_exception(
|
||||
info[0], info[1], info[2])))
|
||||
elif not hasattr(f, 'detail'):
|
||||
f._setDetail("%s %s" % (info[0], info[1]))
|
||||
finally:
|
||||
del info
|
||||
|
||||
resp = buildSOAP(f, encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
status = 500
|
||||
else:
|
||||
status = 200
|
||||
except faultType, e:
|
||||
import traceback
|
||||
info = sys.exc_info()
|
||||
try:
|
||||
if self.server.config.dumpFaultInfo:
|
||||
s = 'Received fault exception'
|
||||
debugHeader(s)
|
||||
traceback.print_exception(info[0], info[1],
|
||||
info[2])
|
||||
debugFooter(s)
|
||||
|
||||
if self.server.config.returnFaultInfo:
|
||||
e._setDetail("".join(traceback.format_exception(
|
||||
info[0], info[1], info[2])))
|
||||
elif not hasattr(e, 'detail'):
|
||||
e._setDetail("%s %s" % (info[0], info[1]))
|
||||
finally:
|
||||
del info
|
||||
|
||||
resp = buildSOAP(e, encoding = self.server.encoding,
|
||||
config = self.server.config)
|
||||
status = 500
|
||||
except Exception, e:
|
||||
# internal error, report as HTTP server error
|
||||
|
||||
if self.server.config.dumpFaultInfo:
|
||||
s = 'Internal exception %s' % e
|
||||
import traceback
|
||||
debugHeader(s)
|
||||
info = sys.exc_info()
|
||||
try:
|
||||
traceback.print_exception(info[0], info[1], info[2])
|
||||
finally:
|
||||
del info
|
||||
|
||||
debugFooter(s)
|
||||
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
|
||||
if self.server.config.dumpHeadersOut and \
|
||||
self.request_version != 'HTTP/0.9':
|
||||
s = 'Outgoing HTTP headers'
|
||||
debugHeader(s)
|
||||
if self.responses.has_key(status):
|
||||
s = ' ' + self.responses[status][0]
|
||||
else:
|
||||
s = ''
|
||||
print "%s %d%s" % (self.protocol_version, 500, s)
|
||||
print "Server:", self.version_string()
|
||||
print "Date:", self.__last_date_time_string
|
||||
debugFooter(s)
|
||||
else:
|
||||
# got a valid SOAP response
|
||||
self.send_response(status)
|
||||
|
||||
t = 'text/xml';
|
||||
if self.server.encoding != None:
|
||||
t += '; charset="%s"' % self.server.encoding
|
||||
self.send_header("Content-type", t)
|
||||
self.send_header("Content-length", str(len(resp)))
|
||||
self.end_headers()
|
||||
|
||||
if self.server.config.dumpHeadersOut and \
|
||||
self.request_version != 'HTTP/0.9':
|
||||
s = 'Outgoing HTTP headers'
|
||||
debugHeader(s)
|
||||
if self.responses.has_key(status):
|
||||
s = ' ' + self.responses[status][0]
|
||||
else:
|
||||
s = ''
|
||||
print "%s %d%s" % (self.protocol_version, status, s)
|
||||
print "Server:", self.version_string()
|
||||
print "Date:", self.__last_date_time_string
|
||||
print "Content-type:", t
|
||||
print "Content-length:", len(resp)
|
||||
debugFooter(s)
|
||||
|
||||
if self.server.config.dumpSOAPOut:
|
||||
s = 'Outgoing SOAP'
|
||||
debugHeader(s)
|
||||
print resp,
|
||||
if resp[-1] != '\n':
|
||||
print
|
||||
debugFooter(s)
|
||||
|
||||
self.wfile.write(resp)
|
||||
self.wfile.flush()
|
||||
|
||||
# We should be able to shut down both a regular and an SSL
|
||||
# connection, but under Python 2.1, calling shutdown on an
|
||||
# SSL connections drops the output, so this work-around.
|
||||
# This should be investigated more someday.
|
||||
|
||||
if self.server.config.SSLserver and \
|
||||
isinstance(self.connection, SSL.Connection):
|
||||
self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN |
|
||||
SSL.SSL_RECEIVED_SHUTDOWN)
|
||||
else:
|
||||
self.connection.shutdown(1)
|
||||
|
||||
def do_GET(self):
|
||||
|
||||
#print 'command ', self.command
|
||||
#print 'path ', self.path
|
||||
#print 'request_version', self.request_version
|
||||
#print 'headers'
|
||||
#print ' type ', self.headers.type
|
||||
#print ' maintype', self.headers.maintype
|
||||
#print ' subtype ', self.headers.subtype
|
||||
#print ' params ', self.headers.plist
|
||||
|
||||
path = self.path.lower()
|
||||
if path.endswith('wsdl'):
|
||||
method = 'wsdl'
|
||||
function = namespace = None
|
||||
if self.server.funcmap.has_key(namespace) \
|
||||
and self.server.funcmap[namespace].has_key(method):
|
||||
function = self.server.funcmap[namespace][method]
|
||||
else:
|
||||
if namespace in self.server.objmap.keys():
|
||||
function = self.server.objmap[namespace]
|
||||
l = method.split(".")
|
||||
for i in l:
|
||||
function = getattr(function, i)
|
||||
|
||||
if function:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", 'text/plain')
|
||||
self.end_headers()
|
||||
response = apply(function, ())
|
||||
self.wfile.write(str(response))
|
||||
return
|
||||
|
||||
# return error
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write('''\
|
||||
<title>
|
||||
<head>Error!</head>
|
||||
</title>
|
||||
|
||||
<body>
|
||||
<h1>Oops!</h1>
|
||||
|
||||
<p>
|
||||
This server supports HTTP GET requests only for the the purpose of
|
||||
obtaining Web Services Description Language (WSDL) for a specific
|
||||
service.
|
||||
|
||||
Either you requested an URL that does not end in "wsdl" or this
|
||||
server does not implement a wsdl method.
|
||||
</p>
|
||||
|
||||
|
||||
</body>''')
|
||||
|
||||
|
||||
def log_message(self, format, *args):
|
||||
if self.server.log:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.\
|
||||
log_message (self, format, *args)
|
||||
|
||||
|
||||
|
||||
class SOAPServer(SOAPServerBase, SocketServer.TCPServer):
|
||||
|
||||
def __init__(self, addr = ('localhost', 8000),
|
||||
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||
config = Config, namespace = None, ssl_context = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
if ssl_context != None and not config.SSLserver:
|
||||
raise AttributeError, \
|
||||
"SSL server not supported by this Python installation"
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.ssl_context = ssl_context
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
SocketServer.TCPServer.__init__(self, addr, RequestHandler)
|
||||
|
||||
|
||||
class ThreadingSOAPServer(SOAPServerBase, SocketServer.ThreadingTCPServer):
|
||||
|
||||
def __init__(self, addr = ('localhost', 8000),
|
||||
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||
config = Config, namespace = None, ssl_context = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
if ssl_context != None and not config.SSLserver:
|
||||
raise AttributeError, \
|
||||
"SSL server not supported by this Python installation"
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.ssl_context = ssl_context
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
SocketServer.ThreadingTCPServer.__init__(self, addr, RequestHandler)
|
||||
|
||||
# only define class if Unix domain sockets are available
|
||||
if hasattr(socket, "AF_UNIX"):
|
||||
|
||||
class SOAPUnixSocketServer(SOAPServerBase, SocketServer.UnixStreamServer):
|
||||
|
||||
def __init__(self, addr = 8000,
|
||||
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||
config = Config, namespace = None, ssl_context = None):
|
||||
|
||||
# Test the encoding, raising an exception if it's not known
|
||||
if encoding != None:
|
||||
''.encode(encoding)
|
||||
|
||||
if ssl_context != None and not config.SSLserver:
|
||||
raise AttributeError, \
|
||||
"SSL server not supported by this Python installation"
|
||||
|
||||
self.namespace = namespace
|
||||
self.objmap = {}
|
||||
self.funcmap = {}
|
||||
self.ssl_context = ssl_context
|
||||
self.encoding = encoding
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
self.allow_reuse_address= 1
|
||||
|
||||
SocketServer.UnixStreamServer.__init__(self, str(addr), RequestHandler)
|
||||
|
1734
others/SOAPpy/Types.py
Normal file
1734
others/SOAPpy/Types.py
Normal file
File diff suppressed because it is too large
Load Diff
23
others/SOAPpy/URLopener.py
Normal file
23
others/SOAPpy/URLopener.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""Provide a class for loading data from URL's that handles basic
|
||||
authentication"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from Config import Config
|
||||
from urllib import FancyURLopener
|
||||
|
||||
class URLopener(FancyURLopener):
|
||||
|
||||
username = None
|
||||
passwd = None
|
||||
|
||||
|
||||
def __init__(self, username=None, passwd=None, *args, **kw):
|
||||
FancyURLopener.__init__( self, *args, **kw)
|
||||
self.username = username
|
||||
self.passwd = passwd
|
||||
|
||||
|
||||
def prompt_user_passwd(self, host, realm):
|
||||
return self.username, self.passwd
|
178
others/SOAPpy/Utilities.py
Normal file
178
others/SOAPpy/Utilities.py
Normal file
@ -0,0 +1,178 @@
|
||||
"""
|
||||
################################################################################
|
||||
# Copyright (c) 2003, Pfizer
|
||||
# Copyright (c) 2001, Cayce Ullman.
|
||||
# Copyright (c) 2001, Brian Matthews.
|
||||
#
|
||||
# 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 actzero, inc. nor the names of its contributors 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 REGENTS 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.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import exceptions
|
||||
import copy
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
from types import *
|
||||
|
||||
# SOAPpy modules
|
||||
from Errors import *
|
||||
|
||||
################################################################################
|
||||
# Utility infielders
|
||||
################################################################################
|
||||
def collapseWhiteSpace(s):
|
||||
return re.sub('\s+', ' ', s).strip()
|
||||
|
||||
def decodeHexString(data):
|
||||
conv = {
|
||||
'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4,
|
||||
'5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9,
|
||||
|
||||
'a': 0xa, 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe,
|
||||
'f': 0xf,
|
||||
|
||||
'A': 0xa, 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe,
|
||||
'F': 0xf,
|
||||
}
|
||||
|
||||
ws = string.whitespace
|
||||
|
||||
bin = ''
|
||||
|
||||
i = 0
|
||||
|
||||
while i < len(data):
|
||||
if data[i] not in ws:
|
||||
break
|
||||
i += 1
|
||||
|
||||
low = 0
|
||||
|
||||
while i < len(data):
|
||||
c = data[i]
|
||||
|
||||
if c in string.whitespace:
|
||||
break
|
||||
|
||||
try:
|
||||
c = conv[c]
|
||||
except KeyError:
|
||||
raise ValueError, \
|
||||
"invalid hex string character `%s'" % c
|
||||
|
||||
if low:
|
||||
bin += chr(high * 16 + c)
|
||||
low = 0
|
||||
else:
|
||||
high = c
|
||||
low = 1
|
||||
|
||||
i += 1
|
||||
|
||||
if low:
|
||||
raise ValueError, "invalid hex string length"
|
||||
|
||||
while i < len(data):
|
||||
if data[i] not in string.whitespace:
|
||||
raise ValueError, \
|
||||
"invalid hex string character `%s'" % c
|
||||
|
||||
i += 1
|
||||
|
||||
return bin
|
||||
|
||||
def encodeHexString(data):
|
||||
h = ''
|
||||
|
||||
for i in data:
|
||||
h += "%02X" % ord(i)
|
||||
|
||||
return h
|
||||
|
||||
def leapMonth(year, month):
|
||||
return month == 2 and \
|
||||
year % 4 == 0 and \
|
||||
(year % 100 != 0 or year % 400 == 0)
|
||||
|
||||
def cleanDate(d, first = 0):
|
||||
ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61))
|
||||
months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||
names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds')
|
||||
|
||||
if len(d) != 6:
|
||||
raise ValueError, "date must have 6 elements"
|
||||
|
||||
for i in range(first, 6):
|
||||
s = d[i]
|
||||
|
||||
if type(s) == FloatType:
|
||||
if i < 5:
|
||||
try:
|
||||
s = int(s)
|
||||
except OverflowError:
|
||||
if i > 0:
|
||||
raise
|
||||
s = long(s)
|
||||
|
||||
if s != d[i]:
|
||||
raise ValueError, "%s must be integral" % names[i]
|
||||
|
||||
d[i] = s
|
||||
elif type(s) == LongType:
|
||||
try: s = int(s)
|
||||
except: pass
|
||||
elif type(s) != IntType:
|
||||
raise TypeError, "%s isn't a valid type" % names[i]
|
||||
|
||||
if i == first and s < 0:
|
||||
continue
|
||||
|
||||
if ranges[i] != None and \
|
||||
(s < ranges[i][0] or ranges[i][1] < s):
|
||||
raise ValueError, "%s out of range" % names[i]
|
||||
|
||||
if first < 6 and d[5] >= 61:
|
||||
raise ValueError, "seconds out of range"
|
||||
|
||||
if first < 2:
|
||||
leap = first < 1 and leapMonth(d[0], d[1])
|
||||
|
||||
if d[2] > months[d[1]] + leap:
|
||||
raise ValueError, "day out of range"
|
||||
|
||||
def debugHeader(title):
|
||||
s = '*** ' + title + ' '
|
||||
print s + ('*' * (72 - len(s)))
|
||||
|
||||
def debugFooter(title):
|
||||
print '*' * 72
|
||||
sys.stdout.flush()
|
102
others/SOAPpy/WSDL.py
Normal file
102
others/SOAPpy/WSDL.py
Normal file
@ -0,0 +1,102 @@
|
||||
"""Parse web services description language to get SOAP methods.
|
||||
|
||||
Rudimentary support."""
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
import wstools
|
||||
from Client import SOAPProxy, SOAPAddress
|
||||
from Config import Config
|
||||
import urllib
|
||||
|
||||
class Proxy:
|
||||
"""WSDL Proxy.
|
||||
|
||||
SOAPProxy wrapper that parses method names, namespaces, soap actions from
|
||||
the web service description language (WSDL) file passed into the
|
||||
constructor. The WSDL reference can be passed in as a stream, an url, a
|
||||
file name, or a string.
|
||||
|
||||
Loads info into self.methods, a dictionary with methodname keys and values
|
||||
of WSDLTools.SOAPCallinfo.
|
||||
|
||||
For example,
|
||||
|
||||
url = 'http://www.xmethods.org/sd/2001/TemperatureService.wsdl'
|
||||
wsdl = WSDL.Proxy(url)
|
||||
print len(wsdl.methods) # 1
|
||||
print wsdl.methods.keys() # getTemp
|
||||
|
||||
|
||||
See WSDLTools.SOAPCallinfo for more info on each method's attributes.
|
||||
"""
|
||||
|
||||
def __init__(self, wsdlsource, config=Config, **kw ):
|
||||
|
||||
reader = wstools.WSDLTools.WSDLReader()
|
||||
self.wsdl = None
|
||||
|
||||
# From Mark Pilgrim's "Dive Into Python" toolkit.py--open anything.
|
||||
if self.wsdl is None and hasattr(wsdlsource, "read"):
|
||||
#print 'stream'
|
||||
self.wsdl = reader.loadFromStream(wsdlsource)
|
||||
|
||||
# NOT TESTED (as of April 17, 2003)
|
||||
#if self.wsdl is None and wsdlsource == '-':
|
||||
# import sys
|
||||
# self.wsdl = reader.loadFromStream(sys.stdin)
|
||||
# print 'stdin'
|
||||
|
||||
if self.wsdl is None:
|
||||
try:
|
||||
file(wsdlsource)
|
||||
self.wsdl = reader.loadFromFile(wsdlsource)
|
||||
#print 'file'
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
if self.wsdl is None:
|
||||
try:
|
||||
stream = urllib.urlopen(wsdlsource)
|
||||
self.wsdl = reader.loadFromStream(stream, wsdlsource)
|
||||
except (IOError, OSError): pass
|
||||
|
||||
if self.wsdl is None:
|
||||
import StringIO
|
||||
self.wsdl = reader.loadFromString(str(wsdlsource))
|
||||
#print 'string'
|
||||
|
||||
# Package wsdl info as a dictionary of remote methods, with method name
|
||||
# as key (based on ServiceProxy.__init__ in ZSI library).
|
||||
self.methods = {}
|
||||
service = self.wsdl.services[0]
|
||||
port = service.ports[0]
|
||||
name = service.name
|
||||
binding = port.getBinding()
|
||||
portType = binding.getPortType()
|
||||
for operation in portType.operations:
|
||||
callinfo = wstools.WSDLTools.callInfoFromWSDL(port, operation.name)
|
||||
self.methods[callinfo.methodName] = callinfo
|
||||
|
||||
self.soapproxy = SOAPProxy('http://localhost/dummy.webservice',
|
||||
config=config, **kw)
|
||||
|
||||
def __str__(self):
|
||||
s = ''
|
||||
for method in self.methods.values():
|
||||
s += str(method)
|
||||
return s
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Set up environment then let parent class handle call.
|
||||
|
||||
Raises AttributeError is method name is not found."""
|
||||
|
||||
if not self.methods.has_key(name): raise AttributeError, name
|
||||
|
||||
callinfo = self.methods[name]
|
||||
self.soapproxy.proxy = SOAPAddress(callinfo.location)
|
||||
self.soapproxy.namespace = callinfo.namespace
|
||||
self.soapproxy.soapaction = callinfo.soapAction
|
||||
return self.soapproxy.__getattr__(name)
|
15
others/SOAPpy/__init__.py
Normal file
15
others/SOAPpy/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
ident = '$Id$'
|
||||
from version import __version__
|
||||
|
||||
from Client import *
|
||||
from Config import *
|
||||
from Errors import *
|
||||
from NS import *
|
||||
from Parser import *
|
||||
from SOAPBuilder import *
|
||||
from Server import *
|
||||
from Types import *
|
||||
from Utilities import *
|
||||
import wstools
|
||||
import WSDL
|
2
others/SOAPpy/version.py
Normal file
2
others/SOAPpy/version.py
Normal file
@ -0,0 +1,2 @@
|
||||
__version__="0.11.6"
|
||||
|
91
others/SOAPpy/wstools/Namespaces.py
Executable file
91
others/SOAPpy/wstools/Namespaces.py
Executable file
@ -0,0 +1,91 @@
|
||||
"""Namespace module, so you don't need PyXML
|
||||
"""
|
||||
|
||||
try:
|
||||
from xml.ns import SOAP, SCHEMA, WSDL, XMLNS, DSIG, ENCRYPTION
|
||||
except:
|
||||
class SOAP:
|
||||
ENV = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||
ENC = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||
ACTOR_NEXT = "http://schemas.xmlsoap.org/soap/actor/next"
|
||||
|
||||
class SCHEMA:
|
||||
XSD1 = "http://www.w3.org/1999/XMLSchema"
|
||||
XSD2 = "http://www.w3.org/2000/10/XMLSchema"
|
||||
XSD3 = "http://www.w3.org/2001/XMLSchema"
|
||||
XSD_LIST = [ XSD1, XSD2, XSD3 ]
|
||||
XSI1 = "http://www.w3.org/1999/XMLSchema-instance"
|
||||
XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance"
|
||||
XSI3 = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
XSI_LIST = [ XSI1, XSI2, XSI3 ]
|
||||
BASE = XSD3
|
||||
|
||||
class WSDL:
|
||||
BASE = "http://schemas.xmlsoap.org/wsdl/"
|
||||
BIND_HTTP = "http://schemas.xmlsoap.org/wsdl/http/"
|
||||
BIND_MIME = "http://schemas.xmlsoap.org/wsdl/mime/"
|
||||
BIND_SOAP = "http://schemas.xmlsoap.org/wsdl/soap/"
|
||||
|
||||
class XMLNS:
|
||||
BASE = "http://www.w3.org/2000/xmlns/"
|
||||
XML = "http://www.w3.org/XML/1998/namespace"
|
||||
HTML = "http://www.w3.org/TR/REC-html40"
|
||||
|
||||
class DSIG:
|
||||
BASE = "http://www.w3.org/2000/09/xmldsig#"
|
||||
C14N = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315"
|
||||
C14N_COMM = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315#WithComments"
|
||||
C14N_EXCL = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||
DIGEST_MD2 = "http://www.w3.org/2000/09/xmldsig#md2"
|
||||
DIGEST_MD5 = "http://www.w3.org/2000/09/xmldsig#md5"
|
||||
DIGEST_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
||||
ENC_BASE64 = "http://www.w3.org/2000/09/xmldsig#base64"
|
||||
ENVELOPED = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
||||
HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
|
||||
SIG_DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
SIG_RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
||||
XPATH = "http://www.w3.org/TR/1999/REC-xpath-19991116"
|
||||
XSLT = "http://www.w3.org/TR/1999/REC-xslt-19991116"
|
||||
|
||||
class ENCRYPTION:
|
||||
BASE = "http://www.w3.org/2001/04/xmlenc#"
|
||||
BLOCK_3DES = "http://www.w3.org/2001/04/xmlenc#des-cbc"
|
||||
BLOCK_AES128 = "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
|
||||
BLOCK_AES192 = "http://www.w3.org/2001/04/xmlenc#aes192-cbc"
|
||||
BLOCK_AES256 = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
|
||||
DIGEST_RIPEMD160 = "http://www.w3.org/2001/04/xmlenc#ripemd160"
|
||||
DIGEST_SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
|
||||
DIGEST_SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512"
|
||||
KA_DH = "http://www.w3.org/2001/04/xmlenc#dh"
|
||||
KT_RSA_1_5 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
|
||||
KT_RSA_OAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
|
||||
STREAM_ARCFOUR = "http://www.w3.org/2001/04/xmlenc#arcfour"
|
||||
WRAP_3DES = "http://www.w3.org/2001/04/xmlenc#kw-3des"
|
||||
WRAP_AES128 = "http://www.w3.org/2001/04/xmlenc#kw-aes128"
|
||||
WRAP_AES192 = "http://www.w3.org/2001/04/xmlenc#kw-aes192"
|
||||
WRAP_AES256 = "http://www.w3.org/2001/04/xmlenc#kw-aes256"
|
||||
|
||||
|
||||
class WSSE:
|
||||
BASE = "http://schemas.xmlsoap.org/ws/2002/04/secext"
|
||||
|
||||
class WSU:
|
||||
BASE = "http://schemas.xmlsoap.org/ws/2002/04/utility"
|
||||
UTILITY = "http://schemas.xmlsoap.org/ws/2002/07/utility"
|
||||
|
||||
class WSR:
|
||||
PROPERTIES = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceProperties"
|
||||
LIFETIME = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceLifetime"
|
||||
|
||||
class WSA:
|
||||
ADDRESS = "http://schemas.xmlsoap.org/ws/2003/03/addressing"
|
||||
ADDRESS2004 = "http://schemas.xmlsoap.org/ws/2004/03/addressing"
|
||||
ANONYMOUS = "%s/role/anonymous" %ADDRESS
|
||||
ANONYMOUS2004 = "%s/role/anonymous" %ADDRESS2004
|
||||
FAULT = "http://schemas.xmlsoap.org/ws/2004/03/addressing/fault"
|
||||
|
||||
class WSP:
|
||||
POLICY = "http://schemas.xmlsoap.org/ws/2002/12/policy"
|
||||
|
||||
|
||||
|
179
others/SOAPpy/wstools/TimeoutSocket.py
Executable file
179
others/SOAPpy/wstools/TimeoutSocket.py
Executable file
@ -0,0 +1,179 @@
|
||||
"""Based on code from timeout_socket.py, with some tweaks for compatibility.
|
||||
These tweaks should really be rolled back into timeout_socket, but it's
|
||||
not totally clear who is maintaining it at this point. In the meantime,
|
||||
we'll use a different module name for our tweaked version to avoid any
|
||||
confusion.
|
||||
|
||||
The original timeout_socket is by:
|
||||
|
||||
Scott Cotton <scott@chronis.pobox.com>
|
||||
Lloyd Zusman <ljz@asfast.com>
|
||||
Phil Mayes <pmayes@olivebr.com>
|
||||
Piers Lauder <piers@cs.su.oz.au>
|
||||
Radovan Garabik <garabik@melkor.dnp.fmph.uniba.sk>
|
||||
"""
|
||||
|
||||
ident = "$Id$"
|
||||
|
||||
import string, socket, select, errno
|
||||
|
||||
WSAEINVAL = getattr(errno, 'WSAEINVAL', 10022)
|
||||
|
||||
|
||||
class TimeoutSocket:
|
||||
"""A socket imposter that supports timeout limits."""
|
||||
|
||||
def __init__(self, timeout=20, sock=None):
|
||||
self.timeout = float(timeout)
|
||||
self.inbuf = ''
|
||||
if sock is None:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock = sock
|
||||
self.sock.setblocking(0)
|
||||
self._rbuf = ''
|
||||
self._wbuf = ''
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Delegate to real socket attributes.
|
||||
return getattr(self.sock, name)
|
||||
|
||||
def connect(self, *addr):
|
||||
timeout = self.timeout
|
||||
sock = self.sock
|
||||
try:
|
||||
# Non-blocking mode
|
||||
sock.setblocking(0)
|
||||
apply(sock.connect, addr)
|
||||
sock.setblocking(timeout != 0)
|
||||
return 1
|
||||
except socket.error,why:
|
||||
if not timeout:
|
||||
raise
|
||||
sock.setblocking(1)
|
||||
if len(why.args) == 1:
|
||||
code = 0
|
||||
else:
|
||||
code, why = why
|
||||
if code not in (
|
||||
errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK
|
||||
):
|
||||
raise
|
||||
r,w,e = select.select([],[sock],[],timeout)
|
||||
if w:
|
||||
try:
|
||||
apply(sock.connect, addr)
|
||||
return 1
|
||||
except socket.error,why:
|
||||
if len(why.args) == 1:
|
||||
code = 0
|
||||
else:
|
||||
code, why = why
|
||||
if code in (errno.EISCONN, WSAEINVAL):
|
||||
return 1
|
||||
raise
|
||||
raise TimeoutError('socket connect() timeout.')
|
||||
|
||||
def send(self, data, flags=0):
|
||||
total = len(data)
|
||||
next = 0
|
||||
while 1:
|
||||
r, w, e = select.select([],[self.sock], [], self.timeout)
|
||||
if w:
|
||||
buff = data[next:next + 8192]
|
||||
sent = self.sock.send(buff, flags)
|
||||
next = next + sent
|
||||
if next == total:
|
||||
return total
|
||||
continue
|
||||
raise TimeoutError('socket send() timeout.')
|
||||
|
||||
def recv(self, amt, flags=0):
|
||||
if select.select([self.sock], [], [], self.timeout)[0]:
|
||||
return self.sock.recv(amt, flags)
|
||||
raise TimeoutError('socket recv() timeout.')
|
||||
|
||||
buffsize = 4096
|
||||
handles = 1
|
||||
|
||||
def makefile(self, mode="r", buffsize=-1):
|
||||
self.handles = self.handles + 1
|
||||
self.mode = mode
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
self.handles = self.handles - 1
|
||||
if self.handles == 0 and self.sock.fileno() >= 0:
|
||||
self.sock.close()
|
||||
|
||||
def read(self, n=-1):
|
||||
if not isinstance(n, type(1)):
|
||||
n = -1
|
||||
if n >= 0:
|
||||
k = len(self._rbuf)
|
||||
if n <= k:
|
||||
data = self._rbuf[:n]
|
||||
self._rbuf = self._rbuf[n:]
|
||||
return data
|
||||
n = n - k
|
||||
L = [self._rbuf]
|
||||
self._rbuf = ""
|
||||
while n > 0:
|
||||
new = self.recv(max(n, self.buffsize))
|
||||
if not new: break
|
||||
k = len(new)
|
||||
if k > n:
|
||||
L.append(new[:n])
|
||||
self._rbuf = new[n:]
|
||||
break
|
||||
L.append(new)
|
||||
n = n - k
|
||||
return "".join(L)
|
||||
k = max(4096, self.buffsize)
|
||||
L = [self._rbuf]
|
||||
self._rbuf = ""
|
||||
while 1:
|
||||
new = self.recv(k)
|
||||
if not new: break
|
||||
L.append(new)
|
||||
k = min(k*2, 1024**2)
|
||||
return "".join(L)
|
||||
|
||||
def readline(self, limit=-1):
|
||||
data = ""
|
||||
i = self._rbuf.find('\n')
|
||||
while i < 0 and not (0 < limit <= len(self._rbuf)):
|
||||
new = self.recv(self.buffsize)
|
||||
if not new: break
|
||||
i = new.find('\n')
|
||||
if i >= 0: i = i + len(self._rbuf)
|
||||
self._rbuf = self._rbuf + new
|
||||
if i < 0: i = len(self._rbuf)
|
||||
else: i = i+1
|
||||
if 0 <= limit < len(self._rbuf): i = limit
|
||||
data, self._rbuf = self._rbuf[:i], self._rbuf[i:]
|
||||
return data
|
||||
|
||||
def readlines(self, sizehint = 0):
|
||||
total = 0
|
||||
list = []
|
||||
while 1:
|
||||
line = self.readline()
|
||||
if not line: break
|
||||
list.append(line)
|
||||
total += len(line)
|
||||
if sizehint and total >= sizehint:
|
||||
break
|
||||
return list
|
||||
|
||||
def writelines(self, list):
|
||||
self.send(''.join(list))
|
||||
|
||||
def write(self, data):
|
||||
self.send(data)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
99
others/SOAPpy/wstools/UserTuple.py
Executable file
99
others/SOAPpy/wstools/UserTuple.py
Executable file
@ -0,0 +1,99 @@
|
||||
"""
|
||||
A more or less complete user-defined wrapper around tuple objects.
|
||||
Adapted version of the standard library's UserList.
|
||||
|
||||
Taken from Stefan Schwarzer's ftputil library, available at
|
||||
<http://www.ndh.net/home/sschwarzer/python/python_software.html>, and used under this license:
|
||||
|
||||
|
||||
|
||||
|
||||
Copyright (C) 1999, Stefan Schwarzer
|
||||
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 above author nor the names of the
|
||||
contributors to the 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 REGENTS 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.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
# $Id$
|
||||
|
||||
#XXX tuple instances (in Python 2.2) contain also:
|
||||
# __class__, __delattr__, __getattribute__, __hash__, __new__,
|
||||
# __reduce__, __setattr__, __str__
|
||||
# What about these?
|
||||
|
||||
class UserTuple:
|
||||
def __init__(self, inittuple=None):
|
||||
self.data = ()
|
||||
if inittuple is not None:
|
||||
# XXX should this accept an arbitrary sequence?
|
||||
if type(inittuple) == type(self.data):
|
||||
self.data = inittuple
|
||||
elif isinstance(inittuple, UserTuple):
|
||||
# this results in
|
||||
# self.data is inittuple.data
|
||||
# but that's ok for tuples because they are
|
||||
# immutable. (Builtin tuples behave the same.)
|
||||
self.data = inittuple.data[:]
|
||||
else:
|
||||
# the same applies here; (t is tuple(t)) == 1
|
||||
self.data = tuple(inittuple)
|
||||
def __repr__(self): return repr(self.data)
|
||||
def __lt__(self, other): return self.data < self.__cast(other)
|
||||
def __le__(self, other): return self.data <= self.__cast(other)
|
||||
def __eq__(self, other): return self.data == self.__cast(other)
|
||||
def __ne__(self, other): return self.data != self.__cast(other)
|
||||
def __gt__(self, other): return self.data > self.__cast(other)
|
||||
def __ge__(self, other): return self.data >= self.__cast(other)
|
||||
def __cast(self, other):
|
||||
if isinstance(other, UserTuple): return other.data
|
||||
else: return other
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.data, self.__cast(other))
|
||||
def __contains__(self, item): return item in self.data
|
||||
def __len__(self): return len(self.data)
|
||||
def __getitem__(self, i): return self.data[i]
|
||||
def __getslice__(self, i, j):
|
||||
i = max(i, 0); j = max(j, 0)
|
||||
return self.__class__(self.data[i:j])
|
||||
def __add__(self, other):
|
||||
if isinstance(other, UserTuple):
|
||||
return self.__class__(self.data + other.data)
|
||||
elif isinstance(other, type(self.data)):
|
||||
return self.__class__(self.data + other)
|
||||
else:
|
||||
return self.__class__(self.data + tuple(other))
|
||||
# dir( () ) contains no __radd__ (at least in Python 2.2)
|
||||
def __mul__(self, n):
|
||||
return self.__class__(self.data*n)
|
||||
__rmul__ = __mul__
|
||||
|
839
others/SOAPpy/wstools/Utility.py
Executable file
839
others/SOAPpy/wstools/Utility.py
Executable file
@ -0,0 +1,839 @@
|
||||
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
|
||||
#
|
||||
# This software is subject to the provisions of the Zope Public License,
|
||||
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
|
||||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
|
||||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE.
|
||||
|
||||
ident = "$Id$"
|
||||
|
||||
import types
|
||||
import string, httplib, smtplib, urllib, socket, weakref
|
||||
import xml.dom.minidom
|
||||
from string import join, strip, split
|
||||
from UserDict import UserDict
|
||||
from StringIO import StringIO
|
||||
from TimeoutSocket import TimeoutSocket, TimeoutError
|
||||
from urlparse import urlparse
|
||||
from httplib import HTTPConnection, HTTPSConnection
|
||||
from exceptions import Exception
|
||||
|
||||
try:
|
||||
from xml.dom.ext import SplitQName
|
||||
except:
|
||||
def SplitQName(qname):
|
||||
'''SplitQName(qname) -> (string, string)
|
||||
|
||||
Split Qualified Name into a tuple of len 2, consisting
|
||||
of the prefix and the local name.
|
||||
|
||||
(prefix, localName)
|
||||
|
||||
Special Cases:
|
||||
xmlns -- (localName, 'xmlns')
|
||||
None -- (None, localName)
|
||||
'''
|
||||
|
||||
l = qname.split(':')
|
||||
if len(l) == 1:
|
||||
l.insert(0, None)
|
||||
elif len(l) == 2:
|
||||
if l[0] == 'xmlns':
|
||||
l.reverse()
|
||||
else:
|
||||
return
|
||||
return tuple(l)
|
||||
|
||||
class RecursionError(Exception):
|
||||
"""Used to indicate a HTTP redirect recursion."""
|
||||
pass
|
||||
|
||||
class HTTPResponse:
|
||||
"""Captures the information in an HTTP response message."""
|
||||
|
||||
def __init__(self, response):
|
||||
self.status = response.status
|
||||
self.reason = response.reason
|
||||
self.headers = response.msg
|
||||
self.body = response.read() or None
|
||||
response.close()
|
||||
|
||||
class TimeoutHTTP(HTTPConnection):
|
||||
"""A custom http connection object that supports socket timeout."""
|
||||
def __init__(self, host, port=None, timeout=20):
|
||||
HTTPConnection.__init__(self, host, port)
|
||||
self.timeout = timeout
|
||||
|
||||
def connect(self):
|
||||
self.sock = TimeoutSocket(self.timeout)
|
||||
self.sock.connect((self.host, self.port))
|
||||
|
||||
|
||||
class TimeoutHTTPS(HTTPSConnection):
|
||||
"""A custom https object that supports socket timeout. Note that this
|
||||
is not really complete. The builtin SSL support in the Python socket
|
||||
module requires a real socket (type) to be passed in to be hooked to
|
||||
SSL. That means our fake socket won't work and our timeout hacks are
|
||||
bypassed for send and recv calls. Since our hack _is_ in place at
|
||||
connect() time, it should at least provide some timeout protection."""
|
||||
def __init__(self, host, port=None, timeout=20, **kwargs):
|
||||
HTTPSConnection.__init__(self, str(host), port, **kwargs)
|
||||
self.timeout = timeout
|
||||
|
||||
def connect(self):
|
||||
sock = TimeoutSocket(self.timeout)
|
||||
sock.connect((self.host, self.port))
|
||||
realsock = getattr(sock.sock, '_sock', sock.sock)
|
||||
ssl = socket.ssl(realsock, self.key_file, self.cert_file)
|
||||
self.sock = httplib.FakeSocket(sock, ssl)
|
||||
|
||||
def urlopen(url, timeout=20, redirects=None):
|
||||
"""A minimal urlopen replacement hack that supports timeouts for http.
|
||||
Note that this supports GET only."""
|
||||
scheme, host, path, params, query, frag = urlparse(url)
|
||||
if not scheme in ('http', 'https'):
|
||||
return urllib.urlopen(url)
|
||||
if params: path = '%s;%s' % (path, params)
|
||||
if query: path = '%s?%s' % (path, query)
|
||||
if frag: path = '%s#%s' % (path, frag)
|
||||
|
||||
if scheme == 'https':
|
||||
# If ssl is not compiled into Python, you will not get an exception
|
||||
# until a conn.endheaders() call. We need to know sooner, so use
|
||||
# getattr.
|
||||
if hasattr(socket, 'ssl'):
|
||||
conn = TimeoutHTTPS(host, None, timeout)
|
||||
else:
|
||||
import M2Crypto
|
||||
ctx = M2Crypto.SSL.Context()
|
||||
ctx.set_session_timeout(timeout)
|
||||
conn = M2Crypto.httpslib.HTTPSConnection(host, ssl_context=ctx)
|
||||
#conn.set_debuglevel(1)
|
||||
else:
|
||||
conn = TimeoutHTTP(host, None, timeout)
|
||||
|
||||
conn.putrequest('GET', path)
|
||||
conn.putheader('Connection', 'close')
|
||||
conn.endheaders()
|
||||
response = None
|
||||
while 1:
|
||||
response = conn.getresponse()
|
||||
if response.status != 100:
|
||||
break
|
||||
conn._HTTPConnection__state = httplib._CS_REQ_SENT
|
||||
conn._HTTPConnection__response = None
|
||||
|
||||
status = response.status
|
||||
|
||||
# If we get an HTTP redirect, we will follow it automatically.
|
||||
if status >= 300 and status < 400:
|
||||
location = response.msg.getheader('location')
|
||||
if location is not None:
|
||||
response.close()
|
||||
if redirects is not None and redirects.has_key(location):
|
||||
raise RecursionError(
|
||||
'Circular HTTP redirection detected.'
|
||||
)
|
||||
if redirects is None:
|
||||
redirects = {}
|
||||
redirects[location] = 1
|
||||
return urlopen(location, timeout, redirects)
|
||||
raise HTTPResponse(response)
|
||||
|
||||
if not (status >= 200 and status < 300):
|
||||
raise HTTPResponse(response)
|
||||
|
||||
body = StringIO(response.read())
|
||||
response.close()
|
||||
return body
|
||||
|
||||
class DOM:
|
||||
"""The DOM singleton defines a number of XML related constants and
|
||||
provides a number of utility methods for DOM related tasks. It
|
||||
also provides some basic abstractions so that the rest of the
|
||||
package need not care about actual DOM implementation in use."""
|
||||
|
||||
# Namespace stuff related to the SOAP specification.
|
||||
|
||||
NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'
|
||||
NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/'
|
||||
|
||||
NS_SOAP_ENV_1_2 = 'http://www.w3.org/2001/06/soap-envelope'
|
||||
NS_SOAP_ENC_1_2 = 'http://www.w3.org/2001/06/soap-encoding'
|
||||
|
||||
NS_SOAP_ENV_ALL = (NS_SOAP_ENV_1_1, NS_SOAP_ENV_1_2)
|
||||
NS_SOAP_ENC_ALL = (NS_SOAP_ENC_1_1, NS_SOAP_ENC_1_2)
|
||||
|
||||
NS_SOAP_ENV = NS_SOAP_ENV_1_1
|
||||
NS_SOAP_ENC = NS_SOAP_ENC_1_1
|
||||
|
||||
_soap_uri_mapping = {
|
||||
NS_SOAP_ENV_1_1 : '1.1',
|
||||
NS_SOAP_ENV_1_2 : '1.2',
|
||||
}
|
||||
|
||||
SOAP_ACTOR_NEXT_1_1 = 'http://schemas.xmlsoap.org/soap/actor/next'
|
||||
SOAP_ACTOR_NEXT_1_2 = 'http://www.w3.org/2001/06/soap-envelope/actor/next'
|
||||
SOAP_ACTOR_NEXT_ALL = (SOAP_ACTOR_NEXT_1_1, SOAP_ACTOR_NEXT_1_2)
|
||||
|
||||
def SOAPUriToVersion(self, uri):
|
||||
"""Return the SOAP version related to an envelope uri."""
|
||||
value = self._soap_uri_mapping.get(uri)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP envelope uri: %s' % uri
|
||||
)
|
||||
|
||||
def GetSOAPEnvUri(self, version):
|
||||
"""Return the appropriate SOAP envelope uri for a given
|
||||
human-friendly SOAP version string (e.g. '1.1')."""
|
||||
attrname = 'NS_SOAP_ENV_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attrname, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP version: %s' % version
|
||||
)
|
||||
|
||||
def GetSOAPEncUri(self, version):
|
||||
"""Return the appropriate SOAP encoding uri for a given
|
||||
human-friendly SOAP version string (e.g. '1.1')."""
|
||||
attrname = 'NS_SOAP_ENC_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attrname, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP version: %s' % version
|
||||
)
|
||||
|
||||
def GetSOAPActorNextUri(self, version):
|
||||
"""Return the right special next-actor uri for a given
|
||||
human-friendly SOAP version string (e.g. '1.1')."""
|
||||
attrname = 'SOAP_ACTOR_NEXT_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attrname, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP version: %s' % version
|
||||
)
|
||||
|
||||
|
||||
# Namespace stuff related to XML Schema.
|
||||
|
||||
NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema'
|
||||
NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance'
|
||||
|
||||
NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema'
|
||||
NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance'
|
||||
|
||||
NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema'
|
||||
NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance'
|
||||
|
||||
NS_XSD_ALL = (NS_XSD_99, NS_XSD_00, NS_XSD_01)
|
||||
NS_XSI_ALL = (NS_XSI_99, NS_XSI_00, NS_XSI_01)
|
||||
|
||||
NS_XSD = NS_XSD_01
|
||||
NS_XSI = NS_XSI_01
|
||||
|
||||
_xsd_uri_mapping = {
|
||||
NS_XSD_99 : NS_XSI_99,
|
||||
NS_XSD_00 : NS_XSI_00,
|
||||
NS_XSD_01 : NS_XSI_01,
|
||||
}
|
||||
|
||||
for key, value in _xsd_uri_mapping.items():
|
||||
_xsd_uri_mapping[value] = key
|
||||
|
||||
|
||||
def InstanceUriForSchemaUri(self, uri):
|
||||
"""Return the appropriate matching XML Schema instance uri for
|
||||
the given XML Schema namespace uri."""
|
||||
return self._xsd_uri_mapping.get(uri)
|
||||
|
||||
def SchemaUriForInstanceUri(self, uri):
|
||||
"""Return the appropriate matching XML Schema namespace uri for
|
||||
the given XML Schema instance namespace uri."""
|
||||
return self._xsd_uri_mapping.get(uri)
|
||||
|
||||
|
||||
# Namespace stuff related to WSDL.
|
||||
|
||||
NS_WSDL_1_1 = 'http://schemas.xmlsoap.org/wsdl/'
|
||||
NS_WSDL_ALL = (NS_WSDL_1_1,)
|
||||
NS_WSDL = NS_WSDL_1_1
|
||||
|
||||
NS_SOAP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/'
|
||||
NS_HTTP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/http/'
|
||||
NS_MIME_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/mime/'
|
||||
|
||||
NS_SOAP_BINDING_ALL = (NS_SOAP_BINDING_1_1,)
|
||||
NS_HTTP_BINDING_ALL = (NS_HTTP_BINDING_1_1,)
|
||||
NS_MIME_BINDING_ALL = (NS_MIME_BINDING_1_1,)
|
||||
|
||||
NS_SOAP_BINDING = NS_SOAP_BINDING_1_1
|
||||
NS_HTTP_BINDING = NS_HTTP_BINDING_1_1
|
||||
NS_MIME_BINDING = NS_MIME_BINDING_1_1
|
||||
|
||||
NS_SOAP_HTTP_1_1 = 'http://schemas.xmlsoap.org/soap/http'
|
||||
NS_SOAP_HTTP_ALL = (NS_SOAP_HTTP_1_1,)
|
||||
NS_SOAP_HTTP = NS_SOAP_HTTP_1_1
|
||||
|
||||
|
||||
_wsdl_uri_mapping = {
|
||||
NS_WSDL_1_1 : '1.1',
|
||||
}
|
||||
|
||||
def WSDLUriToVersion(self, uri):
|
||||
"""Return the WSDL version related to a WSDL namespace uri."""
|
||||
value = self._wsdl_uri_mapping.get(uri)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported SOAP envelope uri: %s' % uri
|
||||
)
|
||||
|
||||
def GetWSDLUri(self, version):
|
||||
attr = 'NS_WSDL_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
def GetWSDLSoapBindingUri(self, version):
|
||||
attr = 'NS_SOAP_BINDING_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
def GetWSDLHttpBindingUri(self, version):
|
||||
attr = 'NS_HTTP_BINDING_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
def GetWSDLMimeBindingUri(self, version):
|
||||
attr = 'NS_MIME_BINDING_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
def GetWSDLHttpTransportUri(self, version):
|
||||
attr = 'NS_SOAP_HTTP_%s' % join(split(version, '.'), '_')
|
||||
value = getattr(self, attr, None)
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError(
|
||||
'Unsupported WSDL version: %s' % version
|
||||
)
|
||||
|
||||
|
||||
# Other xml namespace constants.
|
||||
NS_XMLNS = 'http://www.w3.org/2000/xmlns/'
|
||||
|
||||
|
||||
|
||||
def isElement(self, node, name, nsuri=None):
|
||||
"""Return true if the given node is an element with the given
|
||||
name and optional namespace uri."""
|
||||
if node.nodeType != node.ELEMENT_NODE:
|
||||
return 0
|
||||
return node.localName == name and \
|
||||
(nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri))
|
||||
|
||||
def getElement(self, node, name, nsuri=None, default=join):
|
||||
"""Return the first child of node with a matching name and
|
||||
namespace uri, or the default if one is provided."""
|
||||
nsmatch = self.nsUriMatch
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == ELEMENT_NODE:
|
||||
if ((child.localName == name or name is None) and
|
||||
(nsuri is None or nsmatch(child.namespaceURI, nsuri))
|
||||
):
|
||||
return child
|
||||
if default is not join:
|
||||
return default
|
||||
raise KeyError, name
|
||||
|
||||
def getElementById(self, node, id, default=join):
|
||||
"""Return the first child of node matching an id reference."""
|
||||
attrget = self.getAttr
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == ELEMENT_NODE:
|
||||
if attrget(child, 'id') == id:
|
||||
return child
|
||||
if default is not join:
|
||||
return default
|
||||
raise KeyError, name
|
||||
|
||||
def getMappingById(self, document, depth=None, element=None,
|
||||
mapping=None, level=1):
|
||||
"""Create an id -> element mapping of those elements within a
|
||||
document that define an id attribute. The depth of the search
|
||||
may be controlled by using the (1-based) depth argument."""
|
||||
if document is not None:
|
||||
element = document.documentElement
|
||||
mapping = {}
|
||||
attr = element._attrs.get('id', None)
|
||||
if attr is not None:
|
||||
mapping[attr.value] = element
|
||||
if depth is None or depth > level:
|
||||
level = level + 1
|
||||
ELEMENT_NODE = element.ELEMENT_NODE
|
||||
for child in element.childNodes:
|
||||
if child.nodeType == ELEMENT_NODE:
|
||||
self.getMappingById(None, depth, child, mapping, level)
|
||||
return mapping
|
||||
|
||||
def getElements(self, node, name, nsuri=None):
|
||||
"""Return a sequence of the child elements of the given node that
|
||||
match the given name and optional namespace uri."""
|
||||
nsmatch = self.nsUriMatch
|
||||
result = []
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == ELEMENT_NODE:
|
||||
if ((child.localName == name or name is None) and (
|
||||
(nsuri is None) or nsmatch(child.namespaceURI, nsuri))):
|
||||
result.append(child)
|
||||
return result
|
||||
|
||||
def hasAttr(self, node, name, nsuri=None):
|
||||
"""Return true if element has attribute with the given name and
|
||||
optional nsuri. If nsuri is not specified, returns true if an
|
||||
attribute exists with the given name with any namespace."""
|
||||
if nsuri is None:
|
||||
if node.hasAttribute(name):
|
||||
return True
|
||||
return False
|
||||
return node.hasAttributeNS(nsuri, name)
|
||||
|
||||
def getAttr(self, node, name, nsuri=None, default=join):
|
||||
"""Return the value of the attribute named 'name' with the
|
||||
optional nsuri, or the default if one is specified. If
|
||||
nsuri is not specified, an attribute that matches the
|
||||
given name will be returned regardless of namespace."""
|
||||
if nsuri is None:
|
||||
result = node._attrs.get(name, None)
|
||||
if result is None:
|
||||
for item in node._attrsNS.keys():
|
||||
if item[1] == name:
|
||||
result = node._attrsNS[item]
|
||||
break
|
||||
else:
|
||||
result = node._attrsNS.get((nsuri, name), None)
|
||||
if result is not None:
|
||||
return result.value
|
||||
if default is not join:
|
||||
return default
|
||||
return ''
|
||||
|
||||
def getAttrs(self, node):
|
||||
"""Return a Collection of all attributes
|
||||
"""
|
||||
attrs = {}
|
||||
for k,v in node._attrs.items():
|
||||
attrs[k] = v.value
|
||||
return attrs
|
||||
|
||||
def getElementText(self, node, preserve_ws=None):
|
||||
"""Return the text value of an xml element node. Leading and trailing
|
||||
whitespace is stripped from the value unless the preserve_ws flag
|
||||
is passed with a true value."""
|
||||
result = []
|
||||
for child in node.childNodes:
|
||||
nodetype = child.nodeType
|
||||
if nodetype == child.TEXT_NODE or \
|
||||
nodetype == child.CDATA_SECTION_NODE:
|
||||
result.append(child.nodeValue)
|
||||
value = join(result, '')
|
||||
if preserve_ws is None:
|
||||
value = strip(value)
|
||||
return value
|
||||
|
||||
def findNamespaceURI(self, prefix, node):
|
||||
"""Find a namespace uri given a prefix and a context node."""
|
||||
attrkey = (self.NS_XMLNS, prefix)
|
||||
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
while 1:
|
||||
if node.nodeType != ELEMENT_NODE:
|
||||
node = node.parentNode
|
||||
continue
|
||||
result = node._attrsNS.get(attrkey, None)
|
||||
if result is not None:
|
||||
return result.value
|
||||
if hasattr(node, '__imported__'):
|
||||
raise DOMException('Value for prefix %s not found.' % prefix)
|
||||
node = node.parentNode
|
||||
if node.nodeType == DOCUMENT_NODE:
|
||||
raise DOMException('Value for prefix %s not found.' % prefix)
|
||||
|
||||
def findDefaultNS(self, node):
|
||||
"""Return the current default namespace uri for the given node."""
|
||||
attrkey = (self.NS_XMLNS, 'xmlns')
|
||||
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
while 1:
|
||||
if node.nodeType != ELEMENT_NODE:
|
||||
node = node.parentNode
|
||||
continue
|
||||
result = node._attrsNS.get(attrkey, None)
|
||||
if result is not None:
|
||||
return result.value
|
||||
if hasattr(node, '__imported__'):
|
||||
raise DOMException('Cannot determine default namespace.')
|
||||
node = node.parentNode
|
||||
if node.nodeType == DOCUMENT_NODE:
|
||||
raise DOMException('Cannot determine default namespace.')
|
||||
|
||||
def findTargetNS(self, node):
|
||||
"""Return the defined target namespace uri for the given node."""
|
||||
attrget = self.getAttr
|
||||
attrkey = (self.NS_XMLNS, 'xmlns')
|
||||
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||
ELEMENT_NODE = node.ELEMENT_NODE
|
||||
while 1:
|
||||
if node.nodeType != ELEMENT_NODE:
|
||||
node = node.parentNode
|
||||
continue
|
||||
result = attrget(node, 'targetNamespace', default=None)
|
||||
if result is not None:
|
||||
return result
|
||||
node = node.parentNode
|
||||
if node.nodeType == DOCUMENT_NODE:
|
||||
raise DOMException('Cannot determine target namespace.')
|
||||
|
||||
def getTypeRef(self, element):
|
||||
"""Return (namespaceURI, name) for a type attribue of the given
|
||||
element, or None if the element does not have a type attribute."""
|
||||
typeattr = self.getAttr(element, 'type', default=None)
|
||||
if typeattr is None:
|
||||
return None
|
||||
parts = typeattr.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
nsuri = self.findNamespaceURI(parts[0], element)
|
||||
else:
|
||||
nsuri = self.findDefaultNS(element)
|
||||
return (nsuri, parts[1])
|
||||
|
||||
def importNode(self, document, node, deep=0):
|
||||
"""Implements (well enough for our purposes) DOM node import."""
|
||||
nodetype = node.nodeType
|
||||
if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE):
|
||||
raise DOMException('Illegal node type for importNode')
|
||||
if nodetype == node.ENTITY_REFERENCE_NODE:
|
||||
deep = 0
|
||||
clone = node.cloneNode(deep)
|
||||
self._setOwnerDoc(document, clone)
|
||||
clone.__imported__ = 1
|
||||
return clone
|
||||
|
||||
def _setOwnerDoc(self, document, node):
|
||||
node.ownerDocument = document
|
||||
for child in node.childNodes:
|
||||
self._setOwnerDoc(document, child)
|
||||
|
||||
def nsUriMatch(self, value, wanted, strict=0, tt=type(())):
|
||||
"""Return a true value if two namespace uri values match."""
|
||||
if value == wanted or (type(wanted) is tt) and value in wanted:
|
||||
return 1
|
||||
if not strict:
|
||||
wanted = type(wanted) is tt and wanted or (wanted,)
|
||||
value = value[-1:] != '/' and value or value[:-1]
|
||||
for item in wanted:
|
||||
if item == value or item[:-1] == value:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def createDocument(self, nsuri, qname, doctype=None):
|
||||
"""Create a new writable DOM document object."""
|
||||
impl = xml.dom.minidom.getDOMImplementation()
|
||||
return impl.createDocument(nsuri, qname, doctype)
|
||||
|
||||
def loadDocument(self, data):
|
||||
"""Load an xml file from a file-like object and return a DOM
|
||||
document instance."""
|
||||
return xml.dom.minidom.parse(data)
|
||||
|
||||
def loadFromURL(self, url):
|
||||
"""Load an xml file from a URL and return a DOM document."""
|
||||
file = urlopen(url)
|
||||
try: result = self.loadDocument(file)
|
||||
finally: file.close()
|
||||
return result
|
||||
|
||||
|
||||
class DOMException(Exception):
|
||||
pass
|
||||
|
||||
DOM = DOM()
|
||||
|
||||
|
||||
class Collection(UserDict):
|
||||
"""Helper class for maintaining ordered named collections."""
|
||||
default = lambda self,k: k.name
|
||||
def __init__(self, parent, key=None):
|
||||
UserDict.__init__(self)
|
||||
self.parent = weakref.ref(parent)
|
||||
self.list = []
|
||||
self._func = key or self.default
|
||||
|
||||
def __getitem__(self, key):
|
||||
if type(key) is type(1):
|
||||
return self.list[key]
|
||||
return self.data[key]
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
item.parent = weakref.ref(self)
|
||||
self.list.append(item)
|
||||
self.data[key] = item
|
||||
|
||||
def keys(self):
|
||||
return map(lambda i: self._func(i), self.list)
|
||||
|
||||
def items(self):
|
||||
return map(lambda i: (self._func(i), i), self.list)
|
||||
|
||||
def values(self):
|
||||
return self.list
|
||||
|
||||
|
||||
class CollectionNS(UserDict):
|
||||
"""Helper class for maintaining ordered named collections."""
|
||||
default = lambda self,k: k.name
|
||||
def __init__(self, parent, key=None):
|
||||
UserDict.__init__(self)
|
||||
self.parent = weakref.ref(parent)
|
||||
self.targetNamespace = None
|
||||
self.list = []
|
||||
self._func = key or self.default
|
||||
|
||||
def __getitem__(self, key):
|
||||
self.targetNamespace = self.parent().targetNamespace
|
||||
if type(key) is types.IntType:
|
||||
return self.list[key]
|
||||
elif self.__isSequence(key):
|
||||
nsuri,name = key
|
||||
return self.data[nsuri][name]
|
||||
return self.data[self.parent().targetNamespace][key]
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
item.parent = weakref.ref(self)
|
||||
self.list.append(item)
|
||||
targetNamespace = getattr(item, 'targetNamespace', self.parent().targetNamespace)
|
||||
if not self.data.has_key(targetNamespace):
|
||||
self.data[targetNamespace] = {}
|
||||
self.data[targetNamespace][key] = item
|
||||
|
||||
def __isSequence(self, key):
|
||||
return (type(key) in (types.TupleType,types.ListType) and len(key) == 2)
|
||||
|
||||
def keys(self):
|
||||
keys = []
|
||||
for tns in self.data.keys():
|
||||
keys.append(map(lambda i: (tns,self._func(i)), self.data[tns].values()))
|
||||
return keys
|
||||
|
||||
def items(self):
|
||||
return map(lambda i: (self._func(i), i), self.list)
|
||||
|
||||
def values(self):
|
||||
return self.list
|
||||
|
||||
|
||||
|
||||
# This is a runtime guerilla patch for pulldom (used by minidom) so
|
||||
# that xml namespace declaration attributes are not lost in parsing.
|
||||
# We need them to do correct QName linking for XML Schema and WSDL.
|
||||
# The patch has been submitted to SF for the next Python version.
|
||||
|
||||
from xml.dom.pulldom import PullDOM, START_ELEMENT
|
||||
if 1:
|
||||
def startPrefixMapping(self, prefix, uri):
|
||||
if not hasattr(self, '_xmlns_attrs'):
|
||||
self._xmlns_attrs = []
|
||||
self._xmlns_attrs.append((prefix or 'xmlns', uri))
|
||||
self._ns_contexts.append(self._current_context.copy())
|
||||
self._current_context[uri] = prefix or ''
|
||||
|
||||
PullDOM.startPrefixMapping = startPrefixMapping
|
||||
|
||||
def startElementNS(self, name, tagName , attrs):
|
||||
# Retrieve xml namespace declaration attributes.
|
||||
xmlns_uri = 'http://www.w3.org/2000/xmlns/'
|
||||
xmlns_attrs = getattr(self, '_xmlns_attrs', None)
|
||||
if xmlns_attrs is not None:
|
||||
for aname, value in xmlns_attrs:
|
||||
attrs._attrs[(xmlns_uri, aname)] = value
|
||||
self._xmlns_attrs = []
|
||||
uri, localname = name
|
||||
if uri:
|
||||
# When using namespaces, the reader may or may not
|
||||
# provide us with the original name. If not, create
|
||||
# *a* valid tagName from the current context.
|
||||
if tagName is None:
|
||||
prefix = self._current_context[uri]
|
||||
if prefix:
|
||||
tagName = prefix + ":" + localname
|
||||
else:
|
||||
tagName = localname
|
||||
if self.document:
|
||||
node = self.document.createElementNS(uri, tagName)
|
||||
else:
|
||||
node = self.buildDocument(uri, tagName)
|
||||
else:
|
||||
# When the tagname is not prefixed, it just appears as
|
||||
# localname
|
||||
if self.document:
|
||||
node = self.document.createElement(localname)
|
||||
else:
|
||||
node = self.buildDocument(None, localname)
|
||||
|
||||
for aname,value in attrs.items():
|
||||
a_uri, a_localname = aname
|
||||
if a_uri == xmlns_uri:
|
||||
if a_localname == 'xmlns':
|
||||
qname = a_localname
|
||||
else:
|
||||
qname = 'xmlns:' + a_localname
|
||||
attr = self.document.createAttributeNS(a_uri, qname)
|
||||
node.setAttributeNodeNS(attr)
|
||||
elif a_uri:
|
||||
prefix = self._current_context[a_uri]
|
||||
if prefix:
|
||||
qname = prefix + ":" + a_localname
|
||||
else:
|
||||
qname = a_localname
|
||||
attr = self.document.createAttributeNS(a_uri, qname)
|
||||
node.setAttributeNodeNS(attr)
|
||||
else:
|
||||
attr = self.document.createAttribute(a_localname)
|
||||
node.setAttributeNode(attr)
|
||||
attr.value = value
|
||||
|
||||
self.lastEvent[1] = [(START_ELEMENT, node), None]
|
||||
self.lastEvent = self.lastEvent[1]
|
||||
self.push(node)
|
||||
|
||||
PullDOM.startElementNS = startElementNS
|
||||
|
||||
#
|
||||
# This is a runtime guerilla patch for minidom so
|
||||
# that xmlns prefixed attributes dont raise AttributeErrors
|
||||
# during cloning.
|
||||
#
|
||||
# Namespace declarations can appear in any start-tag, must look for xmlns
|
||||
# prefixed attribute names during cloning.
|
||||
#
|
||||
# key (attr.namespaceURI, tag)
|
||||
# ('http://www.w3.org/2000/xmlns/', u'xsd') <xml.dom.minidom.Attr instance at 0x82227c4>
|
||||
# ('http://www.w3.org/2000/xmlns/', 'xmlns') <xml.dom.minidom.Attr instance at 0x8414b3c>
|
||||
#
|
||||
# xml.dom.minidom.Attr.nodeName = xmlns:xsd
|
||||
# xml.dom.minidom.Attr.value = = http://www.w3.org/2001/XMLSchema
|
||||
|
||||
if 1:
|
||||
def _clone_node(node, deep, newOwnerDocument):
|
||||
"""
|
||||
Clone a node and give it the new owner document.
|
||||
Called by Node.cloneNode and Document.importNode
|
||||
"""
|
||||
if node.ownerDocument.isSameNode(newOwnerDocument):
|
||||
operation = xml.dom.UserDataHandler.NODE_CLONED
|
||||
else:
|
||||
operation = xml.dom.UserDataHandler.NODE_IMPORTED
|
||||
if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
|
||||
clone = newOwnerDocument.createElementNS(node.namespaceURI,
|
||||
node.nodeName)
|
||||
for attr in node.attributes.values():
|
||||
clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
|
||||
|
||||
prefix, tag = xml.dom.minidom._nssplit(attr.nodeName)
|
||||
if prefix == 'xmlns':
|
||||
a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
|
||||
elif prefix:
|
||||
a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
|
||||
else:
|
||||
a = clone.getAttributeNodeNS(attr.namespaceURI, attr.nodeName)
|
||||
a.specified = attr.specified
|
||||
|
||||
if deep:
|
||||
for child in node.childNodes:
|
||||
c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
|
||||
clone.appendChild(c)
|
||||
elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_FRAGMENT_NODE:
|
||||
clone = newOwnerDocument.createDocumentFragment()
|
||||
if deep:
|
||||
for child in node.childNodes:
|
||||
c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
|
||||
clone.appendChild(c)
|
||||
|
||||
elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
|
||||
clone = newOwnerDocument.createTextNode(node.data)
|
||||
elif node.nodeType == xml.dom.minidom.Node.CDATA_SECTION_NODE:
|
||||
clone = newOwnerDocument.createCDATASection(node.data)
|
||||
elif node.nodeType == xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE:
|
||||
clone = newOwnerDocument.createProcessingInstruction(node.target,
|
||||
node.data)
|
||||
elif node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
|
||||
clone = newOwnerDocument.createComment(node.data)
|
||||
elif node.nodeType == xml.dom.minidom.Node.ATTRIBUTE_NODE:
|
||||
clone = newOwnerDocument.createAttributeNS(node.namespaceURI,
|
||||
node.nodeName)
|
||||
clone.specified = True
|
||||
clone.value = node.value
|
||||
elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_TYPE_NODE:
|
||||
assert node.ownerDocument is not newOwnerDocument
|
||||
operation = xml.dom.UserDataHandler.NODE_IMPORTED
|
||||
clone = newOwnerDocument.implementation.createDocumentType(
|
||||
node.name, node.publicId, node.systemId)
|
||||
clone.ownerDocument = newOwnerDocument
|
||||
if deep:
|
||||
clone.entities._seq = []
|
||||
clone.notations._seq = []
|
||||
for n in node.notations._seq:
|
||||
notation = xml.dom.minidom.Notation(n.nodeName, n.publicId, n.systemId)
|
||||
notation.ownerDocument = newOwnerDocument
|
||||
clone.notations._seq.append(notation)
|
||||
if hasattr(n, '_call_user_data_handler'):
|
||||
n._call_user_data_handler(operation, n, notation)
|
||||
for e in node.entities._seq:
|
||||
entity = xml.dom.minidom.Entity(e.nodeName, e.publicId, e.systemId,
|
||||
e.notationName)
|
||||
entity.actualEncoding = e.actualEncoding
|
||||
entity.encoding = e.encoding
|
||||
entity.version = e.version
|
||||
entity.ownerDocument = newOwnerDocument
|
||||
clone.entities._seq.append(entity)
|
||||
if hasattr(e, '_call_user_data_handler'):
|
||||
e._call_user_data_handler(operation, n, entity)
|
||||
else:
|
||||
# Note the cloning of Document and DocumentType nodes is
|
||||
# implemenetation specific. minidom handles those cases
|
||||
# directly in the cloneNode() methods.
|
||||
raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node))
|
||||
|
||||
# Check for _call_user_data_handler() since this could conceivably
|
||||
# used with other DOM implementations (one of the FourThought
|
||||
# DOMs, perhaps?).
|
||||
if hasattr(node, '_call_user_data_handler'):
|
||||
node._call_user_data_handler(operation, node, clone)
|
||||
return clone
|
||||
|
||||
xml.dom.minidom._clone_node = _clone_node
|
1336
others/SOAPpy/wstools/WSDLTools.py
Executable file
1336
others/SOAPpy/wstools/WSDLTools.py
Executable file
File diff suppressed because it is too large
Load Diff
2976
others/SOAPpy/wstools/XMLSchema.py
Executable file
2976
others/SOAPpy/wstools/XMLSchema.py
Executable file
File diff suppressed because it is too large
Load Diff
88
others/SOAPpy/wstools/XMLname.py
Executable file
88
others/SOAPpy/wstools/XMLname.py
Executable file
@ -0,0 +1,88 @@
|
||||
"""Translate strings to and from SOAP 1.2 XML name encoding
|
||||
|
||||
Implements rules for mapping application defined name to XML names
|
||||
specified by the w3 SOAP working group for SOAP version 1.2 in
|
||||
Appendix A of "SOAP Version 1.2 Part 2: Adjuncts", W3C Working Draft
|
||||
17, December 2001, <http://www.w3.org/TR/soap12-part2/#namemap>
|
||||
|
||||
Also see <http://www.w3.org/2000/xp/Group/xmlp-issues>.
|
||||
|
||||
Author: Gregory R. Warnes <gregory_r_warnes@groton.pfizer.com>
|
||||
Date:: 2002-04-25
|
||||
Version 0.9.0
|
||||
|
||||
"""
|
||||
|
||||
ident = "$Id$"
|
||||
|
||||
from re import *
|
||||
|
||||
|
||||
def _NCNameChar(x):
|
||||
return x.isalpha() or x.isdigit() or x=="." or x=='-' or x=="_"
|
||||
|
||||
|
||||
def _NCNameStartChar(x):
|
||||
return x.isalpha() or x=="_"
|
||||
|
||||
|
||||
def _toUnicodeHex(x):
|
||||
hexval = hex(ord(x[0]))[2:]
|
||||
hexlen = len(hexval)
|
||||
# Make hexval have either 4 or 8 digits by prepending 0's
|
||||
if (hexlen==1): hexval = "000" + hexval
|
||||
elif (hexlen==2): hexval = "00" + hexval
|
||||
elif (hexlen==3): hexval = "0" + hexval
|
||||
elif (hexlen==4): hexval = "" + hexval
|
||||
elif (hexlen==5): hexval = "000" + hexval
|
||||
elif (hexlen==6): hexval = "00" + hexval
|
||||
elif (hexlen==7): hexval = "0" + hexval
|
||||
elif (hexlen==8): hexval = "" + hexval
|
||||
else: raise Exception, "Illegal Value returned from hex(ord(x))"
|
||||
|
||||
return "_x"+ hexval + "_"
|
||||
|
||||
|
||||
def _fromUnicodeHex(x):
|
||||
return eval( r'u"\u'+x[2:-1]+'"' )
|
||||
|
||||
|
||||
def toXMLname(string):
|
||||
"""Convert string to a XML name."""
|
||||
if string.find(':') != -1 :
|
||||
(prefix, localname) = string.split(':',1)
|
||||
else:
|
||||
prefix = None
|
||||
localname = string
|
||||
|
||||
T = unicode(localname)
|
||||
|
||||
N = len(localname)
|
||||
X = [];
|
||||
for i in range(N) :
|
||||
if i< N-1 and T[i]==u'_' and T[i+1]==u'x':
|
||||
X.append(u'_x005F_')
|
||||
elif i==0 and N >= 3 and \
|
||||
( T[0]==u'x' or T[0]==u'X' ) and \
|
||||
( T[1]==u'm' or T[1]==u'M' ) and \
|
||||
( T[2]==u'l' or T[2]==u'L' ):
|
||||
X.append(u'_xFFFF_' + T[0])
|
||||
elif (not _NCNameChar(T[i])) or (i==0 and not _NCNameStartChar(T[i])):
|
||||
X.append(_toUnicodeHex(T[i]))
|
||||
else:
|
||||
X.append(T[i])
|
||||
|
||||
return u''.join(X)
|
||||
|
||||
|
||||
def fromXMLname(string):
|
||||
"""Convert XML name to unicode string."""
|
||||
|
||||
retval = sub(r'_xFFFF_','', string )
|
||||
|
||||
def fun( matchobj ):
|
||||
return _fromUnicodeHex( matchobj.group(0) )
|
||||
|
||||
retval = sub(r'_x[0-9A-Za-z]+_', fun, retval )
|
||||
|
||||
return retval
|
35
others/SOAPpy/wstools/__init__.py
Normal file
35
others/SOAPpy/wstools/__init__.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""WSDL parsing services package for Web Services for Python."""
|
||||
|
||||
ident = "$Id$"
|
||||
|
||||
import WSDLTools
|
||||
import XMLname
|
||||
from logging import getLogger as _getLogger
|
||||
import logging.config as _config
|
||||
|
||||
LOGGING = 'logging.txt'
|
||||
DEBUG = True
|
||||
|
||||
#
|
||||
# If LOGGING configuration file is not found, turn off logging
|
||||
# and use _noLogger class because logging module's performance
|
||||
# is terrible.
|
||||
#
|
||||
|
||||
try:
|
||||
_config.fileConfig(LOGGING)
|
||||
except:
|
||||
DEBUG = False
|
||||
|
||||
|
||||
class Base:
|
||||
def __init__(self, module=__name__):
|
||||
self.logger = _noLogger()
|
||||
if DEBUG is True:
|
||||
self.logger = _getLogger('%s-%s(%x)' %(module, self.__class__, id(self)))
|
||||
|
||||
class _noLogger:
|
||||
def __init__(self, *args): pass
|
||||
def warning(self, *args): pass
|
||||
def debug(self, *args): pass
|
||||
def error(self, *args): pass
|
0
others/__init__.py
Normal file
0
others/__init__.py
Normal file
364
others/amazon.py
Normal file
364
others/amazon.py
Normal file
@ -0,0 +1,364 @@
|
||||
"""Python wrapper
|
||||
|
||||
|
||||
for Amazon web APIs
|
||||
|
||||
This module allows you to access Amazon's web APIs,
|
||||
to do things like search Amazon and get the results programmatically.
|
||||
Described here:
|
||||
http://www.amazon.com/webservices
|
||||
|
||||
You need a Amazon-provided license key to use these services.
|
||||
Follow the link above to get one. These functions will look in
|
||||
several places (in this order) for the license key:
|
||||
- the "license_key" argument of each function
|
||||
- the module-level LICENSE_KEY variable (call setLicense once to set it)
|
||||
- an environment variable called AMAZON_LICENSE_KEY
|
||||
- a file called ".amazonkey" in the current directory
|
||||
- a file called "amazonkey.txt" in the current directory
|
||||
- a file called ".amazonkey" in your home directory
|
||||
- a file called "amazonkey.txt" in your home directory
|
||||
- a file called ".amazonkey" in the same directory as amazon.py
|
||||
- a file called "amazonkey.txt" in the same directory as amazon.py
|
||||
|
||||
Sample usage:
|
||||
>>> import amazon
|
||||
>>> amazon.setLicense('...') # must get your own key!
|
||||
>>> pythonBooks = amazon.searchByKeyword('Python')
|
||||
>>> pythonBooks[0].ProductName
|
||||
u'Learning Python (Help for Programmers)'
|
||||
>>> pythonBooks[0].URL
|
||||
...
|
||||
>>> pythonBooks[0].OurPrice
|
||||
...
|
||||
|
||||
Other available functions:
|
||||
- browseBestSellers
|
||||
- searchByASIN
|
||||
- searchByUPC
|
||||
- searchByAuthor
|
||||
- searchByArtist
|
||||
- searchByActor
|
||||
- searchByDirector
|
||||
- searchByManufacturer
|
||||
- searchByListMania
|
||||
- searchSimilar
|
||||
- searchByWishlist
|
||||
|
||||
Other usage notes:
|
||||
- Most functions can take product_line as well, see source for possible values
|
||||
- All functions can take type="lite" to get less detail in results
|
||||
- All functions can take page=N to get second, third, fourth page of results
|
||||
- All functions can take license_key="XYZ", instead of setting it globally
|
||||
- All functions can take http_proxy="http://x/y/z" which overrides your system setting
|
||||
"""
|
||||
|
||||
__author__ = "Mark Pilgrim (f8dy@diveintomark.org)"
|
||||
__version__ = "0.64.1"
|
||||
__cvsversion__ = "$Revision$"[11:-2]
|
||||
__date__ = "$Date$"[7:-2]
|
||||
__copyright__ = "Copyright (c) 2002 Mark Pilgrim"
|
||||
__license__ = "Python"
|
||||
# Powersearch and return object type fix by Joseph Reagle <geek@goatee.net>
|
||||
|
||||
# Locale support by Michael Josephson <mike@josephson.org>
|
||||
|
||||
# Modification to _contentsOf to strip trailing whitespace when loading Amazon key
|
||||
# from a file submitted by Patrick Phalen.
|
||||
|
||||
# Support for specifying locale and associates ID as search parameters and
|
||||
# internationalisation fix for the SalesRank integer conversion by
|
||||
# Christian Theune <ct@gocept.com>, gocept gmbh & co. kg
|
||||
|
||||
# Support for BlendedSearch contributed by Alex Choo
|
||||
|
||||
from xml.dom import minidom
|
||||
import os, sys, getopt, cgi, urllib, string
|
||||
try:
|
||||
import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py
|
||||
timeoutsocket.setDefaultSocketTimeout(10)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
LICENSE_KEY = None
|
||||
ASSOCIATE = "webservices-20"
|
||||
HTTP_PROXY = None
|
||||
LOCALE = "us"
|
||||
|
||||
# don't touch the rest of these constants
|
||||
class AmazonError(Exception): pass
|
||||
class NoLicenseKey(Exception): pass
|
||||
_amazonfile1 = ".amazonkey"
|
||||
_amazonfile2 = "amazonkey.txt"
|
||||
_licenseLocations = (
|
||||
(lambda key: key, 'passed to the function in license_key variable'),
|
||||
(lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'),
|
||||
(lambda key: os.environ.get('AMAZON_LICENSE_KEY', None), 'an environment variable called AMAZON_LICENSE_KEY'),
|
||||
(lambda key: _contentsOf(os.getcwd(), _amazonfile1), '%s in the current directory' % _amazonfile1),
|
||||
(lambda key: _contentsOf(os.getcwd(), _amazonfile2), '%s in the current directory' % _amazonfile2),
|
||||
(lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile1), '%s in your home directory' % _amazonfile1),
|
||||
(lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile2), '%s in your home directory' % _amazonfile2),
|
||||
(lambda key: _contentsOf(_getScriptDir(), _amazonfile1), '%s in the amazon.py directory' % _amazonfile1),
|
||||
(lambda key: _contentsOf(_getScriptDir(), _amazonfile2), '%s in the amazon.py directory' % _amazonfile2)
|
||||
)
|
||||
_supportedLocales = {
|
||||
"us" : (None, "xml.amazon.com"),
|
||||
"uk" : ("uk", "xml-eu.amazon.com"),
|
||||
"de" : ("de", "xml-eu.amazon.com"),
|
||||
"jp" : ("jp", "xml.amazon.co.jp")
|
||||
}
|
||||
|
||||
## administrative functions
|
||||
def version():
|
||||
print """PyAmazon %(__version__)s
|
||||
%(__copyright__)s
|
||||
released %(__date__)s
|
||||
""" % globals()
|
||||
|
||||
def setAssociate(associate):
|
||||
global ASSOCIATE
|
||||
ASSOCIATE=associate
|
||||
|
||||
def getAssociate(override=None):
|
||||
return override or ASSOCIATE
|
||||
|
||||
## utility functions
|
||||
|
||||
def _checkLocaleSupported(locale):
|
||||
if not _supportedLocales.has_key(locale):
|
||||
raise AmazonError, ("Unsupported locale. Locale must be one of: %s" %
|
||||
string.join(_supportedLocales, ", "))
|
||||
|
||||
def setLocale(locale):
|
||||
"""set locale"""
|
||||
global LOCALE
|
||||
_checkLocaleSupported(locale)
|
||||
LOCALE = locale
|
||||
|
||||
def getLocale(locale=None):
|
||||
"""get locale"""
|
||||
return locale or LOCALE
|
||||
|
||||
def setLicense(license_key):
|
||||
"""set license key"""
|
||||
global LICENSE_KEY
|
||||
LICENSE_KEY = license_key
|
||||
|
||||
def getLicense(license_key = None):
|
||||
"""get license key
|
||||
|
||||
license key can come from any number of locations;
|
||||
see module docs for search order"""
|
||||
for get, location in _licenseLocations:
|
||||
rc = get(license_key)
|
||||
if rc: return rc
|
||||
raise NoLicenseKey, 'get a license key at http://www.amazon.com/webservices'
|
||||
|
||||
def setProxy(http_proxy):
|
||||
"""set HTTP proxy"""
|
||||
global HTTP_PROXY
|
||||
HTTP_PROXY = http_proxy
|
||||
|
||||
def getProxy(http_proxy = None):
|
||||
"""get HTTP proxy"""
|
||||
return http_proxy or HTTP_PROXY
|
||||
|
||||
def getProxies(http_proxy = None):
|
||||
http_proxy = getProxy(http_proxy)
|
||||
if http_proxy:
|
||||
proxies = {"http": http_proxy}
|
||||
else:
|
||||
proxies = None
|
||||
return proxies
|
||||
|
||||
def _contentsOf(dirname, filename):
|
||||
filename = os.path.join(dirname, filename)
|
||||
if not os.path.exists(filename): return None
|
||||
fsock = open(filename)
|
||||
contents = fsock.read().strip()
|
||||
fsock.close()
|
||||
return contents
|
||||
|
||||
def _getScriptDir():
|
||||
if __name__ == '__main__':
|
||||
return os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||
else:
|
||||
return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__))
|
||||
|
||||
class Bag: pass
|
||||
|
||||
def unmarshal(element):
|
||||
rc = Bag()
|
||||
if isinstance(element, minidom.Element) and (element.tagName == 'Details'):
|
||||
rc.URL = element.attributes["url"].value
|
||||
childElements = [e for e in element.childNodes if isinstance(e, minidom.Element)]
|
||||
if childElements:
|
||||
for child in childElements:
|
||||
key = child.tagName
|
||||
if hasattr(rc, key):
|
||||
if type(getattr(rc, key)) <> type([]):
|
||||
setattr(rc, key, [getattr(rc, key)])
|
||||
setattr(rc, key, getattr(rc, key) + [unmarshal(child)])
|
||||
elif isinstance(child, minidom.Element) and (child.tagName == 'Details'):
|
||||
# make the first Details element a key
|
||||
setattr(rc,key,[unmarshal(child)])
|
||||
#dbg: because otherwise 'hasattr' only tests
|
||||
#dbg: on the second occurence: if there's a
|
||||
#dbg: single return to a query, it's not a
|
||||
#dbg: list. This module should always
|
||||
#dbg: return a list of Details objects.
|
||||
else:
|
||||
setattr(rc, key, unmarshal(child))
|
||||
else:
|
||||
rc = "".join([e.data for e in element.childNodes if isinstance(e, minidom.Text)])
|
||||
if element.tagName == 'SalesRank':
|
||||
rc = rc.replace('.', '')
|
||||
rc = rc.replace(',', '')
|
||||
rc = int(rc)
|
||||
return rc
|
||||
|
||||
def buildURL(search_type, keyword, product_line, type, page, license_key, locale, associate):
|
||||
_checkLocaleSupported(locale)
|
||||
url = "http://" + _supportedLocales[locale][1] + "/onca/xml3?f=xml"
|
||||
url += "&t=%s" % associate
|
||||
url += "&dev-t=%s" % license_key.strip()
|
||||
url += "&type=%s" % type
|
||||
if _supportedLocales[locale][0]:
|
||||
url += "&locale=%s" % _supportedLocales[locale][0]
|
||||
if page:
|
||||
url += "&page=%s" % page
|
||||
if product_line:
|
||||
url += "&mode=%s" % product_line
|
||||
url += "&%s=%s" % (search_type, urllib.quote(keyword))
|
||||
return url
|
||||
|
||||
|
||||
## main functions
|
||||
|
||||
|
||||
def search(search_type, keyword, product_line, type = "heavy", page = None,
|
||||
license_key=None, http_proxy = None, locale = None, associate = None):
|
||||
"""search Amazon
|
||||
|
||||
You need a license key to call this function; see
|
||||
http://www.amazon.com/webservices
|
||||
to get one. Then you can either pass it to
|
||||
this function every time, or set it globally; see the module docs for details.
|
||||
|
||||
Parameters:
|
||||
keyword - keyword to search
|
||||
search_type - in (KeywordSearch, BrowseNodeSearch, AsinSearch, UpcSearch, AuthorSearch, ArtistSearch, ActorSearch, DirectorSearch, ManufacturerSearch, ListManiaSearch, SimilaritySearch)
|
||||
product_line - type of product to search for. restrictions based on search_type
|
||||
UpcSearch - in (music, classical)
|
||||
AuthorSearch - must be "books"
|
||||
ArtistSearch - in (music, classical)
|
||||
ActorSearch - in (dvd, vhs, video)
|
||||
DirectorSearch - in (dvd, vhs, video)
|
||||
ManufacturerSearch - in (electronics, kitchen, videogames, software, photo, pc-hardware)
|
||||
http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages
|
||||
|
||||
Returns: list of Bags, each Bag may contain the following attributes:
|
||||
Asin - Amazon ID ("ASIN" number) of this item
|
||||
Authors - list of authors
|
||||
Availability - "available", etc.
|
||||
BrowseList - list of related categories
|
||||
Catalog - catalog type ("Book", etc)
|
||||
CollectiblePrice - ?, format "$34.95"
|
||||
ImageUrlLarge - URL of large image of this item
|
||||
ImageUrlMedium - URL of medium image of this item
|
||||
ImageUrlSmall - URL of small image of this item
|
||||
Isbn - ISBN number
|
||||
ListPrice - list price, format "$34.95"
|
||||
Lists - list of ListMania lists that include this item
|
||||
Manufacturer - manufacturer
|
||||
Media - media ("Paperback", "Audio CD", etc)
|
||||
NumMedia - number of different media types in which this item is available
|
||||
OurPrice - Amazon price, format "$24.47"
|
||||
ProductName - name of this item
|
||||
ReleaseDate - release date, format "09 April, 1999"
|
||||
Reviews - reviews (AvgCustomerRating, plus list of CustomerReview with Rating, Summary, Content)
|
||||
SalesRank - sales rank (integer)
|
||||
SimilarProducts - list of Product, which is ASIN number
|
||||
ThirdPartyNewPrice - ?, format "$34.95"
|
||||
URL - URL of this item
|
||||
"""
|
||||
license_key = getLicense(license_key)
|
||||
locale = getLocale(locale)
|
||||
associate = getAssociate(associate)
|
||||
url = buildURL(search_type, keyword, product_line, type, page,
|
||||
license_key, locale, associate)
|
||||
proxies = getProxies(http_proxy)
|
||||
u = urllib.FancyURLopener(proxies)
|
||||
usock = u.open(url)
|
||||
xmldoc = minidom.parse(usock)
|
||||
|
||||
# from xml.dom.ext import PrettyPrint
|
||||
# PrettyPrint(xmldoc)
|
||||
|
||||
usock.close()
|
||||
if search_type == "BlendedSearch":
|
||||
data = unmarshal(xmldoc).BlendedSearch
|
||||
else:
|
||||
data = unmarshal(xmldoc).ProductInfo
|
||||
|
||||
if hasattr(data, 'ErrorMsg'):
|
||||
raise AmazonError, data.ErrorMsg
|
||||
else:
|
||||
if search_type == "BlendedSearch":
|
||||
# a list of ProductLine containing a list of ProductInfo
|
||||
# containing a list of Details.
|
||||
return data
|
||||
else:
|
||||
return data.Details
|
||||
|
||||
def searchByKeyword(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("KeywordSearch", keyword, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def browseBestSellers(browse_node, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("BrowseNodeSearch", browse_node, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByASIN(ASIN, type="heavy", license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("AsinSearch", ASIN, None, type, None, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByUPC(UPC, type="heavy", license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("UpcSearch", UPC, None, type, None, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByAuthor(author, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("AuthorSearch", author, "books", type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByArtist(artist, product_line="music", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
if product_line not in ("music", "classical"):
|
||||
raise AmazonError, "product_line must be in ('music', 'classical')"
|
||||
return search("ArtistSearch", artist, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByActor(actor, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
if product_line not in ("dvd", "vhs", "video"):
|
||||
raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
|
||||
return search("ActorSearch", actor, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByDirector(director, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
if product_line not in ("dvd", "vhs", "video"):
|
||||
raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
|
||||
return search("DirectorSearch", director, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByManufacturer(manufacturer, product_line="pc-hardware", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
if product_line not in ("electronics", "kitchen", "videogames", "software", "photo", "pc-hardware"):
|
||||
raise AmazonError, "product_line must be in ('electronics', 'kitchen', 'videogames', 'software', 'photo', 'pc-hardware')"
|
||||
return search("ManufacturerSearch", manufacturer, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByListMania(listManiaID, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("ListManiaSearch", listManiaID, None, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchSimilar(ASIN, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("SimilaritySearch", ASIN, None, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByWishlist(wishlistID, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("WishlistSearch", wishlistID, None, type, page, license_key, http_proxy, locale, associate)
|
||||
|
||||
def searchByPower(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("PowerSearch", keyword, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||
# >>> RecentKing = amazon.searchByPower('author:Stephen King and pubdate:2003')
|
||||
# >>> SnowCrash = amazon.searchByPower('title:Snow Crash')
|
||||
|
||||
def searchByBlended(keyword, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||
return search("BlendedSearch", keyword, None, type, page, license_key, http_proxy, locale, associate)
|
294
others/asynchat.py
Normal file
294
others/asynchat.py
Normal file
@ -0,0 +1,294 @@
|
||||
# -*- Mode: Python; tab-width: 4 -*-
|
||||
# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
|
||||
# Author: Sam Rushing <rushing@nightmare.com>
|
||||
|
||||
# ======================================================================
|
||||
# Copyright 1996 by Sam Rushing
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose and without fee is hereby
|
||||
# granted, provided that the above copyright notice appear in all
|
||||
# copies and that both that copyright notice and this permission
|
||||
# notice appear in supporting documentation, and that the name of Sam
|
||||
# Rushing not be used in advertising or publicity pertaining to
|
||||
# distribution of the software without specific, written prior
|
||||
# permission.
|
||||
#
|
||||
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
||||
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
# ======================================================================
|
||||
|
||||
r"""A class supporting chat-style (command/response) protocols.
|
||||
|
||||
This class adds support for 'chat' style protocols - where one side
|
||||
sends a 'command', and the other sends a response (examples would be
|
||||
the common internet protocols - smtp, nntp, ftp, etc..).
|
||||
|
||||
The handle_read() method looks at the input stream for the current
|
||||
'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
|
||||
for multi-line output), calling self.found_terminator() on its
|
||||
receipt.
|
||||
|
||||
for example:
|
||||
Say you build an async nntp client using this class. At the start
|
||||
of the connection, you'll have self.terminator set to '\r\n', in
|
||||
order to process the single-line greeting. Just before issuing a
|
||||
'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
|
||||
command will be accumulated (using your own 'collect_incoming_data'
|
||||
method) up to the terminator, and then control will be returned to
|
||||
you - by calling your self.found_terminator() method.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import asyncore
|
||||
|
||||
class async_chat (asyncore.dispatcher):
|
||||
"""This is an abstract class. You must derive from this class, and add
|
||||
the two methods collect_incoming_data() and found_terminator()"""
|
||||
|
||||
# these are overridable defaults
|
||||
|
||||
ac_in_buffer_size = 4096
|
||||
ac_out_buffer_size = 4096
|
||||
|
||||
def __init__ (self, conn=None):
|
||||
self.ac_in_buffer = ''
|
||||
self.ac_out_buffer = ''
|
||||
self.producer_fifo = fifo()
|
||||
asyncore.dispatcher.__init__ (self, conn)
|
||||
|
||||
def set_terminator (self, term):
|
||||
"Set the input delimiter. Can be a fixed string of any length, an integer, or None"
|
||||
self.terminator = term
|
||||
|
||||
def get_terminator (self):
|
||||
return self.terminator
|
||||
|
||||
# grab some more data from the socket,
|
||||
# throw it to the collector method,
|
||||
# check for the terminator,
|
||||
# if found, transition to the next state.
|
||||
|
||||
def handle_read (self):
|
||||
|
||||
try:
|
||||
data = self.recv (self.ac_in_buffer_size)
|
||||
except socket.error, _:
|
||||
self.handle_error()
|
||||
return
|
||||
|
||||
self.ac_in_buffer = self.ac_in_buffer + data
|
||||
|
||||
# Continue to search for self.terminator in self.ac_in_buffer,
|
||||
# while calling self.collect_incoming_data. The while loop
|
||||
# is necessary because we might read several data+terminator
|
||||
# combos with a single recv(1024).
|
||||
|
||||
while self.ac_in_buffer:
|
||||
lb = len(self.ac_in_buffer)
|
||||
terminator = self.get_terminator()
|
||||
if terminator is None:
|
||||
# no terminator, collect it all
|
||||
self.collect_incoming_data (self.ac_in_buffer)
|
||||
self.ac_in_buffer = ''
|
||||
elif type(terminator) == type(0):
|
||||
# numeric terminator
|
||||
n = terminator
|
||||
if lb < n:
|
||||
self.collect_incoming_data (self.ac_in_buffer)
|
||||
self.ac_in_buffer = ''
|
||||
self.terminator = self.terminator - lb
|
||||
else:
|
||||
self.collect_incoming_data (self.ac_in_buffer[:n])
|
||||
self.ac_in_buffer = self.ac_in_buffer[n:]
|
||||
self.terminator = 0
|
||||
self.found_terminator()
|
||||
else:
|
||||
# 3 cases:
|
||||
# 1) end of buffer matches terminator exactly:
|
||||
# collect data, transition
|
||||
# 2) end of buffer matches some prefix:
|
||||
# collect data to the prefix
|
||||
# 3) end of buffer does not match any prefix:
|
||||
# collect data
|
||||
terminator_len = len(terminator)
|
||||
index = self.ac_in_buffer.find(terminator)
|
||||
if index != -1:
|
||||
# we found the terminator
|
||||
if index > 0:
|
||||
# don't bother reporting the empty string (source of subtle bugs)
|
||||
self.collect_incoming_data (self.ac_in_buffer[:index])
|
||||
self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
|
||||
# This does the Right Thing if the terminator is changed here.
|
||||
self.found_terminator()
|
||||
else:
|
||||
# check for a prefix of the terminator
|
||||
index = find_prefix_at_end (self.ac_in_buffer, terminator)
|
||||
if index:
|
||||
if index != lb:
|
||||
# we found a prefix, collect up to the prefix
|
||||
self.collect_incoming_data (self.ac_in_buffer[:-index])
|
||||
self.ac_in_buffer = self.ac_in_buffer[-index:]
|
||||
break
|
||||
else:
|
||||
# no prefix, collect it all
|
||||
self.collect_incoming_data (self.ac_in_buffer)
|
||||
self.ac_in_buffer = ''
|
||||
|
||||
def handle_write (self):
|
||||
self.initiate_send ()
|
||||
|
||||
def handle_close (self):
|
||||
self.close()
|
||||
|
||||
def push (self, data):
|
||||
self.producer_fifo.push (simple_producer (data))
|
||||
self.initiate_send()
|
||||
|
||||
def push_with_producer (self, producer):
|
||||
self.producer_fifo.push (producer)
|
||||
self.initiate_send()
|
||||
|
||||
def readable (self):
|
||||
"predicate for inclusion in the readable for select()"
|
||||
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
|
||||
|
||||
def writable (self):
|
||||
"predicate for inclusion in the writable for select()"
|
||||
# return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
|
||||
# this is about twice as fast, though not as clear.
|
||||
return not (
|
||||
(self.ac_out_buffer == '') and
|
||||
self.producer_fifo.is_empty() and
|
||||
self.connected
|
||||
)
|
||||
|
||||
def close_when_done (self):
|
||||
"automatically close this channel once the outgoing queue is empty"
|
||||
self.producer_fifo.push (None)
|
||||
|
||||
# refill the outgoing buffer by calling the more() method
|
||||
# of the first producer in the queue
|
||||
def refill_buffer (self):
|
||||
_string_type = type('')
|
||||
while 1:
|
||||
if len(self.producer_fifo):
|
||||
p = self.producer_fifo.first()
|
||||
# a 'None' in the producer fifo is a sentinel,
|
||||
# telling us to close the channel.
|
||||
if p is None:
|
||||
if not self.ac_out_buffer:
|
||||
self.producer_fifo.pop()
|
||||
self.close()
|
||||
return
|
||||
elif type(p) is _string_type:
|
||||
self.producer_fifo.pop()
|
||||
self.ac_out_buffer = self.ac_out_buffer + p
|
||||
return
|
||||
data = p.more()
|
||||
if data:
|
||||
self.ac_out_buffer = self.ac_out_buffer + data
|
||||
return
|
||||
else:
|
||||
self.producer_fifo.pop()
|
||||
else:
|
||||
return
|
||||
|
||||
def initiate_send (self):
|
||||
obs = self.ac_out_buffer_size
|
||||
# try to refill the buffer
|
||||
if (len (self.ac_out_buffer) < obs):
|
||||
self.refill_buffer()
|
||||
|
||||
if self.ac_out_buffer and self.connected:
|
||||
# try to send the buffer
|
||||
try:
|
||||
num_sent = self.send (self.ac_out_buffer[:obs])
|
||||
if num_sent:
|
||||
self.ac_out_buffer = self.ac_out_buffer[num_sent:]
|
||||
|
||||
except socket.error, _:
|
||||
self.handle_error()
|
||||
return
|
||||
|
||||
def discard_buffers (self):
|
||||
# Emergencies only!
|
||||
self.ac_in_buffer = ''
|
||||
self.ac_out_buffer = ''
|
||||
while self.producer_fifo:
|
||||
self.producer_fifo.pop()
|
||||
|
||||
|
||||
class simple_producer:
|
||||
|
||||
def __init__ (self, data, buffer_size=512):
|
||||
self.data = data
|
||||
self.buffer_size = buffer_size
|
||||
|
||||
def more (self):
|
||||
if len (self.data) > self.buffer_size:
|
||||
result = self.data[:self.buffer_size]
|
||||
self.data = self.data[self.buffer_size:]
|
||||
return result
|
||||
else:
|
||||
result = self.data
|
||||
self.data = ''
|
||||
return result
|
||||
|
||||
class fifo:
|
||||
def __init__ (self, list=None):
|
||||
if not list:
|
||||
self.list = []
|
||||
else:
|
||||
self.list = list
|
||||
|
||||
def __len__ (self):
|
||||
return len(self.list)
|
||||
|
||||
def is_empty (self):
|
||||
return self.list == []
|
||||
|
||||
def first (self):
|
||||
return self.list[0]
|
||||
|
||||
def push (self, data):
|
||||
self.list.append (data)
|
||||
|
||||
def pop (self):
|
||||
if self.list:
|
||||
result = self.list[0]
|
||||
del self.list[0]
|
||||
return (1, result)
|
||||
else:
|
||||
return (0, None)
|
||||
|
||||
# Given 'haystack', see if any prefix of 'needle' is at its end. This
|
||||
# assumes an exact match has already been checked. Return the number of
|
||||
# characters matched.
|
||||
# for example:
|
||||
# f_p_a_e ("qwerty\r", "\r\n") => 1
|
||||
# f_p_a_e ("qwerty\r\n", "\r\n") => 2
|
||||
# f_p_a_e ("qwertydkjf", "\r\n") => 0
|
||||
|
||||
# this could maybe be made faster with a computed regex?
|
||||
# [answer: no; circa Python-2.0, Jan 2001]
|
||||
# python: 18307/s
|
||||
# re: 12820/s
|
||||
# regex: 14035/s
|
||||
|
||||
def find_prefix_at_end (haystack, needle):
|
||||
nl = len(needle)
|
||||
result = 0
|
||||
for i in range (1,nl):
|
||||
if haystack[-(nl-i):] == needle[:(nl-i)]:
|
||||
result = nl-i
|
||||
break
|
||||
return result
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
494
others/asyncore.py
Normal file
494
others/asyncore.py
Normal file
@ -0,0 +1,494 @@
|
||||
# -*- Mode: Python -*-
|
||||
# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
|
||||
# Author: Sam Rushing <rushing@nightmare.com>
|
||||
|
||||
# ======================================================================
|
||||
# Copyright 1996 by Sam Rushing
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its documentation for any purpose and without fee is hereby
|
||||
# granted, provided that the above copyright notice appear in all
|
||||
# copies and that both that copyright notice and this permission
|
||||
# notice appear in supporting documentation, and that the name of Sam
|
||||
# Rushing not be used in advertising or publicity pertaining to
|
||||
# distribution of the software without specific, written prior
|
||||
# permission.
|
||||
#
|
||||
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
||||
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
# ======================================================================
|
||||
|
||||
"""Basic infrastructure for asynchronous socket service clients and servers.
|
||||
|
||||
There are only two ways to have a program on a single processor do "more
|
||||
than one thing at a time". Multi-threaded programming is the simplest and
|
||||
most popular way to do it, but there is another very different technique,
|
||||
that lets you have nearly all the advantages of multi-threading, without
|
||||
actually using multiple threads. it's really only practical if your program
|
||||
is largely I/O bound. If your program is CPU bound, then pre-emptive
|
||||
scheduled threads are probably what you really need. Network servers are
|
||||
rarely CPU-bound, however.
|
||||
|
||||
If your operating system supports the select() system call in its I/O
|
||||
library (and nearly all do), then you can use it to juggle multiple
|
||||
communication channels at once; doing other work while your I/O is taking
|
||||
place in the "background." Although this strategy can seem strange and
|
||||
complex, especially at first, it is in many ways easier to understand and
|
||||
control than multi-threaded programming. The module documented here solves
|
||||
many of the difficult problems for you, making the task of building
|
||||
sophisticated high-performance network servers and clients a snap.
|
||||
"""
|
||||
|
||||
import exceptions
|
||||
import select
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
import os
|
||||
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
|
||||
ENOTCONN, ESHUTDOWN, EINTR, EISCONN
|
||||
|
||||
try:
|
||||
socket_map
|
||||
except NameError:
|
||||
socket_map = {}
|
||||
|
||||
class ExitNow(exceptions.Exception):
|
||||
pass
|
||||
|
||||
def read(obj):
|
||||
try:
|
||||
obj.handle_read_event()
|
||||
except ExitNow:
|
||||
raise
|
||||
except:
|
||||
obj.handle_error()
|
||||
|
||||
def write(obj):
|
||||
try:
|
||||
obj.handle_write_event()
|
||||
except ExitNow:
|
||||
raise
|
||||
except:
|
||||
obj.handle_error()
|
||||
|
||||
def readwrite(obj, flags):
|
||||
try:
|
||||
if flags & select.POLLIN:
|
||||
obj.handle_read_event()
|
||||
if flags & select.POLLOUT:
|
||||
obj.handle_write_event()
|
||||
except ExitNow:
|
||||
raise
|
||||
except:
|
||||
obj.handle_error()
|
||||
|
||||
def poll(timeout=0.0, map=None):
|
||||
if map is None:
|
||||
map = socket_map
|
||||
if map:
|
||||
r = []; w = []; e = []
|
||||
for fd, obj in map.items():
|
||||
if obj.readable():
|
||||
r.append(fd)
|
||||
if obj.writable():
|
||||
w.append(fd)
|
||||
if [] == r == w == e:
|
||||
time.sleep(timeout)
|
||||
else:
|
||||
try:
|
||||
r, w, e = select.select(r, w, e, timeout)
|
||||
except select.error, err:
|
||||
if err[0] != EINTR:
|
||||
raise
|
||||
else:
|
||||
return
|
||||
|
||||
for fd in r:
|
||||
obj = map.get(fd)
|
||||
if obj is None:
|
||||
continue
|
||||
read(obj)
|
||||
|
||||
for fd in w:
|
||||
obj = map.get(fd)
|
||||
if obj is None:
|
||||
continue
|
||||
write(obj)
|
||||
|
||||
def poll2(timeout=0.0, map=None):
|
||||
import poll
|
||||
if map is None:
|
||||
map = socket_map
|
||||
if timeout is not None:
|
||||
# timeout is in milliseconds
|
||||
timeout = int(timeout*1000)
|
||||
if map:
|
||||
l = []
|
||||
for fd, obj in map.items():
|
||||
flags = 0
|
||||
if obj.readable():
|
||||
flags = poll.POLLIN
|
||||
if obj.writable():
|
||||
flags = flags | poll.POLLOUT
|
||||
if flags:
|
||||
l.append((fd, flags))
|
||||
r = poll.poll(l, timeout)
|
||||
for fd, flags in r:
|
||||
obj = map.get(fd)
|
||||
if obj is None:
|
||||
continue
|
||||
readwrite(obj, flags)
|
||||
|
||||
def poll3(timeout=0.0, map=None):
|
||||
# Use the poll() support added to the select module in Python 2.0
|
||||
if map is None:
|
||||
map = socket_map
|
||||
if timeout is not None:
|
||||
# timeout is in milliseconds
|
||||
timeout = int(timeout*1000)
|
||||
pollster = select.poll()
|
||||
if map:
|
||||
for fd, obj in map.items():
|
||||
flags = 0
|
||||
if obj.readable():
|
||||
flags = select.POLLIN
|
||||
if obj.writable():
|
||||
flags = flags | select.POLLOUT
|
||||
if flags:
|
||||
pollster.register(fd, flags)
|
||||
try:
|
||||
r = pollster.poll(timeout)
|
||||
except select.error, err:
|
||||
if err[0] != EINTR:
|
||||
raise
|
||||
r = []
|
||||
for fd, flags in r:
|
||||
obj = map.get(fd)
|
||||
if obj is None:
|
||||
continue
|
||||
readwrite(obj, flags)
|
||||
|
||||
def loop(timeout=30.0, use_poll=0, map=None):
|
||||
if map is None:
|
||||
map = socket_map
|
||||
|
||||
if use_poll:
|
||||
if hasattr(select, 'poll'):
|
||||
poll_fun = poll3
|
||||
else:
|
||||
poll_fun = poll2
|
||||
else:
|
||||
poll_fun = poll
|
||||
|
||||
while map:
|
||||
poll_fun(timeout, map)
|
||||
|
||||
class dispatcher:
|
||||
|
||||
debug = 0
|
||||
connected = 0
|
||||
accepting = 0
|
||||
closing = 0
|
||||
addr = None
|
||||
|
||||
def __init__(self, sock=None, map=None):
|
||||
if sock:
|
||||
self.set_socket(sock, map)
|
||||
# I think it should inherit this anyway
|
||||
self.socket.setblocking(0)
|
||||
self.connected = 1
|
||||
# XXX Does the constructor require that the socket passed
|
||||
# be connected?
|
||||
try:
|
||||
self.addr = sock.getpeername()
|
||||
except socket.error:
|
||||
# The addr isn't crucial
|
||||
pass
|
||||
else:
|
||||
self.socket = None
|
||||
|
||||
def __repr__(self):
|
||||
status = [self.__class__.__module__+"."+self.__class__.__name__]
|
||||
if self.accepting and self.addr:
|
||||
status.append('listening')
|
||||
elif self.connected:
|
||||
status.append('connected')
|
||||
if self.addr is not None:
|
||||
try:
|
||||
status.append('%s:%d' % self.addr)
|
||||
except TypeError:
|
||||
status.append(repr(self.addr))
|
||||
return '<%s at %#x>' % (' '.join(status), id(self))
|
||||
|
||||
def add_channel(self, map=None):
|
||||
#self.log_info('adding channel %s' % self)
|
||||
if map is None:
|
||||
map = socket_map
|
||||
map[self._fileno] = self
|
||||
|
||||
def del_channel(self, map=None):
|
||||
fd = self._fileno
|
||||
if map is None:
|
||||
map = socket_map
|
||||
if map.has_key(fd):
|
||||
#self.log_info('closing channel %d:%s' % (fd, self))
|
||||
del map[fd]
|
||||
|
||||
def create_socket(self, family, type):
|
||||
self.family_and_type = family, type
|
||||
self.socket = socket.socket(family, type)
|
||||
self.socket.setblocking(0)
|
||||
self._fileno = self.socket.fileno()
|
||||
self.add_channel()
|
||||
|
||||
def set_socket(self, sock, map=None):
|
||||
self.socket = sock
|
||||
## self.__dict__['socket'] = sock
|
||||
self._fileno = sock.fileno()
|
||||
self.add_channel(map)
|
||||
|
||||
def set_reuse_addr(self):
|
||||
# try to re-use a server port if possible
|
||||
try:
|
||||
self.socket.setsockopt(
|
||||
socket.SOL_SOCKET, socket.SO_REUSEADDR,
|
||||
self.socket.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR) | 1
|
||||
)
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
# ==================================================
|
||||
# predicates for select()
|
||||
# these are used as filters for the lists of sockets
|
||||
# to pass to select().
|
||||
# ==================================================
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
if os.name == 'mac':
|
||||
# The macintosh will select a listening socket for
|
||||
# write if you let it. What might this mean?
|
||||
def writable(self):
|
||||
return not self.accepting
|
||||
else:
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
# ==================================================
|
||||
# socket object methods.
|
||||
# ==================================================
|
||||
|
||||
def listen(self, num):
|
||||
self.accepting = 1
|
||||
if os.name == 'nt' and num > 5:
|
||||
num = 1
|
||||
return self.socket.listen(num)
|
||||
|
||||
def bind(self, addr):
|
||||
self.addr = addr
|
||||
return self.socket.bind(addr)
|
||||
|
||||
def connect(self, address):
|
||||
self.connected = 0
|
||||
err = self.socket.connect_ex(address)
|
||||
# XXX Should interpret Winsock return values
|
||||
if err in (EINPROGRESS, EALREADY, EWOULDBLOCK):
|
||||
return
|
||||
if err in (0, EISCONN):
|
||||
self.addr = address
|
||||
self.connected = 1
|
||||
self.handle_connect()
|
||||
else:
|
||||
raise socket.error, err
|
||||
|
||||
def accept(self):
|
||||
# XXX can return either an address pair or None
|
||||
try:
|
||||
conn, addr = self.socket.accept()
|
||||
return conn, addr
|
||||
except socket.error, why:
|
||||
if why[0] == EWOULDBLOCK:
|
||||
pass
|
||||
else:
|
||||
raise socket.error, why
|
||||
|
||||
def send(self, data):
|
||||
try:
|
||||
result = self.socket.send(data)
|
||||
return result
|
||||
except socket.error, why:
|
||||
if why[0] == EWOULDBLOCK:
|
||||
return 0
|
||||
else:
|
||||
raise socket.error, why
|
||||
return 0
|
||||
|
||||
def recv(self, buffer_size):
|
||||
try:
|
||||
data = self.socket.recv(buffer_size)
|
||||
if not data:
|
||||
# a closed connection is indicated by signaling
|
||||
# a read condition, and having recv() return 0.
|
||||
self.handle_close()
|
||||
return ''
|
||||
else:
|
||||
return data
|
||||
except socket.error, why:
|
||||
# winsock sometimes throws ENOTCONN
|
||||
if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
|
||||
self.handle_close()
|
||||
return ''
|
||||
else:
|
||||
raise socket.error, why
|
||||
|
||||
def close(self):
|
||||
self.del_channel()
|
||||
self.socket.close()
|
||||
|
||||
# cheap inheritance, used to pass all other attribute
|
||||
# references to the underlying socket object.
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.socket, attr)
|
||||
|
||||
# log and log_info may be overridden to provide more sophisticated
|
||||
# logging and warning methods. In general, log is for 'hit' logging
|
||||
# and 'log_info' is for informational, warning and error logging.
|
||||
|
||||
def log(self, message):
|
||||
sys.stderr.write('log: %s\n' % str(message))
|
||||
|
||||
def log_info(self, message, type='info'):
|
||||
if __debug__ or type != 'info':
|
||||
print '%s: %s' % (type, message)
|
||||
|
||||
def handle_read_event(self):
|
||||
if self.accepting:
|
||||
# for an accepting socket, getting a read implies
|
||||
# that we are connected
|
||||
if not self.connected:
|
||||
self.connected = 1
|
||||
self.handle_accept()
|
||||
elif not self.connected:
|
||||
self.handle_connect()
|
||||
self.connected = 1
|
||||
self.handle_read()
|
||||
else:
|
||||
self.handle_read()
|
||||
|
||||
def handle_write_event(self):
|
||||
# getting a write implies that we are connected
|
||||
if not self.connected:
|
||||
self.handle_connect()
|
||||
self.connected = 1
|
||||
self.handle_write()
|
||||
|
||||
def handle_expt_event(self):
|
||||
self.handle_expt()
|
||||
|
||||
def handle_error(self):
|
||||
nil, t, v, tbinfo = compact_traceback()
|
||||
|
||||
# sometimes a user repr method will crash.
|
||||
try:
|
||||
self_repr = repr(self)
|
||||
except:
|
||||
self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
|
||||
|
||||
self.log_info(
|
||||
'uncaptured python exception, closing channel %s (%s:%s %s)' % (
|
||||
self_repr,
|
||||
t,
|
||||
v,
|
||||
tbinfo
|
||||
),
|
||||
'error'
|
||||
)
|
||||
self.close()
|
||||
|
||||
def handle_expt(self):
|
||||
self.log_info('unhandled exception', 'warning')
|
||||
|
||||
def handle_read(self):
|
||||
self.log_info('unhandled read event', 'warning')
|
||||
|
||||
def handle_write(self):
|
||||
self.log_info('unhandled write event', 'warning')
|
||||
|
||||
def handle_connect(self):
|
||||
self.log_info('unhandled connect event', 'warning')
|
||||
|
||||
def handle_accept(self):
|
||||
self.log_info('unhandled accept event', 'warning')
|
||||
|
||||
def handle_close(self):
|
||||
self.log_info('unhandled close event', 'warning')
|
||||
self.close()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# adds simple buffered output capability, useful for simple clients.
|
||||
# [for more sophisticated usage use asynchat.async_chat]
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class dispatcher_with_send(dispatcher):
|
||||
|
||||
def __init__(self, sock=None):
|
||||
dispatcher.__init__(self, sock)
|
||||
self.out_buffer = ''
|
||||
|
||||
def initiate_send(self):
|
||||
num_sent = 0
|
||||
num_sent = dispatcher.send(self, self.out_buffer[:512])
|
||||
self.out_buffer = self.out_buffer[num_sent:]
|
||||
|
||||
def handle_write(self):
|
||||
self.initiate_send()
|
||||
|
||||
def writable(self):
|
||||
return (not self.connected) or len(self.out_buffer)
|
||||
|
||||
def send(self, data):
|
||||
if self.debug:
|
||||
self.log_info('sending %s' % repr(data))
|
||||
self.out_buffer = self.out_buffer + data
|
||||
self.initiate_send()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# used for debugging.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def compact_traceback():
|
||||
t, v, tb = sys.exc_info()
|
||||
tbinfo = []
|
||||
assert tb # Must have a traceback
|
||||
while tb:
|
||||
tbinfo.append((
|
||||
tb.tb_frame.f_code.co_filename,
|
||||
tb.tb_frame.f_code.co_name,
|
||||
str(tb.tb_lineno)
|
||||
))
|
||||
tb = tb.tb_next
|
||||
|
||||
# just to be safe
|
||||
del tb
|
||||
|
||||
file, function, line = tbinfo[-1]
|
||||
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
|
||||
return (file, function, line), t, v, info
|
||||
|
||||
def close_all(map=None):
|
||||
if map is None:
|
||||
map = socket_map
|
||||
for x in map.values():
|
||||
x.socket.close()
|
||||
map.clear()
|
179
others/babelfish.py
Normal file
179
others/babelfish.py
Normal file
@ -0,0 +1,179 @@
|
||||
# babelizer.py - API for simple access to babelfish.altavista.com.
|
||||
# Requires python 2.0 or better.
|
||||
#
|
||||
# See it in use at http://babel.MrFeinberg.com/
|
||||
|
||||
"""API for simple access to babelfish.altavista.com.
|
||||
|
||||
Summary:
|
||||
|
||||
import babelizer
|
||||
|
||||
print ' '.join(babelizer.available_languages)
|
||||
|
||||
print babelizer.translate( 'How much is that doggie in the window?',
|
||||
'English', 'French' )
|
||||
|
||||
def babel_callback(phrase):
|
||||
print phrase
|
||||
sys.stdout.flush()
|
||||
|
||||
babelizer.babelize( 'I love a reigning knight.',
|
||||
'English', 'German',
|
||||
callback = babel_callback )
|
||||
|
||||
available_languages
|
||||
A list of languages available for use with babelfish.
|
||||
|
||||
translate( phrase, from_lang, to_lang )
|
||||
Uses babelfish to translate phrase from from_lang to to_lang.
|
||||
|
||||
babelize(phrase, from_lang, through_lang, limit = 12, callback = None)
|
||||
Uses babelfish to translate back and forth between from_lang and
|
||||
through_lang until either no more changes occur in translation or
|
||||
limit iterations have been reached, whichever comes first. Takes
|
||||
an optional callback function which should receive a single
|
||||
parameter, being the next translation. Without the callback
|
||||
returns a list of successive translations.
|
||||
|
||||
It's only guaranteed to work if 'english' is one of the two languages
|
||||
given to either of the translation methods.
|
||||
|
||||
Both translation methods throw exceptions which are all subclasses of
|
||||
BabelizerError. They include
|
||||
|
||||
LanguageNotAvailableError
|
||||
Thrown on an attempt to use an unknown language.
|
||||
|
||||
BabelfishChangedError
|
||||
Thrown when babelfish.altavista.com changes some detail of their
|
||||
layout, and babelizer can no longer parse the results or submit
|
||||
the correct form (a not infrequent occurance).
|
||||
|
||||
BabelizerIOError
|
||||
Thrown for various networking and IO errors.
|
||||
|
||||
Version: $Id$
|
||||
Author: Jonathan Feinberg <jdf@pobox.com>
|
||||
"""
|
||||
import re, string, urllib
|
||||
|
||||
"""
|
||||
Various patterns I have encountered in looking for the babelfish result.
|
||||
We try each of them in turn, based on the relative number of times I've
|
||||
seen each of these patterns. $1.00 to anyone who can provide a heuristic
|
||||
for knowing which one to use. This includes AltaVista employees.
|
||||
"""
|
||||
__where = [ re.compile(r'lang=..>([^<]*)</div'),
|
||||
re.compile(r'name=\"q\" value=\"([^\"]*)\">'),
|
||||
re.compile(r'div style=padding:10px;>([^<]+)</div'),
|
||||
]
|
||||
|
||||
__languages = { 'english' : 'en',
|
||||
'chinese_simple' : 'zh',
|
||||
'chinese_traditional' : 'zt',
|
||||
'french' : 'fr',
|
||||
'german' : 'de',
|
||||
'italian' : 'it',
|
||||
'japanese' : 'ja',
|
||||
'korean' : 'ko',
|
||||
'spanish' : 'es',
|
||||
'portuguese' : 'pt',
|
||||
'russian' : 'ru',
|
||||
'greek' : 'el',
|
||||
'dutch' : 'nl',
|
||||
}
|
||||
|
||||
"""
|
||||
All of the available language names.
|
||||
"""
|
||||
available_languages = [ x.title() for x in __languages.keys() ]
|
||||
|
||||
"""
|
||||
Calling translate() or babelize() can raise a BabelizerError
|
||||
"""
|
||||
class BabelizerError(Exception):
|
||||
pass
|
||||
|
||||
class LanguageNotAvailableError(BabelizerError):
|
||||
pass
|
||||
class BabelfishChangedError(BabelizerError):
|
||||
pass
|
||||
class BabelizerIOError(BabelizerError):
|
||||
pass
|
||||
|
||||
def clean(text):
|
||||
return ' '.join(string.replace(text.strip(), "\n", ' ').split())
|
||||
|
||||
def translate(phrase, from_lang, to_lang):
|
||||
phrase = clean(phrase)
|
||||
try:
|
||||
from_code = __languages[from_lang.lower()]
|
||||
to_code = __languages[to_lang.lower()]
|
||||
except KeyError, lang:
|
||||
raise LanguageNotAvailableError(lang)
|
||||
|
||||
params = urllib.urlencode( { 'BabelFishFrontPage' : 'yes',
|
||||
'doit' : 'done',
|
||||
'tt' : 'urltext',
|
||||
'intl' : '1',
|
||||
'urltext' : phrase,
|
||||
'lp' : from_code + '_' + to_code } )
|
||||
try:
|
||||
response = urllib.urlopen('http://babelfish.altavista.com/babelfish/tr', params)
|
||||
except IOError, what:
|
||||
raise BabelizerIOError("Couldn't talk to server: %s" % what)
|
||||
except:
|
||||
print "Unexpected error:", sys.exc_info()[0]
|
||||
|
||||
html = response.read()
|
||||
try:
|
||||
begin = html.index('<!-- Target text (content) -->')
|
||||
end = html.index('<!-- end: Target text (content) -->')
|
||||
html = html[begin:end]
|
||||
except ValueError:
|
||||
pass
|
||||
for regex in __where:
|
||||
match = regex.search(html)
|
||||
if match:
|
||||
break
|
||||
if not match:
|
||||
raise BabelfishChangedError("Can't recognize translated string.")
|
||||
return clean(match.group(1))
|
||||
|
||||
def babelize(phrase, from_language, through_language, limit = 12, callback = None):
|
||||
phrase = clean(phrase)
|
||||
seen = { phrase: 1 }
|
||||
results = []
|
||||
if callback:
|
||||
def_callback = callback
|
||||
else:
|
||||
def_callback = results.append
|
||||
def_callback(phrase)
|
||||
flip = { from_language: through_language, through_language: from_language }
|
||||
next = from_language
|
||||
for i in range(limit):
|
||||
phrase = translate(phrase, next, flip[next])
|
||||
if seen.has_key(phrase):
|
||||
break
|
||||
seen[phrase] = 1
|
||||
def_callback(phrase)
|
||||
next = flip[next]
|
||||
# next is set to the language of the last entry. this should be the same
|
||||
# as the language we are translating to
|
||||
if next != through_language:
|
||||
phrase = translate(phrase, next, flip[next])
|
||||
def_callback(phrase)
|
||||
if not callback:
|
||||
return results
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
def printer(x):
|
||||
print x
|
||||
sys.stdout.flush();
|
||||
|
||||
|
||||
babelize("I won't take that sort of treatment from you, or from your doggie!",
|
||||
'english', 'french', callback = printer)
|
||||
|
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