Compare commits
10 Commits
878ee963b5
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9b474af40 | ||
|
|
0543a9e9ce | ||
|
|
1ee3da6722 | ||
|
|
f3667d615e | ||
|
|
55788ace47 | ||
|
|
94f34439dc | ||
|
|
795cd984cd | ||
|
|
cde638aa71 | ||
|
|
832bb88ecd | ||
|
|
7cdac92582 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
auto-commit.txt
|
||||
example
|
||||
libneocities.a
|
||||
neocities.o
|
||||
|
||||
44
Makefile
Executable file → Normal file
44
Makefile
Executable file → Normal file
@@ -1,38 +1,10 @@
|
||||
# ------------------------------
|
||||
# Neocities CLI Makefile (Portable)
|
||||
# ------------------------------
|
||||
make:
|
||||
gcc -c neocities.c -o neocities.o
|
||||
ar rcs libneocities.a neocities.o
|
||||
|
||||
# Default compiler
|
||||
CC ?= gcc
|
||||
|
||||
# Compiler flags
|
||||
CFLAGS ?= -Wall -O2 -Iinclude
|
||||
|
||||
# Libraries
|
||||
LIBS ?= -lcurl -ljansson
|
||||
|
||||
# Source and target
|
||||
SRC = \
|
||||
src/main.c \
|
||||
src/info.c
|
||||
|
||||
BIN = neocities
|
||||
|
||||
# Detect Windows (MSYS2 / MinGW)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
RM = del /Q
|
||||
BIN_EXT = .exe
|
||||
else
|
||||
RM = rm -f
|
||||
BIN_EXT =
|
||||
endif
|
||||
|
||||
# Build executable
|
||||
all: $(BIN)$(BIN_EXT)
|
||||
|
||||
$(BIN)$(BIN_EXT): $(SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
|
||||
|
||||
# Clean build files
|
||||
clean:
|
||||
$(RM) $(BIN)$(BIN_EXT)
|
||||
rm libneocities.a neocities.o
|
||||
|
||||
example:
|
||||
rm -f example
|
||||
gcc example.c -L. -lneocities -lcurl -o example
|
||||
|
||||
119
README.md
119
README.md
@@ -1,12 +1,6 @@
|
||||
# Neocities C API
|
||||
# Neocities API C library
|
||||
|
||||
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.
|
||||
Minimal, secure C wrapper for the Neocities API.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,14 +10,9 @@ Users must generate their own API key via the Neocities dashboard.
|
||||
* File listing
|
||||
* Upload files
|
||||
* Delete files
|
||||
* Get API key
|
||||
* TLS verification enabled by default
|
||||
|
||||
Authentication is performed exclusively via:
|
||||
|
||||
```
|
||||
Authorization: Bearer <API_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
@@ -38,25 +27,12 @@ sudo apt install libcurl4-openssl-dev
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Generate your API key at:
|
||||
|
||||
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);
|
||||
int neocities_info(const char *sitename, char **out);
|
||||
```
|
||||
|
||||
Fetch metadata about a site.
|
||||
@@ -66,9 +42,7 @@ Fetch metadata about a site.
|
||||
### List files
|
||||
|
||||
```c
|
||||
int neocities_list(const char *api_key,
|
||||
const char *path,
|
||||
neocities_filelist_t *out);
|
||||
int neocities_list(const char *api_key, const char *path, char** out);
|
||||
```
|
||||
|
||||
List files at a path.
|
||||
@@ -78,11 +52,7 @@ List files at a path.
|
||||
### Upload
|
||||
|
||||
```c
|
||||
int neocities_upload(const char *api_key,
|
||||
const char **local_files,
|
||||
const char **remote_names,
|
||||
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);
|
||||
```
|
||||
|
||||
Uploads multiple files.
|
||||
@@ -95,10 +65,7 @@ Uploads multiple files.
|
||||
### Delete
|
||||
|
||||
```c
|
||||
int neocities_delete(const char *api_key,
|
||||
const char **filenames,
|
||||
size_t count,
|
||||
char **response);
|
||||
int neocities_delete(const char *api_key, const char **filenames, size_t count, char **response);
|
||||
```
|
||||
|
||||
Deletes files from the site.
|
||||
@@ -107,83 +74,17 @@ 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);
|
||||
int neocities_apikey(const char *user, const char *pass, char **out);
|
||||
```
|
||||
|
||||
`response` returned by upload/delete must also be freed by caller.
|
||||
Get your API key.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
See example.c
|
||||
|
||||
---
|
||||
|
||||
|
||||
35
example.c
Normal file
35
example.c
Normal file
@@ -0,0 +1,35 @@
|
||||
#include <stdio.h>
|
||||
#include "neocities.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
int main() {
|
||||
static const char* username = "YOUR_USERNAME";
|
||||
static const char* api_key = "YOUR_API_KEY";
|
||||
|
||||
char* out = NULL;
|
||||
|
||||
// GET /api/key
|
||||
if (neocities_apikey(username, "YOUR_PASSWORD", &out) == 0) printf("%s", out);
|
||||
free(out);
|
||||
|
||||
// GET /api/info
|
||||
if (neocities_info(username, &out) == 0) printf("%s", out);
|
||||
free(out);
|
||||
|
||||
// GET /api/list
|
||||
if (neocities_list(api_key, "/", &out) == 0) printf("%s", out);
|
||||
free(out);
|
||||
|
||||
static const char* files[] = { "test.txt" };
|
||||
static const int qnt_files = sizeof(files) / sizeof(files[0]);
|
||||
|
||||
// GET /api/upload
|
||||
if (neocities_upload(api_key, files, files, qnt_files, &out) == 0) printf("%s", out);
|
||||
free(out);
|
||||
|
||||
// GET /api/delete
|
||||
if (neocities_delete(api_key, files, qnt_files, &out) == 0) printf("%s", out);
|
||||
free(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
#include "neocities.h"
|
||||
#include <curl/curl.h>
|
||||
#include <jansson.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// -------------------------
|
||||
// HTTP responde buffer
|
||||
// -------------------------
|
||||
// HTTP response buffer
|
||||
// -------------------------
|
||||
struct response {
|
||||
char *data;
|
||||
@@ -101,36 +100,12 @@ static int perform_request(
|
||||
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
|
||||
const char *sitename,
|
||||
char** out
|
||||
)
|
||||
{
|
||||
char url[512];
|
||||
@@ -143,49 +118,9 @@ int neocities_info(
|
||||
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;
|
||||
*out = resp.data;
|
||||
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
@@ -193,7 +128,8 @@ int neocities_info(
|
||||
// -------------------------
|
||||
int neocities_list(
|
||||
const char *api_key,
|
||||
const char *path, neocities_filelist_t *out
|
||||
const char *path,
|
||||
char **out
|
||||
)
|
||||
{
|
||||
char url[512];
|
||||
@@ -204,23 +140,8 @@ int neocities_list(
|
||||
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;
|
||||
*out = resp.data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
@@ -316,6 +237,37 @@ int neocities_upload(
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// get_api_key — GET /api/key
|
||||
// key — GET /api/key
|
||||
// -------------------------
|
||||
// no. for security reasons of course.
|
||||
int neocities_apikey(const char *user, const char *pass, char **out) {
|
||||
struct response resp = {0};
|
||||
|
||||
char userpass[256];
|
||||
snprintf(userpass, sizeof(userpass), "%s:%s", user, pass);
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) return 1;
|
||||
|
||||
resp.data = malloc(1);
|
||||
resp.len = 0;
|
||||
resp.data[0] = '\0';
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, "https://neocities.org/api/key");
|
||||
curl_easy_setopt(curl, CURLOPT_USERPWD, userpass); // aqui é equivalente ao -u do curl
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK || http_code >= 400) {
|
||||
free(resp.data);
|
||||
return 2;
|
||||
}
|
||||
|
||||
*out = resp.data; // resposta da API
|
||||
return 0;
|
||||
}
|
||||
15
neocities.h
Normal file
15
neocities.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef NEOCITIES_H
|
||||
#define NEOCITIES_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
// -------------------------
|
||||
// API functions
|
||||
// -------------------------
|
||||
int neocities_info(const char *sitename, char **out);
|
||||
int neocities_list(const char *api_key, const char *path, char **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);
|
||||
int neocities_apikey(const char *user, const char *pass, char **out);
|
||||
|
||||
#endif // NEOCITIES_H
|
||||
Binary file not shown.
@@ -1,38 +0,0 @@
|
||||
#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
BIN
src/neocities.o
Binary file not shown.
Reference in New Issue
Block a user