Compare commits

..

4 Commits

Author SHA1 Message Date
synt-xerror
878ee963b5 Fix: segfault error at info function 2026-02-16 21:37:20 -03:00
synt-xerror
c955ee08d4 New readme 2026-02-16 20:34:40 -03:00
synt-xerror
08cc4f9d75 Security improvements 2026-02-16 19:08:31 -03:00
synt-xerror
9ba4be8a92 Library is done for test and improvements 2026-02-16 17:01:21 -03:00
13 changed files with 525 additions and 413 deletions

0
.gitignore vendored Normal file → Executable file
View File

View File

@@ -15,4 +15,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

258
README.md
View File

@@ -1,121 +1,193 @@
# Neocities C CLI
# Neocities C API
A simple command-line tool written in C that interacts with the [Neocities API](https://neocities.org/api).
Currently, only the `--info` command is implemented — it fetches and displays basic information about your Neocities site.
Minimal, secure C wrapper for the Neocities API using API key authentication.
No login flow.
No credential storage.
Stateless by design.
Users must generate their own API key via the Neocities dashboard.
---
## Features
- Fetch site information using Neocities API
- Authenticates via environment variables
- JSON parsing with [jansson](https://digip.org/jansson/)
- Works on Linux, Windows (MSYS2/MinGW), and Android (via Termux)
- Compilable with a Makefile for easy builds
* Site info retrieval
* File listing
* Upload files
* Delete files
* TLS verification enabled by default
Authentication is performed exclusively via:
```
Authorization: Bearer <API_KEY>
```
---
## Requirements
Youll need:
- A C compiler (`gcc` or `clang`)
- [libcurl](https://curl.se/libcurl/)
- [jansson](https://digip.org/jansson/)
- `make`
* libcurl
### Linux (Debian/Ubuntu-based)
```bash
sudo apt update
sudo apt install build-essential libcurl4-openssl-dev libjansson-dev make
```
### Windows (MSYS2 / MinGW)
```bash
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-curl mingw-w64-x86_64-jansson make
```
### Android (Termux)
```bash
pkg install clang curl-dev jansson make
```
## Building
Run in cloned repository:
```bash
make
```
This will generate the executable neocities (or neocities.exe on Windows).
To clean build files:
```bash
make clean
```
### Environment Variables
Before running, export your Neocities credentials:
#### Linux / macOS / Termux
```bash
export NEOCITIES_USER="your_username"
export NEOCITIES_PASS="your_password"
```
#### Windows (PowerShell)
```batch
setx NEOCITIES_USER "your_username"
setx NEOCITIES_PASS "your_password"
```
## Usage
```bash
./neocities --info
```
Example output:
Linux (example):
```
Neocities C CLI
Sitename: example
Hits: 42
Created at: 2024-01-10
Last updated: 2025-11-01
Domain: domain.com
Tags: art, blog, personal.
sudo apt install libcurl4-openssl-dev
```
## Optional: Install Globally
---
You can move the compiled executable to a directory in your PATH to run it from anywhere:
## Authentication
#### Linux / Termux
Generate your API key at:
```bash
sudo mv neocities /usr/local/bin/
Neocities > Settings > Manage Site Settings > API
(or https://neocities.org/settings/YOUR-USERNAME#api_key)
If you're coding a CLI or application, you must provide the key at runtime (env var, config file, etc).
---
## API Overview
Just an overview. For more detailed information, see: [[Function-Guide.md]]
### Info (public)
```c
int neocities_info(const char *sitename, neocities_info_t *out);
```
#### Windows (PowerShell)
Move neocities.exe to a folder in your PATH, e.g., C:\Windows\System32\.
Fetch metadata about a site.
After that, you can run:
---
```batch
neocities --info
### List files
```c
int neocities_list(const char *api_key,
const char *path,
neocities_filelist_t *out);
```
from any folder without specifying the path.
List files at a path.
## Notes
---
Only the --info command is implemented for now.
### Upload
If you get curl_easy_perform errors, check your network and SSL setup.
```c
int neocities_upload(const char *api_key,
const char **local_files,
const char **remote_names,
size_t count,
char **response);
```
Uploads multiple files.
* `local_files[i]` → local path
* `remote_names[i]` → remote filename
---
### Delete
```c
int neocities_delete(const char *api_key,
const char **filenames,
size_t count,
char **response);
```
Deletes files from the site.
---
### API Key
For security reasons, you should get the API key via Neocities settings. This function is not avaiable.
---
## Memory Management
Always free parsed outputs:
```c
void neocities_free_info(neocities_info_t *info);
void neocities_free_filelist(neocities_filelist_t *list);
```
`response` returned by upload/delete must also be freed by caller.
---
## Example Usage
### List files
```c
neocities_filelist_t list;
if (neocities_list(api_key, "/", &list) == 0) {
for (size_t i = 0; i < list.count; i++)
printf("%s\n", list.paths[i]);
neocities_free_filelist(&list);
}
```
---
### Upload
```c
const char *local[] = {"index.html"};
const char *remote[] = {"index.html"};
char *resp;
neocities_upload(api_key, local, remote, 1, &resp);
free(resp);
```
---
## Security Notes
* TLS verification is enforced
* Redirects are disabled intentionally
* API key is never persisted internally
Recommended:
* Store key in env vars
* Avoid hardcoding
---
## Design Philosophy
* No login/password support
* API key only
* Explicit memory ownership
* Minimal abstraction
---
## Future Extensions (Optional)
Possible areas to explore:
* Structured responses for upload/delete
* JSON passthrough mode
* Streaming uploads
* Error codes standardization
---
## License
[MIT](LICENSE)
On Termux, use clang for the best compatibility.

BIN
neocities

Binary file not shown.

View File

@@ -1,156 +0,0 @@
#include "main.h" // Header local do projeto (declarações próprias: struct response, write_callback, etc.)
#include <stdio.h> // printf, fprintf
#include <stdlib.h> // getenv, malloc, free
#include <curl/curl.h> // libcurl: HTTP/HTTPS client
#include <jansson.h> // Jansson: parsing e manipulação de JSON
#include <string.h>
// Função responsável por buscar informações do site do usuário no Neocities
int fetch_neocities_info() {
// Lê variáveis de ambiente com credenciais
// NEOCITIES_USER e NEOCITIES_PASS são usadas para autenticação HTTP Basic
const char *user = getenv("NEOCITIES_USER");
const char *pass = getenv("NEOCITIES_PASS");
// const char *enc = getenv("NEOCITIES_PASS_ENC"); // Não usado aqui (possível uso futuro)
// Verificação básica: se usuário ou senha não existirem, aborta
if (!user || !pass) {
fprintf(stderr, "You're not logged!\n");
return 1;
}
// Buffer para "user:password", formato exigido por CURLOPT_USERPWD
char auth[256];
snprintf(auth, sizeof(auth), "%s:%s", user, pass);
// Inicializa um handle do libcurl
CURL *curl = curl_easy_init();
if (!curl) {
fprintf(stderr, "Erro ao inicializar cURL\n");
return 1;
}
// Monta a URL da API do Neocities para obter informações do site
char infourl[256];
snprintf(
infourl,
sizeof(infourl),
"https://neocities.org/api/info?sitename=%s",
user
);
// Define a URL da requisição
curl_easy_setopt(curl, CURLOPT_URL, infourl);
// Define autenticação HTTP Basic (user:pass)
curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
// Estrutura usada para armazenar a resposta HTTP em memória
// resp.data será expandido pelo write_callback
struct response resp = { .data = malloc(1), .len = 0 };
resp.data[0] = '\0'; // Garante string vazia inicial
// Função chamada pelo libcurl sempre que dados são recebidos
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
// Ponteiro passado para o write_callback (estado da resposta)
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
// Executa a requisição HTTP de forma síncrona
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "Erro na requisição: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
free(resp.data);
return 1;
}
// Libera o handle do curl (a resposta já está em resp.data)
curl_easy_cleanup(curl);
// Estrutura para capturar erros de parsing JSON
json_error_t error;
// Converte a string JSON recebida em um objeto JSON manipulável
json_t *obj = json_loads(resp.data, 0, &error);
free(resp.data); // Não é mais necessária após o parsing
// Caso o JSON seja inválido
if (!obj) {
fprintf(stderr, "Erro ao parsear JSON: %s\n", error.text);
return 1;
}
// Obtém o campo "result" do JSON raiz
json_t *result_obj = json_object_get(obj, "result");
const char *result_str = json_string_value(result_obj);
// Caso a API tenha retornado erro
if (strcmp(result_str, "error") == 0) {
// Tipo de erro retornado pela API
const char *err_type =
json_string_value(json_object_get(obj, "error_type"));
// Mensagem de erro legível
const char *err_msg =
json_string_value(json_object_get(obj, "message"));
// Tratamento específico para erro de autenticação
if (strcmp(err_type, "invalid_auth") == 0) {
printf("Usuário ou senha incorretos!\n");
} else {
printf("Erro! %s\n", err_msg);
}
// Diminui o contador de referência do objeto JSON
json_decref(obj);
return 1;
}
// Caso a requisição tenha sido bem-sucedida
if (strcmp(result_str, "success") == 0) {
// Objeto "info" contém os dados do site
json_t *info_obj = json_object_get(obj, "info");
// Impressão direta dos campos retornados
printf("\nSitename: %s\n",
json_string_value(json_object_get(info_obj, "sitename")));
printf("Hits: %d\n",
(int)json_integer_value(json_object_get(info_obj, "hits")));
printf("Created at: %s\n",
json_string_value(json_object_get(info_obj, "created_at")));
printf("Last updated: %s\n",
json_string_value(json_object_get(info_obj, "last_updated")));
printf("Domain: %s\n",
json_string_value(json_object_get(info_obj, "domain")));
// Array de tags associadas ao site
json_t *tags = json_object_get(info_obj, "tags");
printf("Tags: ");
size_t index;
json_t *tag;
// Itera sobre o array JSON "tags"
json_array_foreach(tags, index, tag) {
printf(
"#%s%s",
json_string_value(tag),
(index < json_array_size(tags) - 1) ? ", " : "."
);
}
printf("\n\n");
}
// Libera o objeto JSON principal
json_decref(obj);
return 0;
}

View File

@@ -1,12 +0,0 @@
#ifndef INFO
#define INFO
// ^ Guardas de inclusão (include guards)
// Evitam que este header seja processado mais de uma vez
// durante a compilação, o que causaria redefinições.
// Declaração da função que busca informações do Neocities.
// O `void` indica explicitamente que a função não recebe parâmetros.
int fetch_neocities_info(void);
#endif
// ^ Fim do include guard

BIN
src/libneocities.a Normal file

Binary file not shown.

View File

@@ -1,27 +0,0 @@
#!/bin/bash
set -e
CONFIG_DIR="$HOME/.config/neocities"
CONFIG_FILE="$CONFIG_DIR/credentials"
mkdir -p "$CONFIG_DIR"
chmod 700 "$CONFIG_DIR"
read -p "Username: " USER
read -s -p "Password: " PASS
echo
read -p "Would you like to see the credentials on your screen to confirm? [Y/n]: " CHOICE
case $CHOICE in
y|Y) echo -e "Username: $USER\nPassword: $PASS" ;;
*) echo "" ;;
esac
cat > "$CONFIG_FILE" <<EOF
user = $USER
pass = $PASS
EOF
chmod 600 "$CONFIG_FILE"
echo "Credentials saved in $CONFIG_FILE"

View File

@@ -1,93 +0,0 @@
#include <stdio.h> // printf, fprintf
#include <stdlib.h> // malloc, realloc, free
#include <string.h> // strcmp, memcpy
#include <curl/curl.h> // libcurl (necessário para callbacks e tipos)
#include "main.h" // Declarações globais do projeto (upload_func, etc.)
#include "info.h" // Declaração de fetch_neocities_info()
// Estrutura usada para acumular a resposta HTTP em memória.
// É preenchida incrementalmente pelo write_callback.
struct response {
char *data; // Buffer dinâmico com os dados recebidos
size_t len; // Quantidade atual de bytes válidos em data
};
// Callback chamado pelo libcurl sempre que um novo bloco de dados chega.
// Não garante tamanho fixo nem chamada única.
size_t write_callback(void *data, size_t size, size_t nmemb, void *userdata) {
// userdata aponta para a struct response passada via CURLOPT_WRITEDATA
struct response *resp = (struct response *)userdata;
// Tamanho real do bloco recebido
size_t chunk_size = size * nmemb;
// Realoca o buffer para caber os novos dados + '\0'
char *tmp = realloc(resp->data, resp->len + chunk_size + 1);
if (!tmp) {
// Se realloc falhar, aborta a escrita
// Retornar 0 sinaliza erro ao libcurl
fprintf(stderr, "Erro de memória\n");
return 0;
}
// Atualiza o ponteiro com o novo buffer
resp->data = tmp;
// Copia o bloco recebido para o final do buffer atual
memcpy(resp->data + resp->len, data, chunk_size);
// Atualiza o tamanho total acumulado
resp->len += chunk_size;
// Garante que o buffer seja uma string C válida
resp->data[resp->len] = '\0';
// Retornar o número de bytes processados indica sucesso
return chunk_size;
}
// Tipo de ponteiro para função que representa um comando CLI.
// Todas as funções de comando retornam int e não recebem argumentos.
typedef int (*cmd_func_t)(void);
// Estrutura que associa:
// - nome do comando (string digitada no terminal)
// - função que implementa esse comando
typedef struct {
const char *name;
cmd_func_t func;
} command_entry;
int main(int argc, char *argv[]) {
// Tabela de comandos suportados pelo programa.
// Facilita adicionar novos comandos sem alterar a lógica principal.
command_entry commands[] = {
{"--info", fetch_neocities_info},
// {"--upload", upload_func}
};
// Verifica se ao menos um argumento foi passado
if (argc < 2) {
printf("No command provided.\n");
return 0;
}
// Percorre a tabela de comandos procurando correspondência
for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
// Compara o argumento passado com o nome do comando
if (strcmp(argv[1], commands[i].name) == 0) {
// Executa a função associada ao comando
// e retorna imediatamente o código dela
return commands[i].func();
}
}
// Se nenhum comando conhecido foi encontrado
printf("Unknown command.\n");
return 0;
}

