Use GLib serialization (#180)
This commit is contained in:
parent
287065e98b
commit
7e97ca1c54
|
@ -17,7 +17,6 @@ asresources = gnome.compile_resources(
|
|||
)
|
||||
|
||||
libhandy_dep = dependency('libhandy-1', version: '>= 0.80.0')
|
||||
|
||||
if not libhandy_dep.found()
|
||||
libhandy = subproject(
|
||||
'libhandy',
|
||||
|
@ -39,7 +38,6 @@ executable(
|
|||
'src/Desktop.vala',
|
||||
'src/Drawing.vala',
|
||||
'src/Html.vala',
|
||||
'src/Utils.vala',
|
||||
'src/Request.vala',
|
||||
'src/InstanceAccount.vala',
|
||||
'src/Services/Streams.vala',
|
||||
|
@ -59,6 +57,7 @@ executable(
|
|||
'src/API/NotificationType.vala',
|
||||
'src/API/Attachment.vala',
|
||||
'src/API/Conversation.vala',
|
||||
'src/API/Entity.vala',
|
||||
'src/Widgets/Widgetizable.vala',
|
||||
'src/Widgets/Avatar.vala',
|
||||
'src/Widgets/AccountsButton.vala',
|
||||
|
@ -90,8 +89,8 @@ executable(
|
|||
dependency('glib-2.0', version: '>=2.30.0'),
|
||||
dependency('gee-0.8', version: '>=0.8.5'),
|
||||
dependency('granite', version: '>=5.2.0'),
|
||||
dependency('json-glib-1.0'),
|
||||
dependency('libsoup-2.4'),
|
||||
dependency('json-glib-1.0', version: '>=1.4.4'),
|
||||
libhandy_dep,
|
||||
],
|
||||
install: true,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
public class Tootle.API.Account : GLib.Object {
|
||||
public class Tootle.API.Account : Entity {
|
||||
|
||||
public int64 id { get; set; }
|
||||
public string id { get; set; }
|
||||
public string username { get; set; }
|
||||
public string acct { get; set; }
|
||||
public string? _display_name = null;
|
||||
|
@ -19,69 +19,12 @@ public class Tootle.API.Account : GLib.Object {
|
|||
public string created_at { get; set; }
|
||||
public int64 followers_count { get; set; }
|
||||
public int64 following_count { get; set; }
|
||||
public int64 posts_count { get; set; }
|
||||
public int64 statuses_count { get; set; }
|
||||
public Relationship? rs { get; set; default = null; }
|
||||
|
||||
public Account (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
username: obj.get_string_member ("username"),
|
||||
acct: obj.get_string_member ("acct"),
|
||||
display_name: obj.get_string_member ("display_name"),
|
||||
note: obj.get_string_member ("note"),
|
||||
avatar: obj.get_string_member ("avatar"),
|
||||
header: obj.get_string_member ("header"),
|
||||
url: obj.get_string_member ("url"),
|
||||
created_at: obj.get_string_member ("created_at"),
|
||||
|
||||
followers_count: obj.get_int_member ("followers_count"),
|
||||
following_count: obj.get_int_member ("following_count"),
|
||||
posts_count: obj.get_int_member ("statuses_count")
|
||||
);
|
||||
|
||||
if (obj.has_member ("fields")) {
|
||||
obj.get_array_member ("fields").foreach_element ((array, i, node) => {
|
||||
var field_obj = node.get_object ();
|
||||
var field_name = field_obj.get_string_member ("name");
|
||||
var field_val = field_obj.get_string_member ("value");
|
||||
note += "\n";
|
||||
note += field_name + ": ";
|
||||
note += field_val;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
builder.set_member_name ("id");
|
||||
builder.add_string_value (id.to_string ());
|
||||
builder.set_member_name ("created_at");
|
||||
builder.add_string_value (created_at);
|
||||
builder.set_member_name ("following_count");
|
||||
builder.add_int_value (following_count);
|
||||
builder.set_member_name ("followers_count");
|
||||
builder.add_int_value (followers_count);
|
||||
builder.set_member_name ("statuses_count");
|
||||
builder.add_int_value (posts_count);
|
||||
builder.set_member_name ("display_name");
|
||||
builder.add_string_value (display_name);
|
||||
builder.set_member_name ("username");
|
||||
builder.add_string_value (username);
|
||||
builder.set_member_name ("acct");
|
||||
builder.add_string_value (acct);
|
||||
builder.set_member_name ("note");
|
||||
builder.add_string_value (note);
|
||||
builder.set_member_name ("header");
|
||||
builder.add_string_value (header);
|
||||
builder.set_member_name ("avatar");
|
||||
builder.add_string_value (avatar);
|
||||
builder.set_member_name ("url");
|
||||
builder.add_string_value (url);
|
||||
|
||||
builder.end_object ();
|
||||
return builder.get_root ();
|
||||
}
|
||||
public static Account from (Json.Node node) throws Error {
|
||||
return Entity.from_json (typeof (API.Account), node) as API.Account;
|
||||
}
|
||||
|
||||
public bool is_self () {
|
||||
return id == accounts.active.id;
|
||||
|
@ -92,7 +35,7 @@ public class Tootle.API.Account : GLib.Object {
|
|||
.with_account (accounts.active)
|
||||
.with_param ("id", id.to_string ())
|
||||
.then_parse_array (node => {
|
||||
rs = new Relationship (node.get_object ());
|
||||
rs = API.Relationship.from (node);
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
|
@ -103,8 +46,8 @@ public class Tootle.API.Account : GLib.Object {
|
|||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||
.with_account (accounts.active)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
rs = new Relationship (root);
|
||||
var node = network.parse_node (msg);
|
||||
rs = API.Relationship.from (node);
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
|
@ -115,8 +58,8 @@ public class Tootle.API.Account : GLib.Object {
|
|||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||
.with_account (accounts.active)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
rs = new Relationship (root);
|
||||
var node = network.parse_node (msg);
|
||||
rs = API.Relationship.from (node);
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
|
@ -127,8 +70,8 @@ public class Tootle.API.Account : GLib.Object {
|
|||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||
.with_account (accounts.active)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
rs = new Relationship (root);
|
||||
var node = network.parse_node (msg);
|
||||
rs = API.Relationship.from (node);
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
|
|
|
@ -1,45 +1,13 @@
|
|||
public class Tootle.API.Attachment : GLib.Object {
|
||||
public class Tootle.API.Attachment : Entity {
|
||||
|
||||
public int64 id { get; construct set; }
|
||||
public string id { get; set; }
|
||||
public string kind { get; set; }
|
||||
public string url { get; set; }
|
||||
public string? description { get; set; default = null; }
|
||||
|
||||
public string? _preview_url = null;
|
||||
public string? description { get; set; }
|
||||
public string? _preview_url { get; set; }
|
||||
public string preview_url {
|
||||
set { this._preview_url = value; }
|
||||
get { return (_preview_url == null || _preview_url == "") ? url : _preview_url; }
|
||||
}
|
||||
|
||||
public Attachment (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
kind: obj.get_string_member ("type"),
|
||||
preview_url: obj.get_string_member ("preview_url"),
|
||||
url: obj.get_string_member ("url"),
|
||||
description: obj.get_string_member ("description")
|
||||
);
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
builder.set_member_name ("id");
|
||||
builder.add_string_value (id.to_string ());
|
||||
builder.set_member_name ("type");
|
||||
builder.add_string_value (kind);
|
||||
builder.set_member_name ("url");
|
||||
builder.add_string_value (url);
|
||||
builder.set_member_name ("preview_url");
|
||||
builder.add_string_value (preview_url);
|
||||
|
||||
if (description != null) {
|
||||
builder.set_member_name ("description");
|
||||
builder.add_string_value (description);
|
||||
}
|
||||
|
||||
builder.end_object ();
|
||||
return builder.get_root ();
|
||||
}
|
||||
set { this._preview_url = value; }
|
||||
get { return (this._preview_url == null || this._preview_url == "") ? url : _preview_url; }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
public class Tootle.API.Conversation : GLib.Object, Json.Serializable, Widgetizable {
|
||||
public class Tootle.API.Conversation : Entity, Widgetizable {
|
||||
|
||||
public string id { get; construct set; }
|
||||
public bool unread { get; set; default = false; }
|
||||
|
||||
public Conversation () {
|
||||
GLib.Object ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
using Json;
|
||||
|
||||
public class Tootle.Entity : GLib.Object, Widgetizable, Json.Serializable {
|
||||
|
||||
public static string[] ignore_props = {"formal", "handle", "short-instance", "has-spoiler"};
|
||||
|
||||
public new ParamSpec[] list_properties () {
|
||||
ParamSpec[] specs = {};
|
||||
foreach (ParamSpec spec in get_class ().list_properties ()) {
|
||||
if (!(spec.name in ignore_props))
|
||||
specs += spec;
|
||||
}
|
||||
return specs;
|
||||
}
|
||||
|
||||
|
||||
public void patch (GLib.Object with) {
|
||||
var props = with.get_class ().list_properties ();
|
||||
foreach (var prop in props) {
|
||||
var name = prop.get_name ();
|
||||
var defined = get_class ().find_property (name) != null;
|
||||
var forbidden = name in ignore_props;
|
||||
if (defined && !forbidden) {
|
||||
var val = Value (prop.value_type);
|
||||
with.get_property (name, ref val);
|
||||
base.set_property (name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Entity from_json (Type type, Json.Node? node) throws Oopsie {
|
||||
if (node == null)
|
||||
throw new Oopsie.PARSING (@"Received Json.Node for $(type.name ()) is null!");
|
||||
|
||||
var obj = node.get_object ();
|
||||
if (obj == null)
|
||||
throw new Oopsie.PARSING (@"Received Json.Node for $(type.name ()) is not a Json.Object!");
|
||||
|
||||
var kind = obj.get_member ("type");
|
||||
if (kind != null) {
|
||||
obj.set_member ("kind", kind);
|
||||
obj.remove_member ("type");
|
||||
}
|
||||
|
||||
return Json.gobject_deserialize (type, node) as Entity;
|
||||
}
|
||||
|
||||
public Json.Node to_json () {
|
||||
return Json.gobject_serialize (this);
|
||||
}
|
||||
|
||||
public string to_json_data () {
|
||||
size_t len;
|
||||
return Json.gobject_to_data (this, out len);
|
||||
}
|
||||
|
||||
public override bool deserialize_property (string prop, out Value val, ParamSpec spec, Json.Node node) {
|
||||
// debug (@"deserializing $prop of type $(val.type_name ())");
|
||||
var success = default_deserialize_property (prop, out val, spec, node);
|
||||
|
||||
var type = spec.value_type;
|
||||
if (val.type () == Type.INVALID) { // Fix for glib-json < 1.5.1
|
||||
val.init (type);
|
||||
spec.set_value_default (ref val);
|
||||
type = spec.value_type;
|
||||
}
|
||||
|
||||
if (type.is_a (typeof (Gee.ArrayList))) {
|
||||
Type contains;
|
||||
switch (prop) {
|
||||
case "media-attachments":
|
||||
contains = typeof (API.Attachment);
|
||||
break;
|
||||
case "mentions":
|
||||
contains = typeof (API.Mention);
|
||||
break;
|
||||
default:
|
||||
contains = typeof (Entity);
|
||||
break;
|
||||
}
|
||||
return des_list (out val, node, contains);
|
||||
}
|
||||
else if (type.is_a (typeof (API.NotificationType)))
|
||||
return des_notification_type (out val, node);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool des_notification_type (out Value val, Json.Node node) {
|
||||
var str = node.get_string ();
|
||||
val = API.NotificationType.from_string (str);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool des_list (out Value val, Json.Node node, Type type) {
|
||||
if (!node.is_null ()) {
|
||||
var arr = new Gee.ArrayList<Entity> ();
|
||||
node.get_array ().foreach_element ((array, i, elem) => {
|
||||
var obj = Entity.from_json (type, elem);
|
||||
arr.add (obj);
|
||||
});
|
||||
val = arr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Json.Node serialize_property (string prop, Value val, ParamSpec spec) {
|
||||
var type = spec.value_type;
|
||||
// debug (@"serializing $prop of type $(val.type_name ())");
|
||||
|
||||
if (type.is_a (typeof (Gee.ArrayList)))
|
||||
return ser_list (prop, val, spec);
|
||||
if (type.is_a (typeof (API.NotificationType)))
|
||||
return ser_notification_type (prop, val, spec);
|
||||
|
||||
return default_serialize_property (prop, val, spec);
|
||||
}
|
||||
|
||||
static Json.Node ser_notification_type (string prop, Value val, ParamSpec spec) {
|
||||
var enum_val = (API.NotificationType) val;
|
||||
var node = new Json.Node (NodeType.VALUE);
|
||||
node.set_string (enum_val.to_string ());
|
||||
return node;
|
||||
}
|
||||
|
||||
static Json.Node ser_list (string prop, Value val, ParamSpec spec) {
|
||||
var list = (Gee.ArrayList<Entity>) val;
|
||||
if (list == null)
|
||||
return new Json.Node (NodeType.NULL);
|
||||
|
||||
var arr = new Json.Array ();
|
||||
list.@foreach (e => {
|
||||
var enode = e.to_json ();
|
||||
arr.add_element (enode);
|
||||
return true;
|
||||
});
|
||||
|
||||
var node = new Json.Node (NodeType.ARRAY);
|
||||
node.set_array (arr);
|
||||
return node;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +1,11 @@
|
|||
public class Tootle.API.Mention : GLib.Object {
|
||||
public class Tootle.API.Mention : Entity {
|
||||
|
||||
public int64 id { get; construct set; }
|
||||
public string id { get; construct set; }
|
||||
public string username { get; construct set; }
|
||||
public string acct { get; construct set; }
|
||||
public string url { get; construct set; }
|
||||
|
||||
public Mention (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
username: obj.get_string_member ("username"),
|
||||
acct: obj.get_string_member ("acct"),
|
||||
url: obj.get_string_member ("url")
|
||||
);
|
||||
}
|
||||
|
||||
public Mention.from_account (Account account) {
|
||||
public Mention.from_account (API.Account account) {
|
||||
Object (
|
||||
id: account.id,
|
||||
username: account.username,
|
||||
|
@ -23,19 +14,4 @@ public class Tootle.API.Mention : GLib.Object {
|
|||
);
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
builder.set_member_name ("id");
|
||||
builder.add_string_value (id.to_string ());
|
||||
builder.set_member_name ("username");
|
||||
builder.add_string_value (username);
|
||||
builder.set_member_name ("acct");
|
||||
builder.add_string_value (acct);
|
||||
builder.set_member_name ("url");
|
||||
builder.add_string_value (url);
|
||||
builder.end_object ();
|
||||
return builder.get_root ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,59 +1,15 @@
|
|||
public class Tootle.API.Notification : GLib.Object, Widgetizable {
|
||||
public class Tootle.API.Notification : Entity, Widgetizable {
|
||||
|
||||
public int64 id { get; construct set; }
|
||||
public Account account { get; construct set; }
|
||||
|
||||
public NotificationType kind { get; set; }
|
||||
public string id { get; set; }
|
||||
public API.Account account { get; set; }
|
||||
public API.NotificationType kind { get; set; }
|
||||
public string created_at { get; set; }
|
||||
public Status? status { get; set; default = null; }
|
||||
|
||||
public Notification (Json.Object obj) throws Oopsie {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
kind: NotificationType.from_string (obj.get_string_member ("type")),
|
||||
created_at: obj.get_string_member ("created_at"),
|
||||
account: new Account (obj.get_object_member ("account"))
|
||||
);
|
||||
|
||||
if (obj.has_member ("status"))
|
||||
status = new Status (obj.get_object_member ("status"));
|
||||
}
|
||||
|
||||
public Notification.follow_request (Json.Object obj) {
|
||||
Object (
|
||||
id: 0,
|
||||
kind: NotificationType.FOLLOW_REQUEST,
|
||||
account: new Account (obj)
|
||||
);
|
||||
}
|
||||
public API.Status? status { get; set; default = null; }
|
||||
|
||||
public override Gtk.Widget to_widget () {
|
||||
return new Widgets.Notification (this);
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
builder.set_member_name ("id");
|
||||
builder.add_string_value (id.to_string ());
|
||||
builder.set_member_name ("type");
|
||||
builder.add_string_value (kind.to_string ());
|
||||
builder.set_member_name ("created_at");
|
||||
builder.add_string_value (created_at);
|
||||
|
||||
if (status != null) {
|
||||
builder.set_member_name ("status");
|
||||
builder.add_value (status.serialize ());
|
||||
}
|
||||
if (account != null) {
|
||||
builder.set_member_name ("account");
|
||||
builder.add_value (account.serialize ());
|
||||
}
|
||||
|
||||
builder.end_object ();
|
||||
return builder.get_root ();
|
||||
}
|
||||
|
||||
public Soup.Message? dismiss () {
|
||||
if (kind == NotificationType.WATCHLIST) {
|
||||
if (accounts.active.cached_notifications.remove (this))
|
||||
|
@ -66,7 +22,7 @@ public class Tootle.API.Notification : GLib.Object, Widgetizable {
|
|||
|
||||
var req = new Request.POST ("/api/v1/notifications/dismiss")
|
||||
.with_account (accounts.active)
|
||||
.with_param ("id", id.to_string ())
|
||||
.with_param ("id", id)
|
||||
.exec ();
|
||||
return req;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
public class Tootle.API.Relationship : GLib.Object {
|
||||
public class Tootle.API.Relationship : Entity {
|
||||
|
||||
public int64 id { get; construct set; }
|
||||
public string id { get; set; }
|
||||
public bool following { get; set; default = false; }
|
||||
public bool followed_by { get; set; default = false; }
|
||||
public bool muting { get; set; default = false; }
|
||||
|
@ -9,17 +9,8 @@ public class Tootle.API.Relationship : GLib.Object {
|
|||
public bool blocking { get; set; default = false; }
|
||||
public bool domain_blocking { get; set; default = false; }
|
||||
|
||||
public Relationship (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
following: obj.get_boolean_member ("following"),
|
||||
followed_by: obj.get_boolean_member ("followed_by"),
|
||||
blocking: obj.get_boolean_member ("blocking"),
|
||||
muting: obj.get_boolean_member ("muting"),
|
||||
muting_notifications: obj.get_boolean_member ("muting_notifications"),
|
||||
requested: obj.get_boolean_member ("requested"),
|
||||
domain_blocking: obj.get_boolean_member ("domain_blocking")
|
||||
);
|
||||
}
|
||||
public static Relationship from (Json.Node node) throws Error {
|
||||
return Entity.from_json (typeof (API.Relationship), node) as API.Relationship;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
using Gee;
|
||||
|
||||
public class Tootle.API.Status : GLib.Object, Widgetizable {
|
||||
public class Tootle.API.Status : Entity, Widgetizable {
|
||||
|
||||
public int64 id { get; construct set; } //TODO: IDs are no longer guaranteed to be numbers. Replace with strings.
|
||||
public API.Account account { get; construct set; }
|
||||
public string id { get; set; }
|
||||
public API.Account account { get; set; }
|
||||
public string uri { get; set; }
|
||||
public string? url { get; set; default = null; }
|
||||
public string? spoiler_text { get; set; default = null; }
|
||||
public string? in_reply_to_id { get; set; default = null; }
|
||||
public string? in_reply_to_account_id { get; set; default = null; }
|
||||
|
@ -15,93 +14,52 @@ public class Tootle.API.Status : GLib.Object, Widgetizable {
|
|||
public int64 favourites_count { get; set; default = 0; }
|
||||
public string created_at { get; set; default = "0"; }
|
||||
public bool reblogged { get; set; default = false; }
|
||||
public bool favorited { get; set; default = false; }
|
||||
public bool favourited { get; set; default = false; }
|
||||
public bool sensitive { get; set; default = false; }
|
||||
public bool muted { get; set; default = false; }
|
||||
public bool pinned { get; set; default = false; }
|
||||
public API.Visibility visibility { get; set; default = API.Visibility.PUBLIC; }
|
||||
public API.Visibility visibility { get; set; default = settings.default_post_visibility; }
|
||||
public API.Status? reblog { get; set; default = null; }
|
||||
public ArrayList<API.Mention>? mentions { get; set; default = null; }
|
||||
public ArrayList<API.Attachment>? attachments { get; set; default = null; }
|
||||
public ArrayList<API.Attachment>? media_attachments { get; set; default = null; }
|
||||
|
||||
public string? _url { get; set; }
|
||||
public string url {
|
||||
owned get { return this.get_modified_url (); }
|
||||
set { this._url = value; }
|
||||
}
|
||||
string get_modified_url () {
|
||||
if (this._url == null) {
|
||||
return this.uri.replace ("/activity", "");
|
||||
}
|
||||
return this._url;
|
||||
}
|
||||
|
||||
public Status formal {
|
||||
get { return reblog ?? this; }
|
||||
}
|
||||
|
||||
public bool has_spoiler {
|
||||
public bool has_spoiler {
|
||||
get {
|
||||
return formal.spoiler_text != null || formal.sensitive;
|
||||
return formal.sensitive ||
|
||||
!(formal.spoiler_text == null || formal.spoiler_text == "");
|
||||
}
|
||||
}
|
||||
|
||||
public Status (Json.Object obj) {
|
||||
Object (
|
||||
id: int64.parse (obj.get_string_member ("id")),
|
||||
account: new Account (obj.get_object_member ("account")),
|
||||
uri: obj.get_string_member ("uri"),
|
||||
created_at: obj.get_string_member ("created_at"),
|
||||
content: Html.simplify ( obj.get_string_member ("content")),
|
||||
sensitive: obj.get_boolean_member ("sensitive"),
|
||||
visibility: Visibility.from_string (obj.get_string_member ("visibility")),
|
||||
|
||||
in_reply_to_id: obj.get_string_member ("in_reply_to_id") ?? null,
|
||||
in_reply_to_account_id: obj.get_string_member ("in_reply_to_account_id") ?? null,
|
||||
|
||||
replies_count: obj.get_int_member ("replies_count"),
|
||||
reblogs_count: obj.get_int_member ("reblogs_count"),
|
||||
favourites_count: obj.get_int_member ("favourites_count")
|
||||
);
|
||||
|
||||
if (obj.has_member ("url"))
|
||||
url = obj.get_string_member ("url");
|
||||
else
|
||||
url = obj.get_string_member ("uri").replace ("/activity", "");
|
||||
|
||||
var spoiler = obj.get_string_member ("spoiler_text");
|
||||
if (spoiler != "")
|
||||
spoiler_text = Html.simplify (spoiler);
|
||||
|
||||
if (obj.has_member ("reblogged"))
|
||||
reblogged = obj.get_boolean_member ("reblogged");
|
||||
if (obj.has_member ("favourited"))
|
||||
favorited = obj.get_boolean_member ("favourited");
|
||||
if (obj.has_member ("muted"))
|
||||
muted = obj.get_boolean_member ("muted");
|
||||
if (obj.has_member ("pinned"))
|
||||
pinned = obj.get_boolean_member ("pinned");
|
||||
|
||||
if (obj.has_member ("reblog") && obj.get_null_member("reblog") != true)
|
||||
reblog = new Status (obj.get_object_member ("reblog"));
|
||||
|
||||
obj.get_array_member ("mentions").foreach_element ((array, i, node) => {
|
||||
var entity = node.get_object ();
|
||||
if (entity != null) {
|
||||
if (mentions == null)
|
||||
mentions = new ArrayList<API.Mention> ();
|
||||
mentions.add (new API.Mention (entity));
|
||||
}
|
||||
});
|
||||
|
||||
obj.get_array_member ("media_attachments").foreach_element ((array, i, node) => {
|
||||
var entity = node.get_object ();
|
||||
if (entity != null) {
|
||||
if (attachments == null)
|
||||
attachments = new ArrayList<API.Attachment> ();
|
||||
attachments.add (new API.Attachment (entity));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Status from (Json.Node node) throws Error {
|
||||
return Entity.from_json (typeof (API.Status), node) as API.Status;
|
||||
}
|
||||
|
||||
public Status.empty () {
|
||||
Object (
|
||||
id: 0,
|
||||
id: "",
|
||||
visibility: settings.default_post_visibility
|
||||
);
|
||||
}
|
||||
|
||||
public Status.from_account (API.Account account) {
|
||||
Object (
|
||||
id: 0,
|
||||
id: "",
|
||||
account: account,
|
||||
created_at: account.created_at
|
||||
);
|
||||
|
@ -121,61 +79,6 @@ public class Tootle.API.Status : GLib.Object, Widgetizable {
|
|||
return w;
|
||||
}
|
||||
|
||||
public Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
builder.set_member_name ("id");
|
||||
builder.add_string_value (id.to_string ());
|
||||
builder.set_member_name ("uri");
|
||||
builder.add_string_value (uri);
|
||||
builder.set_member_name ("url");
|
||||
builder.add_string_value (url);
|
||||
builder.set_member_name ("content");
|
||||
builder.add_string_value (content);
|
||||
builder.set_member_name ("created_at");
|
||||
builder.add_string_value (created_at);
|
||||
builder.set_member_name ("visibility");
|
||||
builder.add_string_value (visibility.to_string ());
|
||||
builder.set_member_name ("sensitive");
|
||||
builder.add_boolean_value (sensitive);
|
||||
builder.set_member_name ("sensitive");
|
||||
builder.add_boolean_value (sensitive);
|
||||
builder.set_member_name ("replies_count");
|
||||
builder.add_int_value (replies_count);
|
||||
builder.set_member_name ("favourites_count");
|
||||
builder.add_int_value (favourites_count);
|
||||
builder.set_member_name ("reblogs_count");
|
||||
builder.add_int_value (reblogs_count);
|
||||
builder.set_member_name ("account");
|
||||
builder.add_value (account.serialize ());
|
||||
|
||||
if (spoiler_text != null) {
|
||||
builder.set_member_name ("spoiler_text");
|
||||
builder.add_string_value (spoiler_text);
|
||||
}
|
||||
if (reblog != null) {
|
||||
builder.set_member_name ("reblog");
|
||||
builder.add_value (reblog.serialize ());
|
||||
}
|
||||
if (attachments != null) {
|
||||
builder.set_member_name ("media_attachments");
|
||||
builder.begin_array ();
|
||||
foreach (API.Attachment attachment in attachments)
|
||||
builder.add_value (attachment.serialize ());
|
||||
builder.end_array ();
|
||||
}
|
||||
if (mentions != null) {
|
||||
builder.set_member_name ("mentions");
|
||||
builder.begin_array ();
|
||||
foreach (API.Mention mention in mentions)
|
||||
builder.add_value (mention.serialize ());
|
||||
builder.end_array ();
|
||||
}
|
||||
|
||||
builder.end_object ();
|
||||
return builder.get_root ();
|
||||
}
|
||||
|
||||
public bool is_owned (){
|
||||
return formal.account.id == accounts.active.id;
|
||||
}
|
||||
|
@ -201,12 +104,10 @@ public class Tootle.API.Status : GLib.Object, Widgetizable {
|
|||
public void action (string action, owned Network.ErrorCallback? err = network.on_error) {
|
||||
new Request.POST (@"/api/v1/statuses/$(formal.id)/$action")
|
||||
.with_account (accounts.active)
|
||||
.then_parse_obj (obj => {
|
||||
var status = new API.Status (obj).formal;
|
||||
formal.reblogged = status.reblogged;
|
||||
formal.favorited = status.favorited;
|
||||
formal.muted = status.muted;
|
||||
formal.pinned = status.pinned;
|
||||
.then ((sess, msg) => {
|
||||
var node = network.parse_node (msg);
|
||||
var upd = API.Status.from (node).formal;
|
||||
patch (upd);
|
||||
})
|
||||
.on_error ((status, reason) => err (status, reason))
|
||||
.exec ();
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
public class Tootle.API.Tag : GLib.Object {
|
||||
public class Tootle.API.Tag : Entity {
|
||||
|
||||
public string name { get; construct set; }
|
||||
public string url { get; construct set; }
|
||||
public string name { get; set; }
|
||||
public string url { get; set; }
|
||||
|
||||
public Tag (Json.Object obj) {
|
||||
Object (
|
||||
name: obj.get_string_member ("name"),
|
||||
url: obj.get_string_member ("url")
|
||||
);
|
||||
}
|
||||
public static Tag from (Json.Node node) throws Error {
|
||||
return Entity.from_json (typeof (API.Tag), node) as API.Tag;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ public class Tootle.Dialogs.Compose : Window {
|
|||
visibility_button.sensitive = false;
|
||||
box.sensitive = false;
|
||||
|
||||
if (status.id > 0) {
|
||||
if (status.id != "") {
|
||||
info ("Removing old status...");
|
||||
status.poof (publish, on_error);
|
||||
}
|
||||
|
@ -152,8 +152,8 @@ public class Tootle.Dialogs.Compose : Window {
|
|||
req.with_param ("in_reply_to_account_id", status.in_reply_to_account_id);
|
||||
|
||||
req.then ((sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
var status = new API.Status (root);
|
||||
var node = network.parse_node (mess);
|
||||
var status = API.Status.from (node);
|
||||
info ("OK: status id is %s", status.id.to_string ());
|
||||
destroy ();
|
||||
})
|
||||
|
|
|
@ -6,7 +6,7 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
|||
public string instance { get; set; }
|
||||
public string client_id { get; set; }
|
||||
public string client_secret { get; set; }
|
||||
public string token { get; set; }
|
||||
public string access_token { get; set; }
|
||||
|
||||
public int64 last_seen_notification { get; set; default = 0; }
|
||||
public bool has_unread_notifications { get; set; default = false; }
|
||||
|
@ -25,27 +25,11 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
|||
}
|
||||
}
|
||||
|
||||
public InstanceAccount (Json.Object obj) {
|
||||
Object (
|
||||
username: obj.get_string_member ("username"),
|
||||
instance: obj.get_string_member ("instance"),
|
||||
client_id: obj.get_string_member ("id"),
|
||||
client_secret: obj.get_string_member ("secret"),
|
||||
token: obj.get_string_member ("access_token"),
|
||||
last_seen_notification: obj.get_int_member ("last_seen_notification"),
|
||||
has_unread_notifications: obj.get_boolean_member ("has_unread_notifications")
|
||||
);
|
||||
|
||||
var cached = obj.get_object_member ("cached_profile");
|
||||
var account = new API.Account (cached);
|
||||
patch (account);
|
||||
|
||||
var notifications = obj.get_array_member ("cached_notifications");
|
||||
notifications.foreach_element ((arr, i, node) => {
|
||||
var notification = new API.Notification (node.get_object ());
|
||||
cached_notifications.add (notification);
|
||||
});
|
||||
public static InstanceAccount from (Json.Node node) throws Error {
|
||||
return Entity.from_json (typeof (InstanceAccount), node) as InstanceAccount;
|
||||
}
|
||||
|
||||
public InstanceAccount () {
|
||||
on_notification.connect (show_notification);
|
||||
}
|
||||
~InstanceAccount () {
|
||||
|
@ -53,25 +37,25 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
|||
}
|
||||
|
||||
public InstanceAccount.empty (string instance){
|
||||
Object (id: 0, instance: instance);
|
||||
Object (
|
||||
id: "",
|
||||
instance: instance
|
||||
);
|
||||
}
|
||||
|
||||
public InstanceAccount.from_account (API.Account account) {
|
||||
Object (id: account.id);
|
||||
Object (
|
||||
id: account.id
|
||||
);
|
||||
patch (account);
|
||||
}
|
||||
|
||||
public InstanceAccount patch (API.Account account) {
|
||||
Utils.merge (this, account);
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool is_current () {
|
||||
return accounts.active.token == token;
|
||||
return accounts.active.access_token == access_token;
|
||||
}
|
||||
|
||||
public string get_stream_url () {
|
||||
return @"$instance/api/v1/streaming/?stream=user&access_token=$token";
|
||||
return @"$instance/api/v1/streaming/?stream=user&access_token=$access_token";
|
||||
}
|
||||
|
||||
public void subscribe () {
|
||||
|
@ -82,45 +66,6 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
|||
streams.unsubscribe (stream, this);
|
||||
}
|
||||
|
||||
public override Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
|
||||
builder.set_member_name ("hash");
|
||||
builder.add_string_value ("test");
|
||||
builder.set_member_name ("username");
|
||||
builder.add_string_value (username);
|
||||
builder.set_member_name ("instance");
|
||||
builder.add_string_value (instance);
|
||||
builder.set_member_name ("id");
|
||||
builder.add_string_value (client_id);
|
||||
builder.set_member_name ("secret");
|
||||
builder.add_string_value (client_secret);
|
||||
builder.set_member_name ("access_token");
|
||||
builder.add_string_value (token);
|
||||
builder.set_member_name ("last_seen_notification");
|
||||
builder.add_int_value (last_seen_notification);
|
||||
builder.set_member_name ("has_unread_notifications");
|
||||
builder.add_boolean_value (has_unread_notifications);
|
||||
|
||||
var cached_profile = base.serialize ();
|
||||
builder.set_member_name ("cached_profile");
|
||||
builder.add_value (cached_profile);
|
||||
|
||||
builder.set_member_name ("cached_notifications");
|
||||
builder.begin_array ();
|
||||
cached_notifications.@foreach (notification => {
|
||||
var node = notification.serialize ();
|
||||
if (node != null)
|
||||
builder.add_value (node);
|
||||
return true;
|
||||
});
|
||||
builder.end_array ();
|
||||
|
||||
builder.end_object ();
|
||||
return builder.get_root ();
|
||||
}
|
||||
|
||||
protected void show_notification (API.Notification obj) {
|
||||
var title = Html.remove_tags (obj.kind.get_desc (obj.account));
|
||||
var notification = new GLib.Notification (title);
|
||||
|
@ -140,21 +85,4 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
|||
}
|
||||
}
|
||||
|
||||
// protected void on_status_added (API.Status status) { //TODO: Watchlist
|
||||
// if (!is_current ())
|
||||
// return;
|
||||
|
||||
// watchlist.users.@foreach (item => {
|
||||
// var acct = status.account.acct;
|
||||
// if (item == acct || item == "@" + acct) {
|
||||
// var obj = new API.Notification (-1);
|
||||
// obj.kind = API.NotificationType.WATCHLIST;
|
||||
// obj.account = status.account;
|
||||
// obj.status = status;
|
||||
// on_notification (obj);
|
||||
// }
|
||||
// return true;
|
||||
// });
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ using Gee;
|
|||
|
||||
public class Tootle.Request : Soup.Message {
|
||||
|
||||
public string url { construct set; get; }
|
||||
public string url { set; get; }
|
||||
private Network.SuccessCallback? cb;
|
||||
private Network.ErrorCallback? error_cb;
|
||||
private HashMap<string, string>? pars;
|
||||
|
@ -81,11 +81,10 @@ public class Tootle.Request : Soup.Message {
|
|||
|
||||
if (needs_token) {
|
||||
if (account == null) {
|
||||
warning (@"No account found for: $method: $url$parameters");
|
||||
warning (@"No account was specified or found for $method: $url$parameters");
|
||||
return this;
|
||||
}
|
||||
|
||||
request_headers.append ("Authorization", @"Bearer $(account.token)");
|
||||
request_headers.append ("Authorization", @"Bearer $(account.access_token)");
|
||||
}
|
||||
|
||||
if (!("://" in url)) {
|
||||
|
@ -95,7 +94,7 @@ public class Tootle.Request : Soup.Message {
|
|||
this.uri = new URI (url + "" + parameters);
|
||||
|
||||
url = uri.to_string (false);
|
||||
info (@"$method: $url");
|
||||
debug (@"$method: $url");
|
||||
|
||||
network.queue (this, (owned) cb, (owned) error_cb);
|
||||
return this;
|
||||
|
|
|
@ -19,9 +19,9 @@ public class Tootle.Accounts : GLib.Object {
|
|||
new Request.GET ("/api/v1/accounts/verify_credentials")
|
||||
.with_account (acc)
|
||||
.then ((sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
var profile = new API.Account (root);
|
||||
acc.patch (profile);
|
||||
var node = network.parse_node (mess);
|
||||
var updated = API.Account.from (node);
|
||||
acc.patch (updated);
|
||||
info ("OK: Token is valid");
|
||||
active = acc;
|
||||
settings.current_account = id;
|
||||
|
@ -89,7 +89,7 @@ public class Tootle.Accounts : GLib.Object {
|
|||
var builder = new Json.Builder ();
|
||||
builder.begin_array ();
|
||||
saved.foreach ((acc) => {
|
||||
var node = acc.serialize ();
|
||||
var node = acc.to_json ();
|
||||
builder.add_value (node);
|
||||
return true;
|
||||
});
|
||||
|
@ -124,8 +124,7 @@ public class Tootle.Accounts : GLib.Object {
|
|||
var array = parser.get_root ().get_array ();
|
||||
|
||||
array.foreach_element ((_arr, _i, node) => {
|
||||
var obj = node.get_object ();
|
||||
var account = new InstanceAccount (obj);
|
||||
var account = InstanceAccount.from (node);
|
||||
if (account != null) {
|
||||
saved.add (account);
|
||||
account.subscribe ();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
public interface Tootle.IStreamListener : GLib.Object {
|
||||
|
||||
public signal void on_status_removed (int64 id);
|
||||
public signal void on_status_removed (string id);
|
||||
public signal void on_status_added (API.Status s);
|
||||
public signal void on_notification (API.Notification n);
|
||||
|
||||
|
|
|
@ -79,10 +79,14 @@ public class Tootle.Network : GLib.Object {
|
|||
app.error (_("Network Error"), message);
|
||||
}
|
||||
|
||||
public Json.Object parse (Soup.Message msg) throws Error {
|
||||
public Json.Node parse_node (Soup.Message msg) throws Error {
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
return parser.get_root ().get_object ();
|
||||
return parser.get_root ();
|
||||
}
|
||||
|
||||
public Json.Object parse (Soup.Message msg) throws Error {
|
||||
return parse_node (msg).get_object ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -111,65 +111,68 @@ public class Tootle.Streams : Object {
|
|||
return s.get_type ().name ();
|
||||
}
|
||||
|
||||
static void decode (Bytes bytes, out string event, out Json.Object root) throws Error {
|
||||
static void decode (Bytes bytes, out Json.Node root, out Json.Object obj, out string event) throws Error {
|
||||
var msg = (string) bytes.get_data ();
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (msg, -1);
|
||||
root = parser.get_root ().get_object ();
|
||||
event = root.get_string_member ("event");
|
||||
root = parser.steal_root ();
|
||||
obj = root.get_object ();
|
||||
event = obj.get_string_member ("event");
|
||||
}
|
||||
|
||||
static Json.Object sanitize (Json.Object root) {
|
||||
var payload = root.get_string_member ("payload");
|
||||
var sanitized = Soup.URI.decode (payload);
|
||||
static Json.Node payload (Json.Object obj) {
|
||||
var payload = obj.get_string_member ("payload");
|
||||
var data = Soup.URI.decode (payload);
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (sanitized, -1);
|
||||
return parser.get_root ().get_object ();
|
||||
parser.load_from_data (data, -1);
|
||||
return parser.steal_root ();
|
||||
}
|
||||
|
||||
static void emit (Bytes bytes, Connection c) throws Error {
|
||||
if (!settings.live_updates)
|
||||
return;
|
||||
|
||||
string e;
|
||||
Json.Object root;
|
||||
decode (bytes, out e, out root);
|
||||
Json.Node root;
|
||||
Json.Object root_obj;
|
||||
string ev;
|
||||
decode (bytes, out root, out root_obj, out ev);
|
||||
|
||||
// c.subscribers.@foreach (s => {
|
||||
// warning ("%s: %s for %s", c.name, e, get_subscriber_name (s));
|
||||
// return false;
|
||||
// });
|
||||
|
||||
switch (e) {
|
||||
switch (ev) {
|
||||
case "update":
|
||||
var obj = new API.Status (sanitize (root));
|
||||
var node = payload (root_obj);
|
||||
var status = Entity.from_json (typeof (API.Status), node) as API.Status;
|
||||
c.subscribers.@foreach (s => {
|
||||
s.on_status_added (obj);
|
||||
s.on_status_added (status);
|
||||
return true;
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
var id = int64.parse (root.get_string_member ("payload"));
|
||||
var id = root_obj.get_string_member ("payload");
|
||||
c.subscribers.@foreach (s => {
|
||||
s.on_status_removed (id);
|
||||
return true;
|
||||
});
|
||||
break;
|
||||
case "notification":
|
||||
var obj = new API.Notification (sanitize (root));
|
||||
var node = payload (root_obj);
|
||||
var notif = Entity.from_json (typeof (API.Notification), node) as API.Notification;
|
||||
c.subscribers.@foreach (s => {
|
||||
s.on_notification (obj);
|
||||
s.on_notification (notif);
|
||||
return true;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
warning (@"Unknown websocket event: \"$e\". Ignoring.");
|
||||
warning (@"Unknown websocket event: \"$ev\". Ignoring.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void force_delete (int64 id) {
|
||||
warning (@"Force removing status id $id");
|
||||
public void force_delete (string id) {
|
||||
connections.get_values ().@foreach (c => {
|
||||
c.subscribers.@foreach (s => {
|
||||
s.on_status_removed (id);
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
public class Tootle.Utils {
|
||||
|
||||
public static void merge (GLib.Object what, GLib.Object with) {
|
||||
var props = with.get_class ().list_properties ();
|
||||
foreach (var prop in props) {
|
||||
var name = prop.get_name ();
|
||||
var defined = what.get_class ().find_property (name) != null;
|
||||
if (defined) {
|
||||
var val = Value (prop.value_type);
|
||||
with.get_property (name, ref val);
|
||||
what.set_property (name, val) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,7 +10,7 @@ public class Tootle.Views.Conversations : Views.Timeline {
|
|||
}
|
||||
|
||||
public override string? get_stream_url () {
|
||||
return @"/api/v1/streaming/?stream=direct&access_token=$(accounts.active.token)";
|
||||
return @"/api/v1/streaming/?stream=direct&access_token=$(account.access_token)";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,24 +4,23 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
|||
|
||||
public API.Status root_status { get; construct set; }
|
||||
protected InstanceAccount? account = null;
|
||||
protected Widgets.Status root_widget;
|
||||
protected Widget root_widget;
|
||||
|
||||
public ExpandedStatus (API.Status status) {
|
||||
Object (root_status: status, state: "content");
|
||||
|
||||
root_widget = append (status);
|
||||
root_widget.avatar.button_press_event.connect (root_widget.on_avatar_clicked);
|
||||
Object (
|
||||
root_status: status,
|
||||
status_message: STATUS_LOADING
|
||||
);
|
||||
connect_account ();
|
||||
}
|
||||
|
||||
public override void on_account_changed (InstanceAccount? acc) {
|
||||
public override void on_account_changed (InstanceAccount? acc) {
|
||||
account = acc;
|
||||
request ();
|
||||
}
|
||||
|
||||
private Widgets.Status prepend (API.Status status, bool to_end = false){
|
||||
var w = new Widgets.Status (status);
|
||||
w.avatar.button_press_event.connect (w.on_avatar_clicked);
|
||||
Widget prepend (Entity entity, bool to_end = false){
|
||||
var w = entity.to_widget () as Widgets.Status;
|
||||
w.revealer.reveal_child = true;
|
||||
|
||||
if (to_end)
|
||||
|
@ -32,8 +31,8 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
|||
check_resize ();
|
||||
return w;
|
||||
}
|
||||
private Widgets.Status append (API.Status status) {
|
||||
return prepend (status, true);
|
||||
Widget append (Entity entity) {
|
||||
return prepend (entity, true);
|
||||
}
|
||||
|
||||
public void request () {
|
||||
|
@ -44,25 +43,23 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
|||
|
||||
var ancestors = root.get_array_member ("ancestors");
|
||||
ancestors.foreach_element ((array, i, node) => {
|
||||
var object = node.get_object ();
|
||||
if (object != null) {
|
||||
var status = new API.Status (object);
|
||||
prepend (status);
|
||||
}
|
||||
var status = Entity.from_json (typeof (API.Status), node);
|
||||
append (status);
|
||||
});
|
||||
|
||||
root_widget = append (root_status);
|
||||
|
||||
var descendants = root.get_array_member ("descendants");
|
||||
descendants.foreach_element ((array, i, node) => {
|
||||
var object = node.get_object ();
|
||||
if (object != null) {
|
||||
var status = new API.Status (object);
|
||||
append (status);
|
||||
}
|
||||
var status = Entity.from_json (typeof (API.Status), node);
|
||||
append (status);
|
||||
});
|
||||
|
||||
on_content_changed ();
|
||||
|
||||
int x,y;
|
||||
translate_coordinates (root_widget, 0, 0, out x, out y);
|
||||
scrolled.vadjustment.value = (double)(y*-1); //TODO: Animate scrolling?
|
||||
scrolled.vadjustment.value = (double)(y*-1);
|
||||
//content_list.select_row (root_widget);
|
||||
})
|
||||
.exec ();
|
||||
|
@ -76,9 +73,9 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
|||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
var statuses = root.get_array_member ("statuses");
|
||||
var object = statuses.get_element (0).get_object ();
|
||||
if (object != null){
|
||||
var status = new API.Status (object);
|
||||
var node = statuses.get_element (0);
|
||||
if (node != null){
|
||||
var status = API.Status.from (node);
|
||||
window.open_view (new Views.ExpandedStatus (status));
|
||||
}
|
||||
else
|
||||
|
|
|
@ -10,7 +10,7 @@ public class Tootle.Views.Federated : Views.Timeline {
|
|||
}
|
||||
|
||||
public override string? get_stream_url () {
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public&access_token=$(account.token)" : null;
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public&access_token=$(account.access_token)" : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ public class Tootle.Views.Hashtag : Views.Timeline {
|
|||
|
||||
public override string? get_stream_url () {
|
||||
var tag = url.substring (4);
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=hashtag&tag=$tag&access_token=$(account.token)" : null;
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=hashtag&tag=$tag&access_token=$(account.access_token)" : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ public class Tootle.Views.Local : Views.Federated {
|
|||
}
|
||||
|
||||
public override string? get_stream_url () {
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public:local&access_token=$(account.token)" : null;
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public:local&access_token=$(account.access_token)" : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
info ("New account view was requested");
|
||||
}
|
||||
|
||||
private bool reset () {
|
||||
bool reset () {
|
||||
info ("State invalidated");
|
||||
instance = code = client_id = client_secret = access_token = null;
|
||||
instance_entry.sensitive = true;
|
||||
|
@ -50,11 +50,11 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
return true;
|
||||
}
|
||||
|
||||
private void oopsie (string message) {
|
||||
void oopsie (string message) {
|
||||
warning (message);
|
||||
}
|
||||
|
||||
private void on_next_clicked () {
|
||||
void on_next_clicked () {
|
||||
try {
|
||||
step ();
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
}
|
||||
}
|
||||
|
||||
private void step () throws Error {
|
||||
void step () throws Error {
|
||||
if (instance == null)
|
||||
setup_instance ();
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
request_token ();
|
||||
}
|
||||
|
||||
private void setup_instance () throws Error {
|
||||
void setup_instance () throws Error {
|
||||
info ("Checking instance URL");
|
||||
|
||||
var str = instance_entry.text
|
||||
|
@ -91,7 +91,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
throw new Oopsie.USER (_("Instance URL is invalid"));
|
||||
}
|
||||
|
||||
private void register_client () throws Error {
|
||||
void register_client () throws Error {
|
||||
info ("Registering client");
|
||||
instance_entry.sensitive = false;
|
||||
|
||||
|
@ -119,7 +119,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
.exec ();
|
||||
}
|
||||
|
||||
private void open_confirmation_page () {
|
||||
void open_confirmation_page () {
|
||||
info ("Opening permission request page");
|
||||
|
||||
var pars = @"scope=$scopes&response_type=code&redirect_uri=$redirect_uri&client_id=$client_id";
|
||||
|
@ -127,7 +127,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
Desktop.open_uri (url);
|
||||
}
|
||||
|
||||
private void request_token () throws Error {
|
||||
void request_token () throws Error {
|
||||
if (code.char_count () <= 10)
|
||||
throw new Oopsie.USER (_("Please paste a valid authorization code"));
|
||||
|
||||
|
@ -142,8 +142,8 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
access_token = root.get_string_member ("access_token");
|
||||
account.token = access_token;
|
||||
account.id = 0;
|
||||
account.access_token = access_token;
|
||||
account.id = "";
|
||||
info ("OK: received access token");
|
||||
request_profile ();
|
||||
})
|
||||
|
@ -151,13 +151,13 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
.exec ();
|
||||
}
|
||||
|
||||
private void request_profile () throws Error {
|
||||
void request_profile () throws Error {
|
||||
info ("Testing received access token");
|
||||
new Request.GET ("/api/v1/accounts/verify_credentials")
|
||||
.with_account (account)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
var account = new API.Account (root);
|
||||
var node = network.parse_node (msg);
|
||||
var account = API.Account.from (node);
|
||||
info ("OK: received user profile");
|
||||
save (account);
|
||||
})
|
||||
|
@ -168,13 +168,13 @@ public class Tootle.Views.NewAccount : Views.Base {
|
|||
.exec ();
|
||||
}
|
||||
|
||||
private void save (API.Account profile) {
|
||||
void save (API.Account profile) {
|
||||
info ("Account validated. Saving...");
|
||||
account.patch (profile);
|
||||
account.instance = instance;
|
||||
account.client_id = client_id;
|
||||
account.client_secret = client_secret;
|
||||
account.token = access_token;
|
||||
account.access_token = access_token;
|
||||
accounts.add (account);
|
||||
|
||||
destroy ();
|
||||
|
|
|
@ -17,7 +17,7 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr
|
|||
}
|
||||
|
||||
public override string? get_stream_url () {
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=user&access_token=$(account.token)" : null;
|
||||
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=user&access_token=$(account.access_token)" : null;
|
||||
}
|
||||
|
||||
public override void on_shown () {
|
||||
|
@ -34,25 +34,14 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr
|
|||
var nw = w as Widgets.Notification;
|
||||
var notification = nw.notification;
|
||||
|
||||
if (notification.id > last_id)
|
||||
last_id = notification.id;
|
||||
if (int64.parse (notification.id) > last_id)
|
||||
last_id = int64.parse (notification.id);
|
||||
|
||||
needs_attention = has_unread () && !current;
|
||||
if (needs_attention)
|
||||
accounts.save ();
|
||||
}
|
||||
|
||||
public override GLib.Object to_entity (Json.Node node) throws Oopsie {
|
||||
if (node == null)
|
||||
throw new Oopsie.PARSING ("Received null Json.Node");
|
||||
|
||||
var obj = node.get_object ();
|
||||
if (obj == null)
|
||||
throw new Oopsie.PARSING ("Received Json.Node is not a Json.Object!");
|
||||
|
||||
return new API.Notification (obj);
|
||||
}
|
||||
|
||||
public override void on_account_changed (InstanceAccount? acc) {
|
||||
base.on_account_changed (acc);
|
||||
if (account == null) {
|
||||
|
|
|
@ -57,7 +57,7 @@ public class Tootle.Views.Profile : Views.Timeline {
|
|||
relationship = builder.get_object ("relationship") as Label;
|
||||
|
||||
posts_label = builder.get_object ("posts_label") as Label;
|
||||
profile.bind_property ("posts_count", posts_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
profile.bind_property ("statuses_count", posts_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
var val = (int64) src;
|
||||
target.set_string (_("%s Posts").printf (@"<b>$val</b>"));
|
||||
return true;
|
||||
|
@ -151,28 +151,22 @@ public class Tootle.Views.Profile : Views.Timeline {
|
|||
}
|
||||
|
||||
public override Request append_params (Request req) {
|
||||
req.with_param ("exclude_replies", (!filter_replies.active).to_string ());
|
||||
req.with_param ("only_media", filter_media.active.to_string ());
|
||||
return base.append_params (req);
|
||||
if (page_next == null) {
|
||||
req.with_param ("exclude_replies", (!filter_replies.active).to_string ());
|
||||
req.with_param ("only_media", filter_media.active.to_string ());
|
||||
return base.append_params (req);
|
||||
}
|
||||
else
|
||||
return req;
|
||||
}
|
||||
|
||||
public override GLib.Object to_entity (Json.Node node) {
|
||||
var obj = node.get_object ();
|
||||
if (posts_tab.active)
|
||||
return new API.Status (obj);
|
||||
else {
|
||||
var account = new API.Account (obj);
|
||||
return new API.Status.from_account (account);
|
||||
}
|
||||
}
|
||||
|
||||
public static void open_from_id (int64 id){
|
||||
var url = "%s/api/v1/accounts/%lld".printf (accounts.active.instance, id);
|
||||
public static void open_from_id (string id){
|
||||
var url = @"$(accounts.active.instance)/api/v1/accounts/$id";
|
||||
var msg = new Soup.Message ("GET", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
network.queue (msg, (sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
var acc = new API.Account (root);
|
||||
var node = network.parse_node (mess);
|
||||
var acc = API.Account.from (node);
|
||||
window.open_view (new Views.Profile (acc));
|
||||
}, (status, reason) => {
|
||||
network.on_error (status, reason);
|
||||
|
|
|
@ -92,8 +92,7 @@ public class Tootle.Views.Search : Views.Base {
|
|||
if (accounts.get_length () > 0) {
|
||||
append_header (_("Accounts"));
|
||||
accounts.foreach_element ((array, i, node) => {
|
||||
var obj = node.get_object ();
|
||||
var acc = new API.Account (obj);
|
||||
var acc = API.Account.from (node);
|
||||
append_account (acc);
|
||||
});
|
||||
}
|
||||
|
@ -101,8 +100,7 @@ public class Tootle.Views.Search : Views.Base {
|
|||
if (statuses.get_length () > 0) {
|
||||
append_header (_("Statuses"));
|
||||
statuses.foreach_element ((array, i, node) => {
|
||||
var obj = node.get_object ();
|
||||
var status = new API.Status (obj);
|
||||
var status = API.Status.from (node);
|
||||
append_status (status);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -30,17 +30,6 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
|
|||
return status.is_owned ();
|
||||
}
|
||||
|
||||
public virtual GLib.Object to_entity (Json.Node node) throws Oopsie {
|
||||
if (node == null)
|
||||
throw new Oopsie.PARSING ("Received null Json.Node");
|
||||
|
||||
var obj = node.get_object ();
|
||||
if (obj == null)
|
||||
throw new Oopsie.PARSING ("Received Json.Node is not a Json.Object!");
|
||||
|
||||
return new API.Status (obj);
|
||||
}
|
||||
|
||||
public void prepend (Widget? w) {
|
||||
append (w, true);
|
||||
}
|
||||
|
@ -90,30 +79,34 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
|
|||
public virtual string get_req_url () {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public virtual Request append_params (Request req) {
|
||||
return req.with_param ("limit", @"$(settings.timeline_page_size)");
|
||||
if (page_next == null)
|
||||
return req.with_param ("limit", @"$(settings.timeline_page_size)");
|
||||
else
|
||||
return req;
|
||||
}
|
||||
|
||||
public virtual bool request () {
|
||||
append_params (new Request.GET (get_req_url ()))
|
||||
var req = append_params (new Request.GET (get_req_url ()))
|
||||
.with_account (account)
|
||||
.then_parse_array ((node, msg) => {
|
||||
try {
|
||||
var e = to_entity (node);
|
||||
var e = Entity.from_json (accepts, node);
|
||||
var w = e as Widgetizable;
|
||||
append (w.to_widget ());
|
||||
}
|
||||
catch (Error e) {
|
||||
warning (@"Timeline item parse error: $(e.message)");
|
||||
}
|
||||
get_pages (msg.response_headers.get_one ("Link"));
|
||||
})
|
||||
.on_error (on_error)
|
||||
.exec ();
|
||||
.on_error (on_error);
|
||||
req.finished.connect (() => {
|
||||
get_pages (req.response_headers.get_one ("Link"));
|
||||
});
|
||||
req.exec ();
|
||||
return GLib.Source.REMOVE;
|
||||
}
|
||||
|
||||
|
@ -152,7 +145,7 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
|
|||
prepend (status.to_widget ());
|
||||
}
|
||||
|
||||
protected virtual void remove_status (int64 id) {
|
||||
protected virtual void remove_status (string id) {
|
||||
if (settings.live_updates) {
|
||||
content.get_children ().@foreach (w => {
|
||||
var sw = w as Widgets.Status;
|
||||
|
|
|
@ -4,7 +4,7 @@ using Gdk;
|
|||
public class Tootle.Widgets.Attachment.Item : EventBox {
|
||||
|
||||
public API.Attachment attachment { get; construct set; }
|
||||
|
||||
|
||||
private Cache.Reference? cached;
|
||||
|
||||
public Item (API.Attachment obj) {
|
||||
|
@ -13,15 +13,15 @@ public class Tootle.Widgets.Attachment.Item : EventBox {
|
|||
~Item () {
|
||||
cache.unload (cached);
|
||||
}
|
||||
|
||||
|
||||
construct {
|
||||
get_style_context ().add_class ("attachment");
|
||||
width_request = height_request = 128;
|
||||
hexpand = true;
|
||||
tooltip_text = attachment.description ?? _("No description is available");
|
||||
|
||||
|
||||
button_press_event.connect (on_clicked);
|
||||
|
||||
|
||||
show ();
|
||||
on_request ();
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public class Tootle.Widgets.Attachment.Item : EventBox {
|
|||
var h = get_allocated_height ();
|
||||
var style = get_style_context ();
|
||||
var border_radius = style.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS, style.get_state ()).get_int ();
|
||||
|
||||
|
||||
if (cached != null) {
|
||||
if (cached.loading) {
|
||||
Drawing.center (ctx, w, h, 32, 32);
|
||||
|
@ -74,7 +74,7 @@ public class Tootle.Widgets.Attachment.Item : EventBox {
|
|||
ctx.fill ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Gdk.EVENT_STOP;
|
||||
}
|
||||
|
||||
|
@ -85,15 +85,15 @@ public class Tootle.Widgets.Attachment.Item : EventBox {
|
|||
}
|
||||
else if (ev.button == 3) {
|
||||
var menu = new Gtk.Menu ();
|
||||
|
||||
|
||||
var item_open = new Gtk.MenuItem.with_label (_("Open"));
|
||||
item_open.activate.connect (open);
|
||||
menu.add (item_open);
|
||||
|
||||
|
||||
var item_download = new Gtk.MenuItem.with_label (_("Download"));
|
||||
item_download.activate.connect (download);
|
||||
menu.add (item_download);
|
||||
|
||||
|
||||
menu.show_all ();
|
||||
menu.attach_widget = this;
|
||||
menu.popup_at_pointer ();
|
||||
|
|
|
@ -69,18 +69,18 @@ public class Tootle.Widgets.RichLabel : Label {
|
|||
var hashtags = root.get_array_member ("hashtags");
|
||||
|
||||
if (accounts.get_length () > 0) {
|
||||
var item = accounts.get_object_element (0);
|
||||
var obj = new API.Account (item);
|
||||
var node = accounts.get_element (0);
|
||||
var obj = API.Account.from (node);
|
||||
window.open_view (new Views.Profile (obj));
|
||||
}
|
||||
else if (statuses.get_length () > 0) {
|
||||
var item = accounts.get_object_element (0);
|
||||
var obj = new API.Status (item);
|
||||
var node = accounts.get_element (0);
|
||||
var obj = API.Status.from (node);
|
||||
window.open_view (new Views.ExpandedStatus (obj));
|
||||
}
|
||||
else if (hashtags.get_length () > 0) {
|
||||
var item = accounts.get_object_element (0);
|
||||
var obj = new API.Tag (item);
|
||||
var node = accounts.get_element (0);
|
||||
var obj = API.Tag.from (node);
|
||||
window.open_view (new Views.Hashtag (obj.name));
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -90,9 +90,9 @@ public class Tootle.Widgets.Status : EventBox {
|
|||
kind = API.NotificationType.REBLOG_REMOTE_USER;
|
||||
}
|
||||
|
||||
status.formal.bind_property ("favorited", favorite_button, "active", BindingFlags.SYNC_CREATE);
|
||||
status.formal.bind_property ("favourited", favorite_button, "active", BindingFlags.SYNC_CREATE);
|
||||
favorite_button.clicked.connect (() => {
|
||||
status.action (status.formal.favorited ? "unfavourite" : "favourite");
|
||||
status.action (status.formal.favourited ? "unfavourite" : "favourite");
|
||||
});
|
||||
|
||||
status.formal.bind_property ("reblogged", reblog_button, "active", BindingFlags.SYNC_CREATE);
|
||||
|
@ -118,7 +118,7 @@ public class Tootle.Widgets.Status : EventBox {
|
|||
reblog_button.tooltip_text = _("This post can't be boosted");
|
||||
}
|
||||
|
||||
if (status.id <= 0) {
|
||||
if (status.id == "") {
|
||||
actions.destroy ();
|
||||
date_label.destroy ();
|
||||
content.single_line_mode = true;
|
||||
|
@ -130,7 +130,7 @@ public class Tootle.Widgets.Status : EventBox {
|
|||
button_press_event.connect (open);
|
||||
}
|
||||
|
||||
if (!attachments.populate (status.formal.attachments) || status.id <= 0) {
|
||||
if (!attachments.populate (status.formal.media_attachments) || status.id == "") {
|
||||
attachments.destroy ();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue