summaryrefslogtreecommitdiffstats
path: root/src/h.c
blob: 4512cd98252c1ec5a833ba801831072f97ff65dc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/* note: ISAs will (hopefully) no longer be used in my programs in favour of linked lists */
/* ISAs: _sizeof means size of array, _length means usable/initialized members in array */
#define DC_ISA(type, name) type ** name; size_t name##_sizeof; size_t name##_length /* in struct array */
#define DC_ALLOC_CHUNK 1
#define DC_REALLOC_K 1.5
#define DC_BIGGER_ARRAY(name) do { /* unlike in previous programs, _BIGGER_ARRAY */ \
		name##_sizeof = ceil(name##_sizeof*DC_REALLOC_K); /* no longer initializes-that'd */ \
		name = realloc(name, sizeof(name[0])*name##_sizeof); /* prevent inserting noninited */ \
	} while (0) /* note that sizeof(name[0]) does not dereferencec name */
#define DC_MR(n) if (n##_sizeof <= n##_length) /* make room */ \
	DC_BIGGER_ARRAY(n)
#define DC_STRUCT_PREFIX void * data; /* some user data to be kind to api library users */
#define DC_LWS_BUF (2^14) /* 16384 byte chunks are handled at a time when RXing HTTP body */
#define DC_LWS_MAX_RX DC_LWS_BUF /* max bytes a websocket may handle in a single receive */
#define DC_LWS_MAX_FD 64 /* max file descriptors LWS will have open. if unset, LWS acquires all unused */
#define DC_LWS_MAX_HEADER_LENGTH 64 /* _MAX_HEADER_LENGTH*_HEADERS_LENGTH is allocated for every reque */
/* it's strongly recommended to calloc structs during initialization. */
enum dc_status { /* theese are flags and should be and-checked */
	DC_UNSET = 0, /* default value when enum is calloced */
	DC_INCOMPLETE = 1 << 1, /* struct SHALL NOT be used by the ui, it is yet to be filled by api */
	DC_OK = 1 << 2, /* success status, api usually sets this after completion/filling of the strct */
	DC_BAD_LOGIN = 1 << 3, /* login failed */
	DC_VERIFICATION_NEEDED = 1 << 4, /* login: check email, click link/reg: tough luck ur IP flagd */
	DC_CAPTCHA_NEEDED = 1 << 5, /* must solve captcha, tough luck, not impl, use browser login */
	DC_BAD_USERNAME = 1 << 6, /* provided username can't be registered */
	DC_BAD_EMAIL = 1 << 7, /* provided email can't be registered */
	DC_NOT_FOUND = 1 << 8, /* when querying for roles/users/... received a 404 */
	DC_CONTINUE = 1 << 9, /* attached handlers return this to continue processing this output, N/I */
	DC_BREAK = 1 << 10, /* attached handlers return this to stop processing this output */
	DC_BODY_SENT = 1 << 11, /* LWS already sent body, it's now time to send _FINAL for H2 */
	DC_MUST_FREE = 1 << 12 /* cb pass: body must be freed when request is done with user_data */
};
enum dc_permissions { /* other permissions exist, but are not implemented/understood */
	DC_ALL_PERMISSIONS = 1 << 3, /* this is incredibly retarded, why is this SEPARATE?!? - admins */
	DC_CHANNEL_VIEW = 1 << 10, /* all enum fields here have values same as the server values */
	DC_MESSAGE_SEND = 1 << 11,
	DC_MESSAGE_READ = 1 << 16, /* na tistem vegova serverju sem lahko pošiljal ne pa bral sporočil */
	DC_VOICE_LISTEN = 1 << 20,
	DC_VOICE_SPEAK = 1 << 21
}; /* all enum fields here have values same as the values that the server sends */
enum dc_channel_type { /* other types exist, but are not implemented/understood */
	DC_TEXT, /* all enum fields here have values same as the values that the server sends */
	DC_DM,
	DC_VOICE,
	DC_DM2 /* retarded. server sometimes sends this... converted to DC_DM at parsing server resp. */
};
enum dc_api_io_type {
	/* all output structs have important structs set when an output is broadcast, for example if we get a channel from server but we don't possess it's guild (by id), api will wait for the guild and hold the channel in it's cache. for messages, tags in message are also parsed and queried. roles in premissions in channels are also fetched but not users in permissions, because permissions are currently only used for checking our permissions, not others' */
	/* none of the outputs transfer memory ownership. tr0 for inputs means transfer none, trp means pointer to struct owned by library is to be passed, tr1 means transfer of ownership to library */
	/* network requests are sent to the event-based network library immediatley on input. network library is polled for incoming network traffic on each _event call. not totally filled structs in cache are checked and if one is/becomes filled, it will be output. if a role is requested when this role already exists and is searched for (not filled), for example because a channel needs the role to be output or the user created another search for this role before, this search will be dropped and the passed struct will be freed. */
	/* this library has internally attached functions for handling many io types so that the library nicely detects when for example a new role is received. the on-role-received function for example checks cache and updates structs that need this role */
	/* struct dc_program * serves as a handle for the API. create it with dc_program_init (may return NULL which indicates a LWS init failure) and destroy it with dc_program_free. pass it to every call inside struct dc_api_io.program */
	DC_API_NONE,	/* i: N/A */
			/* o: nothing to output */
	DC_API_MESSAGE,	/* i: send a message-tr0 or edit a message that was already sent-trp */
			/* o: message notification: GUI spawn or respawn if edited. */
	DC_API_CHANNEL,	/* i: TODO: create a channel-tr0 */
			/* o: new channel created, GUI spawn it. */
	DC_API_GUILD,	/* i: TODO: create a guild-tr0 */
			/* o: TODO: new guild created, GUI spawn it */
	DC_API_LOGIN,	/* i: pass a dc_client-tr1, to relogin FIX prev retd cl not create new */
			/* o: the previously passed dc_client with set status */
	DC_API_LOGIN_CB,/* i: used internally for passing response from http client to api, see source */
			/* o: n/a */
	DC_API_REGISTER,/* i: pass a dc_client, to relogin FIX pr rt cl&cl->user not creat new */
			/* o: the previously passed dc_client with set status */
	DC_API_STATUS,	/* i: N/A */
			/* o: new status message, pointer to internal string - tr0 */
	DC_API_USER,	/* i: query for user by id, pass dc_user-tr1 */
			/* o: prev passed dc_user but filled (or not: ->status may indicate error) */
	DC_API_ROLE,	/* i: query for role by id, pass dc_role-tr1 */
			/* o: prev passed dc_role but filled (or not: ->status may indicate error) */
	DC_API_ATTACH	/* i: attaches function to handle output types */
			/* o: N/A */
};
/* enum dc_status (* dc_api_attached_func) (struct dc_api_io, void * data); */ /* i tried simplifying but didn't work */
struct dc_api_io { /* output struct does NOT contain void * data (user pointer) from the input struct! */
	DC_STRUCT_PREFIX /* mostly useless here but it's only a couple of bytes so wth */
	enum dc_api_io_type type; /* NOTE: ALL POINTERS HERE ARE NOFREE */
	enum dc_status status;
	void * body;
	char * string;
	struct dc_message * message; /* for safety and to mininize casting, each type has a pointer */
	struct dc_channel * channel;
	struct dc_guild * guild;
	struct dc_client * client;
	struct dc_user * user;
	struct dc_role * role;
	struct dc_program * program; /* this is library handle */
	struct dc_attached_function * attached_function; /* points to allocated struct when DC_API_ATTAH */
	struct dc_lws_pass * pass;
};
void dc_api_io_free (struct dc_api_io * s) {
	if (!s)
		return;
	free(s);
	return;
}
enum dc_lws_headers {
	DC_LWS_AUTHORIZATION,
	DC_LWS_HEADERS_LENGTH
}
char dc_lws_headers[][] = {
	"Authorization:",
};
struct dc_lws_pass { /* struct that is allocated for in dc_lws_cb unique per connection in void * us */
	char * body; /* this contains post body and when _CB is called, it contains response */
	size_t body_length; /* body is NULL terminated - note0 in src/api.c */
	char headers[DC_LWS_HEADERS_LENGTH][DC_LWS_MAX_HEADER_LENGTH]; /* nofree, a static 2d array */
	int status; /* HTTP response code		/\ headers contain request headers, then resp. */
	struct dc_api_io api_io; /* so dc_api_io can decide what shall be passed into _CB */
};
struct dc_client {
	DC_STRUCT_PREFIX
	char * authorization; /* yesfree - authorization header value */
	char * email; /* yesfree */
	char * password; /* yesfree */
	struct dc_user * user; /* nofree - logged in user */
	struct dc_guild * guild; /* nofree - first guild */
	enum dc_status status;
};
struct dc_client * dc_client_init () {
	struct dc_client * s = calloc(1, sizeof(*s));
	return s;
}
void dc_client_free (struct dc_client * s) {
	if (!s)
		return;
	free(s->authorization);
	free(s->email);
	free(s->password);
	free(s);
}
struct dc_guild {
	DC_STRUCT_PREFIX
	char * name; /* yesfree */
	char * alternative_messages_url; /* yesfree, internal - for virtual DMs guild */
	unsigned long long int id; /* 0 for virtual DMs guild */
	struct dc_client * client; /* nofree */
	struct dc_guild * next; /* nofree - next guild (linked list of all guilds of dc_client) */
	struct dc_channel * channel; /* nofree - first channel */
	struct dc_role * role; /* nofree - first role. NOTE: first role is always role with role ID that is same as guild ID and it is the @everyone role */
	enum dc_permissions permissions;
#ifdef DC_UI_GTK
	GtkTreeIter iter; /* NOTE: only works when GtkTreeModel has a flag GTK_TREE_MODEL_ITERS_PERSIST; see paragraph 8 of description of file:///usr/share/doc/libgtk-3-doc/gtk3/GtkTreeModel.html */
	gboolean is_iter;
	/* GtkTreeRowReference * row; */ /* yesfree - indicating the row */ /* not used: IRC message: "00:51:29        @Company | also: Don't create too many tree row references, those things are slow"  */
#endif
};
struct dc_guild * dc_guild_init () {
	struct dc_guild * s = calloc(1, sizeof(*s));
	return s;
}
void dc_guild_free (struct dc_guild * s) {
	if (!s)
		return;
	free(s->name);
	free(s->alternative_messages_url);
	free(s);
}
struct dc_channel {
	DC_STRUCT_PREFIX
	char * name; /* yesfree - name */
	char * topic; /* yesfree - topic */
	unsigned long long int id;
	enum dc_channel_type type;
	struct dc_guild * guild; /* nofree */
	struct dc_channel * next; /* nofree - next channel (linked list of all guilds of dc_guild) */
	struct dc_message * message; /* nofree - first message (ordered by time) */
	struct dc_permission * permission; /* nofree - first permission */
#ifdef DC_UI_GTK
	GtkTreeIter iter; /* see notes of struct dc_guild */
	gboolean is_iter; /* XXX use this with caution: only if it's possible for treemodels to contains orphans, this works. otherwise, parents will be removed first and then channels will not het their is_iters invalidated. guild's is_iter will always get invalidated. file:///usr/share/doc/libgtk-3-doc/gtk3/GtkTreeStore.html#gtk-tree-store-iter-is-valid could be a solution but it's said to be slow. */
#endif
};
struct dc_channel * dc_channel_init () {
	struct dc_channel * s = calloc(1, sizeof(*s));
	return s;
}
void dc_channel_free (struct dc_channel * s) {
	if (!s)
		return;
	free(s->name);
	free(s->topic);
	free(s);
}
struct dc_message {
	DC_STRUCT_PREFIX
	char * message; /* yesfree */
	char * attachment; /* yesfree - this is a HTTP URL. it would be nice to request file and store to ~/.cache/discord.c/ and only then use (RENDER IMAGE) from disk */
	struct dc_channel * channel; /* nofree */
	struct dc_user * user; /* nofree */
	time_t time;
	unsigned long long int id;
	struct dc_message * next; /* next message (linked list of all messages of dc_channel) */
	struct dc_message * reply; /* nofree - this message replies to another message or NULL */
};
struct dc_message * dc_message_init () {
	struct dc_message * s = calloc(1, sizeof(*s));
	return s;
}
void dc_message_free (struct dc_message * s) {
	if (!s)
		return;
	free(s->message);
	free(s->attachment);
	free(s);
}
struct dc_role {
	DC_STRUCT_PREFIX
	char * name; /* yesfree */
	unsigned long long int id;
	enum dc_permissions permissions;
	struct dc_guild * guild; /* nofree - owner of the role */
	struct dc_role * next; /* nofree - next role (linked list of all roles of dc_guild) */
	struct dc_role_membership * role_membership; /* nofree - first role membership ll) */
	enum dc_status status;
};
struct dc_role * dc_role_init () {
	struct dc_role * s = calloc(1, sizeof(*s));
	return s;
}
void dc_role_free (struct dc_role * s) {
	if (!s)
		return;
	free(s->name);
	free(s);
}
struct dc_role_membership {
	DC_STRUCT_PREFIX
	struct dc_channel * channel; /* nofree */
	struct dc_user * user; /* nofree */
	struct dc_role * role; /* nofree */
	struct dc_role_membership * next; /* nofree - next role membership (lili in role) */
};
struct dc_role_membership * dc_role_membership_init () {
	struct dc_role_membership * s = calloc(1, sizeof(*s));
	return s;
}
void dc_role_membership_free (struct dc_role_membership * s) {
	if (!s)
		return;
	free(s);
}
struct dc_user {
	DC_STRUCT_PREFIX
	char * username; /* yesfree */
	unsigned long long int id;
	short int discriminator;
	enum dc_status status;
};
struct dc_user * dc_user_init () {
	struct dc_user * s = calloc(1, sizeof(*s));
	return s;
}
void dc_user_free (struct dc_user * s) {
	if (!s)
		return;
	free(s->username);
	free(s);
}
struct dc_permission { /* permissions can be individual on a per-channel basis */
	DC_STRUCT_PREFIX
	enum dc_permissions allow;
	enum dc_permissions deny;
	unsigned long long int id; /* to whom does this permission apply */
	struct dc_channel * channel; /* nofree - on which channel does it apply */
	struct dc_user * user; /* nofree - non-null if permission applies to a user */
	struct dc_role * role; /* nofree - non-null if it applies to a role */
	struct dc_permission * next; /* nexrt permission in ll in channel */
}; /* permissions are only useful for checking OUR permissions, not others'. keep that in mind. */
struct dc_permission * dc_permission_init () {
	struct dc_permission * s = calloc(1, sizeof(*s));
	return s;
}
void dc_permission_free (struct dc_permission * s) {
	if (!s)
		return;
	free(s);
}
struct dc_attached_function {
	DC_STRUCT_PREFIX
	enum dc_api_io_type type;
	void * user_data; /* I hope I don't confuse this with void * data in DC_STRUCT_PREFIX */
	enum dc_status (* function) (struct dc_api_io, void *);
};
struct dc_attached_function * dc_attached_function_init () {
	struct dc_attached_function * s = calloc(1, sizeof(*s));
	return s;
}
void dc_attached_function_free (struct dc_attached_function * s) {
	if (!s)
		return;
	free(s);
}
static int dc_lws_cb (struct lws *, enum lws_callback_reasons, void *, void *, size_t);
static const struct lws_protocols dc_lws_protocols[] = {
	{"dc", dc_lws_cb, sizeof(struct dc_lws_pass), DC_LWS_MAX_RX, 0, NULL, 0},
	{NULL, NULL, 0, 0, 0, NULL, 0}
};
#define DC_ISASQ(shortname) DC_ISA(struct dc_##shortname, shortname##s) /* in struct array of structs quick */
struct dc_program { /* data storage and token used for communication with the library */
	DC_STRUCT_PREFIX /* this is the only struct that contains DC_ISAs */
	DC_ISASQ(client); /* yesfree */
	DC_ISASQ(guild); /* yesfree */
	DC_ISASQ(channel); /* yesfree */
	DC_ISASQ(message); /* yesfree */
	DC_ISASQ(role); /* yesfree */
	DC_ISASQ(role_membership); /* yesfree */
	DC_ISASQ(user); /* yesfree */
	DC_ISASQ(permission); /* yesfree */
	DC_ISASQ(attached_function); /* yesfree */
	DC_ISASQ(api_io); /* yesfree */
	struct lws_context * lws_context; /* yesfree */
}; /* ui functions MUST check if !(->status & DC_INCOMPLETE) before using array members, incompletes are yet to be filled by the api */
#define DC_ISAS_INIT(type/* w/o struct */, name) do { name##_sizeof = DC_ALLOC_CHUNK; /* structs ISA */ \
	name = calloc(name##_sizeof, sizeof(struct type *)); } while (0) /* prep arr, NO INIT membrs */
#define DC_ISASIQ(shortname) DC_ISAS_INIT(dc_##shortname, s->shortname##s) /* ISAS init quick */
struct dc_program * dc_program_init () {
	struct dc_program * s = calloc(1, sizeof(struct dc_program));
	DC_ISASIQ(client);
	DC_ISASIQ(guild);
	DC_ISASIQ(channel);
	DC_ISASIQ(message);
	DC_ISASIQ(role);
	DC_ISASIQ(role_membership);
	DC_ISASIQ(user);
	DC_ISASIQ(permission);
	DC_ISASIQ(attached_function);
	DC_ISASIQ(api_io);
	/* lws init */
	struct lws_context_creation_info info;
	memset(&info, 0, sizeof(info));
	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
	info.port = CONTEXT_PORT_NO_LISTEN; /* we are not listening - we are a client */
	info.protocols = dc_lws_protocols;
	info.fd_limit_per_thread = DC_LWS_MAX_FD;
	if (!(s->lws_context = lws_create_context(&info))) {
		lwsl_err("lws init failed\n");
		return NULL;
	}
	return s;
}
#define DC_ISAF(shortname) for (size_t i = 0; i < s->shortname##s_sizeof; i++) /* ISA free */ \
		dc_##shortname##_free(s->shortname##s[i]); \
	free(s->shortname##s); /* no problem if we free past _lenght, as uninited are NULL */
void dc_program_free (struct dc_program * s) {
	if (!s)
		return;
	DC_ISAF(client);
	DC_ISAF(guild);
	DC_ISAF(channel);
	DC_ISAF(message);
	DC_ISAF(role);
	DC_ISAF(role_membership);
	DC_ISAF(user);
	DC_ISAF(permission);
	DC_ISAF(attached_function);
	DC_ISAF(api_io);
	lws_context_destroy(s->lws_context);
	free(s);
}