25 |
26 |
Click the button to sign off.
27 |
28 |
29 |
Click the button to test a toot.
30 |
31 |
32 |
Click the button to get info about yourself, open the JavaScript console to see.
33 |
34 |
35 |
36 |
Click the button to sign on via Mastodon.
37 |
38 |
39 |
40 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/home/api.js:
--------------------------------------------------------------------------------
1 | const mastoConsts = {
2 | serverUrl: "http://oauth.masto.land/",
3 |
4 | clientKey: "2l26ogBP5utGp-VAf0J66r23KDn5aCRTjC0m9MoHdfQ" //radio3 on social.masto.land
5 | };
6 |
7 | var mastoMemory = {
8 | access_token: undefined,
9 | created_at: undefined,
10 | scope: undefined,
11 | token_type: undefined
12 | }
13 |
14 | function saveMastoMemory () {
15 | localStorage.mastoMemory = jsonStringify (mastoMemory);
16 | }
17 | function restoreMastoMemory () {
18 | if (localStorage.mastoMemory !== undefined) {
19 | var jstruct;
20 | try {
21 | jstruct = JSON.parse (localStorage.mastoMemory);
22 | for (var x in jstruct) {
23 | mastoMemory [x] = jstruct [x];
24 | }
25 | }
26 | catch (err) {
27 | console.log ("restoreMastoMemory: err.message == " + err.message);
28 | }
29 | }
30 | console.log ("restoreMastoMemory: mastoMemory == " + jsonStringify (mastoMemory));
31 | }
32 | function initMastoMemory () {
33 | for (var x in mastoMemory) {
34 | mastoMemory [x] = undefined;
35 | }
36 | }
37 |
38 | function getAllUrlParams () { //9/7/22 by DW
39 | var s = location.search;
40 | var allparams = new Object ();
41 | if (beginsWith (s, "?")) {
42 | s = stringDelete (s, 1, 1);
43 | }
44 | var splits = s.split ("&");
45 | splits.forEach (function (item) {
46 | var splits = item.split ("=");
47 | allparams [splits [0]] = splits [1];
48 | });
49 | return (allparams);
50 | }
51 | function getFirstUrlParam (paramval) {//5/26/22 by DW
52 | var s = location.search;
53 | if (beginsWith (s, "?")) {
54 | s = stringDelete (s, 1, 1);
55 | }
56 | var param1 = stringNthField (s, "&", 1);
57 | if (param1.length == 0) {
58 | return ("");
59 | }
60 | paramval.val = decodeURIComponent (stringNthField (param1, "=", 2));
61 | return (stringNthField (param1, "=", 1));
62 | }
63 | function httpRequest (url, timeout, headers, callback) {
64 | timeout = (timeout === undefined) ? 30000 : timeout;
65 | var jxhr = $.ajax ({
66 | url: url,
67 | method: "GET",
68 | dataType: "text",
69 | headers,
70 | timeout
71 | })
72 | .success (function (data, status) {
73 | callback (undefined, data);
74 | })
75 | .error (function (status) {
76 | var message;
77 | try { //9/18/21 by DW
78 | message = JSON.parse (status.responseText).message;
79 | }
80 | catch (err) {
81 | message = status.responseText;
82 | }
83 | if ((message === undefined) || (message.length == 0)) { //7/22/22 by DW & 8/31/22 by DW
84 | message = "There was an error communicating with the server.";
85 | }
86 | var err = {
87 | code: status.status,
88 | message
89 | };
90 | callback (err);
91 | });
92 | }
93 | function buildParamList (paramtable, flPrivate) { //8/4/21 by DW
94 | var s = "";
95 | if (flPrivate) {
96 | paramtable.flprivate = "true";
97 | }
98 | for (var x in paramtable) {
99 | if (paramtable [x] !== undefined) { //8/4/21 by DW
100 | if (s.length > 0) {
101 | s += "&";
102 | }
103 | s += x + "=" + encodeURIComponent (paramtable [x]);
104 | }
105 | }
106 | return (s);
107 | }
108 |
109 | function userLogin (clientKey) {
110 | const urlThisPage = trimTrailing (window.location.href, "#");
111 | const urlRedirectTo = mastoConsts.serverUrl + "connect?redirect_url=" + urlThisPage + "&client_key=" + clientKey;
112 | window.location.href = urlRedirectTo;
113 | }
114 | function getAccessToken (codeFromMasto, callback) {
115 | var urlServer = mastoConsts.serverUrl + "getaccesstoken?code=" + codeFromMasto + "&client_key=" + mastoConsts.clientKey;
116 | httpRequest (urlServer, undefined, undefined, callback);
117 | }
118 |
119 | function servercall (path, params, flAuthenticated, callback, urlServer=mastoConsts.serverUrl) {
120 | var whenstart = new Date ();
121 | if (params === undefined) {
122 | params = new Object ();
123 | }
124 | if (flAuthenticated) { //1/11/21 by DW
125 | params.access_token = mastoMemory.access_token;
126 | params.client_key = mastoConsts.clientKey;
127 | }
128 | var url = urlServer + path + "?" + buildParamList (params);
129 | httpRequest (url, undefined, undefined, function (err, jsontext) {
130 | if (err) {
131 | console.log ("servercall: url == " + url + ", err.message == " + err.message);
132 | callback (err);
133 | }
134 | else {
135 | callback (undefined, JSON.parse (jsontext));
136 | }
137 | });
138 | }
139 |
140 | function getServerInfo (callback) {
141 | servercall ("api/v1/instance", undefined, undefined, callback);
142 | }
143 | function getPublicTimeline (limit=100, callback) {
144 | servercall ("api/v1/timelines/public", {limit}, undefined, callback);
145 | }
146 | function getPublicStatusesWithTag (theTag, limit=100, callback) {
147 | servercall ("api/v1/timelines/tag/" + theTag, {limit}, undefined, callback);
148 | }
149 | function getUserStatuses (id, limit=100, callback) {
150 | servercall ("api/v1/accounts/" + id + "/statuses/", {limit}, undefined, callback);
151 | }
152 | function postNewStatus (status, inReplyTo, callback) {
153 | const params = {
154 | status,
155 | inReplyTo
156 | };
157 | servercall ("toot", params, true, callback);
158 | }
159 | function getUserInfo (callback) {
160 | servercall ("getuserinfo", undefined, true, callback);
161 | }
162 |
163 | function testGetServerInfo () {
164 | getServerInfo (function (err, theServerInfo) {
165 | if (err) {
166 | console.log (err.message);
167 | }
168 | else {
169 | console.log (jsonStringify (theServerInfo));
170 | }
171 | })
172 | }
173 | function testGetPublicTimeline () {
174 | getPublicTimeline (undefined, function (err, theTimeline) {
175 | if (err) {
176 | console.log (err.message);
177 | }
178 | else {
179 | console.log (jsonStringify (theTimeline));
180 | }
181 | })
182 | }
183 | function testGetPublicStatusesWithTag () {
184 | getPublicStatusesWithTag ("testing", undefined, function (err, theStatuses) {
185 | if (err) {
186 | console.log (err.message);
187 | }
188 | else {
189 | console.log (jsonStringify (theStatuses));
190 | }
191 | })
192 | }
193 | function testGetUserStatuses () {
194 | getUserStatuses ("109348280299564804", undefined, function (err, theStatuses) {
195 | if (err) {
196 | console.log (err.message);
197 | }
198 | else {
199 | console.log (jsonStringify (theStatuses));
200 | }
201 | })
202 | }
203 | function testGetUserInfo () {
204 | getUserInfo (function (err, theInfo) {
205 | if (err) {
206 | console.log (err.message);
207 | }
208 | else {
209 | console.log (jsonStringify (theInfo));
210 | }
211 | });
212 | }
213 | function testPostStatus () {
214 | postStatus ("I'm a tootin fool", function (err, data) {
215 | if (err) {
216 | alertDialog (err.message);
217 | }
218 | else {
219 | console.log (data); //11/20/22 -- I have yet to see what I get back from this! ;-)
220 | }
221 | });
222 | }
223 |
224 | function lookForOauthToken () { //if it finds the token it dosn't return
225 | var allparams = getAllUrlParams (); //9/7/22 by DW
226 | var paramval = {
227 | };
228 | var firstParam = getFirstUrlParam (paramval);
229 | switch (firstParam) {
230 | case "code":
231 | getAccessToken (paramval.val, function (err, jsontext) {
232 | if (err) {
233 | alertDialog (err.message);
234 | }
235 | else {
236 | try {
237 | jstruct = JSON.parse (jsontext);
238 | }
239 | catch (err) {
240 | alertDialog (err.message);
241 | }
242 | for (var x in jstruct) {
243 | mastoMemory [x] = jstruct [x];
244 | }
245 | saveMastoMemory ();
246 | setTimeout (function () { //make absolutely sure the localStorage data is saved before we redirect
247 | window.location.href = stringNthField (window.location.href, "?", 1);
248 | }, 5)
249 | }
250 | });
251 | break;
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/server/mastoserver.js:
--------------------------------------------------------------------------------
1 | const myVersion = "0.4.0", myProductName = "mastoserver";
2 |
3 | const fs = require ("fs");
4 | const request = require ("request");
5 | const davehttp = require ("davehttp");
6 | const utils = require ("daveutils");
7 |
8 | var config = {
9 | apps: new Array (),
10 | httpPort: process.env.PORT || 1401,
11 | flLogToConsole: true,
12 | flAllowAccessFromAnywhere: true,
13 | flPostEnabled: false,
14 | blockedAddresses: []
15 | };
16 | const fnameConfig = "config.json";
17 | function readConfig (callback) {
18 | fs.readFile (fnameConfig, function (err, jsontext) {
19 | if (!err) {
20 | var jstruct;
21 | try {
22 | jstruct = JSON.parse (jsontext);
23 | for (var x in jstruct) {
24 | config [x] = jstruct [x];
25 | }
26 | }
27 | catch (err) {
28 | console.log ("readConfig: err.message == " + utils.jsonStringify (err.message));
29 | }
30 | }
31 | callback ();
32 | });
33 | }
34 |
35 | function httpRequest (url, method="GET", callback) {
36 | var theRequest = {
37 | method,
38 | url
39 | };
40 | request (theRequest, function (err, response, data) {
41 | if (err) {
42 | callback (err);
43 | }
44 | else {
45 | var code = response.statusCode;
46 | if ((code < 200) || (code > 299)) {
47 | const message = "The request returned a status code of " + response.statusCode + ".";
48 | callback ({message});
49 | }
50 | else {
51 | callback (undefined, data)
52 | }
53 | }
54 | });
55 | }
56 | function buildParamList (paramtable, flPrivate=false) { //8/4/21 by DW
57 | var s = "";
58 | if (paramtable === undefined) { //11/21/22 by DW
59 | paramtable = new Object ();
60 | }
61 | for (var x in paramtable) {
62 | if (paramtable [x] !== undefined) { //8/4/21 by DW
63 | if (s.length > 0) {
64 | s += "&";
65 | }
66 | s += x + "=" + encodeURIComponent (paramtable [x]);
67 | }
68 | }
69 | return (s);
70 | }
71 | function getAppInfo (clientKey, callback) {
72 | readConfig (function () {
73 | var info = undefined;
74 | config.apps.forEach (function (item) {
75 | if (item.clientKey == clientKey) {
76 | info = item;
77 | }
78 | });
79 | if (info === undefined) {
80 | const message = "Can't get info about the server because there is no server with the key.";
81 | callback ({message});
82 | }
83 | else {
84 | callback (undefined, info);
85 | }
86 | });
87 | }
88 | function getUrlForAuthorize (clientKey, urlRedirect, callback) {
89 | const path = "oauth/authorize";
90 | getAppInfo (clientKey, function (err, appInfo) {
91 | if (err) {
92 | console.log ("\ngetUrlForAuthorize: err.message == " + err.message + ", clientKey == " + clientKey + "\n");
93 | callback (err);
94 | }
95 | else {
96 | const params = {
97 | client_id: clientKey,
98 | redirect_uri: urlRedirect,
99 | response_type: "code",
100 | force_login: true
101 | };
102 | const paramlist = buildParamList (params, false) + "&scope=" + appInfo.scopes;
103 | const url = appInfo.urlMastodonServer + path + "?" + paramlist;
104 | console.log ("\ngetUrlForAuthorize: url == " + url + "\n");
105 | callback (undefined, url);
106 | }
107 | });
108 | }
109 | function getAccessToken (codeFromMasto, clientKey, callback) {
110 | const path = "oauth/token";
111 | getAppInfo (clientKey, function (err, appInfo) {
112 | if (err) {
113 | callback (err);
114 | }
115 | else {
116 | const params = {
117 | grant_type: "authorization_code",
118 | client_id: appInfo.clientKey,
119 | client_secret: appInfo.clientSecret,
120 | redirect_uri: appInfo.urlRedirect,
121 | code: codeFromMasto
122 | };
123 | const url = appInfo.urlMastodonServer + path + "?" + buildParamList (params, false) + "&scope=" + appInfo.scopes;
124 | console.log ("getAccessToken: url == " + url);
125 | httpRequest (url, "POST", function (err, jsontext) {
126 | if (err) {
127 | callback (err);
128 | }
129 | else {
130 | try {
131 | var jstruct = JSON.parse (jsontext);
132 | callback (undefined, jstruct);
133 | }
134 | catch (err) {
135 | callback (err);
136 | }
137 | }
138 | });
139 | }
140 | });
141 | }
142 |
143 | function mastocall (path, params, accessToken, clientKey, callback) {
144 | getAppInfo (clientKey, function (err, appInfo) {
145 | if (err) {
146 | callback (err);
147 | }
148 | else {
149 | var headers = undefined;
150 | if (accessToken !== undefined) {
151 | headers = {
152 | Authorization: "Bearer " + accessToken
153 | };
154 | }
155 | const theRequest = {
156 | url: appInfo.urlMastodonServer + path + "?" + buildParamList (params),
157 | method: "GET",
158 | headers,
159 | };
160 | request (theRequest, function (err, response, data) {
161 | function sendBackError (defaultMessage) {
162 | var flcalledback = false;
163 | if (data !== undefined) {
164 | try {
165 | jstruct = JSON.parse (data);
166 | if (jstruct.error !== undefined) {
167 | callback ({message: jstruct.error});
168 | flcalledback = true;
169 | }
170 | }
171 | catch (err) {
172 | }
173 | }
174 |
175 | if (!flcalledback) {
176 | callback ({message: defaultMessage});
177 | }
178 | }
179 | if (err) {
180 | sendBackError (err.message);
181 | }
182 | else {
183 | var code = response.statusCode;
184 | if ((code < 200) || (code > 299)) {
185 | const message = "The request returned a status code of " + response.statusCode + ".";
186 | sendBackError (message);
187 | }
188 | else {
189 | callback (undefined, data)
190 | }
191 | }
192 | });
193 | }
194 | });
195 | }
196 | function mastopost (path, params, accessToken, clientKey, filedata, callback) {
197 | getAppInfo (clientKey, function (err, appInfo) {
198 | if (err) {
199 | callback (err);
200 | }
201 | else {
202 | const theRequest = {
203 | url: appInfo.urlMastodonServer + path + "?" + buildParamList (params),
204 | method: "POST",
205 | headers: {
206 | Authorization: "Bearer " + accessToken
207 | },
208 | body: filedata
209 | };
210 | console.log ("mastopost: theRequest == " + utils.jsonStringify (theRequest));
211 | request (theRequest, function (err, response, data) {
212 | if (err) {
213 | console.log ("mastopost: err.message == " + err.message);
214 | callback (err);
215 | }
216 | else {
217 | var code = response.statusCode;
218 | console.log ("mastopost: response.statusCode == " + response.statusCode);
219 | if ((code < 200) || (code > 299)) {
220 | const message = "The request returned a status code of " + response.statusCode + ".";
221 | callback ({message});
222 | }
223 | else {
224 | callback (undefined, data)
225 | }
226 | }
227 | });
228 | }
229 | });
230 | }
231 |
232 | function tootStatus (accessToken, clientKey, statusText, inReplyTo, callback) {
233 | const params = {
234 | status: statusText,
235 | in_reply_to_id: inReplyTo
236 | };
237 | console.log ("tootStatus: statusText == " + statusText + ", inReplyTo == " + inReplyTo);
238 | mastopost ("api/v1/statuses", params, accessToken, clientKey, undefined, callback);
239 | }
240 | function getUserInfo (accessToken, clientKey, callback) {
241 | mastocall ("api/v1/accounts/verify_credentials", undefined, accessToken, clientKey, callback);
242 | }
243 |
244 |
245 | function handleHttpRequest (theRequest) {
246 | var params = theRequest.params;
247 | const token = params.oauth_token;
248 | const secret = params.oauth_token_secret;
249 | function returnPlainText (s) {
250 | theRequest.httpReturn (200, "text/plain", s.toString ());
251 | }
252 | function returnHtml (htmltext) {
253 | theRequest.httpReturn (200, "text/html", htmltext.toString ());
254 | }
255 | function returnData (jstruct) {
256 | if (jstruct === undefined) {
257 | jstruct = {};
258 | }
259 | theRequest.httpReturn (200, "application/json", utils.jsonStringify (jstruct));
260 | }
261 | function returnJsontext (jsontext) { //9/14/22 by DW
262 | theRequest.httpReturn (200, "application/json", jsontext.toString ());
263 | }
264 | function returnError (jstruct) {
265 | theRequest.httpReturn (500, "application/json", utils.jsonStringify (jstruct));
266 | }
267 | function returnNotFound () {
268 | theRequest.httpReturn (404, "text/plain", "Not found.");
269 | }
270 | function returnRedirect (url, code) {
271 | var headers = {
272 | location: url
273 | };
274 | if (code === undefined) {
275 | code = 302;
276 | }
277 | theRequest.httpReturn (code, "text/plain", code + " REDIRECT", headers);
278 | }
279 |
280 | function httpReturn (err, returnedValue) {
281 | if (err) {
282 | returnError (err);
283 | }
284 | else {
285 | if (typeof returnedValue == "object") {
286 | returnData (returnedValue);
287 | }
288 | else {
289 | returnJsontext (returnedValue); //9/14/22 by DW
290 | }
291 | }
292 | }
293 |
294 |
295 |
296 | switch (theRequest.method) {
297 | case "POST":
298 | switch (theRequest.lowerpath) {
299 | default:
300 | returnNotFound ();
301 | break;
302 | }
303 | case "GET":
304 | switch (theRequest.lowerpath) {
305 | case "/now":
306 | theRequest.httpReturn (200, "text/plain", new Date ());
307 | return;
308 | case "/connect":
309 | console.log ("handleHttpRequest: params.redirect_url == " + params.redirect_url);
310 | getUrlForAuthorize (params.client_key, params.redirect_url, function (err, url) {
311 | if (err) {
312 | returnError (err);
313 | }
314 | else {
315 | returnRedirect (url);
316 | }
317 | });
318 | return;
319 | case "/getaccesstoken":
320 | console.log ("\n/getaccesstoken: params.code == " + params.code + ", params.client_key == " + params.client_key);
321 | getAccessToken (params.code, params.client_key, httpReturn);
322 | return;
323 |
324 | case "/toot": //11/20/22 by DW
325 | console.log (utils.jsonStringify (params));
326 | tootStatus (params.access_token, params.client_key, params.status, params.inReplyTo, httpReturn);
327 | break;
328 | case "/getuserinfo":
329 | getUserInfo (params.access_token, params.client_key, httpReturn);
330 | break;
331 | default:
332 | returnNotFound ();
333 | break;
334 | }
335 | break;
336 | }
337 |
338 |
339 |
340 |
341 | }
342 |
343 | readConfig (function () {
344 | console.log ("config == " + utils.jsonStringify (config));
345 | const httpConfig = {
346 | port: config.httpPort,
347 | flLogToConsole: config.flLogToConsole,
348 | flAllowAccessFromAnywhere: config.flAllowAccessFromAnywhere,
349 | flPostEnabled: config.flPostEnabled,
350 | blockedAddresses: config.blockedAddresses
351 | };
352 | davehttp.start (httpConfig, handleHttpRequest);
353 | });
354 |
355 |
356 |
--------------------------------------------------------------------------------
/server/source.opml:
--------------------------------------------------------------------------------
1 |
2 |
3 |