#!/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
import gettext
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."""
default_values = {
'str': '',
'passwd': ''}
for option in auths:
options.add_option(group=namespace,
option=option['name'],
default=default_values[option['type']],
help=option['help'],
*(option['flags']))
#--------------------------------------------------------------------
# i18n
#--------------------------------------------------------------------
t = gettext.translation('networks', fallback=True)
_ = t.gettext
N_ = t.ngettext
#--------------------------------------------------------------------
# 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."""
def __init__(self, network_name):
self.name = network_name
def __str__(self):
return _('No more available requests on %s') % (self.name)
pass
class NetworkDNSError(NetworkError):
"""A DNS failure prevented the request to continue."""
def __init__(self, network_name):
self.name = network_name
def __str__(self):
return _('DNS failure on a request to %s') % (self.name)
pass
class NetworkInvalidResponseError(NetworkError):
"""The server returned the information in an unexpected way."""
def __init__(self, network_name):
self.name = network_name
def __str__(self):
return _('The server on %s return the information in an ' \
'unexpected way.') % (self.name)
pass
class NetworkLowLevelError(NetworkError):
"""A low level error occurred in the network layer."""
def __init__(self, network_name):
self.name = network_name
def __str__(self):
return _('Low level error in the network layer when talking ' \
'to %s') % (self.name)
pass
class NetworkBadStatusLineError(NetworkError):
"""Bad status line exception."""
def __init__(self, network_name):
self.name = network_name
def __str__(self):
return _('Bad status line in network %s') % (self.name)
pass
class NetworkAuthorizationFailError(NetworkError):
"""Authorization failure."""
def __init__(self, network_name):
self.name = network_name
def __str__(self):
return _('Autorization failed for %s') % (self.name)
pass
class NetworkPermissionDeniedError(NetworkError):
"""Permission denied when accessing the message/list."""
def __init__(self, network_name):
self.name = network_name
def __str__(self):
return _('Permission denied received on %s') % (self.name)
pass
#--------------------------------------------------------------------
# Warnings
#--------------------------------------------------------------------
class NetworkWarning(Warning):
"""Base warning for networks."""
pass
class MessageTooLongWarning(NetworkWarning):
"""The message is too long for the network."""
def __init__(self, network_name):
self.name = network_name
def __str__(self):
return _('Message too long for %s') % (self.name)
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.favorite = False
"""Boolean indicating if the message was marked as "favorite" 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.user_regexp = None
"""Regular expression used to identify usernames in the message. If
the network doesn't support an easy way to identify usernames, it
should be set to None."""
self.group_regexp = None
"""Regular expression used to identify groups in the message. If the
network doesn't support groups or doesn't have an easy way to
identify groups, it should be set to None."""
self.tag_regexps = None
"""Regular expression used to identify tags (hashtags) in the
message. If the network doesn't support tags or doesn't have an easy
way to identify groups, it should be set no None."""
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 favorite(self, message):
"""Toggle the favorite 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