conduit r1511 - in trunk: . conduit/datatypes conduit/modules/GoogleModule conduit/modules/GoogleModule/atom conduit/modules/GoogleModule/gdata conduit/modules/GoogleModule/gdata/apps conduit/modules/GoogleModule/gdata/blogger conduit/modules/GoogleModule/gdata/calendar conduit/modules/GoogleModule/gdata/media conduit/modules/GoogleModule/gdata/youtube
- From: jstowers svn gnome org
- To: svn-commits-list gnome org
- Subject: conduit r1511 - in trunk: . conduit/datatypes conduit/modules/GoogleModule conduit/modules/GoogleModule/atom conduit/modules/GoogleModule/gdata conduit/modules/GoogleModule/gdata/apps conduit/modules/GoogleModule/gdata/blogger conduit/modules/GoogleModule/gdata/calendar conduit/modules/GoogleModule/gdata/media conduit/modules/GoogleModule/gdata/youtube
- Date: Sat, 7 Jun 2008 07:56:04 +0000 (UTC)
Author: jstowers
Date: Sat Jun 7 07:56:04 2008
New Revision: 1511
URL: http://svn.gnome.org/viewvc/conduit?rev=1511&view=rev
Log:
Merge youtube video upload branch
Added:
trunk/conduit/modules/GoogleModule/atom/mock_service.py (contents, props changed)
trunk/conduit/modules/GoogleModule/gdata/blogger/
trunk/conduit/modules/GoogleModule/gdata/blogger/Makefile.am
trunk/conduit/modules/GoogleModule/gdata/blogger/__init__.py
trunk/conduit/modules/GoogleModule/gdata/blogger/service.py
trunk/conduit/modules/GoogleModule/gdata/youtube/
trunk/conduit/modules/GoogleModule/gdata/youtube/Makefile.am
trunk/conduit/modules/GoogleModule/gdata/youtube/__init__.py (contents, props changed)
trunk/conduit/modules/GoogleModule/gdata/youtube/service.py
Modified:
trunk/ (props changed)
trunk/ChangeLog
trunk/NEWS
trunk/conduit/datatypes/Video.py
trunk/conduit/modules/GoogleModule/GoogleModule.py
trunk/conduit/modules/GoogleModule/atom/Makefile.am
trunk/conduit/modules/GoogleModule/atom/__init__.py
trunk/conduit/modules/GoogleModule/atom/service.py
trunk/conduit/modules/GoogleModule/gdata/Makefile.am
trunk/conduit/modules/GoogleModule/gdata/__init__.py
trunk/conduit/modules/GoogleModule/gdata/apps/service.py
trunk/conduit/modules/GoogleModule/gdata/auth.py
trunk/conduit/modules/GoogleModule/gdata/calendar/__init__.py
trunk/conduit/modules/GoogleModule/gdata/calendar/service.py
trunk/conduit/modules/GoogleModule/gdata/media/__init__.py
trunk/conduit/modules/GoogleModule/gdata/service.py
trunk/conduit/modules/GoogleModule/gdata/test_data.py
trunk/conduit/modules/GoogleModule/gdata/urlfetch.py
trunk/conduit/modules/GoogleModule/youtube-config.glade
trunk/configure.ac
Modified: trunk/NEWS
==============================================================================
--- trunk/NEWS (original)
+++ trunk/NEWS Sat Jun 7 07:56:04 2008
@@ -1,6 +1,7 @@
NEW in 0.3.12:
==============
* Support google documents upload/sync
+* Support youtube video upload
NEW in 0.3.11.2:
==============
Modified: trunk/conduit/datatypes/Video.py
==============================================================================
--- trunk/conduit/datatypes/Video.py (original)
+++ trunk/conduit/datatypes/Video.py Sat Jun 7 07:56:04 2008
@@ -29,6 +29,8 @@
def __init__(self, URI, **kwargs):
File.File.__init__(self, URI, **kwargs)
+ self._title = None
+ self._description = None
def get_video_duration(self):
return None
@@ -36,4 +38,22 @@
def get_video_size(self):
return None,None
+ def get_description(self):
+ """
+ @returns: the video's description
+ """
+ return self._description
+
+ def set_description(self, description):
+ self._description = description
+
+ def __getstate__(self):
+ data = File.File.__getstate__(self)
+ data["description"] = self._description
+ return data
+
+ def __setstate__(self, data):
+ self.pb = None
+ self._description = data["description"]
+ File.File.__setstate__(self, data)
Modified: trunk/conduit/modules/GoogleModule/GoogleModule.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/GoogleModule.py (original)
+++ trunk/conduit/modules/GoogleModule/GoogleModule.py Sat Jun 7 07:56:04 2008
@@ -33,11 +33,12 @@
import gdata.calendar.service
import gdata.contacts.service
import gdata.docs.service
+ import gdata.youtube.service
MODULES = {
"GoogleCalendarTwoWay" : { "type": "dataprovider" },
"PicasaTwoWay" : { "type": "dataprovider" },
- "YouTubeSource" : { "type": "dataprovider" },
+ "YouTubeTwoWay" : { "type": "dataprovider" },
"ContactsTwoWay" : { "type": "dataprovider" },
"DocumentsSink" : { "type": "dataprovider" },
}
@@ -1287,7 +1288,30 @@
self._set_password(password.get_text())
dlg.destroy()
-class YouTubeSource(DataProvider.DataSource):
+class VideoUploadInfo:
+ """
+ Upload information container, this way we can add info
+ and keep the _upload_info method on the VideoSink retain
+ its API
+
+ Default category for videos is People/Blogs (for lack of a better
+ general category).
+
+ Default name and description are placeholders, or the generated XML is invalid
+ as the corresponding elements automatically self-close.
+
+ Default keyword is "miscellaneous" as the upload fails if no keywords
+ are specified.
+ """
+ def __init__ (self, url, mimeType, name=None, keywords=None, description=None, category=None):
+ self.url = url
+ self.mimeType = mimeType
+ self.name = name or _("Unknown")
+ self.keywords = keywords or (_("miscellaneous"),)
+ self.description = description or _("No description.")
+ self.category = category or "People" # Note: don't translate this; it's an identifier
+
+class YouTubeTwoWay(_GoogleBase, DataProvider.TwoWay):
"""
Downloads YouTube videos using the gdata API.
Based on youtube client from : Philippe Normand (phil at base-art dot net)
@@ -1296,7 +1320,8 @@
_name_ = _("YouTube")
_description_ = _("Sync data from YouTube")
_category_ = conduit.dataproviders.CATEGORY_MISC
- _module_type_ = "source"
+ _module_type_ = "twoway"
+ _in_type_ = "file/video"
_out_type_ = "file/video"
_icon_ = "youtube"
@@ -1310,49 +1335,44 @@
UPLOAD_URL="http://uploads.gdata.youtube.com/feeds/api/users/%(username)s/uploads"
def __init__(self, *args):
- DataProvider.DataSource.__init__(self)
+ youtube_service = gdata.youtube.service.YouTubeService()
+ youtube_service.client_id = self.UPLOAD_CLIENT_ID
+ youtube_service.developer_key = self.UPLOAD_DEVELOPER_KEY
+
+ _GoogleBase.__init__(self,youtube_service)
+ DataProvider.TwoWay.__init__(self)
+
self.entries = None
- self.username = ""
self.max_downloads = 0
- #filter type {0 = mostviewed, 1 = toprated, 2 = user}
+ #filter type {0 = mostviewed, 1 = toprated, 2 = user upload, 3 = user favorites}
self.filter_type = 0
- #filter user type {0 = upload, 1 = favorites}
- self.user_filter_type = 0
-
- def initialize(self):
- return True
def configure(self, window):
tree = Utils.dataprovider_glade_get_widget (
__file__,
"youtube-config.glade",
- "YouTubeSourceConfigDialog")
+ "YouTubeTwoWayConfigDialog")
- dlg = tree.get_widget ("YouTubeSourceConfigDialog")
+ dlg = tree.get_widget ("YouTubeTwoWayConfigDialog")
mostviewedRb = tree.get_widget("mostviewed")
topratedRb = tree.get_widget("toprated")
- byuserRb = tree.get_widget("byuser")
- user_frame = tree.get_widget("frame")
uploadedbyRb = tree.get_widget("uploadedby")
favoritesofRb = tree.get_widget("favoritesof")
- user = tree.get_widget("user")
- maxdownloads = tree.get_widget("maxdownloads")
-
- byuserRb.connect("toggled", self._filter_user_toggled_cb, user_frame)
+ max_downloads = tree.get_widget("maxdownloads")
+ username = tree.get_widget("username")
+ password = tree.get_widget("password")
if self.filter_type == 0:
mostviewedRb.set_active(True)
elif self.filter_type == 1:
topratedRb.set_active(True)
+ elif self.filter_type == 2:
+ uploadedbyRb.set_active(True)
else:
- byuserRb.set_active(True)
- user_frame.set_sensitive(True)
- if self.user_filter_type == 0:
- uploadedbyRb.set_active(True)
- else:
- favoritesofRb.set_active(True)
- user.set_text(self.username)
- maxdownloads.set_value(self.max_downloads)
+ favoritesofRb.set_active(True)
+ max_downloads.set_value(self.max_downloads)
+ username.set_text(self.username)
+ password.set_text(self.password)
response = Utils.run_dialog(dlg, window)
if response == True:
@@ -1360,32 +1380,58 @@
self.filter_type = 0
elif topratedRb.get_active():
self.filter_type = 1
- else:
+ elif uploadedbyRb.get_active():
self.filter_type = 2
- if uploadedbyRb.get_active():
- self.user_filter_type = 0
- else:
- self.user_filter_type = 1
- self.username = user.get_text()
- self.max_downloads = int(maxdownloads.get_value())
+ else:
+ self.filter_type = 3
+ self.max_downloads = int(max_downloads.get_value())
+ self.username = username.get_text()
+ self.password = password.get_text()
dlg.destroy()
+ def _get_video_info (self, id):
+ if self.entries.has_key(id):
+ return self.entries[id]
+ else:
+ return None
+
+ def _do_login(self):
+ # The YouTube login URL is slightly different to the normal Google one
+ self.service.ClientLogin(self.username, self.password, auth_service_url="https://www.google.com/youtube/accounts/ClientLogin")
+
+ def _upload_video (self, uploadInfo):
+ try:
+ self.gvideo = gdata.youtube.YouTubeVideoEntry()
+ self.gvideo.media = gdata.media.Group(
+ title = gdata.media.Title(text=uploadInfo.name),
+ description = gdata.media.Description(text=uploadInfo.description),
+ category = gdata.media.Category(text=uploadInfo.category),
+ keywords = gdata.media.Keywords(text=','.join(uploadInfo.keywords)))
+
+ gvideo = self.service.InsertVideoEntry(
+ self.gvideo,
+ uploadInfo.url)
+ return Rid(uid=gvideo.id.text)
+ except Exception, e:
+ raise Exceptions.SyncronizeError("YouTube Upload Error.")
+
+ def _replace_video (self, uploadInfo):
+ raise Exceptions.NotSupportedError("FIXME: Not supported yet")
+
def refresh(self):
- DataProvider.DataSource.refresh(self)
+ DataProvider.TwoWay.refresh(self)
self.entries = {}
try:
- feedUrl = ""
if self.filter_type == 0:
videos = self._most_viewed ()
elif self.filter_type == 1:
videos = self._top_rated ()
+ elif self.filter_type == 2:
+ videos = self._videos_upload_by (self.username)
else:
- if self.user_filter_type == 0:
- videos = self._videos_upload_by (self.username)
- else:
- videos = self._favorite_videos (self.username)
+ videos = self._favorite_videos (self.username)
for video in videos:
self.entries[video.title.text] = self._get_flv_video_url (video.link[0].href)
@@ -1397,7 +1443,7 @@
return self.entries.keys()
def get(self, LUID):
- DataProvider.DataSource.get(self, LUID)
+ DataProvider.TwoWay.get(self, LUID)
url = self.entries[LUID]
log.debug("Title: '%s', Url: '%s'"%(LUID, url))
@@ -1408,24 +1454,68 @@
return f
+ def put(self, video, overwrite, LUID=None):
+ """
+ Based off the ImageTwoWay put method.
+ Accepts a VFS file. Must be made local.
+ I also store a MD5 of the video's URI to check for duplicates
+ """
+ DataProvider.TwoWay.put(self, video, overwrite, LUID)
+
+ self._login()
+
+ originalName = video.get_filename()
+ #Gets the local URI (/foo/bar). If this is a remote file then
+ #it is first transferred to the local filesystem
+ videoURI = video.get_local_uri()
+ mimeType = video.get_mimetype()
+ keywords = video.get_tags ()
+ description = video.get_description()
+
+ uploadInfo = VideoUploadInfo(videoURI, mimeType, originalName, keywords, description)
+
+ #Check if we have already uploaded the video
+ if LUID != None:
+ url = self._get_video_info(LUID)
+ #Check if a video exists at that UID
+ if url != None:
+ if overwrite == True:
+ #Replace the video
+ return self._replace_video(LUID, uploadInfo)
+ else:
+ #Only upload the video if it is newer than the remote one
+ url = self._get_flv_video_url(url)
+ remoteFile = File.File(url)
+
+ #This is a limited test for equality type comparison
+ comp = video.compare(remoteFile,True)
+ log.debug("Compared %s with %s to check if they are the same (size). Result = %s" %
+ (video.get_filename(),remoteFile.get_filename(),comp))
+ if comp != conduit.datatypes.COMPARISON_EQUAL:
+ raise Exceptions.SynchronizeConflictError(comp, video, remoteFile)
+ else:
+ return conduit.datatypes.Rid(uid=LUID)
+
+ log.debug("Uploading video URI = %s, Mimetype = %s, Original Name = %s" % (videoURI, mimeType, originalName))
+
+ #Upload the file
+ return self._upload_video (uploadInfo)
+
def finish(self, aborted, error, conflict):
- DataProvider.DataSource.finish(self)
+ DataProvider.TwoWay.finish(self)
self.files = None
def get_configuration(self):
return {
"filter_type" : self.filter_type,
- "user_filter_type" : self.user_filter_type,
+ "max_downloads" : self.max_downloads,
"username" : self.username,
- "max_downloads" : self.max_downloads
+ "password" : self.password
}
def get_UID(self):
return Utils.get_user_string()
- def _filter_user_toggled_cb (self, toggle, frame):
- frame.set_sensitive(toggle.get_active())
-
def _format_url (self, url):
if self.max_downloads > 0:
url = ("%s?max-results=%d" % (url, self.max_downloads))
@@ -1436,19 +1526,19 @@
return service.Get(feed % params)
def _top_rated(self):
- url = self._format_url ("%s/top_rated" % YouTubeSource.STD_FEEDS)
+ url = self._format_url ("%s/top_rated" % YouTubeTwoWay.STD_FEEDS)
return self._request(url).entry
def _most_viewed(self):
- url = self._format_url ("%s/most_viewed" % YouTubeSource.STD_FEEDS)
+ url = self._format_url ("%s/most_viewed" % YouTubeTwoWay.STD_FEEDS)
return self._request(url).entry
def _videos_upload_by(self, username):
- url = self._format_url ("%s/%s/uploads" % (YouTubeSource.USERS_FEED, username))
+ url = self._format_url ("%s/%s/uploads" % (YouTubeTwoWay.USERS_FEED, username))
return self._request(url).entry
def _favorite_videos(self, username):
- url = self._format_url ("%s/%s/favorites" % (YouTubeSource.USERS_FEED, username))
+ url = self._format_url ("%s/%s/favorites" % (YouTubeTwoWay.USERS_FEED, username))
return self._request(url).entry
# Generic extract step
@@ -1459,7 +1549,7 @@
data = doc.read()
# extract video name
- match = YouTubeSource.VIDEO_NAME_RE.search(data)
+ match = YouTubeTwoWay.VIDEO_NAME_RE.search(data)
if match is not None:
video_name = match.group(1)
Modified: trunk/conduit/modules/GoogleModule/atom/Makefile.am
==============================================================================
--- trunk/conduit/modules/GoogleModule/atom/Makefile.am (original)
+++ trunk/conduit/modules/GoogleModule/atom/Makefile.am Sat Jun 7 07:56:04 2008
@@ -1,5 +1,5 @@
conduit_handlersdir = $(libdir)/conduit/modules/GoogleModule/atom
-conduit_handlers_PYTHON = __init__.py service.py
+conduit_handlers_PYTHON = __init__.py service.py mock_service.py
clean-local:
rm -rf *.pyc *.pyo
Modified: trunk/conduit/modules/GoogleModule/atom/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/atom/__init__.py (original)
+++ trunk/conduit/modules/GoogleModule/atom/__init__.py Sat Jun 7 07:56:04 2008
@@ -165,8 +165,8 @@
child._BecomeChildElement(tree)
for attribute, value in self.extension_attributes.iteritems():
if value:
- # Encode the value in the desired type (default UTF-8).
- tree.attrib[attribute] = value.encode(MEMBER_STRING_ENCODING)
+ # Decode the value from the desired encoding (default UTF-8).
+ tree.attrib[attribute] = value.decode(MEMBER_STRING_ENCODING)
if self.text and not isinstance(self.text, unicode):
tree.text = self.text.decode(MEMBER_STRING_ENCODING)
else:
Added: trunk/conduit/modules/GoogleModule/atom/mock_service.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/atom/mock_service.py Sat Jun 7 07:56:04 2008
@@ -0,0 +1,243 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""MockService provides CRUD ops. for mocking calls to AtomPub services.
+
+ MockService: Exposes the publicly used methods of AtomService to provide
+ a mock interface which can be used in unit tests.
+"""
+
+import atom.service
+import pickle
+
+
+__author__ = 'api.jscudder (Jeffrey Scudder)'
+
+
+# Recordings contains pairings of HTTP MockRequest objects with MockHttpResponse objects.
+recordings = []
+# If set, the mock service HttpRequest are actually made through this object.
+real_request_handler = None
+
+def ConcealValueWithSha(source):
+ import sha
+ return sha.new(source[:-5]).hexdigest()
+
+def DumpRecordings(conceal_func=ConcealValueWithSha):
+ if conceal_func:
+ for recording_pair in recordings:
+ recording_pair[0].ConcealSecrets(conceal_func)
+ return pickle.dumps(recordings)
+
+def LoadRecordings(recordings_file_or_string):
+ if isinstance(recordings_file_or_string, str):
+ atom.mock_service.recordings = pickle.loads(recordings_file_or_string)
+ elif hasattr(recordings_file_or_string, 'read'):
+ atom.mock_service.recordings = pickle.loads(
+ recordings_file_or_string.read())
+
+def HttpRequest(service, operation, data, uri, extra_headers=None,
+ url_params=None, escape_params=True, content_type='application/atom+xml'):
+ """Simulates an HTTP call to the server, makes an actual HTTP request if
+ real_request_handler is set.
+
+ This function operates in two different modes depending on if
+ real_request_handler is set or not. If real_request_handler is not set,
+ HttpRequest will look in this module's recordings list to find a response
+ which matches the parameters in the function call. If real_request_handler
+ is set, this function will call real_request_handler.HttpRequest, add the
+ response to the recordings list, and respond with the actual response.
+
+ Args:
+ service: atom.AtomService object which contains some of the parameters
+ needed to make the request. The following members are used to
+ construct the HTTP call: server (str), additional_headers (dict),
+ port (int), and ssl (bool).
+ operation: str The HTTP operation to be performed. This is usually one of
+ 'GET', 'POST', 'PUT', or 'DELETE'
+ data: ElementTree, filestream, list of parts, or other object which can be
+ converted to a string.
+ Should be set to None when performing a GET or PUT.
+ If data is a file-like object which can be read, this method will read
+ a chunk of 100K bytes at a time and send them.
+ If the data is a list of parts to be sent, each part will be evaluated
+ and sent.
+ uri: The beginning of the URL to which the request should be sent.
+ Examples: '/', '/base/feeds/snippets',
+ '/m8/feeds/contacts/default/base'
+ extra_headers: dict of strings. HTTP headers which should be sent
+ in the request. These headers are in addition to those stored in
+ service.additional_headers.
+ url_params: dict of strings. Key value pairs to be added to the URL as
+ URL parameters. For example {'foo':'bar', 'test':'param'} will
+ become ?foo=bar&test=param.
+ escape_params: bool default True. If true, the keys and values in
+ url_params will be URL escaped when the form is constructed
+ (Special characters converted to %XX form.)
+ content_type: str The MIME type for the data being sent. Defaults to
+ 'application/atom+xml', this is only used if data is set.
+ """
+ full_uri = atom.service.BuildUri(uri, url_params, escape_params)
+ (server, port, ssl, uri) = atom.service.ProcessUrl(service, uri)
+ current_request = MockRequest(operation, full_uri, host=server, ssl=ssl,
+ data=data, extra_headers=extra_headers, url_params=url_params,
+ escape_params=escape_params, content_type=content_type)
+ # If the request handler is set, we should actually make the request using
+ # the request handler and record the response to replay later.
+ if real_request_handler:
+ response = real_request_handler.HttpRequest(service, operation, data, uri,
+ extra_headers=extra_headers, url_params=url_params,
+ escape_params=escape_params, content_type=content_type)
+ # TODO: need to copy the HTTP headers from the real response into the
+ # recorded_response.
+ recorded_response = MockHttpResponse(body=response.read(),
+ status=response.status, reason=response.reason)
+ # Insert a tuple which maps the request to the response object returned
+ # when making an HTTP call using the real_request_handler.
+ recordings.append((current_request, recorded_response))
+ return recorded_response
+ else:
+ # Look through available recordings to see if one matches the current
+ # request.
+ for request_response_pair in recordings:
+ if request_response_pair[0].IsMatch(current_request):
+ return request_response_pair[1]
+ return None
+
+
+class MockRequest(object):
+ """Represents a request made to an AtomPub server.
+
+ These objects are used to determine if a client request matches a recorded
+ HTTP request to determine what the mock server's response will be.
+ """
+
+ def __init__(self, operation, uri, host=None, ssl=False, port=None,
+ data=None, extra_headers=None, url_params=None, escape_params=True,
+ content_type='application/atom+xml'):
+ """Constructor for a MockRequest
+
+ Args:
+ operation: str One of 'GET', 'POST', 'PUT', or 'DELETE' this is the
+ HTTP operation requested on the resource.
+ uri: str The URL describing the resource to be modified or feed to be
+ retrieved. This should include the protocol (http/https) and the host
+ (aka domain). For example, these are some valud full_uris:
+ 'http://example.com', 'https://www.google.com/accounts/ClientLogin'
+ host: str (optional) The server name which will be placed at the
+ beginning of the URL if the uri parameter does not begin with 'http'.
+ Examples include 'example.com', 'www.google.com', 'www.blogger.com'.
+ ssl: boolean (optional) If true, the request URL will begin with https
+ instead of http.
+ data: ElementTree, filestream, list of parts, or other object which can be
+ converted to a string. (optional)
+ Should be set to None when performing a GET or PUT.
+ If data is a file-like object which can be read, the constructor
+ will read the entire file into memory. If the data is a list of
+ parts to be sent, each part will be evaluated and stored.
+ extra_headers: dict (optional) HTTP headers included in the request.
+ url_params: dict (optional) Key value pairs which should be added to
+ the URL as URL parameters in the request. For example uri='/',
+ url_parameters={'foo':'1','bar':'2'} could become '/?foo=1&bar=2'.
+ escape_params: boolean (optional) Perform URL escaping on the keys and
+ values specified in url_params. Defaults to True.
+ content_type: str (optional) Provides the MIME type of the data being
+ sent.
+ """
+ self.operation = operation
+ self.uri = _ConstructFullUrlBase(uri, host=host, ssl=ssl)
+ self.data = data
+ self.extra_headers = extra_headers
+ self.url_params = url_params or {}
+ self.escape_params = escape_params
+ self.content_type = content_type
+
+ def ConcealSecrets(self, conceal_func):
+ """Conceal secret data in this request."""
+ if self.extra_headers.has_key('Authorization'):
+ self.extra_headers['Authorization'] = conceal_func(
+ self.extra_headers['Authorization'])
+
+ def IsMatch(self, other_request):
+ """Check to see if the other_request is equivalent to this request.
+
+ Used to determine if a recording matches an incoming request so that a
+ recorded response should be sent to the client.
+
+ The matching is not exact, only the operation and URL are examined
+ currently.
+
+ Args:
+ other_request: MockRequest The request which we want to check this
+ (self) MockRequest against to see if they are equivalent.
+ """
+ # More accurate matching logic will likely be required.
+ return (self.operation == other_request.operation and self.uri ==
+ other_request.uri)
+
+
+def _ConstructFullUrlBase(uri, host=None, ssl=False):
+ """Puts URL components into the form http(s)://full.host.strinf/uri/path
+
+ Used to construct a roughly canonical URL so that URLs which begin with
+ 'http://example.com/' can be compared to a uri of '/' when the host is
+ set to 'example.com'
+
+ If the uri contains 'http://host' already, the host and ssl parameters
+ are ignored.
+
+ Args:
+ uri: str The path component of the URL, examples include '/'
+ host: str (optional) The host name which should prepend the URL. Example:
+ 'example.com'
+ ssl: boolean (optional) If true, the returned URL will begin with https
+ instead of http.
+
+ Returns:
+ String which has the form http(s)://example.com/uri/string/contents
+ """
+ if uri.startswith('http'):
+ return uri
+ if ssl:
+ return 'https://%s%s' % (host, uri)
+ else:
+ return 'http://%s%s' % (host, uri)
+
+
+class MockHttpResponse(object):
+ """Returned from MockService crud methods as the server's response."""
+
+ def __init__(self, body=None, status=None, reason=None, headers=None):
+ """Construct a mock HTTPResponse and set members.
+
+ Args:
+ body: str (optional) The HTTP body of the server's response.
+ status: int (optional)
+ reason: str (optional)
+ headers: dict (optional)
+ """
+ self.body = body
+ self.status = status
+ self.reason = reason
+ self.headers = headers or {}
+
+ def read(self):
+ return self.body
+
+ def getheader(self, header_name):
+ return self.headers[header_name]
+
Modified: trunk/conduit/modules/GoogleModule/atom/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/atom/service.py (original)
+++ trunk/conduit/modules/GoogleModule/atom/service.py Sat Jun 7 07:56:04 2008
@@ -499,13 +499,18 @@
"""Processes a passed URL. If the URL does not begin with https?, then
the default value for server is used"""
- server = service.server
- if for_proxy:
- port = 80
- ssl = False
+ server = None
+ port = 80
+ ssl = False
+ if hasattr(service, 'server'):
+ server = service.server
else:
- port = service.port
- ssl = service.ssl
+ server = service
+ if not for_proxy:
+ if hasattr(service, 'port'):
+ port = service.port
+ if hasattr(service, 'ssl'):
+ ssl = service.ssl
uri = url
m = URL_REGEX.match(url)
Modified: trunk/conduit/modules/GoogleModule/gdata/Makefile.am
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/Makefile.am (original)
+++ trunk/conduit/modules/GoogleModule/gdata/Makefile.am Sat Jun 7 07:56:04 2008
@@ -1,4 +1,4 @@
-SUBDIRS = apps base calendar codesearch contacts docs exif geo media photos spreadsheet
+SUBDIRS = apps base blogger calendar codesearch contacts docs exif geo media photos spreadsheet youtube
conduit_handlersdir = $(libdir)/conduit/modules/GoogleModule/gdata
conduit_handlers_PYTHON = __init__.py auth.py service.py
Modified: trunk/conduit/modules/GoogleModule/gdata/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/__init__.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/__init__.py Sat Jun 7 07:56:04 2008
@@ -252,7 +252,7 @@
# text node.
def __SetId(self, id):
self.__id = id
- if id is not None:
+ if id is not None and id.text is not None:
self.__id.text = id.text.strip()
id = property(__GetId, __SetId)
@@ -302,7 +302,7 @@
def __SetId(self, id):
self.__id = id
- if id is not None:
+ if id is not None and id.text is not None:
self.__id.text = id.text.strip()
id = property(__GetId, __SetId)
Modified: trunk/conduit/modules/GoogleModule/gdata/apps/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/apps/service.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/apps/service.py Sat Jun 7 07:56:04 2008
@@ -24,7 +24,7 @@
except ImportError:
try:
from xml.etree import ElementTree
- except Import Error:
+ except ImportError:
from elementtree import ElementTree
import urllib
import gdata
@@ -91,6 +91,15 @@
def _baseURL(self):
return "/a/feeds/%s" % self.domain
+ def GetGenaratorFromLinkFinder(self, link_finder, func):
+ """returns a generator for pagination"""
+ yield link_finder
+ next = link_finder.GetNextLink()
+ while next is not None:
+ next_feed = func(str(self.Get(next.href)))
+ yield next_feed
+ next = next_feed.GetNextLink()
+
def AddAllElementsFromAllPages(self, link_finder, func):
"""retrieve all pages and add all elements"""
next = link_finder.GetNextLink()
@@ -204,7 +213,6 @@
uri = "%s/emailList/%s" % (self._baseURL(), API_VER)
email_list_entry = gdata.apps.EmailListEntry()
email_list_entry.email_list = gdata.apps.EmailList(name=list_name)
-
try:
return gdata.apps.EmailListEntryFromString(
str(self.Post(email_list_entry, uri)))
@@ -361,10 +369,17 @@
except gdata.service.RequestError, e:
raise AppsForYourDomainException(e.args[0])
+ def GetGeneratorForAllUsers(self):
+ """Retrieve a generator for all users in this domain."""
+ first_page = self.RetrievePageOfUsers()
+ return self.GetGenaratorFromLinkFinder(first_page,
+ gdata.apps.UserFeedFromString)
+
def RetrieveAllUsers(self):
- """Retrieve all users in this domain."""
+ """Retrieve all users in this domain. OBSOLETE"""
ret = self.RetrievePageOfUsers()
# pagination
return self.AddAllElementsFromAllPages(
ret, gdata.apps.UserFeedFromString)
+
Modified: trunk/conduit/modules/GoogleModule/gdata/auth.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/auth.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/auth.py Sat Jun 7 07:56:04 2008
@@ -75,7 +75,8 @@
"""
for response_line in http_body.splitlines():
if response_line.startswith('Auth='):
- return 'GoogleLogin auth=%s' % response_line.lstrip('Auth=')
+ # Strip off the leading Auth= and return the Authorization value.
+ return 'GoogleLogin auth=%s' % response_line[5:]
return None
@@ -107,10 +108,11 @@
if response_line.startswith('Error=CaptchaRequired'):
contains_captcha_challenge = True
elif response_line.startswith('CaptchaToken='):
- captcha_parameters['token'] = response_line.lstrip('CaptchaToken=')
+ # Strip off the leading CaptchaToken=
+ captcha_parameters['token'] = response_line[13:]
elif response_line.startswith('CaptchaUrl='):
captcha_parameters['url'] = '%s%s' % (captcha_base_url,
- response_line.lstrip('CaptchaUrl='))
+ response_line[11:])
if contains_captcha_challenge:
return captcha_parameters
else:
@@ -191,6 +193,7 @@
"""
for response_line in http_body.splitlines():
if response_line.startswith('Token='):
- auth_token = response_line.lstrip('Token=')
+ # Strip off Token= and construct the Authorization value.
+ auth_token = response_line[6:]
return 'AuthSub token=%s' % auth_token
return None
Added: trunk/conduit/modules/GoogleModule/gdata/blogger/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/blogger/Makefile.am Sat Jun 7 07:56:04 2008
@@ -0,0 +1,5 @@
+conduit_handlersdir = $(libdir)/conduit/modules/GoogleModule/gdata/blogger
+conduit_handlers_PYTHON = __init__.py service.py
+
+clean-local:
+ rm -rf *.pyc *.pyo
Added: trunk/conduit/modules/GoogleModule/gdata/blogger/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/blogger/__init__.py Sat Jun 7 07:56:04 2008
@@ -0,0 +1,174 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2007, 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Contains extensions to Atom objects used with Blogger."""
+
+
+__author__ = 'api.jscudder (Jeffrey Scudder)'
+
+
+import atom
+import gdata
+import re
+
+
+LABEL_SCHEME = 'http://www.blogger.com/atom/ns#'
+
+
+class BloggerEntry(gdata.GDataEntry):
+ """Adds convenience methods inherited by all Blogger entries."""
+
+ blog_name_pattern = re.compile('(http://)(\w*)')
+ blog_id_pattern = re.compile('(tag:blogger.com,1999:blog-)(\w*)')
+
+ def GetBlogId(self):
+ """Extracts the Blogger id of this blog.
+ This method is useful when contructing URLs by hand. The blog id is
+ often used in blogger operation URLs. This should not be confused with
+ the id member of a BloggerBlog. The id element is the Atom id XML element.
+ The blog id which this method returns is a part of the Atom id.
+
+ Returns:
+ The blog's unique id as a string.
+ """
+ if self.id.text:
+ return self.blog_id_pattern.match(self.id.text).group(2)
+ return None
+
+ def GetBlogName(self):
+ """Finds the name of this blog as used in the 'alternate' URL.
+ An alternate URL is in the form 'http://blogName.blogspot.com/'. For an
+ entry representing the above example, this method would return 'blogName'.
+
+ Returns:
+ The blog's URL name component as a string.
+ """
+ for link in self.link:
+ if link.rel == 'alternate':
+ return self.blog_name_pattern.match(link.href).group(2)
+ return None
+
+
+class BlogCommentEntry(BloggerEntry):
+ """Describes a blog comment entry in the feed of a blog's comments.
+
+ """
+ pass
+
+
+def BlogCommentEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(BlogCommentEntry, xml_string)
+
+
+class BlogCommentFeed(gdata.GDataFeed):
+ """Describes a feed of a blog's comments.
+
+ """
+ pass
+
+
+def BlogCommentFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(BlogCommentFeed, xml_string)
+
+
+class BlogEntry(BloggerEntry):
+ """Describes a blog entry in the feed of a user's blogs.
+
+ """
+ pass
+
+
+def BlogEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(BlogEntry, xml_string)
+
+
+class BlogFeed(gdata.GDataFeed):
+ """Describes a feed of a user's blogs.
+
+ """
+
+
+
+def BlogFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(BlogFeed, xml_string)
+
+
+class BlogPostEntry(BloggerEntry):
+ """Describes a blog post entry in the feed of a blog's posts.
+
+ """
+
+ def AddLabel(self, label):
+ """Adds a label to the blog post.
+
+ The label is represented by an Atom category element, so this method
+ is shorthand for appending a new atom.Category object.
+
+ Args:
+ label: str
+ """
+ self.category.append(atom.Category(scheme=LABEL_SCHEME, term=label))
+
+
+def BlogPostEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(BlogPostEntry, xml_string)
+
+
+class BlogPostFeed(gdata.GDataFeed):
+ """Describes a feed of a blog's posts.
+
+ """
+ pass
+
+
+def BlogPostFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(BlogPostFeed, xml_string)
+
+
+class BloggerLink(atom.Link):
+ """Extends the base Link class with Blogger extensions.
+
+ """
+ pass
+
+
+def BloggerLinkFromString(xml_string):
+ return atom.CreateClassFromXMLString(BloggerLink, xml_string)
+
+
+class PostCommentEntry(BloggerEntry):
+ """Describes a blog post comment entry in the feed of a blog post's comments.
+
+ """
+ pass
+
+
+def PostCommentEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(PostCommentEntry, xml_string)
+
+
+class PostCommentFeed(gdata.GDataFeed):
+ """Describes a feed of a blog post's comments.
+
+ """
+ pass
+
+
+def PostCommentFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(PostCommentFeed, xml_string)
+
+
Added: trunk/conduit/modules/GoogleModule/gdata/blogger/service.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/blogger/service.py Sat Jun 7 07:56:04 2008
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Classes to interact with the Blogger server."""
+
+__author__ = 'api.jscudder (Jeffrey Scudder)'
+
+import gdata.service
+import gdata.blogger
+
+
+class BloggerService(gdata.service.GDataService):
+
+ def __init__(self, email=None, password=None, source=None,
+ server=None, api_key=None,
+ additional_headers=None):
+ gdata.service.GDataService.__init__(self, email=email, password=password,
+ service='blogger', source=source,
+ server=server,
+ additional_headers=additional_headers)
+
+ def GetBlogFeed(self, uri):
+ return self.Get(uri, converter=gdata.blogger.BlogFeedFromString)
+
+ def GetBlogCommentFeed(self, uri):
+ return self.Get(uri, converter=gdata.blogger.BlogCommentFeedFromString)
+
+ def GetBlogPostFeed(self, uri):
+ return self.Get(uri, converter=gdata.blogger.BlogPostFeedFromString)
+
+ def GetPostCommentFeed(self, uri):
+ return self.Get(uri, converter=gdata.blogger.PostCommentFeedFromString)
+
+ def AddPost(self, entry, blog_id=None, uri=None):
+ if blog_id:
+ uri = 'http://www.blogger.com/feeds/%s/posts/default' % blog_id
+ return self.Post(entry, uri,
+ converter=gdata.blogger.BlogPostEntryFromString)
+
+ def UpdatePost(self, entry, uri=None):
+ if not uri:
+ uri = entry.GetEditLink().href
+ return self.Put(entry, uri,
+ converter=gdata.blogger.BlogPostEntryFromString)
+
+ def PostComment(self, comment_entry, blog_id=None, post_id=None, uri=None):
+ # TODO
+ pass
+
Modified: trunk/conduit/modules/GoogleModule/gdata/calendar/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/calendar/__init__.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/calendar/__init__.py Sat Jun 7 07:56:04 2008
@@ -880,12 +880,13 @@
_attributes = gdata.BatchFeed._attributes.copy()
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
[CalendarEventEntry])
+ _children['{%s}timezone' % GCAL_NAMESPACE] = ('timezone', Timezone)
def __init__(self, author=None, category=None, contributor=None,
generator=None, icon=None, atom_id=None, link=None, logo=None,
rights=None, subtitle=None, title=None, updated=None, entry=None,
total_results=None, start_index=None, items_per_page=None,
- interrupted=None,
+ interrupted=None, timezone=None,
extension_elements=None, extension_attributes=None, text=None):
gdata.BatchFeed.__init__(self, author=author, category=category,
contributor=contributor, generator=generator,
@@ -899,6 +900,7 @@
extension_elements=extension_elements,
extension_attributes=extension_attributes,
text=text)
+ self.timezone = timezone
def CalendarListEntryFromString(xml_string):
Modified: trunk/conduit/modules/GoogleModule/gdata/calendar/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/calendar/service.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/calendar/service.py Sat Jun 7 07:56:04 2008
@@ -440,10 +440,12 @@
def __init__(self, user='default', visibility='private', projection='full',
text_query=None, params=None, categories=None):
- gdata.service.Query.__init__(self, feed='http://www.google.com/calendar/feeds/'+
- '%s/%s/%s' % (user, visibility, projection,),
- text_query=text_query, params=params,
- categories=categories)
+ gdata.service.Query.__init__(self,
+ feed='http://www.google.com/calendar/feeds/%s/%s/%s' % (
+ urllib.quote(user),
+ urllib.quote(visibility),
+ urllib.quote(projection)),
+ text_query=text_query, params=params, categories=categories)
def _GetStartMin(self):
if 'start-min' in self.keys():
Modified: trunk/conduit/modules/GoogleModule/gdata/media/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/media/__init__.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/media/__init__.py Sat Jun 7 07:56:04 2008
@@ -53,9 +53,11 @@
import gdata
MEDIA_NAMESPACE = 'http://search.yahoo.com/mrss/'
+YOUTUBE_NAMESPACE = 'http://gdata.youtube.com/schemas/2007'
class MediaBaseElement(atom.AtomBase):
- """Base class for elements in the MEDIA_NAMESPACE. To add new elements, you only need to add the element tag name to self._tag
+ """Base class for elements in the MEDIA_NAMESPACE.
+ To add new elements, you only need to add the element tag name to self._tag
"""
_tag = ''
@@ -101,18 +103,20 @@
_attributes['medium'] = 'medium'
_attributes['type'] = 'type'
_attributes['fileSize'] = 'fileSize'
+
def __init__(self, url=None, width=None, height=None,
- medium=None, content_type=None, fileSize=None,
+ medium=None, content_type=None, fileSize=None, format=None,
extension_elements=None, extension_attributes=None, text=None):
MediaBaseElement.__init__(self, extension_elements=extension_elements,
- extension_attributes=extension_attributes,
- text=text)
+ extension_attributes=extension_attributes,
+ text=text)
self.url = url
self.width = width
self.height = height
self.medium = medium
self.type = content_type
self.fileSize = fileSize
+
def ContentFromString(xml_string):
return atom.CreateClassFromXMLString(Content, xml_string)
@@ -150,8 +154,8 @@
def __init__(self, description_type=None,
extension_elements=None, extension_attributes=None, text=None):
MediaBaseElement.__init__(self, extension_elements=extension_elements,
- extension_attributes=extension_attributes,
- text=text)
+ extension_attributes=extension_attributes,
+ text=text)
self.type = description_type
def DescriptionFromString(xml_string):
@@ -197,8 +201,8 @@
def __init__(self, url=None, width=None, height=None,
extension_attributes=None, text=None, extension_elements=None):
MediaBaseElement.__init__(self, extension_elements=extension_elements,
- extension_attributes=extension_attributes,
- text=text)
+ extension_attributes=extension_attributes,
+ text=text)
self.url = url
self.width = width
self.height = height
@@ -218,15 +222,76 @@
def __init__(self, title_type=None,
extension_attributes=None, text=None, extension_elements=None):
MediaBaseElement.__init__(self, extension_elements=extension_elements,
- extension_attributes=extension_attributes,
- text=text)
+ extension_attributes=extension_attributes,
+ text=text)
self.type = title_type
def TitleFromString(xml_string):
return atom.CreateClassFromXMLString(Title, xml_string)
+class Player(MediaBaseElement):
+ """(string) Contains the embeddable player URL for the entry's media content
+ if the media is a video.
+
+ Attributes:
+ url: Always set to plain
+ """
+
+ _tag = 'player'
+ _attributes = atom.AtomBase._attributes.copy()
+ _attributes['url'] = 'url'
+
+ def __init__(self, player_url=None,
+ extension_attributes=None, extension_elements=None):
+ MediaBaseElement.__init__(self, extension_elements=extension_elements,
+ extension_attributes=extension_attributes)
+ self.url= player_url
+
+class Private(atom.AtomBase):
+ """The YouTube Private element"""
+ _tag = 'private'
+ _namespace = YOUTUBE_NAMESPACE
+
+class Duration(atom.AtomBase):
+ """The YouTube Duration element"""
+ _tag = 'duration'
+ _namespace = YOUTUBE_NAMESPACE
+ _attributes = atom.AtomBase._attributes.copy()
+ _attributes['seconds'] = 'seconds'
+
+class Category(MediaBaseElement):
+ """The mediagroup:category element"""
+
+ _tag = 'category'
+ _attributes = atom.AtomBase._attributes.copy()
+ _attributes['term'] = 'term'
+ _attributes['scheme'] = 'scheme'
+ _attributes['label'] = 'label'
+
+ def __init__(self, term=None, scheme=None, label=None, text=None,
+ extension_elements=None, extension_attributes=None):
+ """Constructor for Category
+
+ Args:
+ term: str
+ scheme: str
+ label: str
+ text: str The text data in the this element
+ extension_elements: list A list of ExtensionElement instances
+ extension_attributes: dict A dictionary of attribute value string pairs
+ """
+
+ self.term = term
+ self.scheme = scheme
+ self.label = label
+ self.text = text
+ self.extension_elements = extension_elements or []
+ self.extension_attributes = extension_attributes or {}
+
+
class Group(MediaBaseElement):
"""Container element for all media elements.
- The <media:group> element can appear as a child of an album or photo entry."""
+ The <media:group> element can appear as a child of an album, photo or
+ video entry."""
_tag = 'group'
_children = atom.AtomBase._children.copy()
@@ -236,18 +301,29 @@
_children['{%s}keywords' % MEDIA_NAMESPACE] = ('keywords', Keywords)
_children['{%s}thumbnail' % MEDIA_NAMESPACE] = ('thumbnail', [Thumbnail,])
_children['{%s}title' % MEDIA_NAMESPACE] = ('title', Title)
+ _children['{%s}category' % MEDIA_NAMESPACE] = ('category', Category)
+ _children['{%s}duration' % YOUTUBE_NAMESPACE] = ('duration', Duration)
+ _children['{%s}private' % YOUTUBE_NAMESPACE] = ('private', Private)
+ _children['{%s}player' % MEDIA_NAMESPACE] = ('player', Player)
def __init__(self, content=None, credit=None, description=None, keywords=None,
- thumbnail=None, title=None,
- extension_elements=None, extension_attributes=None, text=None):
+ thumbnail=None, title=None, duration=None, private=None,
+ category=None, player=None, extension_elements=None,
+ extension_attributes=None, text=None):
+
MediaBaseElement.__init__(self, extension_elements=extension_elements,
- extension_attributes=extension_attributes,
- text=text)
+ extension_attributes=extension_attributes,
+ text=text)
self.content=content
self.credit=credit
self.description=description
self.keywords=keywords
self.thumbnail=thumbnail or []
self.title=title
+ self.duration=duration
+ self.private=private
+ self.category=category
+ self.player=player
+
def GroupFromString(xml_string):
return atom.CreateClassFromXMLString(Group, xml_string)
Modified: trunk/conduit/modules/GoogleModule/gdata/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/service.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/service.py Sat Jun 7 07:56:04 2008
@@ -114,6 +114,8 @@
class UnexpectedReturnType(Error):
pass
+class BadAuthenticationServiceURL(Error):
+ pass
class GDataService(atom.service.AtomService):
"""Contains elements needed for GData login and CRUD request headers.
@@ -123,7 +125,7 @@
"""
def __init__(self, email=None, password=None, account_type='HOSTED_OR_GOOGLE',
- service=None, source=None, server=None,
+ service=None, auth_service_url=None, source=None, server=None,
additional_headers=None, handler=None):
"""Creates an object of type GDataService.
@@ -138,6 +140,8 @@
GOOGLE account. Default value: 'HOSTED_OR_GOOGLE'.
service: string (optional) The desired service for which credentials
will be obtained.
+ auth_service_url: string (optional) User-defined auth token request URL
+ allows users to explicitly specify where to send auth token requests.
source: string (optional) The name of the user's application.
server: string (optional) The name of the server to which a connection
will be opened. Default value: 'base.google.com'.
@@ -152,6 +156,7 @@
self.password = password
self.account_type = account_type
self.service = service
+ self.auth_service_url = auth_service_url
self.server = server
self.additional_headers = additional_headers or {}
self.handler = handler or http_request_handler
@@ -233,6 +238,17 @@
doc="""Get the captcha URL for a login request.""")
def GetAuthSubToken(self):
+ """Returns the AuthSub Token after removing the AuthSub Authorization
+ Label.
+
+ The AuthSub Authorization Label reads: "AuthSub token"
+
+ Returns:
+ If the AuthSub Token is set AND it begins with the AuthSub
+ Authorization Label, the AuthSub Token is returned minus the AuthSub
+ Label. If the AuthSub Token does not start with the AuthSub
+ Authorization Label or it is not set, None is returned.
+ """
if self.__auth_token.startswith(AUTHSUB_AUTH_LABEL):
# Strip off the leading 'AUTHSUB_AUTH_LABEL=' and just return the
# token value.
@@ -261,7 +277,8 @@
def __SetSource(self, new_source):
self.__source = new_source
# Update the UserAgent header to include the new application name.
- self.additional_headers['User-Agent'] = '%s GData-Python/1.0.12.1' % self.__source
+ self.additional_headers['User-Agent'] = '%s GData-Python/1.0.13' % (
+ self.__source)
source = property(__GetSource, __SetSource,
doc="""The source is the name of the application making the request.
@@ -295,8 +312,15 @@
self.password, self.service, self.source, self.account_type,
captcha_token, captcha_response)
+ # If the user has defined their own authentication service URL,
+ # send the ClientLogin requests to this URL:
+ if not self.auth_service_url:
+ auth_request_url = AUTH_SERVER_HOST + '/accounts/ClientLogin'
+ else:
+ auth_request_url = self.auth_service_url
+
auth_response = self.handler.HttpRequest(self, 'POST', request_body,
- AUTH_SERVER_HOST + '/accounts/ClientLogin',
+ auth_request_url,
extra_headers={'Content-Length':str(len(request_body))},
content_type='application/x-www-form-urlencoded')
response_body = auth_response.read()
@@ -311,7 +335,7 @@
# Examine each line to find the error type and the captcha token and
# captch URL if they are present.
captcha_parameters = gdata.auth.GetCaptchChallenge(response_body,
- captcha_base_url='%saccounts/' % AUTH_SERVER_HOST)
+ captcha_base_url='%s/accounts/' % AUTH_SERVER_HOST)
if captcha_parameters:
self.__captcha_token = captcha_parameters['token']
self.__captcha_url = captcha_parameters['url']
@@ -324,9 +348,16 @@
self.__captcha_token = None
self.__captcha_url = None
raise Error, 'Server responded with a 403 code'
+ elif auth_response.status == 302:
+ self.__captcha_token = None
+ self.__captcha_url = None
+ # Google tries to redirect all bad URLs back to
+ # http://www.google.<locale>. If a redirect
+ # attempt is made, assume the user has supplied an incorrect authentication URL
+ raise BadAuthenticationServiceURL, 'Server responded with a 302 code.'
def ClientLogin(self, username, password, account_type=None, service=None,
- source=None, captcha_token=None, captcha_response=None):
+ auth_service_url=None, source=None, captcha_token=None, captcha_response=None):
"""Convenience method for authenticating using ProgrammaticLogin.
Sets values for email, password, and other optional members.
@@ -336,6 +367,7 @@
password:
account_type: string (optional)
service: string (optional)
+ auth_service_url: string (optional)
captcha_token: string (optional)
captcha_response: string (optional)
"""
@@ -348,6 +380,8 @@
self.service = service
if source:
self.source = source
+ if auth_service_url:
+ self.auth_service_url = auth_service_url
self.ProgrammaticLogin(captcha_token, captcha_response)
@@ -522,7 +556,8 @@
"""
response_handle = self.handler.HttpRequest(self, 'GET', None, uri,
extra_headers=extra_headers)
- return gdata.MediaSource(response_handle, response_handle.getheader('Content-Type'),
+ return gdata.MediaSource(response_handle, response_handle.getheader(
+ 'Content-Type'),
response_handle.getheader('Content-Length'))
def GetEntry(self, uri, extra_headers=None):
@@ -908,7 +943,7 @@
"""
self.feed = feed
- self.categories = categories or []
+ self.categories = []
if text_query:
self.text_query = text_query
if isinstance(params, dict):
Modified: trunk/conduit/modules/GoogleModule/gdata/test_data.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/test_data.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/test_data.py Sat Jun 7 07:56:04 2008
@@ -2074,153 +2074,188 @@
</feed>"""
-YOU_TUBE_VIDEO_FEED = """<?xml version='1.0' encoding='UTF-8'?>
-<feed xmlns='http://www.w3.org/2005/Atom'
- xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
- xmlns:gml='http://www.opengis.net/gml'
- xmlns:georss='http://www.georss.org/georss'
- xmlns:media='http://search.yahoo.com/mrss/'
- xmlns:yt='http://gdata.youtube.com/schemas/2007'
- xmlns:gd='http://schemas.google.com/g/2005'>
- <id>http://gdata.youtube.com/feeds/api/standardfeeds/top_rated</id>
- <updated>2008-02-21T18:57:10.801Z</updated>
+YOUTUBE_VIDEO_FEED = """<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gml='http://www.opengis.net/gml' xmlns:georss='http://www.georss.org/georss' xmlns:media='http://search.yahoo.com/mrss/' xmlns:yt='http://gdata.youtube.com/schemas/2007' xmlns:gd='http://schemas.google.com/g/2005'><id>http://gdata.youtube.com/feeds/api/standardfeeds/top_rated</id><updated>2008-05-14T02:24:07.000-07:00</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><title type='text'>Top Rated</title><logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo><link rel='alternate' type='text/html' href='http://www.youtube.com/browse?s=tr'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated'/><link rel='self' type='application/atom+xml' href='ht
tp://gdata.youtube.com/feeds/api/standardfeeds/top_rated?start-index=1&max-results=25'/><link rel='next' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated?start-index=26&max-results=25'/><author><name>YouTube</name><uri>http://www.youtube.com/</uri></author><generator version='beta' uri='http://gdata.youtube.com/'>YouTube data API</generator><openSearch:totalResults>100</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage>
+<entry><id>http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8</id><published>2008-03-20T10:17:27.000-07:00</published><updated>2008-05-14T04:26:37.000-07:00</updated><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='karyn'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='garcia'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='me'/><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='boyfriend'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='por'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='te'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='odeio'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='amar'/><category scheme='http://gdata.youtube.com/
schemas/2007/categories.cat' term='Music' label='Music'/><title type='text'>Me odeio por te amar - KARYN GARCIA</title><content type='text'>http://www.karyngarcia.com.br</content><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=C71ypXYGho8'/><link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8/related'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated/C71ypXYGho8'/><author><name>TvKarynGarcia</name><uri>http://gdata.youtube.com/feeds/api/users/tvkaryngarcia</uri></author><media:group><media:title type='plain'>Me odeio por te amar - KARYN GARCIA</media:title><media:description type='plain'>http://www.karyngarcia.com.br</media:description><media:keywords>amar, boyfriend, garcia, karyn, me, odeio, por, te</media:keywords><yt:duration seconds='203'/><media:category label='Music' scheme='http://g
data.youtube.com/schemas/2007/categories.cat'>Music</media:category><media:content url='http://www.youtube.com/v/C71ypXYGho8' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='203' yt:format='5'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQmPhgZ2pXK9CxMYDSANFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='203' yt:format='1'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQmPhgZ2pXK9CxMYESARFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='203' yt:format='6'/><media:player url='http://www.youtube.com/watch?v=C71ypXYGho8'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/2.jpg' height='97' width='130' time='00:01:41.500'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/1.jpg' height='97' width='130' time='00:00:50.750'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/3.jpg' height='97' width='130'
time='00:02:32.250'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/0.jpg' height='240' width='320' time='00:01:41.500'/></media:group><yt:statistics viewCount='138864' favoriteCount='2474'/><gd:rating min='1' max='5' numRaters='4626' average='4.95'/><gd:comments><gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8/comments' countHint='27'/></gd:comments></entry>
+<entry><id>http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw</id><published>2008-02-15T04:31:45.000-08:00</published><updated>2008-05-14T05:09:42.000-07:00</updated><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='extreme'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='cam'/><category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' term='Sports' label='Sports'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='alcala'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='kani'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='helmet'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='campillo'/><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='pato'/><category scheme='
http://gdata.youtube.com/schemas/2007/keywords.cat' term='dirt'/><title type='text'>extreme helmet cam Kani, Keil and Pato</title><content type='text'>trimmed</content><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=gsVaTyb1tBw'/><link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw/responses'/><link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw/related'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/recently_featured/gsVaTyb1tBw'/><author><name>peraltamagic</name><uri>http://gdata.youtube.com/feeds/api/users/peraltamagic</uri></author><media:group><media:title type='plain'>extreme helmet cam Kani, Keil and Pato</media:title><media:description type='plain'>trimmed</media:description><media:keywords
>alcala, cam, campillo, dirt, extreme, helmet, kani, pato</media:keywords><yt:duration seconds='31'/><media:category label='Sports' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>Sports</media:category><media:content url='http://www.youtube.com/v/gsVaTyb1tBw' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='31' yt:format='5'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQkctPUmT1rFghMYDSANFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='31' yt:format='1'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQkctPUmT1rFghMYESARFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='31' yt:format='6'/><media:player url='http://www.youtube.com/watch?v=gsVaTyb1tBw'/><media:thumbnail url='http://img.youtube.com/vi/gsVaTyb1tBw/2.jpg' height='97' width='130' time='00:00:15.500'/><media:thumbnail url='http://img.youtube.com/vi/gsVaTyb1
tBw/1.jpg' height='97' width='130' time='00:00:07.750'/><media:thumbnail url='http://img.youtube.com/vi/gsVaTyb1tBw/3.jpg' height='97' width='130' time='00:00:23.250'/><media:thumbnail url='http://img.youtube.com/vi/gsVaTyb1tBw/0.jpg' height='240' width='320' time='00:00:15.500'/></media:group><yt:statistics viewCount='489941' favoriteCount='561'/><gd:rating min='1' max='5' numRaters='1255' average='4.11'/><gd:comments><gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw/comments' countHint='1116'/></gd:comments></entry>
+</feed>"""
+
+YOUTUBE_ENTRY_PRIVATE = """<?xml version='1.0' encoding='utf-8'?>
+<entry xmlns='http://www.w3.org/2005/Atom'
+xmlns:media='http://search.yahoo.com/mrss/'
+xmlns:gd='http://schemas.google.com/g/2005'
+xmlns:yt='http://gdata.youtube.com/schemas/2007'
+xmlns:gml='http://www.opengis.net/gml'
+xmlns:georss='http://www.georss.org/georss'
+xmlns:app='http://purl.org/atom/app#'>
+ <id>http://gdata.youtube.com/feeds/videos/UMFI1hdm96E</id>
+ <published>2007-01-07T01:50:15.000Z</published>
+ <updated>2007-01-07T01:50:15.000Z</updated>
<category scheme='http://schemas.google.com/g/2005#kind'
- term='http://gdata.youtube.com/schemas/2007#video'/>
- <title type='text'>Top Rated</title>
- <logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo>
- <link rel='alternate' type='text/html'
- href='http://www.youtube.com/browser?s=tr'/>
- <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml'
- href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated'/>
- <link rel='self' type='application/atom+xml'
- href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated?start_index=1&max-results=25'/>
+ term='http://gdata.youtube.com/schemas/2007#video' />
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat'
+ term='barkley' />
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat'
+ term='singing' />
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat'
+ term='acoustic' />
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat'
+ term='cover' />
+ <category scheme='http://gdata.youtube.com/schemas/2007/categories.cat'
+ term='Music' label='Music' />
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat'
+ term='gnarls' />
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat'
+ term='music' />
+ <title type='text'>"Crazy (Gnarles Barkley)" - Acoustic Cover</title>
+ <content type='html'><div style="color: #000000;font-family:
+ Arial, Helvetica, sans-serif; font-size:12px; font-size: 12px;
+ width: 555px;"><table cellspacing="0" cellpadding="0"
+ border="0"><tbody><tr><td width="140"
+ valign="top" rowspan="2"><div style="border: 1px solid
+ #999999; margin: 0px 10px 5px 0px;"><a
+ href="http://www.youtube.com/watch?v=UMFI1hdm96E"><img
+ alt=""
+ src="http://img.youtube.com/vi/UMFI1hdm96E/2.jpg"></a></div></td>
+ <td width="256" valign="top"><div style="font-size:
+ 12px; font-weight: bold;"><a style="font-size: 15px;
+ font-weight: bold; font-decoration: none;"
+ href="http://www.youtube.com/watch?v=UMFI1hdm96E">&quot;Crazy
+ (Gnarles Barkley)&quot; - Acoustic Cover</a>
+ <br></div> <div style="font-size: 12px; margin:
+ 3px 0px;"><span>Gnarles Barkley acoustic cover
+ http://www.myspace.com/davidchoimusic</span></div></td>
+ <td style="font-size: 11px; line-height: 1.4em; padding-left:
+ 20px; padding-top: 1px;" width="146"
+ valign="top"><div><span style="color: #666666;
+ font-size: 11px;">From:</span> <a
+ href="http://www.youtube.com/profile?user=davidchoimusic">davidchoimusic</a></div>
+ <div><span style="color: #666666; font-size:
+ 11px;">Views:</span> 113321</div> <div
+ style="white-space: nowrap;text-align: left"><img
+ style="border: 0px none; margin: 0px; padding: 0px;
+ vertical-align: middle; font-size: 11px;" align="top" alt=""
+ src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif">
+ <img style="border: 0px none; margin: 0px; padding: 0px;
+ vertical-align: middle; font-size: 11px;" align="top" alt=""
+ src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif">
+ <img style="border: 0px none; margin: 0px; padding: 0px;
+ vertical-align: middle; font-size: 11px;" align="top" alt=""
+ src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif">
+ <img style="border: 0px none; margin: 0px; padding: 0px;
+ vertical-align: middle; font-size: 11px;" align="top" alt=""
+ src="http://gdata.youtube.com/static/images/icn_star_full_11x11.gif">
+ <img style="border: 0px none; margin: 0px; padding: 0px;
+ vertical-align: middle; font-size: 11px;" align="top" alt=""
+ src="http://gdata.youtube.com/static/images/icn_star_half_11x11.gif"></div>
+ <div style="font-size: 11px;">1005 <span style="color:
+ #666666; font-size:
+ 11px;">ratings</span></div></td></tr>
+ <tr><td><span style="color: #666666; font-size:
+ 11px;">Time:</span> <span style="color: #000000;
+ font-size: 11px; font-weight:
+ bold;">04:15</span></td> <td style="font-size:
+ 11px; padding-left: 20px;"><span style="color: #666666;
+ font-size: 11px;">More in</span> <a
+ href="http://www.youtube.com/categories_portal?c=10">Music</a></td></tr></tbody></table></div></content>
<link rel='self' type='application/atom+xml'
- href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated?start_index=26&max-results=25'/>
+ href='http://gdata.youtube.com/feeds/videos/UMFI1hdm96E' />
+ <link rel='alternate' type='text/html'
+ href='http://www.youtube.com/watch?v=UMFI1hdm96E' />
+ <link rel='http://gdata.youtube.com/schemas/2007#video.responses'
+ type='application/atom+xml'
+ href='http://gdata.youtube.com/feeds/videos/UMFI1hdm96E/responses' />
+ <link rel='http://gdata.youtube.com/schemas/2007#video.related'
+ type='application/atom+xml'
+ href='http://gdata.youtube.com/feeds/videos/UMFI1hdm96E/related' />
<author>
- <name>YouTube</name>
- <uri>http://www.youtube.com/</uri>
+ <name>davidchoimusic</name>
+ <uri>http://gdata.youtube.com/feeds/users/davidchoimusic</uri>
</author>
- <generator version='beta'
- uri='http://gdata.youtube.com/'>YouTube data API</generator>
- <openSearch:totalResults>99</openSearch:totalResults>
- <openSearch:startIndex>1</openSearch:startIndex>
- <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
- <entry>
- <id>http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b</id>
- <published>2007-02-16T20:22:57.000Z</published>
- <updated>2007-02-16T20:22:57.000Z</updated>
- <category scheme="http://schemas.google.com/g/2005#kind"
- term="http://gdata.youtube.com/schemas/2007#video"/>
- <category scheme="http://gdata.youtube.com/schemas/2007/keywords.cat"
- term="Steventon"/>
- <category scheme="http://gdata.youtube.com/schemas/2007/keywords.cat"
- term="walk"/>
- <category scheme="http://gdata.youtube.com/schemas/2007/keywords.cat"
- term="Darcy"/>
- <category scheme="http://gdata.youtube.com/schemas/2007/categories.cat"
- term="Entertainment" label="Entertainment"/>
- <title type="text">My walk with Mr. Darcy</title>
- <content type="html"><div ... html content trimmed ...></content>
- <link rel="self" type="application/atom+xml"
- href="http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b"/>
- <link rel="alternate" type="text/html"
- href="http://www.youtube.com/watch?v=ZTUVgYoeN_b"/>
- <link rel="http://gdata.youtube.com/schemas/2007#video.responses"
- type="application/atom+xml"
- href="http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/responses"/>
- <link rel="http://gdata.youtube.com/schemas/2007#video.ratings"
- type="application/atom+xml"
- href="http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/ratings"/>
- <link rel="http://gdata.youtube.com/schemas/2007#video.complaints"
- type="application/atom+xml"
- href="http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/complaints"/>
- <link rel="http://gdata.youtube.com/schemas/2007#video.related"
- type="application/atom+xml"
- href="http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/related"/>
- <author>
- <name>Andy Samplo</name>
- <uri>http://gdata.youtube.com/feeds/api/users/andyland74</uri>
- </author>
<media:group>
- <media:title type="plain">Shopping for Coats</media:title>
- <media:description type="plain">
- What could make for more exciting video?
- </media:description>
- <media:keywords>Shopping, parkas</media:keywords>
- <yt:duration seconds="79"/>
- <media:category label="People"
- scheme="http://gdata.youtube.com/schemas/2007/categories.cat">People
- </media:category>
- <media:content
- url='http://www.youtube.com/v/ZTUVgYoeN_b'
- type='application/x-shockwave-flash' medium='video'
- isDefault='true' expression="full" duration='215' yt:format="5"/>
- <media:content
- url='rtsp://rtsp2.youtube.com/ChoLENy73bIAEQ1k30OPEgGDA==/0/0/0/video.3gp'
- type='video/3gpp' medium='video'
- expression="full" duration='215' yt:format="1"/>
- <media:content
- url='rtsp://rtsp2.youtube.com/ChoLENy73bIAEQ1k30OPEgGDA==/0/0/0/video.3gp'
- type='video/3gpp' medium='video'
- expression="full" duration='215' yt:format="6"/>
- <media:player url="http://www.youtube.com/watch?v=ZTUVgYoeN_b"/>
- <media:thumbnail url="http://img.youtube.com/vi/ZTUVgYoeN_b/2.jpg"
- height="97" width="130" time="00:00:03.500"/>
- <media:thumbnail url="http://img.youtube.com/vi/ZTUVgYoeN_b/1.jpg"
- height="97" width="130" time="00:00:01.750"/>
- <media:thumbnail url="http://img.youtube.com/vi/ZTUVgYoeN_b/3.jpg"
- height="97" width="130" time="00:00:05.250"/>
- <media:thumbnail url="http://img.youtube.com/vi/ZTUVgYoeN_b/0.jpg"
- height="240" width="320" time="00:00:03.500"/>
+ <media:title type='plain'>"Crazy (Gnarles Barkley)" - Acoustic Cover</media:title>
+ <media:description type='plain'>Gnarles Barkley acoustic cover http://www.myspace.com/davidchoimusic</media:description>
+ <media:keywords>music, singing, gnarls, barkley, acoustic, cover</media:keywords>
+ <yt:duration seconds='255' />
+ <media:category label='Music'
+ scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>
+ Music</media:category>
+ <media:category
+ scheme='http://gdata.youtube.com/schemas/2007/developertags.cat'>
+ DeveloperTag1</media:category>
+ <media:content url='http://www.youtube.com/v/UMFI1hdm96E'
+ type='application/x-shockwave-flash' medium='video'
+ isDefault='true' expression='full' duration='255'
+ yt:format='5' />
+ <media:player url='http://www.youtube.com/watch?v=UMFI1hdm96E' />
+ <media:thumbnail url='http://img.youtube.com/vi/UMFI1hdm96E/2.jpg'
+ height='97' width='130' time='00:02:07.500' />
+ <media:thumbnail url='http://img.youtube.com/vi/UMFI1hdm96E/1.jpg'
+ height='97' width='130' time='00:01:03.750' />
+ <media:thumbnail url='http://img.youtube.com/vi/UMFI1hdm96E/3.jpg'
+ height='97' width='130' time='00:03:11.250' />
+ <media:thumbnail url='http://img.youtube.com/vi/UMFI1hdm96E/0.jpg'
+ height='240' width='320' time='00:02:07.500' />
+ <yt:private />
</media:group>
- <yt:statistics viewCount="93"/>
- <gd:rating min='1' max='5' numRaters='435' average='4.94'/>
+ <yt:statistics viewCount='113321' />
+ <gd:rating min='1' max='5' numRaters='1005' average='4.77' />
+ <georss:where>
+ <gml:Point>
+ <gml:pos>37.398529052734375 -122.0635986328125</gml:pos>
+ </gml:Point>
+ </georss:where>
<gd:comments>
- <gd:feedLink
- href="http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/comments"
- countHint='2197'/>
+ <gd:feedLink href='http://gdata.youtube.com/feeds/videos/UMFI1hdm96E/comments' />
</gd:comments>
-</entry>
-</feed>"""
+ <yt:noembed />
+ <app:control>
+ <app:draft>yes</app:draft>
+ <yt:state
+ name="rejected"
+ reasonCode="inappropriate"
+ helpUrl="http://www.youtube.com/t/community_guidelines">
+ The content of this video may violate the terms of use.</yt:state>
+ </app:control>
+</entry>"""
-YOU_TUBE_COMMENT_FEED = """<?xml version='1.0' encoding='UTF-8'?>
-<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'>
- <id>http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/comments?start-index=1&max-results=25</id>
- <updated>2008-02-25T23:14:03.148Z</updated>
- <category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#comment'/>
- <title type='text'>Comments on 'My walk with Mr. Darcy'</title>
- <logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo>
- <link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b'/>
- <link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=ZTUVgYoeN_b'/>
- <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/comments'/>
- <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/comments?start-index=1&max-results=25'/>
- <link rel='next' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/comments?start-index=26&max-results=25'/>
- <author>
- <name>YouTube</name>
- <uri>http://www.youtube.com/</uri>
- </author>
- <generator version='beta' uri='http://gdata.youtube.com/'>YouTube data API</generator>
- <openSearch:totalResults>100</openSearch:totalResults>
- <openSearch:startIndex>1</openSearch:startIndex>
- <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
+YOUTUBE_COMMENT_FEED = """<?xml version='1.0' encoding='UTF-8'?>
+<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'><id>http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments</id><updated>2008-05-19T21:45:45.261Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#comment'/><title type='text'>Comments</title><logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo><link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU'/><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=2Idhz9ef5oU'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments?start-index=1&max-results=25'/><author><name>YouTube</name><uri>http://www.youtube.com/</uri></author><generato
r version='beta' uri='http://gdata.youtube.com/'>YouTube data API</generator><openSearch:totalResults>0</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage>
<entry>
- <id>http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/comments/7F2BAAD03653A691</id>
- <published>2007-05-23T00:21:59.000-07:00</published>
- <updated>2007-05-23T00:21:59.000-07:00</updated>
+ <id>http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/91F809A3DE2EB81B</id>
+ <published>2008-02-22T15:27:15.000-08:00</published><updated>2008-02-22T15:27:15.000-08:00</updated>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#comment'/>
- <title type='text'>Walking is fun.</title>
- <content type='text'>Walking is fun.</content>
- <link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b'/>
- <link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=ZTUVgYoeN_b'/>
- <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/ZTUVgYoeN_b/comments/7F2BAAD03653A691'/>
- <author>
- <name>andyland744</name>
- <uri>http://gdata.youtube.com/feeds/api/users/andyland744</uri>
- </author>
- </entry>
+ <title type='text'>test66</title>
+ <content type='text'>test66</content>
+ <link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU'/>
+ <link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=2Idhz9ef5oU'/>
+ <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/91F809A3DE2EB81B'/>
+ <author><name>apitestjhartmann</name><uri>http://gdata.youtube.com/feeds/users/apitestjhartmann</uri></author>
+ </entry>
+ <entry>
+ <id>http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/A261AEEFD23674AA</id>
+ <published>2008-02-22T15:27:01.000-08:00</published><updated>2008-02-22T15:27:01.000-08:00</updated>
+ <category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#comment'/>
+ <title type='text'>test333</title>
+ <content type='text'>test333</content>
+ <link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU'/>
+ <link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=2Idhz9ef5oU'/>
+ <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/A261AEEFD23674AA'/>
+ <author><name>apitestjhartmann</name><uri>http://gdata.youtube.com/feeds/users/apitestjhartmann</uri></author>
+ </entry>
+ <entry>
+ <id>http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/0DCF1E3531B3FF85</id>
+ <published>2008-02-22T15:11:06.000-08:00</published><updated>2008-02-22T15:11:06.000-08:00</updated>
+ <category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#comment'/>
+ <title type='text'>test2</title>
+ <content type='text'>test2</content>
+ <link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU'/>
+ <link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=2Idhz9ef5oU'/>
+ <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2Idhz9ef5oU/comments/0DCF1E3531B3FF85'/>
+ <author><name>apitestjhartmann</name><uri>http://gdata.youtube.com/feeds/users/apitestjhartmann</uri></author>
+ </entry>
</feed>"""
-YOU_TUBE_PLAYLIST_FEED = """<?xml version='1.0' encoding='UTF-8'?>
+YOUTUBE_PLAYLIST_FEED = """<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom'
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
xmlns:media='http://search.yahoo.com/mrss/'
@@ -2244,6 +2279,8 @@
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>25</openSearch:itemsPerPage>
<entry>
+ <yt:description>My new playlist Description</yt:description>
+ <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#playlist' href='http://gdata.youtube.com/feeds/playlists/8BCDD04DE8F771B2'/>
<id>http://gdata.youtube.com/feeds/users/andyland74/playlists/8BCDD04DE8F771B2</id>
<published>2007-11-04T17:30:27.000-08:00</published>
<updated>2008-02-22T09:55:14.000-08:00</updated>
@@ -2257,12 +2294,24 @@
<name>andyland74</name>
<uri>http://gdata.youtube.com/feeds/users/andyland74</uri>
</author>
- <yt:description>My new playlist Description</yt:description>
- <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#playlist' href='http://gdata.youtube.com/feeds/playlists/8BCDD04DE8F771B2'/>
</entry>
</feed>"""
-YOU_TUBE_SUBSCRIPTIONS_FEED = """<?xml version='1.0' encoding='UTF-8'?>
+YOUTUBE_PLAYLIST_VIDEO_FEED = """<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gml='http://www.opengis.net/gml' xmlns:georss='http://www.georss.org/georss' xmlns:media='http://search.yahoo.com/mrss/' xmlns:yt='http://gdata.youtube.com/schemas/2007' xmlns:gd='http://schemas.google.com/g/2005'><id>http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505</id><updated>2008-05-16T12:03:17.000-07:00</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#playlist'/><category scheme='http://gdata.youtube.com/schemas/2007/tags.cat' term='videos'/><category scheme='http://gdata.youtube.com/schemas/2007/tags.cat' term='python'/><title type='text'>Test Playlist</title><subtitle type='text'>Test playlist 1</subtitle><logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo><link rel='alternate' type='text/html' href='http:/
/www.youtube.com/view_play_list?p=BCB3BB96DF51B505'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505?start-index=1&max-results=25'/><author><name>gdpython</name><uri>http://gdata.youtube.com/feeds/api/users/gdpython</uri></author><generator version='beta' uri='http://gdata.youtube.com/'>YouTube data API</generator><openSearch:totalResults>1</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><media:group><media:title type='plain'>Test Playlist</media:title><media:description type='plain'>Test playlist 1</media:description><media:content url='http://www.youtube.com/ep.swf?id=BCB3BB96DF51B505' type='application/x-shockwave-flash' yt:format='5'/></media:group><entry><id>http://gdata.youtube.c
om/feeds/api/playlists/BCB3BB96DF51B505/B0F29389E537F888</id><updated>2008-05-16T20:54:08.520Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#playlist'/><title type='text'>Uploading YouTube Videos with the PHP Client Library</title><content type='text'>Jochen Hartmann demonstrates the basics of how to use the PHP Client Library with the YouTube Data API.
+
+PHP Developer's Guide:
+http://code.google.com/apis/youtube/developers_guide_php.html
+
+Other documentation:
+http://code.google.com/apis/youtube/</content><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=iIp7OnHXBlo'/><link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo/responses'/><link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo/related'/><link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505/B0F29389E537F888'/><author><name>GoogleDevelopers</name><uri>http://gdata.youtube.com/feeds/api/users/googledevelopers</uri></author><media:group><media:title type='plain'>Uploading YouTube Videos with the PHP Client Library</media:title><media:description type='plain'>Jochen Hartmann demonstrates the
basics of how to use the PHP Client Library with the YouTube Data API.
+
+PHP Developer's Guide:
+http://code.google.com/apis/youtube/developers_guide_php.html
+
+Other documentation:
+http://code.google.com/apis/youtube/</media:description><media:keywords>api, data, demo, php, screencast, tutorial, uploading, walkthrough, youtube</media:keywords><yt:duration seconds='466'/><media:category label='Education' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>Education</media:category><media:content url='http://www.youtube.com/v/iIp7OnHXBlo' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='466' yt:format='5'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQlaBtdxOnuKiBMYDSANFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='466' yt:format='1'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQlaBtdxOnuKiBMYESARFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='466' yt:format='6'/><media:player url='http://www.youtube.com/watch?v=iIp7OnHXBlo'/><media:thumbnail url='http://img.youtube.com/vi/iIp7OnHXBlo/2.jpg' h
eight='97' width='130' time='00:03:53'/><media:thumbnail url='http://img.youtube.com/vi/iIp7OnHXBlo/1.jpg' height='97' width='130' time='00:01:56.500'/><media:thumbnail url='http://img.youtube.com/vi/iIp7OnHXBlo/3.jpg' height='97' width='130' time='00:05:49.500'/><media:thumbnail url='http://img.youtube.com/vi/iIp7OnHXBlo/0.jpg' height='240' width='320' time='00:03:53'/></media:group><yt:statistics viewCount='1550' favoriteCount='5'/><gd:rating min='1' max='5' numRaters='3' average='4.67'/><yt:location>undefined</yt:location><gd:comments><gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo/comments' countHint='2'/></gd:comments><yt:position>1</yt:position></entry></feed>"""
+
+YOUTUBE_SUBSCRIPTION_FEED = """<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom'
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
xmlns:media='http://search.yahoo.com/mrss/'
@@ -2315,7 +2364,53 @@
</entry>
</feed>"""
-YOU_TUBE_PROFILE = """<?xml version='1.0' encoding='UTF-8'?>
+YOUTUBE_VIDEO_RESPONSE_FEED = """<?xml version='1.0' encoding='UTF-8'?>
+ <feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gml='http://www.opengis.net/gml' xmlns:georss='http://www.georss.org/georss' xmlns:media='http://search.yahoo.com/mrss/' xmlns:yt='http://gdata.youtube.com/schemas/2007' xmlns:gd='http://schemas.google.com/g/2005'>
+ <id>http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY/responses</id><updated>2008-05-19T22:37:34.076Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><title type='text'>Videos responses to 'Giant NES controller coffee table'</title><logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo><link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY'/><link rel='alternate' type='text/html' href='http://www.youtube.com/video_response_view_all?v=2c3q9K4cHzY'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY/responses'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY/responses?start-index=1&max-results=25'/><author><name>YouTube</name><uri>http://www.youtube.com/</uri></author><generator version='beta' uri='http://gdat
a.youtube.com/'>YouTube data API</generator><openSearch:totalResults>8</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage>
+ <entry>
+ <id>http://gdata.youtube.com/feeds/videos/7b9EnRI9VbY</id><published>2008-03-11T19:08:53.000-07:00</published><updated>2008-05-18T21:33:10.000-07:00</updated>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='OD'/><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='chat'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Uncle'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='sex'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='catmint'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='kato'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='kissa'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='katt'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' term='Animals' label='Pets & Animals'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='kat'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='cat'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='cats'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='kedi'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='gato'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Brattman'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='drug'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='overdose'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='catnip'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='party'/>
+ <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='Katze'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='gatto'/>
+ <title type='text'>Catnip Party</title><content type='html'>snipped</content>
+ <link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=7b9EnRI9VbY'/>
+ <link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/7b9EnRI9VbY/responses'/>
+ <link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/7b9EnRI9VbY/related'/>
+ <link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/videos/2c3q9K4cHzY/responses/7b9EnRI9VbY'/>
+ <author><name>PismoBeach</name><uri>http://gdata.youtube.com/feeds/users/pismobeach</uri></author>
+ <media:group>
+ <media:title type='plain'>Catnip Party</media:title>
+ <media:description type='plain'>Uncle, Hillary, Hankette, and B4 all but overdose on the patio</media:description><media:keywords>Brattman, cat, catmint, catnip, cats, chat, drug, gato, gatto, kat, kato, katt, Katze, kedi, kissa, OD, overdose, party, sex, Uncle</media:keywords>
+ <yt:duration seconds='139'/>
+ <media:category label='Pets & Animals' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>Animals</media:category>
+ <media:content url='http://www.youtube.com/v/7b9EnRI9VbY' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='139' yt:format='5'/>
+ <media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQm2VT0SnUS_7RMYDSANFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='139' yt:format='1'/>
+ <media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQm2VT0SnUS_7RMYESARFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='139' yt:format='6'/>
+ <media:player url='http://www.youtube.com/watch?v=7b9EnRI9VbY'/>
+ <media:thumbnail url='http://img.youtube.com/vi/7b9EnRI9VbY/2.jpg' height='97' width='130' time='00:01:09.500'/>
+ <media:thumbnail url='http://img.youtube.com/vi/7b9EnRI9VbY/1.jpg' height='97' width='130' time='00:00:34.750'/>
+ <media:thumbnail url='http://img.youtube.com/vi/7b9EnRI9VbY/3.jpg' height='97' width='130' time='00:01:44.250'/>
+ <media:thumbnail url='http://img.youtube.com/vi/7b9EnRI9VbY/0.jpg' height='240' width='320' time='00:01:09.500'/>
+ </media:group>
+ <yt:statistics viewCount='4235' favoriteCount='3'/>
+ <gd:rating min='1' max='5' numRaters='24' average='3.54'/>
+ <gd:comments>
+ <gd:feedLink href='http://gdata.youtube.com/feeds/videos/7b9EnRI9VbY/comments' countHint='14'/>
+ </gd:comments>
+ </entry>
+</feed>
+"""
+
+
+YOUTUBE_PROFILE = """<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns='http://www.w3.org/2005/Atom'
xmlns:media='http://search.yahoo.com/mrss/'
xmlns:yt='http://gdata.youtube.com/schemas/2007'
@@ -2338,10 +2433,13 @@
</author>
<yt:age>33</yt:age>
<yt:username>andyland74</yt:username>
+ <yt:firstName>andy</yt:firstName>
+ <yt:lastName>example</yt:lastName>
<yt:books>Catch-22</yt:books>
<yt:gender>m</yt:gender>
<yt:company>Google</yt:company>
<yt:hobbies>Testing YouTube APIs</yt:hobbies>
+ <yt:hometown>Somewhere</yt:hometown>
<yt:location>US</yt:location>
<yt:movies>Aqua Teen Hungerforce</yt:movies>
<yt:music>Elliott Smith</yt:music>
@@ -2364,6 +2462,15 @@
href='http://gdata.youtube.com/feeds/users/andyland74/uploads' countHint='1'/>
</entry>"""
+YOUTUBE_CONTACTS_FEED = """<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:yt='http://gdata.youtube.com/schemas/2007' xmlns:gd='http://schemas.google.com/g/2005'>
+ <id>http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts</id><updated>2008-05-16T19:24:34.916Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#friend'/><title type='text'>apitestjhartmann's Contacts</title><logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo><link rel='alternate' type='text/html' href='http://www.youtube.com/profile_friends?user=apitestjhartmann'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts'/><link rel='http://schemas.google.com/g/2005#post' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts?start-index=1&max-results=25'/><author><name>apitestjhartmann</name><uri>http://gdata.youtube.com/feeds/users/apitestjh
artmann</uri></author><generator version='beta' uri='http://gdata.youtube.com/'>YouTube data API</generator><openSearch:totalResults>2</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage>
+ <entry>
+ <id>http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/test89899090</id><published>2008-02-04T11:27:54.000-08:00</published><updated>2008-05-16T19:24:34.916Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#friend'/><title type='text'>test89899090</title><link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/test89899090'/><link rel='alternate' type='text/html' href='http://www.youtube.com/profile?user=test89899090'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/test89899090'/><link rel='edit' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/test89899090'/><author><name>apitestjhartmann</name><uri>http://gdata.youtube.com/feeds/users/apitestjhartmann</uri></author><yt:username>test89899090</yt:username><yt:status>requested</yt:status></entry>
+ <entry>
+ <id>http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/testjfisher</id><published>2008-02-26T14:13:03.000-08:00</published><updated>2008-05-16T19:24:34.916Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#friend'/><title type='text'>testjfisher</title><link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/testjfisher'/><link rel='alternate' type='text/html' href='http://www.youtube.com/profile?user=testjfisher'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/testjfisher'/><link rel='edit' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/testjfisher'/><author><name>apitestjhartmann</name><uri>http://gdata.youtube.com/feeds/users/apitestjhartmann</uri></author><yt:username>testjfisher</yt:username><yt:status>pending</yt:status></entry>
+</feed>"""
+
+
NEW_CONTACT = """<?xml version='1.0' encoding='UTF-8'?>
<atom:entry xmlns:atom='http://www.w3.org/2005/Atom'
xmlns:gd='http://schemas.google.com/g/2005'>
@@ -2424,3 +2531,117 @@
primary='true'>456</gd:phoneNumber>
</entry>
</feed>"""
+
+BLOG_ENTRY = """<entry xmlns='http://www.w3.org/2005/Atom'>
+ <id>tag:blogger.com,1999:blog-blogID.post-postID</id>
+ <published>2006-08-02T18:44:43.089-07:00</published>
+ <updated>2006-11-08T18:10:23.020-08:00</updated>
+ <title type='text'>Lizzy's Diary</title>
+ <summary type='html'>Being the journal of Elizabeth Bennet</summary>
+ <link rel='alternate' type='text/html'
+ href='http://blogName.blogspot.com/'>
+ </link>
+ <link rel='http://schemas.google.com/g/2005#feed'
+ type='application/atom+xml'
+ href='http://blogName.blogspot.com/feeds/posts/default'>
+ </link>
+ <link rel='http://schemas.google.com/g/2005#post'
+ type='application/atom+xml'
+ href='http://www.blogger.com/feeds/blogID/posts/default'>
+ </link>
+ <link rel='self' type='application/atom+xml'
+ href='http://www.blogger.com/feeds/userID/blogs/blogID'>
+ </link>
+ <link rel='edit' type='application/atom+xml'
+ href='http://www.blogger.com/feeds/userID/blogs/blogID'>
+ </link>
+ <author>
+ <name>Elizabeth Bennet</name>
+ <email>liz gmail com</email>
+ </author>
+</entry>"""
+
+BLOG_POST = """<entry xmlns='http://www.w3.org/2005/Atom'>
+ <title type='text'>Marriage!</title>
+ <content type='xhtml'>
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>Mr. Darcy has <em>proposed marriage</em> to me!</p>
+ <p>He is the last man on earth I would ever desire to marry.</p>
+ <p>Whatever shall I do?</p>
+ </div>
+ </content>
+ <author>
+ <name>Elizabeth Bennet</name>
+ <email>liz gmail com</email>
+ </author>
+</entry>"""
+
+BLOG_POSTS_FEED = """<feed xmlns='http://www.w3.org/2005/Atom'>
+ <id>tag:blogger.com,1999:blog-blogID</id>
+ <updated>2006-11-08T18:10:23.020-08:00</updated>
+ <title type='text'>Lizzy's Diary</title>
+ <link rel='alternate' type='text/html'
+ href='http://blogName.blogspot.com/index.html'>
+ </link>
+ <link rel='http://schemas.google.com/g/2005#feed'
+ type='application/atom+xml'
+ href='http://blogName.blogspot.com/feeds/posts/default'>
+ </link>
+ <link rel='self' type='application/atom+xml'
+ href='http://blogName.blogspot.com/feeds/posts/default'>
+ </link>
+ <author>
+ <name>Elizabeth Bennet</name>
+ <email>liz gmail com</email>
+ </author>
+ <generator version='7.00' uri='http://www2.blogger.com'>Blogger</generator>
+ <entry>
+ <id>tag:blogger.com,1999:blog-blogID.post-postID</id>
+ <published>2006-11-08T18:10:00.000-08:00</published>
+ <updated>2006-11-08T18:10:14.954-08:00</updated>
+ <title type='text'>Quite disagreeable</title>
+ <content type='html'><p>I met Mr. Bingley's friend Mr. Darcy
+ this evening. I found him quite disagreeable.</p></content>
+ <link rel='alternate' type='text/html'
+ href='http://blogName.blogspot.com/2006/11/quite-disagreeable.html'>
+ </link>
+ <link rel='self' type='application/atom+xml'
+ href='http://blogName.blogspot.com/feeds/posts/default/postID'>
+ </link>
+ <link rel='edit' type='application/atom+xml'
+ href='http://www.blogger.com/feeds/blogID/posts/default/postID'>
+ </link>
+ <author>
+ <name>Elizabeth Bennet</name>
+ <email>liz gmail com</email>
+ </author>
+ </entry>
+</feed>"""
+
+BLOG_COMMENTS_FEED = """<feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/">
+ <id>tag:blogger.com,1999:blog-blogID.postpostID..comments</id>
+ <updated>2007-04-04T21:56:29.803-07:00</updated>
+ <title type="text">My Blog : Time to relax</title>
+ <link rel="alternate" type="text/html" href="http://blogName.blogspot.com/2007/04/first-post.html"/>
+ <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blogName.blogspot.com/feeds/postID/comments/default"/>
+ <link rel="self" type="application/atom+xml" href="http://blogName.blogspot.com/feeds/postID/comments/default"/>
+ <author>
+ <name>Blog Author name</name>
+ </author>
+ <generator version="7.00" uri="http://www2.blogger.com">Blogger</generator>
+ <openSearch:totalResults>1</openSearch:totalResults>
+ <openSearch:startIndex>1</openSearch:startIndex>
+ <entry>
+ <id>tag:blogger.com,1999:blog-blogID.post-commentID</id>
+ <published>2007-04-04T21:56:00.000-07:00</published>
+ <updated>2007-04-04T21:56:29.803-07:00</updated>
+ <title type="text">This is my first comment</title>
+ <content type="html">This is my first comment</content>
+ <link rel="alternate" type="text/html" href="http://blogName.blogspot.com/2007/04/first-post.html#commentID"/>
+ <link rel="self" type="application/atom+xml" href="http://blogName.blogspot.com/feeds/postID/comments/default/commentID"/>
+ <link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/blogID/postID/comments/default/commentID"/>
+ <author>
+ <name>Blog Author name</name>
+ </author>
+ </entry>
+</feed>"""
Modified: trunk/conduit/modules/GoogleModule/gdata/urlfetch.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/urlfetch.py (original)
+++ trunk/conduit/modules/GoogleModule/gdata/urlfetch.py Sat Jun 7 07:56:04 2008
@@ -50,7 +50,7 @@
port (int), and ssl (bool).
operation: str The HTTP operation to be performed. This is usually one of
'GET', 'POST', 'PUT', or 'DELETE'
- data: ElementTree, filestream, list of parts, or other object which can be
+ data: filestream, list of parts, or other object which can be
converted to a string.
Should be set to None when performing a GET or PUT.
If data is a file-like object which can be read, this method will read
@@ -150,5 +150,7 @@
return self.body.read(length)
def getheader(self, name):
+ if not self.headers.has_key(name):
+ return self.headers[name.lower()]
return self.headers[name]
Added: trunk/conduit/modules/GoogleModule/gdata/youtube/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/youtube/Makefile.am Sat Jun 7 07:56:04 2008
@@ -0,0 +1,5 @@
+conduit_handlersdir = $(libdir)/conduit/modules/GoogleModule/gdata/youtube
+conduit_handlers_PYTHON = __init__.py service.py
+
+clean-local:
+ rm -rf *.pyc *.pyo
Added: trunk/conduit/modules/GoogleModule/gdata/youtube/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/youtube/__init__.py Sat Jun 7 07:56:04 2008
@@ -0,0 +1,615 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2006 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = ('api stephaniel gmail com (Stephanie Liu)'
+ ', api jhartmann gmail com (Jochen Hartmann)')
+
+import atom
+import gdata
+import gdata.media as Media
+import gdata.geo as Geo
+
+# XML namespaces which are often used in YouTube entities.
+YOUTUBE_NAMESPACE = 'http://gdata.youtube.com/schemas/2007'
+YOUTUBE_TEMPLATE = '{http://gdata.youtube.com/schemas/2007}%s'
+YOUTUBE_FORMAT = '{http://gdata.youtube.com/schemas/2007}format'
+
+class Username(atom.AtomBase):
+ """The YouTube Username element"""
+ _tag = 'username'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class FirstName(atom.AtomBase):
+ """The YouTube FirstName element"""
+ _tag = 'firstName'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class LastName(atom.AtomBase):
+ """The YouTube LastName element"""
+ _tag = 'lastName'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Age(atom.AtomBase):
+ """The YouTube Age element"""
+ _tag = 'age'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Books(atom.AtomBase):
+ """The YouTube Books element"""
+ _tag = 'books'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Gender(atom.AtomBase):
+ """The YouTube Gender element"""
+ _tag = 'gender'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Company(atom.AtomBase):
+ """The YouTube Company element"""
+ _tag = 'company'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Hobbies(atom.AtomBase):
+ """The YouTube Hobbies element"""
+ _tag = 'hobbies'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Hometown(atom.AtomBase):
+ """The YouTube Hometown element"""
+ _tag = 'hometown'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Location(atom.AtomBase):
+ """The YouTube Location element"""
+ _tag = 'location'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Movies(atom.AtomBase):
+ """The YouTube Movies element"""
+ _tag = 'movies'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Music(atom.AtomBase):
+ """The YouTube Music element"""
+ _tag = 'music'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Occupation(atom.AtomBase):
+ """The YouTube Occupation element"""
+ _tag = 'occupation'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class School(atom.AtomBase):
+ """The YouTube School element"""
+ _tag = 'school'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Relationship(atom.AtomBase):
+ """The YouTube Relationship element"""
+ _tag = 'relationship'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Recorded(atom.AtomBase):
+ """The YouTube Recorded element"""
+ _tag = 'recorded'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Statistics(atom.AtomBase):
+ """The YouTube Statistics element"""
+ _tag = 'statistics'
+ _namespace = YOUTUBE_NAMESPACE
+ _attributes = atom.AtomBase._attributes.copy()
+ _attributes['viewCount'] = 'view_count'
+ _attributes['videoWatchCount'] = 'video_watch_count'
+ _attributes['subscriberCount'] = 'subscriber_count'
+ _attributes['lastWebAccess'] = 'last_web_access'
+ _attributes['favoriteCount'] = 'favorite_count'
+
+ def __init__(self, view_count=None, video_watch_count=None,
+ favorite_count=None, subscriber_count=None, last_web_access=None,
+ extension_elements=None, extension_attributes=None, text=None):
+
+ self.view_count = view_count
+ self.video_watch_count = video_watch_count
+ self.subscriber_count = subscriber_count
+ self.last_web_access = last_web_access
+ self.favorite_count = favorite_count
+
+ atom.AtomBase.__init__(self, extension_elements=extension_elements,
+ extension_attributes=extension_attributes, text=text)
+
+
+class Status(atom.AtomBase):
+ """The YouTube Status element"""
+ _tag = 'status'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Position(atom.AtomBase):
+ """The YouTube Position element. The position in a playlist feed."""
+ _tag = 'position'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Racy(atom.AtomBase):
+ """The YouTube Racy element."""
+ _tag = 'racy'
+ _namespace = YOUTUBE_NAMESPACE
+
+class Description(atom.AtomBase):
+ """The YouTube Description element."""
+ _tag = 'description'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Private(atom.AtomBase):
+ """The YouTube Private element."""
+ _tag = 'private'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class NoEmbed(atom.AtomBase):
+ """The YouTube VideoShare element. Whether a video can be embedded or not."""
+ _tag = 'noembed'
+ _namespace = YOUTUBE_NAMESPACE
+
+
+class Comments(atom.AtomBase):
+ """The GData Comments element"""
+ _tag = 'comments'
+ _namespace = gdata.GDATA_NAMESPACE
+ _children = atom.AtomBase._children.copy()
+ _attributes = atom.AtomBase._attributes.copy()
+ _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
+ [gdata.FeedLink])
+
+ def __init__(self, feed_link=None, extension_elements=None,
+ extension_attributes=None, text=None):
+
+ self.feed_link = feed_link
+ atom.AtomBase.__init__(self, extension_elements=extension_elements,
+ extension_attributes=extension_attributes, text=text)
+
+
+class Rating(atom.AtomBase):
+ """The GData Rating element"""
+ _tag = 'rating'
+ _namespace = gdata.GDATA_NAMESPACE
+ _attributes = atom.AtomBase._attributes.copy()
+ _attributes['min'] = 'min'
+ _attributes['max'] = 'max'
+ _attributes['numRaters'] = 'num_raters'
+ _attributes['average'] = 'average'
+
+ def __init__(self, min=None, max=None,
+ num_raters=None, average=None, extension_elements=None,
+ extension_attributes=None, text=None):
+
+ self.min = min
+ self.max = max
+ self.num_raters = num_raters
+ self.average = average
+
+ atom.AtomBase.__init__(self, extension_elements=extension_elements,
+ extension_attributes=extension_attributes, text=text)
+
+
+class YouTubePlaylistVideoEntry(gdata.GDataEntry):
+ _tag = gdata.GDataEntry._tag
+ _namespace = gdata.GDataEntry._namespace
+ _children = gdata.GDataEntry._children.copy()
+ _attributes = gdata.GDataEntry._attributes.copy()
+ _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
+ [gdata.FeedLink])
+ _children['{%s}description' % YOUTUBE_NAMESPACE] = ('description',
+ Description)
+ _children['{%s}rating' % gdata.GDATA_NAMESPACE] = ('rating', Rating)
+ _children['{%s}comments' % gdata.GDATA_NAMESPACE] = ('comments', Comments)
+ _children['{%s}statistics' % YOUTUBE_NAMESPACE] = ('statistics', Statistics)
+ _children['{%s}location' % YOUTUBE_NAMESPACE] = ('location', Location)
+ _children['{%s}position' % YOUTUBE_NAMESPACE] = ('position', Position)
+ _children['{%s}group' % gdata.media.MEDIA_NAMESPACE] = ('media', Media.Group)
+
+ def __init__(self, author=None, category=None, content=None,
+ atom_id=None, link=None, published=None, title=None,
+ updated=None, feed_link=None, description=None,
+ rating=None, comments=None, statistics=None,
+ location=None, position=None, media=None,
+ extension_elements=None, extension_attributes=None):
+
+ self.feed_link = feed_link
+ self.description = description
+ self.rating = rating
+ self.comments = comments
+ self.statistics = statistics
+ self.location = location
+ self.position = position
+ self.media = media
+
+ gdata.GDataEntry.__init__(self, author=author, category=category,
+ content=content, atom_id=atom_id,
+ link=link, published=published, title=title,
+ updated=updated,
+ extension_elements=extension_elements,
+ extension_attributes=extension_attributes)
+
+
+class YouTubeVideoCommentEntry(gdata.GDataEntry):
+ _tag = gdata.GDataEntry._tag
+ _namespace = gdata.GDataEntry._namespace
+ _children = gdata.GDataEntry._children.copy()
+ _attributes = gdata.GDataEntry._attributes.copy()
+
+
+class YouTubeSubscriptionEntry(gdata.GDataEntry):
+ _tag = gdata.GDataEntry._tag
+ _namespace = gdata.GDataEntry._namespace
+ _children = gdata.GDataEntry._children.copy()
+ _attributes = gdata.GDataEntry._attributes.copy()
+ _children['{%s}username' % YOUTUBE_NAMESPACE] = ('username', Username)
+ _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
+ [gdata.FeedLink])
+
+ def __init__(self, author=None, category=None, content=None,
+ atom_id=None, link=None, published=None, title=None,
+ updated=None, username=None, feed_link=None,
+ extension_elements=None, extension_attributes=None):
+
+ gdata.GDataEntry.__init__(self, author=author, category=category,
+ content=content, atom_id=atom_id, link=link,
+ published=published, title=title, updated=updated)
+
+ self.username = username
+ self.feed_link = feed_link
+
+
+class YouTubeVideoResponseEntry(gdata.GDataEntry):
+ _tag = gdata.GDataEntry._tag
+ _namespace = gdata.GDataEntry._namespace
+ _children = gdata.GDataEntry._children.copy()
+ _attributes = gdata.GDataEntry._attributes.copy()
+ _children['{%s}rating' % gdata.GDATA_NAMESPACE] = ('rating', Rating)
+ _children['{%s}noembed' % YOUTUBE_NAMESPACE] = ('noembed', NoEmbed)
+ _children['{%s}statistics' % YOUTUBE_NAMESPACE] = ('statistics', Statistics)
+ _children['{%s}racy' % YOUTUBE_NAMESPACE] = ('racy', Racy)
+ _children['{%s}group' % gdata.media.MEDIA_NAMESPACE] = ('media', Media.Group)
+
+ def __init__(self, author=None, category=None, content=None, atom_id=None,
+ link=None, published=None, title=None, updated=None, rating=None,
+ noembed=None, statistics=None, racy=None, media=None,
+ extension_elements=None, extension_attributes=None):
+
+ gdata.GDataEntry.__init__(self, author=author, category=category,
+ content=content, atom_id=atom_id, link=link,
+ published=published, title=title, updated=updated)
+
+ self.rating = rating
+ self.noembed = noembed
+ self.statistics = statistics
+ self.racy = racy
+ self.media = media or Media.Group()
+
+
+class YouTubeContactEntry(gdata.GDataEntry):
+ _tag = gdata.GDataEntry._tag
+ _namespace = gdata.GDataEntry._namespace
+ _children = gdata.GDataEntry._children.copy()
+ _attributes = gdata.GDataEntry._attributes.copy()
+ _children['{%s}username' % YOUTUBE_NAMESPACE] = ('username', Username)
+ _children['{%s}status' % YOUTUBE_NAMESPACE] = ('status', Status)
+
+
+ def __init__(self, author=None, category=None, content=None, atom_id=None,
+ link=None, published=None, title=None, updated=None,
+ username=None, status=None, extension_elements=None,
+ extension_attributes=None, text=None):
+
+ gdata.GDataEntry.__init__(self, author=author, category=category,
+ content=content, atom_id=atom_id, link=link,
+ published=published, title=title, updated=updated)
+
+ self.username = username
+ self.status = status
+
+
+class YouTubeVideoEntry(gdata.GDataEntry):
+ _tag = gdata.GDataEntry._tag
+ _namespace = gdata.GDataEntry._namespace
+ _children = gdata.GDataEntry._children.copy()
+ _attributes = gdata.GDataEntry._attributes.copy()
+ _children['{%s}rating' % gdata.GDATA_NAMESPACE] = ('rating', Rating)
+ _children['{%s}comments' % gdata.GDATA_NAMESPACE] = ('comments', Comments)
+ _children['{%s}noembed' % YOUTUBE_NAMESPACE] = ('noembed', NoEmbed)
+ _children['{%s}statistics' % YOUTUBE_NAMESPACE] = ('statistics', Statistics)
+ _children['{%s}recorded' % YOUTUBE_NAMESPACE] = ('recorded', Recorded)
+ _children['{%s}racy' % YOUTUBE_NAMESPACE] = ('racy', Racy)
+ _children['{%s}group' % gdata.media.MEDIA_NAMESPACE] = ('media', Media.Group)
+ _children['{%s}where' % gdata.geo.GEORSS_NAMESPACE] = ('geo', Geo.Where)
+
+ def __init__(self, author=None, category=None, content=None, atom_id=None,
+ link=None, published=None, title=None, updated=None, rating=None,
+ noembed=None, statistics=None, racy=None, media=None, geo=None,
+ recorded=None, comments=None, extension_elements=None,
+ extension_attributes=None):
+
+ self.rating = rating
+ self.noembed = noembed
+ self.statistics = statistics
+ self.racy = racy
+ self.comments = comments
+ self.media = media or Media.Group()
+ self.geo = geo
+ self.recorded = recorded
+
+ gdata.GDataEntry.__init__(self, author=author, category=category,
+ content=content, atom_id=atom_id, link=link,
+ published=published, title=title, updated=updated,
+ extension_elements=extension_elements,
+ extension_attributes=extension_attributes)
+
+ def GetSwfUrl(self):
+ """Return the URL for the embeddable Video
+
+ Returns:
+ URL of the embeddable video
+ """
+ if self.media.content:
+ for content in self.media.content:
+ if content.extension_attributes[YOUTUBE_FORMAT] == '5':
+ return content.url
+ else:
+ return None
+
+
+class YouTubeUserEntry(gdata.GDataEntry):
+ _tag = gdata.GDataEntry._tag
+ _namespace = gdata.GDataEntry._namespace
+ _children = gdata.GDataEntry._children.copy()
+ _attributes = gdata.GDataEntry._attributes.copy()
+ _children['{%s}username' % YOUTUBE_NAMESPACE] = ('username', Username)
+ _children['{%s}firstName' % YOUTUBE_NAMESPACE] = ('first_name', FirstName)
+ _children['{%s}lastName' % YOUTUBE_NAMESPACE] = ('last_name', LastName)
+ _children['{%s}age' % YOUTUBE_NAMESPACE] = ('age', Age)
+ _children['{%s}books' % YOUTUBE_NAMESPACE] = ('books', Books)
+ _children['{%s}gender' % YOUTUBE_NAMESPACE] = ('gender', Gender)
+ _children['{%s}company' % YOUTUBE_NAMESPACE] = ('company', Company)
+ _children['{%s}description' % YOUTUBE_NAMESPACE] = ('description',
+ Description)
+ _children['{%s}hobbies' % YOUTUBE_NAMESPACE] = ('hobbies', Hobbies)
+ _children['{%s}hometown' % YOUTUBE_NAMESPACE] = ('hometown', Hometown)
+ _children['{%s}location' % YOUTUBE_NAMESPACE] = ('location', Location)
+ _children['{%s}movies' % YOUTUBE_NAMESPACE] = ('movies', Movies)
+ _children['{%s}music' % YOUTUBE_NAMESPACE] = ('music', Music)
+ _children['{%s}occupation' % YOUTUBE_NAMESPACE] = ('occupation', Occupation)
+ _children['{%s}school' % YOUTUBE_NAMESPACE] = ('school', School)
+ _children['{%s}relationship' % YOUTUBE_NAMESPACE] = ('relationship',
+ Relationship)
+ _children['{%s}statistics' % YOUTUBE_NAMESPACE] = ('statistics', Statistics)
+ _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
+ [gdata.FeedLink])
+ _children['{%s}thumbnail' % gdata.media.MEDIA_NAMESPACE] = ('thumbnail',
+ Media.Thumbnail)
+
+ def __init__(self, author=None, category=None, content=None, atom_id=None,
+ link=None, published=None, title=None, updated=None,
+ username=None, first_name=None, last_name=None, age=None,
+ books=None, gender=None, company=None, description=None,
+ hobbies=None, hometown=None, location=None, movies=None,
+ music=None, occupation=None, school=None, relationship=None,
+ statistics=None, feed_link=None, extension_elements=None,
+ extension_attributes=None, text=None):
+
+ self.username = username
+ self.first_name = first_name
+ self.last_name = last_name
+ self.age = age
+ self.books = books
+ self.gender = gender
+ self.company = company
+ self.description = description
+ self.hobbies = hobbies
+ self.hometown = hometown
+ self.location = location
+ self.movies = movies
+ self.music = music
+ self.occupation = occupation
+ self.school = school
+ self.relationship = relationship
+ self.statistics = statistics
+ self.feed_link = feed_link
+
+ gdata.GDataEntry.__init__(self, author=author, category=category,
+ content=content, atom_id=atom_id,
+ link=link, published=published,
+ title=title, updated=updated,
+ extension_elements=extension_elements,
+ extension_attributes=extension_attributes,
+ text=text)
+
+
+class YouTubeVideoFeed(gdata.GDataFeed, gdata.LinkFinder):
+ _tag = gdata.GDataFeed._tag
+ _namespace = gdata.GDataFeed._namespace
+ _children = gdata.GDataFeed._children.copy()
+ _attributes = gdata.GDataFeed._attributes.copy()
+ _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubeVideoEntry])
+
+class YouTubePlaylistEntry(gdata.GDataEntry):
+ _tag = gdata.GDataEntry._tag
+ _namespace = gdata.GDataEntry._namespace
+ _children = gdata.GDataEntry._children.copy()
+ _attributes = gdata.GDataEntry._attributes.copy()
+ _children['{%s}description' % YOUTUBE_NAMESPACE] = ('description',
+ Description)
+ _children['{%s}private' % YOUTUBE_NAMESPACE] = ('private',
+ Private)
+ _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
+ [gdata.FeedLink])
+
+ def __init__(self, author=None, category=None, content=None,
+ atom_id=None, link=None, published=None, title=None,
+ updated=None, private=None, feed_link=None,
+ description=None, extension_elements=None,
+ extension_attributes=None):
+
+ self.description = description
+ self.private = private
+ self.feed_link = feed_link
+
+ gdata.GDataEntry.__init__(self, author=author, category=category,
+ content=content, atom_id=atom_id,
+ link=link, published=published, title=title,
+ updated=updated,
+ extension_elements=extension_elements,
+ extension_attributes=extension_attributes)
+
+
+
+class YouTubePlaylistFeed(gdata.GDataFeed, gdata.LinkFinder):
+ """ A feed of a user's playlists """
+ _tag = gdata.GDataFeed._tag
+ _namespace = gdata.GDataFeed._namespace
+ _children = gdata.GDataFeed._children.copy()
+ _attributes = gdata.GDataFeed._attributes.copy()
+ _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
+ [YouTubePlaylistEntry])
+
+
+class YouTubePlaylistVideoFeed(gdata.GDataFeed, gdata.LinkFinder):
+ """ A feed of videos in a user's playlist """
+ _tag = gdata.GDataFeed._tag
+ _namespace = gdata.GDataFeed._namespace
+ _children = gdata.GDataFeed._children.copy()
+ _attributes = gdata.GDataFeed._attributes.copy()
+ _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
+ [YouTubePlaylistVideoEntry])
+
+
+class YouTubeContactFeed(gdata.GDataFeed, gdata.LinkFinder):
+ _tag = gdata.GDataFeed._tag
+ _namespace = gdata.GDataFeed._namespace
+ _children = gdata.GDataFeed._children.copy()
+ _attributes = gdata.GDataFeed._attributes.copy()
+ _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
+ [YouTubeContactEntry])
+
+
+class YouTubeSubscriptionFeed(gdata.GDataFeed, gdata.LinkFinder):
+ _tag = gdata.GDataFeed._tag
+ _namespace = gdata.GDataFeed._namespace
+ _children = gdata.GDataFeed._children.copy()
+ _attributes = gdata.GDataFeed._attributes.copy()
+ _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
+ [YouTubeSubscriptionEntry])
+
+
+class YouTubeVideoCommentFeed(gdata.GDataFeed, gdata.LinkFinder):
+ _tag = gdata.GDataFeed._tag
+ _namespace = gdata.GDataFeed._namespace
+ _children = gdata.GDataFeed._children.copy()
+ _attributes = gdata.GDataFeed._attributes.copy()
+ _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
+ [YouTubeVideoCommentEntry])
+
+
+class YouTubeVideoResponseFeed(gdata.GDataFeed, gdata.LinkFinder):
+ _tag = gdata.GDataFeed._tag
+ _namespace = gdata.GDataFeed._namespace
+ _children = gdata.GDataFeed._children.copy()
+ _attributes = gdata.GDataFeed._attributes.copy()
+ _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
+ [YouTubeVideoResponseEntry])
+
+
+def YouTubeVideoFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeVideoFeed, xml_string)
+
+
+def YouTubeVideoEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeVideoEntry, xml_string)
+
+
+def YouTubeContactFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeContactFeed, xml_string)
+
+
+def YouTubeContactEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeContactEntry, xml_string)
+
+
+def YouTubeVideoCommentFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeVideoCommentFeed, xml_string)
+
+
+def YouTubeVideoCommentEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeVideoCommentEntry, xml_string)
+
+
+def YouTubeUserFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeVideoFeed, xml_string)
+
+
+def YouTubeUserEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeUserEntry, xml_string)
+
+
+def YouTubePlaylistFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubePlaylistFeed, xml_string)
+
+
+def YouTubePlaylistVideoFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubePlaylistVideoFeed, xml_string)
+
+
+def YouTubePlaylistEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubePlaylistEntry, xml_string)
+
+
+def YouTubePlaylistVideoEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubePlaylistVideoEntry, xml_string)
+
+
+def YouTubeSubscriptionFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeSubscriptionFeed, xml_string)
+
+
+def YouTubeSubscriptionEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeSubscriptionEntry, xml_string)
+
+
+def YouTubeVideoResponseFeedFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeVideoResponseFeed, xml_string)
+
+
+def YouTubeVideoResponseEntryFromString(xml_string):
+ return atom.CreateClassFromXMLString(YouTubeVideoResponseEntry, xml_string)
Added: trunk/conduit/modules/GoogleModule/gdata/youtube/service.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/youtube/service.py Sat Jun 7 07:56:04 2008
@@ -0,0 +1,1016 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2006 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""YouTubeService extends GDataService to streamline YouTube operations.
+
+ YouTubeService: Provides methods to perform CRUD operations on YouTube feeds.
+ Extends GDataService.
+
+"""
+
+__author__ = ('api stephaniel gmail com (Stephanie Liu), '
+ 'api jhartmann gmail com (Jochen Hartmann)')
+
+try:
+ from xml.etree import ElementTree
+except ImportError:
+ from elementtree import ElementTree
+import urllib
+import os
+import gdata
+import atom
+import gdata.service
+import gdata.youtube
+# TODO (jhartmann) - rewrite query class structure + allow passing in projections
+
+YOUTUBE_SERVER = 'gdata.youtube.com'
+YOUTUBE_SERVICE = 'youtube'
+YOUTUBE_SUPPORTED_UPLOAD_TYPES = ('mov', 'avi', 'wmv', 'mpg', 'quicktime')
+YOUTUBE_QUERY_VALID_TIME_PARAMETERS = ('today', 'this_week', 'this_month',
+ 'all_time')
+YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS = ('updated', 'viewCount', 'rating',
+ 'relevance')
+YOUTUBE_QUERY_VALID_RACY_PARAMETERS = ('include', 'exclude')
+YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS = ('1', '5', '6')
+YOUTUBE_STANDARDFEEDS = ('most_recent', 'recently_featured',
+ 'top_rated', 'most_viewed','watch_on_mobile')
+
+YOUTUBE_UPLOAD_TOKEN_URI = 'http://gdata.youtube.com/action/GetUploadToken'
+YOUTUBE_VIDEO_URI = 'http://gdata.youtube.com/feeds/api/videos'
+YOUTUBE_USER_FEED_URI = 'http://gdata.youtube.com/feeds/api/users/'
+
+YOUTUBE_STANDARD_FEEDS = 'http://gdata.youtube.com/feeds/api/standardfeeds'
+YOUTUBE_STANDARD_TOP_RATED_URI = YOUTUBE_STANDARD_FEEDS + '/top_rated'
+YOUTUBE_STANDARD_MOST_VIEWED_URI = YOUTUBE_STANDARD_FEEDS + '/most_viewed'
+YOUTUBE_STANDARD_RECENTLY_FEATURED_URI = YOUTUBE_STANDARD_FEEDS + (
+ '/recently_featured')
+YOUTUBE_STANDARD_WATCH_ON_MOBILE_URI = YOUTUBE_STANDARD_FEEDS + (
+ '/watch_on_mobile')
+YOUTUBE_STANDARD_TOP_FAVORITES_URI = YOUTUBE_STANDARD_FEEDS + '/top_favorites'
+YOUTUBE_STANDARD_MOST_RECENT_URI = YOUTUBE_STANDARD_FEEDS + '/most_recent'
+YOUTUBE_STANDARD_MOST_DISCUSSED_URI = YOUTUBE_STANDARD_FEEDS + '/most_discussed'
+YOUTUBE_STANDARD_MOST_LINKED_URI = YOUTUBE_STANDARD_FEEDS + '/most_linked'
+YOUTUBE_STANDARD_MOST_RESPONDED_URI = YOUTUBE_STANDARD_FEEDS + '/most_responded'
+
+YOUTUBE_RATING_LINK_REL = 'http://gdata.youtube.com/schemas/2007#video.ratings'
+YOUTUBE_COMPLAINT_CATEGORY_SCHEME = 'http://gdata.youtube.com/schemas/2007/complaint-reasons.cat'
+YOUTUBE_COMPLAINT_CATEGORY_TERMS = ('PORN', 'VIOLENCE', 'HATE', 'DANGEROUS',
+ 'RIGHTS', 'SPAM')
+YOUTUBE_CONTACT_STATUS = ('accepted', 'rejected')
+YOUTUBE_CONTACT_CATEGORY = ('Friends', 'Family')
+
+UNKOWN_ERROR=1000
+YOUTUBE_BAD_REQUEST=400
+YOUTUBE_CONFLICT=409
+YOUTUBE_INTERNAL_SERVER_ERROR=500
+YOUTUBE_INVALID_ARGUMENT=601
+YOUTUBE_INVALID_CONTENT_TYPE=602
+YOUTUBE_NOT_A_VIDEO=603
+YOUTUBE_INVALID_KIND=604
+
+class Error(Exception):
+ pass
+
+class RequestError(Error):
+ pass
+
+class YouTubeError(Error):
+ pass
+
+class YouTubeService(gdata.service.GDataService):
+ """Client for the YouTube service."""
+
+ def __init__(self, email=None, password=None, source=None,
+ server=YOUTUBE_SERVER, additional_headers=None, client_id=None,
+ developer_key=None):
+ if client_id and developer_key:
+ self.client_id = client_id
+ self.developer_key = developer_key
+ self.additional_headers = {'X-Gdata-Client': self.client_id,
+ 'X-GData-Key': 'key=' + self.developer_key}
+ gdata.service.GDataService.__init__(
+ self, email=email, password=password,
+ service=YOUTUBE_SERVICE, source=source, server=server,
+ additional_headers=self.additional_headers)
+ elif developer_key and not client_id:
+ raise YouTubeError('You must also specify the clientId')
+ else:
+ gdata.service.GDataService.__init__(
+ self, email=email, password=password,
+ service=YOUTUBE_SERVICE, source=source, server=server,
+ additional_headers=additional_headers)
+
+ def GetYouTubeVideoFeed(self, uri):
+ return self.Get(uri, converter=gdata.youtube.YouTubeVideoFeedFromString)
+
+ def GetYouTubeVideoEntry(self, uri=None, video_id=None):
+ if not uri and not video_id:
+ raise YouTubeError('You must provide at least a uri or a video_id '
+ 'to the GetYouTubeVideoEntry() method')
+ elif video_id and not uri:
+ uri = YOUTUBE_VIDEO_URI + '/' + video_id
+
+ return self.Get(uri, converter=gdata.youtube.YouTubeVideoEntryFromString)
+
+ def GetYouTubeContactFeed(self, uri=None, username=None):
+ if not uri and not username:
+ raise YouTubeError('You must provide at least a uri or a username '
+ 'to the GetYouTubeContactFeed() method')
+ elif username and not uri:
+ uri = YOUTUBE_USER_FEED_URI + username + '/contacts'
+
+ return self.Get(uri, converter=gdata.youtube.YouTubeContactFeedFromString)
+
+ def GetYouTubeContactEntry(self, uri=None):
+ return self.Get(uri, converter=gdata.youtube.YouTubeContactEntryFromString)
+
+ def GetYouTubeVideoCommentFeed(self, uri=None, video_id=None):
+ if not uri and not video_id:
+ raise YouTubeError('You must provide at least a uri or a video_id '
+ 'to the GetYouTubeVideoCommentFeed() method')
+ elif video_id and not uri:
+ uri = YOUTUBE_VIDEO_URI + '/' + video_id + '/comments'
+
+ return self.Get(
+ uri,
+ converter=gdata.youtube.YouTubeVideoCommentFeedFromString)
+
+ def GetYouTubeVideoCommentEntry(self, uri):
+ return self.Get(
+ uri,
+ converter=gdata.youtube.YouTubeVideoCommentEntryFromString)
+
+ def GetYouTubeUserFeed(self, uri=None, username=None):
+ if not uri and not username:
+ raise YouTubeError('You must provide at least a uri or a username '
+ 'to the GetYouTubeUserFeed() method')
+ elif username and not uri:
+ uri = YOUTUBE_USER_FEED_URI + username + '/uploads'
+
+ return self.Get(uri, converter=gdata.youtube.YouTubeUserFeedFromString)
+
+ def GetYouTubeUserEntry(self, uri=None, username=None):
+ if not uri and not username:
+ raise YouTubeError('You must provide at least a uri or a username '
+ 'to the GetYouTubeUserEntry() method')
+ elif username and not uri:
+ uri = YOUTUBE_USER_FEED_URI + username
+
+ return self.Get(uri, converter=gdata.youtube.YouTubeUserEntryFromString)
+
+ def GetYouTubePlaylistFeed(self, uri=None, username=None):
+ if not uri and not username:
+ raise YouTubeError('You must provide at least a uri or a username '
+ 'to the GetYouTubePlaylistFeed() method')
+ elif username and not uri:
+ uri = YOUTUBE_USER_FEED_URI + username + '/playlists'
+
+ return self.Get(uri, converter=gdata.youtube.YouTubePlaylistFeedFromString)
+
+ def GetYouTubePlaylistEntry(self, uri):
+ return self.Get(uri, converter=gdata.youtube.YouTubePlaylistEntryFromString)
+
+ def GetYouTubePlaylistVideoFeed(self, uri=None, playlist_id=None):
+ if not uri and not playlist_id:
+ raise YouTubeError('You must provide at least a uri or a playlist_id '
+ 'to the GetYouTubePlaylistVideoFeed() method')
+ elif playlist_id and not uri:
+ uri = 'http://gdata.youtube.com/feeds/api/playlists/' + playlist_id
+
+ return self.Get(
+ uri,
+ converter=gdata.youtube.YouTubePlaylistVideoFeedFromString)
+
+ def GetYouTubeVideoResponseFeed(self, uri=None, video_id=None):
+ if not uri and not video_id:
+ raise YouTubeError('You must provide at least a uri or a video_id '
+ 'to the GetYouTubeVideoResponseFeed() method')
+ elif video_id and not uri:
+ uri = YOUTUBE_VIDEO_URI + '/' + video_id + '/responses'
+
+ return self.Get(uri,
+ converter=gdata.youtube.YouTubeVideoResponseFeedFromString)
+
+ def GetYouTubeVideoResponseEntry(self, uri):
+ return self.Get(uri,
+ converter=gdata.youtube.YouTubeVideoResponseEntryFromString)
+
+ def GetYouTubeSubscriptionFeed(self, uri=None, username=None):
+ if not uri and not username:
+ raise YouTubeError('You must provide at least a uri or a username '
+ 'to the GetYouTubeSubscriptionFeed() method')
+ elif username and not uri:
+ uri = ('http://gdata.youtube.com'
+ '/feeds/users/') + username + '/subscriptions'
+
+ return self.Get(
+ uri,
+ converter=gdata.youtube.YouTubeSubscriptionFeedFromString)
+
+ def GetYouTubeSubscriptionEntry(self, uri):
+ return self.Get(uri,
+ converter=gdata.youtube.YouTubeSubscriptionEntryFromString)
+
+ def GetYouTubeRelatedVideoFeed(self, uri=None, video_id=None):
+ if not uri and not video_id:
+ raise YouTubeError('You must provide at least a uri or a video_id '
+ 'to the GetYouTubeRelatedVideoFeed() method')
+ elif video_id and not uri:
+ uri = YOUTUBE_VIDEO_URI + '/' + video_id + '/related'
+
+ return self.Get(uri,
+ converter=gdata.youtube.YouTubeVideoFeedFromString)
+
+ def GetTopRatedVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_TOP_RATED_URI)
+
+ def GetMostViewedVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_VIEWED_URI)
+
+ def GetRecentlyFeaturedVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_RECENTLY_FEATURED_URI)
+
+ def GetWatchOnMobileVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_WATCH_ON_MOBILE_URI)
+
+ def GetTopFavoritesVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_TOP_FAVORITES_URI)
+
+ def GetMostRecentVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_RECENT_URI)
+
+ def GetMostDiscussedVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_DISCUSSED_URI)
+
+ def GetMostLinkedVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_LINKED_URI)
+
+ def GetMostRespondedVideoFeed(self):
+ return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_RESPONDED_URI)
+
+ def GetUserFavoritesFeed(self, username='default'):
+ return self.GetYouTubeVideoFeed('http://gdata.youtube.com/feeds/api/users/'
+ + username + '/favorites')
+
+ def InsertVideoEntry(self, video_entry, filename_or_handle,
+ youtube_username='default',
+ content_type='video/quicktime'):
+ """Upload a new video to YouTube using the direct upload mechanism
+
+ Needs authentication.
+
+ Arguments:
+ video_entry: The YouTubeVideoEntry to upload
+ filename_or_handle: A file-like object or file name where the video
+ will be read from
+ youtube_username: (optional) Username into whose account this video is
+ to be uploaded to. Defaults to the currently authenticated user.
+ content_type (optional): Internet media type (a.k.a. mime type) of
+ media object. Currently the YouTube API supports these types:
+ o video/mpeg
+ o video/quicktime
+ o video/x-msvideo
+ o video/mp4
+
+ Returns:
+ The newly created YouTubeVideoEntry or a YouTubeError
+
+ """
+
+ # check to make sure we have a valid video entry
+ try:
+ assert(isinstance(video_entry, gdata.youtube.YouTubeVideoEntry))
+ except AssertionError:
+ raise YouTubeError({'status':YOUTUBE_INVALID_ARGUMENT,
+ 'body':'`video_entry` must be a gdata.youtube.VideoEntry instance',
+ 'reason':'Found %s, not VideoEntry' % type(video_entry)
+ })
+
+ # check to make sure the MIME type is supported
+ try:
+ majtype, mintype = content_type.split('/')
+ assert(mintype in YOUTUBE_SUPPORTED_UPLOAD_TYPES)
+ except (ValueError, AssertionError):
+ raise YouTubeError({'status':YOUTUBE_INVALID_CONTENT_TYPE,
+ 'body':'This is not a valid content type: %s' % content_type,
+ 'reason':'Accepted content types: %s' %
+ ['video/' + t for t in YOUTUBE_SUPPORTED_UPLOAD_TYPES]
+ })
+ # check that the video file is valid and readable
+ if (isinstance(filename_or_handle, (str, unicode))
+ and os.path.exists(filename_or_handle)):
+ mediasource = gdata.MediaSource()
+ mediasource.setFile(filename_or_handle, content_type)
+ elif hasattr(filename_or_handle, 'read'):
+ if hasattr(filename_or_handle, 'seek'):
+ filename_or_handle.seek(0)
+ file_handle = StringIO.StringIO(filename_or_handle.read())
+ name = 'video'
+ if hasattr(filename_or_handle, 'name'):
+ name = filename_or_handle.name
+ mediasource = gdata.MediaSource(file_handle, content_type,
+ content_length=file_handle.len, file_name=name)
+ else:
+ raise YouTubeError({'status':YOUTUBE_INVALID_ARGUMENT, 'body':
+ '`filename_or_handle` must be a path name or a file-like object',
+ 'reason': ('Found %s, not path name or object '
+ 'with a .read() method' % type(filename_or_handle))})
+
+ upload_uri = ('http://uploads.gdata.youtube.com/feeds/api/users/' +
+ youtube_username + '/uploads')
+
+ self.additional_headers['Slug'] = mediasource.file_name
+
+ # post the video file
+ try:
+ try:
+ return self.Post(video_entry, uri=upload_uri, media_source=mediasource,
+ converter=gdata.youtube.YouTubeVideoEntryFromString)
+ except gdata.service.RequestError, e:
+ raise YouTubeError(e.args[0])
+ finally:
+ del(self.additional_headers['Slug'])
+
+ def CheckUploadStatus(self, video_entry=None, video_id=None):
+ """Check upload status on a recently uploaded video entry
+
+ Needs authentication.
+
+ Arguments:
+ video_entry: (optional) The YouTubeVideoEntry to upload
+ video_id: (optional) The videoId of a recently uploaded entry. One of
+ these two arguments will need to be present.
+
+ Returns:
+ A tuple containing (video_upload_state, detailed_message) or None if
+ no status information is found.
+ """
+ if not video_entry and not video_id:
+ raise YouTubeError('You must provide at least a uri or a video_id '
+ 'to the CheckUploadStatus() method')
+ elif video_id and not video_entry:
+ video_entry = self.GetYouTubeVideoEntry(video_id=video_id)
+
+ control = video_entry.control
+ if control is not None:
+ draft = control.draft
+ if draft is not None:
+ if draft.text == 'yes':
+ yt_state = control.extension_elements[0]
+ if yt_state is not None:
+ state_value = yt_state.attributes['name']
+ message = ''
+ if yt_state.text is not None:
+ message = yt_state.text
+
+ return (state_value, message)
+
+ def GetFormUploadToken(self, video_entry, uri=YOUTUBE_UPLOAD_TOKEN_URI):
+ """Receives a YouTube Token and a YouTube PostUrl with which to construct
+ the HTML Upload form for browser-based video uploads
+
+ Needs authentication.
+
+ Arguments:
+ video_entry: The YouTubeVideoEntry to upload (meta-data only)
+ uri: (optional) A url from where to fetch the token information
+
+ Returns:
+ A tuple containing (post_url, youtube_token)
+ """
+ response = self.Post(video_entry, uri)
+ tree = ElementTree.fromstring(response)
+
+ for child in tree:
+ if child.tag == 'url':
+ post_url = child.text
+ elif child.tag == 'token':
+ youtube_token = child.text
+
+ return (post_url, youtube_token)
+
+ def UpdateVideoEntry(self, video_entry):
+ """Updates a video entry's meta-data
+
+ Needs authentication.
+
+ Arguments:
+ video_entry: The YouTubeVideoEntry to update, containing updated
+ meta-data
+
+ Returns:
+ An updated YouTubeVideoEntry on success or None
+ """
+ for link in video_entry.link:
+ if link.rel == 'edit':
+ edit_uri = link.href
+
+ return self.Put(video_entry, uri=edit_uri,
+ converter=gdata.youtube.YouTubeVideoEntryFromString)
+
+ def DeleteVideoEntry(self, video_entry):
+ """Deletes a video entry
+
+ Needs authentication.
+
+ Arguments:
+ video_entry: The YouTubeVideoEntry to be deleted
+
+ Returns:
+ True if entry was deleted successfully
+ """
+ for link in video_entry.link:
+ if link.rel == 'edit':
+ edit_uri = link.href
+
+ return self.Delete(edit_uri)
+
+ def AddRating(self, rating_value, video_entry):
+ """Add a rating to a video entry
+
+ Needs authentication.
+
+ Arguments:
+ rating_value: The value for the rating (between 1 and 5)
+ video_entry: The YouTubeVideoEntry to be rated
+
+ Returns:
+ True if the rating was added successfully
+ """
+
+ if rating_value < 1 or rating_value > 5:
+ raise YouTubeError('AddRating: rating_value must be between 1 and 5')
+
+ entry = gdata.GDataEntry()
+ rating = gdata.youtube.Rating(min='1', max='5')
+ rating.extension_attributes['name'] = 'value'
+ rating.extension_attributes['value'] = str(rating_value)
+ entry.extension_elements.append(rating)
+
+ for link in video_entry.link:
+ if link.rel == YOUTUBE_RATING_LINK_REL:
+ rating_uri = link.href
+
+ return self.Post(entry, uri=rating_uri)
+
+ def AddComment(self, comment_text, video_entry):
+ """Add a comment to a video entry
+
+ Needs authentication.
+
+ Arguments:
+ comment_text: The text of the comment
+ video_entry: The YouTubeVideoEntry to be commented on
+
+ Returns:
+ True if the comment was added successfully
+ """
+ content = atom.Content(text=comment_text)
+ comment_entry = gdata.youtube.YouTubeVideoCommentEntry(content=content)
+ comment_post_uri = video_entry.comments.feed_link[0].href
+
+ return self.Post(comment_entry, uri=comment_post_uri)
+
+ def AddVideoResponse(self, video_id_to_respond_to, video_response):
+ """Add a video response
+
+ Needs authentication.
+
+ Arguments:
+ video_id_to_respond_to: Id of the YouTubeVideoEntry to be responded to
+ video_response: YouTubeVideoEntry to be posted as a response
+
+ Returns:
+ True if video response was posted successfully
+ """
+ post_uri = YOUTUBE_VIDEO_URI + '/' + video_id_to_respond_to + '/responses'
+ return self.Post(video_response, uri=post_uri)
+
+ def DeleteVideoResponse(self, video_id, response_video_id):
+ """Delete a video response
+
+ Needs authentication.
+
+ Arguments:
+ video_id: Id of YouTubeVideoEntry that contains the response
+ response_video_id: Id of the YouTubeVideoEntry posted as response
+
+ Returns:
+ True if video response was deleted succcessfully
+ """
+ delete_uri = (YOUTUBE_VIDEO_URI + '/' + video_id +
+ '/responses/' + response_video_id)
+
+ return self.Delete(delete_uri)
+
+ def AddComplaint(self, complaint_text, complaint_term, video_id):
+ """Add a complaint for a particular video entry
+
+ Needs authentication.
+
+ Arguments:
+ complaint_text: Text explaining the complaint
+ complaint_term: Complaint category term
+ video_id: Id of YouTubeVideoEntry to complain about
+
+ Returns:
+ True if posted successfully
+ """
+ if complaint_term not in YOUTUBE_COMPLAINT_CATEGORY_TERMS:
+ raise YouTubeError('Your complaint must be a valid term')
+
+ content = atom.Content(text=complaint_text)
+ category = atom.Category(term=complaint_term,
+ scheme=YOUTUBE_COMPLAINT_CATEGORY_SCHEME)
+
+ complaint_entry = gdata.GDataEntry(content=content, category=[category])
+ post_uri = YOUTUBE_VIDEO_URI + '/' + video_id + '/complaints'
+
+ return self.Post(complaint_entry, post_uri)
+
+ def AddVideoEntryToFavorites(self, video_entry, username='default'):
+ """Add a video entry to a users favorite feed
+
+ Needs authentication.
+
+ Arguments:
+ video_entry: The YouTubeVideoEntry to add
+ username: (optional) The username to whose favorite feed you wish to
+ add the entry. Your client must be authenticated to the username's
+ account.
+ Returns:
+ A GDataEntry if posted successfully
+ """
+ post_uri = ('http://gdata.youtube.com/feeds/api/users/' +
+ username + '/favorites')
+
+ return self.Post(video_entry, post_uri)
+
+ def DeleteVideoEntryFromFavorites(self, video_id, username='default'):
+ """Delete a video entry from the users favorite feed
+
+ Needs authentication.
+
+ Arguments:
+ video_id: The Id for the YouTubeVideoEntry to be removed
+ username: (optional) The username of the user's favorite feed. Defaults
+ to the currently authenticated user.
+
+ Returns:
+ True if entry was successfully deleted
+ """
+ edit_link = YOUTUBE_USER_FEED_URI + username + '/favorites/' + video_id
+ return self.Delete(edit_link)
+
+ def AddPlaylist(self, playlist_title, playlist_description,
+ playlist_private=None):
+ """Add a new playlist to the currently authenticated users account
+
+ Needs authentication
+
+ Arguments:
+ playlist_title: The title for the new playlist
+ playlist_description: The description for the playlist
+ playlist_private: (optiona) Submit as True if the playlist is to be
+ private
+ Returns:
+ A new YouTubePlaylistEntry if successfully posted
+ """
+ playlist_entry = gdata.youtube.YouTubePlaylistEntry(
+ title=atom.Title(text=playlist_title),
+ description=gdata.youtube.Description(text=playlist_description))
+ if playlist_private:
+ playlist_entry.private = gdata.youtube.Private()
+
+ playlist_post_uri = YOUTUBE_USER_FEED_URI + 'default/playlists'
+ return self.Post(playlist_entry, playlist_post_uri,
+ converter=gdata.youtube.YouTubePlaylistEntryFromString)
+
+ def DeletePlaylist(self, playlist_uri):
+ """Delete a playlist from the currently authenticated users playlists
+
+ Needs authentication
+
+ Arguments:
+ playlist_uri: The uri of the playlist to delete
+
+ Returns:
+ True if successfully deleted
+ """
+ return self.Delete(playlist_uri)
+
+ def AddPlaylistVideoEntryToPlaylist(self, playlist_uri, video_id,
+ custom_video_title=None,
+ custom_video_description=None):
+ """Add a video entry to a playlist, optionally providing a custom title
+ and description
+
+ Needs authentication
+
+ Arguments:
+ playlist_uri: Uri of playlist to add this video to.
+ video_id: Id of the video entry to add
+ custom_video_title: (optional) Custom title for the video
+ custom_video_description: (optional) Custom video description
+
+ Returns:
+ A YouTubePlaylistVideoEntry if successfully posted
+ """
+
+ playlist_video_entry = gdata.youtube.YouTubePlaylistVideoEntry(
+ atom_id=atom.Id(text=video_id))
+ if custom_video_title:
+ playlist_video_entry.title = atom.Title(text=custom_video_title)
+ if custom_video_description:
+ playlist_video_entry.description = gdata.youtube.Description(
+ text=custom_video_description)
+ return self.Post(playlist_video_entry, playlist_uri,
+ converter=gdata.youtube.YouTubePlaylistVideoEntryFromString)
+
+ def UpdatePlaylistVideoEntryMetaData(self, playlist_uri, playlist_entry_id,
+ new_video_title,
+ new_video_description,
+ new_video_position):
+ """Update the meta data for a YouTubePlaylistVideoEntry
+
+ Needs authentication
+
+ Arguments:
+ playlist_uri: Uri of the playlist that contains the entry to be updated
+ playlist_entry_id: Id of the entry to be updated
+ new_video_title: New title for the video entry
+ new_video_description: New description for the video entry
+ new_video_position: New position for the video
+
+ Returns:
+ A YouTubePlaylistVideoEntry if the update was successful
+ """
+ playlist_video_entry = gdata.youtube.YouTubePlaylistVideoEntry(
+ title=atom.Title(text=new_video_title),
+ description=gdata.youtube.Description(text=new_video_description),
+ position=gdata.youtube.Position(text=str(new_video_position)))
+
+ playlist_put_uri = playlist_uri + '/' + playlist_entry_id
+
+ return self.Put(playlist_video_entry, playlist_put_uri,
+ converter=gdata.youtube.YouTubePlaylistVideoEntryFromString)
+
+
+ def AddSubscriptionToChannel(self, username):
+ """Add a new channel subscription to the currently authenticated users
+ account
+
+ Needs authentication
+
+ Arguments:
+ username: The username of the channel to subscribe to.
+
+ Returns:
+ A new YouTubeSubscriptionEntry if successfully posted
+ """
+ subscription_category = atom.Category(
+ scheme='http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat',
+ term='channel')
+ subscription_username = gdata.youtube.Username(text=username)
+
+ subscription_entry = gdata.youtube.YouTubeSubscriptionEntry(
+ category=subscription_category,
+ username=subscription_username)
+
+ post_uri = YOUTUBE_USER_FEED_URI + 'default/subscriptions'
+ return self.Post(subscription_entry, post_uri,
+ converter=gdata.youtube.YouTubeSubscriptionEntryFromString)
+
+ def AddSubscriptionToFavorites(self, username):
+ """Add a new subscription to a users favorites to the currently
+ authenticated user's account
+
+ Needs authentication
+
+ Arguments:
+ username: The username of the users favorite feed to subscribe to
+
+ Returns:
+ A new YouTubeSubscriptionEntry if successful
+ """
+ subscription_category = atom.Category(
+ scheme='http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat',
+ term='favorites')
+ subscription_username = gdata.youtube.Username(text=username)
+
+ subscription_entry = gdata.youtube.YouTubeSubscriptionEntry(
+ category=subscription_category,
+ username=subscription_username)
+
+ post_uri = YOUTUBE_USER_FEED_URI + 'default/subscriptions'
+ return self.Post(subscription_entry, post_uri,
+ converter=gdata.youtube.YouTubeSubscriptionEntryFromString)
+
+ def DeleteSubscription(self, subscription_uri):
+ """Delete a subscription from the currently authenticated user's account
+
+ Needs authentication
+
+ Arguments:
+ subscription_uri: The uri of a subscription
+
+ Returns:
+ True if successfully deleted
+ """
+ return self.Delete(subscription_uri)
+
+ def AddContact(self, contact_username, my_username='default'):
+ """Add a new contact to the currently authenticated user's contact feed.
+
+ Needs authentication
+
+ Arguments:
+ contact_username: The username of the contact that you wish to add
+ my_username: (optional) The username of the contact feed
+
+ Returns:
+ A YouTubeContactEntry if added successfully
+ """
+ contact_category = atom.Category(
+ scheme = 'http://gdata.youtube.com/schemas/2007/contact.cat',
+ term = 'Friends')
+ contact_username = gdata.youtube.Username(text=contact_username)
+ contact_entry = gdata.youtube.YouTubeContactEntry(
+ category=contact_category,
+ username=contact_username)
+ contact_post_uri = YOUTUBE_USER_FEED_URI + my_username + '/contacts'
+ return self.Post(contact_entry, contact_post_uri,
+ converter=gdata.youtube.YouTubeContactEntryFromString)
+
+ def UpdateContact(self, contact_username, new_contact_status,
+ new_contact_category, my_username='default'):
+ """Update a contact, providing a new status and a new category
+
+ Needs authentication
+
+ Arguments:
+ contact_username: The username of the contact to be updated
+ new_contact_status: New status, either 'accepted' or 'rejected'
+ new_contact_category: New category for the contact, either 'Friends' or
+ 'Family'
+ my_username: (optional) Username of the user whose contact feed we are
+ modifying. Defaults to the currently authenticated user
+
+ Returns:
+ A YouTubeContactEntry if updated succesfully
+ """
+ if new_contact_status not in YOUTUBE_CONTACT_STATUS:
+ raise YouTubeError('New contact status must be one of ' +
+ ' '.join(YOUTUBE_CONTACT_STATUS))
+ if new_contact_category not in YOUTUBE_CONTACT_CATEGORY:
+ raise YouTubeError('New contact category must be one of ' +
+ ' '.join(YOUTUBE_CONTACT_CATEGORY))
+
+ contact_category = atom.Category(
+ scheme='http://gdata.youtube.com/schemas/2007/contact.cat',
+ term=new_contact_category)
+ contact_status = gdata.youtube.Status(text=new_contact_status)
+ contact_entry = gdata.youtube.YouTubeContactEntry(
+ category=contact_category,
+ status=contact_status)
+ contact_put_uri = (YOUTUBE_USER_FEED_URI + my_username + '/contacts/' +
+ contact_id)
+ return self.Put(contact_entry, contact_put_uri,
+ converter=gdata.youtube.YouTubeContactEntryFromString)
+
+ def DeleteContact(self, contact_username, my_username='default'):
+ """Delete a contact from a users contact feed
+
+ Needs authentication
+
+ Arguments:
+ contact_username: Username of the contact to be deleted
+ my_username: (optional) Username of the users contact feed that is to
+ be modified. Defaults to the currently authenticated user
+
+ Returns:
+ True if the contact was deleted successfully
+ """
+ contact_edit_uri = (YOUTUBE_USER_FEED_URI + my_username +
+ '/contacts/' + contact_username)
+ return self.Delete(contact_edit_uri)
+
+
+ def _GetDeveloperKey(self):
+ """Getter for Developer Key property"""
+ if '_developer_key' in self.keys():
+ return self._developer_key
+ else:
+ return None
+
+ def _SetDeveloperKey(self, developer_key):
+ """Setter for Developer Key property"""
+ self._developer_key = developer_key
+ self.additional_headers['X-GData-Key'] = 'key=' + developer_key
+
+ developer_key = property(_GetDeveloperKey, _SetDeveloperKey,
+ doc="""The Developer Key property""")
+
+ def _GetClientId(self):
+ """Getter for Client Id property"""
+ if '_client_id' in self.keys():
+ return self._client_id
+ else:
+ return None
+
+ def _SetClientId(self, client_id):
+ """Setter for Client Id property"""
+ self._client_id = client_id
+ self.additional_headers['X-Gdata-Client'] = client_id
+
+ client_id = property(_GetClientId, _SetClientId,
+ doc="""The ClientId property""")
+
+ def Query(self, uri):
+ """Performs a query and returns a resulting feed or entry.
+
+ Args:
+ feed: string The feed which is to be queried
+
+ Returns:
+ On success, a tuple in the form
+ (boolean succeeded=True, ElementTree._Element result)
+ On failure, a tuple in the form
+ (boolean succeeded=False, {'status': HTTP status code from server,
+ 'reason': HTTP reason from the server,
+ 'body': HTTP body of the server's response})
+ """
+
+ result = self.Get(uri)
+ return result
+
+ def YouTubeQuery(self, query):
+ result = self.Query(query.ToUri())
+ if isinstance(query, YouTubeVideoQuery):
+ return gdata.youtube.YouTubeVideoFeedFromString(result.ToString())
+ elif isinstance(query, YouTubeUserQuery):
+ return gdata.youtube.YouTubeUserFeedFromString(result.ToString())
+ elif isinstance(query, YouTubePlaylistQuery):
+ return gdata.youtube.YouTubePlaylistFeedFromString(result.ToString())
+ else:
+ return result
+
+class YouTubeVideoQuery(gdata.service.Query):
+
+ def __init__(self, video_id=None, feed_type=None, text_query=None,
+ params=None, categories=None):
+
+ if feed_type in YOUTUBE_STANDARDFEEDS:
+ feed = 'http://%s/feeds/standardfeeds/%s' % (YOUTUBE_SERVER, feed_type)
+ elif feed_type is 'responses' or feed_type is 'comments' and video_id:
+ feed = 'http://%s/feeds/videos/%s/%s' % (YOUTUBE_SERVER, video_id,
+ feed_type)
+ else:
+ feed = 'http://%s/feeds/videos' % (YOUTUBE_SERVER)
+
+ gdata.service.Query.__init__(self, feed, text_query=text_query,
+ params=params, categories=categories)
+
+ def _GetStartMin(self):
+ if 'start-min' in self.keys():
+ return self['start-min']
+ else:
+ return None
+
+ def _SetStartMin(self, val):
+ self['start-min'] = val
+
+ start_min = property(_GetStartMin, _SetStartMin,
+ doc="""The start-min query parameter""")
+
+ def _GetStartMax(self):
+ if 'start-max' in self.keys():
+ return self['start-max']
+ else:
+ return None
+
+ def _SetStartMax(self, val):
+ self['start-max'] = val
+
+ start_max = property(_GetStartMax, _SetStartMax,
+ doc="""The start-max query parameter""")
+
+ def _GetVideoQuery(self):
+ if 'vq' in self.keys():
+ return self['vq']
+ else:
+ return None
+
+ def _SetVideoQuery(self, val):
+ self['vq'] = val
+
+ vq = property(_GetVideoQuery, _SetVideoQuery,
+ doc="""The video query (vq) query parameter""")
+
+ def _GetOrderBy(self):
+ if 'orderby' in self.keys():
+ return self['orderby']
+ else:
+ return None
+
+ def _SetOrderBy(self, val):
+ if val not in YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS:
+ raise YouTubeError('OrderBy must be one of: %s ' %
+ ' '.join(YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS))
+ self['orderby'] = val
+
+ orderby = property(_GetOrderBy, _SetOrderBy,
+ doc="""The orderby query parameter""")
+
+ def _GetTime(self):
+ if 'time' in self.keys():
+ return self['time']
+ else:
+ return None
+
+ def _SetTime(self, val):
+ if val not in YOUTUBE_QUERY_VALID_TIME_PARAMETERS:
+ raise YouTubeError('Time must be one of: %s ' %
+ ' '.join(YOUTUBE_QUERY_VALID_TIME_PARAMETERS))
+ self['time'] = val
+
+ time = property(_GetTime, _SetTime,
+ doc="""The time query parameter""")
+
+ def _GetFormat(self):
+ if 'format' in self.keys():
+ return self['format']
+ else:
+ return None
+
+ def _SetFormat(self, val):
+ if val not in YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS:
+ raise YouTubeError('Format must be one of: %s ' %
+ ' '.join(YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS))
+ self['format'] = val
+
+ format = property(_GetFormat, _SetFormat,
+ doc="""The format query parameter""")
+
+ def _GetRacy(self):
+ if 'racy' in self.keys():
+ return self['racy']
+ else:
+ return None
+
+ def _SetRacy(self, val):
+ if val not in YOUTUBE_QUERY_VALID_RACY_PARAMETERS:
+ raise YouTubeError('Racy must be one of: %s ' %
+ ' '.join(YOUTUBE_QUERY_VALID_RACY_PARAMETERS))
+ self['racy'] = val
+
+ racy = property(_GetRacy, _SetRacy,
+ doc="""The racy query parameter""")
+
+class YouTubeUserQuery(YouTubeVideoQuery):
+
+ def __init__(self, username=None, feed_type=None, subscription_id=None,
+ text_query=None, params=None, categories=None):
+
+ uploads_favorites_playlists = ('uploads', 'favorites', 'playlists')
+
+ if feed_type is 'subscriptions' and subscription_id and username:
+ feed = "http://%s/feeds/users/%s/%s/%s" % (
+ YOUTUBE_SERVER, username, feed_type, subscription_id)
+ elif feed_type is 'subscriptions' and not subscription_id and username:
+ feed = "http://%s/feeds/users/%s/%s" % (
+ YOUTUBE_SERVER, username, feed_type)
+ elif feed_type in uploads_favorites_playlists:
+ feed = "http://%s/feeds/users/%s/%s" % (
+ YOUTUBE_SERVER, username, feed_type)
+ else:
+ feed = "http://%s/feeds/users" % (YOUTUBE_SERVER)
+
+ YouTubeVideoQuery.__init__(self, feed, text_query=text_query,
+ params=params, categories=categories)
+
+
+class YouTubePlaylistQuery(YouTubeVideoQuery):
+
+ def __init__(self, playlist_id, text_query=None, params=None,
+ categories=None):
+ if playlist_id:
+ feed = "http://%s/feeds/playlists/%s" % (YOUTUBE_SERVER, playlist_id)
+ else:
+ feed = "http://%s/feeds/playlists" % (YOUTUBE_SERVER)
+
+ YouTubeVideoQuery.__init__(self, feed, text_query=text_query,
+ params=params, categories=categories)
Modified: trunk/conduit/modules/GoogleModule/youtube-config.glade
==============================================================================
--- trunk/conduit/modules/GoogleModule/youtube-config.glade (original)
+++ trunk/conduit/modules/GoogleModule/youtube-config.glade Sat Jun 7 07:56:04 2008
@@ -1,423 +1,215 @@
-<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
-<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
-
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
<glade-interface>
-
-<widget class="GtkDialog" id="YouTubeSourceConfigDialog">
- <property name="border_width">5</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="title" translatable="yes">YouTube Source</property>
- <property name="type">GTK_WINDOW_TOPLEVEL</property>
- <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
- <property name="modal">False</property>
- <property name="resizable">False</property>
- <property name="destroy_with_parent">False</property>
- <property name="decorated">True</property>
- <property name="skip_taskbar_hint">False</property>
- <property name="skip_pager_hint">False</property>
- <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
- <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
- <property name="focus_on_map">True</property>
- <property name="urgency_hint">False</property>
- <property name="has_separator">True</property>
-
- <child internal-child="vbox">
- <widget class="GtkVBox" id="dialog-vbox1">
- <property name="visible">True</property>
- <property name="homogeneous">False</property>
- <property name="spacing">2</property>
-
- <child internal-child="action_area">
- <widget class="GtkHButtonBox" id="dialog-action_area1">
- <property name="visible">True</property>
- <property name="layout_style">GTK_BUTTONBOX_END</property>
-
- <child>
- <placeholder/>
- </child>
-
- <child>
- <widget class="GtkButton" id="button1">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="label">gtk-cancel</property>
- <property name="use_stock">True</property>
- <property name="relief">GTK_RELIEF_NORMAL</property>
- <property name="focus_on_click">True</property>
- <property name="response_id">-6</property>
- </widget>
- </child>
-
- <child>
- <widget class="GtkButton" id="button2">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="label">gtk-ok</property>
- <property name="use_stock">True</property>
- <property name="relief">GTK_RELIEF_NORMAL</property>
- <property name="focus_on_click">True</property>
- <property name="response_id">-5</property>
- </widget>
- </child>
-
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="pack_type">GTK_PACK_END</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkTable" id="table1">
- <property name="visible">True</property>
- <property name="n_rows">2</property>
- <property name="n_columns">2</property>
- <property name="homogeneous">False</property>
- <property name="row_spacing">5</property>
- <property name="column_spacing">5</property>
-
- <child>
- <widget class="GtkSpinButton" id="maxdownloads">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="climb_rate">1</property>
- <property name="digits">0</property>
- <property name="numeric">False</property>
- <property name="update_policy">GTK_UPDATE_ALWAYS</property>
- <property name="snap_to_ticks">False</property>
- <property name="wrap">False</property>
- <property name="adjustment">0 0 100 1 10 10</property>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkLabel" id="label3">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Max retrieved videos (0 is unlimited):</property>
- <property name="use_underline">False</property>
- <property name="use_markup">False</property>
- <property name="justify">GTK_JUSTIFY_LEFT</property>
- <property name="wrap">False</property>
- <property name="selectable">False</property>
- <property name="xalign">0.5</property>
- <property name="yalign">0.5</property>
- <property name="xpad">0</property>
- <property name="ypad">0</property>
- <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
- <property name="width_chars">-1</property>
- <property name="single_line_mode">False</property>
- <property name="angle">0</property>
- </widget>
- <packing>
- <property name="left_attach">0</property>
- <property name="right_attach">1</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkFrame" id="frame1">
- <property name="visible">True</property>
- <property name="label_xalign">0</property>
- <property name="label_yalign">0.5</property>
- <property name="shadow_type">GTK_SHADOW_NONE</property>
-
- <child>
- <widget class="GtkAlignment" id="alignment1">
- <property name="visible">True</property>
- <property name="xalign">0.5</property>
- <property name="yalign">0.5</property>
- <property name="xscale">1</property>
- <property name="yscale">1</property>
- <property name="top_padding">0</property>
- <property name="bottom_padding">0</property>
- <property name="left_padding">12</property>
- <property name="right_padding">0</property>
-
- <child>
- <widget class="GtkVBox" id="vbox1">
- <property name="visible">True</property>
- <property name="homogeneous">False</property>
- <property name="spacing">5</property>
-
- <child>
- <widget class="GtkRadioButton" id="mostviewed">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="label" translatable="yes">Most viewed</property>
- <property name="use_underline">True</property>
- <property name="relief">GTK_RELIEF_NORMAL</property>
- <property name="focus_on_click">True</property>
- <property name="active">True</property>
- <property name="inconsistent">False</property>
- <property name="draw_indicator">True</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkRadioButton" id="toprated">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="label" translatable="yes">Top rated</property>
- <property name="use_underline">True</property>
- <property name="relief">GTK_RELIEF_NORMAL</property>
- <property name="focus_on_click">True</property>
- <property name="active">False</property>
- <property name="inconsistent">False</property>
- <property name="draw_indicator">True</property>
- <property name="group">mostviewed</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkVBox" id="vbox3">
- <property name="visible">True</property>
- <property name="homogeneous">False</property>
- <property name="spacing">5</property>
-
- <child>
- <widget class="GtkRadioButton" id="byuser">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="label" translatable="yes">By user</property>
- <property name="use_underline">True</property>
- <property name="relief">GTK_RELIEF_NORMAL</property>
- <property name="focus_on_click">True</property>
- <property name="active">False</property>
- <property name="inconsistent">False</property>
- <property name="draw_indicator">True</property>
- <property name="group">mostviewed</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkFrame" id="frame">
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="label_xalign">0</property>
- <property name="label_yalign">0.5</property>
- <property name="shadow_type">GTK_SHADOW_NONE</property>
-
- <child>
- <widget class="GtkAlignment" id="alignment2">
- <property name="visible">True</property>
- <property name="xalign">0.5</property>
- <property name="yalign">0.5</property>
- <property name="xscale">1</property>
- <property name="yscale">1</property>
- <property name="top_padding">0</property>
- <property name="bottom_padding">0</property>
- <property name="left_padding">12</property>
- <property name="right_padding">0</property>
-
- <child>
- <widget class="GtkTable" id="table2">
- <property name="visible">True</property>
- <property name="n_rows">2</property>
- <property name="n_columns">2</property>
- <property name="homogeneous">False</property>
- <property name="row_spacing">5</property>
- <property name="column_spacing">5</property>
-
- <child>
- <widget class="GtkHBox" id="hbox2">
- <property name="visible">True</property>
- <property name="homogeneous">False</property>
- <property name="spacing">0</property>
-
- <child>
- <widget class="GtkRadioButton" id="uploadedby">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="label" translatable="yes">Uploaded by</property>
- <property name="use_underline">True</property>
- <property name="relief">GTK_RELIEF_NORMAL</property>
- <property name="focus_on_click">True</property>
- <property name="active">True</property>
- <property name="inconsistent">False</property>
- <property name="draw_indicator">True</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkRadioButton" id="favoritesof">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="label" translatable="yes">Favorites of</property>
- <property name="use_underline">True</property>
- <property name="relief">GTK_RELIEF_NORMAL</property>
- <property name="focus_on_click">True</property>
- <property name="active">False</property>
- <property name="inconsistent">False</property>
- <property name="draw_indicator">True</property>
- <property name="group">uploadedby</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">0</property>
- <property name="bottom_attach">1</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkHBox" id="hbox1">
- <property name="visible">True</property>
- <property name="homogeneous">False</property>
- <property name="spacing">0</property>
-
- <child>
- <widget class="GtkLabel" id="label4">
- <property name="visible">True</property>
- <property name="label" translatable="yes">User: </property>
- <property name="use_underline">False</property>
- <property name="use_markup">False</property>
- <property name="justify">GTK_JUSTIFY_LEFT</property>
- <property name="wrap">False</property>
- <property name="selectable">False</property>
- <property name="xalign">0.5</property>
- <property name="yalign">0.5</property>
- <property name="xpad">0</property>
- <property name="ypad">0</property>
- <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
- <property name="width_chars">-1</property>
- <property name="single_line_mode">False</property>
- <property name="angle">0</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">False</property>
- <property name="fill">False</property>
- </packing>
- </child>
-
- <child>
- <widget class="GtkEntry" id="user">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="editable">True</property>
- <property name="visibility">True</property>
- <property name="max_length">0</property>
- <property name="text" translatable="yes"></property>
- <property name="has_frame">True</property>
- <property name="invisible_char">â</property>
- <property name="activates_default">False</property>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- <property name="x_options"></property>
- </packing>
- </child>
- </widget>
- </child>
- </widget>
- </child>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
- </widget>
- </child>
- </widget>
- </child>
-
- <child>
- <widget class="GtkLabel" id="label1">
- <property name="visible">True</property>
- <property name="label" translatable="yes"><b>Donwload Videos</b></property>
- <property name="use_underline">False</property>
- <property name="use_markup">True</property>
- <property name="justify">GTK_JUSTIFY_LEFT</property>
- <property name="wrap">False</property>
- <property name="selectable">False</property>
- <property name="xalign">0.5</property>
- <property name="yalign">0.5</property>
- <property name="xpad">0</property>
- <property name="ypad">0</property>
- <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
- <property name="width_chars">-1</property>
- <property name="single_line_mode">False</property>
- <property name="angle">0</property>
- </widget>
- <packing>
- <property name="type">label_item</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="left_attach">0</property>
- <property name="right_attach">2</property>
- <property name="top_attach">0</property>
- <property name="bottom_attach">1</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="padding">0</property>
- <property name="expand">True</property>
- <property name="fill">True</property>
- </packing>
- </child>
- </widget>
- </child>
-</widget>
-
+ <widget class="GtkDialog" id="YouTubeTwoWayConfigDialog">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">YouTube Source</property>
+ <property name="resizable">False</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Account Details</b></property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Username:</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="username">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Password:</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_START</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ </widget>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Download Videos</b></property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <widget class="GtkRadioButton" id="mostviewed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Most viewed</property>
+ <property name="use_underline">True</property>
+ <property name="response_id">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="toprated">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Top rated</property>
+ <property name="use_underline">True</property>
+ <property name="response_id">0</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">mostviewed</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="uploadedby">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Uploaded by above user</property>
+ <property name="use_underline">True</property>
+ <property name="response_id">0</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">mostviewed</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="favoritesof">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Favorites of above user</property>
+ <property name="response_id">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">mostviewed</property>
+ </widget>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Max retrieved videos (0 is unlimited):</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkSpinButton" id="maxdownloads">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">0 0 100 1 10 10</property>
+ </widget>
+ <packing>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
</glade-interface>
Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac (original)
+++ trunk/configure.ac Sat Jun 7 07:56:04 2008
@@ -135,6 +135,7 @@
conduit/modules/GoogleModule/gdata/Makefile
conduit/modules/GoogleModule/gdata/apps/Makefile
conduit/modules/GoogleModule/gdata/base/Makefile
+conduit/modules/GoogleModule/gdata/blogger/Makefile
conduit/modules/GoogleModule/gdata/calendar/Makefile
conduit/modules/GoogleModule/gdata/codesearch/Makefile
conduit/modules/GoogleModule/gdata/contacts/Makefile
@@ -144,6 +145,7 @@
conduit/modules/GoogleModule/gdata/media/Makefile
conduit/modules/GoogleModule/gdata/photos/Makefile
conduit/modules/GoogleModule/gdata/spreadsheet/Makefile
+conduit/modules/GoogleModule/gdata/youtube/Makefile
conduit/modules/ShutterflyModule/Makefile
conduit/modules/ShutterflyModule/shutterfly/Makefile
conduit/modules/RhythmboxModule/Makefile
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]