mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-23 11:12:47 +01:00
More updating Docbook stuff
This commit is contained in:
parent
a4b3d66748
commit
b5d7045359
@ -36,6 +36,11 @@
|
|||||||
<revnumber>0.4</revnumber>
|
<revnumber>0.4</revnumber>
|
||||||
<date>26 Feb 2004</date>
|
<date>26 Feb 2004</date>
|
||||||
<revremark>Converted to use Supybot DTD</revremark>
|
<revremark>Converted to use Supybot DTD</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.5</revnumber>
|
||||||
|
<date>4 Sep 2004</date>
|
||||||
|
<revremark>Updated Docbook translation</revremark>
|
||||||
</revision>
|
</revision>
|
||||||
</revhistory>
|
</revhistory>
|
||||||
</articleinfo>
|
</articleinfo>
|
||||||
@ -56,7 +61,6 @@
|
|||||||
details.
|
details.
|
||||||
</para>
|
</para>
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
<sect1>
|
<sect1>
|
||||||
<title>Creating your own plugin</title>
|
<title>Creating your own plugin</title>
|
||||||
<sect2>
|
<sect2>
|
||||||
@ -105,7 +109,7 @@ functor%
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
###
|
###
|
||||||
# Copyright (c) 2002, Jeremiah Fincher
|
# Copyright (c) 2004, Jeremiah Fincher
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without
|
# Redistribution and use in source and binary forms, with or without
|
||||||
@ -137,20 +141,24 @@ functor%
|
|||||||
Add the module docstring here. This will be used by the setup.py script.
|
Add the module docstring here. This will be used by the setup.py script.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from baseplugin import *
|
__revision__ = "$Id$"
|
||||||
|
__author__ = ''
|
||||||
|
|
||||||
import utils
|
import supybot.plugins as plugins
|
||||||
import privmsgs
|
|
||||||
import callbacks
|
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):
|
def configure(onStart, afterConnect, advanced):
|
||||||
# This will be called by setup.py to configure this module. onStart and
|
# This will be called by setup.py to configure this module. Advanced is
|
||||||
# afterConnect are both lists. Append to onStart the commands you would
|
# a bool that specifies whether the user identified himself as an advanced
|
||||||
# like to be run when the bot is started; append to afterConnect the
|
# user or not. You should effect your configuration by manipulating the
|
||||||
# commands you would like to be run when the bot has finished connecting.
|
# registry as appropriate.
|
||||||
from questions import expect, anything, something, yn
|
from questions import expect, anything, something, yn
|
||||||
onStart.append('load Random')
|
conf.registerPlugin('Random', True)
|
||||||
|
|
||||||
class Random(callbacks.Privmsg):
|
class Random(callbacks.Privmsg):
|
||||||
pass
|
pass
|
||||||
@ -193,10 +201,8 @@ Class = Random
|
|||||||
Then you see a <function>configure</function> function. This
|
Then you see a <function>configure</function> function. This
|
||||||
the function that's called when users decide to add your
|
the function that's called when users decide to add your
|
||||||
module in <script>scripts/setup.py</script>. You'll
|
module in <script>scripts/setup.py</script>. You'll
|
||||||
note that by default it simply adds <literal>"load
|
note that by default it simply registers the plugin to be
|
||||||
Example"</literal> (where 'Example' is the name you provided
|
automatically loaded on startup. For many
|
||||||
as the name of your plugin, so in our case it is
|
|
||||||
<literal>"load Random"</literal>) at the bottom. For many
|
|
||||||
plugins this is all you need; for more complex plugins, you
|
plugins this is all you need; for more complex plugins, you
|
||||||
might need to ask questions and add commands based on the
|
might need to ask questions and add commands based on the
|
||||||
answers.
|
answers.
|
||||||
@ -248,10 +254,8 @@ def __init__(self):
|
|||||||
any arguments (other than <varname>self</varname>, of course).
|
any arguments (other than <varname>self</varname>, of course).
|
||||||
There's no way anything will ever get to them! If you have
|
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
|
some sort of initial values you need to get to your plugin
|
||||||
before it can do anything interesting, add a command that gets
|
before it can do anything interesting, you should get those
|
||||||
those values. By convention, those commands begin with
|
values from the registry.
|
||||||
"start" -- check out the Relay and Enforcer plugins for
|
|
||||||
examples of such commands.
|
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
There's an easier way to get our plugin to have its own rng
|
There's an easier way to get our plugin to have its own rng
|
||||||
@ -285,7 +289,7 @@ def __init__(self):
|
|||||||
Returns the next random number generated by the random number
|
Returns the next random number generated by the random number
|
||||||
generator.
|
generator.
|
||||||
"""
|
"""
|
||||||
irc.reply(msg, str(self.rng.random()))
|
irc.reply(str(self.rng.random()))
|
||||||
</programlisting>
|
</programlisting>
|
||||||
<para>
|
<para>
|
||||||
And that's it! Pretty simple, huh? Anyway, you're probably
|
And that's it! Pretty simple, huh? Anyway, you're probably
|
||||||
@ -297,7 +301,7 @@ def __init__(self):
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
<para>
|
<para>
|
||||||
What that does is define a command
|
What that does is define a command
|
||||||
<function>random</function>. You can call it by saying
|
<botcommand>random</botcommand>. You can call it by saying
|
||||||
"@random" (or whatever prefix character your specific bot
|
"@random" (or whatever prefix character your specific bot
|
||||||
uses). The arguments are a bit less obvious.
|
uses). The arguments are a bit less obvious.
|
||||||
<varname>self</varname> is self-evident (hah!).
|
<varname>self</varname> is self-evident (hah!).
|
||||||
@ -308,22 +312,20 @@ def __init__(self):
|
|||||||
(with the exception of calling <function>irc.reply</function>
|
(with the exception of calling <function>irc.reply</function>
|
||||||
or <function>irc.error</function>). What you're
|
or <function>irc.error</function>). What you're
|
||||||
<emphasis>really</emphasis> interested in is the
|
<emphasis>really</emphasis> interested in is the
|
||||||
<varname>args</varname> arg. That if a list of all the
|
<varname>args</varname> arg. That is a list of all the
|
||||||
arguments passed to your command, pre-parsed and already
|
arguments passed to your command, pre-parsed and already
|
||||||
evaluated (i.e., you never have to worry about nested
|
evaluated (i.e., you never have to worry about nested
|
||||||
commands, or handling double quoted strings, or splitting on
|
commands, or handling double quoted strings, or splitting on
|
||||||
whitespace -- the work has already been done for you). You
|
whitespace – the work has already been done for you).
|
||||||
can read about the <classname>Irc</classname> object in
|
You can read about the <classname>Irc</classname> object in
|
||||||
<filename>irclib.py</filename> (you won't find
|
<filename>irclib.py</filename> (you won't find
|
||||||
<function>.reply</function> or <function>.error</function>
|
<function>.reply</function> or <function>.error</function>
|
||||||
there, though, because you're actually getting an
|
there, though, because you're actually getting an
|
||||||
<classname>IrcObjectProxy</classname>, but that's beyond the
|
<classname>IrcObjectProxy</classname>, but that's beyond the
|
||||||
level we want to describe here :)). You can read about the
|
level we want to describe here :)). You can read about the
|
||||||
<varname>msg</varname> object in
|
<varname>msg</varname> object in
|
||||||
<filename>ircmsgs.py</filename>. But again, aside from
|
<filename>ircmsgs.py</filename>. But again, you'll very
|
||||||
calling <function>irc.reply</function> or
|
rarely be using these objects.
|
||||||
<function>irc.error</function>, you'll very rarely be using
|
|
||||||
these objects.
|
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
(In case you're curious, the answer is yes, you
|
(In case you're curious, the answer is yes, you
|
||||||
@ -348,39 +350,28 @@ def __init__(self):
|
|||||||
this is what a supybot does:
|
this is what a supybot does:
|
||||||
</para>
|
</para>
|
||||||
<ircsession>
|
<ircsession>
|
||||||
<angryman> jemfinch: random takes no arguments (for more help
|
<jemfinch> @help random
|
||||||
use the morehelp command)
|
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||||
<jemfinch> $morehelp random
|
next random number from the random number generator.
|
||||||
<angryman> jemfinch: Returns the next random number from the
|
|
||||||
current random number generator.
|
|
||||||
</ircsession>
|
</ircsession>
|
||||||
<para>
|
<para>
|
||||||
'help <command>' replies with the command name followed
|
Now on to the actual body of the function:
|
||||||
by the first line of the command's docstring; there should be
|
|
||||||
a blank line following, and then 'morehelp <command>'
|
|
||||||
will reply with the remainder of the docstring. So that
|
|
||||||
explains the docstring. Now on to the actual body of the
|
|
||||||
function:
|
|
||||||
</para>
|
</para>
|
||||||
<programlisting>
|
<programlisting>
|
||||||
irc.reply(msg, str(self.rng.random()))
|
irc.reply(msg, str(self.rng.random()))
|
||||||
</programlisting>
|
</programlisting>
|
||||||
<para>
|
<para>
|
||||||
<function>irc.reply</function> takes two arguments, an
|
<function>irc.reply</function> simply takes one simple
|
||||||
<classname>IrcMsg</classname> (like the one passed into your
|
argument: a string The string is the reply to be sent. Don't
|
||||||
function) and a string. The <classname>IrcMsg</classname> is
|
worry about length restrictions or anything
|
||||||
used to determine who the reply should go to and whether or
|
– if the string you want to send is too big for an IRC
|
||||||
not it should be sent in private message (commands sent in
|
|
||||||
private are replied to in private). 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
|
message (and oftentimes that turns out to be the case :)) the
|
||||||
supybot framework handles that entirely transparently to you.
|
Supybot framework handles that entirely transparently to you.
|
||||||
Do make sure, however, that you give
|
Do make sure, however, that you give
|
||||||
<function>irc.reply</function> a string. It doesn't take
|
<function>irc.reply</function> a string. It doesn't take
|
||||||
anything else (sometimes even unicode fails!). That's why we
|
anything else (sometimes even unicode fails!). That's why we
|
||||||
have "str(self.rng.random())" instead of simply
|
have "str(self.rng.random())" instead of simply
|
||||||
"self.rng.random()" -- we had to give
|
"self.rng.random()" – we had to give
|
||||||
<function>irc.reply</function> a string.
|
<function>irc.reply</function> a string.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
@ -406,14 +397,15 @@ def __init__(self):
|
|||||||
irc.error(msg, '<seed> must be a valid int or long.')
|
irc.error(msg, '<seed> must be a valid int or long.')
|
||||||
return
|
return
|
||||||
self.rng.seed(seed)
|
self.rng.seed(seed)
|
||||||
irc.reply(msg, conf.replySuccess)
|
irc.replySuccess()
|
||||||
</programlisting>
|
</programlisting>
|
||||||
<para>
|
<para>
|
||||||
So this one's a bit more complicated. But it's still pretty
|
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
|
simple. The method name is <botcommand>seed</botcommand> so
|
||||||
name. The arguments are the same, the docstring is of the
|
that'll be the command name. The arguments are the same, the
|
||||||
same form, so we don't need to go over that again. The body
|
docstring is of the same form, so we don't need to go over
|
||||||
of the function, however, is significantly different.
|
that again. The body of the function, however, is
|
||||||
|
significantly different.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
<function>privmsgs.getArgs</function> is a function you're
|
<function>privmsgs.getArgs</function> is a function you're
|
||||||
@ -444,26 +436,25 @@ def __init__(self):
|
|||||||
<function>irc.error</function>. It has the same interface as
|
<function>irc.error</function>. It has the same interface as
|
||||||
we saw before in <function>irc.reply</function>, but it makes
|
we saw before in <function>irc.reply</function>, but it makes
|
||||||
sure to remind the user that an error has been encountered
|
sure to remind the user that an error has been encountered
|
||||||
(currently, that means it puts "Error: " at the beginning of
|
(currently, that means it puts <literal>"Error: "</literal> at
|
||||||
the message). After erroring, we return. It's important to
|
the beginning of the message). After erroring, we return.
|
||||||
remember this <keyword>return</keyword> here; otherwise,
|
It's important to remember this <keyword>return</keyword>
|
||||||
we'll just keep going down through the function and try to use
|
here; otherwise, we'll just keep going down through the
|
||||||
this "seed" variable that never got assigned. A good general
|
function and try to use this <varname>seed</varname> variable
|
||||||
rule of thumb is that any time you use
|
that never got assigned. A good general rule of thumb is that
|
||||||
<function>irc.error</function>, you'll want to return
|
any time you use <function>irc.error</function>, you'll want
|
||||||
immediately afterwards.
|
to return immediately afterwards.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Then we set the seed -- that's a simple function on our rng
|
Then we set the seed – that's a simple function on our
|
||||||
object. Assuming that succeeds (and doesn't raise an
|
rng object. Assuming that succeeds (and doesn't raise an
|
||||||
exception, which it shouldn't, because we already read the
|
exception, which it shouldn't, because we already read the
|
||||||
documentation and know that it should work) we reply to say
|
documentation and know that it should work) we reply to say
|
||||||
that everything worked fine. That's what
|
that everything worked fine. That's what
|
||||||
<varname>conf.replySuccess</varname> says. By default, it has
|
<function>irc.replySuccess</function> says. By default, it
|
||||||
the very dry (and appropriately robot-like) "The operation
|
has the very dry (and appropriately robot-like) "The operation
|
||||||
succeeded." but you're perfectly welcome to customize it
|
succeeded." but you're perfectly welcome to customize it
|
||||||
yourself -- <filename>conf.py</filename> was written to be
|
yourself – the registry was written to be modified!
|
||||||
modified!
|
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
So that's a bit more complicated command. But we still
|
So that's a bit more complicated command. But we still
|
||||||
@ -529,14 +520,14 @@ def __init__(self):
|
|||||||
except IndexError: # raised by .pop(0)
|
except IndexError: # raised by .pop(0)
|
||||||
raise callbacks.ArgumentError
|
raise callbacks.ArgumentError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
irc.error(msg, '<number of items> must be an integer.')
|
irc.error('<number of items> must be an integer.')
|
||||||
return
|
return
|
||||||
if n > len(args):
|
if n > len(args):
|
||||||
irc.error(msg, '<number of items> must be less than the number '
|
irc.error('<number of items> must be less than the number '
|
||||||
'of arguments.')
|
'of arguments.')
|
||||||
return
|
return
|
||||||
sample = self.rng.sample(args, n)
|
sample = self.rng.sample(args, n)
|
||||||
irc.reply(msg, utils.commaAndify(map(repr, sample)))
|
irc.reply(utils.commaAndify(map(repr, sample)))
|
||||||
</programlisting>
|
</programlisting>
|
||||||
<para>
|
<para>
|
||||||
Most everything here is familiar. The difference between this
|
Most everything here is familiar. The difference between this
|
||||||
@ -592,8 +583,7 @@ def __init__(self):
|
|||||||
irc.error(msg, 'Dice have integer numbers of sides. Use one.')
|
irc.error(msg, 'Dice have integer numbers of sides. Use one.')
|
||||||
return
|
return
|
||||||
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||||
irc.queueMsg(ircmsgs.action(ircutils.replyTo(msg), s))
|
irc.reply(s, action=True)
|
||||||
raise callbacks.CannotNest
|
|
||||||
</programlisting>
|
</programlisting>
|
||||||
<para>
|
<para>
|
||||||
There's a lot of stuff you haven't seen before in there. The
|
There's a lot of stuff you haven't seen before in there. The
|
||||||
@ -611,33 +601,10 @@ def __init__(self):
|
|||||||
= 6", where we provide the default.
|
= 6", where we provide the default.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Later, though, you'll see something other than
|
You'll also note that <function>irc.reply</function> was given
|
||||||
<function>irc.reply</function>. This is
|
a keyword argument here, <varname>action</varname>. This
|
||||||
<function>irc.queueMsg</function>, the general interface for
|
means that the reply is to be made as an action rather than a
|
||||||
sending messages to the server. It's what
|
normal reply.
|
||||||
<function>irc.reply</function> is using under the covers. It
|
|
||||||
takes an <classname>IrcMsg</classname> object. Fortunately,
|
|
||||||
that's exactly what's returned by
|
|
||||||
<function>ircmsgs.action</function>. An action message, just
|
|
||||||
in case you don't know, is a /me kind of message.
|
|
||||||
<function>ircmsgs.action</function> is a helper function that
|
|
||||||
takes a target (a place to send the message, either a channel
|
|
||||||
or a person) and a payload (the thing to /me) and returns the
|
|
||||||
appropriate <classname>IrcMsg</classname> object.
|
|
||||||
<function>ircutils.replyTo</function> simply takes an
|
|
||||||
<classname>IrcMsg</classname> and returns where we should
|
|
||||||
reply to; if the message was originally sent to a channel,
|
|
||||||
we'll reply to there, if it was originally sent to us
|
|
||||||
privately, we'll reply in private.
|
|
||||||
</para>
|
|
||||||
<para>
|
|
||||||
At the end, you might be surprised by the "raise
|
|
||||||
callbacks.CannotNest". That's used simply because at the
|
|
||||||
moment you can't nest actions (just like you can't nest
|
|
||||||
anything that doesn't go through
|
|
||||||
<function>irc.reply</function>). That raise just makes sure
|
|
||||||
the user finds this out if he tries to nest this like "@rot13
|
|
||||||
[diceroll]".
|
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
So that's our plugin. 5 commands, each building in
|
So that's our plugin. 5 commands, each building in
|
||||||
@ -649,59 +616,12 @@ def __init__(self):
|
|||||||
</para>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
<sect2>
|
<sect2>
|
||||||
<title>Finishing touches</title>
|
<title>Using the registry in your plugin</title>
|
||||||
<para>
|
<para>
|
||||||
Let's take a look at that <function>configure</function>
|
TODO: Describe the registry and how to write a proper plugin
|
||||||
function <script>scripts/newplugin.py</script> made
|
configure function.
|
||||||
for us. Here it is, in case you've forgotten:
|
|
||||||
</para>
|
|
||||||
<programlisting>
|
|
||||||
def configure(onStart, afterConnect, advanced):
|
|
||||||
# This will be called by setup.py to configure this module. onStart and
|
|
||||||
# afterConnect are both lists. Append to onStart the commands you would
|
|
||||||
# like to be run when the bot is started; append to afterConnect the
|
|
||||||
# commands you would like to be run when the bot has finished connecting.
|
|
||||||
from questions import expect, anything, something, yn
|
|
||||||
onStart.append('load Random')
|
|
||||||
</programlisting>
|
|
||||||
<para>
|
|
||||||
You remember when you first started running supybot and ran
|
|
||||||
<script>scripts/setup.py</script> and it asked you
|
|
||||||
all those questions? Well, now's your chance to ask other
|
|
||||||
users some questions of your own. In our case, with our
|
|
||||||
<plugin>Random</plugin> plugin, it might be nice to offer
|
|
||||||
the user the ability to specify a seed to use whenever the
|
|
||||||
plugin is loaded. So let's ask him if he wants to do that,
|
|
||||||
and if so, let's ask him what the seed should be.
|
|
||||||
</para>
|
|
||||||
<programlisting>
|
|
||||||
def configure(onStart, afterConnect, advanced):
|
|
||||||
# This will be called by setup.py to configure this module. onStart and
|
|
||||||
# afterConnect are both lists. Append to onStart the commands you would
|
|
||||||
# like to be run when the bot is started; append to afterConnect the
|
|
||||||
# commands you would like to be run when the bot has finished connecting.
|
|
||||||
from questions import expect, anything, something, yn
|
|
||||||
onStart.append('load Random')
|
|
||||||
if yn('Do you want to specify a seed to be used for the RNG')=='y':
|
|
||||||
seed = something('What seed? It must be an int or long.')
|
|
||||||
while not seed.isdigit():
|
|
||||||
print 'That\'s not a valid seed.'
|
|
||||||
seed = something('What seed?')
|
|
||||||
onStart.append('seed %s' % seed)
|
|
||||||
</programlisting>
|
|
||||||
<para>
|
|
||||||
As you can see, what the <module>questions</module> module
|
|
||||||
does is fairly self-evident: <function>yn</function> returns
|
|
||||||
either 'y' or 'n'; <function>something</function> returns
|
|
||||||
<emphasis>something</emphasis> (but not nothing; for nothing,
|
|
||||||
you'd want <function>anything</function>). So basically we
|
|
||||||
ask some questions until we get a good seed. Then we do this
|
|
||||||
"onStart.append('seed %s' % seed)" doohickey.
|
|
||||||
<varname>onStart</varname> is a list of the commands to run
|
|
||||||
when the bot starts; we're just throwing our little piece into
|
|
||||||
it. These commands will then be written into the template
|
|
||||||
<script>scripts/setup.py</script> creates for the bot.
|
|
||||||
</para>
|
</para>
|
||||||
|
</sect2>
|
||||||
<para>
|
<para>
|
||||||
We've written our own plugin from scratch (well, from the
|
We've written our own plugin from scratch (well, from the
|
||||||
boilerplate that we got from
|
boilerplate that we got from
|
||||||
@ -709,6 +629,5 @@ def configure(onStart, afterConnect, advanced):
|
|||||||
survived! Now go write more plugins for supybot, and send
|
survived! Now go write more plugins for supybot, and send
|
||||||
them to me so I can use them too :)
|
them to me so I can use them too :)
|
||||||
</para>
|
</para>
|
||||||
</sect2>
|
|
||||||
</sect1>
|
</sect1>
|
||||||
</article>
|
</article>
|
||||||
|
@ -304,9 +304,9 @@ 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
|
Assuming that succeeds (and doesn't raise an exception, which it
|
||||||
shouldn't, because we already read the documentation and know that 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
|
should work) we reply to say that everything worked fine. That's what
|
||||||
conf.replySuccess says. By default, it has the very dry (and
|
irc.replySuccess says. By default, it has the very dry (and
|
||||||
appropriately robot-like) "The operation succeeded." but you're
|
appropriately robot-like) "The operation succeeded." but you're
|
||||||
perfectly welcome to customize it yourself -- conf.py was written to
|
perfectly welcome to customize it yourself -- the registry was written to
|
||||||
be modified!
|
be modified!
|
||||||
|
|
||||||
So that's a bit more complicated command. But we still haven't dealt
|
So that's a bit more complicated command. But we still haven't dealt
|
||||||
|
Loading…
Reference in New Issue
Block a user