summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip.withnall@collabora.co.uk>2015-07-10 10:38:11 +0100
committerPhilip Withnall <philip.withnall@collabora.co.uk>2015-07-10 12:22:19 +0100
commit687c2768c3585da9c2e5878c9377f7f9a7f08a19 (patch)
tree28d336fc5d03fb790e1adf668a609db85ee61e90
parentb37f185f7a24ec0ff87e4acadeb90ab2fd556382 (diff)
downloadlibgdata-687c2768c3585da9c2e5878c9377f7f9a7f08a19.tar.gz
youtube: Port YouTube comments to the v3 API
This should make YouTube comments work again. They have been ported to JSON from XML, and the GDataCommentable implementations updated to use the correct comment URIs.
-rw-r--r--gdata/services/youtube/gdata-youtube-comment.c375
-rw-r--r--gdata/services/youtube/gdata-youtube-service.c9
-rw-r--r--gdata/services/youtube/gdata-youtube-video.c53
3 files changed, 421 insertions, 16 deletions
diff --git a/gdata/services/youtube/gdata-youtube-comment.c b/gdata/services/youtube/gdata-youtube-comment.c
index f4996df1..e3aad2ac 100644
--- a/gdata/services/youtube/gdata-youtube-comment.c
+++ b/gdata/services/youtube/gdata-youtube-comment.c
@@ -1,7 +1,7 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* GData Client
- * Copyright (C) Philip Withnall 2011 <philip@tecnocode.co.uk>
+ * Copyright (C) Philip Withnall 2011, 2015 <philip@tecnocode.co.uk>
*
* GData Client is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -40,12 +40,24 @@
#include <config.h>
#include <glib.h>
+#include "gdata-parser.h"
+#include "gdata-private.h"
#include "gdata-youtube-comment.h"
#define GDATA_LINK_PARENT_COMMENT_URI "http://gdata.youtube.com/schemas/2007#in-reply-to"
static void gdata_youtube_comment_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
static void gdata_youtube_comment_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_youtube_comment_finalize (GObject *object);
+static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
+static void get_json (GDataParsable *parsable, JsonBuilder *builder);
+static const gchar *get_content_type (void);
+
+struct _GDataYouTubeCommentPrivate {
+ gchar *channel_id; /* owned */
+ gchar *video_id; /* owned */
+ gboolean can_reply;
+};
enum {
PROP_PARENT_COMMENT_URI = 1,
@@ -57,12 +69,20 @@ static void
gdata_youtube_comment_class_init (GDataYouTubeCommentClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
+ g_type_class_add_private (klass, sizeof (GDataYouTubeCommentPrivate));
+
gobject_class->get_property = gdata_youtube_comment_get_property;
gobject_class->set_property = gdata_youtube_comment_set_property;
+ gobject_class->finalize = gdata_youtube_comment_finalize;
+
+ parsable_class->parse_json = parse_json;
+ parsable_class->get_json = get_json;
+ parsable_class->get_content_type = get_content_type;
- entry_class->kind_term = "http://gdata.youtube.com/schemas/2007#comment";
+ entry_class->kind_term = "youtube#commentThread";
/**
* GDataYouTubeComment:parent-comment-uri:
@@ -83,7 +103,7 @@ gdata_youtube_comment_class_init (GDataYouTubeCommentClass *klass)
static void
gdata_youtube_comment_init (GDataYouTubeComment *self)
{
- /* Nothing to see here. */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_YOUTUBE_COMMENT, GDataYouTubeCommentPrivate);
}
static void
@@ -118,6 +138,297 @@ gdata_youtube_comment_set_property (GObject *object, guint property_id, const GV
}
}
+static void
+gdata_youtube_comment_finalize (GObject *object)
+{
+ GDataYouTubeCommentPrivate *priv = GDATA_YOUTUBE_COMMENT (object)->priv;
+
+ g_free (priv->channel_id);
+ g_free (priv->video_id);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_youtube_comment_parent_class)->finalize (object);
+}
+
+/* Reference: https://developers.google.com/youtube/v3/docs/comments#resource */
+static gboolean
+parse_comment (GDataParsable *parsable, JsonReader *reader, GError **error)
+{
+ GDataYouTubeComment *self = GDATA_YOUTUBE_COMMENT (parsable);
+ const gchar *id, *etag, *parent_id, *author_name, *author_uri;
+ const gchar *published_at, *updated_at;
+ gint64 published, updated;
+
+ /* Check this is an object. */
+ if (!json_reader_is_object (reader)) {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ /* id */
+ json_reader_read_member (reader, "id");
+ id = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ /* Empty ID? */
+ if (id == NULL || *id == '\0') {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ _gdata_entry_set_id (GDATA_ENTRY (parsable), id);
+
+ /* etag */
+ json_reader_read_member (reader, "etag");
+ etag = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ /* Empty ETag? */
+ if (etag != NULL && *id == '\0') {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ _gdata_entry_set_etag (GDATA_ENTRY (parsable), etag);
+
+ /* snippet */
+ json_reader_read_member (reader, "snippet");
+
+ if (!json_reader_is_object (reader)) {
+ json_reader_end_member (reader);
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ json_reader_read_member (reader, "textDisplay");
+ gdata_entry_set_content (GDATA_ENTRY (self),
+ json_reader_get_string_value (reader));
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "parentId");
+ parent_id = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ if (parent_id != NULL) {
+ gchar *uri = NULL;
+
+ uri = _gdata_service_build_uri ("https://www.googleapis.com"
+ "/youtube/v3/comments"
+ "?part=snippet"
+ "&id=%s", parent_id);
+ gdata_youtube_comment_set_parent_comment_uri (self, uri);
+ g_free (uri);
+ }
+
+ json_reader_read_member (reader, "authorDisplayName");
+ author_name = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ json_reader_read_member (reader, "authorChannelUrl");
+ author_uri = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ if (author_name != NULL && *author_name != '\0') {
+ GDataAuthor *author = NULL;
+
+ author = gdata_author_new (author_name, author_uri, NULL);
+ gdata_entry_add_author (GDATA_ENTRY (self), author);
+ }
+
+ json_reader_read_member (reader, "publishedAt");
+ published_at = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ if (published_at != NULL &&
+ gdata_parser_int64_from_iso8601 (published_at, &published)) {
+ _gdata_entry_set_published (GDATA_ENTRY (self), published);
+ } else if (published_at != NULL) {
+ /* Error */
+ gdata_parser_error_not_iso8601_format_json (reader,
+ published_at,
+ error);
+ json_reader_end_member (reader);
+ return FALSE;
+ }
+
+ json_reader_read_member (reader, "updatedAt");
+ updated_at = json_reader_get_string_value (reader);
+ json_reader_end_member (reader);
+
+ if (updated_at != NULL &&
+ gdata_parser_int64_from_iso8601 (updated_at, &updated)) {
+ _gdata_entry_set_updated (GDATA_ENTRY (self), updated);
+ } else if (updated_at != NULL) {
+ /* Error */
+ gdata_parser_error_not_iso8601_format_json (reader,
+ updated_at,
+ error);
+ json_reader_end_member (reader);
+ return FALSE;
+ }
+
+ /* FIXME: Implement:
+ * - channelId
+ * - videoId
+ * - textOriginal
+ * - canRate
+ * - viewerRating
+ * - likeCount
+ * - moderationStatus
+ * - authorProfileImageUrl
+ * - authorChannelId
+ * - authorGoogleplusProfileUrl
+ */
+
+ json_reader_end_member (reader);
+
+ return TRUE;
+}
+
+/* Reference: https://developers.google.com/youtube/v3/docs/commentThreads#resource */
+static gboolean
+parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
+{
+ gboolean success;
+ GDataYouTubeComment *self = GDATA_YOUTUBE_COMMENT (parsable);
+ GDataYouTubeCommentPrivate *priv = self->priv;
+
+ if (g_strcmp0 (json_reader_get_member_name (reader), "snippet") == 0) {
+ guint i;
+
+ /* Check this is an object. */
+ if (!json_reader_is_object (reader)) {
+ return gdata_parser_error_required_json_content_missing (reader, error);
+ }
+
+ for (i = 0; i < (guint) json_reader_count_members (reader); i++) {
+ json_reader_read_element (reader, i);
+
+ if (gdata_parser_string_from_json_member (reader, "channelId", P_DEFAULT, &priv->channel_id, &success, error) ||
+ gdata_parser_string_from_json_member (reader, "videoId", P_DEFAULT, &priv->video_id, &success, error) ||
+ gdata_parser_boolean_from_json_member (reader, "canReply", P_DEFAULT, &priv->can_reply, &success, error)) {
+ /* Fall through. */
+ } else if (g_strcmp0 (json_reader_get_member_name (reader), "topLevelComment") == 0) {
+ success = parse_comment (parsable, reader, error);
+ }
+
+ json_reader_end_element (reader);
+
+ if (!success) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else {
+ return GDATA_PARSABLE_CLASS (gdata_youtube_comment_parent_class)->parse_json (parsable, reader, user_data, error);
+ }
+
+ return TRUE;
+}
+
+/* Reference: https://developers.google.com/youtube/v3/docs/comments#resource */
+static void
+get_comment (GDataParsable *parsable, JsonBuilder *builder)
+{
+ GDataYouTubeComment *self = GDATA_YOUTUBE_COMMENT (parsable);
+ GDataEntry *entry = GDATA_ENTRY (parsable);
+ GDataYouTubeCommentPrivate *priv = GDATA_YOUTUBE_COMMENT (parsable)->priv;
+
+ json_builder_set_member_name (builder, "kind");
+ json_builder_add_string_value (builder, "youtube#comment");
+
+ if (gdata_entry_get_etag (entry) != NULL) {
+ json_builder_set_member_name (builder, "etag");
+ json_builder_add_string_value (builder,
+ gdata_entry_get_etag (entry));
+ }
+
+ if (gdata_entry_get_id (entry) != NULL) {
+ json_builder_set_member_name (builder, "id");
+ json_builder_add_string_value (builder,
+ gdata_entry_get_id (entry));
+ }
+
+ json_builder_set_member_name (builder, "snippet");
+ json_builder_begin_object (builder);
+
+ if (priv->channel_id != NULL) {
+ json_builder_set_member_name (builder, "channelId");
+ json_builder_add_string_value (builder, priv->channel_id);
+ }
+
+ if (priv->video_id != NULL) {
+ json_builder_set_member_name (builder, "videoId");
+ json_builder_add_string_value (builder, priv->video_id);
+ }
+
+ /* Note we build textOriginal and parse textDisplay. */
+ if (gdata_entry_get_content (entry) != NULL) {
+ json_builder_set_member_name (builder, "textOriginal");
+ json_builder_add_string_value (builder,
+ gdata_entry_get_content (entry));
+ }
+
+ if (gdata_youtube_comment_get_parent_comment_uri (self) != NULL) {
+ json_builder_set_member_name (builder, "parentId");
+ json_builder_add_string_value (builder,
+ gdata_youtube_comment_get_parent_comment_uri (self));
+ }
+
+ json_builder_end_object (builder);
+}
+
+/* Reference: https://developers.google.com/youtube/v3/docs/commentThreads#resource
+ *
+ * Sort of. If creating a new top-level comment, we need to create a
+ * commentThread; otherwise we need to create a comment. */
+static void
+get_json (GDataParsable *parsable, JsonBuilder *builder)
+{
+ GDataEntry *entry = GDATA_ENTRY (parsable);
+ GDataYouTubeCommentPrivate *priv = GDATA_YOUTUBE_COMMENT (parsable)->priv;
+
+ /* Don’t chain up because it’s mostly irrelevant. */
+ json_builder_set_member_name (builder, "kind");
+ json_builder_add_string_value (builder, "youtube#commentThread");
+
+ if (gdata_entry_get_etag (entry) != NULL) {
+ json_builder_set_member_name (builder, "etag");
+ json_builder_add_string_value (builder,
+ gdata_entry_get_etag (entry));
+ }
+
+ if (gdata_entry_get_id (entry) != NULL) {
+ json_builder_set_member_name (builder, "id");
+ json_builder_add_string_value (builder,
+ gdata_entry_get_id (entry));
+ }
+
+ /* snippet object. */
+ json_builder_set_member_name (builder, "snippet");
+ json_builder_begin_object (builder);
+
+ if (priv->channel_id != NULL) {
+ json_builder_set_member_name (builder, "channelId");
+ json_builder_add_string_value (builder, priv->channel_id);
+ }
+
+ if (priv->video_id != NULL) {
+ json_builder_set_member_name (builder, "videoId");
+ json_builder_add_string_value (builder, priv->video_id);
+ }
+
+ json_builder_set_member_name (builder, "topLevelComment");
+ json_builder_begin_object (builder);
+ get_comment (parsable, builder);
+ json_builder_end_object (builder);
+
+ json_builder_end_object (builder);
+}
+
+static const gchar *
+get_content_type (void)
+{
+ return "application/json";
+}
+
/**
* gdata_youtube_comment_new:
* @id: the comment's ID, or %NULL
@@ -205,3 +516,61 @@ gdata_youtube_comment_set_parent_comment_uri (GDataYouTubeComment *self, const g
g_object_notify (G_OBJECT (self), "parent-comment-uri");
}
+
+G_GNUC_INTERNAL void
+_gdata_youtube_comment_set_video_id (GDataYouTubeComment *self,
+ const gchar *video_id);
+G_GNUC_INTERNAL void
+_gdata_youtube_comment_set_channel_id (GDataYouTubeComment *self,
+ const gchar *channel_id);
+
+/**
+ * _gdata_youtube_comment_set_video_id:
+ * @self: a #GDataYouTubeComment
+ * @video_id: (nullable): the comment’s video ID, or %NULL
+ *
+ * Set the ID of the video the comment is attached to. This may be %NULL if the
+ * comment has not yet been inserted, or if it is just attached to a channel
+ * rather than a video.
+ *
+ * Since: UNRELEASED
+ */
+void
+_gdata_youtube_comment_set_video_id (GDataYouTubeComment *self,
+ const gchar *video_id)
+{
+ GDataYouTubeCommentPrivate *priv;
+
+ g_return_if_fail (GDATA_IS_YOUTUBE_COMMENT (self));
+ g_return_if_fail (video_id == NULL || *video_id != '\0');
+
+ priv = self->priv;
+
+ g_free (priv->video_id);
+ priv->video_id = g_strdup (video_id);
+}
+
+/**
+ * _gdata_youtube_comment_set_channel_id:
+ * @self: a #GDataYouTubeComment
+ * @channel_id: (nullable): the comment’s channel ID, or %NULL
+ *
+ * Set the ID of the channel the comment is attached to. This may be %NULL if
+ * the comment has not yet been inserted, but must be set otherwise.
+ *
+ * Since: UNRELEASED
+ */
+void
+_gdata_youtube_comment_set_channel_id (GDataYouTubeComment *self,
+ const gchar *channel_id)
+{
+ GDataYouTubeCommentPrivate *priv;
+
+ g_return_if_fail (GDATA_IS_YOUTUBE_COMMENT (self));
+ g_return_if_fail (channel_id == NULL || *channel_id != '\0');
+
+ priv = self->priv;
+
+ g_free (priv->channel_id);
+ priv->channel_id = g_strdup (channel_id);
+}
diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c
index 8afd1478..c7fad800 100644
--- a/gdata/services/youtube/gdata-youtube-service.c
+++ b/gdata/services/youtube/gdata-youtube-service.c
@@ -289,6 +289,8 @@ enum {
/* Reference: https://developers.google.com/youtube/v3/guides/authentication */
_GDATA_DEFINE_AUTHORIZATION_DOMAIN (youtube, "youtube",
"https://www.googleapis.com/auth/youtube")
+_GDATA_DEFINE_AUTHORIZATION_DOMAIN (youtube_force_ssl, "youtube-force-ssl",
+ "https://www.googleapis.com/auth/youtube.force-ssl")
G_DEFINE_TYPE_WITH_CODE (GDataYouTubeService, gdata_youtube_service, GDATA_TYPE_SERVICE,
G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE,
gdata_youtube_service_batchable_init))
@@ -613,7 +615,12 @@ parent:
static GList *
get_authorization_domains (void)
{
- return g_list_prepend (NULL, get_youtube_authorization_domain ());
+ GList *authorization_domains = NULL;
+
+ authorization_domains = g_list_prepend (authorization_domains, get_youtube_authorization_domain ());
+ authorization_domains = g_list_prepend (authorization_domains, get_youtube_force_ssl_authorization_domain ());
+
+ return authorization_domains;
}
/**
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index 06d346f0..3f8b8685 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -26,10 +26,7 @@
* #GDataYouTubeVideo is a subclass of #GDataEntry to represent a single video on YouTube, either when uploading or querying.
*
* #GDataYouTubeVideo implements #GDataCommentable, allowing comments on videos
- * to be queried and added. However, the initial version of the v3 YouTube API
- * does not currently support comments, so all #GDataCommentable calls will
- * fail. It is hoped that the YouTube API will regain support for comments in
- * future.
+ * to be queried and added.
*
* For more details of YouTube’s GData API, see the
* <ulink type="http" url="https://developers.google.com/youtube/v3/docs/">
@@ -117,6 +114,7 @@ struct _GDataYouTubeVideoPrivate {
GDataMediaCategory *category;
guint duration;
gboolean is_private;
+ gchar *channel_id; /* owned */
/* Location. */
gdouble latitude;
@@ -553,6 +551,7 @@ gdata_youtube_video_finalize (GObject *object)
g_free (priv->location);
g_hash_table_destroy (priv->access_controls);
g_strfreev (priv->keywords);
+ g_free (priv->channel_id);
g_free (priv->player_uri);
g_strfreev (priv->region_restriction_allowed);
g_strfreev (priv->region_restriction_blocked);
@@ -1102,7 +1101,8 @@ parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GEr
g_free (description);
} else if (gdata_parser_strv_from_json_member (reader, "tags", P_DEFAULT, &priv->keywords, &success, error) ||
- thumbnails_from_json_member (reader, "thumbnails", P_DEFAULT, &priv->thumbnails, &success, error)) {
+ thumbnails_from_json_member (reader, "thumbnails", P_DEFAULT, &priv->thumbnails, &success, error) ||
+ gdata_parser_string_from_json_member (reader, "channelId", P_DEFAULT, &priv->channel_id, &success, error)) {
/* Fall through. */
} else if (gdata_parser_string_from_json_member (reader, "categoryId", P_DEFAULT, &category_id, &success, error)) {
if (success) {
@@ -1482,24 +1482,53 @@ get_authorization_domain (GDataCommentable *self)
static gchar *
get_query_comments_uri (GDataCommentable *self)
{
- /* FIXME: Currently unsupported:
- * https://developers.google.com/youtube/v3/migration-guide#to_be_migrated */
- return NULL;
+ const gchar *video_id;
+
+ video_id = gdata_entry_get_id (GDATA_ENTRY (self));
+
+ /* https://developers.google.com/youtube/v3/docs/commentThreads/list */
+ return _gdata_service_build_uri ("https://www.googleapis.com"
+ "/youtube/v3/commentThreads"
+ "?part=snippet"
+ "&videoId=%s", video_id);
}
+G_GNUC_INTERNAL void
+_gdata_youtube_comment_set_video_id (GDataYouTubeComment *self,
+ const gchar *video_id);
+G_GNUC_INTERNAL void
+_gdata_youtube_comment_set_channel_id (GDataYouTubeComment *self,
+ const gchar *channel_id);
+
static gchar *
get_insert_comment_uri (GDataCommentable *self, GDataComment *comment_)
{
- /* FIXME: Currently unsupported:
- * https://developers.google.com/youtube/v3/migration-guide#to_be_migrated */
- return NULL;
+ const gchar *video_id, *channel_id;
+ GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (self)->priv;
+
+ video_id = gdata_entry_get_id (GDATA_ENTRY (self));
+ channel_id = priv->channel_id;
+
+ /* https://developers.google.com/youtube/v3/docs/commentThreads/insert
+ *
+ * We have to set the video ID on the @comment_. */
+ _gdata_youtube_comment_set_video_id (GDATA_YOUTUBE_COMMENT (comment_),
+ video_id);
+ _gdata_youtube_comment_set_channel_id (GDATA_YOUTUBE_COMMENT (comment_),
+ channel_id);
+
+ return _gdata_service_build_uri ("https://www.googleapis.com"
+ "/youtube/v3/commentThreads"
+ "?part=snippet"
+ "&shareOnGooglePlus=false");
}
static gboolean
is_comment_deletable (GDataCommentable *self, GDataComment *comment_)
{
/* FIXME: Currently unsupported:
- * https://developers.google.com/youtube/v3/migration-guide#to_be_migrated */
+ * https://developers.google.com/youtube/v3/migration-guide#to_be_migrated
+ * https://developers.google.com/youtube/v3/guides/implementation/comments#comments-delete */
return FALSE;
}