#!/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 .
"""
.. moduleauthor:: Julio Biason
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 NetworkData(object):
"""Provides an uniform way to access information about posts. The
following fields should appear [*]_ [*]_:
**id**
The message identification. *Optional*
**name**
The name to be displayed as author of the message. *Required*
**username**
The message author username in the network. *Required*
**avatar**
URL to the author avatar. *Optional*
**message**
The message. *Required*
**message_time**
Message timestamp (as a datetime object). Defaults to None. *Optional*
**favourite**
Boolean indicating if the message was marked as "favourite" or not.
*Optional*
**parent**
The parent of this message, in case of a reply. *Optional*
**parent_owner**
Username of the owner of the parent message (in other words, "in reply
to".) *Optional*
**reposted_by**
Username friend of the current user that reposted that message. 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
will must return the original user in *name*, *username* and *avatar*,
while the friend username will be moved to *reposted_by*. *Optional*
**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. *Networks should NEVER fill
this field*
.. [*] Not all fields need to be filled if that would require more than
one network call.
.. [*] Interfaces should be aware that optional fields may not be filled.
"""
def __init__(self):
self.id = ''
self.name = ''
self.username = ''
self.avatar = ''
self.message = ''
self.message_time = None
self.favourite = False
self.parent = ''
self.parent_owner = ''
self.reposted_by = ''
self.network = ''
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 is_setup(self):
"""Should return a boolean indicating if the network have all
necessary options set up so it can retrieve information.
:class:`Networks` won't send requests to networks that return False to
this function."""
return False
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 reply_prefix(self, message):
"""Return the prefix to be used in case the user requests a reply for
that message. *message* must be a :class:`NetworkData` object. Returns
a string."""
return ''
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
def can_delete(self, message):
"""Return True if the message can be deleted; False otherwise."""
return False
def can_reply(self, message):
"""Return True if the message can be replied; False otherwise."""
return False
def can_repost(self, message):
"""Return True if the message can be resposted; False otherwise."""
return False
def can_favourite(self, message):
"""Return True if the message can be favourited/unfavourited; False
otherwise."""
return False