krz/vox-populi
A Tumblr web client.
clone: git clone https://gitbay.org/krz/vox-populi.git
main: dashboard/index.php · raw
1<?php
2 require __DIR__ . '/../vendor/autoload.php';
3
4 session_start();
5
6 $consumer_key = getenv('CONSUMER_KEY');
7 $consumer_secret = getenv('CONSUMER_SECRET');
8 $client = new Tumblr\API\Client($consumer_key, $consumer_secret);
9 $requestHandler = $client->getRequestHandler();
10 $requestHandler->setBaseUrl('https://www.tumblr.com/');
11
12 // Check if the user has already authenticated
13 if(isset($_SESSION['perm_token']) && !empty($_SESSION['perm_token']) && isset($_SESSION['perm_secret']) && !empty($_SESSION['perm_secret'])) {
14 $token = $_SESSION['perm_token'];
15 $token_secret = $_SESSION['perm_secret'];
16 }
17 // Check if the user was here earlier by checking cookies
18 else if (isset($_COOKIE['perm_token']) && !empty($_COOKIE['perm_token']) && isset($_COOKIE['perm_secret']) && !empty($_COOKIE['perm_secret'])) {
19 $token = $_COOKIE['perm_token'];
20 $token_secret = $_COOKIE['perm_secret'];
21 }
22 // Check if this is the user's first visit
23 else if (!isset($_GET['oauth_verifier'])) {
24
25 // Grab the oauth token
26 $resp = $requestHandler->request('POST', 'oauth/request_token', array());
27 $out = $result = $resp->body;
28 $data = array();
29 parse_str($out, $data);
30
31 // Save temporary tokens to session
32 $_SESSION['tmp_token'] = $data['oauth_token'];
33 $_SESSION['tmp_secret'] = $data['oauth_token_secret'];
34
35 // Redirect user to Tumblr auth page
36 session_regenerate_id(true);
37 $header_url = 'https://www.tumblr.com/oauth/authorize?oauth_token=' . $data['oauth_token'];
38 header('Location: ' . $header_url);
39 die();
40
41 }
42 // Check if the user was just sent back from the Tumblr authentication site
43 else {
44
45 $verifier = $_GET['oauth_verifier'];
46
47 // Use the stored temporary tokens
48 $client->setToken($_SESSION['tmp_token'], $_SESSION['tmp_secret']);
49
50 // Access the permanent tokens
51 $resp = $requestHandler->request('POST', 'oauth/access_token', array('oauth_verifier' => $verifier));
52 $out = $result = $resp->body;
53 $data = array();
54 parse_str($out, $data);
55
56 // Set permanent tokens
57 $token = $data['oauth_token'];
58 $token_secret = $data['oauth_token_secret'];;
59 $_SESSION['perm_token'] = $data['oauth_token'];
60 $_SESSION['perm_secret'] = $data['oauth_token_secret'];
61
62 // Set cookies in case the user comes back later
63 setcookie("perm_token", $_SESSION['perm_token']);
64 setcookie("perm_secret", $_SESSION['perm_secret']);
65
66 // Redirect user to homepage for a clean URL
67 session_regenerate_id(true);
68 $header_url = 'https://example.com/vox-populi/';
69 header('Location: ' . $header_url);
70 die();
71
72 }
73
74 // Authenticate via OAuth
75 $client = new Tumblr\API\Client(
76 $consumer_key,
77 $consumer_secret,
78 $token,
79 $token_secret
80 );
81
82 // Set up a function to check if variables are blank later (this function accepts 0, 0.0, and "0" as valid)
83 function not_blank($value) {
84 return !empty($value) && isset($value) && $value !== '';
85 }
86
87 // Echo HTML contents
88 echo '<!doctype html><html lang="en"><head>
89 <meta charset="utf-8">
90 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
91 <meta http-equiv="x-ua-compatible" content="ie=edge">
92 <meta name="author" content="">
93 <meta name="description" content="Vox Populi is a web client for Tumblr, allowing you to access you personal Tumblr dashboard without ads.">
94 <link rel="apple-touch-icon" sizes="180x180" href="../assets/favicon/apple-touch-icon.png">
95 <link rel="icon" type="image/png" sizes="32x32" href="../assets/favicon/favicon-32x32.png">
96 <link rel="icon" type="image/png" sizes="16x16" href="../assets/favicon/favicon-16x16.png">
97 <link rel="manifest" href="../assets/favicon/site.webmanifest">
98 <link rel="stylesheet" href="../assets/css/bootstrap-4.5.2.min.css">
99 <link rel="stylesheet" href="../assets/css/app.css">
100 <title>Vox Populi - A Tumblr Web Client</title>
101 <style>
102 @font-face {
103 font-family: "IBM Plex Sans";
104 src: url("../assets/fonts/IBMPlexSans-Regular.eot");
105 src: url("../assets/fonts/IBMPlexSans-Regular.woff2") format("woff2"),
106 url("../assets/fonts/IBMPlexSans-Regular.woff") format("woff"),
107 url("../assets/fonts/IBMPlexSans-Regular.ttf") format("truetype");
108 }
109
110 @font-face {
111 font-family: "IBM Plex Sans";
112 src: url("../assets/fonts/IBMPlexSans-Bold.eot");
113 src: url("../assets/fonts/IBMPlexSans-Bold.woff2") format("woff2"),
114 url("../assets/fonts/IBMPlexSans-Bold.woff") format("woff"),
115 url("../assets/fonts/IBMPlexSans-Bold.ttf") format("truetype");
116 font-weight: bold;
117 }
118
119 @font-face {
120 font-family: "IBM Plex Mono";
121 src: url("../assets/fonts/IBMPlexMono-Regular.eot");
122 src: url("../assets/fonts/IBMPlexMono-Regular.woff2") format("woff2"),
123 url("../assets/fonts/IBMPlexMono-Regular.woff") format("woff"),
124 url("../assets/fonts/IBMPlexMono-Regular.ttf") format("truetype");
125 }
126
127 @font-face {
128 font-family: "IBM Plex Mono";
129 src: url("../assets/fonts/IBMPlexMono-Bold.eot");
130 src: url("../assets/fonts/IBMPlexMono-Bold.woff2") format("woff2"),
131 url("../assets/fonts/IBMPlexMono-Bold.woff") format("woff"),
132 url("../assets/fonts/IBMPlexMono-Bold.ttf") format("truetype");
133 font-weight: bold;
134 }
135 </style>
136 </head>
137
138 <body>
139 <div class="banner fixed-top" role="contentinfo">
140 <p class="m-0 mr-2">Black Lives Matter.</p>
141 <a href="https://developer.ibm.com/callforcode/racial-justice/" target="_blank" rel="noopener">Support the Call for Code®</a>
142 </div>
143 <nav class="navbar navbar-expand-lg navbar-dark fixed-top">
144 <li class="nav-item d-block d-md-none">
145 <a id="sidebarButton" class="nav-link" href="javascript:void(0)"><svg id="icon-side-menu"
146 xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32" aria-hidden="true">
147 <path
148 d="M14 4H18V8H14zM4 4H8V8H4zM24 4H28V8H24zM14 14H18V18H14zM4 14H8V18H4zM24 14H28V18H24zM14 24H18V28H14zM4 24H8V28H4zM24 24H28V28H24z">
149 </path>
150 </svg></a>
151 </li>
152 <a class="navbar-brand" href="./">Vox Populi</a>
153
154 <ul class="navbar-nav mr-0 ml-auto flex-row">
155 <li class="nav-item">
156 <a id="search-button" class="nav-link" href="javascript:void(0)"><svg id="icon-search"
157 xmlns="http://www.w3.org/2000/svg" description="Open search" width="20" height="20"
158 viewBox="0 0 32 32" aria-hidden="true">
159 <path d="M30,28.59,22.45,21A11,11,0,1,0,21,22.45L28.59,30ZM5,14a9,9,0,1,1,9,9A9,9,0,0,1,5,14Z">
160 </path>
161 </svg></a>
162 </li>
163 </ul>
164 <form id="search-form" class="form-inline h-100 d-none flex-fill" method="get"
165 action="../search.php">
166 <input class="form-control h-100 ml-auto" type="search" placeholder="Search" aria-label="Search"
167 name="query">
168 <a id="search-close-button" class="nav-link" href="javascript:void(0)">
169 <svg id="icon-search" xmlns="http://www.w3.org/2000/svg" description="Clear search" width="20"
170 height="20" viewBox="0 0 32 32" aria-hidden="true">
171 <path
172 d="M24 9.4L22.6 8 16 14.6 9.4 8 8 9.4 14.6 16 8 22.6 9.4 24 16 17.4 22.6 24 24 22.6 17.4 16 24 9.4z">
173 </path>
174 </svg>
175 </a>
176 </form>
177 </nav>
178
179 <div class="container-fluid">
180 <div class="row">
181 <div id="sidebar" class="sidebar d-none d-md-block col-md-3 col-lg-2 p-0">
182 <div class="sidebar-box p-0">
183 <div>
184 <ul class="actions">
185 <li><p>Account</p></li>
186 <hr class="sidebar-divider">
187 <li><a href="../dashboard/">Dashboard</a></li>
188 <li><a href="../profile/">Profile</a></li>
189 <li><a href="../logout.php">Logout</a></li>
190 </ul>
191 <ul class="categories">
192 <li><p>Filter</p></li>
193 <hr class="sidebar-divider">
194 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == '') ? 'active' : ""); echo '" href="./">All Posts</a></li>
195 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == 'answer') ? 'active' : ""); echo '" href="./?type=answer">Answer</a></li>
196 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == 'audio') ? 'active' : ""); echo '" href="./?type=audio">Audio</a></li>
197 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == 'chat') ? 'active' : ""); echo '" href="./?type=chat">Chat</a></li>
198 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == 'link') ? 'active' : ""); echo '" href="./?type=link">Link</a></li>
199 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == 'photo') ? 'active' : ""); echo '" href="./?type=photo">Photo</a></li>
200 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == 'quote') ? 'active' : ""); echo '" href="./?type=quote">Quote</a></li>
201 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == 'text') ? 'active' : ""); echo '" href="./?type=text">Text</a></li>
202 <li><a class="'; echo ((isset($_GET['type']) && $_GET['type'] == 'video') ? 'active' : ""); echo '" href="./?type=video">Video</a></li>
203 </ul>
204 </div>
205 <div>
206 <hr class="sidebar-divider">
207 <ul class="meta">
208 <li>
209 <a href="#" target="_blank">Source Code <svg
210 id="icon-external-link" xmlns="http://www.w3.org/2000/svg" width="16"
211 height="16" viewBox="0 0 16 16" aria-hidden="true">
212 <path
213 d="M13,14H3c-0.6,0-1-0.4-1-1V3c0-0.6,0.4-1,1-1h5v1H3v10h10V8h1v5C14,13.6,13.6,14,13,14z">
214 </path>
215 <path d="M10 1L10 2 13.3 2 9 6.3 9.7 7 14 2.7 14 6 15 6 15 1z"></path>
216 </svg>
217 </a>
218 </li>
219 <li>
220 <a href="https://example.com" target="_blank">© 0000</a>
221 </li>
222 </ul>
223 </div>
224 </div>
225 </div>
226 <div id="main" class="main col-xs-12 col-md-9 col-lg-10 ml-auto">
227 <span class="homepage--dots"></span>';
228
229 // Get the user's blog name for welcome message
230 if ($client->getUserInfo()) {
231 $client->getUserInfo();
232 } else {
233 // Echo rate-limit error message
234 $rate_error = '<div id="rate-limit-exceeded" class="alert alert-danger my-5"><b>[429] Error</b>: Tumblr rate limit exceeded. Please visit again tomorrow.</div>';
235 echo $rate_error;
236 die();
237 }
238 foreach ($client->getUserInfo()->user->blogs as $blog) {
239 echo '<h1 class="text-center py-4">Welcome, <a href="https://' . $blog->name . '.tumblr.com">' . $blog->name . '</a>!</h1>';
240 }
241
242 // Create function to allow a client to get 20 posts per page
243 function get_dashboard_posts($client, $post_start, $limit, $post_type) {
244 if ($post_type != NULL) {
245 $dashboard_posts = $client->getDashboardPosts(array('limit' => $limit, 'offset' => $post_start, 'reblog_info' => true, 'type' => $post_type));
246 } else {
247 $dashboard_posts = $client->getDashboardPosts(array('limit' => $limit, 'offset' => $post_start, 'reblog_info' => true));
248 }
249 $card_columns = '<div class="card-columns">';
250 foreach ($dashboard_posts->posts as $post) {
251 $card_columns .= '<div class="card" data-type="' . $post->type . '" data-id="' . $post->id_string . '">';
252 $card_columns .= '<div class="card-header d-flex justify-content-between"><div class="card-header-blog"><a href="' . $post->blog->url . '" target="_blank"><img class="avatar" src="' . $client->getBlogAvatar($post->blog_name, 32) . '"></a>';
253 $card_columns .= '<a href="' . $post->blog->url . '">' . $post->blog_name . '</a>';
254 if (not_blank($post->reblogged_from_name)) {
255 $card_columns .= '<i data-feather="repeat"></i><a href="' . $post->reblogged_from_url . '">' . $post->reblogged_from_name . '</a>';
256 if (!$post->reblogged_from_following) {
257 $card_columns .= '<a href="javascript:void(0);" onclick="follow(\'' . $post->reblogged_from_name . '\', ' . $post->reblogged_from_uuid . ');" data-follow-id="' . $post->reblogged_from_uuid . '"><span class="badge badge-secondary ml-2">Follow</span></a>';
258 }
259 }
260 $card_columns .= '</div>';
261
262 // Add 'follow user' button if not following blog
263 if ($post->followed) {
264 $card_columns .= '</div>';
265 } else {
266 $card_columns .= '<a href="javascript:void(0);" onclick="follow(\'' . $post->blog_name . '\', ' . $post->blog->uuid . ');" title="Follow" data-id="' . $post->blog->uuid . '"><i data-feather="user-plus"></i></a></div>';
267 }
268
269 // Add root blog (original poster)
270 if ($post->reblogged_root_following) {
271 $card_columns .= '<div class="card-header d-flex justify-content-between"><div class="card-header-blog">';
272 $card_columns .= '<a href="' . $post->reblogged_root_url . '" target="_blank"><img class="avatar" src="' . $client->getBlogAvatar($post->reblogged_root_name, 32) . '"></a>';
273 $card_columns .= '<a href="' . $post->reblogged_root_url . '">' . $post->reblogged_root_name . '</a></div>';
274 $card_columns .= '</div>';
275 } else {
276 $card_columns .= '<div class="card-header d-flex justify-content-between"><div class="card-header-blog">';
277 $card_columns .= '<a href="' . $post->reblogged_root_url . '" target="_blank"><img class="avatar" src="' . $client->getBlogAvatar($post->reblogged_root_name, 32) . '"></a>';
278 $card_columns .= '<a href="' . $post->reblogged_root_url . '">' . $post->reblogged_root_name . '</a></div>';
279 if (strpos($post->reblogged_root_name, 'deactivated') == false) {
280 $card_columns .= '<a href="javascript:void(0);" onclick="follow(\'' . $post->reblogged_root_name . '\');" title="Follow" data-id="' . $post->reblogged_root_uuid . '"><i data-feather="user-plus"></i></a>';
281 }
282 $card_columns .= '</div>';
283 }
284
285 if ($post->type == 'photo') {
286 $card_columns .= '<a href="' . $post->photos[0]->original_size->url . '"><img src="' . $post->photos[0]->original_size->url . '" class="card-img" alt="..."></a>';
287 }
288 if ($post->type == 'video') {
289 $card_columns .= '<video controls poster="'.$post->thumbnail_url.'" class="card-img"><source src="'.$post->video_url.'"></source></video>';
290 }
291 $card_columns .= '<div class="card-body">';
292 if ($post->type == 'photo') {
293 if (not_blank($post->caption)) {
294 $card_columns .= '<p class="card-text post-caption">' . $post->caption . '</p>';
295 }
296 }
297 if ($post->type == 'text') {
298 if (not_blank($post->title)) {
299 $card_columns .= '<h5 class="card-title post-title">' . $post->title . '</h5>';
300 }
301 if (not_blank($post->body)) {
302 $card_columns .= '<p class="card-text post-body">' . $post->body . '</p>';
303 }
304 }
305 if ($post->type == 'answer') {
306 $card_columns .= '<p class="card-text"><b>' . $post->asking_name . '</b>: ' . $post->question . '</p>';
307 $card_columns .= '<hr>';
308 $card_columns .= $post->answer;
309 }
310 if ($post->type == 'quote') {
311 $card_columns .= '<blockquote class="card-text post-quote">' . $post->summary;
312 $card_columns .= '<br><br><cite>' . $post->reblog->comment . '</cite></blockquote>';
313 }
314 if ($post->type == 'chat') {
315 for ($i = 0; $i <= count($post->dialogue); $i++) {
316 $card_columns .= '<blockquote class="card-text post-chat-' . $i . '">' . (not_blank($post->dialogue[$i]->name) ? ('<b>' . $post->dialogue[$i]->name . '</b>: ') : "") . $post->dialogue[$i]->phrase . '</blockquote>';
317 }
318 }
319 /* This seems to just post a duplicate of a QA answer
320 if (isset($post->reblog->comment) && $post->reblog->comment !== '') {
321 $card_columns .= '<p class="card-text reblog-comment">' . $post->reblog->comment . '</p>';
322 }
323 */
324 // $card_columns .= '<a href="' . $post->post_url . '" class="card-link">Visit Post →</a>';
325 $card_columns .= '<div class="card-footer d-flex justify-content-between align-items-center p-0"><div class="note-count">' . number_format($post->note_count, 0) . ' notes</div>';
326 $card_columns .= '<div class="post-icons"><a href="' . $post->post_url . '" target="_blank" title="View on Tumblr"><i data-feather="external-link"></i></a>';
327 $card_columns .= '<a disabled href="#" title="Share"><i data-feather="send"></i></a>';
328 $card_columns .= '<a disabled href="#" title="Comment"><i data-feather="message-square"></i></a>';
329
330 // Set up actions to reblog/unreblog post
331 if ($post->liked != true) {
332 // Reblog this post
333 $card_columns .= '<a href="javascript:void(0);" onclick="reblog(\'' . urlencode($post->blog_name) . '\', \'' . urlencode($post->id) . '\', \'' . urlencode($post->reblog_key) . '\');" title="Reblog" data-reblog-id="' . $post->id . '"><i data-feather="repeat"></i></a>';
334 } else {
335 // Unreblog this post
336 $card_columns .= '<a href="javascript:void(0);" onclick="unblog(\'' . urlencode($post->blog_name) . '\', \'' . urlencode($post->id) . '\', \'' . urlencode($post->reblog_key) . '\');" title="Unblog" data-unreblog-id="' . $post->id . '"><i data-feather="repeat"></i></a>';
337 }
338
339 // Set up actions to like/unlike post
340 if ($post->liked != true) {
341 // Like this post
342 $card_columns .= '<a href="javascript:void(0);" onclick="like(\'' . urlencode($post->id) . '\', \'' . urlencode($post->reblog_key) . '\');" title="Like" data-like-id="' . $post->id . '"><i data-feather="heart"></i></a></div></div>';
343 } else {
344 // Unlike this post
345 $card_columns .= '<a href="javascript:void(0);" onclick="unlike(\'' . urlencode($post->id) . '\', \'' . urlencode($post->reblog_key) . '\');" title="Unlike" data-unlike-id="' . $post->id . '"><i data-feather="heart"></i></a></div></div>';
346 }
347
348 $card_columns .= '</div></div>';
349 }
350 $card_columns .= '</div>';
351 return $card_columns;
352 }
353
354 // Create a loop to call as many dashboard posts as you want (results are returned in sets of 20 per API rules)
355 // Can specify post type: text, chat, link, photo, audio, video, NULL
356 if (isset($_GET['type'])) {
357 $post_type = $_GET['type'];
358 } else {
359 $post_type = NULL;
360 }
361 if (isset($_GET['page'])) {
362 $page = $_GET['page'];
363 } else {
364 $page = 1;
365 }
366 $post_start = (($page - 1) * 20) + 1;
367 $limit = 20;
368 echo get_dashboard_posts($client, $post_start, $limit, $post_type);
369
370 // Echo HTML page navigation
371 // Embedded PHP tags here are calculating page numbers for URL parameters
372 echo '<nav aria-label="Page navigation">
373 <ul class="pagination justify-content-center">
374 <li class="page-item '; echo ($page <= 1 ? "disabled" : ""); echo'">
375 <a class="page-link" href="./?page='; echo ($page-1); echo (isset($_GET['type']) ? "&type=" . $_GET['type'] : ""); echo '" aria-label="Previous">
376 <span aria-hidden="true">«</span>
377 </a>
378 </li>
379 <li class="page-item '; echo ($page <= 1 ? "disabled" : ""); echo '"><a class="page-link"
380 href="./?page='; echo ($page-1); echo (isset($_GET['type']) ? "&type=" . $_GET['type'] : ""); echo '">'; echo ($page-1); echo '</a></li>
381 <li class="page-item active"><a class="page-link" href="#">'; echo $page; echo '</a></li>
382 <li class="page-item"><a class="page-link"
383 href="./?page='; echo ($page+1); echo (isset($_GET['type']) ? "&type=" . $_GET['type'] : ""); echo '">'; echo ($page+1); echo '</a></li>
384 <li class="page-item">
385 <a class="page-link" href="./?page='; echo ($page+1); echo (isset($_GET['type']) ? "&type=" . $_GET['type'] : ""); echo'" aria-label="Next">
386 <span aria-hidden="true">»</span>
387 </a>
388 </li>
389 </ul>
390 </nav>
391 </div>
392
393 </div>
394 </div>
395 <button class="btn-to-top" type="button" aria-label="Back to Top">
396 <svg id="icon-to-top" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32"
397 aria-hidden="true">
398 <path d="M16 14L6 24 7.4 25.4 16 16.8 24.6 25.4 26 24zM4 8H28V10H4z"></path>
399 </svg>
400 </button>
401
402 <!-- JavaScript -->
403 <script src="../assets/js/feather-icons.4.2.8.min.js"></script>
404 <script src="../assets/js/jquery-3.5.1.min.js"></script>
405 <script src="../assets/js/bootstrap.bundle.min.js"></script>
406 <script src="../assets/js/app.js"></script>
407</body></html>';
408?>