From: Dan Brown Date: Wed, 24 Jul 2024 15:42:00 +0000 (+0100) Subject: Added php-generate-tree script X-Git-Url: http://source.bookstackapp.com/api-scripts/commitdiff_plain/28f1407c82b92d297ac6f322b296bdde51cf97b3?hp=8e94a530b0dfb5caebab8b410a893f960f79e089 Added php-generate-tree script --- diff --git a/php-generate-tree/example.txt b/php-generate-tree/example.txt new file mode 100644 index 0000000..aabf7ac --- /dev/null +++ b/php-generate-tree/example.txt @@ -0,0 +1,9 @@ +├── BOOKSHELF 1: My wonderful shelf of notes +│ └── BOOK 39: My lovely book in my notes +│ ├── PAGE 2745: A page within the book +│ ├── CHAPTER 643: A lone chapter +│ └── CHAPTER 644: My chapter with page +│ └── PAGE 47830: My new great page +├── BOOK 239: Scratch notes +│ ├── PAGE 47870: Note A +│ └── PAGE 47872: Note B diff --git a/php-generate-tree/generate-tree.php b/php-generate-tree/generate-tree.php new file mode 100755 index 0000000..76dc316 --- /dev/null +++ b/php-generate-tree/generate-tree.php @@ -0,0 +1,189 @@ +#!/usr/bin/env php + $shelf) { + $shelvesById[$id]['books'] = getBooksForShelf($id); + usleep($apiPauseMicrosecs); +} + +// For each book, fetch its contents list +foreach ($booksById as $id => $book) { + $booksById[$id]['contents'] = apiGetJson("api/books/{$id}")['contents'] ?? []; + usleep($apiPauseMicrosecs); +} + +// Cycle through the shelves and display their contents +$isBookShownById = []; +foreach ($shelvesById as $id => $shelf) { + output($shelf, 'bookshelf', [false]); + $bookCount = count($shelf['books']); + for ($i=0; $i < $bookCount; $i++) { + $bookId = $shelf['books'][$i]; + $book = $booksById[$bookId] ?? null; + if ($book) { + outputBookAndContents($book, [false, $i === $bookCount - 1]); + $isBookShownById[strval($book['id'])] = true; + } + } +} + +// Cycle through books and display any that have not been +// part of a shelve's output +foreach ($booksById as $id => $book) { + if (isset($isBookShownById[$id])) { + continue; + } + + outputBookAndContents($book, [false]); +} + +/** + * Output a book for display, along with its contents. + */ +function outputBookAndContents(array $book, array $depthPath): void +{ + output($book, 'book', $depthPath); + $childCount = count($book['contents']); + for ($i=0; $i < $childCount; $i++) { + $child = $book['contents'][$i]; + $childPath = array_merge($depthPath, [($i === $childCount - 1)]); + output($child, $child['type'], $childPath); + $pages = $child['pages'] ?? []; + $pageCount = count($pages); + for ($j=0; $j < count($pages); $j++) { + $page = $pages[$j]; + $innerPath = array_merge($childPath, [($j === $pageCount - 1)]); + output($page, 'page', $innerPath); + } + } +} + +/** + * Output a single item for display. + */ +function output(array $item, string $type, array $depthPath): void +{ + $upperType = strtoupper($type); + $prefix = ''; + $depth = count($depthPath); + for ($i=0; $i < $depth; $i++) { + $isLastAtDepth = $depthPath[$i]; + $end = ($i === $depth - 1); + if ($end) { + $prefix .= $isLastAtDepth ? '└' : '├'; + } else { + $prefix .= $isLastAtDepth ? ' ' : '│ '; + } + } + echo $prefix . "── {$upperType} {$item['id']}: {$item['name']}\n"; +} + +/** + * Key an array of array-based data objects by 'id' value. + */ +function keyById(array $data): array +{ + $byId = []; + foreach ($data as $item) { + $id = $item['id']; + $byId[$id] = $item; + } + return $byId; +} + +/** + * Get the books for the given shelf ID. + * Returns an array of the book IDs. + */ +function getBooksForShelf(int $shelfId): array +{ + $resp = apiGetJson("api/shelves/{$shelfId}"); + return array_map(function ($bookData) { + return $bookData['id']; + }, $resp['books'] ?? []); +} + +/** + * Consume all items from the given API listing endpoint. + */ +function getAllOfAtListEndpoint(string $endpoint, array $params): array +{ + global $apiPauseMicrosecs; + $count = 100; + $offset = 0; + $all = []; + + do { + $endpoint = $endpoint . '?' . http_build_query(array_merge($params, ['count' => $count, 'offset' => $offset])); + $resp = apiGetJson($endpoint); + + $total = $resp['total'] ?? 0; + $new = $resp['data'] ?? []; + array_push($all, ...$new); + $offset += $count; + usleep($apiPauseMicrosecs); + } while ($offset < $total); + + return $all; +} + +/** + * Make a simple GET HTTP request to the API. + */ +function apiGet(string $endpoint): string +{ + global $baseUrl, $clientId, $clientSecret; + $url = rtrim($baseUrl, '/') . '/' . ltrim($endpoint, '/'); + $opts = ['http' => ['header' => "Authorization: Token {$clientId}:{$clientSecret}"]]; + $context = stream_context_create($opts); + return @file_get_contents($url, false, $context); +} + +/** + * Make a simple GET HTTP request to the API & + * decode the JSON response to an array. + */ +function apiGetJson(string $endpoint): array +{ + $data = apiGet($endpoint); + $array = json_decode($data, true); + + if (!is_array($array)) { + dd("Failed request to {$endpoint}", $data); + } + + return $array; +} + +/** + * DEBUG: Dump out the given variables and exit. + */ +function dd(...$args) +{ + foreach ($args as $arg) { + var_dump($arg); + } + exit(1); +} \ No newline at end of file diff --git a/php-generate-tree/readme.md b/php-generate-tree/readme.md new file mode 100644 index 0000000..a054297 --- /dev/null +++ b/php-generate-tree/readme.md @@ -0,0 +1,47 @@ +# Generate Tree + +This script will scan through all pages, chapters books and shelves via the API to generate a big tree structure list in plaintext. + +**This is a very simplistic single-script-file example of using the endpoints API together** +, it is not a fully-featured & validated script, it error handling is very limited. + +Keep in mind, The tree generated will reflect content visible to the API user used when running the script. + +This script follows a `((Shelves > Books > (Chapters > Pages | Pages)) | Books)` structure so books and their contents may be repeated if on multiple shelves. Books not on any shelves will be shown at the end. + +## Requirements + +You will need php (~8.1+) installed on the machine you want to run this script on. +You will also need BookStack API credentials (TOKEN_ID & TOKEN_SECRET) at the ready. + +## Running + +```bash +# Downloading the script +# ALTERNATIVELY: Clone the project from GitHub and run locally. +curl https://raw.githubusercontent.com/BookStackApp/api-scripts/main/php-generate-tree/generate-tree.php > generate-tree.php + +# Setup +# ALTERNATIVELY: Open the script and edit the variables at the top. +export BS_URL=https://bookstack.example.com # Set to be your BookStack base URL +export BS_TOKEN_ID=abc123 # Set to be your API token_id +export BS_TOKEN_SECRET=123abc # Set to be your API token_secret + +# Running the script +php generate-tree.php +``` + +## Examples + +```bash +# Generate out the tree to the command line +php generate-tree.php + +# Generate & redirect output to a file +php generate-tree.php > bookstack-tree.txt + +# Generate with the output shown on the command line and write to a file +php generate-tree.php | tee bookstack-tree.txt +``` + +An example of the output can be seen in the [example.txt](./example.txt) file within the directory of this readme. \ No newline at end of file