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')
|
libhandy_dep = dependency('libhandy-1', version: '>= 0.80.0')
|
||||||
|
|
||||||
if not libhandy_dep.found()
|
if not libhandy_dep.found()
|
||||||
libhandy = subproject(
|
libhandy = subproject(
|
||||||
'libhandy',
|
'libhandy',
|
||||||
|
@ -39,7 +38,6 @@ executable(
|
||||||
'src/Desktop.vala',
|
'src/Desktop.vala',
|
||||||
'src/Drawing.vala',
|
'src/Drawing.vala',
|
||||||
'src/Html.vala',
|
'src/Html.vala',
|
||||||
'src/Utils.vala',
|
|
||||||
'src/Request.vala',
|
'src/Request.vala',
|
||||||
'src/InstanceAccount.vala',
|
'src/InstanceAccount.vala',
|
||||||
'src/Services/Streams.vala',
|
'src/Services/Streams.vala',
|
||||||
|
@ -59,6 +57,7 @@ executable(
|
||||||
'src/API/NotificationType.vala',
|
'src/API/NotificationType.vala',
|
||||||
'src/API/Attachment.vala',
|
'src/API/Attachment.vala',
|
||||||
'src/API/Conversation.vala',
|
'src/API/Conversation.vala',
|
||||||
|
'src/API/Entity.vala',
|
||||||
'src/Widgets/Widgetizable.vala',
|
'src/Widgets/Widgetizable.vala',
|
||||||
'src/Widgets/Avatar.vala',
|
'src/Widgets/Avatar.vala',
|
||||||
'src/Widgets/AccountsButton.vala',
|
'src/Widgets/AccountsButton.vala',
|
||||||
|
@ -90,8 +89,8 @@ executable(
|
||||||
dependency('glib-2.0', version: '>=2.30.0'),
|
dependency('glib-2.0', version: '>=2.30.0'),
|
||||||
dependency('gee-0.8', version: '>=0.8.5'),
|
dependency('gee-0.8', version: '>=0.8.5'),
|
||||||
dependency('granite', version: '>=5.2.0'),
|
dependency('granite', version: '>=5.2.0'),
|
||||||
dependency('json-glib-1.0'),
|
|
||||||
dependency('libsoup-2.4'),
|
dependency('libsoup-2.4'),
|
||||||
|
dependency('json-glib-1.0', version: '>=1.4.4'),
|
||||||
libhandy_dep,
|
libhandy_dep,
|
||||||
],
|
],
|
||||||
install: true,
|
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 username { get; set; }
|
||||||
public string acct { get; set; }
|
public string acct { get; set; }
|
||||||
public string? _display_name = null;
|
public string? _display_name = null;
|
||||||
|
@ -19,69 +19,12 @@ public class Tootle.API.Account : GLib.Object {
|
||||||
public string created_at { get; set; }
|
public string created_at { get; set; }
|
||||||
public int64 followers_count { get; set; }
|
public int64 followers_count { get; set; }
|
||||||
public int64 following_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 Relationship? rs { get; set; default = null; }
|
||||||
|
|
||||||
public Account (Json.Object obj) {
|
public static Account from (Json.Node node) throws Error {
|
||||||
Object (
|
return Entity.from_json (typeof (API.Account), node) as API.Account;
|
||||||
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 bool is_self () {
|
public bool is_self () {
|
||||||
return id == accounts.active.id;
|
return id == accounts.active.id;
|
||||||
|
@ -92,7 +35,7 @@ public class Tootle.API.Account : GLib.Object {
|
||||||
.with_account (accounts.active)
|
.with_account (accounts.active)
|
||||||
.with_param ("id", id.to_string ())
|
.with_param ("id", id.to_string ())
|
||||||
.then_parse_array (node => {
|
.then_parse_array (node => {
|
||||||
rs = new Relationship (node.get_object ());
|
rs = API.Relationship.from (node);
|
||||||
})
|
})
|
||||||
.on_error (network.on_error)
|
.on_error (network.on_error)
|
||||||
.exec ();
|
.exec ();
|
||||||
|
@ -103,8 +46,8 @@ public class Tootle.API.Account : GLib.Object {
|
||||||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||||
.with_account (accounts.active)
|
.with_account (accounts.active)
|
||||||
.then ((sess, msg) => {
|
.then ((sess, msg) => {
|
||||||
var root = network.parse (msg);
|
var node = network.parse_node (msg);
|
||||||
rs = new Relationship (root);
|
rs = API.Relationship.from (node);
|
||||||
})
|
})
|
||||||
.on_error (network.on_error)
|
.on_error (network.on_error)
|
||||||
.exec ();
|
.exec ();
|
||||||
|
@ -115,8 +58,8 @@ public class Tootle.API.Account : GLib.Object {
|
||||||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||||
.with_account (accounts.active)
|
.with_account (accounts.active)
|
||||||
.then ((sess, msg) => {
|
.then ((sess, msg) => {
|
||||||
var root = network.parse (msg);
|
var node = network.parse_node (msg);
|
||||||
rs = new Relationship (root);
|
rs = API.Relationship.from (node);
|
||||||
})
|
})
|
||||||
.on_error (network.on_error)
|
.on_error (network.on_error)
|
||||||
.exec ();
|
.exec ();
|
||||||
|
@ -127,8 +70,8 @@ public class Tootle.API.Account : GLib.Object {
|
||||||
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||||
.with_account (accounts.active)
|
.with_account (accounts.active)
|
||||||
.then ((sess, msg) => {
|
.then ((sess, msg) => {
|
||||||
var root = network.parse (msg);
|
var node = network.parse_node (msg);
|
||||||
rs = new Relationship (root);
|
rs = API.Relationship.from (node);
|
||||||
})
|
})
|
||||||
.on_error (network.on_error)
|
.on_error (network.on_error)
|
||||||
.exec ();
|
.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 kind { get; set; }
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public string? description { get; set; default = null; }
|
public string? description { get; set; }
|
||||||
|
public string? _preview_url { get; set; }
|
||||||
public string? _preview_url = null;
|
|
||||||
public string preview_url {
|
public string preview_url {
|
||||||
set { this._preview_url = value; }
|
set { this._preview_url = value; }
|
||||||
get { return (_preview_url == null || _preview_url == "") ? url : _preview_url; }
|
get { return (this._preview_url == null || this._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 ();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 string id { get; construct set; }
|
||||||
public bool unread { get; set; default = false; }
|
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 username { get; construct set; }
|
||||||
public string acct { get; construct set; }
|
public string acct { get; construct set; }
|
||||||
public string url { get; construct set; }
|
public string url { get; construct set; }
|
||||||
|
|
||||||
public Mention (Json.Object obj) {
|
public Mention.from_account (API.Account account) {
|
||||||
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) {
|
|
||||||
Object (
|
Object (
|
||||||
id: account.id,
|
id: account.id,
|
||||||
username: account.username,
|
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 string id { get; set; }
|
||||||
public Account account { get; construct set; }
|
public API.Account account { get; set; }
|
||||||
|
public API.NotificationType kind { get; set; }
|
||||||
public NotificationType kind { get; set; }
|
|
||||||
public string created_at { get; set; }
|
public string created_at { get; set; }
|
||||||
public Status? status { get; set; default = null; }
|
public API.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 override Gtk.Widget to_widget () {
|
public override Gtk.Widget to_widget () {
|
||||||
return new Widgets.Notification (this);
|
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 () {
|
public Soup.Message? dismiss () {
|
||||||
if (kind == NotificationType.WATCHLIST) {
|
if (kind == NotificationType.WATCHLIST) {
|
||||||
if (accounts.active.cached_notifications.remove (this))
|
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")
|
var req = new Request.POST ("/api/v1/notifications/dismiss")
|
||||||
.with_account (accounts.active)
|
.with_account (accounts.active)
|
||||||
.with_param ("id", id.to_string ())
|
.with_param ("id", id)
|
||||||
.exec ();
|
.exec ();
|
||||||
return req;
|
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 following { get; set; default = false; }
|
||||||
public bool followed_by { get; set; default = false; }
|
public bool followed_by { get; set; default = false; }
|
||||||
public bool muting { 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 blocking { get; set; default = false; }
|
||||||
public bool domain_blocking { get; set; default = false; }
|
public bool domain_blocking { get; set; default = false; }
|
||||||
|
|
||||||
public Relationship (Json.Object obj) {
|
public static Relationship from (Json.Node node) throws Error {
|
||||||
Object (
|
return Entity.from_json (typeof (API.Relationship), node) as API.Relationship;
|
||||||
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")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
using Gee;
|
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 string id { get; set; }
|
||||||
public API.Account account { get; construct set; }
|
public API.Account account { get; set; }
|
||||||
public string uri { get; set; }
|
public string uri { get; set; }
|
||||||
public string? url { get; set; default = null; }
|
|
||||||
public string? spoiler_text { 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_id { get; set; default = null; }
|
||||||
public string? in_reply_to_account_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 int64 favourites_count { get; set; default = 0; }
|
||||||
public string created_at { get; set; default = "0"; }
|
public string created_at { get; set; default = "0"; }
|
||||||
public bool reblogged { get; set; default = false; }
|
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 sensitive { get; set; default = false; }
|
||||||
public bool muted { get; set; default = false; }
|
public bool muted { get; set; default = false; }
|
||||||
public bool pinned { 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 API.Status? reblog { get; set; default = null; }
|
||||||
public ArrayList<API.Mention>? mentions { 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 {
|
public Status formal {
|
||||||
get { return reblog ?? this; }
|
get { return reblog ?? this; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool has_spoiler {
|
public bool has_spoiler {
|
||||||
get {
|
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 () {
|
public Status.empty () {
|
||||||
Object (
|
Object (
|
||||||
id: 0,
|
id: "",
|
||||||
visibility: settings.default_post_visibility
|
visibility: settings.default_post_visibility
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status.from_account (API.Account account) {
|
public Status.from_account (API.Account account) {
|
||||||
Object (
|
Object (
|
||||||
id: 0,
|
id: "",
|
||||||
account: account,
|
account: account,
|
||||||
created_at: account.created_at
|
created_at: account.created_at
|
||||||
);
|
);
|
||||||
|
@ -121,61 +79,6 @@ public class Tootle.API.Status : GLib.Object, Widgetizable {
|
||||||
return w;
|
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 (){
|
public bool is_owned (){
|
||||||
return formal.account.id == accounts.active.id;
|
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) {
|
public void action (string action, owned Network.ErrorCallback? err = network.on_error) {
|
||||||
new Request.POST (@"/api/v1/statuses/$(formal.id)/$action")
|
new Request.POST (@"/api/v1/statuses/$(formal.id)/$action")
|
||||||
.with_account (accounts.active)
|
.with_account (accounts.active)
|
||||||
.then_parse_obj (obj => {
|
.then ((sess, msg) => {
|
||||||
var status = new API.Status (obj).formal;
|
var node = network.parse_node (msg);
|
||||||
formal.reblogged = status.reblogged;
|
var upd = API.Status.from (node).formal;
|
||||||
formal.favorited = status.favorited;
|
patch (upd);
|
||||||
formal.muted = status.muted;
|
|
||||||
formal.pinned = status.pinned;
|
|
||||||
})
|
})
|
||||||
.on_error ((status, reason) => err (status, reason))
|
.on_error ((status, reason) => err (status, reason))
|
||||||
.exec ();
|
.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 name { get; set; }
|
||||||
public string url { get; construct set; }
|
public string url { get; set; }
|
||||||
|
|
||||||
public Tag (Json.Object obj) {
|
public static Tag from (Json.Node node) throws Error {
|
||||||
Object (
|
return Entity.from_json (typeof (API.Tag), node) as API.Tag;
|
||||||
name: obj.get_string_member ("name"),
|
}
|
||||||
url: obj.get_string_member ("url")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ public class Tootle.Dialogs.Compose : Window {
|
||||||
visibility_button.sensitive = false;
|
visibility_button.sensitive = false;
|
||||||
box.sensitive = false;
|
box.sensitive = false;
|
||||||
|
|
||||||
if (status.id > 0) {
|
if (status.id != "") {
|
||||||
info ("Removing old status...");
|
info ("Removing old status...");
|
||||||
status.poof (publish, on_error);
|
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.with_param ("in_reply_to_account_id", status.in_reply_to_account_id);
|
||||||
|
|
||||||
req.then ((sess, mess) => {
|
req.then ((sess, mess) => {
|
||||||
var root = network.parse (mess);
|
var node = network.parse_node (mess);
|
||||||
var status = new API.Status (root);
|
var status = API.Status.from (node);
|
||||||
info ("OK: status id is %s", status.id.to_string ());
|
info ("OK: status id is %s", status.id.to_string ());
|
||||||
destroy ();
|
destroy ();
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,7 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
||||||
public string instance { get; set; }
|
public string instance { get; set; }
|
||||||
public string client_id { get; set; }
|
public string client_id { get; set; }
|
||||||
public string client_secret { 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 int64 last_seen_notification { get; set; default = 0; }
|
||||||
public bool has_unread_notifications { get; set; default = false; }
|
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) {
|
public static InstanceAccount from (Json.Node node) throws Error {
|
||||||
Object (
|
return Entity.from_json (typeof (InstanceAccount), node) as InstanceAccount;
|
||||||
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 InstanceAccount () {
|
||||||
on_notification.connect (show_notification);
|
on_notification.connect (show_notification);
|
||||||
}
|
}
|
||||||
~InstanceAccount () {
|
~InstanceAccount () {
|
||||||
|
@ -53,25 +37,25 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstanceAccount.empty (string instance){
|
public InstanceAccount.empty (string instance){
|
||||||
Object (id: 0, instance: instance);
|
Object (
|
||||||
|
id: "",
|
||||||
|
instance: instance
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstanceAccount.from_account (API.Account account) {
|
public InstanceAccount.from_account (API.Account account) {
|
||||||
Object (id: account.id);
|
Object (
|
||||||
|
id: account.id
|
||||||
|
);
|
||||||
patch (account);
|
patch (account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstanceAccount patch (API.Account account) {
|
|
||||||
Utils.merge (this, account);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool is_current () {
|
public bool is_current () {
|
||||||
return accounts.active.token == token;
|
return accounts.active.access_token == access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string get_stream_url () {
|
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 () {
|
public void subscribe () {
|
||||||
|
@ -82,45 +66,6 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
||||||
streams.unsubscribe (stream, this);
|
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) {
|
protected void show_notification (API.Notification obj) {
|
||||||
var title = Html.remove_tags (obj.kind.get_desc (obj.account));
|
var title = Html.remove_tags (obj.kind.get_desc (obj.account));
|
||||||
var notification = new GLib.Notification (title);
|
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 class Tootle.Request : Soup.Message {
|
||||||
|
|
||||||
public string url { construct set; get; }
|
public string url { set; get; }
|
||||||
private Network.SuccessCallback? cb;
|
private Network.SuccessCallback? cb;
|
||||||
private Network.ErrorCallback? error_cb;
|
private Network.ErrorCallback? error_cb;
|
||||||
private HashMap<string, string>? pars;
|
private HashMap<string, string>? pars;
|
||||||
|
@ -81,11 +81,10 @@ public class Tootle.Request : Soup.Message {
|
||||||
|
|
||||||
if (needs_token) {
|
if (needs_token) {
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
warning (@"No account found for: $method: $url$parameters");
|
warning (@"No account was specified or found for $method: $url$parameters");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
request_headers.append ("Authorization", @"Bearer $(account.access_token)");
|
||||||
request_headers.append ("Authorization", @"Bearer $(account.token)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!("://" in url)) {
|
if (!("://" in url)) {
|
||||||
|
@ -95,7 +94,7 @@ public class Tootle.Request : Soup.Message {
|
||||||
this.uri = new URI (url + "" + parameters);
|
this.uri = new URI (url + "" + parameters);
|
||||||
|
|
||||||
url = uri.to_string (false);
|
url = uri.to_string (false);
|
||||||
info (@"$method: $url");
|
debug (@"$method: $url");
|
||||||
|
|
||||||
network.queue (this, (owned) cb, (owned) error_cb);
|
network.queue (this, (owned) cb, (owned) error_cb);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -19,9 +19,9 @@ public class Tootle.Accounts : GLib.Object {
|
||||||
new Request.GET ("/api/v1/accounts/verify_credentials")
|
new Request.GET ("/api/v1/accounts/verify_credentials")
|
||||||
.with_account (acc)
|
.with_account (acc)
|
||||||
.then ((sess, mess) => {
|
.then ((sess, mess) => {
|
||||||
var root = network.parse (mess);
|
var node = network.parse_node (mess);
|
||||||
var profile = new API.Account (root);
|
var updated = API.Account.from (node);
|
||||||
acc.patch (profile);
|
acc.patch (updated);
|
||||||
info ("OK: Token is valid");
|
info ("OK: Token is valid");
|
||||||
active = acc;
|
active = acc;
|
||||||
settings.current_account = id;
|
settings.current_account = id;
|
||||||
|
@ -89,7 +89,7 @@ public class Tootle.Accounts : GLib.Object {
|
||||||
var builder = new Json.Builder ();
|
var builder = new Json.Builder ();
|
||||||
builder.begin_array ();
|
builder.begin_array ();
|
||||||
saved.foreach ((acc) => {
|
saved.foreach ((acc) => {
|
||||||
var node = acc.serialize ();
|
var node = acc.to_json ();
|
||||||
builder.add_value (node);
|
builder.add_value (node);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -124,8 +124,7 @@ public class Tootle.Accounts : GLib.Object {
|
||||||
var array = parser.get_root ().get_array ();
|
var array = parser.get_root ().get_array ();
|
||||||
|
|
||||||
array.foreach_element ((_arr, _i, node) => {
|
array.foreach_element ((_arr, _i, node) => {
|
||||||
var obj = node.get_object ();
|
var account = InstanceAccount.from (node);
|
||||||
var account = new InstanceAccount (obj);
|
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
saved.add (account);
|
saved.add (account);
|
||||||
account.subscribe ();
|
account.subscribe ();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
public interface Tootle.IStreamListener : GLib.Object {
|
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_status_added (API.Status s);
|
||||||
public signal void on_notification (API.Notification n);
|
public signal void on_notification (API.Notification n);
|
||||||
|
|
||||||
|
|
|
@ -79,10 +79,14 @@ public class Tootle.Network : GLib.Object {
|
||||||
app.error (_("Network Error"), message);
|
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 ();
|
var parser = new Json.Parser ();
|
||||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
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 ();
|
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 msg = (string) bytes.get_data ();
|
||||||
var parser = new Json.Parser ();
|
var parser = new Json.Parser ();
|
||||||
parser.load_from_data (msg, -1);
|
parser.load_from_data (msg, -1);
|
||||||
root = parser.get_root ().get_object ();
|
root = parser.steal_root ();
|
||||||
event = root.get_string_member ("event");
|
obj = root.get_object ();
|
||||||
|
event = obj.get_string_member ("event");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Json.Object sanitize (Json.Object root) {
|
static Json.Node payload (Json.Object obj) {
|
||||||
var payload = root.get_string_member ("payload");
|
var payload = obj.get_string_member ("payload");
|
||||||
var sanitized = Soup.URI.decode (payload);
|
var data = Soup.URI.decode (payload);
|
||||||
var parser = new Json.Parser ();
|
var parser = new Json.Parser ();
|
||||||
parser.load_from_data (sanitized, -1);
|
parser.load_from_data (data, -1);
|
||||||
return parser.get_root ().get_object ();
|
return parser.steal_root ();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void emit (Bytes bytes, Connection c) throws Error {
|
static void emit (Bytes bytes, Connection c) throws Error {
|
||||||
if (!settings.live_updates)
|
if (!settings.live_updates)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string e;
|
Json.Node root;
|
||||||
Json.Object root;
|
Json.Object root_obj;
|
||||||
decode (bytes, out e, out root);
|
string ev;
|
||||||
|
decode (bytes, out root, out root_obj, out ev);
|
||||||
|
|
||||||
// c.subscribers.@foreach (s => {
|
// c.subscribers.@foreach (s => {
|
||||||
// warning ("%s: %s for %s", c.name, e, get_subscriber_name (s));
|
// warning ("%s: %s for %s", c.name, e, get_subscriber_name (s));
|
||||||
// return false;
|
// return false;
|
||||||
// });
|
// });
|
||||||
|
|
||||||
switch (e) {
|
switch (ev) {
|
||||||
case "update":
|
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 => {
|
c.subscribers.@foreach (s => {
|
||||||
s.on_status_added (obj);
|
s.on_status_added (status);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
var id = int64.parse (root.get_string_member ("payload"));
|
var id = root_obj.get_string_member ("payload");
|
||||||
c.subscribers.@foreach (s => {
|
c.subscribers.@foreach (s => {
|
||||||
s.on_status_removed (id);
|
s.on_status_removed (id);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "notification":
|
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 => {
|
c.subscribers.@foreach (s => {
|
||||||
s.on_notification (obj);
|
s.on_notification (notif);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
warning (@"Unknown websocket event: \"$e\". Ignoring.");
|
warning (@"Unknown websocket event: \"$ev\". Ignoring.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void force_delete (int64 id) {
|
public void force_delete (string id) {
|
||||||
warning (@"Force removing status id $id");
|
|
||||||
connections.get_values ().@foreach (c => {
|
connections.get_values ().@foreach (c => {
|
||||||
c.subscribers.@foreach (s => {
|
c.subscribers.@foreach (s => {
|
||||||
s.on_status_removed (id);
|
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 () {
|
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; }
|
public API.Status root_status { get; construct set; }
|
||||||
protected InstanceAccount? account = null;
|
protected InstanceAccount? account = null;
|
||||||
protected Widgets.Status root_widget;
|
protected Widget root_widget;
|
||||||
|
|
||||||
public ExpandedStatus (API.Status status) {
|
public ExpandedStatus (API.Status status) {
|
||||||
Object (root_status: status, state: "content");
|
Object (
|
||||||
|
root_status: status,
|
||||||
root_widget = append (status);
|
status_message: STATUS_LOADING
|
||||||
root_widget.avatar.button_press_event.connect (root_widget.on_avatar_clicked);
|
);
|
||||||
connect_account ();
|
connect_account ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void on_account_changed (InstanceAccount? acc) {
|
public override void on_account_changed (InstanceAccount? acc) {
|
||||||
account = acc;
|
account = acc;
|
||||||
request ();
|
request ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Widgets.Status prepend (API.Status status, bool to_end = false){
|
Widget prepend (Entity entity, bool to_end = false){
|
||||||
var w = new Widgets.Status (status);
|
var w = entity.to_widget () as Widgets.Status;
|
||||||
w.avatar.button_press_event.connect (w.on_avatar_clicked);
|
|
||||||
w.revealer.reveal_child = true;
|
w.revealer.reveal_child = true;
|
||||||
|
|
||||||
if (to_end)
|
if (to_end)
|
||||||
|
@ -32,8 +31,8 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
||||||
check_resize ();
|
check_resize ();
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
private Widgets.Status append (API.Status status) {
|
Widget append (Entity entity) {
|
||||||
return prepend (status, true);
|
return prepend (entity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void request () {
|
public void request () {
|
||||||
|
@ -44,25 +43,23 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
||||||
|
|
||||||
var ancestors = root.get_array_member ("ancestors");
|
var ancestors = root.get_array_member ("ancestors");
|
||||||
ancestors.foreach_element ((array, i, node) => {
|
ancestors.foreach_element ((array, i, node) => {
|
||||||
var object = node.get_object ();
|
var status = Entity.from_json (typeof (API.Status), node);
|
||||||
if (object != null) {
|
append (status);
|
||||||
var status = new API.Status (object);
|
|
||||||
prepend (status);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
root_widget = append (root_status);
|
||||||
|
|
||||||
var descendants = root.get_array_member ("descendants");
|
var descendants = root.get_array_member ("descendants");
|
||||||
descendants.foreach_element ((array, i, node) => {
|
descendants.foreach_element ((array, i, node) => {
|
||||||
var object = node.get_object ();
|
var status = Entity.from_json (typeof (API.Status), node);
|
||||||
if (object != null) {
|
append (status);
|
||||||
var status = new API.Status (object);
|
|
||||||
append (status);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
on_content_changed ();
|
||||||
|
|
||||||
int x,y;
|
int x,y;
|
||||||
translate_coordinates (root_widget, 0, 0, out x, out 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);
|
//content_list.select_row (root_widget);
|
||||||
})
|
})
|
||||||
.exec ();
|
.exec ();
|
||||||
|
@ -76,9 +73,9 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
||||||
.then ((sess, msg) => {
|
.then ((sess, msg) => {
|
||||||
var root = network.parse (msg);
|
var root = network.parse (msg);
|
||||||
var statuses = root.get_array_member ("statuses");
|
var statuses = root.get_array_member ("statuses");
|
||||||
var object = statuses.get_element (0).get_object ();
|
var node = statuses.get_element (0);
|
||||||
if (object != null){
|
if (node != null){
|
||||||
var status = new API.Status (object);
|
var status = API.Status.from (node);
|
||||||
window.open_view (new Views.ExpandedStatus (status));
|
window.open_view (new Views.ExpandedStatus (status));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -10,7 +10,7 @@ public class Tootle.Views.Federated : Views.Timeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string? get_stream_url () {
|
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 () {
|
public override string? get_stream_url () {
|
||||||
var tag = url.substring (4);
|
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 () {
|
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");
|
info ("New account view was requested");
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool reset () {
|
bool reset () {
|
||||||
info ("State invalidated");
|
info ("State invalidated");
|
||||||
instance = code = client_id = client_secret = access_token = null;
|
instance = code = client_id = client_secret = access_token = null;
|
||||||
instance_entry.sensitive = true;
|
instance_entry.sensitive = true;
|
||||||
|
@ -50,11 +50,11 @@ public class Tootle.Views.NewAccount : Views.Base {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void oopsie (string message) {
|
void oopsie (string message) {
|
||||||
warning (message);
|
warning (message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_next_clicked () {
|
void on_next_clicked () {
|
||||||
try {
|
try {
|
||||||
step ();
|
step ();
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void step () throws Error {
|
void step () throws Error {
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
setup_instance ();
|
setup_instance ();
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
||||||
request_token ();
|
request_token ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setup_instance () throws Error {
|
void setup_instance () throws Error {
|
||||||
info ("Checking instance URL");
|
info ("Checking instance URL");
|
||||||
|
|
||||||
var str = instance_entry.text
|
var str = instance_entry.text
|
||||||
|
@ -91,7 +91,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
||||||
throw new Oopsie.USER (_("Instance URL is invalid"));
|
throw new Oopsie.USER (_("Instance URL is invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void register_client () throws Error {
|
void register_client () throws Error {
|
||||||
info ("Registering client");
|
info ("Registering client");
|
||||||
instance_entry.sensitive = false;
|
instance_entry.sensitive = false;
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ public class Tootle.Views.NewAccount : Views.Base {
|
||||||
.exec ();
|
.exec ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void open_confirmation_page () {
|
void open_confirmation_page () {
|
||||||
info ("Opening permission request page");
|
info ("Opening permission request page");
|
||||||
|
|
||||||
var pars = @"scope=$scopes&response_type=code&redirect_uri=$redirect_uri&client_id=$client_id";
|
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);
|
Desktop.open_uri (url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void request_token () throws Error {
|
void request_token () throws Error {
|
||||||
if (code.char_count () <= 10)
|
if (code.char_count () <= 10)
|
||||||
throw new Oopsie.USER (_("Please paste a valid authorization code"));
|
throw new Oopsie.USER (_("Please paste a valid authorization code"));
|
||||||
|
|
||||||
|
@ -142,8 +142,8 @@ public class Tootle.Views.NewAccount : Views.Base {
|
||||||
.then ((sess, msg) => {
|
.then ((sess, msg) => {
|
||||||
var root = network.parse (msg);
|
var root = network.parse (msg);
|
||||||
access_token = root.get_string_member ("access_token");
|
access_token = root.get_string_member ("access_token");
|
||||||
account.token = access_token;
|
account.access_token = access_token;
|
||||||
account.id = 0;
|
account.id = "";
|
||||||
info ("OK: received access token");
|
info ("OK: received access token");
|
||||||
request_profile ();
|
request_profile ();
|
||||||
})
|
})
|
||||||
|
@ -151,13 +151,13 @@ public class Tootle.Views.NewAccount : Views.Base {
|
||||||
.exec ();
|
.exec ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void request_profile () throws Error {
|
void request_profile () throws Error {
|
||||||
info ("Testing received access token");
|
info ("Testing received access token");
|
||||||
new Request.GET ("/api/v1/accounts/verify_credentials")
|
new Request.GET ("/api/v1/accounts/verify_credentials")
|
||||||
.with_account (account)
|
.with_account (account)
|
||||||
.then ((sess, msg) => {
|
.then ((sess, msg) => {
|
||||||
var root = network.parse (msg);
|
var node = network.parse_node (msg);
|
||||||
var account = new API.Account (root);
|
var account = API.Account.from (node);
|
||||||
info ("OK: received user profile");
|
info ("OK: received user profile");
|
||||||
save (account);
|
save (account);
|
||||||
})
|
})
|
||||||
|
@ -168,13 +168,13 @@ public class Tootle.Views.NewAccount : Views.Base {
|
||||||
.exec ();
|
.exec ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save (API.Account profile) {
|
void save (API.Account profile) {
|
||||||
info ("Account validated. Saving...");
|
info ("Account validated. Saving...");
|
||||||
account.patch (profile);
|
account.patch (profile);
|
||||||
account.instance = instance;
|
account.instance = instance;
|
||||||
account.client_id = client_id;
|
account.client_id = client_id;
|
||||||
account.client_secret = client_secret;
|
account.client_secret = client_secret;
|
||||||
account.token = access_token;
|
account.access_token = access_token;
|
||||||
accounts.add (account);
|
accounts.add (account);
|
||||||
|
|
||||||
destroy ();
|
destroy ();
|
||||||
|
|
|
@ -17,7 +17,7 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string? get_stream_url () {
|
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 () {
|
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 nw = w as Widgets.Notification;
|
||||||
var notification = nw.notification;
|
var notification = nw.notification;
|
||||||
|
|
||||||
if (notification.id > last_id)
|
if (int64.parse (notification.id) > last_id)
|
||||||
last_id = notification.id;
|
last_id = int64.parse (notification.id);
|
||||||
|
|
||||||
needs_attention = has_unread () && !current;
|
needs_attention = has_unread () && !current;
|
||||||
if (needs_attention)
|
if (needs_attention)
|
||||||
accounts.save ();
|
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) {
|
public override void on_account_changed (InstanceAccount? acc) {
|
||||||
base.on_account_changed (acc);
|
base.on_account_changed (acc);
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
|
|
|
@ -57,7 +57,7 @@ public class Tootle.Views.Profile : Views.Timeline {
|
||||||
relationship = builder.get_object ("relationship") as Label;
|
relationship = builder.get_object ("relationship") as Label;
|
||||||
|
|
||||||
posts_label = builder.get_object ("posts_label") 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;
|
var val = (int64) src;
|
||||||
target.set_string (_("%s Posts").printf (@"<b>$val</b>"));
|
target.set_string (_("%s Posts").printf (@"<b>$val</b>"));
|
||||||
return true;
|
return true;
|
||||||
|
@ -151,28 +151,22 @@ public class Tootle.Views.Profile : Views.Timeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Request append_params (Request req) {
|
public override Request append_params (Request req) {
|
||||||
req.with_param ("exclude_replies", (!filter_replies.active).to_string ());
|
if (page_next == null) {
|
||||||
req.with_param ("only_media", filter_media.active.to_string ());
|
req.with_param ("exclude_replies", (!filter_replies.active).to_string ());
|
||||||
return base.append_params (req);
|
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) {
|
public static void open_from_id (string id){
|
||||||
var obj = node.get_object ();
|
var url = @"$(accounts.active.instance)/api/v1/accounts/$id";
|
||||||
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);
|
|
||||||
var msg = new Soup.Message ("GET", url);
|
var msg = new Soup.Message ("GET", url);
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
msg.priority = Soup.MessagePriority.HIGH;
|
||||||
network.queue (msg, (sess, mess) => {
|
network.queue (msg, (sess, mess) => {
|
||||||
var root = network.parse (mess);
|
var node = network.parse_node (mess);
|
||||||
var acc = new API.Account (root);
|
var acc = API.Account.from (node);
|
||||||
window.open_view (new Views.Profile (acc));
|
window.open_view (new Views.Profile (acc));
|
||||||
}, (status, reason) => {
|
}, (status, reason) => {
|
||||||
network.on_error (status, reason);
|
network.on_error (status, reason);
|
||||||
|
|
|
@ -92,8 +92,7 @@ public class Tootle.Views.Search : Views.Base {
|
||||||
if (accounts.get_length () > 0) {
|
if (accounts.get_length () > 0) {
|
||||||
append_header (_("Accounts"));
|
append_header (_("Accounts"));
|
||||||
accounts.foreach_element ((array, i, node) => {
|
accounts.foreach_element ((array, i, node) => {
|
||||||
var obj = node.get_object ();
|
var acc = API.Account.from (node);
|
||||||
var acc = new API.Account (obj);
|
|
||||||
append_account (acc);
|
append_account (acc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -101,8 +100,7 @@ public class Tootle.Views.Search : Views.Base {
|
||||||
if (statuses.get_length () > 0) {
|
if (statuses.get_length () > 0) {
|
||||||
append_header (_("Statuses"));
|
append_header (_("Statuses"));
|
||||||
statuses.foreach_element ((array, i, node) => {
|
statuses.foreach_element ((array, i, node) => {
|
||||||
var obj = node.get_object ();
|
var status = API.Status.from (node);
|
||||||
var status = new API.Status (obj);
|
|
||||||
append_status (status);
|
append_status (status);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,17 +30,6 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
|
||||||
return status.is_owned ();
|
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) {
|
public void prepend (Widget? w) {
|
||||||
append (w, true);
|
append (w, true);
|
||||||
}
|
}
|
||||||
|
@ -90,30 +79,34 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
|
||||||
public virtual string get_req_url () {
|
public virtual string get_req_url () {
|
||||||
if (page_next != null)
|
if (page_next != null)
|
||||||
return page_next;
|
return page_next;
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Request append_params (Request req) {
|
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 () {
|
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)
|
.with_account (account)
|
||||||
.then_parse_array ((node, msg) => {
|
.then_parse_array ((node, msg) => {
|
||||||
try {
|
try {
|
||||||
var e = to_entity (node);
|
var e = Entity.from_json (accepts, node);
|
||||||
var w = e as Widgetizable;
|
var w = e as Widgetizable;
|
||||||
append (w.to_widget ());
|
append (w.to_widget ());
|
||||||
}
|
}
|
||||||
catch (Error e) {
|
catch (Error e) {
|
||||||
warning (@"Timeline item parse error: $(e.message)");
|
warning (@"Timeline item parse error: $(e.message)");
|
||||||
}
|
}
|
||||||
get_pages (msg.response_headers.get_one ("Link"));
|
|
||||||
})
|
})
|
||||||
.on_error (on_error)
|
.on_error (on_error);
|
||||||
.exec ();
|
req.finished.connect (() => {
|
||||||
|
get_pages (req.response_headers.get_one ("Link"));
|
||||||
|
});
|
||||||
|
req.exec ();
|
||||||
return GLib.Source.REMOVE;
|
return GLib.Source.REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +145,7 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
|
||||||
prepend (status.to_widget ());
|
prepend (status.to_widget ());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void remove_status (int64 id) {
|
protected virtual void remove_status (string id) {
|
||||||
if (settings.live_updates) {
|
if (settings.live_updates) {
|
||||||
content.get_children ().@foreach (w => {
|
content.get_children ().@foreach (w => {
|
||||||
var sw = w as Widgets.Status;
|
var sw = w as Widgets.Status;
|
||||||
|
|
|
@ -4,7 +4,7 @@ using Gdk;
|
||||||
public class Tootle.Widgets.Attachment.Item : EventBox {
|
public class Tootle.Widgets.Attachment.Item : EventBox {
|
||||||
|
|
||||||
public API.Attachment attachment { get; construct set; }
|
public API.Attachment attachment { get; construct set; }
|
||||||
|
|
||||||
private Cache.Reference? cached;
|
private Cache.Reference? cached;
|
||||||
|
|
||||||
public Item (API.Attachment obj) {
|
public Item (API.Attachment obj) {
|
||||||
|
@ -13,15 +13,15 @@ public class Tootle.Widgets.Attachment.Item : EventBox {
|
||||||
~Item () {
|
~Item () {
|
||||||
cache.unload (cached);
|
cache.unload (cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
get_style_context ().add_class ("attachment");
|
get_style_context ().add_class ("attachment");
|
||||||
width_request = height_request = 128;
|
width_request = height_request = 128;
|
||||||
hexpand = true;
|
hexpand = true;
|
||||||
tooltip_text = attachment.description ?? _("No description is available");
|
tooltip_text = attachment.description ?? _("No description is available");
|
||||||
|
|
||||||
button_press_event.connect (on_clicked);
|
button_press_event.connect (on_clicked);
|
||||||
|
|
||||||
show ();
|
show ();
|
||||||
on_request ();
|
on_request ();
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ public class Tootle.Widgets.Attachment.Item : EventBox {
|
||||||
var h = get_allocated_height ();
|
var h = get_allocated_height ();
|
||||||
var style = get_style_context ();
|
var style = get_style_context ();
|
||||||
var border_radius = style.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS, style.get_state ()).get_int ();
|
var border_radius = style.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS, style.get_state ()).get_int ();
|
||||||
|
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
if (cached.loading) {
|
if (cached.loading) {
|
||||||
Drawing.center (ctx, w, h, 32, 32);
|
Drawing.center (ctx, w, h, 32, 32);
|
||||||
|
@ -74,7 +74,7 @@ public class Tootle.Widgets.Attachment.Item : EventBox {
|
||||||
ctx.fill ();
|
ctx.fill ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Gdk.EVENT_STOP;
|
return Gdk.EVENT_STOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,15 +85,15 @@ public class Tootle.Widgets.Attachment.Item : EventBox {
|
||||||
}
|
}
|
||||||
else if (ev.button == 3) {
|
else if (ev.button == 3) {
|
||||||
var menu = new Gtk.Menu ();
|
var menu = new Gtk.Menu ();
|
||||||
|
|
||||||
var item_open = new Gtk.MenuItem.with_label (_("Open"));
|
var item_open = new Gtk.MenuItem.with_label (_("Open"));
|
||||||
item_open.activate.connect (open);
|
item_open.activate.connect (open);
|
||||||
menu.add (item_open);
|
menu.add (item_open);
|
||||||
|
|
||||||
var item_download = new Gtk.MenuItem.with_label (_("Download"));
|
var item_download = new Gtk.MenuItem.with_label (_("Download"));
|
||||||
item_download.activate.connect (download);
|
item_download.activate.connect (download);
|
||||||
menu.add (item_download);
|
menu.add (item_download);
|
||||||
|
|
||||||
menu.show_all ();
|
menu.show_all ();
|
||||||
menu.attach_widget = this;
|
menu.attach_widget = this;
|
||||||
menu.popup_at_pointer ();
|
menu.popup_at_pointer ();
|
||||||
|
|
|
@ -69,18 +69,18 @@ public class Tootle.Widgets.RichLabel : Label {
|
||||||
var hashtags = root.get_array_member ("hashtags");
|
var hashtags = root.get_array_member ("hashtags");
|
||||||
|
|
||||||
if (accounts.get_length () > 0) {
|
if (accounts.get_length () > 0) {
|
||||||
var item = accounts.get_object_element (0);
|
var node = accounts.get_element (0);
|
||||||
var obj = new API.Account (item);
|
var obj = API.Account.from (node);
|
||||||
window.open_view (new Views.Profile (obj));
|
window.open_view (new Views.Profile (obj));
|
||||||
}
|
}
|
||||||
else if (statuses.get_length () > 0) {
|
else if (statuses.get_length () > 0) {
|
||||||
var item = accounts.get_object_element (0);
|
var node = accounts.get_element (0);
|
||||||
var obj = new API.Status (item);
|
var obj = API.Status.from (node);
|
||||||
window.open_view (new Views.ExpandedStatus (obj));
|
window.open_view (new Views.ExpandedStatus (obj));
|
||||||
}
|
}
|
||||||
else if (hashtags.get_length () > 0) {
|
else if (hashtags.get_length () > 0) {
|
||||||
var item = accounts.get_object_element (0);
|
var node = accounts.get_element (0);
|
||||||
var obj = new API.Tag (item);
|
var obj = API.Tag.from (node);
|
||||||
window.open_view (new Views.Hashtag (obj.name));
|
window.open_view (new Views.Hashtag (obj.name));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -90,9 +90,9 @@ public class Tootle.Widgets.Status : EventBox {
|
||||||
kind = API.NotificationType.REBLOG_REMOTE_USER;
|
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 (() => {
|
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);
|
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");
|
reblog_button.tooltip_text = _("This post can't be boosted");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.id <= 0) {
|
if (status.id == "") {
|
||||||
actions.destroy ();
|
actions.destroy ();
|
||||||
date_label.destroy ();
|
date_label.destroy ();
|
||||||
content.single_line_mode = true;
|
content.single_line_mode = true;
|
||||||
|
@ -130,7 +130,7 @@ public class Tootle.Widgets.Status : EventBox {
|
||||||
button_press_event.connect (open);
|
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 ();
|
attachments.destroy ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue