├── .gitignore ├── .gitmodules ├── ADMIN_ADVANCED_USAGE.md ├── ADMIN_GUIDE.md ├── CMakeLists.txt ├── COMPILING.md ├── CONTRIBUTING.md ├── DESIGN_DECISIONS.md ├── Dockerfile ├── Dockerfile.win64 ├── FRONTEND.md ├── INSTALL.md ├── LICENSE ├── README.md ├── STATS.md ├── USER_GUIDE.md ├── caffe ├── CMakeLists.txt └── src │ └── gen-img-db.cpp ├── commands.sql ├── egs.md ├── example.cfg ├── ffmpegthumbnailer-static.patch ├── scripts ├── .gitignore ├── record-media ├── record-reddit-post └── tagem-auth ├── server ├── .gitignore ├── CMakeLists.txt ├── client │ └── userscripts │ │ └── reddit.js └── src │ ├── .gitignore │ ├── curl_utils.cpp │ ├── curl_utils.hpp │ ├── db_info.cpp │ ├── db_info.hpp │ ├── errors.hpp │ ├── fix-missing-symbol.monkeypatch.cpp │ ├── fn_successness.hpp │ ├── get_cookies.hpp │ ├── handler_buf_pool.hpp │ ├── help.txt │ ├── html │ ├── components │ │ ├── change-file-to-backup.html │ │ └── tbl.html │ ├── credits.html │ ├── examples.html │ ├── filter-syntax.html │ ├── keyboard-shortcuts.html │ ├── profile.html │ └── svg │ │ ├── .gitignore │ │ ├── mine.txt │ │ └── tabler-icons.txt │ ├── info_extractor-domainid.txt │ ├── info_extractor-verify.txt │ ├── info_extractor.hpp │ ├── initialise_tagem_db.cpp │ ├── initialise_tagem_db.hpp │ ├── log.hpp │ ├── mimetype.hpp │ ├── nullable_string_view.hpp │ ├── os.cpp │ ├── os.hpp │ ├── python.hpp │ ├── python_stuff.hpp │ ├── qry-cli.cpp │ ├── qry-get_attribute_name-tokens.txt │ ├── qry-help-text.txt │ ├── qry-process_arg-tokens.txt │ ├── qry.cpp │ ├── qry.hpp │ ├── server-endpoints.txt │ ├── server.cpp │ ├── skip_to_body.hpp │ ├── static │ ├── css │ │ ├── .gitignore │ │ ├── profile.css │ │ ├── table_as_blocks.css │ │ └── table_as_table.css │ └── js │ │ ├── .gitignore │ │ ├── add_to_db.js │ │ ├── admin.js │ │ ├── ajax.js │ │ ├── alert.js │ │ ├── box.js │ │ ├── colour-picker.js │ │ ├── cookies.js │ │ ├── description.js │ │ ├── devices.js │ │ ├── dirs.js │ │ ├── document.on-ready.js │ │ ├── era.js │ │ ├── error.js │ │ ├── escape-html.js │ │ ├── external_dbs.js │ │ ├── file.js │ │ ├── files.js │ │ ├── get-selected-ids.js │ │ ├── globals.js │ │ ├── hidden.js │ │ ├── history.js │ │ ├── humanise.js │ │ ├── init.js │ │ ├── login.js │ │ ├── nicknames.js │ │ ├── paste-txt.js │ │ ├── playlist.js │ │ ├── popup.js │ │ ├── profile-name.js │ │ ├── prompt.js │ │ ├── qry.js │ │ ├── regexp.js │ │ ├── select3.js │ │ ├── set-profile-meta.js │ │ ├── split.js │ │ ├── state.js │ │ ├── storage.js │ │ ├── stylesheet-switching.js │ │ ├── svg.js │ │ ├── tags.js │ │ ├── tasks.js │ │ ├── tbls.js │ │ ├── text-editor.js │ │ ├── thumbs.js │ │ ├── time.js │ │ ├── upload.js │ │ ├── user-settings.js │ │ ├── utils.js │ │ └── view-device.js │ ├── str_utils.hpp │ ├── test.hpp │ ├── thread_pool.hpp │ ├── thumbnailer.hpp │ ├── user_agent.hpp │ ├── user_auth.hpp │ ├── utils.test.js.hpp │ └── verify_str.hpp ├── tagemConfig.cmake.in ├── tagemConfigVersion.cmake.in └── utils ├── .gitignore ├── CMakeLists.txt ├── Makefile └── src ├── extract-audio.hpp ├── ffmpeg-stuff.hpp ├── hash.cpp ├── init.cpp ├── init.sql ├── init_data.sql ├── init_user_admin.sql ├── init_user_guest.sql ├── init_user_invalid.sql ├── procedures.sql ├── triggers.sql └── view-instances.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | build-mxe/* 3 | build-dbg/* 4 | etc/* 5 | *.kate-swp 6 | *.pro.user 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wangle-server/scripts"] 2 | path = server/scripts 3 | url = https://github.com/NotCompsky/wangler 4 | [submodule "wangle-server/src/sprexer"] 5 | path = server/src/sprexer 6 | url = https://github.com/NotCompsky/sprexer 7 | -------------------------------------------------------------------------------- /ADMIN_ADVANCED_USAGE.md: -------------------------------------------------------------------------------- 1 | # Advanced Options 2 | 3 | ## Server Command Line Options 4 | 5 | ### Viewing Contents from Web Pages 6 | 7 | The command line arguments are `d /path/to/view-remote-dir`. Included is [an example for Pinterest](scripts/remote-dir-viewers/pinterest.com) - I think you may need to supply your own cookies, however. 8 | 9 | This command line argument will enable web users to request the site via `Filesystem Dir` - they can type `pinterest [QUERY]`, and the script will place the pins from that query into the file page, as though they were files on the local filesystem. 10 | 11 | # Advanced Configuration 12 | 13 | ## Multiple Servers on the Same Database with Different Permissions 14 | 15 | Simply rename all the core tables: 16 | 17 | RENAME TABLE tag TO _tag; 18 | RENAME TABLE era TO _era; 19 | RENAME TABLE file TO _file; 20 | RENAME TABLE dir TO _dir; 21 | RENAME TABLE device TO _device; 22 | 23 | and create security views (that emulate row-level security) (based on https://mariadb.com/resources/blog/protect-your-data-row-level-security-in-mariadb-10-0/): 24 | 25 | CREATE TABLE IF NOT EXISTS permission ( 26 | id BIGINT UNSIGNED NOT NULL PRIMARY KEY, 27 | name VARCHAR(32) NOT NULL UNIQUE KEY 28 | ); 29 | CREATE TABLE IF NOT EXISTS user2permissions ( 30 | user VARBINARY(32) NOT NULL PRIMARY KEY, 31 | permissions BIGINT UNSIGNED NOT NULL 32 | ); 33 | CREATE SQL SECURITY DEFINER 34 | VIEW tag 35 | AS 36 | SELECT * 37 | FROM _tag 38 | WHERE id NOT IN ( 39 | SELECT t2pt.tag 40 | FROM user u 41 | JOIN user2blacklist_tag u2ht ON u2ht.user=u.id 42 | JOIN tag2parent_tree t2pt ON t2pt.parent=u2ht.tag 43 | WHERE u.name=SESSION_USER() 44 | ) 45 | ; 46 | CREATE SQL SECURITY DEFINER 47 | VIEW file 48 | AS 49 | SELECT * 50 | FROM _file 51 | WHERE id NOT IN ( 52 | SELECT f2t.file 53 | FROM user u 54 | JOIN user2blacklist_tag u2ht ON u2ht.user=u.id 55 | JOIN tag2parent_tree t2pt ON t2pt.parent=u2ht.tag 56 | JOIN file2tag f2t ON f2t.tag=t2pt.tag 57 | WHERE u.name=SESSION_USER() 58 | ) 59 | ; 60 | CREATE SQL SECURITY DEFINER 61 | VIEW dir 62 | AS 63 | SELECT d.* 64 | FROM _dir d 65 | JOIN user2permissions u2p ON u2p.user=SESSION_USER() 66 | WHERE u2p.permissions & d.permissions = d.permissions 67 | ; 68 | CREATE SQL SECURITY DEFINER 69 | VIEW device 70 | AS 71 | SELECT d.* 72 | FROM _device d 73 | JOIN user2permissions u2p ON u2p.user=SESSION_USER() 74 | WHERE u2p.permissions & d.permissions = d.permissions 75 | ; 76 | 77 | The client servers should be given permission to access these views, and any permissions to the underlying tables (now prefixed with underscores) revoked. 78 | -------------------------------------------------------------------------------- /ADMIN_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Admin Tasks 2 | 3 | ![admin-dashboard1](https://user-images.githubusercontent.com/30552567/88488184-97a41600-cf83-11ea-8d80-d02abf8aab22.jpg) 4 | ![admin-dashboard2](https://user-images.githubusercontent.com/30552567/88488514-8956f980-cf85-11ea-879e-6be51b9452eb.jpg) 5 | ![admin-dashboard3](https://user-images.githubusercontent.com/30552567/88495546-26c82280-cfb2-11ea-9daa-3e07439c5974.jpg) 6 | 7 | # General Advice 8 | 9 | It is advised not to remove devices that were present when the server started; only because the server for some operations will assume that those devices are accessible. It is absolutely not dangerous to remove the devices, it will only waste the server's time and processing power somewhat. 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7.0 FATAL_ERROR) # CONTRIBUTIONS WELCOME: Tests of previous/future versions which work or not 2 | 3 | if( EXISTS "${CMAKE_BINARY_DIR}/CMakeLists.txt") 4 | message(FATAL_ERROR "Refusing to run in-source build.") 5 | endif() 6 | 7 | project(tagem CXX) # WARNING: Sets some important variables about the plarform. Don't call find_package before setting a project name. 8 | 9 | find_package(Compsky REQUIRED) 10 | include_directories(${COMPSKY_INCLUDE_DIRS}) 11 | 12 | set(TAGEM_MAJOR_VERSION 0) 13 | set(TAGEM_MINOR_VERSION 1) 14 | set(TAGEM_PATCH_VERSION 0) 15 | set(TAGEM_VERSION ${TAGEM_MAJOR_VERSION}.${TAGEM_MINOR_VERSION}.${TAGEM_PATCH_VERSION}) 16 | 17 | 18 | 19 | 20 | # BEGIN src: https://gitlab.kitware.com/cmake/community/wikis/doc/tutorials/How-to-create-a-ProjectConfig.cmake-file 21 | 22 | if(WIN32 AND NOT CYGWIN) 23 | set(DEF_INSTALL_CMAKE_DIR CMake) 24 | else() 25 | set(DEF_INSTALL_CMAKE_DIR lib/CMake/tagem) 26 | endif() 27 | set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for CMake files") 28 | 29 | # Make relative paths absolute (needed later on) 30 | foreach(p LIB BIN INCLUDE CMAKE) 31 | set(var INSTALL_${p}_DIR) 32 | if(NOT IS_ABSOLUTE "${${var}}") 33 | set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}") 34 | endif() 35 | endforeach() 36 | 37 | # set up include-directories 38 | include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include" "${PROJECT_SOURCE_DIR}") 39 | 40 | if(EXISTS utils) 41 | add_subdirectory(utils) 42 | endif() 43 | if(EXISTS caffe) 44 | add_subdirectory(caffe) 45 | endif() 46 | 47 | # END src: https://gitlab.kitware.com/cmake/community/wikis/doc/tutorials/How-to-create-a-ProjectConfig.cmake-file 48 | -------------------------------------------------------------------------------- /COMPILING.md: -------------------------------------------------------------------------------- 1 | ## Docker 2 | 3 | ### Single Binary Build 4 | 5 | This build uses a custom docker image where the entire dependency chain is in static libraries, rather than dynamically linked. The resulting binary can run on any Linux platform with the same type of CPU (e.g. x86 or x64). 6 | 7 | docker build -t notcompsky/tagem-min . 8 | 9 | To copy the binary from the image, you must create a dummy container and copy from that: 10 | 11 | docker create -ti --name dummy notcompsky/tagem-min bash 12 | docker cp dummy:/tagem-server ./tagem-server 13 | docker rm -f dummy 14 | 15 | ## Non-Docker 16 | 17 | ### Dependencies 18 | 19 | #### Required 20 | 21 | * Boost ASIO 22 | * Python 3 interpreter 23 | * CMake 24 | 25 | #### Optional 26 | 27 | * FFMPEG/libAV 28 | * Used for assigning video statistics (such as duration, fps, width, height). 29 | * OpenCV and Caffe 30 | * Used for generating image databases for machine learning. Untested for a long time, will probably be fixed when 'instances' are recognised by the server. 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Issues 2 | 3 | ## Logs 4 | 5 | Plain text logs are preferred to screenshots. 6 | 7 | # Development 8 | 9 | ## Overviews 10 | 11 | ### Server 12 | 13 | #### Pipeline 14 | 15 | * Python scripts generate C++ header files from `.txt` files describing inline SVGs, server endpoitns, and `qry` tokens. 16 | * Python script generates a single JavaScript C++ header file from all the non-3rdparty JavaScript files. This JavaScript is optionally minimised. Consequently, the names of each non-3rdparty JavaScript file is unimportant. 17 | This header file contains the final names of each JavaScript function, many of which are used in `profile.html` 18 | * `qry` is built 19 | * The server itself is built 20 | 21 | ## General Guidelines 22 | 23 | ### Maintainability 24 | 25 | #### Deduplication 26 | 27 | Deduplicate code where possible. Compile-time deduplication (preferably through metaprogramming, although short macros are also usually acceptable) is preferred when there is a risk of significant runtime performance degredation. 28 | 29 | In particular, if you need to extract information from a string in a particular way, for instance counting the number of instances of a character in that string, prefer to create a separate function for that in a utils header file, and call that function where it is needed. 30 | 31 | In the commit history you'll notice a lot of instances of "Tidy: Deduplicate code", where there is no net loss in lines of code. In fact, some of these appear to make the code *more complex*. Many of these commits are specifically deduplicating SQL code, introducing a slight overhead (in terms of compile-time and reading comprehension) of C++ templates in order to increase the maintainability of the SQL code (which is more important, as it has by far the biggest performance implications and is not checked by the compiler). 32 | 33 | #### Dependencies 34 | 35 | Minimise the dependencies added to the project (without sacrificing features), although dependencies added to the developer pipeline probably are't a big deal. 36 | 37 | For instance, do not use the `{fmt}` aka `std::fmt` library, use [libcompsky](https://github.com/NotCompsky/libcompsky)::asciify, unless the latter cannot do what is desired (in which case you should file a feature request under that project). 38 | 39 | #### Readability 40 | 41 | Try to use readable JavaScript, not complex one-liners. This is partly because the code generator parser has a limited range of syntax it recognises. 42 | 43 | ### Code Style 44 | 45 | It's hard to describe hard-and-fast rules for code linting, just try to copy that of the files you are modifying. If adding new files, copy the coding style in [server.cpp](server/src/server.cpp). 46 | 47 | In particular: 48 | * Use tabs for left indentaiton, and spaces for right indentation. 49 | * There is no line length limitation. Do not split lines just to limit the line lengths. 50 | * Use `Type* foo` rather than `Type *foo` 51 | * Avoid `goto`, `new`, `delete`, `malloc` etc. where possible (although use is permitted if the alternative is messier). 52 | * Class and struct names should be in CamelCase, while function and variable names should be in snake_case. Exceptions are for compatibility with external libraries. 53 | * Source code and scripts should be named in lower case separated by hyphens. 54 | * Names should be descriptive. Yes I study maths, no I don't think that names should all be single characters. If something takes hundreds of characters to describe, so be it. 55 | * C++ source code should have the `cpp` extension. 56 | * C++ header files should have the `hpp` extension. 57 | 58 | JavaScript files pass through a code generator, that extracts function names as macros, pastes in macro values, and places everything on a single line in a C++ header file. The parser for this was written by me, so naturally it is not a full JavaScript parser, it has a very limited idea of what is acceptable syntax. 59 | 60 | # Legal Stuff 61 | 62 | I'm not a lawyer. Contributors are expected to have the rights to the code they add, in which case the newly added code will be licensed under 63 | -------------------------------------------------------------------------------- /DESIGN_DECISIONS.md: -------------------------------------------------------------------------------- 1 | # Outline 2 | 3 | I've been creating this project since mid-2018, when I started to learn programming - the first version was written in mid-2018 while learning Python, the second version in mid-2019 as a C++ GUI app written while learning C++. This web version began in mid-2020. This project has evolved along a very jagged path, and consequently design decisions shouldn't be assumed to exist for a reason, but are in fact open to change, if there is a compelling reason. 4 | 5 | If you want a detailed reasoning behind design decisions, [here's a blog](https://gist.github.com/NotCompsky/f1ab63fa2f191b156b9187b111449d20) detailing the various visions for this project. 6 | 7 | # C++ Libraries 8 | 9 | A core part of this project is the use of [libcompsky](/NotCompsky/libcompsky) for string concatenation and using MySQL. It is this project that is largely driving the development of libcompsky; even [rather bizarre uses](blob/638a4b2a798520859b2741ea4197104d143cfb9b/wangle-server/src/server.cpp#L449) can be [easily integrated into that library](/NotCompsky/libcompsky/blob/5da03a9743cfecc5ff9b594c25f67e1aa5b4071c/include/compsky/mysql/alternating_qry.hpp)'s template metaprogramming. 10 | 11 | # Database 12 | 13 | ## Engine 14 | 15 | One design decision that has remained is the use of MySQL/MariaDB. 16 | 17 | SQLite has thus far been avoided because I believe allowing multiple concurrent connections to the database will continue to be useful - however, the goal is eventually to ship this server as a single binary, and that will probably utilise SQLite due to it being easy to embed. SQLite will probably never be the main DB engine, however, because allowing for simultaneous connections from multiple processes is always going to be important - this isn't just a web server, the design is meant to allow for writing to the database from multiple machines running various utilities (be they perceptual hashes of video files, object recognition on image files, sentiment analysis on linked web pages - the latter two being utilities that don't exist yet, just to be clear). 18 | 19 | Sticking to MySQL itself - rather than PostgreSQL, for instance - is only because I have not seen significant reason to migrate. However, the decision to support *older* versions of the DB engine is deliberate, to support the surprisingly outdated Google Cloud Compute Engine instances. 20 | 21 | # Documentation 22 | 23 | Code generation is not included because I have rarely found auto-generated documentation actually useful - it is almost always *what* documentation rather than *why* documentation. If other developers feel like it would be useful for this project, I would be happy to accept pull requests for auto-documentation. 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Has to be in the root directory, otherwise the docker build system will not allow copying the necessary files from the host to the container 2 | 3 | FROM notcompsky/amd64-static-mariadb-ffmpeg:latest AS intermediate 4 | WORKDIR /tagem 5 | # NOTE: libcompsky should be rebuilt every time, there is a reasonable chance that it is upgraded when tagem is 6 | COPY ffmpegthumbnailer-static.patch /ffmpegthumbnailer-static.patch 7 | 8 | ENV PATH=$PATH:/usr/local/x86_64-linux-musl/lib 9 | ENV CC=/usr/local/bin/x86_64-linux-musl-gcc 10 | ENV CXX=/usr/local/bin/x86_64-linux-musl-g++ 11 | ENV C_INCLUDE_PATH=/usr/local/x86_64-linux-musl/include 12 | ENV LDFLAGS="-Wl,-Bstatic" 13 | ENV CFLAGS="-static" 14 | ENV CXXFLAGS="-static" 15 | 16 | WORKDIR /tagem 17 | COPY server /tagem/server 18 | COPY utils /tagem/utils 19 | 20 | ARG libmagic_version=5.39 21 | 22 | RUN git clone --depth 1 https://github.com/lexbor/lexbor \ 23 | \ 24 | && apk add --no-cache python3-dev \ 25 | && for d in /usr/lib/python3.*; do \ 26 | cp $(find "$d" -type f -name '*python*.a') /usr/lib/ \ 27 | ; done \ 28 | \ 29 | && curl -s ftp://ftp.astron.com/pub/file/file-${libmagic_version}.tar.gz | tar -xz \ 30 | && cd file-${libmagic_version} \ 31 | && ./configure \ 32 | --enable-static \ 33 | --disable-shared \ 34 | && ( \ 35 | make && make install || ( \ 36 | echo "Tries to build linked executable despite options" \ 37 | && mv src/.libs/libmagic.a /usr/local/lib/libmagic.a \ 38 | && mv src/magic.h /usr/local/include/magic.h \ 39 | ) \ 40 | ) \ 41 | \ 42 | && git clone --depth 1 https://github.com/Tencent/rapidjson \ 43 | && mv rapidjson/include/rapidjson /usr/include/rapidjson \ 44 | \ 45 | && git clone --depth 1 https://github.com/dirkvdb/ffmpegthumbnailer \ 46 | && cd ffmpegthumbnailer \ 47 | && git apply /ffmpegthumbnailer-static.patch \ 48 | && addlocalinclude() { \ 49 | mv CMakeLists.txt CMakeLists.old.txt \ 50 | && echo 'include_directories("/usr/local/include" "/usr/include")' > CMakeLists.txt \ 51 | && cat CMakeLists.old.txt >> CMakeLists.txt \ 52 | ; \ 53 | } \ 54 | && addlocalinclude \ 55 | && mkdir build \ 56 | && cd build \ 57 | && cmake \ 58 | -DCMAKE_BUILD_TYPE=Release \ 59 | -DCMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES=/FFmpeg \ 60 | -DENABLE_SHARED=OFF \ 61 | -DENABLE_STATIC=ON \ 62 | -DENABLE_TESTS=OFF .. \ 63 | && make install \ 64 | \ 65 | && git clone --depth 1 https://github.com/NotCompsky/libcompsky \ 66 | && cd libcompsky \ 67 | && addlocalinclude \ 68 | && mkdir build \ 69 | && cd build \ 70 | && cmake \ 71 | -DCMAKE_BUILD_TYPE=Release \ 72 | -DWHICH_MYSQL_CLIENT=mariadbclient \ 73 | -DMYSQL_IS_UNDER_MARIADB_DIR=1 \ 74 | -DMYSQL_UNDER_DIR_OVERRIDE=1 \ 75 | .. \ 76 | && make install \ 77 | \ 78 | && cd /tagem/lexbor \ 79 | && cmake \ 80 | -DLEXBOR_BUILD_SHARED=OFF \ 81 | -DLEXBOR_BUILD_STATIC=ON \ 82 | -DLEXBOR_BUILD_TESTS=OFF \ 83 | -DLEXBOR_BUILD_TESTS_CPP=OFF \ 84 | -DLEXBOR_BUILD_UTILS=OFF \ 85 | -DLEXBOR_BUILD_EXAMPLES=OFF \ 86 | -DLEXBOR_BUILD_SEPARATELY=ON \ 87 | . \ 88 | && make \ 89 | && make install \ 90 | \ 91 | && mv /usr/include/python3.8/* /usr/include/ \ 92 | \ 93 | && chmod +x /tagem/server/scripts/* \ 94 | && ( \ 95 | rm -rf /tagem/build \ 96 | ; mkdir /tagem/build \ 97 | ) && cd /tagem/server \ 98 | && addlocalinclude \ 99 | && cd /tagem/build \ 100 | && LD_LIBRARY_PATH="/usr/local/lib64:$LD_LIBRARY_PATH" cmake \ 101 | \ 102 | -DWHICH_MYSQL_CLIENT=mariadbclient \ 103 | -DCMAKE_BUILD_TYPE=Release \ 104 | -DENABLE_STATIC=ON \ 105 | -DEMBED_PYTHON=ON \ 106 | /tagem/server \ 107 | && make server 108 | 109 | FROM alpine:latest 110 | COPY --from=intermediate /tagem/build/server /tagem-server 111 | EXPOSE 80 112 | ENTRYPOINT ["/tagem-server", "p", "80"] 113 | -------------------------------------------------------------------------------- /Dockerfile.win64: -------------------------------------------------------------------------------- 1 | # Has to be in the root directory, otherwise the docker build system will not allow copying the necessary files from the host to the container 2 | 3 | FROM notcompsky/mxe_amd64-mysql-ffmpeg:latest AS intermediate 4 | WORKDIR /tagem 5 | 6 | COPY ffmpegthumbnailer-static.patch /ffmpegthumbnailer-static.patch 7 | COPY server /tagem/server 8 | COPY utils /tagem/utils 9 | 10 | ARG libmagic_version=5.39 11 | ARG python_version=3.9.1 12 | 13 | RUN apt update \ 14 | && apt install -y curl git tar sed python3 \ 15 | \ 16 | && cd /mxe \ 17 | && make libgnurx \ 18 | \ 19 | && echo "First make Python of the same version for the host, which is required to cross-compile Python" \ 20 | && curl -s https://www.python.org/ftp/python/${python_version}/Python-${python_version}.tgz \ 21 | | tar xz \ 22 | && cd Python-${python_version} \ 23 | && ./configure \ 24 | && make altinstall \ 25 | \ 26 | && curl -s https://www.python.org/ftp/python/${python_version}/Python-${python_version}.tgz \ 27 | | tar xz \ 28 | && cd Python-${python_version} \ 29 | && sed -i 's+/* Compiler specific defines */+#define MS_WIN64+g' PC/pyconfig.h \ 30 | && ./configure \ 31 | --host=x86_64-w64-mingw32 \ 32 | --build=x86_64-pc-linux-gnu \ 33 | --enable-static \ 34 | --disable-shared \ 35 | && make \ 36 | && make install \ 37 | \ 38 | && mkdir py \ 39 | && cd py \ 40 | && curl -s https://www.python.org/ftp/python/${python_version}/python-${python_version}-embed-amd64.zip > py.zip \ 41 | && unzip py.zip \ 42 | && mv python39.dll /usr/lib/python3.9.a \ 43 | \ 44 | && git clone --depth 1 https://github.com/lexbor/lexbor \ 45 | \ 46 | && curl -s ftp://ftp.astron.com/pub/file/file-${libmagic_version}.tar.gz | tar -xz \ 47 | && cd file-${libmagic_version} \ 48 | && ./configure \ 49 | --enable-static \ 50 | --disable-shared \ 51 | --host=x86_64-w64-mingw32.static \ 52 | && ( \ 53 | make && make install || ( \ 54 | echo "Tries to build linked executable despite options" \ 55 | && mv src/.libs/libmagic.a /usr/local/lib/libmagic.a \ 56 | && mv src/magic.h /usr/local/include/magic.h \ 57 | ) \ 58 | ) \ 59 | \ 60 | && git clone --depth 1 https://github.com/Tencent/rapidjson \ 61 | && mv rapidjson/include/rapidjson /usr/include/rapidjson \ 62 | \ 63 | && git clone --depth 1 https://github.com/dirkvdb/ffmpegthumbnailer \ 64 | && cd ffmpegthumbnailer \ 65 | && git apply /ffmpegthumbnailer-static.patch \ 66 | && addlocalinclude() { \ 67 | mv CMakeLists.txt CMakeLists.old.txt \ 68 | && echo 'include_directories("/usr/local/include" "/usr/include")' > CMakeLists.txt \ 69 | && cat CMakeLists.old.txt >> CMakeLists.txt \ 70 | ; \ 71 | } \ 72 | && addlocalinclude \ 73 | && mkdir build \ 74 | && cd build \ 75 | && x86_64-w64-mingw32.static-cmake \ 76 | -DCMAKE_BUILD_TYPE=Release \ 77 | -DCMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES=/FFmpeg \ 78 | -DENABLE_SHARED=OFF \ 79 | -DENABLE_STATIC=ON \ 80 | -DENABLE_TESTS=OFF .. \ 81 | && make install \ 82 | \ 83 | && git clone --depth 1 https://github.com/NotCompsky/libcompsky \ 84 | && cd libcompsky \ 85 | && addlocalinclude \ 86 | && mkdir build \ 87 | && cd build \ 88 | && x86_64-w64-mingw32.static-cmake \ 89 | -DCMAKE_BUILD_TYPE=Release \ 90 | -DMYSQL_IS_UNDER_MARIADB_DIR=1 \ 91 | -DMYSQL_UNDER_DIR_OVERRIDE=1 \ 92 | .. \ 93 | && make install \ 94 | \ 95 | && cd /tagem/lexbor \ 96 | && x86_64-w64-mingw32.static-cmake \ 97 | -DLEXBOR_BUILD_SHARED=OFF \ 98 | -DLEXBOR_BUILD_STATIC=ON \ 99 | -DLEXBOR_BUILD_TESTS=OFF \ 100 | -DLEXBOR_BUILD_TESTS_CPP=OFF \ 101 | -DLEXBOR_BUILD_UTILS=OFF \ 102 | -DLEXBOR_BUILD_EXAMPLES=OFF \ 103 | -DLEXBOR_BUILD_SEPARATELY=ON \ 104 | . \ 105 | && make \ 106 | && make install \ 107 | \ 108 | && mv /usr/include/python3.8/* /usr/include/ \ 109 | \ 110 | && chmod +x /tagem/server/scripts/* \ 111 | && ( \ 112 | rm -rf /tagem/build \ 113 | ; mkdir /tagem/build \ 114 | ) && cd /tagem/server \ 115 | && addlocalinclude \ 116 | && cd /tagem/build \ 117 | && LD_LIBRARY_PATH="/usr/local/lib64:$LD_LIBRARY_PATH" cmake \ 118 | \ 119 | -DCMAKE_BUILD_TYPE=Release \ 120 | -DENABLE_STATIC=ON \ 121 | -DEMBED_PYTHON=ON \ 122 | /tagem/server \ 123 | && make server 124 | 125 | FROM alpine:latest 126 | COPY --from=intermediate /tagem/build/server /tagem-server 127 | -------------------------------------------------------------------------------- /FRONTEND.md: -------------------------------------------------------------------------------- 1 | # Outline 2 | 3 | What the browser sees: 4 | 5 | One HTML file - with inline SVG icons, inline CSS, and inline Javascript. 6 | 7 | What is really written: 8 | 9 | * A directory of C++ header files which generate the HTML 10 | * A directory of text files containing labelled SVG icons to display 11 | * A directory of CSS files where every line is double-quoted (this will eventually be tidied) 12 | * A directory of (almost) entirely valid JavaScript files, with several oddities: 13 | * The occasional `!!!MACRO!!!SOME_NAME_HERE`, where a C++ macro is pasted later 14 | * An overabundance of `$` signs in global variable names (this will eventually be tidied) 15 | 16 | # Pipeline 17 | 18 | ## JavaScript 19 | 20 | The JavaScript passes through [minjs.py](server/scripts/minjs.py), which minimises the names beginning with `$$$`, and performs a limited number of checks on them (for instance, ensuring that every such variable that is used is defined somewhere). With that code generator, JavaScript files can be added to the directory without needing to be explicitly referenced in any build files, and they will still appear in the final JavaScript. 21 | 22 | ## CSS 23 | 24 | The CSS is included in the HTML in basically its written form - the double quotes around every line basically just strip the whitespace. 25 | 26 | ## SVG 27 | 28 | The text files contain a license, followed by a list of labelled SVG files subject to that license. They pass through [svggen.py](server/scripts/svggen.py), which takes all the SVGs listed, optionally attaches the license to each, optionally attaches a license (or even replaces the icon with the label), and pastes the SVGs into a C++ header file, which defines them as macros for inlined inclusion into the HTML. 29 | 30 | ## HTML 31 | 32 | Much of the HTML is generated using simple C++ macros. It is, at this point, far more a C++ file than an HTML file, which is the reason it is not in the `static` directory - it is more a backend problem than frontend. 33 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # General 2 | 3 | This project is statically compiled within a Docker container - hence it's tiny size. 4 | 5 | I will release the executable - which can be run directly - with every major release, however to get the very latest release, you will have to either use the Docker image, or extract the executable from that Docker image. 6 | 7 | # Docker Installation 8 | 9 | ## Description 10 | 11 | This sets up both the web server on a docker container on the machine these commands are being executed on. 12 | 13 | Note that the docker image does not also host the MySQL/MariaDB server, as the web server updates far more frequently than the database. 14 | 15 | ## Installation 16 | 17 | docker pull notcompsky/tagem 18 | 19 | ## Configuration 20 | 21 | The container needs to know how to connect to the database, and for that we use a config file. 22 | 23 | **NOTE**: If the MySQL server is on your local machine, and your machine is not running Windows, use `localhost` as the host name, and leave the port number blank. MySQL is weird about connecting to localhost - as an optimisation, it overrides connection settings when it thinks a UNIX socket might be available, and this somehow makes it crash when a port number is assigned to localhost. 24 | 25 | ### Recommended Method 26 | 27 | `CREATE` the database and `GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE, ALTER, CREATE, TRIGGER` permissions to a user. You can revoke the `ALTER, CREATE, TRIGGER` permissions once the server is up and running. 28 | 29 | Edit [example.cfg](example.cfg) to be correct for your use case, and pass its location to the container using the command line options: `--env TAGEM_MYSQL_CFG=/path/to/edited.cfg` - ensuring that the container has access to the directory `/path/to` (achieved with the options: `-v /path/to:/path/to`. 30 | 31 | ### Alternative Method: Inbuilt Config Generator 32 | 33 | If the environmental variable `TAGEM_MYSQL_CFG` is not set, when the docker contaienr is run, a utility will run which requires some manual input to set up the MySQL server. 34 | 35 | The options you should use are: 36 | 37 | * Absolute path to save the config file to: `/tagem-auth.cfg` 38 | * Host: **Domain name or IP address of the MySQL/MariaDB server machine. Even if the MySQL server is on the same machine as the docker container, it will NOT be localhost unless you run the docker container with `--network="host"` - instead, it would be the loopback address.** 39 | * Socket file path: **Leave blank** 40 | * Username: **This option is up to you** 41 | * Password: **This option is up to you** 42 | * Database name: `tagem` 43 | * MySQL Server port number: **Remote server's port number** 44 | 45 | At this point, the configuration file has been writte, but the database has not been set up by this utility. If the database already exists, you can quit this script now. Otherwise, assuming the remote database allows foreign root access, you can set the database up by continuing with this script: 46 | 47 | * MySQL admin username: **Remote server's admin username** 48 | * MySQL admin password: **Remote server's admin password** 49 | 50 | ## Run 51 | 52 | docker run -p 80:80 [[OPTIONS]] notcompsky/tagem 53 | 54 | where `[[OPTIONS]]` is a list of directories you want the server to be able to access, e.g. `-v /path/to/directory` 55 | 56 | **The first time it is run, the `--interactive` option should be used**. 57 | 58 | 59 | # Bare Metal Install with Shared Libraries 60 | 61 | ## Dependencies 62 | 63 | The easiest method is to build [Proxygen](https://github.com/facebook/proxygen) (just to automatically build/install all its dependencies, building proxygen itself isn't important). 64 | 65 | On Ubuntu 18.04, the other dependencies can be installed with: 66 | 67 | sudo apt install -y --no-install-recommends default-mysql-client default-libmysqlclient-dev libsodium23 libboost-context1.71.0 libevent-2.1-7 libdouble-conversion3 libunwind8 libgoogle-glog0v5 68 | 69 | ## Installation 70 | 71 | Copy a provided binary and hope it was linked against all the same libraries as on your machine. 72 | Or compile the project, as described in [COMPILING.md](COMPILING.md). 73 | 74 | ## Configuration 75 | 76 | Almost the same as for Docker installs, except the initialisation utility must be run directly: 77 | 78 | tagem-init 79 | -------------------------------------------------------------------------------- /STATS.md: -------------------------------------------------------------------------------- 1 | # Statistics 2 | 3 | ## Commands 4 | 5 | ### Installation 6 | 7 | pip3 install labours 8 | curl -s -L https://github.com/src-d/hercules/releases/download/v10.7.2/hercules.linux_amd64.gz > hercules.gz 9 | gzip -d hercules.gz 10 | chmod +x hercules 11 | 12 | ### Generate the Graphs Below 13 | 14 | hercules --burndown https://github.com/notcompsky/tagem | labours -m burndown-project --resample month -o /tmp/monthly-burndown.png 15 | hercules --devs https://github.com/notcompsky/tagem | labours -m old-vs-new -o /tmp/lines-changed_old-vs-new.png 16 | hercules --devs https://github.com/notcompsky/tagem | labours -m devs -o /tmp/devs.png 17 | 18 | ### Misc Graphs of Interest 19 | 20 | hercules --burndown --languages C,C++ ~/repos/compsky/tagem | labours -m burndown-project --resample month 21 | hercules --burndown --languages JavaScript ~/repos/compsky/tagem | labours -m burndown-project --resample month 22 | hercules --burndown --languages CSS ~/repos/compsky/tagem | labours -m burndown-project --resample month 23 | hercules --burndown --languages Python ~/repos/compsky/tagem | labours -m burndown-project --resample month 24 | 25 | ## Graphs 26 | 27 | ![lines-changed_old-vs-new](https://user-images.githubusercontent.com/30552567/89189604-1dcde700-d598-11ea-9960-4d0f6977f947.png) 28 | ![burndown](https://user-images.githubusercontent.com/30552567/89189627-2aead600-d598-11ea-90de-9c550d77821c.png) 29 | -------------------------------------------------------------------------------- /USER_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Home Page 2 | 3 | ![home](https://user-images.githubusercontent.com/30552567/88488234-c7531e00-cf83-11ea-98ef-59b158392fb7.png) 4 | 5 | # Tables 6 | 7 | ![table1](https://user-images.githubusercontent.com/30552567/88488304-40527580-cf84-11ea-8e5b-8f077c13420c.jpg) 8 | ![table2](https://user-images.githubusercontent.com/30552567/88488306-40eb0c00-cf84-11ea-85a0-23902d67a3a7.jpg) 9 | ![table3](https://user-images.githubusercontent.com/30552567/88488307-4183a280-cf84-11ea-8770-dabc7dae9756.jpg) 10 | ![table4](https://user-images.githubusercontent.com/30552567/88488308-4183a280-cf84-11ea-885c-18c7652e7697.jpg) 11 | ![table5](https://user-images.githubusercontent.com/30552567/88488309-421c3900-cf84-11ea-8b8b-38d9a2b842da.jpg) 12 | ![table6](https://user-images.githubusercontent.com/30552567/88488177-8bb85400-cf83-11ea-9621-1564859653f6.jpg) 13 | 14 | ## Files/Eras 15 | 16 | ### Eras 17 | 18 | ![eras](https://user-images.githubusercontent.com/30552567/88488189-98d54300-cf83-11ea-9391-34c8e8629d06.jpg) 19 | 20 | ## Tags 21 | 22 | ![tags](https://user-images.githubusercontent.com/30552567/88488185-983cac80-cf83-11ea-8548-3e5d4fd8a6ff.jpg) 23 | 24 | # Settings 25 | 26 | ![user-settings1](https://user-images.githubusercontent.com/30552567/88488191-996dd980-cf83-11ea-8bc1-79b4128819e5.jpg) 27 | ![user-settings2](https://user-images.githubusercontent.com/30552567/88488190-996dd980-cf83-11ea-8997-406711f82bff.jpg) 28 | 29 | # File 30 | 31 | ![file1](https://user-images.githubusercontent.com/30552567/88488207-9e328d80-cf83-11ea-8b2d-379b4c7cd861.jpg) 32 | 33 | ## Downloading 34 | 35 | ![file2-dl1](https://user-images.githubusercontent.com/30552567/88488206-9e328d80-cf83-11ea-979a-b4a537bacf22.jpg) 36 | ![file2-dl2](https://user-images.githubusercontent.com/30552567/88488205-9d99f700-cf83-11ea-9e18-0d9684a4e455.jpg) 37 | ![file2-dl3](https://user-images.githubusercontent.com/30552567/88488203-9d016080-cf83-11ea-8b92-b2ffb5f23465.jpg) 38 | 39 | ## Eras 40 | 41 | ![file3-era1](https://user-images.githubusercontent.com/30552567/88488201-9d016080-cf83-11ea-93bf-a941a074536f.jpg) 42 | ![file3-era2](https://user-images.githubusercontent.com/30552567/88488200-9c68ca00-cf83-11ea-8283-1eaafdcaf51d.jpg) 43 | ![file3-era3](https://user-images.githubusercontent.com/30552567/88488199-9c68ca00-cf83-11ea-9c4a-93d3f17383d8.jpg) 44 | 45 | # Adding 46 | 47 | ## Tags 48 | 49 | ![add-tag1](https://user-images.githubusercontent.com/30552567/88488198-9bd03380-cf83-11ea-9e76-36dc02b38427.jpg) 50 | ![add-tag2](https://user-images.githubusercontent.com/30552567/88488197-9b379d00-cf83-11ea-898c-ec5f4b99602a.jpg) 51 | ![add-tag3](https://user-images.githubusercontent.com/30552567/88488196-9b379d00-cf83-11ea-949c-ddf45e9b40c5.jpg) 52 | ![add-tag4](https://user-images.githubusercontent.com/30552567/88488195-9a9f0680-cf83-11ea-8172-b73788ae830f.jpg) 53 | 54 | ## Files 55 | 56 | ![add-file](https://user-images.githubusercontent.com/30552567/88488194-9a9f0680-cf83-11ea-86e0-26045172be72.jpg) 57 | ![add-file2](https://user-images.githubusercontent.com/30552567/88488193-9a067000-cf83-11ea-8672-b964b7c2718f.jpg) 58 | 59 | # Qry 60 | 61 | Documentation is included in the web server build. E.g. [on the demo](https://notcompsky.github.io/tagem-eg/#?foobar). 62 | -------------------------------------------------------------------------------- /caffe/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) # CONTRIBUTIONS WELCOME: Tests of previous/future versions which work or not 2 | find_package(OpenCV COMPONENTS core imgcodecs highgui) 3 | find_package(Compsky REQUIRED COMPONENTS asciify mysql) 4 | 5 | set(CMAKE_CXX_STANDARD 11) 6 | 7 | if(NOT OpenCV_FOUND) 8 | message(WARNING "OpenCV not found, so tagem-gen-img-db will not be built") 9 | return() 10 | endif() 11 | 12 | 13 | add_executable(tagem-gen-img-db src/gen-img-db.cpp) 14 | target_include_directories(tagem-gen-img-db PRIVATE ${OpenCV_INCLUDE_DIRS}) 15 | target_link_libraries(tagem-gen-img-db PRIVATE compsky_asciify compsky_mysql opencv_core opencv_imgcodecs opencv_highgui) 16 | 17 | 18 | install( 19 | TARGETS tagem-gen-img-db 20 | EXPORT tagemTargets 21 | RUNTIME DESTINATION "${INSTALL_BIN_DIR}" 22 | COMPONENT bin 23 | ) 24 | -------------------------------------------------------------------------------- /egs.md: -------------------------------------------------------------------------------- 1 | SELECT t.name, f.name, t2f.created_on, t.added_on FROM tag t JOIN tag2file t2f ON t.id=t2f.tag_id JOIN file f ON f.id=t2f.file_id; 2 | 3 | 4 | # Delete tag2file entries that reference a tag that didn't exist at the time of creation 5 | 6 | DELETE t2f FROM tag t JOIN tag2file t2f ON t.id=t2f.tag_id JOIN file f ON f.id=t2f.file_id WHERE t2f.created_on < t.added_on; 7 | 8 | 9 | 10 | 11 | # List files ordered by score 12 | 13 | SELECT name, score FROM file ORDER BY score DESC; 14 | 15 | 16 | # List most common tags 17 | 18 | SELECT t.name, COUNT(f2t.tag_id) AS c FROM tag t JOIN file2tag f2t ON t.id=f2t.tag_id GROUP BY t.id ORDER BY c ASC; 19 | 20 | 21 | 22 | 23 | # List all files tagged with 'TAGNAME' 24 | 25 | SELECT f.name FROM file f JOIN (SELECT f2t.file_id FROM file2tag f2t JOIN (SELECT t.id FROM tag t WHERE t.name = 'TAGNAME') T on f2t.tag_id = T.id) F on f.id = F.file_id; 26 | 27 | 28 | 29 | 30 | # List all files tagged with at least one of TAG1 or TAG2 31 | 32 | SELECT f.name FROM file f JOIN (SELECT f2t.file_id FROM file2tag f2t JOIN (SELECT t.id FROM tag t WHERE t.name IN ('TAG1', 'TAG2')) T on f2t.tag_id = T.id) F on f.id = F.file_id; 33 | 34 | SELECT f.name 35 | FROM file f 36 | JOIN ( 37 | SELECT f2t.file_id 38 | FROM file2tag f2t 39 | JOIN ( 40 | SELECT t.id 41 | FROM tag t 42 | WHERE t.name 43 | IN ('TAG1','TAG2') 44 | ) T ON f2t.tag_id = T.id 45 | ) F on f.id = F.file_id; 46 | 47 | 48 | 49 | 50 | # List all files tagged with ((TAG1 or TAG2) and TAG3) 51 | 52 | SELECT f.name 53 | FROM file f 54 | JOIN ( 55 | SELECT f2t.file_id 56 | FROM file2tag f2t 57 | JOIN ( 58 | SELECT t.id 59 | FROM tag t 60 | WHERE t.name 61 | IN ('TAG1','TAG2') 62 | ) T ON f2t.tag_id = T.id 63 | ) F on f.id = F.file_id 64 | WHERE f.id IN ( 65 | SELECT f2.id 66 | FROM file f2 67 | JOIN ( 68 | SELECT f2t2.file_id 69 | FROM file2tag f2t2 70 | JOIN ( 71 | SELECT t2.id 72 | FROM tag t2 73 | WHERE t2.name 74 | IN ('TAG3') 75 | ) T2 ON f2t2.tag_id = T2.id 76 | ) F2 on f2.id = F2.file_id 77 | ) 78 | ; 79 | 80 | *~4 times faster*: 81 | SELECT f.name 82 | FROM file f 83 | JOIN ( 84 | SELECT file_id 85 | FROM ( 86 | ( 87 | SELECT DISTINCT f2t.file_id 88 | FROM file2tag f2t 89 | JOIN ( 90 | SELECT t.id 91 | FROM tag t 92 | WHERE t.name 93 | IN ('TAG1','TAG2') 94 | ) T ON f2t.tag_id = T.id 95 | ) 96 | UNION ALL 97 | ( 98 | SELECT DISTINCT f2t.file_id 99 | FROM file2tag f2t 100 | JOIN ( 101 | SELECT t.id 102 | FROM tag t 103 | WHERE t.name 104 | IN ('TAG3') 105 | ) T ON f2t.tag_id = T.id 106 | ) 107 | ) 108 | AS u 109 | GROUP BY file_id 110 | HAVING count(*) >= 2 111 | ) F ON f.id = F.file_id 112 | ; 113 | Note that the `DISTINCT` is necessary iff we have multiple tags in the union (so here we could do away with it for the `sfm` subtable). 114 | 115 | 116 | # List all files tagged with at least two of ((TAG1 or TAG2) and (TAG3) and (TAG4)) 117 | SELECT f.name 118 | FROM file f 119 | JOIN ( 120 | SELECT file_id 121 | FROM ( 122 | ( 123 | SELECT DISTINCT f2t.file_id 124 | FROM file2tag f2t 125 | JOIN ( 126 | SELECT t.id 127 | FROM tag t 128 | WHERE t.name 129 | IN ('TAG1','TAG2') 130 | ) T ON f2t.tag_id = T.id 131 | ) 132 | UNION ALL 133 | ( 134 | SELECT DISTINCT f2t.file_id 135 | FROM file2tag f2t 136 | JOIN ( 137 | SELECT t.id 138 | FROM tag t 139 | WHERE t.name 140 | IN ('TAG3', 'TAG4', 'TAG5') 141 | ) T ON f2t.tag_id = T.id 142 | ) 143 | UNION ALL 144 | ( 145 | SELECT DISTINCT f2t.file_id 146 | FROM file2tag f2t 147 | JOIN ( 148 | SELECT t.id 149 | FROM tag t 150 | WHERE t.name 151 | IN ('TAG6', 'TAG7', 'TAG8') 152 | ) T ON f2t.tag_id = T.id 153 | ) 154 | ) 155 | AS u 156 | GROUP BY file_id 157 | HAVING count(*) >= 2 158 | ) F ON f.id = F.file_id 159 | ; 160 | -------------------------------------------------------------------------------- /example.cfg: -------------------------------------------------------------------------------- 1 | HOST: localhost 2 | PATH: / 3 | USER: tagem 4 | PWRD: username 5 | DBNM: password 6 | PORT: - 7 | DONT DELETE THIS LINE MMKAY 8 | -------------------------------------------------------------------------------- /ffmpegthumbnailer-static.patch: -------------------------------------------------------------------------------- 1 | diff --git a/CMakeLists.txt b/CMakeLists.txt 2 | index 15bfa3f..c735467 100644 3 | --- a/CMakeLists.txt 4 | +++ b/CMakeLists.txt 5 | @@ -44,14 +44,6 @@ if (SANITIZE_ADDRESS) 6 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") 7 | endif () 8 | 9 | -find_package(FFmpeg REQUIRED) 10 | -if (NOT FFmpeg_FOUND) 11 | - message(FATAL_ERROR "BAAM") 12 | -endif () 13 | -if (NOT TARGET FFmpeg::avcodec) 14 | - message(FATAL_ERROR "BOOM") 15 | -endif () 16 | - 17 | find_package(JPEG) 18 | if (JPEG_FOUND) 19 | set(HAVE_JPEG ON) 20 | @@ -93,10 +85,10 @@ add_library(libffmpegthumbnailerobj OBJECT 21 | ) 22 | 23 | target_link_libraries(libffmpegthumbnailerobj 24 | - FFmpeg::avformat 25 | - FFmpeg::avcodec 26 | - FFmpeg::avutil 27 | - FFmpeg::avfilter 28 | + /usr/lib/x86_64-linux-gnu/libavformat.a 29 | + /usr/lib/x86_64-linux-gnu/libavcodec.a 30 | + /usr/lib/x86_64-linux-gnu/libavutil.a 31 | + /usr/lib/x86_64-linux-gnu/libavfilter.a 32 | $<$:${JPEG_LIBRARIES}> 33 | $<$:PNG::PNG> 34 | ) 35 | @@ -133,6 +125,7 @@ if (ENABLE_STATIC) 36 | 37 | set_target_properties(libffmpegthumbnailerstatic PROPERTIES 38 | OUTPUT_NAME ffmpegthumbnailer 39 | + PUBLIC_HEADER "${LIB_HDRS}" 40 | ) 41 | target_include_directories(libffmpegthumbnailerstatic 42 | PUBLIC 43 | @@ -161,35 +154,15 @@ if (ENABLE_SHARED) 44 | set (SHARED_LIB libffmpegthumbnailer) 45 | endif () 46 | 47 | -ADD_EXECUTABLE(ffmpegthumbnailer main.cpp) 48 | -target_include_directories(ffmpegthumbnailer PRIVATE ${CMAKE_BINARY_DIR}) 49 | - 50 | -if (ENABLE_GIO) 51 | - find_path(DL_INCLUDE dlfcn.h) 52 | - target_include_directories(ffmpegthumbnailer PRIVATE ${DL_INCLUDE}) 53 | - target_link_libraries(ffmpegthumbnailer ${CMAKE_DL_LIBS}) 54 | -endif () 55 | - 56 | -if (ENABLE_SHARED) 57 | - target_link_libraries(ffmpegthumbnailer ${SHARED_LIB}) 58 | -else () 59 | - target_link_libraries(ffmpegthumbnailer ${STATIC_LIB}) 60 | -endif () 61 | - 62 | -install(TARGETS ffmpegthumbnailer ${STATIC_LIB} ${SHARED_LIB} 63 | +install(TARGETS ${STATIC_LIB} ${SHARED_LIB} 64 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 65 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 66 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 67 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libffmpegthumbnailer 68 | ) 69 | 70 | -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/man/ffmpegthumbnailer.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) 71 | install(FILES ${CMAKE_BINARY_DIR}/libffmpegthumbnailer.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 72 | 73 | -if (ENABLE_THUMBNAILER) 74 | - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dist/ffmpegthumbnailer.thumbnailer DESTINATION ${CMAKE_INSTALL_DATADIR}/thumbnailers) 75 | -endif () 76 | - 77 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_BINARY_DIR}/config.h) 78 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libffmpegthumbnailer.pc.in ${CMAKE_BINARY_DIR}/libffmpegthumbnailer.pc @ONLY) 79 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/CMakeUninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/CMakeUninstall.cmake" IMMEDIATE @ONLY) 80 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/* 2 | cppflow/* 3 | -------------------------------------------------------------------------------- /scripts/tagem-auth: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | get_auth(){ 5 | echo "$(echo "$1" | grep "^$2: " | sed "s_^$2: __g")" 6 | } 7 | 8 | 9 | auth="$(cat "$TAGEM_MYSQL_CFG")" 10 | host="$(get_auth "$auth" HOST)" 11 | path="$(get_auth "$auth" PATH)" 12 | user="$(get_auth "$auth" USER)" 13 | pwrd="$(get_auth "$auth" PWRD)" 14 | port="$(get_auth "$auth" PORT)" 15 | tagem_auth=(--host "$host" --user "$user" --password="$pwrd" --socket="$path") 16 | if [ "$port" = "" ]; then 17 | dummy=1 18 | else 19 | tagem_auth+=(--port "$port") 20 | fi 21 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | -------------------------------------------------------------------------------- /server/client/userscripts/reddit.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name tagem-archive-reddit-posts 3 | // @namespace https://goooooooooooooooooooooooooooooooooooooooooooooooooooooooogle.com 4 | // @description Adds buttons to new.reddit.com (eugh!) that will tell the tagem server to archive the submissions 5 | // @include https://new.reddit.com/* 6 | // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js 7 | // @version 0.0.1 8 | // ==/UserScript== 9 | 10 | const tagem_server = "localhost:1999"; 11 | 12 | function add_post_to_db(){ 13 | const score = this.parentNode.childNodes[0].childNodes[0].childNodes[1].innerText; 14 | 15 | const username = this.parentNode.childNodes[1].getElementsByTagName('div')[2].getElementsByTagName('div')[0].getElementsByTagName('a')[0].innerText; 16 | const title = this.parentNode.childNodes[1].childNodes[0].childNodes[0].childNodes[1].childNodes[1].childNodes[0].childNodes[0].childNodes[0].innerText; 17 | const link = this.parentNode.childNodes[1].childNodes[0].childNodes[0].childNodes[2].childNodes[0].href; 18 | 19 | const permalink = this.parentNode.childNodes[1].childNodes[1].childNodes[1].childNodes[0].href; 20 | const n_cmnts = this.parentNode.childNodes[1].childNodes[1].childNodes[1].childNodes[0].childNodes[1].innerText; 21 | 22 | $.ajax({ 23 | method: "POST", 24 | url:"http://" + tagem_server + "/x/dl/reddit/p/" + permalink, 25 | success: function(){ alert("Success");}, 26 | error: function(){ alert("Error");} 27 | }); 28 | } 29 | 30 | const post_containers = document.evaluate('/html/body/div[1]/div/div[2]/div[2]/div/div/div/div[2]/div[3]/div[1]/div[5]/div', document, null, XPathResult.ANY_TYPE, null); 31 | var btn; 32 | const posts = []; 33 | var post; 34 | 35 | window.addEventListener('load', function(){ 36 | while(true){ 37 | post = post_containers.iterateNext(); 38 | if(post === null) 39 | break; 40 | posts.push(post.getElementsByTagName('div')[0].getElementsByTagName('div')[0]); 41 | } 42 | for(post of posts){ 43 | btn = document.createElement("button"); 44 | btn.innerText = "Record in DB"; 45 | btn.onclick = add_post_to_db; 46 | post.appendChild(btn); 47 | } 48 | }, false); 49 | -------------------------------------------------------------------------------- /server/src/.gitignore: -------------------------------------------------------------------------------- 1 | auto-generated/* 2 | __pycache__/* 3 | qry.wasm 4 | -------------------------------------------------------------------------------- /server/src/curl_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | 14 | #ifdef USE_LIBCURL 15 | 16 | #include "curl_utils.hpp" 17 | #include "str_utils.hpp" 18 | #include "os.hpp" 19 | #include "errors.hpp" 20 | #include 21 | #include 22 | #include // for fopen 23 | #include // for memccpy 24 | 25 | 26 | namespace curl { 27 | 28 | 29 | size_t curl_write_callback(char* contents, size_t size, size_t nmemb, void* buf_itr){ 30 | const size_t n_bytes = size * nmemb; 31 | memcpy(*reinterpret_cast(buf_itr), (char*)contents, n_bytes); 32 | *reinterpret_cast(buf_itr) += n_bytes; 33 | return n_bytes; 34 | } 35 | 36 | 37 | size_t dl_buf(const char* const url, char*& dst_buf_orig){ 38 | char* dst_buf = dst_buf_orig; 39 | 40 | compsky::dl::Curl curl( 41 | CURLOPT_WRITEFUNCTION, curl_write_callback, 42 | CURLOPT_WRITEDATA, &dst_buf, 43 | CURLOPT_FOLLOWLOCATION, true 44 | #ifdef DNS_OVER_HTTPS_CLIENT_URL 45 | , CURLOPT_DOH_URL, DNS_OVER_HTTPS_CLIENT_URL 46 | #endif 47 | ); 48 | 49 | return curl.perform(url) ? 0 : (compsky::utils::ptrdiff(dst_buf, dst_buf_orig)); 50 | } 51 | 52 | 53 | void init(){ 54 | curl_global_init(CURL_GLOBAL_ALL); 55 | } 56 | 57 | 58 | void clean(){ 59 | curl_global_cleanup(); 60 | } 61 | 62 | 63 | } // namespace curl 64 | 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /server/src/db_info.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | 14 | #pragma once 15 | 16 | #include "log.hpp" 17 | #include "thread_pool.hpp" 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | class DatabaseInfo : public ThreadPool { 25 | public: 26 | constexpr static const size_t buf_sz = 512; 27 | char buf[buf_sz]; 28 | compsky::mysql::MySQLAuth auth; 29 | 30 | enum B : unsigned { 31 | has_post_tbl, 32 | 33 | // User columns 34 | has_user_full_name_column, 35 | has_user_verified_column, 36 | has_n_followers_column, 37 | 38 | // Post columns 39 | has_n_likes_column, 40 | 41 | // Comment columns 42 | has_cmnt_n_likes_column, 43 | 44 | has_follow_tbl, 45 | has_cmnt_tbl, 46 | has_post2mention_tbl, 47 | has_cmnt2mention_tbl, 48 | has_hashtag_tbl, 49 | has_post2like_tbl, 50 | has_cmnt2like_tbl, 51 | has_user2tag_tbl, 52 | 53 | is_accessible_from_master_connection, 54 | 55 | COUNT 56 | }; 57 | bool bools[COUNT]; 58 | 59 | bool is_true(unsigned enum_indx) const { 60 | return this->bools[enum_indx]; 61 | } 62 | 63 | void attempt_to_access_tbl(MYSQL* mysql_obj, const char* const tbl_name) const; 64 | void attempt_qry(MYSQL* mysql_obj, const char* const qry) const; 65 | 66 | const char* host() const { 67 | return auth.host; 68 | } 69 | const char* path() const { 70 | return auth.path; 71 | } 72 | const char* user() const { 73 | return auth.user; 74 | } 75 | const char* pwrd() const { 76 | return auth.pwrd; 77 | } 78 | const char* name() const { 79 | return auth.dbnm; 80 | } 81 | unsigned port() const { 82 | return a2n(auth.port); 83 | } 84 | void close(); 85 | void test_is_accessible_from_master_connection(MYSQL* const master_connection, char* buf); 86 | 87 | template 88 | void logs(Args... args) const { 89 | //static_log(this->name(), ": ", args...); 90 | } 91 | 92 | void query_buffer(MYSQL_RES*& res, const char* const qry, const size_t sz){ 93 | MYSQL* const mysql_obj = this->get(); 94 | try { 95 | compsky::mysql::query_buffer(mysql_obj, res, qry, sz); 96 | } catch(compsky::mysql::except::SQLExec& e){ 97 | this->logs("Bad SQL: ", mysql_error(mysql_obj)); 98 | throw(e); 99 | } 100 | this->free(mysql_obj); 101 | } 102 | 103 | void query_buffer(MYSQL_RES*& res, const char* const qry){ 104 | this->query_buffer(res, qry, strlen(qry)); 105 | } 106 | 107 | void exec_buffer(const char* const qry, const size_t sz){ 108 | MYSQL* const mysql_obj = this->get(); 109 | try { 110 | compsky::mysql::exec_buffer(mysql_obj, qry, sz); 111 | } catch(compsky::mysql::except::SQLExec& e){ 112 | this->logs("Bad SQL: ", mysql_error(mysql_obj)); 113 | throw(e); 114 | } 115 | this->free(mysql_obj); 116 | } 117 | 118 | void exec_buffer(const char* const qry){ 119 | this->exec_buffer(qry, strlen(qry)); 120 | } 121 | 122 | template 123 | void exec(char* const buf, Args&&... args){ 124 | char* itr = buf; 125 | compsky::asciify::asciify(itr, args...); 126 | this->exec_buffer(buf, compsky::utils::ptrdiff(itr, buf)); 127 | } 128 | 129 | void new_obj(MYSQL*& mysql_obj) const; 130 | void kill_obj(MYSQL* mysql_obj) const; 131 | 132 | DatabaseInfo(const char* const env_var_name, const bool set_bools); 133 | }; 134 | -------------------------------------------------------------------------------- /server/src/errors.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include // for exit 5 | #include "log.hpp" 6 | 7 | 8 | enum { 9 | NAH_NO_ERROR, 10 | MISC_ERROR, 11 | 12 | 13 | 14 | N_ERRORS 15 | }; 16 | 17 | #ifdef NO_EXCEPTIONS 18 | # define LOG(...) 19 | #else 20 | # include 21 | constexpr static 22 | const char* const handler_msgs[] = { 23 | "No error", 24 | "Misc error", 25 | "" 26 | }; 27 | #endif 28 | 29 | inline 30 | void handler(const int rc){ 31 | // Do nothing on a bad test result in order for the test itself to be optimised out 32 | #ifdef TESTS 33 | #ifndef NO_EXCEPTIONS 34 | compsky::os::write::write_to_stderr(handler_msgs[rc]); 35 | #endif 36 | exit(rc); 37 | #endif 38 | } 39 | 40 | template 41 | void handler(const int msg, Args... args){ 42 | log(args...); 43 | handler(msg); 44 | } 45 | -------------------------------------------------------------------------------- /server/src/fix-missing-symbol.monkeypatch.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" { 5 | void my_sha512(char* dst, char* src, const size_t slen){ 6 | abort(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /server/src/fn_successness.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | #pragma once 14 | 15 | 16 | enum FunctionSuccessness { 17 | ok, 18 | malicious_request, 19 | server_error, 20 | unimplemented, 21 | COUNT 22 | }; 23 | -------------------------------------------------------------------------------- /server/src/get_cookies.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | #pragma once 14 | 15 | #include "nullable_string_view.hpp" 16 | #include 17 | #include 18 | 19 | 20 | constexpr 21 | NullableStringView get_cookie(const char* headers, const char* const cookie_name){ 22 | // NOTE: cookie_name should include the equals sign 23 | const char* cookies = SKIP_TO_HEADER(8,"Cookie: ")(headers); 24 | NullableStringView desired_cookie; 25 | if (cookies == nullptr){ 26 | return desired_cookie; 27 | } 28 | while(*(++cookies) != 0){ 29 | const char* cookie_name_itr = cookie_name; 30 | while(*cookies == *cookie_name_itr){ 31 | ++cookie_name_itr; 32 | ++cookies; 33 | if(*cookie_name_itr == 0) 34 | desired_cookie.data = cookies; 35 | } 36 | 37 | // Skip to next cookie name 38 | while((*cookies != ';') and (*cookies != 0) and (*cookies != '\r')) 39 | ++cookies; 40 | desired_cookie.sz = compsky::utils::ptrdiff(cookies, desired_cookie.data); 41 | switch(*cookies){ 42 | case 0: 43 | // Probably invalid end of headers 44 | case '\r': 45 | // No more cookies 46 | return desired_cookie; 47 | default: // ';' 48 | switch(*(++cookies)){ 49 | case ' ': 50 | // Good 51 | break; 52 | default: 53 | return desired_cookie; 54 | } 55 | } 56 | if (desired_cookie.data != nullptr) 57 | break; 58 | } 59 | return desired_cookie; 60 | } 61 | -------------------------------------------------------------------------------- /server/src/handler_buf_pool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thread_pool.hpp" 4 | 5 | 6 | class HandlerBufPool : public ThreadPool { 7 | public: 8 | void new_obj(char*& buf) const { 9 | buf = reinterpret_cast(malloc(HANDLER_BUF_SZ)); 10 | } 11 | void kill_obj(char* buf) const {} 12 | }; 13 | -------------------------------------------------------------------------------- /server/src/help.txt: -------------------------------------------------------------------------------- 1 | R"=========( 2 | USAGE 3 | [[OPTIONS]] p [PORT_NUMBER] [[EXTERNAL_DATABASES]] 4 | 5 | OPTIONS 6 | Y [youtube-dl format] 7 | DEFAULT 8 | The default value restricts downloads to [height<=720] and prioritises the most compressed formats. 9 | EXAMPLES 10 | See https://github.com/TheFrenchGhosty/TheFrenchGhostys-YouTube-DL-Archivist-Scripts 11 | c [/PATH/TO/THUMBNAIL/DIRECTORY] 12 | f [/PATH/TO/ffmpeg] 13 | Default: /usr/bin/ffmpeg 14 | 15 | EXTERNAL DATABASES 16 | Optional 17 | List of environmental variables, each preceded by "x", pointing to files of the same format as $TAGEM_MYSQL_CFG, containing login data for foreign databases 18 | Eg 19 | x REDDIT_MYSQL_CFG x TWITTER_MYSQL_CFG 20 | These foreign databases should contain, at a minimum, a "post" table 21 | Each database should be of a unique name. 22 | The tagem database itself contains the "post2file" table, which maps the external database's posts to tagem's files 23 | // The alternative - the external database containing this table - would involve a lot more database calls. With this system, we can do a simple join, to tell clients that which posts are available in which external databases, and then only access the external database from file info and advanced queries. 24 | Other obtional tables, that will be recognised and used if available, are: 25 | "user" for users 26 | "user2tagem_tag" linking the user to tagem's tag table 27 | "follow" linking users to other users 28 | "post2mention" and "post2like" linking users to posts 29 | "cmnt" for comments 30 | "cmnt2mention" and "cmnt2like" linking users to comments 31 | "hashtag2tagem_tag" linking tagem's tag table to the foreign hashtags 32 | "hashtag" 33 | "post2hashtag" linking posts to hashtags 34 | "follow_hashtag" linking users to hashtags 35 | )=========" 36 | -------------------------------------------------------------------------------- /server/src/html/components/change-file-to-backup.html: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | "
" 14 | "" 15 | "" 16 | "" 17 | "
" 18 | -------------------------------------------------------------------------------- /server/src/html/components/tbl.html: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | #define CREATE_ACTION_BTN(r, data, elem) \ 14 | "" \ 15 | 16 | #define CREATE_HEADER(r, data, tpl) \ 17 | "
" \ 18 | BOOST_PP_TUPLE_ELEM(1, tpl) \ 19 | "
" 20 | #define CREATE_FILTER(r, data, elem) \ 21 | BOOST_PP_IF( \ 22 | BOOST_PP_SUB(BOOST_PP_TUPLE_ELEM(2, elem), 1), /* 0 for 0 and 1; 1 for 2 */ \ 23 | "
" \ 24 | "" \ 25 | "
", \ 26 | "
" \ 27 | ) 28 | #define CREATE_SORT(r, data, elem) \ 29 | BOOST_PP_IF( \ 30 | BOOST_PP_TUPLE_ELEM(2, elem), \ 31 | "", \ 32 | "
" \ 33 | ) 34 | #define CREATE_HIDE(r, data, tpl) \ 35 | "" 36 | 37 | "
" 38 | 39 | "
" 40 | #ifdef IS_SELECTABLE 41 | // Display examples of what each selection colour means 42 | "" SVG_INFO_CIRCLE "" 43 | # undef IS_SELECTABLE 44 | #endif 45 | "" 46 | "" 47 | "" 48 | #ifdef ACTION_BTNS 49 | BOOST_PP_SEQ_FOR_EACH(CREATE_ACTION_BTN, _, BOOST_PP_VARIADIC_SEQ_TO_SEQ(ACTION_BTNS)) 50 | # undef ACTION_BTNS 51 | #endif 52 | #undef CREATE_ACTION_BTN 53 | "
" 54 | 55 | "
" 56 | // Name row 57 | "
" 58 | BOOST_PP_SEQ_FOR_EACH(CREATE_HEADER, _, BOOST_PP_VARIADIC_SEQ_TO_SEQ(COLS)) 59 | #undef CREATE_HEADER 60 | "
" 61 | 62 | // Filter row 63 | "
" 64 | BOOST_PP_SEQ_FOR_EACH(CREATE_FILTER, _, BOOST_PP_VARIADIC_SEQ_TO_SEQ(COLS)) 65 | #undef CREATE_FILTER 66 | "
" 67 | 68 | // Sort row 69 | "
" 70 | BOOST_PP_SEQ_FOR_EACH(CREATE_SORT, _, BOOST_PP_VARIADIC_SEQ_TO_SEQ(COLS)) 71 | "
" 72 | #undef CREATE_SORT 73 | "
" 74 | BOOST_PP_SEQ_FOR_EACH(CREATE_HIDE, _, BOOST_PP_VARIADIC_SEQ_TO_SEQ(COLS)) 75 | "
" 76 | #undef COLS 77 | #undef CREATE_HIDE 78 | "
" 79 | "
" 80 | "
" 81 | 82 | #undef TBL_ID 83 | #undef TBL_NAME_COLS 84 | #undef TBL_TAGS_COLS 85 | -------------------------------------------------------------------------------- /server/src/html/credits.html: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | R"=========( 14 | This site is an implementation of the tagem project. All elements are copyright Adam Gray, subject to the GPLv3 license, with the exceptions listed below. 15 | 16 | The HTML that is included inline: 17 | Almost all SVG icons are sourced or derived from tabler-icons by Paweł Kuna 18 | The exceptions are the two triangle icons by myself, and the search icon by Aysenur Turk 19 | 20 | JavaScript libraries taht are used but not included inline: 21 | MouseTrap 22 | For intercepting key events 23 | )=========" 24 | -------------------------------------------------------------------------------- /server/src/html/examples.html: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | R"=========( 14 | Suppose you have a screenshot of a Twitter post. You can get the link to the original post - for instance, by using find-tweet. You can then go to that file, click "Update src", and change the source to the Twitter post's URL. This will keep the screenshot as a 'backup' of the post. 15 | 16 | Suppose you wish to record some snippets of C++ code. You can open the text editor, paste the code into the main content section, name the file, tag it 'C++', then select a remote directory. ANY remote directory can be used, however I would suggest creating a 'directory' named "Code Snippets" - specifically ommitting the protocol and other unnecessary parts - specifically for code snippets like these. When this 'file' is saved, it creates a record in the database, with the contents as the 'file' description - no content is written to an actual file aside from the database. 17 | 18 | 19 | )=========" 20 | -------------------------------------------------------------------------------- /server/src/html/filter-syntax.html: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | R"=========( 14 | Date Query 15 | USAGE 16 | [DATE] 17 | [DATE] - [DATE] 18 | Second date should be the largest 19 | DATE 20 | yyyy 21 | yyyy/mm 22 | yyyy/mm/dd 23 | EXAMPLES 24 | 2020 25 | 2020/03 26 | 2018-2020 27 | 2016/05-2020/06 28 | )=========" 29 | -------------------------------------------------------------------------------- /server/src/html/keyboard-shortcuts.html: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | R"=========( 14 | Keyboard Shortcuts 15 | b Toggle the 'add backup file' dialog 16 | e Add era vertex (start or end) 17 | m Merge selected files 18 | p View selected files as a playlist 19 | q Make a qry 20 | v Toggle the 'add file2 variable to file' dialog 21 | )=========" 22 | -------------------------------------------------------------------------------- /server/src/html/svg/.gitignore: -------------------------------------------------------------------------------- 1 | favicon.svg 2 | -------------------------------------------------------------------------------- /server/src/html/svg/mine.txt: -------------------------------------------------------------------------------- 1 | // SVGs I wrote myself. Same license as repository. 2 | 3 | TRIANGLE_LEFT:◂ 4 | 5 | 6 | 7 | 8 | TRIANGLE_RIGHT:▸ 9 | 10 | 11 | 12 | 13 | FAVICON:Favicon 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /server/src/info_extractor-domainid.txt: -------------------------------------------------------------------------------- 1 | THIS LINE HERE IS IMPORTANT DO NOT REMOVE 2 | www.bbc.co.uk/news/> 3 | return BBCNews 4 | www.theguardian.co> 5 | return Guardian 6 | www.telegraph.co> 7 | return Telegraph 8 | thetab.com/> 9 | return TheTab 10 | twitter.com/> 11 | return Twitter 12 | www.reddit.com/r/> 13 | return Reddit 14 | old.reddit.com/r/> 15 | return Reddit 16 | new.reddit.com/r/> 17 | return Reddit 18 | www.reddit.com/user/> 19 | return Reddit 20 | old.reddit.com/user/> 21 | return Reddit 22 | new.reddit.com/user/> 23 | return Reddit 24 | 25 | i.> 26 | return DirectFile 27 | 28 | streamable.com/> 29 | return Streamable 30 | 31 | prospect.org/> 32 | return TheAmericanProspect 33 | www.nytimes.com/> 34 | return NYT 35 | www.pajiba.com/> 36 | return Pajiba 37 | www.buzzfeednews.com/article/> 38 | return BuzzFeedNews 39 | www.thecourier.co.uk/> 40 | return TheCourier 41 | www.wbur.org/> 42 | return WBUR 43 | apnews.com/article/> 44 | return APNews 45 | reason.com/> 46 | return ReasonCom 47 | www.thenation.com/article/> 48 | return TheNation 49 | rsf.org/en/news/> 50 | return ReportersWithoutBorders 51 | www.jpost.com/> 52 | return JPost 53 | www.theatlantic.com/> 54 | return TheAtlantic 55 | www.reuters.com/article/> 56 | return Reuters 57 | www.scmp.com/> 58 | return SCMP 59 | www.algemeiner.com/> 60 | return Algemeiner 61 | observer.com/> 62 | return ObserverCom 63 | theintercept.com/> 64 | return TheIntercept 65 | thehill.com/> 66 | return TheHill 67 | heavy.com/news/> 68 | return HeavyCom 69 | www.vanityfair.com/news/> 70 | return VanityFairNews 71 | globalnews.ca/news/> 72 | return GlobalNewsCA 73 | www.mediaite.com/news/> 74 | return Mediaite 75 | 76 | www.volkskrant.nl/> 77 | return VolkskRant 78 | 79 | journals.plos.org/plosone/article?id=> 80 | return PLOSOneJournal 81 | 82 | digg.com/> 83 | return Digg 84 | www.instagram.com/p/> 85 | return InstagramPost 86 | threadreaderapp.com/thread/> 87 | return ThreadReaderApp 88 | -------------------------------------------------------------------------------- /server/src/info_extractor-verify.txt: -------------------------------------------------------------------------------- 1 | THIS LINE HERE IS IMPORTANT DO NOT REMOVE 2 | http://> 3 | return true 4 | https://> 5 | return true 6 | -------------------------------------------------------------------------------- /server/src/initialise_tagem_db.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | 14 | #include "initialise_tagem_db.hpp" 15 | #include 16 | #include 17 | 18 | 19 | void initialise_tagem_db(MYSQL* mysql_obj){ 20 | constexpr static const char* const stmts = 21 | #include "../../utils/src/init.sql" 22 | #include "../../utils/src/init_user_invalid.sql" 23 | #include "../../utils/src/init_user_guest.sql" 24 | #include "../../utils/src/init_user_admin.sql" 25 | #include "../../utils/src/init_data.sql" 26 | #include "../../utils/src/triggers.sql" 27 | #include "../../utils/src/procedures.sql" 28 | ; 29 | 30 | try { 31 | compsky::mysql::exec_buffer(mysql_obj, "UPDATE tagem_db_initialised SET version=1"); 32 | return; 33 | } catch(compsky::mysql::except::SQLExec&){} 34 | 35 | const char* last_stmt = stmts; 36 | for (const char* itr = stmts; *itr != 0; ++itr){ 37 | if (unlikely(*itr == ';')){ 38 | compsky::mysql::exec_buffer(mysql_obj, last_stmt, compsky::utils::ptrdiff(itr, last_stmt)); 39 | last_stmt = itr + 1; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/src/initialise_tagem_db.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | void initialise_tagem_db(MYSQL* mysql_obj); 7 | -------------------------------------------------------------------------------- /server/src/log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | template 8 | void log(Args&&... args){ 9 | char buf[1024]; 10 | compsky::os::write_to_stderr(buf, args..., '\n'); 11 | } 12 | 13 | template 14 | void static_log(Args&&... args){ 15 | static char buf[1024 * 50]; 16 | compsky::os::write_to_stderr(buf, args..., '\n'); 17 | } 18 | -------------------------------------------------------------------------------- /server/src/mimetype.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | constexpr size_t MAX_MIMETYPE_SZ = 100; 7 | -------------------------------------------------------------------------------- /server/src/nullable_string_view.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | #pragma once 14 | 15 | struct NullableStringView { 16 | const char* data; 17 | size_t sz; 18 | 19 | constexpr 20 | NullableStringView() 21 | : data(nullptr) 22 | , sz(0) 23 | {} 24 | 25 | constexpr 26 | NullableStringView(const char* const _str, const size_t sz) 27 | : data(_str) 28 | , sz(sz) 29 | {} 30 | 31 | constexpr 32 | bool operator==(const NullableStringView& other) const { 33 | if(other.sz != this->sz) 34 | return false; 35 | size_t i = 0; 36 | while(i < this->sz){ 37 | if(this->data[i] != other.data[i]) 38 | return false; 39 | ++i; 40 | } 41 | return true; 42 | } 43 | 44 | constexpr 45 | bool operator<(const NullableStringView other) const { 46 | if(other.sz != this->sz) 47 | return (this->sz < other.sz); 48 | size_t i = 0; 49 | while(i < this->sz){ 50 | if(this->data[i] != other.data[i]) 51 | return (this->data[i] < other.data[i]); 52 | ++i; 53 | } 54 | return false; 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /server/src/os.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotCompsky/tagem/c879c93fb334314aef568707e27f8078ae8831df/server/src/os.cpp -------------------------------------------------------------------------------- /server/src/python.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // WARNING: Must be before any Qt includes, because slots is a macro name. 4 | 5 | 6 | #define PY_ASSERT_NOT_NULL(obj, msg) \ 7 | if (unlikely(obj == nullptr)) \ 8 | throw std::runtime_error("Python error: " msg); 9 | #define PY_ASSERT_ZERO(rc, msg) \ 10 | if (unlikely(rc != 0)) \ 11 | throw std::runtime_error("Python error: " msg); 12 | 13 | 14 | namespace python { 15 | 16 | 17 | class PyObj { 18 | public: 19 | PyObject* obj; 20 | 21 | 22 | void set_attr(const char* const attr_name, PyObject* const _obj){ 23 | PyObject_SetAttrString(this->obj, attr_name, _obj); 24 | } 25 | 26 | PyObject* get_attr(const char* const attr_name) const { 27 | PyObject* const rc = PyObject_GetAttrString(this->obj, attr_name); 28 | PY_ASSERT_NOT_NULL(rc, "Cannot get attribute") 29 | return rc; 30 | } 31 | 32 | 33 | /* Call self */ 34 | 35 | template 36 | PyObject* call(Args... args){ 37 | PyObject* const result = PyObject_CallObject(this->obj, PyObj(PyTuple_Pack(sizeof...(args), args...)).obj); 38 | PY_ASSERT_NOT_NULL(result, "Cannot call object") 39 | return result; 40 | } 41 | 42 | /* Call self without return value */ 43 | 44 | template 45 | void call_void(Args... args){ 46 | Py_DECREF(this->call(args...)); 47 | } 48 | 49 | /* Call member function */ 50 | 51 | template 52 | PyObject* call_fn(const char* const fn_name, Args... args){ 53 | PyObj fn(this->obj, fn_name); 54 | PyObject* const result = PyObject_CallFunctionObjArgs(fn.obj, args..., NULL); 55 | if (unlikely(result == nullptr)) 56 | throw std::runtime_error("Python exception"); 57 | return result; 58 | } 59 | 60 | /* Call member function without return value */ 61 | 62 | template 63 | void call_fn_void(Args... args){ 64 | Py_DECREF(this->call_fn(args...)); 65 | } 66 | 67 | template 68 | bool call_fn_bool(Args... args){ 69 | PyObject* res = this->call_fn(args...); 70 | Py_DECREF(res); 71 | return (res == Py_True); 72 | } 73 | 74 | 75 | const char* as_str() const { 76 | return PyUnicode_AsUTF8(this->obj); 77 | } 78 | 79 | void copy_str(char* buf) const { 80 | Py_ssize_t sz; 81 | const char* const str = PyUnicode_AsUTF8AndSize(this->obj, &sz); 82 | memcpy(buf, str, sz); 83 | buf[sz] = 0; 84 | } 85 | 86 | void print_as_str(const char* const msg) const { 87 | constexpr compsky::asciify::flag::until::NullOrNthChar _f_nthchar; 88 | log(msg, _f_nthchar, 100, PyUnicode_AsUTF8(PyObject_Str(this->obj))); 89 | } 90 | 91 | 92 | PyObj(){} 93 | 94 | PyObj(PyObject* const _obj) 95 | : obj(_obj) 96 | { 97 | this->print_as_str("Created: "); 98 | } 99 | 100 | PyObj(PyObject* const _obj, const char* const attr_name) 101 | : PyObj(PyObject_GetAttrString(_obj, attr_name)) 102 | {} 103 | 104 | PyObj(PyObj& _obj, const char* const attr_name) 105 | : PyObj(_obj.obj, attr_name) 106 | {} 107 | 108 | ~PyObj(){ 109 | this->print_as_str("Destroyed: "); 110 | if (likely((this->obj != Py_None) and (this->obj != nullptr))) 111 | Py_DECREF(this->obj); 112 | } 113 | 114 | PyObj& operator=(PyObj& other){ 115 | this->print_as_str("Copying: "); 116 | this->obj = other.obj; 117 | return *this; 118 | } 119 | }; 120 | 121 | class PyStr : public PyObj { 122 | private: 123 | const char* const str; 124 | public: 125 | PyStr(const char* const _str) 126 | : str(_str) 127 | , PyObj(_str ? PyUnicode_FromString(_str) : Py_None) 128 | {} 129 | }; 130 | 131 | class PyInt : public PyObj { 132 | private: 133 | const long n; 134 | public: 135 | PyInt(const long _n) 136 | : n(_n) 137 | , PyObj(PyLong_FromLong(_n)) 138 | {} 139 | }; 140 | 141 | template 142 | class PyDict : public PyObj { 143 | private: 144 | PyObject* vals[n]; 145 | 146 | template 147 | void instatiate(){ 148 | static_assert(i == 0); 149 | } 150 | 151 | template 152 | void instatiate(const char* const _key, PyObject* const _obj, Args... args){ 153 | this->vals[n-i] = _obj; 154 | PY_ASSERT_ZERO(PyDict_SetItemString(this->obj, _key, _obj), "Cannot set dictionary item") 155 | this->instatiate(args...); 156 | } 157 | public: 158 | template 159 | PyDict(Args... args) 160 | : PyObj(PyDict_New()) 161 | { 162 | this->instatiate(args...); 163 | } 164 | 165 | ~PyDict(){ 166 | for (PyObject* const _key : this->vals){ 167 | Py_DECREF(_key); 168 | } 169 | } 170 | 171 | PyDict& operator=(PyDict& other){ 172 | memcpy(this->vals, other.vals, sizeof(vals)); 173 | this->obj = other.obj; 174 | return *this; 175 | } 176 | }; 177 | 178 | class GILLock { 179 | private: 180 | PyGILState_STATE gstate; 181 | public: 182 | GILLock(){ 183 | this->gstate = PyGILState_Ensure(); 184 | } 185 | ~GILLock(){ 186 | PyGILState_Release(this->gstate); 187 | } 188 | }; 189 | 190 | 191 | } // namespace python 192 | -------------------------------------------------------------------------------- /server/src/python_stuff.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | 14 | #pragma once 15 | 16 | #include "python_stuff.hpp" 17 | #include "os.hpp" 18 | #include "errors.hpp" 19 | #include "python.hpp" 20 | #include 21 | #include 22 | 23 | 24 | extern const char* YTDL_FORMAT; 25 | extern const char* FFMPEG_LOCATION; 26 | 27 | 28 | namespace python { 29 | 30 | // youtube_dl.YoutubeDL(options).download(args) 31 | // MODULE INSTANTIATION FUNCTION 32 | 33 | static PyObj ytdl_obj; 34 | 35 | namespace tagem_module { 36 | static PyObject* to_stdout; 37 | 38 | static PyObject* modul; 39 | 40 | static PyObject* file_path; 41 | static PyObject* json_metadata; 42 | 43 | static PyObject* ffmpeg_location; 44 | 45 | static 46 | PyObject* to_stdout_fn(PyObject* const self, PyObject* const args, PyObject* const keyword_args){ 47 | // Overrides to_stdout - saves file path instead of writing to stdout 48 | 49 | // NOTE: self is the module, not the class instantiation, for some reason. 50 | // So as a workaround, static C-string is set, and mutex lock is needed in the calling function 51 | // In reality, the download() function could take many minutes, so a mutex lock is completely infeasible. Hence we must *hope* that there won't be a data race. 52 | 53 | const PyObj data(PyTuple_GetItem(args, 0)); 54 | if (unlikely(data.obj == nullptr)) 55 | Py_RETURN_NONE; 56 | const char* const str = data.as_str(); 57 | Py_INCREF(data.obj); 58 | if (str[0] == '{') 59 | json_metadata = data.obj; 60 | else if (os::is_local_file_or_dir(str)) 61 | file_path = data.obj; 62 | else 63 | Py_DECREF(data.obj); 64 | Py_RETURN_NONE; 65 | } 66 | 67 | static 68 | PyMethodDef _methods[] = { 69 | {"to_stdout", (PyCFunction)to_stdout_fn, METH_VARARGS | METH_KEYWORDS, "Description."}, 70 | {nullptr, nullptr, 0, nullptr} 71 | }; 72 | 73 | struct _state { 74 | PyObject* error; 75 | }; 76 | 77 | static 78 | struct PyModuleDef _module = { 79 | PyModuleDef_HEAD_INIT, 80 | "tagem", 81 | nullptr, 82 | sizeof(struct _state), 83 | _methods, 84 | nullptr, 85 | 0, 86 | 0, 87 | nullptr 88 | }; 89 | } 90 | 91 | 92 | bool is_valid_ytdl_url(const char* const url){ 93 | PyDict<3> opts( 94 | "quiet", Py_False, 95 | "simulate", Py_True, 96 | "skip_download", Py_True 97 | ); 98 | Py_INCREF(Py_False); 99 | Py_INCREF(Py_True); 100 | Py_INCREF(Py_True); 101 | 102 | PyObj iterator(PyObject_GetIter(PyObj(ytdl_obj.call(opts.obj), "_ies").obj)); 103 | if (unlikely(iterator.obj == nullptr)){ 104 | log("Cannot validate ytdl URL"); 105 | return true; 106 | } 107 | PyStr pyurl(url); 108 | while(true){ 109 | PyObj item(PyIter_Next(iterator.obj)); 110 | if (unlikely(item.obj == nullptr)) 111 | break; 112 | if (strcmp(PyObj(item, "__name__").as_str(), "GenericIE") == 0) 113 | continue; 114 | if (item.call_fn_bool("suitable", pyurl.obj)) 115 | return true; 116 | } 117 | log("Invalid ytdl URL: ", url); 118 | return false; 119 | } 120 | 121 | 122 | PyObject* attempt_import_failover(const char* const name){ 123 | log("Unable to import ", name); 124 | abort(); 125 | } 126 | 127 | template 128 | PyObject* attempt_import_failover(const char* const name, const char* const module_name, Strings... module_names){ 129 | PyObject* o = PyImport_ImportModule(module_name); 130 | return o ? o : attempt_import_failover(name, module_names...); 131 | } 132 | 133 | 134 | void init_ytdl(){ 135 | Py_SetProgramName(L"tagem"); 136 | Py_Initialize(); 137 | tagem_module::modul = PyModule_Create(&tagem_module::_module); 138 | tagem_module::to_stdout = PyObject_GetAttrString(tagem_module::modul, "to_stdout"); 139 | tagem_module::ffmpeg_location = PyUnicode_FromString(FFMPEG_LOCATION); 140 | 141 | PyObject* const ytdl_module = attempt_import_failover("youtube-dl", "youtube_dlc", "youtube_dl"); 142 | ytdl_obj.obj = PyObject_GetAttrString(ytdl_module, "YoutubeDL"); 143 | } 144 | 145 | PyObject* create_mysql_obj(const DatabaseInfo& db_info){ 146 | PyObject* const _module = attempt_import_failover("PyMySQL", "pymysql"); 147 | 148 | return PyObj(_module, "connect").call(PyStr(db_info.host()).obj, PyStr(db_info.user()).obj, PyStr(db_info.pwrd()).obj, PyStr(db_info.name()).obj, PyInt(db_info.port()).obj, PyStr(db_info.path()).obj); 149 | } 150 | 151 | } // namespace python 152 | -------------------------------------------------------------------------------- /server/src/qry-cli.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | #include "qry.hpp" 14 | #include 15 | #include 16 | 17 | 18 | int main(const int argc, const char* const* const argv){ 19 | if (argc != 3){ 20 | fprintf(stderr, "USAGE: ./qry 'FILTER STRING HERE ' USER_ID\n"); 21 | fprintf(stderr, 22 | #include "qry-help-text.txt" 23 | ); 24 | return 1; 25 | } 26 | char buf[4096]; 27 | const auto rc = sql_factory::parse_into(buf, argv[1], "0", a2n(argv[2])); 28 | if (rc != sql_factory::selected_field::INVALID) 29 | printf("%s\n", buf); 30 | return rc; 31 | } 32 | -------------------------------------------------------------------------------- /server/src/qry-process_arg-tokens.txt: -------------------------------------------------------------------------------- 1 | THIS LINE HERE IS IMPORTANT DO NOT REMOVE 2 | \0> 3 | return arg::END_OF_STRING 4 | ( > 5 | return arg::bracket_open 6 | ) > 7 | return arg::bracket_close 8 | "> 9 | --str; 10 | return arg::name; 11 | and > 12 | return arg::operator_and 13 | attr > 14 | return arg::attribute 15 | eras > 16 | return arg::eras 17 | backups > 18 | return arg::backups 19 | boxes > 20 | return arg::boxes 21 | tags > 22 | return arg::tags 23 | e > 24 | return arg::era 25 | f > 26 | return arg::file 27 | t > 28 | return arg::tag_tree 29 | t! > 30 | return arg::tag 31 | d > 32 | return arg::dir 33 | d! > 34 | return arg::dir_basename 35 | D exists > 36 | return arg::device_exists 37 | limit > 38 | return arg::limit 39 | not > 40 | return arg::NOT | process_arg(str) 41 | offset > 42 | return arg::offset 43 | or > 44 | return arg::operator_or 45 | order a > 46 | return arg::order_by_asc 47 | order d > 48 | return arg::order_by_desc 49 | order-by-value a > 50 | return arg::order_by_value_asc 51 | order-by-value d > 52 | return arg::order_by_value_desc 53 | same > 54 | return arg::same_attribute 55 | value > 56 | return arg::value 57 | x > 58 | return arg::external_db 59 | ?count > 60 | return arg::select_count 61 | ?list > 62 | return arg::select_list 63 | ?url-and-title-md > 64 | return arg::select_url_and_title__markdown 65 | ?size > 66 | return arg::select_total_size 67 | ?views > 68 | return arg::select_total_views 69 | ?del_local > 70 | return arg::select_delete_local_files 71 | ?check_local > 72 | return arg::select_check_local_files 73 | ?export > 74 | return arg::select_export_results 75 | -------------------------------------------------------------------------------- /server/src/qry.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | #pragma once 14 | 15 | 16 | #include 17 | 18 | 19 | #define JOIN_TAG_BLACKLIST \ 20 | "FROM user2blacklist_tag u2ht " \ 21 | "JOIN tag2parent_tree _t2pt ON _t2pt.parent=u2ht.tag " 22 | 23 | 24 | #define NOT_DISALLOWED_TAG__BASE(...) \ 25 | "NOT EXISTS(" \ 26 | "SELECT 1 " \ 27 | JOIN_TAG_BLACKLIST \ 28 | "WHERE u2ht.user=" __VA_ARGS__ \ 29 | ")" 30 | #define NOT_DISALLOWED_TAG(tag, user_id) \ 31 | NOT_DISALLOWED_TAG__BASE("", user_id, " AND (_t2pt.id=", tag, ")") 32 | #define NOT_DISALLOWED_TAG__COMPILE_TIME(tag, user_id) \ 33 | NOT_DISALLOWED_TAG__BASE(user_id " AND (_t2pt.id=" tag ")") 34 | #define USER_DISALLOWED_DEVICES_INNER_PRE \ 35 | "SELECT 1 " \ 36 | JOIN_TAG_BLACKLIST \ 37 | "JOIN device2tag D2t ON D2t.tag=_t2pt.id " \ 38 | "WHERE u2ht.user=" 39 | #define NOT_DISALLOWED_DEVICE(device, user_id) \ 40 | "NOT EXISTS(" \ 41 | USER_DISALLOWED_DEVICES_INNER_PRE, user_id, \ 42 | " AND D2t.device=" device \ 43 | ")" 44 | #define NOT_DISALLOWED_DEVICE__COMPILE_TIME(device, user_id) \ 45 | "NOT EXISTS(" \ 46 | USER_DISALLOWED_DEVICES_INNER_PRE user_id \ 47 | " AND D2t.device=" device \ 48 | ")" 49 | #define NOT_DISALLOWED_DIR(dir_id, device_id, user_id) \ 50 | "NOT EXISTS(" \ 51 | "SELECT 1 " \ 52 | JOIN_TAG_BLACKLIST \ 53 | "JOIN dir2parent_tree d2pt ON d2pt.id=" dir_id " " \ 54 | "JOIN dir2tag d2t ON d2t.tag=_t2pt.id AND d2t.dir=d2pt.parent " \ 55 | "WHERE u2ht.user=", user_id, \ 56 | ")" \ 57 | "AND " NOT_DISALLOWED_DEVICE(device_id, user_id) 58 | #define NOT_DISALLOWED_FILE(file, dir, device, user_id) \ 59 | "NOT EXISTS(" \ 60 | "SELECT 1 " \ 61 | JOIN_TAG_BLACKLIST \ 62 | "JOIN file2tag f2t ON f2t.tag=_t2pt.id " \ 63 | "WHERE u2ht.user=", user_id, " " \ 64 | "AND f2t.file=", file \ 65 | ")" \ 66 | "AND " NOT_DISALLOWED_DIR(dir, device, user_id) 67 | #define NOT_DISALLOWED_ERA(era, file, dir, device, user_id) \ 68 | "NOT EXISTS(" \ 69 | "SELECT 1 " \ 70 | JOIN_TAG_BLACKLIST \ 71 | "JOIN era2tag e2t ON e2t.tag=_t2pt.id " \ 72 | "WHERE u2ht.user=", user_id, " AND id=" era \ 73 | ")" \ 74 | "AND " NOT_DISALLOWED_FILE(file, dir, device, user_id) 75 | 76 | namespace sql_factory{ 77 | 78 | namespace successness { 79 | enum ReturnType { 80 | ok, 81 | unimplemented, 82 | invalid, 83 | malicious 84 | }; 85 | } 86 | 87 | namespace selected_field { 88 | enum Type { 89 | INVALID, 90 | X_ID, 91 | LIST, 92 | URL_AND_TITLE__MARKDOWN, 93 | TOTAL_SIZE, 94 | TOTAL_VIEWS, 95 | DELETE_LOCAL_FILES, 96 | CHECK_LOCAL_FILES, 97 | EXPORT_RESULTS, 98 | COUNT 99 | }; 100 | } 101 | 102 | selected_field::Type parse_into(char* itr, const char* qry, const std::string& connected_local_devices_str, const unsigned user_id); 103 | 104 | 105 | } // namespace sql_factory 106 | -------------------------------------------------------------------------------- /server/src/skip_to_body.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adam Gray 3 | This file is part of the tagem program. 4 | The tagem program is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as published by the 6 | Free Software Foundation version 3 of the License. 7 | The tagem program is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | */ 13 | #pragma once 14 | 15 | 16 | constexpr 17 | const char* skip_to_body(const char* s){ 18 | while(*(++s) != 0){ 19 | if (*s != '\n') 20 | continue; 21 | if (*(++s) == '\r'){ 22 | if (*(++s) == '\n'){ 23 | if (*(++s) == '\r') 24 | ++s; 25 | return s; 26 | } 27 | } 28 | } 29 | return nullptr; 30 | } 31 | -------------------------------------------------------------------------------- /server/src/static/css/.gitignore: -------------------------------------------------------------------------------- 1 | 3rd-party/* 2 | -------------------------------------------------------------------------------- /server/src/static/css/table_as_blocks.css: -------------------------------------------------------------------------------- 1 | ".tbl-blocks .tbody{" 2 | "max-height:75vh;" 3 | "overflow-y:scroll;" 4 | "}" 5 | ".tbl-blocks .tbody .tr{" 6 | "display:inline-block;" 7 | "overflow:hidden;" 8 | "}" 9 | ".tbl-blocks .tbody .tr{" 10 | "width:256px;" 11 | "}" 12 | 13 | ".tbl-blocks .thead{" 14 | "display:table;" 15 | "}" 16 | ".tbl-blocks .thead .tr{" 17 | "display:table-row;" 18 | "}" 19 | ".tbl-blocks .thead .td{" 20 | "display:table-cell;" 21 | "}" 22 | ".tbl-blocks .thead button{" 23 | "display:block;" 24 | "}" 25 | 26 | ".tbl-blocks .table{" 27 | "padding:10px;" 28 | "border:#136 solid 1px;" 29 | "}" 30 | -------------------------------------------------------------------------------- /server/src/static/css/table_as_table.css: -------------------------------------------------------------------------------- 1 | ".tbl-table .thead{" 2 | "display:table-header-group;" 3 | "}" 4 | ".tbl-table .tbody{" 5 | "display:table-row-group;" 6 | "}" 7 | 8 | ".tbl-table .th{" 9 | "display:table-cell;" 10 | "}" 11 | ".tbl-table .tr{" 12 | "display:table-row;" 13 | "}" 14 | ".tbl-table .td{" 15 | "display:table-cell;" 16 | "}" 17 | -------------------------------------------------------------------------------- /server/src/static/js/.gitignore: -------------------------------------------------------------------------------- 1 | 3rd-party/* 2 | -------------------------------------------------------------------------------- /server/src/static/js/add_to_db.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | function $$$nickname2fullname(obj_type){ 14 | switch(obj_type){ 15 | case 'f': 16 | return 'file'; 17 | case 'd': 18 | return 'directory'; 19 | case 'D': 20 | return 'device'; 21 | case 'P': 22 | return 'protocol'; 23 | case 't': 24 | return 'tag'; 25 | } 26 | } 27 | 28 | function $$$add_to_db(obj_type){ 29 | const queue = $$$document_getElementById('add-'+obj_type+'-queue'); 30 | 31 | if(obj_type==='t'){ 32 | const tag_names = []; 33 | queue.innerText.replace(/(?:^|\n)([^\n]+)/g, function(group0, group1){ 34 | tag_names.push(group1); 35 | }); 36 | if(tag_names.length===0) 37 | return; 38 | const tagselect = $$$document_getElementById('tagselect-self-p'); 39 | const parent_ids = $$$select3__get_csv(tagselect); 40 | if(parent_ids === "") 41 | return; 42 | $$$ajax_POST_data_w_text_response("/t/add/"+parent_ids+"/", tag_names.join("\n"), function(){ 43 | $$$select3__wipe_values(tagselect); 44 | queue.innerHTML = ""; // Remove URLs 45 | $$$alert("Success"); 46 | }); 47 | return; 48 | } 49 | 50 | const urls = [...queue.innerText.matchAll(/[^\n]+/g)]; 51 | if(urls.length===0){ 52 | $$$alert("No URLs"); 53 | return; 54 | } 55 | const tagselect = $$$document_getElementById('tagselect-files'); 56 | const tag_ids = $$$select3__get_csv(tagselect); 57 | if(tag_ids === ""){ 58 | // TODO: Replace with confirmation dialog 59 | $$$alert("No tags"); 60 | return; 61 | } 62 | 63 | const backup_dir_id = $$$get_dir_id_to_backup_into(); 64 | if((obj_type==='f')&&(backup_dir_id===""||backup_dir_id===null)){ 65 | $$$alert("Backup requested, but no directory selected"); 66 | return; 67 | } 68 | 69 | $$$ajax_POST_data_w_text_response( 70 | obj_type + "/add/" + tag_ids+"/" + backup_dir_id + "/" + $$$is_ytdl_checked() + "/" + $$$is_audio_only_checked(), // is_ytdl_checked etc is meaningless for anything other than files. Safe to include though. 71 | urls.join("\n"), 72 | function(){ 73 | $$$select3__wipe_values(tagselect); 74 | queue.innerHTML = ""; // Remove URLs 75 | $$$alert("Success"); 76 | if((obj_type!=='f')&&(obj_type!=='d')) 77 | $$$refetch_json(obj_type, 'a/'+obj_type+'.json'); 78 | } 79 | ); 80 | } 81 | 82 | function $$$get_dir_id_to_backup_into(){ 83 | const x = $$$document_getElementById_add_f_backup_toggle; 84 | if($$$is_node_hidden(x)||(!x.checked)) 85 | return "0"; 86 | return $$$get_dirselect_value(); 87 | } 88 | 89 | function $$$uncheck_dl_locally(){ 90 | $$$document_getElementById_add_f_backup_toggle.checked = false; 91 | } 92 | 93 | function $$$is_ytdl_checked(){ 94 | return $$$document_getElementById_add_f_backup_ytdl.checked ? 1 : 0; 95 | } 96 | 97 | function $$$is_audio_only_checked(){ 98 | return $$$document_getElementById_audio_only.checked ? 1 : 0; 99 | } 100 | 101 | function $$$reset_audio_only_checked(){ 102 | $$$document_getElementById_audio_only.checked = false; 103 | } 104 | 105 | function $$$add_to_db__append(obj_type){ 106 | const inp = $$$document_getElementById('add-' + obj_type + '-input'); 107 | const x = inp.value; 108 | if(x === ""){ 109 | $$$alert("Enter a tag or URL"); 110 | return; 111 | } 112 | $$$document_getElementById('add-' + obj_type + '-queue').innerText += "\n" + x; 113 | $$$select3__wipe_values(inp); 114 | } 115 | -------------------------------------------------------------------------------- /server/src/static/js/admin.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Adam Gray 2 | // This file is part of the tagem program. 3 | // The tagem program is free software: you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License as published by the 5 | // Free Software Foundation version 3 of the License. 6 | // The tagem program is distributed in the hope that it will be useful, but 7 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 9 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 10 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 11 | 12 | var $$$users_tags_dict; 13 | 14 | function $$$toggle_admin_dashboard(){ 15 | if(!$$$logged_in()) 16 | return; 17 | $$$hide_all_except(['admin-dashboard']); 18 | $$$ajax_GET_w_JSON_response("/a/users.json",function(data){ 19 | [$$$users_dict, $$$users_tags_dict] = data; 20 | }); 21 | } 22 | 23 | 24 | function $$$recursively_record_filesystem_dir_dialog(){ 25 | $$$toggle('record-fs-dir'); 26 | } 27 | 28 | function $$$recursively_record_filesystem_dir(){ 29 | const s = $$$prompt("!!!MACRO!!!PATH_OF_DIRECTORY_EXAMPLE") !!!MACRO!!!JS__REPLACE_PATH_SEP; 30 | if((s==="")||(s===null)) 31 | return; 32 | const max_depth = $$$get_int_with_prompt("Maximum depth - 0 being 'no limit'"); 33 | if (max_depth === null) 34 | return; 35 | let tag_ids = $$$select3__get_csv($$$document_getElementById("tagselect-record-fs-dir")); 36 | if (tag_ids === "") 37 | tag_ids = "0"; 38 | $$$ajax_POST_data_w_text_response( 39 | "/record-local-dir/"+max_depth+"/"+tag_ids, 40 | s, 41 | function(){ 42 | $$$hide('record-fs-dir'); 43 | } 44 | ); 45 | } 46 | 47 | function $$$generate_thumbs(){ 48 | $$$ajax_POST_w_text_response_generic_success("/gen-thumbs/"); 49 | } 50 | 51 | function $$$update_video_metadatas(){ 52 | $$$ajax_POST_w_text_response_generic_success("/update-vid-metas/"); 53 | } 54 | 55 | function $$$guess_mimetypes(){ 56 | $$$ajax_POST_w_text_response_generic_success("/guess-mimes/"); 57 | } 58 | 59 | function $$$get_id_of_user_currently_being_edited(){ 60 | return $$$select3__get_csv($$$document_getElementById('select-user-for-edit')); 61 | } 62 | 63 | function $$$user_tag_bl_row(id,name){ 64 | return "
"+name+"
Remove
"; 65 | } 66 | 67 | function $$$load_user_for_edit(user_id_name_tpls){ 68 | const [name,vals,bl] = $$$users_dict[$$$get_id_of_user_currently_being_edited()]; 69 | $$$document_getElementById("edit-user-permissions-username").innerText = name; 70 | let s = ""; 71 | const ls = $$$get_tbl_body("edit-user-permissions-tbl").getElementsByClassName("tr"); 72 | const vals_ls = vals.split(","); 73 | for(let i=0, n=vals_ls.length; i r.text().then(fn)) 28 | .catch(function(){ 29 | $$$err_alert(); 30 | throw Error(`Server returned ${r.status}: ${r.statusText}`); 31 | }); 32 | } 33 | function $$$ajax_w_JSON_response(mthd,url,fn){ 34 | fetch( 35 | url, 36 | { 37 | method:mthd, 38 | credentials:"include", 39 | headers:headers, 40 | } 41 | ).then(r => { 42 | if(!r.ok){ 43 | $$$err_alert(); 44 | throw Error(`Server returned ${r.status}: ${r.statusText}`); 45 | } 46 | r.json().then(fn); 47 | }); 48 | } 49 | 50 | function $$$ajax_data_w_err(mthd,url,resp_type,data,succ,err){ 51 | fetch( 52 | url, 53 | { 54 | method:mthd, 55 | body:data, 56 | credentials:"include", 57 | headers:headers, 58 | } 59 | ).then(r => r.text().then(succ)) 60 | .catch(err); 61 | } 62 | 63 | function $$$ajax_data_w_JSON_response_and_err(mthd,url,data,fn,err){ 64 | fetch( 65 | url, 66 | { 67 | method:mthd, 68 | body:data, 69 | credentials:"include", 70 | headers:headers, 71 | } 72 | ).then(r => { 73 | if(!r.ok){ 74 | err(); 75 | throw Error(`Server returned ${r.status}: ${r.statusText}`); 76 | } 77 | r.json().then(fn); 78 | }); 79 | } 80 | 81 | function $$$ajax_data_w_JSON_response(mthd,url,data,fn){ 82 | $$$ajax_data_w_JSON_response_and_err(mthd,url,data,fn,$$$err_alert); 83 | } 84 | 85 | function $$$ajax_POST_w_JSON_response(url,fn){ 86 | $$$ajax_w_JSON_response("POST",url,fn); 87 | } 88 | function $$$ajax_POST_data_w_err(url,resp_type,data,succ,err){ 89 | $$$ajax_data_w_err("POST",url,resp_type,data,succ,err); 90 | } 91 | function $$$ajax_POST_data_w_JSON_response_and_err(url,data,succ,err){ 92 | $$$ajax_data_w_JSON_response_and_err("POST",url,data,succ,err); 93 | } 94 | function $$$ajax_POST_data(url,resp_type,data,fn){ 95 | $$$ajax_POST_data_w_err(url,resp_type,data,fn,$$$err_alert); 96 | } 97 | function $$$ajax_POST_data_w_text_response(url,data,fn){ 98 | $$$ajax_POST_data(url,"text",data,fn); 99 | } 100 | function $$$ajax_POST_data_w_JSON_response(url,data,fn){ 101 | $$$ajax_POST_data_w_JSON_response_and_err(url,data,fn,$$$err_alert); 102 | } 103 | 104 | function $$$ajax_POST_data_w_text_response_generic_success(url,data){ 105 | $$$ajax_POST_data_w_text_response(url,data,$$$alert_success); 106 | } 107 | 108 | function $$$ajax_GET_w_JSON_response(url,fn){ 109 | $$$ajax_w_JSON_response("GET",url,fn); 110 | } 111 | 112 | function $$$ajax_GET_w_text_response(url,fn){ 113 | $$$ajax("GET","text",url,fn); 114 | } 115 | 116 | function $$$ajax_POST_w_text_response(url,fn){ 117 | $$$ajax("POST","text",url,fn); 118 | } 119 | 120 | function $$$ajax_POST_w_json_response(url,fn){ 121 | $$$ajax("POST","json",url,fn); 122 | } 123 | 124 | function $$$ajax_POST_w_text_response_generic_success(url){ 125 | $$$ajax_POST_w_text_response(url,$$$alert_success); 126 | } 127 | -------------------------------------------------------------------------------- /server/src/static/js/alert.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | function $$$alert(s){ 14 | alert(s); 15 | } 16 | 17 | function $$$alert_success(){ 18 | $$$alert("Success"); 19 | } 20 | 21 | function $$$alert_requires_login(){ 22 | $$$alert("Not authorised.\nPlease log in first."); 23 | $$$throw_error(); 24 | } 25 | 26 | function $$$error_alert(title, text){ 27 | $$$alert(title + "\n" + text); 28 | $$$throw_error(); 29 | } 30 | 31 | function $$$err_alert(r,title,text){ 32 | $$$error_alert(r.statusText, text + "\nfor url: " + this.url); 33 | $$$throw_error(); 34 | } 35 | -------------------------------------------------------------------------------- /server/src/static/js/colour-picker.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | 14 | function $$$set_css_colour(name,val){ 15 | const x = "--custom-"+name; 16 | if(val === undefined) 17 | val = $$$css_root.getPropertyValue(x); 18 | else 19 | $$$css_root.setProperty(x,val); 20 | $$$local_storage_store("css_colour_"+name, val); 21 | } 22 | 23 | function $$$on_css_colour_picker_change(e){ 24 | const x = e.target; 25 | $$$set_css_colour(x.dataset.for,x.value); 26 | } 27 | 28 | function $$$set_css_theme(name){ 29 | $$$local_storage_store("css-theme",name); 30 | $$$document_body.classList.add(name); 31 | for(let opt of $$$document_getElementById("css-theme").childNodes){ 32 | if(opt.value !== name){ 33 | $$$document_body.classList.remove(opt.value); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/src/static/js/cookies.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | function $$$set_cookie(name, value, lifetime){ 14 | document.cookie = name + '=' + value + ';max-age=' + lifetime + ';path=/;samesite=strict;'; 15 | // NOTE: Previous cookies are NOT overwritten 16 | } 17 | function $$$set_cookie_forever(name, value){ 18 | $$$set_cookie(name,value,999999999); 19 | } 20 | function $$$get_cookie(name){ 21 | const regexp = new RegExp('(?:^|;) ?' + name + "=([^;]+)"); 22 | const x = regexp.exec(document.cookie); 23 | if (x === null) 24 | return undefined; 25 | return x[1]; 26 | } 27 | function $$$unset_cookie(name){ 28 | document.cookie = name + '=;max-age=-1;'; 29 | } 30 | 31 | function $$$merge_into(dict, d2){ 32 | for (const [key, value] of Object.entries(d2)){ 33 | dict[key] = value; 34 | } 35 | } 36 | 37 | function $$$del_keys(dict, keys){ 38 | for (const key of keys){ 39 | delete dict[key]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /server/src/static/js/description.js: -------------------------------------------------------------------------------- 1 | var $$$descr_contents; // NOTE: Only used within this file 2 | 3 | function $$$get_descr_obj(){ 4 | return $$$document_getElementById_descr; 5 | } 6 | function $$$set_description(s){ 7 | // Called when displaying files, tags and dirs, not when the user edits it 8 | $$$get_descr_obj().innerText = s; 9 | $$$descr_contents = s; 10 | } 11 | function $$$get_description(){ 12 | return $$$get_descr_obj().innerText; 13 | } 14 | 15 | function $$$mousedown_on_descr(){ 16 | $$$descr_contents = $$$get_description(); 17 | } 18 | 19 | function $$$mouseleave_descr(){ 20 | const s = $$$get_description(); 21 | if(s != $$$descr_contents){ 22 | if (['f','d','t'].includes($$$currently_viewing_object_type)){ 23 | $$$ajax_POST_data_w_text_response( 24 | $$$currently_viewing_object_type+"/descr/"+$$$id_of_currently_viewed_page(), 25 | $$$get_description(), 26 | $$$mousedown_on_descr 27 | ); 28 | } else { 29 | $$$alert("Current object cannot have a description updated"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/static/js/devices.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | function $$$add_devices_dialog(){ 14 | $$$hide_all_except(['tagselect-files-container', 'add-D-dialog']); 15 | } 16 | -------------------------------------------------------------------------------- /server/src/static/js/dirs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Adam Gray 2 | // This file is part of the tagem program. 3 | // The tagem program is free software: you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License as published by the 5 | // Free Software Foundation version 3 of the License. 6 | // The tagem program is distributed in the hope that it will be useful, but 7 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 9 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 10 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 11 | 12 | function $$$set_profile_name_from_this_dir(){ 13 | $$$set_profile_name($$$dir_name); 14 | } 15 | function $$$display_related_dirs(dir_id,n,arr){ 16 | n.innerHTML = arr.filter(x => x[0]!==dir_id).map(([id,name]) => "" + $$$escape_html_text(name) + "").join("
"); 17 | } 18 | 19 | 20 | function $$$view_dir(_dir_id_or_path, is_not_in_db, page){ 21 | if(_dir_id_or_path===null) 22 | // From prompt 23 | return; 24 | if(_dir_id_or_path==="") 25 | _dir_id_or_path=$$$dir_id; 26 | if(page===undefined) 27 | page = 0; 28 | let ls = ['f','f-action-btns','tags-container','parents-container','children-container','tagselect-dirs-container','tagselect-files-container','tagselect-files-btn']; 29 | if(!is_not_in_db){ 30 | ls.push('merge-files-btn'); 31 | ls.push('backup-files-btn'); 32 | ls.push('view-as-playlist-btn'); 33 | } 34 | $$$hide_all_except(ls); 35 | 36 | $$$file_tagger_fn = $$$after_tagged_selected_files; 37 | $$$dir_tagger_fn = $$$after_tagged_this_dir; 38 | $$$get_file_ids = $$$get_selected_file_ids; 39 | $$$get_dir_ids = $$$get_this_dir_id; 40 | 41 | if (_dir_id_or_path !== undefined){ 42 | if(is_not_in_db){ 43 | $$$dir_id = "0"; // $$$dir_id will be set by the call to populate_f_table 44 | $$$populate_f_table(':', null, _dir_id_or_path,page); 45 | $$$set_window_location_hash(':'+page+'/'+_dir_id_or_path); 46 | }else{ 47 | $$$dir_id = _dir_id_or_path; 48 | $$$ajax_GET_w_JSON_response("a/d/i/"+$$$dir_id, function(data){ 49 | $$$dir_name = data[0]; 50 | $$$display_related_dirs($$$dir_id,$$$parent_tags_ls,data[1]); 51 | $$$display_related_dirs($$$dir_id,$$$child_tags_ls, data[2]); 52 | $$$display_tags(data[3], "tags", "$$$unlink_this_tag_from_this", 'd'); 53 | $$$set_profile_name_from_this_dir(); 54 | }); 55 | $$$populate_f_table('d', $$$dir_id, null, page); 56 | $$$set_window_location_hash('d'+page+'/'+$$$dir_id); 57 | } 58 | }else{ 59 | // Returning to directory listing via "Dir" tab/button 60 | $$$set_window_location_hash('d'+page+'/'+$$$dir_id); 61 | // BUG: This switches :PAGE/directory/path/ to dPAGE/DIR_ID 62 | } 63 | 64 | if(is_not_in_db){ 65 | $$$set_profile_name(_dir_id_or_path); 66 | }else if(_dir_id_or_path===undefined){ 67 | $$$set_profile_name_from_this_dir(); 68 | } 69 | $$$set_profile_thumb('d'); 70 | } 71 | function $$$get_selected_dir_ids(){ 72 | return $$$get_selected_ids('d'); 73 | } 74 | function $$$get_this_dir_id(){ 75 | return $$$dir_id; 76 | } 77 | 78 | function $$$after_tagged_this_dir(ids, tags){ 79 | $$$display_tags_add(tags, 'tags', 'd') 80 | } 81 | function $$$after_tagged_selected_dirs(ids, tags){ 82 | $$$after_tagged_selected_stuff('d',ids,tags,3); 83 | } 84 | 85 | function $$$display_d_tbl(data){ 86 | let s = ""; 87 | $$$get_dir_ids = $$$get_selected_dir_ids; 88 | for (const [id, name, device, tag_ids, size] of data[0]){ 89 | s += "
"; 90 | s += "
"; 91 | s += "" + $$$escape_html_text(name) + ""; 92 | s += "
"; 93 | s += "
"; 94 | s += "Device"; 95 | s += "
"; 96 | s += "
" + size + "
"; 97 | s += "
" + tag_ids + "
"; 98 | s += "
"; 99 | } 100 | $$$get_tbl_body("d").innerHTML = s; 101 | } 102 | 103 | function $$$view_dirs(ids){ 104 | $$$ajax_GET_w_JSON_response( 105 | "a/d/id/0/"+ids.join(","), 106 | $$$display_d_tbl 107 | ); 108 | } 109 | 110 | function $$$setup_page_for_d_tbl(){ 111 | $$$hide_all_except(['d']); 112 | $$$dir_tagger_fn = $$$after_tagged_selected_dirs; 113 | $$$get_dir_ids = $$$get_selected_dir_ids; 114 | } 115 | 116 | function $$$add_dirs_dialog(){ 117 | $$$hide_all_except(['tagselect-files-container', 'add-d-dialog']); 118 | } 119 | -------------------------------------------------------------------------------- /server/src/static/js/era.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Adam Gray 2 | // This file is part of the tagem program. 3 | // The tagem program is free software: you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License as published by the 5 | // Free Software Foundation version 3 of the License. 6 | // The tagem program is distributed in the hope that it will be useful, but 7 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 9 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 10 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 11 | 12 | var $$$era_start = null; 13 | var $$$era_end = null; 14 | 15 | function $$$set_era_vertex(){ 16 | const t = ($$$active_media.currentTime || $$$active_media.getCurrentTime()); 17 | if(t===undefined) 18 | return; 19 | const T = ($$$active_media.duration || $$$active_media.getDuration()); 20 | if($$$era_start===null){ 21 | $$$era_start = t; 22 | }else{ 23 | $$$era_end = t; 24 | $$$ask_user_to_input_tags($$$tag_era); 25 | } 26 | } 27 | 28 | function $$$tag_era(tags){ 29 | $$$ajax_POST_w_text_response( 30 | "/e/add/"+$$$file_id+"/"+$$$era_start+"-"+$$$era_end+"/"+tags.map(x => x.id).join(","), 31 | function(){ 32 | $$$document_getElementById_eras_info_tbody.innerHTML += $$$create_era_info_row([0,$$$era_start,$$$era_end,tags.map(x => "" + x.text + "")]); 33 | $$$era_start = null; 34 | } 35 | ); 36 | } 37 | 38 | function $$$setup_page_for_e_tbl(){ 39 | $$$hide_all_except(['f','view-as-playlist-btn']); 40 | $$$set_profile_name("Eras"); 41 | } 42 | 43 | function $$$ytdl_era(node){ 44 | if($$$dirselect.value===""){ 45 | $$$alert("No directory selected"); 46 | return; 47 | } 48 | $$$ajax_POST_data_w_text_response("/e/dl/"+$$$dirselect.value+"/"+node.dataset.id,function(){ 49 | $$$select3__wipe_values($$$dirselect.value); 50 | $$$alert("Saved to '" + $$$file_id + "@" + node.dataset.start + "-" + node.dataset.end + ".mkv'"); 51 | }); 52 | } 53 | 54 | function $$$create_era_info_row(era){ 55 | const [id,start,end,era_tag_ids] = era; 56 | let _s = ""; 57 | _s += ""; 58 | _s += "" + id + ""; 59 | _s += "" + $$$t2human(start) + ""; 60 | _s += "" + $$$t2human(end) + ""; 61 | _s += "" + era_tag_ids + ""; 62 | _s += "youtube-dl"; 63 | _s += ""; 64 | return _s; 65 | } 66 | 67 | function $$$era_tagger_fn(ids,t_dict){ 68 | for(x of $$$document_getElementById_eras_info_tbody){ 69 | if(!ids.includes(x.dataset.id)) 70 | continue; 71 | x.getElementsByClassName('td')[3].innerHTML += "and some new tags"; 72 | } 73 | } 74 | 75 | function $$$get_era_ids(){ 76 | let ids = ""; 77 | for(let node of $$$get_tbl_body('eras-info').getElementsByClassName('selected1')){ 78 | if(node.dataset.id==="0"){ 79 | $$$alert("Can only tag eras with non-zero IDs.\nSome eras have ID of zero, probably because they are newly added eras."); 80 | return ""; 81 | }else 82 | ids += "," + node.dataset.id 83 | } 84 | return ids.substr(1); 85 | } 86 | 87 | function $$$get_file_id_and_selected_era_start_and_ends_csv(){ 88 | const fid = $$$get_file_id(false); 89 | let csv = ""; 90 | for(let node of $$$get_tbl_body('eras-info').getElementsByClassName('selected1')){ 91 | csv += "," + fid + "@" + node.dataset.start + "-" + node.dataset.end; 92 | } 93 | return (csv==="") ? fid : csv.substr(1); 94 | } 95 | -------------------------------------------------------------------------------- /server/src/static/js/error.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | function $$$throw_error(){ 14 | throw new Error(); 15 | } 16 | -------------------------------------------------------------------------------- /server/src/static/js/escape-html.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | var $$$dummy_text_node = $$$document.createElement('div'); 14 | function $$$escape_html_text(s){ 15 | $$$dummy_text_node.textContent = s; 16 | return $$$dummy_text_node.innerHTML; 17 | } 18 | -------------------------------------------------------------------------------- /server/src/static/js/get-selected-ids.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | function $$$get_selected_ids(tbl){ 14 | let ids = ""; 15 | for(let node of $$$get_tbl_body(tbl).getElementsByClassName('selected1')){ 16 | ids += "," + node.dataset.id 17 | } 18 | return ids.substr(1); 19 | } 20 | -------------------------------------------------------------------------------- /server/src/static/js/globals.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Adam Gray 2 | // This file is part of the tagem program. 3 | // The tagem program is free software: you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License as published by the 5 | // Free Software Foundation version 3 of the License. 6 | // The tagem program is distributed in the hope that it will be useful, but 7 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 9 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 10 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 11 | 12 | const $$$MAX_RESULTS_PER_PAGE = 100; 13 | 14 | const $$$YOUTUBE_DEVICE_ID = "1"; 15 | 16 | var $$$currently_viewing_object_type; 17 | 18 | var $$$mouse_is_down = false; 19 | 20 | var $$$tag_id; 21 | var $$$file_id; 22 | var $$$file_name; 23 | var $$$dir_id; 24 | var $$$dir_name; 25 | var $$$device_id; 26 | var $$$mimetype; 27 | var $$$file_tags; 28 | 29 | // External database vars 30 | var $$$db_id; 31 | var $$$post_id; 32 | var $$$user_id; 33 | var $$$user_name; 34 | 35 | // Don't mangle these names 36 | var t; 37 | var f; 38 | var d; 39 | var D; 40 | var P; 41 | var x; 42 | var mt; // mimetypes 43 | var f2; // CSV of file2 variable names that needs to be split into an array 44 | var $$$f2_as_array; 45 | var $$$f2_for_select3; 46 | var $$$yt_player; 47 | 48 | // User options stored in localStorage 49 | var $$$use_regex; 50 | var $$$prioritise_local_autoplay; 51 | 52 | const $$$stylesheet_opts = ["blocks","table"]; 53 | 54 | const $$$tbl2namecol = {"f2":0,"t":0,"d":0,"D":0,"P":0,"$$$users_dict":0}; 55 | 56 | const $$$BLANK_IMG_SRC = "data:,"; 57 | 58 | var $$$get_file_ids; 59 | var $$$get_tag_ids; 60 | var $$$get_dir_ids; 61 | var $$$file_tagger_fn; // Allows for updating file tag lists when they have tags manually added 62 | var $$$dir_tagger_fn; 63 | 64 | 65 | // Admin variables 66 | var $$$users_dict; // Only really used by functions in this file, but *referred to* - for initialisation of select2 selects - in document-on-ready.js and here 67 | 68 | 69 | // Pointers to DOM objects (only those available in the root HTML; initialised after document loading) 70 | var $$$document_getElementById_username; 71 | var $$$document_getElementById_view; 72 | var $$$document_getElementById_values; 73 | var $$$dirselect; 74 | var $$$document_getElementById_f; 75 | var $$$document_getElementById_t; 76 | var $$$document_getElementById_d; 77 | var $$$document_getElementById_cmnts; 78 | var $$$document_getElementById_autoplay; 79 | var $$$document_getElementById_descr; 80 | var $$$document_getElementById_tasks; 81 | var $$$document_getElementById_qry; 82 | 83 | var $$$parent_tags_ls; 84 | var $$$child_tags_ls; 85 | var $$$sibling_tags_ls; 86 | 87 | var $$$qry_help; 88 | var $$$qry_textarea; 89 | 90 | var $$$document_getElementById_audio_only; 91 | var $$$document_getElementById_add_f_backup_ytdl; 92 | var $$$document_getElementById_profile_name; 93 | var $$$document_getElementById_view_img; 94 | var $$$document_getElementById_profile_img; 95 | var $$$document_getElementById_post_user; 96 | var $$$document_getElementById_add_f_backup_toggle; 97 | var $$$text_edit; 98 | var $$$view_btns; 99 | var $$$document_getElementById_eras_info_tbody; 100 | var $$$recent_pages; 101 | 102 | var $$$tagselect_popup_container; 103 | var $$$tagselect_popup_btn; 104 | var $$$tagselect_popup; 105 | 106 | var $$$css_root; 107 | var $$$document_body; 108 | 109 | // for f in *.js; do sed -i -E "s/([\$][\$][\$]document_getElementById)[(]['\"](eras-info-tbody)['\"][)]/\1_eras_info_tbody/g" "$f"; done 110 | -------------------------------------------------------------------------------- /server/src/static/js/hidden.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Adam Gray 2 | // This file is part of the tagem program. 3 | // The tagem program is free software: you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License as published by the 5 | // Free Software Foundation version 3 of the License. 6 | // The tagem program is distributed in the hope that it will be useful, but 7 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 9 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 10 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 11 | 12 | function $$$hide_node(x){ 13 | x.classList.add("hidden"); 14 | } 15 | function $$$unhide_node(x){ 16 | x.classList.remove("hidden"); 17 | } 18 | function $$$toggle_node(x){ 19 | x.classList.toggle("hidden"); 20 | } 21 | 22 | function $$$hide(id){ 23 | $$$hide_node($$$document_getElementById(id)); 24 | } 25 | function $$$unhide(id){ 26 | $$$unhide_node($$$document_getElementById(id)); 27 | } 28 | 29 | function $$$hide_class(s){ 30 | $$$for_node_in_document_getElementsByClassName(s, $$$hide_node); 31 | } 32 | function $$$unhide_class(s){ 33 | $$$for_node_in_document_getElementsByClassName(s, $$$unhide_node); 34 | } 35 | 36 | function $$$set_visibility(id,b){ 37 | $$$set_node_visibility($$$document_getElementById(id),b); 38 | } 39 | 40 | function $$$set_node_visibility(node,b){ 41 | if(b) 42 | node.classList.remove("hidden"); 43 | else 44 | node.classList.add("hidden"); 45 | } 46 | 47 | function $$$is_node_hidden(node){ 48 | return node.classList.contains("hidden"); 49 | } 50 | 51 | 52 | function $$$hide_all_except(ids,classes){ 53 | // More like 'page switch' event now 54 | if(!ids.includes('file-info')) 55 | $$$playlist_file_ids = undefined; // Destroy playlist 56 | $$$document_getElementById_profile_img.removeAttribute('onclick'); 57 | $$$hide_class('help'); 58 | for(const id of [ 59 | 'qry-textarea', 60 | 'admin-dashboard', 61 | 'text-editor', 62 | 'text-edit', 63 | 'add-f-backup-ytdl-container', 64 | 'audio-only-container', 65 | 'add-f-backup-toggle-container', 66 | 'add-f-backup', 67 | 'f', 68 | 'd', 69 | 't', 70 | 'merge-files-btn', 71 | 'backup-files-btn', 72 | 'view-as-playlist-btn', 73 | 'tasks-container', 74 | 'file-info', 75 | 'next-f-in-playlist', 76 | 'user-info', 77 | 'post-container', 78 | 'file2-container', 79 | 'file2', 80 | 'meta', 81 | 't-added', 82 | 't1', 83 | 't2', 84 | 'descr', 85 | 'values-container', 86 | 'tags-container', 87 | 'parents-container', 88 | 'siblings-container', 89 | 'children-container', 90 | 'dirselect', 91 | 'tagselect-files-container', 92 | 'tagselect-files-btn', 93 | 'tagselect-era-container', 94 | 'tagselect-eras-container', 95 | 'tagselect-dirs-container', 96 | 'tagselect-self-p-container', 97 | 'tagselect-self-p-btn', 98 | 'tagselect-self-c-container', 99 | 'tagselect-self-c-btn', 100 | 'tagselect-self-s-container', 101 | 'tagselect-self-s-btn', 102 | 'add-t-dialog', 103 | 'add-f-dialog', 104 | 'add-d-dialog', 105 | 'add-D-dialog', 106 | 'orig-src-dialog' 107 | ]){ 108 | if(ids.includes(id)) 109 | $$$unhide(id); 110 | else 111 | $$$hide(id); 112 | } 113 | for(const c of [ 114 | 'repeatness', 115 | 'file-meta' 116 | ]){ 117 | if((classes===undefined)||(!classes.includes(c))) 118 | $$$hide_class(c); 119 | else 120 | $$$unhide_class(c); 121 | } 122 | } 123 | function $$$is_visible(id){ 124 | return (!$$$document_getElementById(id).classList.contains('hidden')); 125 | } 126 | function $$$toggle(id){ 127 | $$$toggle_node($$$document_getElementById(id)); 128 | } 129 | function $$$toggle_class(x){ 130 | $$$for_node_in_document_getElementsByClassName(x, $$$toggle_node); 131 | } 132 | 133 | 134 | function $$$toggle_sidebar(id){ 135 | $$$toggle(id); 136 | // TODO: Set CSS variables to be able to reclaim the space 137 | } 138 | -------------------------------------------------------------------------------- /server/src/static/js/history.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | function $$$add_window_location_hash_to_history(s){ 14 | if((s==="")||($$$document.title==="")) 15 | return; 16 | for(node of $$$recent_pages.childNodes){ 17 | if(node.innerText===$$$document.title) 18 | return; 19 | } 20 | if($$$recent_pages.childNodes.length===10) 21 | $$$recent_pages.removeChild($$$recent_pages.childNodes[9]); 22 | $$$recent_pages.innerHTML = "" + $$$document.title + "" + $$$recent_pages.innerHTML; // WARNING: Not escaped 23 | } 24 | function $$$set_window_location_hash(s){ 25 | const S = "#" + s; 26 | $$$add_window_location_hash_to_history(S); 27 | $$$currently_viewing_object_type = s[0]; 28 | if(S!=history.state) 29 | history.pushState(S, "", S); 30 | } 31 | function $$$unset_window_location_hash(){ 32 | $$$set_window_location_hash(""); 33 | } 34 | -------------------------------------------------------------------------------- /server/src/static/js/humanise.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Adam Gray 3 | // This file is part of the tagem program. 4 | // The tagem program is free software: you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License as published by the 6 | // Free Software Foundation version 3 of the License. 7 | // The tagem program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 10 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 11 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 12 | // 13 | const $$$BYTES_UNITS=['','K','M','G','T','P']; 14 | 15 | function $$$bytes2human(n){ 16 | if(n===null) 17 | return "N/A"; 18 | let i = 0; 19 | while(n > 1024){ 20 | ++i; 21 | n /= 1024; 22 | } 23 | return n.toFixed(1) + ' ' + $$$BYTES_UNITS[i] + 'iB'; 24 | } 25 | 26 | // Dehumanise 27 | function $$$_abs_human2bytes(n, unit, magnitude){ 28 | return n*(magnitude**$$$BYTES_UNITS.indexOf(unit)) 29 | } 30 | function $$$human2bytes(s){ 31 | const [_, n, unit, ibi_or_iga] = new RegExp('^([0-9]+(?:\.[0-9]+)?)(|[KMGTP])(i)?B$').exec(s); 32 | return $$$_abs_human2bytes(parseFloat(n), unit, (ibi_or_iga===undefined)?1000:1024) 33 | } 34 | 35 | 36 | const $$$NUMBER_UNITS=['','K','M','B','T']; 37 | 38 | function $$$n2human(n){ 39 | if(n===null) 40 | return "N/A"; 41 | let i = 0; 42 | while(n > 1024){ 43 | ++i; 44 | n /= 1024; 45 | } 46 | return n.toFixed(1) + $$$NUMBER_UNITS[i]; 47 | } 48 | -------------------------------------------------------------------------------- /server/src/static/js/init.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Adam Gray 2 | // This file is part of the tagem program. 3 | // The tagem program is free software: you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License as published by the 5 | // Free Software Foundation version 3 of the License. 6 | // The tagem program is distributed in the hope that it will be useful, but 7 | // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 9 | // This copyright notice should be included in any copy or substantial copy of the tagem source code. 10 | // The absense of this copyright notices on some other files in this project does not indicate that those files do not also fall under this license, unless they have a different license written at the top of the file. 11 | 12 | const $$$dummy = function(){}; 13 | var $$$YTPlayer_onReady_fn = $$$dummy; 14 | function $$$YTPlayer_onReady(){ 15 | $$$YTPlayer_onReady_fn(); 16 | } 17 | function onYouTubeIframeAPIReady(){ 18 | // NOTE: Must not be name-mangled - used by YouTube IFrame API 19 | $$$yt_player = new YT.Player('yt-player', { 20 | height: '390', 21 | width: '640', 22 | events:{ 23 | 'onError': $$$on_media_error, 24 | 'onReady': $$$YTPlayer_onReady, 25 | 'onStateChange':$$$YTPlayer_onStateChange 26 | } 27 | }); 28 | $$$when_data_loaded(); 29 | } 30 | 31 | function $$$load_page_from_a_hash_string(S){ 32 | const s = decodeURI(S); 33 | let _x = s.substr(2); 34 | const c = s.substr(1,1); 35 | const _xs = /([^\/]+)\/(.*)$/.exec(_x); 36 | switch(c){ 37 | case "f": $$$view_file(_x); break; 38 | case "F": $$$view_files_as_playlist(_x); break; 39 | case "t": $$$view_tag(_xs[2],_xs[1]); break; 40 | case "x": 41 | $$$db_id = _xs[1]; 42 | _x = _x2.substr(1); 43 | switch(_xs[2][0]){ 44 | case "u": $$$view_user($$$db_id, _x); break; 45 | } 46 | break; 47 | case "$": $$$view_files_by_value(_x); break; 48 | case "d": 49 | case ":": 50 | $$$view_dir(_xs[2], (c===":"), _xs[1]); 51 | break; 52 | case '?': 53 | $$$view_qry(_x); 54 | break; 55 | default: 56 | !!!MACRO!!!ACTION_ON_DOCUMENT_LOAD 57 | ; // In case the macro is empty 58 | } 59 | } 60 | 61 | function $$$init_file2_input_for_file2_var_of_index(id){ 62 | const [name,min,max,conversion] = $$$f2[id]; 63 | const inp = $$$document_getElementById('file2-value'); 64 | $$$set_visibility('file2-value-dt', (conversion===2)); 65 | $$$set_visibility('file2-value', (conversion!==2)); 66 | switch(conversion){ 67 | // NOTE: Enum values are listed in file2.hpp 68 | case 0: // integer 69 | inp.type = "text"; 70 | inp.pattern = "\d+"; 71 | inp.placeholder = "12345"; 72 | inp.min = min; 73 | inp.max = max; 74 | break; 75 | case 2: // datetime 76 | inp.type = "datetime-local"; // WARNING: Only Chrome has a nice GUI input, Firefox has mere text 77 | break; 78 | default: 79 | $$$alert("Not implemented yet"); 80 | //case 1: // string 81 | //case 3: // multiline string // TODO: Use