├── .github ├── CODEOWNERS ├── workflows │ ├── auto-author-assign.yml │ ├── codesee-arch-diagram.yml │ ├── c-cpp.yml │ └── codeql-analysis.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── task.md │ ├── bug-report.md │ └── feature-request.md └── config.yml ├── .gitignore ├── CITATION.cff ├── SECURITY.md ├── CMakeLists.txt ├── PKGBUILD ├── LICENSE ├── Makefile ├── CONTRIBUTING.md ├── .all-contributorsrc ├── CODE_OF_CONDUCT.md ├── README.md └── src └── mya.c /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jmakhack 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | /.idea 4 | /.vscode 5 | /bin 6 | /obj 7 | /Makefile 8 | .DS_Store 9 | CMakeFiles/* 10 | cmake_install.cmake 11 | CMakeCache.txt 12 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Mak" 5 | given-names: "Josh" 6 | title: "myanimelist-cli" 7 | version: 0.1.0 8 | date-released: 2020-11-13 9 | url: "https://github.com/jmakhack/myanimelist-cli" 10 | -------------------------------------------------------------------------------- /.github/workflows/auto-author-assign.yml: -------------------------------------------------------------------------------- 1 | name: Auto Author Assign 2 | 3 | on: 4 | pull_request_target: 5 | types: [ opened, reopened ] 6 | 7 | permissions: 8 | pull-requests: write 9 | 10 | jobs: 11 | assign-author: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: toshimaru/auto-author-assign@v1.6.0 -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Associated Issue 2 | 8 | Closes #ISSUE_NUMBER 9 | 10 | ## Implemented Solution 11 | 16 | -------------------------------------------------------------------------------- /.github/workflows/codesee-arch-diagram.yml: -------------------------------------------------------------------------------- 1 | # This workflow was added by CodeSee. Learn more at https://codesee.io/ 2 | # This is v2.0 of this workflow file 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request_target: 8 | types: [opened, synchronize, reopened] 9 | 10 | name: CodeSee 11 | 12 | permissions: read-all 13 | 14 | jobs: 15 | codesee: 16 | runs-on: ubuntu-latest 17 | continue-on-error: true 18 | name: Analyze the repo with CodeSee 19 | steps: 20 | - uses: Codesee-io/codesee-action@v2 21 | with: 22 | codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Task 3 | about: Create a task that requires work but is neither a bug nor enhancement 4 | title: "[TASK] " 5 | 6 | --- 7 | 8 | ## Task Context 9 | 10 | 11 | ## Acceptance Criteria 12 | 13 | 14 | ## Additional Context 15 | 16 | 17 | Please read through the [Contributing to the Project](https://github.com/jmakhack/myanimelist-cli/blob/master/CONTRIBUTING.md) document before working on this project. 18 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Supported Versions 2 | 3 | | Version | Supported | 4 | | ------- | ------------------ | 5 | | < 1.0 | :x: | 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Check to see if the vulnerability has already been reported in [Issues](https://github.com/jmakhack/myanimelist-cli/issues). 10 | Report any newly discovered vulnerabilities by submitting a [bug report](https://github.com/jmakhack/myanimelist-cli/issues/new?assignees=&labels=bug%2C+up+for+grabs&template=bug-report.md&title=%5BBUG%5D+) with details about the vulnerability. 11 | Please be as detailed as possible when describing the vulnerability in the issue description. 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(mya) 3 | set(CMAKE_C_STANDARD 11 ) 4 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall") 5 | 6 | if(UNIX) 7 | set(LINUX TRUE) 8 | endif() 9 | 10 | set(SRC_DIR src) 11 | set(INC_DIR include) 12 | 13 | add_executable(mya ${SRC_DIR}/mya.c) 14 | 15 | set_target_properties(mya PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") 16 | 17 | target_include_directories(mya PRIVATE ${INC_DIR}) 18 | 19 | set(LIBS curl json-c bsd) 20 | if(LINUX) 21 | list(APPEND LIBS) 22 | endif() 23 | target_link_libraries(mya ${LIBS}) 24 | -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: make clean 17 | run: make clean 18 | - name: Update apt-get 19 | run: sudo apt-get update 20 | - name: Install libcurl 21 | run: sudo apt-get install libcurl4-openssl-dev 22 | - name: Install libjson-c 23 | run: sudo apt-get install libjson-c-dev 24 | - name: Install libbsd-dev 25 | run: sudo apt-get install libbsd-dev 26 | - name: cmake 27 | run: cmake CMakeLists.txt 28 | - name: make all 29 | run: make all 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a bug report to help us improve the project 4 | title: "[BUG] " 5 | labels: bug, up for grabs, C 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug Description 11 | 12 | 13 | ## Expected Behavior 14 | 15 | 16 | ## Environment 17 | 18 | Operating System: \[e.g. macOS Catalina\] 19 | 20 | ## Additional Context 21 | 22 | 23 | Please read through the [Contributing to the Project](https://github.com/jmakhack/myanimelist-cli/blob/master/CONTRIBUTING.md) document before working on this project. 24 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Josh Mak 2 | # Co-Maintainer: N.Pranav Krishna 3 | pkgname=mya-git 4 | pkgver=r134.587fa4f 5 | pkgrel=1 6 | pkgdesc="A CLI tool for fetching data from MyAnimeList" 7 | arch=('x86_64') 8 | url="https://github.com/jmakhack/myanimelist-cli/tree/master" 9 | license=('MIT') 10 | depends=('libcurl-compat' 'json-c' 'libbsd') 11 | makedepends=('git' 'gcc' 'curl') 12 | source=("mya-git::git+https://github.com/jmakhack/myanimelist-cli.git") 13 | md5sums=('SKIP') 14 | 15 | pkgver(){ 16 | cd "$pkgname" 17 | printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short=7 HEAD)" 18 | } 19 | 20 | build(){ 21 | cd "$pkgname" 22 | make 23 | } 24 | package(){ 25 | cd "$pkgname" 26 | install -Dm755 "./bin/mya" "$pkgdir/usr/bin/mya" 27 | install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname" 28 | } 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea or enhancement for this project 4 | title: "[FEATURE] " 5 | labels: enhancement, up for grabs, C 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe 11 | 12 | 13 | ## Describe the solution you'd like 14 | 15 | 16 | ## Describe any possible alternatives 17 | 18 | 19 | ## Additional context 20 | 21 | 22 | Please read through the [Contributing to the Project](https://github.com/jmakhack/myanimelist-cli/blob/master/CONTRIBUTING.md) document before working on this project. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joshua Mak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | newIssueWelcomeComment: > 2 | Thanks for your contribution in creating this myanimelist-cli issue! 3 | 4 | If you like what we're building here, please give our project a star. 5 | 6 | ![https://media1.tenor.com/images/650a651cefb981999f0187b798a598a0/tenor.gif](https://media1.tenor.com/images/650a651cefb981999f0187b798a598a0/tenor.gif) 7 | 8 | newPRWelcomeComment: > 9 | Thanks for opening this pull request for myanimelist-cli! 10 | 11 | Please wait shortly for someone to review. 12 | 13 | ![https://media1.tenor.com/images/de54dcf0d5723e5c190b36aed008917f/tenor.gif](https://media1.tenor.com/images/de54dcf0d5723e5c190b36aed008917f/tenor.gif) 14 | 15 | firstPRMergeComment: > 16 | Congrats on merging your first pull request to myanimelist-cli! 17 | 18 | Please give our project a star if you like what we are building here. 19 | 20 | ![https://media1.tenor.com/images/7eaebbe5e4d63fc9b5f029d0bab363e3/tenor.gif](https://media1.tenor.com/images/7eaebbe5e4d63fc9b5f029d0bab363e3/tenor.gif) 21 | 22 | sentimentBotToxicityThreshold: .7 23 | 24 | sentimentBotReplyComment: > 25 | Please be courteous and respectful of other users. cc/ @jmakhack 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | # 3 | # Project _|_|_|_| _|_| _| _| _|_|_| 4 | # _| _| _| _| _| _| _| 5 | # _| _| _| _| _| _| _| _| 6 | # _| _| _| _|_|_| _|_|_|_| 7 | # _| 8 | # _|_| 9 | # 10 | # 11 | # Copyright (C) 2020 - 2023, Joshua Mak, et al. 12 | # 13 | ############################################################################ 14 | 15 | .DEFAULT_GOAL:=all 16 | CC:=gcc 17 | CFLAGS:=-c -g -Wall -std=c11 18 | DIRGUARD=@mkdir -p $(@D) 19 | ODIR:=obj 20 | SDIR:=src 21 | BDIR:=bin 22 | EXEC:=mya 23 | LIBS:=-lcurl -ljson-c -lbsd 24 | UNAME:=$(shell uname) 25 | 26 | ifeq ($(UNAME),Darwin) 27 | LIBS+=-largp 28 | endif 29 | 30 | TARGET:=$(BDIR)/$(EXEC) 31 | 32 | _OBJ:=$(EXEC).o 33 | OBJ:=$(patsubst %,$(ODIR)/%,$(_OBJ)) 34 | 35 | $(TARGET): $(OBJ) 36 | $(DIRGUARD) 37 | $(CC) -o $@ $^ $(LIBS) 38 | 39 | $(ODIR)/%.o: $(SDIR)/%.c 40 | $(DIRGUARD) 41 | $(CC) $(CFLAGS) $< -o $@ 42 | 43 | .PHONY: all clean 44 | 45 | all: $(TARGET) 46 | 47 | clean: 48 | rm -rf $(ODIR) $(BDIR) *~ 49 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '33 4 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'cpp' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Generate Build 53 | run: | 54 | make clean 55 | sudo apt-get update 56 | sudo apt-get install libcurl4-openssl-dev 57 | sudo apt-get install libjson-c-dev 58 | sudo apt-get install libbsd-dev 59 | make all 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 https://git.io/JvXDl 63 | 64 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 65 | # and modify them (or add more) to build your code if your project 66 | # uses a compiled language 67 | 68 | #- run: | 69 | # make bootstrap 70 | # make release 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v1 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Project 2 | 3 | Hey there! 4 | 5 | Thanks for looking into how you're able to contribute to the project! 6 | 7 | Check the [Issues](https://github.com/jmakhack/myanimelist-cli/issues) tab on GitHub to check the open issues. 8 | 9 | Feel free to ping @jmakhack in the comments of any issue. 10 | 11 | ## Building the Project 12 | 13 | To build the project, execute the following steps: 14 | 15 | ``` 16 | - cmake CMakeLists.txt 17 | - make all 18 | - ./bin/mya -V 19 | ``` 20 | 21 | If any errors surface, make sure to download and install all the needed libraries. 22 | 23 | View the `Included Libraries` section below for more info. 24 | 25 | ## Using the MyAnimeList API 26 | 27 | In order to properly setup the project, a Client ID from the MyAnimeList API is required. 28 | 29 | Go to [https://myanimelist.net/apiconfig](https://myanimelist.net/apiconfig) and create a new Client ID. 30 | 31 | Note that this also requires having/creating a MyAnimeList account. 32 | 33 | Afterwards, set the `CLIENT_ID` field in `src/mya.c` to your generated Client ID. 34 | 35 | This should allow you to run the project with actual data coming from MyAnimeList. 36 | 37 | For more info on the API docs, visit [https://myanimelist.net/clubs.php?cid=13727](https://myanimelist.net/clubs.php?cid=13727) 38 | 39 | ## Contributing 40 | 41 | In case you'd like to contribute to this project, there are a couple things that you'll have to do: 42 | 43 | 1. Fork the project from [https://github.com/jmakhack/myanimelist-cli](https://github.com/jmakhack/myanimelist-cli) 44 | 2. Clone the github project which you just forked 45 | 3. Make the adjustments to solve the issue/ticket 46 | 4. Add your changes and push them to any branch 47 | 5. Visit [https://github.com/jmakhack/myanimelist-cli](https://github.com/jmakhack/myanimelist-cli) and press on the `Contribute` button. 48 | 6. Create the pull request, and wait for reviews and/or approval. 49 | 50 | And that's it! Thanks for contributing to the project! 51 | 52 | ## Code Quality / Conventions 53 | Please apply this style to your code before setting up the pull request, so we're able to remain a similar and professional code style. 54 | 55 | For coding in C, it's a good practice to follow along the following code standard: [https://users.ece.cmu.edu/~eno/coding/CCodingStandard.html](https://users.ece.cmu.edu/~eno/coding/CCodingStandard.html) 56 | 57 | ## Testing 58 | 59 | At moment of writing, testing is not possible yet for this project. 60 | 61 | ## Included Libraries 62 | 63 | The following artifacts / tools are used by and included in the extension as-is: 64 | 65 | - Argp: [https://www.gnu.org/software/libc/manual/html_node/Argp.html](https://www.gnu.org/software/libc/manual/html_node/Argp.html) 66 | - Curl: [https://curl.se/libcurl/c/](https://curl.se/libcurl/c/) 67 | - Json-C: [http://json-c.github.io/json-c/](http://json-c.github.io/json-c/) 68 | - Libbsd: [https://libbsd.freedesktop.org/wiki/](https://libbsd.freedesktop.org/wiki/) 69 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "jmakhack", 10 | "name": "jmakhack", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/1442227?v=4", 12 | "profile": "https://github.com/jmakhack", 13 | "contributions": [ 14 | "code", 15 | "maintenance", 16 | "doc" 17 | ] 18 | }, 19 | { 20 | "login": "all-contributors", 21 | "name": "All Contributors", 22 | "avatar_url": "https://avatars.githubusercontent.com/u/46410174?v=4", 23 | "profile": "https://allcontributors.org", 24 | "contributions": [ 25 | "doc" 26 | ] 27 | }, 28 | { 29 | "login": "TBeeren", 30 | "name": "Tim Beeren", 31 | "avatar_url": "https://avatars.githubusercontent.com/u/36151761?v=4", 32 | "profile": "https://www.linkedin.com/in/tim-beeren-88355615b/", 33 | "contributions": [ 34 | "test", 35 | "code", 36 | "doc" 37 | ] 38 | }, 39 | { 40 | "login": "VividhPandey003", 41 | "name": "Vividh Pandey", 42 | "avatar_url": "https://avatars.githubusercontent.com/u/91251535?v=4", 43 | "profile": "https://vividhpandey.netlify.app/", 44 | "contributions": [ 45 | "maintenance" 46 | ] 47 | }, 48 | { 49 | "login": "Nikhil-1503", 50 | "name": "Nikhil Shanbhag", 51 | "avatar_url": "https://avatars.githubusercontent.com/u/61755381?v=4", 52 | "profile": "https://github.com/Nikhil-1503", 53 | "contributions": [ 54 | "maintenance" 55 | ] 56 | }, 57 | { 58 | "login": "AdityaJ7", 59 | "name": "Aditya Jetely", 60 | "avatar_url": "https://avatars.githubusercontent.com/u/42397096?v=4", 61 | "profile": "https://adityaj7.github.io/", 62 | "contributions": [ 63 | "maintenance" 64 | ] 65 | }, 66 | { 67 | "login": "MuriloucoLouco", 68 | "name": "Murilo Leandro", 69 | "avatar_url": "https://avatars.githubusercontent.com/u/58440129?v=4", 70 | "profile": "https://muriloucolouco.github.io/", 71 | "contributions": [ 72 | "code", 73 | "maintenance" 74 | ] 75 | }, 76 | { 77 | "login": "Sakshi-75", 78 | "name": "Sakshi Jain", 79 | "avatar_url": "https://avatars.githubusercontent.com/u/20265098?v=4", 80 | "profile": "https://github.com/Sakshi-75", 81 | "contributions": [ 82 | "code" 83 | ] 84 | }, 85 | { 86 | "login": "dependabot", 87 | "name": "Dependabot", 88 | "avatar_url": "https://avatars.githubusercontent.com/u/27347476?v=4", 89 | "profile": "https://github.com/features/security", 90 | "contributions": [ 91 | "maintenance" 92 | ] 93 | }, 94 | { 95 | "login": "ahmedheltaher", 96 | "name": "Ahmed Eltaher", 97 | "avatar_url": "https://avatars.githubusercontent.com/u/42752070?v=4", 98 | "profile": "https://www.linkedin.com/in/ahmedheltaher/", 99 | "contributions": [ 100 | "code" 101 | ] 102 | }, 103 | { 104 | "login": "sameersecond", 105 | "name": "Sameer", 106 | "avatar_url": "https://avatars.githubusercontent.com/u/101405993?v=4", 107 | "profile": "https://github.com/sameersecond", 108 | "contributions": [ 109 | "code", 110 | "doc" 111 | ] 112 | }, 113 | { 114 | "login": "metal-oopa", 115 | "name": "Sudip Banerjee", 116 | "avatar_url": "https://avatars.githubusercontent.com/u/70171925?v=4", 117 | "profile": "https://portfolio-metaloopa.vercel.app/", 118 | "contributions": [ 119 | "code" 120 | ] 121 | }, 122 | { 123 | "login": "The-Debarghya", 124 | "name": "Debarghya Maitra", 125 | "avatar_url": "https://avatars.githubusercontent.com/u/79015784?v=4", 126 | "profile": "https://github.com/The-Debarghya", 127 | "contributions": [ 128 | "code", 129 | "doc" 130 | ] 131 | }, 132 | { 133 | "login": "j-karthik", 134 | "name": "Karthik", 135 | "avatar_url": "https://avatars.githubusercontent.com/u/26465882?v=4", 136 | "profile": "https://github.com/j-karthik", 137 | "contributions": [ 138 | "code" 139 | ] 140 | }, 141 | { 142 | "login": "Godson-Gnanaraj", 143 | "name": "Godson", 144 | "avatar_url": "https://avatars.githubusercontent.com/u/30664729?v=4", 145 | "profile": "https://github.com/Godson-Gnanaraj", 146 | "contributions": [ 147 | "code" 148 | ] 149 | }, 150 | { 151 | "login": "npranav7619", 152 | "name": "N.Pranav Krishna", 153 | "avatar_url": "https://avatars.githubusercontent.com/u/52345192?v=4", 154 | "profile": "https://npranav7619.github.io/", 155 | "contributions": [ 156 | "code", 157 | "maintenance" 158 | ] 159 | }, 160 | { 161 | "login": "viv1kk", 162 | "name": "Vivek", 163 | "avatar_url": "https://avatars.githubusercontent.com/u/35196281?v=4", 164 | "profile": "https://github.com/viv1kk", 165 | "contributions": [ 166 | "code", 167 | "doc" 168 | ] 169 | } 170 | ], 171 | "contributorsPerLine": 7, 172 | "projectName": "myanimelist-cli", 173 | "projectOwner": "jmakhack", 174 | "repoType": "github", 175 | "repoHost": "https://github.com", 176 | "skipCi": true, 177 | "commitConvention": "angular", 178 | "commitType": "docs" 179 | } 180 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 24 | * Focusing on what is best not just for us as individuals, but for the overall community 25 | 26 | Examples of unacceptable behavior include: 27 | 28 | * The use of sexualized language or imagery, and sexual attention or advances of any kind 29 | * Trolling, insulting or derogatory comments, and personal or political attacks 30 | * Public or private harassment 31 | * Publishing others' private information, such as a physical or email address, without their explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a professional setting 33 | 34 | ## Enforcement Responsibilities 35 | 36 | Community leaders are responsible for clarifying and enforcing our standards of 37 | acceptable behavior and will take appropriate and fair corrective action in 38 | response to any behavior that they deem inappropriate, threatening, offensive, 39 | or harmful. 40 | 41 | Community leaders have the right and responsibility to remove, edit, or reject 42 | comments, commits, code, wiki edits, issues, and other contributions that are 43 | not aligned to this Code of Conduct, and will communicate reasons for moderation 44 | decisions when appropriate. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all community spaces, and also applies when 49 | an individual is officially representing the community in public spaces. 50 | Examples of representing our community include using an official e-mail address, 51 | posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. 53 | 54 | ## Enforcement 55 | 56 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 57 | reported to the community leaders responsible for enforcement. 58 | All complaints will be reviewed and investigated promptly and fairly. 59 | 60 | All community leaders are obligated to respect the privacy and security of the 61 | reporter of any incident. 62 | 63 | ## Enforcement Guidelines 64 | 65 | Community leaders will follow these Community Impact Guidelines in determining 66 | the consequences for any action they deem in violation of this Code of Conduct: 67 | 68 | ### 1. Correction 69 | 70 | **Community Impact**: Use of inappropriate language or other behavior deemed 71 | unprofessional or unwelcome in the community. 72 | 73 | **Consequence**: A private, written warning from community leaders, providing 74 | clarity around the nature of the violation and an explanation of why the 75 | behavior was inappropriate. A public apology may be requested. 76 | 77 | ### 2. Warning 78 | 79 | **Community Impact**: A violation through a single incident or series 80 | of actions. 81 | 82 | **Consequence**: A warning with consequences for continued behavior. No 83 | interaction with the people involved, including unsolicited interaction with 84 | those enforcing the Code of Conduct, for a specified period of time. This 85 | includes avoiding interactions in community spaces as well as external channels 86 | like social media. Violating these terms may lead to a temporary or 87 | permanent ban. 88 | 89 | ### 3. Temporary Ban 90 | 91 | **Community Impact**: A serious violation of community standards, including 92 | sustained inappropriate behavior. 93 | 94 | **Consequence**: A temporary ban from any sort of interaction or public 95 | communication with the community for a specified period of time. No public or 96 | private interaction with the people involved, including unsolicited interaction 97 | with those enforcing the Code of Conduct, is allowed during this period. 98 | Violating these terms may lead to a permanent ban. 99 | 100 | ### 4. Permanent Ban 101 | 102 | **Community Impact**: Demonstrating a pattern of violation of community 103 | standards, including sustained inappropriate behavior, harassment of an 104 | individual, or aggression toward or disparagement of classes of individuals. 105 | 106 | **Consequence**: A permanent ban from any sort of public interaction within 107 | the community. 108 | 109 | ## Attribution 110 | 111 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 112 | version 2.0, available at 113 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 114 | 115 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 116 | enforcement ladder](https://github.com/mozilla/diversity). 117 | 118 | For answers to common questions about this code of conduct, see the FAQ at 119 | https://www.contributor-covenant.org/faq. Translations are available at 120 | https://www.contributor-covenant.org/translations. 121 | 122 | [homepage]: https://www.contributor-covenant.org 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## myanimelist-cli 2 | 3 | myanimelist-cli is a minimalistic command line interface for fetching user anime data from [MyAnimeList](https://myanimelist.net/) 4 | 5 | [![C/C++ CI](https://github.com/jmakhack/mya/actions/workflows/c-cpp.yml/badge.svg?branch=master)](https://github.com/jmakhack/mya/actions/workflows/c-cpp.yml) 6 | [![CodeQL](https://github.com/jmakhack/mya/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/jmakhack/mya/actions/workflows/codeql-analysis.yml) 7 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/a30e5356c06e4381a0b5ea92aba65ada)](https://www.codacy.com/gh/jmakhack/myanimelist-cli/dashboard?utm_source=github.com&utm_medium=referral&utm_content=jmakhack/myanimelist-cli&utm_campaign=Badge_Grade) 8 | [![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors-) 9 | 10 | 11 | ## Usage 12 | ``` 13 | $ mya --help 14 | Usage: mya [OPTION...] [USERNAME] 15 | Simple command line tool for fetching user anime data from MyAnimeList. 16 | 17 | -a, --all Fetch all anime for a user 18 | -c, --completed Fetch a user's completed anime 19 | -d, --dropped Fetch a user's dropped anime 20 | -h, --onhold Fetch a user's on hold anime 21 | -p, --plantowatch Fetch a user's plan to watch anime 22 | -w, --watching Fetch a user's currently watching anime 23 | -s, --sfw Fetch only SFW anime 24 | -?, --help Give this help list 25 | --usage Give a short usage message 26 | -V, --version Print program version 27 | ``` 28 | 29 | ## Example 30 | ``` 31 | $ mya -w jmak 32 | watching 4 anime 33 | 1. Commit on Titan 34 | 2. JoJo's Bizarre Pull Request 35 | 3. My Neighbor Octocat 36 | 4. One Push Man 37 | ``` 38 | 39 | ## Contributing 40 | For info on how to contribute to myanimelist-cli, please refer to the [Contributing to the Project](CONTRIBUTING.md) document. 41 | 42 | ## Code of Conduct 43 | myanimelist-cli is governed by the [Contributor Covenant v2.0](CODE_OF_CONDUCT.md). 44 | 45 | ## License 46 | myanimelist-cli is licensed under the [MIT License](LICENSE). 47 | 48 | ## Contributors ✨ 49 | 50 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
jmakhack
jmakhack

💻 🚧 📖
All Contributors
All Contributors

📖
Tim Beeren
Tim Beeren

⚠️ 💻 📖
Vividh Pandey
Vividh Pandey

🚧
Nikhil Shanbhag
Nikhil Shanbhag

🚧
Aditya Jetely
Aditya Jetely

🚧
Murilo Leandro
Murilo Leandro

💻 🚧
Sakshi Jain
Sakshi Jain

💻
Dependabot
Dependabot

🚧
Ahmed Eltaher
Ahmed Eltaher

💻
Sameer
Sameer

💻 📖
Sudip Banerjee
Sudip Banerjee

💻
Debarghya Maitra
Debarghya Maitra

💻 📖
Karthik
Karthik

💻
Godson
Godson

💻
N.Pranav Krishna
N.Pranav Krishna

💻 🚧
Vivek
Vivek

💻 📖
82 | 83 | 84 | 85 | 86 | 87 | 88 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 89 | -------------------------------------------------------------------------------- /src/mya.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define MIN_USERNAME_LENGTH 2 11 | #define MAX_USERNAME_LENGTH 16 12 | #define MAX_ENDPOINT_LENGTH 15 13 | #define PAGE_SIZE 1000 14 | #define CLIENT_ID_ENV getenv("MYANIMELIST_CLIENT_ID") 15 | #define CLIENT_ID "YOUR TOKEN HERE" 16 | 17 | /* define constants for program options */ 18 | #define OPT_WATCHING 'w' 19 | #define OPT_COMPLETED 'c' 20 | #define OPT_ONHOLD 'h' 21 | #define OPT_DROPPED 'd' 22 | #define OPT_PLANTOWATCH 'p' 23 | #define OPT_ALL 'a' 24 | #define OPT_SFW 's' 25 | 26 | /* constants for ANSI color codes */ 27 | #define ANSI_CODE_CYAN "\033" "[0;36m" 28 | #define ANSI_CODE_YELLOW "\033" "[1;33m" 29 | #define ANSI_CODE_MAGENTA "\033" "[1;35m" 30 | #define ANSI_CODE_RESET "\033" "[0m" 31 | 32 | /* initialize argp vars */ 33 | const char *argp_program_version = "mya v0.1.0"; 34 | static char doc[] = "Simple command line tool for fetching user anime data from MyAnimeList."; 35 | static char args_doc[] = "[USERNAME]"; 36 | static struct argp_option options[] = { 37 | { "watching", OPT_WATCHING, 0, 0, "Fetch a user's currently watching anime" }, 38 | { "completed", OPT_COMPLETED, 0, 0, "Fetch a user's completed anime" }, 39 | { "onhold", OPT_ONHOLD, 0, 0, "Fetch a user's on hold anime" }, 40 | { "dropped", OPT_DROPPED, 0, 0, "Fetch a user's dropped anime" }, 41 | { "plantowatch", OPT_PLANTOWATCH, 0, 0, "Fetch a user's plan to watch anime" }, 42 | { "all", OPT_ALL, 0, 0, "Fetch all anime for a user" }, 43 | { "sfw", OPT_SFW, 0, 0, "Fetch only SFW anime" }, 44 | { 0 }, 45 | }; 46 | 47 | /* struct to keep track of selected options and arguments */ 48 | struct arguments { 49 | enum { WATCHING_MODE, COMPLETED_MODE, HOLD_MODE, DROPPED_MODE, PLAN_MODE, ALL_MODE } mode; 50 | int nsfw; 51 | char *args[1]; 52 | }; 53 | 54 | /* struct to store curl fetch payload and size */ 55 | struct curl_fetch_st { 56 | char *payload; 57 | size_t size; 58 | }; 59 | 60 | /* 61 | * Function: validate_username 62 | * --------------------------- 63 | * Runs validation on the inputted username by checking: 64 | * - username is at least 2 characters long 65 | * - username is no more than 16 characters long 66 | * - username only contains letters, numbers, underscores and dashes only 67 | * 68 | * If validation fails, an error message is displayed and the program exits 69 | * 70 | * username: the username string to validate 71 | */ 72 | void validate_username (char *username) { 73 | /* create username buffer to ensure null terminated string */ 74 | char username_buf[MAX_USERNAME_LENGTH + 2]; 75 | strlcpy(username_buf, username, sizeof(username_buf)); 76 | size_t username_len = strlen(username_buf); 77 | 78 | /* initialize regex vars */ 79 | regex_t regex; 80 | const char *pattern = "^[a-zA-Z0-9_-]+$"; 81 | size_t nmatch = 1; 82 | regmatch_t pmatch[1]; 83 | 84 | /* check if username is acceptable length */ 85 | if (((int)username_len < MIN_USERNAME_LENGTH) || ((int)username_len > MAX_USERNAME_LENGTH)) { 86 | fprintf(stderr, "Username must be between %d and %d characters in length\n", MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH); 87 | exit(argp_err_exit_status); 88 | } 89 | 90 | /* check if regex pattern is valid */ 91 | if (regcomp(®ex, pattern, REG_EXTENDED) != 0) { 92 | fprintf(stderr, "Failed to compile username validation regex\n"); 93 | exit(argp_err_exit_status); 94 | } 95 | 96 | /* check if username contains only letters, numbers, underscores and dashes */ 97 | if (regexec(®ex, username, nmatch, pmatch, 0) != 0) { 98 | fprintf(stderr, "Please enter a valid username (letters, numbers, underscores and dashes only)\n"); 99 | exit(argp_err_exit_status); 100 | } 101 | 102 | /* free regex var */ 103 | regfree(®ex); 104 | } 105 | 106 | /* 107 | * Function: parse_opt 108 | * ------------------- 109 | * Parses all the various options and arguments when running the program 110 | * 111 | * key: specifies which option or non-option argument to parse 112 | * arg: string value of argument or NULL if unapplicable 113 | * state: pointer to argp_state struct 114 | * 115 | * returns: 0 on success, otherwise an error value 116 | */ 117 | static error_t parse_opt (int key, char *arg, struct argp_state *state) { 118 | struct arguments *arguments = state->input; 119 | switch (key) { 120 | 121 | /* parse available options */ 122 | case OPT_WATCHING: arguments->mode = WATCHING_MODE; break; 123 | case OPT_COMPLETED: arguments->mode = COMPLETED_MODE; break; 124 | case OPT_ONHOLD: arguments->mode = HOLD_MODE; break; 125 | case OPT_DROPPED: arguments->mode = DROPPED_MODE; break; 126 | case OPT_PLANTOWATCH: arguments->mode = PLAN_MODE; break; 127 | case OPT_ALL: arguments->mode = ALL_MODE; break; 128 | case OPT_SFW: arguments->nsfw = 0; break; 129 | 130 | /* parse arguments */ 131 | case ARGP_KEY_ARG: 132 | /* show usage info if more than one argument given */ 133 | if (state->arg_num >= 1) { 134 | argp_usage(state); 135 | } 136 | validate_username(arg); 137 | arguments->args[state->arg_num] = arg; 138 | break; 139 | 140 | /* validate number of arguments */ 141 | case ARGP_KEY_END: 142 | if (state->arg_num < 1) { 143 | argp_usage(state); 144 | } 145 | break; 146 | 147 | /* error if invalid option provided */ 148 | default: return ARGP_ERR_UNKNOWN; 149 | } 150 | 151 | /* parse success */ 152 | return 0; 153 | } 154 | 155 | /* struct for specifying argument parsing behavior */ 156 | static struct argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 }; 157 | 158 | /* 159 | * Function: curl_callback 160 | * ----------------------- 161 | * Curl callback which assigns the delivered data to a properly sized struct buffer 162 | * 163 | * contents: pointer to the data 164 | * size: always 1 165 | * nmemb: size of the data 166 | * userp: pointer to the struct to store the data 167 | * 168 | * returns: number of bytes in processed data 169 | */ 170 | size_t curl_callback (void *contents, size_t size, size_t nmemb, void *userp) { 171 | /* calculate size of incoming data */ 172 | size_t rsize = size * nmemb; 173 | struct curl_fetch_st *p = (struct curl_fetch_st *) userp; 174 | 175 | /* allocate enough memory to hold data payload */ 176 | char *temp = realloc(p->payload, (int)p->size + (int)rsize + 1); 177 | 178 | /* error if memory allocation fails */ 179 | if (!temp) { 180 | fprintf(stderr, "Failed to expand buffer for fetch payload"); 181 | free(p->payload); 182 | return 0; 183 | } 184 | 185 | /* write incoming data into curl struct */ 186 | p->payload = temp; 187 | memcpy(&(p->payload[p->size]), contents, rsize); 188 | p->size += rsize; 189 | p->payload[p->size] = 0; 190 | 191 | /* return size of the data payload in bytes */ 192 | return rsize; 193 | } 194 | 195 | /* 196 | * Function: curl_fetch_url 197 | * ------------------------ 198 | * Fetch the data from the url and capture the return code 199 | * 200 | * curl: curl handle 201 | * url: url to fetch data from 202 | * fetch: pointer to fetch struct 203 | * 204 | * returns: return code of the url fetch 205 | */ 206 | CURLcode curl_fetch_url (CURL *curl, const char *url, struct curl_fetch_st *fetch) { 207 | /* initialize curl struct values */ 208 | fetch->payload = (char *) calloc(1, sizeof(fetch->payload)); 209 | fetch->size = 0; 210 | 211 | /* error if memory allocation fails */ 212 | if (!(fetch->payload)) { 213 | fprintf(stderr, "Failed to allocate payload"); 214 | return CURLE_FAILED_INIT; 215 | } 216 | 217 | /* set the client id header */ 218 | struct curl_slist *chunk = NULL; 219 | size_t client_id_header_size = 50; 220 | char client_id_header[client_id_header_size]; 221 | strlcpy(client_id_header, "X-MAL-CLIENT-ID:", client_id_header_size); 222 | 223 | /* if environment variable doesn't exist, use hard coded client id value */ 224 | if (CLIENT_ID_ENV == NULL) { 225 | strlcat(client_id_header, CLIENT_ID, client_id_header_size); 226 | } else { 227 | strlcat(client_id_header, CLIENT_ID_ENV, client_id_header_size); 228 | } 229 | 230 | /* add client id header to request */ 231 | chunk = curl_slist_append(chunk, client_id_header); 232 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); 233 | 234 | /* set url to retreive data from */ 235 | curl_easy_setopt(curl, CURLOPT_URL, url); 236 | 237 | /* set user agent request header */ 238 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0"); 239 | 240 | /* disable follow location */ 241 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0); 242 | 243 | /* set curl timeout to 30 seconds */ 244 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30); 245 | 246 | /* set struct to copy incoming data into */ 247 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) fetch); 248 | 249 | /* set callback for handling the writing of incoming data */ 250 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback); 251 | 252 | /* return curl code returned from request */ 253 | return curl_easy_perform(curl); 254 | } 255 | 256 | /* 257 | * Function: generate_endpoint 258 | * --------------------------- 259 | * Generates the appropriate endpoint based on the inputted mode 260 | * 261 | * endpoint: string to store the endpoint value 262 | * endpoint_size: size of endpoint string buffer 263 | * mode: type of list to retrieve 264 | */ 265 | void generate_endpoint (char *endpoint, size_t endpoint_size, size_t mode) { 266 | const char* s; 267 | switch (mode) { 268 | case ALL_MODE: 269 | strlcpy(endpoint, "", endpoint_size); 270 | break; 271 | case COMPLETED_MODE: 272 | s = "completed"; 273 | strlcpy(endpoint, s, endpoint_size); 274 | break; 275 | case HOLD_MODE: 276 | s = "on_hold"; 277 | strlcpy(endpoint, s, endpoint_size); 278 | break; 279 | case DROPPED_MODE: 280 | s = "dropped"; 281 | strlcpy(endpoint, s, endpoint_size); 282 | break; 283 | case PLAN_MODE: 284 | s = "plan_to_watch"; 285 | strlcpy(endpoint, s, endpoint_size); 286 | break; 287 | default: 288 | s = "watching"; 289 | strlcpy(endpoint, s, endpoint_size); 290 | break; 291 | } 292 | } 293 | 294 | /* 295 | * Function: generate_anime_api_uri 296 | * -------------------------------- 297 | * Generates the base uri for retrieving user anime list data 298 | * 299 | * uri: string to store the uri 300 | * uri_size: size of uri string buffer 301 | * username: user to fetch the data of 302 | * endpoint: endpoint to fetch the data from 303 | * allow_nsfw: allow/block nsfw results to be fetched 304 | */ 305 | void generate_anime_api_uri (char *uri, size_t uri_size, char *username, char *endpoint, int allow_nsfw) { 306 | const char* s = "https://api.myanimelist.net/v2/users/"; 307 | strlcpy(uri, s, uri_size); 308 | strlcat(uri, username, uri_size); 309 | const char* s1 = "/animelist?status="; 310 | strlcat(uri, s1, uri_size); 311 | strlcat(uri, endpoint, uri_size); 312 | 313 | /* enable/disable NSFW */ 314 | if (allow_nsfw == 1) { 315 | strlcat(uri, "&nsfw=true", uri_size); 316 | } else { 317 | strlcat(uri, "&nsfw=false", uri_size); 318 | } 319 | 320 | /* sort list by title ascending, descending not supported by MAL API */ 321 | strlcat(uri, "&sort=anime_title", uri_size); 322 | 323 | /* set number of animes per request */ 324 | strlcat(uri, "&limit=", uri_size); 325 | const int limit = 5; 326 | char page_size_str[limit]; 327 | snprintf(page_size_str, limit, "%d", PAGE_SIZE); 328 | strlcat(uri, page_size_str, uri_size); 329 | } 330 | 331 | /* 332 | * Function: fetch_curl_payload 333 | * ---------------------------- 334 | * Fetches the payload from the uri 335 | * 336 | * curl_fetch: pointer to curl fetch struct to store payload 337 | * paginated_uri: uri to fetch the data from 338 | */ 339 | void fetch_curl_payload (struct curl_fetch_st *curl_fetch, char *paginated_uri) { 340 | /* initialize curl */ 341 | CURL *curl = curl_easy_init(); 342 | 343 | /* error on curl initialization failure */ 344 | if (!curl) { 345 | fprintf(stderr, "Curl init failed\n"); 346 | exit(EXIT_FAILURE); 347 | } 348 | 349 | /* fetch curl and cleanup */ 350 | CURLcode res = curl_fetch_url(curl, paginated_uri, curl_fetch); 351 | curl_easy_cleanup(curl); 352 | 353 | /* error on failed curl response */ 354 | if (res != CURLE_OK) { 355 | fprintf(stderr, "API fetch error: %s\n", curl_easy_strerror(res)); 356 | free(curl_fetch->payload); 357 | exit(EXIT_FAILURE); 358 | } 359 | } 360 | 361 | /* 362 | * Function: print_anime_list 363 | * -------------------------- 364 | * Prints the anime list values in a list format 365 | * 366 | * anime_list: anime list to print 367 | * page: page number of paginated list 368 | * list_name: name of the type of list being printed 369 | * mode: type of list retrieved 370 | */ 371 | void print_anime_list (struct json_object *anime_list, size_t page, char *list_name, size_t mode) { 372 | /* get number of anime in anime list */ 373 | size_t n_anime = json_object_array_length(anime_list); 374 | const char *disp_name; 375 | 376 | /* set list display name based on the mode setting */ 377 | switch (mode) { 378 | case ALL_MODE: 379 | disp_name = "all"; 380 | break; 381 | case HOLD_MODE: 382 | disp_name = "on hold"; 383 | break; 384 | case PLAN_MODE: 385 | disp_name = "plan to watch"; 386 | break; 387 | default: 388 | disp_name = list_name; 389 | break; 390 | } 391 | 392 | /* print list header before the first page of data */ 393 | if ((int)page == 1) { 394 | if ((int)n_anime == PAGE_SIZE) { 395 | printf("%s%s %d+ anime\n%s", ANSI_CODE_MAGENTA, disp_name, 396 | PAGE_SIZE, ANSI_CODE_RESET); 397 | } else { 398 | printf("%s%s %zu anime\n%s", ANSI_CODE_MAGENTA, disp_name, n_anime, 399 | ANSI_CODE_RESET); 400 | } 401 | } 402 | 403 | /* iterate through anime list and print each anime title */ 404 | for (size_t i = 0; i < n_anime; i++) { 405 | struct json_object *anime = json_object_array_get_idx(anime_list, i); 406 | struct json_object *anime_node = json_object_object_get(anime, "node"); 407 | struct json_object *anime_title = json_object_object_get(anime_node, "title"); 408 | 409 | /* Printing in alternate colors for better readability */ 410 | const char* color = (((int)i % 2) == 1) ? ANSI_CODE_YELLOW : ANSI_CODE_CYAN; 411 | 412 | /* print each anime title in a numbered list format */ 413 | const int list_num = ((int)i + 1) + (PAGE_SIZE * ((int)page - 1)); 414 | printf("%s%d. %s%s\n", color, list_num, json_object_get_string(anime_title), ANSI_CODE_RESET); 415 | } 416 | } 417 | 418 | /* 419 | * Function: get_new_uri 420 | * --------------------- 421 | * Get the next page of the list from the json 422 | * 423 | * uri: current uri buffer 424 | * uri_size: size of uri string buffer 425 | * json: the json object that contains the nex uri 426 | */ 427 | void get_new_uri (char *uri, size_t uri_size, struct json_object *json) { 428 | struct json_object *paging = json_object_object_get(json, "paging"); 429 | struct json_object *next; 430 | if (!json_object_object_get_ex(paging, "next", &next)) { 431 | strlcpy(uri, "", uri_size); 432 | } else { 433 | const char* s = json_object_get_string(next); 434 | strlcpy(uri, s, uri_size); 435 | } 436 | } 437 | 438 | /* 439 | * Function: main 440 | * -------------- 441 | * Main entrypoint of program 442 | * 443 | * argc: number of arguments 444 | * argv[]: array of arguments 445 | * 446 | * returns: 0 if success, otherwise error number 447 | */ 448 | int main (int argc, char *argv[]) { 449 | /* exit early if client id is not provided */ 450 | if (CLIENT_ID_ENV == NULL && strcmp(CLIENT_ID, "YOUR TOKEN HERE") == 0) { 451 | fprintf(stderr, "Client ID has not been provided\n\n"); 452 | fprintf(stderr, "Here are the Steps to get your MyAnimeList Client ID (skip if you already have the Client ID):\n"); 453 | fprintf(stderr, "\t1. Go to this link https://myanimelist.net/apiconfig \n"); 454 | fprintf(stderr, "\t2. Click on Create ID.\n"); 455 | fprintf(stderr, "\t3. Fill the Form with appropriate details.\n"); 456 | fprintf(stderr, "\t4. Copy the Client ID (the weird alpha-numeric value next to the label.\n\n"); 457 | 458 | fprintf(stderr, "After copying the Client ID, open \'./src/mya.c\' and replace \'MYANIMELIST_CLIENT_ID\' and \'YOUR TOKEN HERE\' with the Client ID on line no. 14 & 15.\n"); 459 | exit(EXIT_FAILURE); 460 | } 461 | 462 | /* parse options and arguments */ 463 | struct arguments arguments; 464 | arguments.mode = WATCHING_MODE; 465 | arguments.nsfw = 1; 466 | argp_parse(&argp, argc, argv, 0, 0, &arguments); 467 | 468 | /* setup uri to fetch based on arguments */ 469 | size_t endpoint_size = 14; 470 | size_t uri_size = 146; 471 | char endpoint[endpoint_size]; 472 | char uri[uri_size]; 473 | generate_endpoint(endpoint, endpoint_size, arguments.mode); 474 | generate_anime_api_uri(uri, uri_size, arguments.args[0], endpoint, arguments.nsfw); 475 | 476 | /* iterator value for paginated data */ 477 | size_t page_num = 0; 478 | 479 | /* main loop for printing paginated anime list */ 480 | while ((int)(++page_num) > 0) { 481 | /* fetch data from uri */ 482 | struct curl_fetch_st curl_fetch; 483 | fetch_curl_payload(&curl_fetch, uri); 484 | struct json_object *json = json_tokener_parse(curl_fetch.payload); 485 | free(curl_fetch.payload); 486 | 487 | /* create json object to store anime list data */ 488 | struct json_object *anime_list; 489 | 490 | /* error when anime list is not found due to invalid user */ 491 | if (!json_object_object_get_ex(json, "data", &anime_list)) { 492 | fprintf(stderr, "User not found\n"); 493 | exit(EXIT_FAILURE); 494 | } 495 | 496 | /* get the next page of the list */ 497 | get_new_uri(uri, uri_size, json); 498 | 499 | /* print out the anime list */ 500 | print_anime_list(anime_list, page_num, endpoint, arguments.mode); 501 | 502 | /* cleanup json */ 503 | json_object_put(json); 504 | 505 | /* exit main loop after all pages have been looped through */ 506 | if (uri[0] == '\0') { 507 | break; 508 | } 509 | } 510 | return EXIT_SUCCESS; 511 | } 512 | --------------------------------------------------------------------------------