View File

@@ -1,31 +0,0 @@
#ifndef MAIN_H
#define MAIN_H
// Include guard:
// Impede múltiplas inclusões deste header no mesmo processo de compilação.
#include <stddef.h> // size_t
// Necessário para o tipo size_t, usado em tamanhos e contadores de memória.
// Declarações externas de variáveis globais.
// O `extern` indica que a definição real existe em outro arquivo .c.
extern const char *user;
extern const char *pass;
// Estrutura usada para armazenar dados recebidos dinamicamente,
// geralmente como resposta de uma requisição HTTP.
struct response {
char *data; // Buffer dinâmico contendo os dados acumulados
size_t len; // Quantidade de bytes válidos em data
};
// Protótipo da função de callback usada pelo libcurl.
// Essa função será chamada repetidamente conforme dados chegam.
size_t write_callback(
void *ptr, // Ponteiro para os dados recebidos
size_t size, // Tamanho de cada elemento
size_t nmemb, // Número de elementos
void *userdata // Ponteiro de contexto definido pelo usuário
);
#endif
// Fim do include guard

321
src/neocities.c Normal file
View File

@@ -0,0 +1,321 @@
#include "neocities.h"
#include <curl/curl.h>
#include <jansson.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// -------------------------
// HTTP responde buffer
// -------------------------
struct response {
char *data;
size_t len;
};
static size_t write_cb(
void *ptr,
size_t size,
size_t nmemb,
void *userdata
)
{
struct response *r = (struct response*)userdata;
size_t chunk = size * nmemb;
char *tmp = realloc(r->data, r->len + chunk + 1);
if (!tmp) return 0;
r->data = tmp;
memcpy(r->data + r->len, ptr, chunk);
r->len += chunk;
r->data[r->len] = '\0';
return chunk;
}
// -------------------------
// HTTP helpers
// -------------------------
static int perform_request(
const char *url,
const char *api_key,
const char *post_fields,
struct response *resp,
curl_mime *mime
)
{
CURL *curl = curl_easy_init();
if (!curl) return 1;
resp->data = malloc(1);
resp->len = 0;
resp->data[0] = '\0';
struct curl_slist *headers = NULL;
if (api_key && strlen(api_key) > 0) {
char auth_header[512];
snprintf(auth_header, sizeof(auth_header),
"Authorization: Bearer %s", api_key);
headers = curl_slist_append(headers, auth_header);
}
curl_easy_setopt(curl, CURLOPT_URL, url);
if (headers)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
/* SSL hardening */
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); // política
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, resp);
if (post_fields) {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
}
if (mime)
curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (res != CURLE_OK || http_code >= 400) {
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
free(resp->data);
return 2;
}
return 0;
}
// -------------------------
// JSON parsers
// -------------------------
void neocities_free_info(
neocities_info_t *info
)
{
if (!info) return;
free(info->sitename);
free(info->created_at);
free(info->last_updated);
free(info->domain);
for (size_t i=0; i<info->tag_count; i++) free(info->tags[i]);
free(info->tags);
}
void neocities_free_filelist(
neocities_filelist_t *list
)
{
if (!list) return;
for (size_t i=0; i<list->count; i++) free(list->paths[i]);
free(list->paths);
}
// -------------------------
// info — GET /api/info
// -------------------------
int neocities_info(
const char *sitename, neocities_info_t *out
)
{
char url[512];
if (sitename)
snprintf(url, sizeof(url), "https://neocities.org/api/info?sitename=%s", sitename);
else
snprintf(url, sizeof(url), "https://neocities.org/api/info");
struct response resp;
int ret = perform_request(url, NULL, NULL, &resp, NULL);
if (ret) return ret;
json_error_t error;
json_t *root = json_loads(resp.data, 0, &error);
free(resp.data);
if (!root) return 3;
const char *result = json_string_value(json_object_get(root,"result"));
if (!result || strcmp(result,"success")!=0) { json_decref(root); return 4; }
json_t *info = json_object_get(root,"info");
if (!json_is_object(info)) {
json_decref(root);
return 4;
}
json_t *j_sitename = json_object_get(info,"sitename");
json_t *j_created = json_object_get(info,"created_at");
json_t *j_updated = json_object_get(info,"last_updated");
json_t *j_domain = json_object_get(info,"domain");
json_t *j_hits = json_object_get(info,"hits");
out->sitename = json_is_string(j_sitename) ? strdup(json_string_value(j_sitename)) : NULL;
out->created_at = json_is_string(j_created) ? strdup(json_string_value(j_created)) : NULL;
out->last_updated = json_is_string(j_updated) ? strdup(json_string_value(j_updated)) : NULL;
out->domain = json_is_string(j_domain) ? strdup(json_string_value(j_domain)) : NULL;
out->hits = json_is_integer(j_hits) ? json_integer_value(j_hits) : 0;
json_t *tags = json_object_get(info,"tags");
if (!json_is_array(tags)) {
out->tags = NULL;
out->tag_count = 0;
} else {
size_t tc = json_array_size(tags);
out->tags = malloc(sizeof(char*) * tc);
out->tag_count = tc;
for (size_t i=0; i<tc; i++) {
json_t *tag = json_array_get(tags,i);
out->tags[i] = json_is_string(tag) ? strdup(json_string_value(tag)) : NULL;
}
}
json_decref(root);
return 0;
}
// -------------------------
// list — GET /api/list
// -------------------------
int neocities_list(
const char *api_key,
const char *path, neocities_filelist_t *out
)
{
char url[512];
if (path) snprintf(url, sizeof(url), "https://neocities.org/api/list?path=%s", path);
else snprintf(url, sizeof(url), "https://neocities.org/api/list");
struct response resp;
int ret = perform_request(url, api_key, NULL, &resp, NULL);
if (ret) return ret;
json_error_t error;
json_t *root = json_loads(resp.data, 0, &error);
free(resp.data);
if (!root) return 3;
json_t *arr = json_object_get(root, "files");
if (!arr) { json_decref(root); return 4; }
size_t count = json_array_size(arr);
out->paths = malloc(sizeof(char*) * count);
out->count = count;
for (size_t i=0; i<count; i++) {
json_t *f = json_array_get(arr, i);
out->paths[i] = strdup(json_string_value(json_object_get(f,"path")));
}
json_decref(root);
return 0;
}
// -------------------------
// delete — POST /api/delete
// -------------------------
int neocities_delete(
const char *api_key,
const char **filenames,
size_t count,
char **response
)
{
char body[1024] = "";
for (size_t i=0; i<count; i++) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "filenames[]=%s&", filenames[i]);
strncat(body, tmp, sizeof(body)-strlen(body)-1);
}
struct response resp;
int ret = perform_request(
"https://neocities.org/api/delete",
api_key,
body,
&resp,
NULL
);
if (ret) return ret;
*response = resp.data;
return 0;
}
// -------------------------
// upload — POST /api/upload
// (multipart/form-data)
// -------------------------
int neocities_upload(
const char *api_key,
const char **local_files,
const char **remote_names,
size_t count,
char **response
)
{
if (!api_key || strlen(api_key) == 0)
return 3;
CURL *curl = curl_easy_init();
if (!curl) return 1;
struct response resp;
resp.data = malloc(1);
resp.len = 0;
resp.data[0] = '\0';
curl_easy_setopt(curl, CURLOPT_URL, "https://neocities.org/api/upload");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
curl_easy_setopt(curl, CURLOPT_USERPWD, NULL);
struct curl_slist *headers = NULL;
char auth_header[512];
snprintf(auth_header, sizeof(auth_header),
"Authorization: Bearer %s", api_key);
headers = curl_slist_append(headers, auth_header);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_mime *form = curl_mime_init(curl);
for (size_t i=0; i<count; i++) {
curl_mimepart *part = curl_mime_addpart(form);
curl_mime_name(part, remote_names[i]);
curl_mime_filedata(part, local_files[i]);
}
curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);
CURLcode res = curl_easy_perform(curl);
curl_mime_free(form);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) { free(resp.data); return 2; }
*response = resp.data;
return 0;
}
// -------------------------
// get_api_key — GET /api/key
// -------------------------
// no. for security reasons of course.

38
src/neocities.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef NEOCITIES_H
#define NEOCITIES_H
#include <stddef.h>
// -------------------------
// Output structs
// -------------------------
typedef struct {
char *sitename;
int hits;
char *created_at;
char *last_updated;
char *domain;
char **tags;
size_t tag_count;
} neocities_info_t;
typedef struct {
char **paths;
size_t count;
} neocities_filelist_t;
// -------------------------
// API functions
// -------------------------
int neocities_info(const char *sitename, neocities_info_t *out);
int neocities_list(const char *api_key, const char *path, neocities_filelist_t *out);
int neocities_delete(const char *api_key, const char **filenames, size_t count, char **response);
int neocities_upload(const char *api_key, const char **local_files, const char **remote_names, size_t count, char **response);
// -------------------------
// Free functions
// -------------------------
void neocities_free_info(neocities_info_t *info);
void neocities_free_filelist(neocities_filelist_t *list);
#endif // NEOCITIES_H

BIN
src/neocities.o Normal file

Binary file not shown.