This section covers technical details of how clans works, in more detail than you probably want to know. It might be useful if you are interested in contributing to the clans source, developing an extension, or writing some other application that will use clans as a library.
Clans’ extension framework is based on hooks - named points in the execution of various commands where it stops to call other code. Extensions are Python modules that define functions with names matching one or more of these hooks.
Hooks all accept clans’ controller object,
ClansSession, as the
first argument. Most are also passed a number of other arguments,
depending on the context. For example, the
post_search hook is
ClansSession as well as the
results of that search.
To enable a clans extension that you write yourself, make sure the
module is on Python’s module search path, so that it’s importable via
import my_clans_extension. Then, in
clans.cfg, add it to the
On the left side of the equal sign is a casual name for your extension, and the right should be its importable name, using Python dot-syntax if necessary.
Some extensions are packaged with clans, like
and for these it is unnecessary to specify the full importable path.
It is worth looking at the included
clans.ext.example extension to
get an idea of how to write your own.
I think I’ve implemented this in a moderately intelligent way, but the hook API should not be considered stable prior to clans 1.0.
List of Hooks¶
Each hook accepts one or more (usually mutable) arguments, and need not return anything. Typically, arguments can be modified in-place or left untouched.
The first argument passed is always the
This hook is called during plan editing, after the edit text has been retrieved from the server.
The edit text is passed as an immutable unicode string as the second argument.
If this hook returns any value other than
None, clans will skip interactive editing.
This hook is called right after the standard commands and arguments are defined.
This hook is a good place to add arguments or subcommands to the command table, which you can do by modifying the
For example, to add an argument to an existing command:
cs.commands['love'].add_argument( '-t', '--time', dest='time', action='store_true', default=False, help="Order results by time first seen.")
or, to add a whole new command:
cs.commands.add_command( 'secrets', secrets, parents=[global_parser], description='Glimpse into the souls of others.', help='View secrets.')
secretsis a function you define elsewhere in your extension.
This hook is called after a (quicklove or regular) search, and is passed a list containing the results.
Elements of this list are 3-tuples:
- the name of the plan on which the term was found (str)
- the number of instances found (int)
- a list of snippets.
Note that the snippet list may not be the same length as the number of instances found.
Lists are mutable, so results may be filtered by modifying this list in-place.
pre_search(cs, term, planlove)¶
This hook is called before a (quicklove or regular) search, and is passed the same arguments as is the search function:
- the search
planlove, a boolean of whether to restrict search to planlove.
- the search
This hook is called during plan editing, after the edit text has been modified, but before being submitted to the server.
The modified edit text is passed as an immutable unicode string as the second argument.
Plans does not have a complete API, so clans utilizes a Plans-specific scraping library to communicate with the Plans server. This is packaged as a separate sub-module so that it can be used in other Python programs independent of clans.
Code changes on the server side could break the scrAPI at any time, so it should not be considered in any way stable.
The entire thing is built around one class,
Additionally there is the
They can be imported like so:
from clans.scraper import PlansConnection, PlansError
Then we can instantiate a
PlansConnection, log into Plans, and begin
pc = PlansConnection() pc.plans_login('baldwint', 'not_my_password_lol') pc.read_plan('gorp')
PlansConnection(cookiejar=None, base_url='https://www.grinnellplans.com', server_tz='US/Central')¶
Encapsulates an active login to plans.
Retrieve all levels of the autofinger (autoread) list.
Returns a dictionary where the keys are the group names “Level 1”, “Level 2”, etc. and the values are a list of usernames waiting to be read.
Retrieve contents of the edit plan field.
Returns the edit_text of the plan and its md5 hash, as computed on the server side.
Log into plans.
Returns True on success, False on failure. Leave username and password blank to check an existing login.
Return plans updated in the last
The result is a list of (username, timestamp) 2-tuples.
Retrieve the contents of the specified plan.
- Returns two objects: the plan header (as a python dictionary)
- the plan text (in HTML format)
Search plans for the provided
termis a username, and the search will be for incidences of planlove for that user.
returns: list of plans upon which the search term was found. each list element is a 3-tuple:
- plan name
- number of occurrences of search term on the plan
- list of plan excerpts giving context
the length of the excerpt list may be equal to or less than the number of occurrences of the search term, since overlapping excerpts are consolidated.
Update plan with new content.
To prevent errors, the server does a hash check on the existing plan before replacing it with the new one. We provide an md5 sum to confirm that yes, we really want to update the plan.
Returns info message.
Exception raised when there is an error talking to plans.