You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
9.8 KiB
304 lines
9.8 KiB
#!/usr/bin/env python |
|
# -*- coding: utf-8 -*- |
|
|
|
# Mitter, a client for Twitter. |
|
# Copyright (C) 2007, 2008 The Mitter Contributors |
|
# |
|
# This program is free software: you can redistribute it and/or modify |
|
# it under the terms of the GNU General Public License as published by |
|
# the Free Software Foundation, either version 3 of the License, or |
|
# (at your option) any later version. |
|
# |
|
# This program is distributed in the hope that it will be useful, |
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
# GNU General Public License for more details. |
|
# |
|
# You should have received a copy of the GNU General Public License |
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
""" |
|
.. moduleauthor:: Julio Biason <julio.biason@gmail.com> |
|
|
|
The :mod:`networkbase` module defines the base classes for all networks. |
|
""" |
|
|
|
import logging |
|
|
|
from mitterlib.constants import version |
|
|
|
# logging |
|
_log = logging.getLogger('mitterlib.network.Network') |
|
|
|
|
|
def auth_options(namespace, options, auths): |
|
"""Convert the auth fields into options for the command line.""" |
|
for option in auths: |
|
options.add_option(group=namespace, |
|
option=option['name'], |
|
default=None, |
|
help=option['help'], |
|
*(option['flags'])) |
|
|
|
#-------------------------------------------------------------------- |
|
# Exceptions |
|
#-------------------------------------------------------------------- |
|
|
|
class NetworkError(Exception): |
|
"""Base class for all network related exceptions.""" |
|
pass |
|
|
|
|
|
class NetworkUnknownError(NetworkError): |
|
"""Some non-expected error occurred.""" |
|
pass |
|
|
|
|
|
class NetworkLimitExceededError(NetworkError): |
|
"""The number of requests available was exceeded.""" |
|
pass |
|
|
|
|
|
class NetworkDNSError(NetworkError): |
|
"""A DNS failure prevented the request to continue.""" |
|
pass |
|
|
|
|
|
class NetworkInvalidResponseError(NetworkError): |
|
"""The server returned the information in an unexpected way.""" |
|
pass |
|
|
|
|
|
class NetworkLowLevelError(NetworkError): |
|
"""A low level error occurred in the network layer.""" |
|
pass |
|
|
|
|
|
class NetworkBadStatusLineError(NetworkError): |
|
"""Bad status line exception.""" |
|
pass |
|
|
|
|
|
class NetworkAuthorizationFailError(NetworkError): |
|
"""Authorization failure.""" |
|
pass |
|
|
|
|
|
class NetworkPermissionDeniedError(NetworkError): |
|
"""Permission denied when accessing the message/list.""" |
|
pass |
|
|
|
#-------------------------------------------------------------------- |
|
# Warnings |
|
#-------------------------------------------------------------------- |
|
|
|
class NetworkWarning(Warning): |
|
"""Base warning for networks.""" |
|
pass |
|
|
|
|
|
class MessageTooLongWarning(NetworkWarning): |
|
"""The message is too long for the network.""" |
|
pass |
|
|
|
#-------------------------------------------------------------------- |
|
# The classes |
|
#-------------------------------------------------------------------- |
|
|
|
class NetworkUser(object): |
|
"""Provides an uniform way to access information about users.""" |
|
def __init__(self): |
|
self.name = '' |
|
"""The name to be displayed as author of the message. *Required*""" |
|
|
|
self.username = '' |
|
"""The message author username in the network. *Required*""" |
|
|
|
self.avatar = None |
|
"""URL to the author avatar. *Optional*""" |
|
|
|
def __repr__(self): |
|
return "%s (%s)" % (self.name, self.username) |
|
|
|
|
|
class NetworkData(object): |
|
"""Provides an uniform way to access information about posts.""" |
|
|
|
def __init__(self): |
|
self.id = '' |
|
"""The message identification. *Optional*""" |
|
|
|
self.author = None |
|
"""Author of the message. A :class:`NetworkUser` object. |
|
*Required*""" |
|
|
|
self.message = '' |
|
"""The message. *Required*""" |
|
|
|
self.message_time = None |
|
"""Message timestamp (as a datetime object). Defaults to None. |
|
*Optional*""" |
|
|
|
self.favourite = False |
|
"""Boolean indicating if the message was marked as "favourite" or not. |
|
*Optional*""" |
|
|
|
self.parent = None |
|
"""The parent of this message, in case of a reply. *Optional*""" |
|
|
|
self.parent_owner = None |
|
"""Owner of the parent message (in other words, "in reply to".) |
|
It will be a :class:`NetworkUser` object. *Optional*""" |
|
|
|
self.reposted_by = None |
|
"""Author of the reposted message. A :class:`NetworkUser` object. |
|
|
|
Some networks will return the original |
|
user in a separate table (Twitter, in this case, returns the original |
|
message with the original user in a "retweeted_status" structure.) In |
|
this case, the network layer must return the original user in *author* |
|
while the friend will be in *reposted_by*. *Optional*""" |
|
|
|
self.protected = False |
|
"""Boolean indicating if the message is protected/private or not.""" |
|
|
|
self.deletable = False |
|
"""Boolean indicating if the message can be deleted.""" |
|
|
|
self.replyable = False |
|
"""Boolean indicating if the message can be replied.""" |
|
|
|
self.repostable = False |
|
"""Boolean indicating if the message can be reposted.""" |
|
|
|
self.favoritable = False |
|
"""Boolean indicating if the message can be favorited.""" |
|
|
|
self.link = None |
|
"""Link to the original message.""" |
|
|
|
self.reply_prefix = '' |
|
"""Prefix to be used in messages when replying to this message.""" |
|
|
|
self.network = '' |
|
"""The network id source of the message. Network classes don't need to |
|
worry about this field themselves; :class:`Networks` will set it when |
|
merging information from all networks.""" |
|
|
|
self.network_name = '' |
|
"""The network name, source of the message. Again, network classes |
|
don't need to worry about this field themselves 'cause |
|
:class:`Networks` will set it when merging information from all |
|
networks.""" |
|
|
|
return |
|
|
|
|
|
class NetworkBase(object): |
|
"""Base class for all networks.""" |
|
_user_agent = 'Mitter %s' % (version) |
|
|
|
# TODO: We'll probably need a ICON attribute in the future. |
|
#: Namespace of the network, used to identify options. |
|
NAMESPACE = 'Meta' |
|
|
|
AUTH = [] |
|
"""List of fields the interface must request to the user in order to |
|
retrieve information from the network. It's a list of dictionaries, |
|
containing: |
|
|
|
*name* |
|
Name of the option, used in ConfigOpt (for the name in the |
|
config file and to access it through the options variable); |
|
|
|
*flags* |
|
The list of command line options for this option (as in |
|
OptParse); |
|
|
|
*prompt* |
|
The prompt to be used by interfaces when requesting the |
|
variable; |
|
|
|
*help* |
|
Description for the value; it's used by ConfigOpt to show the |
|
description of the paramater in the command line options and can be used |
|
by interfaces to show tooltips about the field; |
|
|
|
*type* |
|
The type of the option; valid values are: |
|
|
|
**str** |
|
A string; |
|
|
|
**passwd** |
|
Password; string, but interfaces should hide the information |
|
if possible. |
|
""" |
|
|
|
def __init__(self): |
|
self.is_setup = False |
|
"""Boolean indication if the network have all necessary options set up |
|
so it can retrieve the messages. :class:`Networks` will not send |
|
requests to networks that return False to this function.""" |
|
|
|
self.user = NetworkUser() |
|
"""A :class:`NetworkUser` with the information about the currently |
|
logged user.""" |
|
|
|
def messages(self): |
|
"""Return a list of :class:`NetworkData` objects for the main |
|
"timeline" (the default list presented to the user.)""" |
|
return [] |
|
|
|
def update(self, status, reply_to=None): |
|
"""Update the user status. *status* should be the string with the |
|
status update; *reply_to* should be used in case the message is a |
|
reply to another message, it could be a simple id or the |
|
:class:`NetworkData` object of the original data. Must return the id |
|
for the new status.""" |
|
# TODO: All networks will return an id? If so, what we do with it |
|
# anyway? |
|
return None |
|
|
|
def repost(self, message): |
|
"""Repost a message in your current timeline. *message* must be a |
|
valid :class:`NetworkData` object. Must return the id of the new |
|
message.""" |
|
# TODO: All networks will return an id? If so, what we do with it |
|
# anyway? |
|
return None |
|
|
|
def favourite(self, message): |
|
"""Toggle the favourite status of a message. *message* must be a valid |
|
:class:`NetworkData` object. Returns True if the request was |
|
successful or False otherwise.""" |
|
# TODO: Again, if errors appear as exceptions, why return something? |
|
return False |
|
|
|
def delete_message(self, message): |
|
"""Delete an update. Must return True if the message was deleted or |
|
False if not. *message* can be either an id or a :class:`NetworkData` |
|
object with the information about the message to be deleted.""" |
|
return False |
|
|
|
def message(self, message_id): |
|
"""Return a single :class:`NetworkData` object for a specified |
|
message.""" |
|
return None |
|
|
|
def link(self, message): |
|
"""Returns a link (as string) to the message in the network site. If |
|
the network doesn't provide a link directly to the message, None shall |
|
be returned. *message* is a :class:`NetworkData` object.""" |
|
return None |
|
|
|
def replies(self): |
|
"""Return a list of :class:`NetworkData` objects for the replies for |
|
the user messages.""" |
|
return [] |
|
|
|
def available_requests(self): |
|
"""Return the number of requests the user can request before being |
|
capped. If such limitation doesn't exist for the network, a negative |
|
number should be returned.""" |
|
return -1 |