Compare commits
10 Commits
878ee963b5
...
main
| 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 @@
|
|||||||
# ------------------------------
|
make:
|
||||||
# Neocities CLI Makefile (Portable)
|
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:
|
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.
|
Minimal, secure C wrapper for the Neocities API.
|
||||||
|
|
||||||
No login flow.
|
|
||||||
No credential storage.
|
|
||||||
Stateless by design.
|
|
||||||
|
|
||||||
Users must generate their own API key via the Neocities dashboard.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,14 +10,9 @@ Users must generate their own API key via the Neocities dashboard.
|
|||||||
* File listing
|
* File listing
|
||||||
* Upload files
|
* Upload files
|
||||||
* Delete files
|
* Delete files
|
||||||
|
* Get API key
|
||||||
* TLS verification enabled by default
|
* TLS verification enabled by default
|
||||||
|
|
||||||
Authentication is performed exclusively via:
|
|
||||||
|
|
||||||
```
|
|
||||||
Authorization: Bearer <API_KEY>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Requirements
|
## 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
|
## API Overview
|
||||||
|
|
||||||
Just an overview. For more detailed information, see: [[Function-Guide.md]]
|
|
||||||
|
|
||||||
### Info (public)
|
### Info (public)
|
||||||
|
|
||||||
```c
|
```c
|
||||||
int neocities_info(const char *sitename, neocities_info_t *out);
|
int neocities_info(const char *sitename, char **out);
|
||||||
```
|
```
|
||||||
|
|
||||||
Fetch metadata about a site.
|
Fetch metadata about a site.
|
||||||
@@ -66,9 +42,7 @@ Fetch metadata about a site.
|
|||||||
### List files
|
### List files
|
||||||
|
|
||||||
```c
|
```c
|
||||||
int neocities_list(const char *api_key,
|
int neocities_list(const char *api_key, const char *path, char** out);
|
||||||
const char *path,
|
|
||||||
neocities_filelist_t *out);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
List files at a path.
|
List files at a path.
|
||||||
@@ -78,11 +52,7 @@ List files at a path.
|
|||||||
### Upload
|
### Upload
|
||||||
|
|
||||||
```c
|
```c
|
||||||
int neocities_upload(const char *api_key,
|
int neocities_upload(const char *api_key, const char **local_files, const char **remote_names, size_t count, char **response);
|
||||||
const char **local_files,
|
|
||||||
const char **remote_names,
|
|
||||||
size_t count,
|
|
||||||
char **response);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Uploads multiple files.
|
Uploads multiple files.
|
||||||
@@ -95,10 +65,7 @@ Uploads multiple files.
|
|||||||
### Delete
|
### Delete
|
||||||
|
|
||||||
```c
|
```c
|
||||||
int neocities_delete(const char *api_key,
|
int neocities_delete(const char *api_key, const char **filenames, size_t count, char **response);
|
||||||
const char **filenames,
|
|
||||||
size_t count,
|
|
||||||
char **response);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Deletes files from the site.
|
Deletes files from the site.
|
||||||
@@ -107,83 +74,17 @@ Deletes files from the site.
|
|||||||
|
|
||||||
### API Key
|
### 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
|
```c
|
||||||
void neocities_free_info(neocities_info_t *info);
|
int neocities_apikey(const char *user, const char *pass, char **out);
|
||||||
void neocities_free_filelist(neocities_filelist_t *list);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`response` returned by upload/delete must also be freed by caller.
|
Get your API key.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
### List files
|
See example.c
|
||||||
|
|
||||||
```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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
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 "neocities.h"
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <jansson.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
// -------------------------
|
// -------------------------
|
||||||
// HTTP responde buffer
|
// HTTP response buffer
|
||||||
// -------------------------
|
// -------------------------
|
||||||
struct response {
|
struct response {
|
||||||
char *data;
|
char *data;
|
||||||
@@ -101,36 +100,12 @@ static int perform_request(
|
|||||||
return 0;
|
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
|
// info — GET /api/info
|
||||||
// -------------------------
|
// -------------------------
|
||||||
int neocities_info(
|
int neocities_info(
|
||||||
const char *sitename, neocities_info_t *out
|
const char *sitename,
|
||||||
|
char** out
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
char url[512];
|
char url[512];
|
||||||
@@ -143,49 +118,9 @@ int neocities_info(
|
|||||||
int ret = perform_request(url, NULL, NULL, &resp, NULL);
|
int ret = perform_request(url, NULL, NULL, &resp, NULL);
|
||||||
if (ret) return ret;
|
if (ret) return ret;
|
||||||
|
|
||||||
json_error_t error;
|
*out = resp.data;
|
||||||
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"));
|
return 0;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------
|
// -------------------------
|
||||||
@@ -193,7 +128,8 @@ int neocities_info(
|
|||||||
// -------------------------
|
// -------------------------
|
||||||
int neocities_list(
|
int neocities_list(
|
||||||
const char *api_key,
|
const char *api_key,
|
||||||
const char *path, neocities_filelist_t *out
|
const char *path,
|
||||||
|
char **out
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
char url[512];
|
char url[512];
|
||||||
@@ -204,23 +140,8 @@ int neocities_list(
|
|||||||
int ret = perform_request(url, api_key, NULL, &resp, NULL);
|
int ret = perform_request(url, api_key, NULL, &resp, NULL);
|
||||||
if (ret) return ret;
|
if (ret) return ret;
|
||||||
|
|
||||||
json_error_t error;
|
*out = resp.data;
|
||||||
json_t *root = json_loads(resp.data, 0, &error);
|
return 0;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------
|
// -------------------------
|
||||||
@@ -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