mirror of
https://github.com/morgan9e/scripts
synced 2026-04-14 00:14:13 +09:00
Init
This commit is contained in:
152
BLEMouse.ino
Normal file
152
BLEMouse.ino
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "BLEDevice.h"
|
||||
#include "BLEHIDDevice.h"
|
||||
#include "HIDTypes.h"
|
||||
|
||||
#define DEVICE_NAME "ESP32 Mouse"
|
||||
#define BLE_MANUFACTURER "TinyUSB"
|
||||
|
||||
BLEHIDDevice* hid;
|
||||
BLECharacteristic* input;
|
||||
BLECharacteristic* output;
|
||||
bool isBleConnected = false;
|
||||
|
||||
#define MOUSE_LEFT 1
|
||||
#define MOUSE_RIGHT 2
|
||||
#define MOUSE_MIDDLE 4
|
||||
|
||||
struct InputReport {
|
||||
uint8_t buttons;
|
||||
int8_t x;
|
||||
int8_t y;
|
||||
int8_t w;
|
||||
int8_t hw;
|
||||
};
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial2.begin(115200, SERIAL_8N1, 22, 23);
|
||||
xTaskCreate(bluetoothTask, "bluetooth", 20000, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
while (Serial2.available()) {
|
||||
String uartData = Serial2.readStringUntil('\n');
|
||||
if (uartData.startsWith("REPORT: ")) {
|
||||
int r, m, l, w, x, y;
|
||||
sscanf(uartData.c_str(), "REPORT: BTN %d %d %d WHEEL %d X %d Y %d", &l, &m, &r, &w, &x, &y);
|
||||
|
||||
uint8_t button = 0;
|
||||
|
||||
if (r) button |= MOUSE_RIGHT;
|
||||
else button &= ~MOUSE_RIGHT;
|
||||
|
||||
if (m) button |= MOUSE_MIDDLE;
|
||||
else button &= ~MOUSE_MIDDLE;
|
||||
|
||||
if (l) button |= MOUSE_LEFT;
|
||||
else button &= ~MOUSE_LEFT;
|
||||
|
||||
InputReport report = {
|
||||
.buttons = button,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.w = w,
|
||||
.hw = 0
|
||||
};
|
||||
|
||||
if (isBleConnected) {
|
||||
input->setValue((uint8_t*)&report, sizeof(report));
|
||||
input->notify();
|
||||
}
|
||||
}
|
||||
// Serial.println(uartData);
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t _hidReportDescriptor[] = {
|
||||
USAGE_PAGE(1), 0x01, // USAGE_PAGE (Generic Desktop)
|
||||
USAGE(1), 0x02, // USAGE (Mouse)
|
||||
COLLECTION(1), 0x01, // COLLECTION (Application)
|
||||
USAGE(1), 0x01, // USAGE (Pointer)
|
||||
COLLECTION(1), 0x00, // COLLECTION (Physical)
|
||||
// ------------------------------------------------- Buttons (Left, Right, Middle, Back, Forward)
|
||||
USAGE_PAGE(1), 0x09, // USAGE_PAGE (Button)
|
||||
USAGE_MINIMUM(1), 0x01, // USAGE_MINIMUM (Button 1)
|
||||
USAGE_MAXIMUM(1), 0x05, // USAGE_MAXIMUM (Button 5)
|
||||
LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM (0)
|
||||
LOGICAL_MAXIMUM(1), 0x01, // LOGICAL_MAXIMUM (1)
|
||||
REPORT_SIZE(1), 0x01, // REPORT_SIZE (1)
|
||||
REPORT_COUNT(1), 0x05, // REPORT_COUNT (5)
|
||||
HIDINPUT(1), 0x02, // INPUT (Data, Variable, Absolute) ;5 button bits
|
||||
// ------------------------------------------------- Padding
|
||||
REPORT_SIZE(1), 0x03, // REPORT_SIZE (3)
|
||||
REPORT_COUNT(1), 0x01, // REPORT_COUNT (1)
|
||||
HIDINPUT(1), 0x03, // INPUT (Constant, Variable, Absolute) ;3 bit padding
|
||||
// ------------------------------------------------- X/Y position, Wheel
|
||||
USAGE_PAGE(1), 0x01, // USAGE_PAGE (Generic Desktop)
|
||||
USAGE(1), 0x30, // USAGE (X)
|
||||
USAGE(1), 0x31, // USAGE (Y)
|
||||
USAGE(1), 0x38, // USAGE (Wheel)
|
||||
LOGICAL_MINIMUM(1), 0x81, // LOGICAL_MINIMUM (-127)
|
||||
LOGICAL_MAXIMUM(1), 0x7f, // LOGICAL_MAXIMUM (127)
|
||||
REPORT_SIZE(1), 0x08, // REPORT_SIZE (8)
|
||||
REPORT_COUNT(1), 0x03, // REPORT_COUNT (3)
|
||||
HIDINPUT(1), 0x06, // INPUT (Data, Variable, Relative) ;3 bytes (X,Y,Wheel)
|
||||
// ------------------------------------------------- Horizontal wheel
|
||||
USAGE_PAGE(1), 0x0c, // USAGE PAGE (Consumer Devices)
|
||||
USAGE(2), 0x38, 0x02, // USAGE (AC Pan)
|
||||
LOGICAL_MINIMUM(1), 0x81, // LOGICAL_MINIMUM (-127)
|
||||
LOGICAL_MAXIMUM(1), 0x7f, // LOGICAL_MAXIMUM (127)
|
||||
REPORT_SIZE(1), 0x08, // REPORT_SIZE (8)
|
||||
REPORT_COUNT(1), 0x01, // REPORT_COUNT (1)
|
||||
HIDINPUT(1), 0x06, // INPUT (Data, Var, Rel)
|
||||
END_COLLECTION(0), // END_COLLECTION
|
||||
END_COLLECTION(0) // END_COLLECTION
|
||||
};
|
||||
|
||||
class BleHIDCallbacks : public BLEServerCallbacks {
|
||||
void onConnect(BLEServer* server) {
|
||||
isBleConnected = true;
|
||||
BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
|
||||
cccDesc->setNotifications(true);
|
||||
Serial.println("CONNECTED");
|
||||
}
|
||||
|
||||
void onDisconnect(BLEServer* server) {
|
||||
isBleConnected = false;
|
||||
BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
|
||||
cccDesc->setNotifications(false);
|
||||
Serial.println("DISCONNECTED");
|
||||
}
|
||||
};
|
||||
|
||||
void bluetoothTask(void*) {
|
||||
BLEDevice::init(DEVICE_NAME);
|
||||
BLEServer* server = BLEDevice::createServer();
|
||||
server->setCallbacks(new BleHIDCallbacks());
|
||||
|
||||
hid = new BLEHIDDevice(server);
|
||||
input = hid->inputReport(0);
|
||||
|
||||
hid->manufacturer()->setValue(BLE_MANUFACTURER);
|
||||
hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
|
||||
hid->hidInfo(0x00, 0x02);
|
||||
|
||||
BLESecurity* security = new BLESecurity();
|
||||
security->setAuthenticationMode(ESP_LE_AUTH_BOND);
|
||||
|
||||
hid->reportMap((uint8_t*)_hidReportDescriptor, sizeof(_hidReportDescriptor));
|
||||
hid->startServices();
|
||||
hid->setBatteryLevel(100);
|
||||
|
||||
BLEAdvertising* advertising = server->getAdvertising();
|
||||
advertising->setAppearance(HID_MOUSE);
|
||||
advertising->addServiceUUID(hid->hidService()->getUUID());
|
||||
advertising->addServiceUUID(hid->deviceInfo()->getUUID());
|
||||
advertising->addServiceUUID(hid->batteryService()->getUUID());
|
||||
advertising->start();
|
||||
|
||||
Serial.println("BLE READY");
|
||||
delay(portMAX_DELAY);
|
||||
};
|
||||
282
anthropic.py
Normal file
282
anthropic.py
Normal file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
|
||||
import httpx
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import Response, StreamingResponse, JSONResponse
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
|
||||
CLAUDE_BASE_URL = "https://api.anthropic.com/v1/messages"
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
allow_credentials=True,
|
||||
)
|
||||
|
||||
|
||||
def get_api_key(headers: dict) -> str:
|
||||
auth = headers.get("authorization")
|
||||
if auth:
|
||||
parts = auth.split(" ")
|
||||
if len(parts) > 1:
|
||||
return parts[1]
|
||||
return
|
||||
|
||||
|
||||
def format_stream_response_json(claude_response: dict) -> dict:
|
||||
typ = claude_response.get("type")
|
||||
if typ == "message_start":
|
||||
return {
|
||||
"id": claude_response["message"]["id"],
|
||||
"model": claude_response["message"]["model"],
|
||||
"inputTokens": claude_response["message"]["usage"]["input_tokens"],
|
||||
}
|
||||
elif typ in ("content_block_start", "ping", "content_block_stop", "message_stop"):
|
||||
return None
|
||||
elif typ == "content_block_delta":
|
||||
return {"content": claude_response["delta"]["text"]}
|
||||
elif typ == "message_delta":
|
||||
return {
|
||||
"stopReason": claude_response["delta"].get("stop_reason"),
|
||||
"outputTokens": claude_response["usage"]["output_tokens"],
|
||||
}
|
||||
elif typ == "error":
|
||||
return {
|
||||
"errorType": claude_response["error"].get("type"),
|
||||
"errorMsg": claude_response["error"]["message"],
|
||||
}
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def claude_to_chatgpt_response(claude_response: dict, meta_info: dict, stream: bool = False) -> dict:
|
||||
timestamp = int(time.time())
|
||||
completion_tokens = meta_info.get("outputTokens", 0) or 0
|
||||
prompt_tokens = meta_info.get("inputTokens", 0) or 0
|
||||
|
||||
if meta_info.get("stopReason") and stream:
|
||||
return {
|
||||
"id": meta_info.get("id"),
|
||||
"object": "chat.completion.chunk",
|
||||
"created": timestamp,
|
||||
"model": meta_info.get("model"),
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"delta": {},
|
||||
"logprobs": None,
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": prompt_tokens,
|
||||
"completion_tokens": completion_tokens,
|
||||
"total_tokens": prompt_tokens + completion_tokens,
|
||||
},
|
||||
}
|
||||
|
||||
message_content = claude_response.get("content", "")
|
||||
result = {
|
||||
"id": meta_info.get("id", "unknown"),
|
||||
"created": timestamp,
|
||||
"model": meta_info.get("model"),
|
||||
"usage": {
|
||||
"prompt_tokens": prompt_tokens,
|
||||
"completion_tokens": completion_tokens,
|
||||
"total_tokens": prompt_tokens + completion_tokens,
|
||||
},
|
||||
"choices": [{"index": 0}],
|
||||
}
|
||||
message = {"role": "assistant", "content": message_content}
|
||||
if not stream:
|
||||
result["object"] = "chat.completion"
|
||||
result["choices"][0]["message"] = message
|
||||
result["choices"][0]["finish_reason"] = "stop" if meta_info.get("stopReason") == "end_turn" else None
|
||||
else:
|
||||
result["object"] = "chat.completion.chunk"
|
||||
result["choices"][0]["delta"] = message
|
||||
return result
|
||||
|
||||
|
||||
async def stream_generator(response: httpx.Response, model: str):
|
||||
meta_info = {"model": model}
|
||||
buffer = ""
|
||||
regex = re.compile(r"event:\s*.*?\s*\ndata:\s*(.*?)(?=\n\n|\s*$)", re.DOTALL)
|
||||
async for chunk in response.aiter_text():
|
||||
buffer += chunk
|
||||
for match in regex.finditer(buffer):
|
||||
try:
|
||||
decoded_line = json.loads(match.group(1).strip())
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
formated_chunk = format_stream_response_json(decoded_line)
|
||||
if formated_chunk is None:
|
||||
continue
|
||||
if formated_chunk.get("errorType", None):
|
||||
etyp = formated_chunk.get("errorType")
|
||||
emsg = formated_chunk.get("errorMsg")
|
||||
data = {"error": {"type": etyp, "code": etyp, "message": emsg, "param": None}}
|
||||
yield f"data: {json.dumps(data)}\n\n"
|
||||
|
||||
meta_info["id"] = formated_chunk.get("id", meta_info.get("id"))
|
||||
meta_info["model"] = formated_chunk.get("model", meta_info.get("model"))
|
||||
meta_info["inputTokens"] = formated_chunk.get("inputTokens", meta_info.get("inputTokens"))
|
||||
meta_info["outputTokens"] = formated_chunk.get("outputTokens", meta_info.get("outputTokens"))
|
||||
meta_info["stopReason"] = formated_chunk.get("stopReason", meta_info.get("stopReason"))
|
||||
transformed_line = claude_to_chatgpt_response(formated_chunk, meta_info, stream=True)
|
||||
yield f"data: {json.dumps(transformed_line)}\n\n"
|
||||
|
||||
else:
|
||||
try:
|
||||
resp = json.loads(buffer)
|
||||
etyp = resp["error"]["type"]
|
||||
emsg = resp["error"]["message"]
|
||||
data = {"error": {"type": etyp, "code": etyp, "message": emsg, "param": None}}
|
||||
yield f"data: {json.dumps(data)}\n\n"
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
last_end = 0
|
||||
for m in regex.finditer(buffer):
|
||||
last_end = m.end()
|
||||
buffer = buffer[last_end:]
|
||||
|
||||
yield "data: [DONE]"
|
||||
|
||||
|
||||
@app.get("/v1/models")
|
||||
async def get_models(request: Request):
|
||||
headers = dict(request.headers)
|
||||
api_key = get_api_key(headers)
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=403, detail="Not Allowed")
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
anthro_resp = await client.get(
|
||||
"https://api.anthropic.com/v1/models",
|
||||
headers={
|
||||
"x-api-key": api_key,
|
||||
"anthropic-version": "2023-06-01",
|
||||
},
|
||||
)
|
||||
if anthro_resp.status_code != 200:
|
||||
raise HTTPException(status_code=anthro_resp.status_code, detail="Error getting models")
|
||||
|
||||
data = anthro_resp.json()
|
||||
models_list = [
|
||||
{"id": m["id"], "object": m["type"], "owned_by": "Anthropic"}
|
||||
for m in data.get("data", [])
|
||||
]
|
||||
|
||||
return JSONResponse(content={"object": "list", "data": models_list})
|
||||
|
||||
|
||||
@app.post("/v1/chat/completions")
|
||||
async def chat_completions(request: Request):
|
||||
headers = dict(request.headers)
|
||||
api_key = get_api_key(headers)
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=403, detail="Not Allowed")
|
||||
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception:
|
||||
raise HTTPException(status_code=400, detail="Invalid JSON payload")
|
||||
|
||||
model = body.get("model")
|
||||
messages = body.get("messages", [])
|
||||
temperature = body.get("n", 1)
|
||||
max_tokens = body.get("max_tokens", 4096)
|
||||
stop = body.get("stop")
|
||||
stream = body.get("stream", False)
|
||||
|
||||
system_message = next((m for m in messages if m.get("role") == "system"), [])
|
||||
filtered_messages = [m for m in messages if m.get("role") != "system"]
|
||||
|
||||
claude_request_body = {
|
||||
"model": model,
|
||||
"messages": filtered_messages,
|
||||
"temperature": temperature,
|
||||
"max_tokens": max_tokens,
|
||||
"stop_sequences": stop,
|
||||
"system": system_message.get("content") if system_message else [],
|
||||
"stream": stream,
|
||||
}
|
||||
|
||||
if not stream:
|
||||
async with httpx.AsyncClient(timeout=None) as client:
|
||||
claude_resp = await client.post(
|
||||
CLAUDE_BASE_URL,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": api_key,
|
||||
"anthropic-version": "2023-06-01",
|
||||
},
|
||||
json=claude_request_body,
|
||||
)
|
||||
|
||||
if claude_resp.status_code != 200:
|
||||
print(claude_resp.content)
|
||||
return Response(status_code=claude_resp.status_code, content=claude_resp.content)
|
||||
|
||||
resp_json = claude_resp.json()
|
||||
|
||||
if resp_json.get("type") == "error":
|
||||
result = {
|
||||
"error": {
|
||||
"message": resp_json.get("error", {}).get("message"),
|
||||
"type": resp_json.get("error", {}).get("type"),
|
||||
"param": None,
|
||||
"code": resp_json.get("error", {}).get("type"),
|
||||
}
|
||||
}
|
||||
else:
|
||||
formated_info = {
|
||||
"id": resp_json.get("id"),
|
||||
"model": resp_json.get("model"),
|
||||
"inputTokens": resp_json.get("usage", {}).get("input_tokens"),
|
||||
"outputTokens": resp_json.get("usage", {}).get("output_tokens"),
|
||||
"stopReason": resp_json.get("stop_reason"),
|
||||
}
|
||||
|
||||
content = ""
|
||||
try:
|
||||
content = resp_json.get("content", [])[0].get("text", "")
|
||||
except Exception:
|
||||
pass
|
||||
result = claude_to_chatgpt_response({"content": content}, formated_info)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=claude_resp.status_code,
|
||||
content=result
|
||||
)
|
||||
else:
|
||||
async def stream_response(model: str, claude_request_body: dict, api_key: str):
|
||||
async with httpx.AsyncClient(timeout=None) as client:
|
||||
async with client.stream(
|
||||
"POST",
|
||||
CLAUDE_BASE_URL,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": api_key,
|
||||
"anthropic-version": "2023-06-01",
|
||||
},
|
||||
json=claude_request_body,
|
||||
) as response:
|
||||
async for event in stream_generator(response, model):
|
||||
yield event
|
||||
|
||||
return StreamingResponse(
|
||||
stream_response(model, claude_request_body, api_key),
|
||||
media_type = "text/event-stream"
|
||||
|
||||
)
|
||||
77
cfddns.py
Normal file
77
cfddns.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import requests
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
|
||||
CF_GLOBAL_KEY = os.environ.get("CF_GLOBAL_KEY")
|
||||
CF_EMAIL = os.environ.get("CF_EMAIL")
|
||||
|
||||
auth = {"X-Auth-Email": CF_EMAIL, "X-Auth-Key": CF_GLOBAL_KEY, "Content-Type": "application/json"}
|
||||
|
||||
|
||||
def update_dns(domain, new_ip):
|
||||
for i in list_zones():
|
||||
if domain.endswith(i[1]):
|
||||
zone = i[0]
|
||||
if not zone:
|
||||
print("ERROR DOMAIN NOT FOUND")
|
||||
|
||||
if create_domain(zone, domain, new_ip):
|
||||
print(f"RECORD {domain} -> {new_ip} CREATED")
|
||||
return
|
||||
|
||||
list_api = f"https://api.cloudflare.com/client/v4/zones/{zone}/dns_records?name="
|
||||
edit_api = f"https://api.cloudflare.com/client/v4/zones/{zone}/dns_records"
|
||||
dns_info = {"type": "A", "name": domain, "ttl": 3600, "proxied": False}
|
||||
|
||||
try:
|
||||
old_ip = socket.gethostbyname(dns_info["name"])
|
||||
except socket.gaierror:
|
||||
old_ip = None
|
||||
|
||||
if old_ip == new_ip:
|
||||
print(f"RECORD {dns_info['name']} = {old_ip} NOT CHANGED")
|
||||
else:
|
||||
dns_id = requests.get(f"{list_api}{domain}", headers=auth).json()["result"][0]["id"]
|
||||
dns_info["content"] = new_ip
|
||||
upd = requests.put(f"{edit_api}/{dns_id}", headers=auth, json=dns_info).json()
|
||||
dn_name = upd["result"]["name"]
|
||||
dn_type = upd["result"]["type"]
|
||||
dn_content = upd["result"]["content"]
|
||||
print(f"RECORD {dn_name} {dn_type} {dn_content} UPDATED")
|
||||
|
||||
|
||||
def list_zones():
|
||||
api_url = "https://api.cloudflare.com/client/v4/zones"
|
||||
resp = requests.get(api_url, headers=auth).json()
|
||||
return [(z["id"], z["name"]) for z in resp["result"]]
|
||||
|
||||
|
||||
def list_domains(zid):
|
||||
api_url = f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records"
|
||||
resp = requests.get(api_url, headers=auth).json()
|
||||
return resp["result"]
|
||||
|
||||
|
||||
def create_domain(zone, domain, ip=None):
|
||||
existing = [i["name"] for i in list_domains(zone)]
|
||||
if domain not in existing:
|
||||
create_record(domain, zone, ip)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def create_record(sub, zid, ip="1.1.1.1", record="A"):
|
||||
api_url = f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records"
|
||||
data = {"content": ip, "name": sub, "proxied": False, "type": record, "comment": "", "ttl": 3600}
|
||||
res_txt = requests.post(api_url, headers=auth, json=data).text
|
||||
print(res_txt)
|
||||
return res_txt
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TARGET_DOMAIN = sys.argv[1]
|
||||
TARGET_ADDR = requests.get("https://ping.api.morgan.kr").json()["info"]["client"]
|
||||
print(f"UPDATE {TARGET_DOMAIN} <- {TARGET_ADDR}")
|
||||
update_dns(TARGET_DOMAIN, TARGET_ADDR)
|
||||
43
desktop-file-list
Normal file
43
desktop-file-list
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
parse_desktop_file() {
|
||||
local file="$1"
|
||||
local section=""
|
||||
|
||||
local mainexec=""
|
||||
local mainname=""
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^\;.* || "$line" =~ ^#.* || -z "$line" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^\[.*\]$ ]]; then
|
||||
section="${line:1:-1}"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[2]}"
|
||||
# echo "[$section] $key = $value"
|
||||
|
||||
if [ "$section" == "Desktop Entry" ]; then
|
||||
if [ "$key" == "Exec" ]; then mainexec=$value; fi
|
||||
if [ "$key" == "Name" ]; then mainname=$value; fi
|
||||
fi
|
||||
fi
|
||||
done < "$file"
|
||||
|
||||
echo "{\"file\": \"$file\", \"name\": \"$mainname\", \"exec\": \"$mainexec\"}"
|
||||
}
|
||||
|
||||
shopt -s nullglob
|
||||
IFS=':' read -r -a paths <<< "$XDG_DATA_DIRS:/home/$USER/.local/share/"
|
||||
for path in "${paths[@]}"; do
|
||||
AP=$(realpath "$path/applications")
|
||||
for appfile in $AP/*.desktop; do
|
||||
parse_desktop_file $appfile;
|
||||
done
|
||||
done
|
||||
shopt -u nullglob
|
||||
108
en_XX
Normal file
108
en_XX
Normal file
@@ -0,0 +1,108 @@
|
||||
comment_char %
|
||||
escape_char /
|
||||
|
||||
LC_IDENTIFICATION
|
||||
title "English International"
|
||||
source ""
|
||||
address ""
|
||||
contact ""
|
||||
email ""
|
||||
tel ""
|
||||
fax ""
|
||||
language ""
|
||||
territory ""
|
||||
revision ""
|
||||
date ""
|
||||
category "i18n:2012";LC_IDENTIFICATION
|
||||
category "i18n:2012";LC_CTYPE
|
||||
category "i18n:2012";LC_COLLATE
|
||||
category "i18n:2012";LC_TIME
|
||||
category "i18n:2012";LC_NUMERIC
|
||||
category "i18n:2012";LC_MONETARY
|
||||
category "i18n:2012";LC_MESSAGES
|
||||
category "i18n:2012";LC_PAPER
|
||||
category "i18n:2012";LC_NAME
|
||||
category "i18n:2012";LC_ADDRESS
|
||||
category "i18n:2012";LC_TELEPHONE
|
||||
category "i18n:2012";LC_MEASUREMENT
|
||||
END LC_IDENTIFICATION
|
||||
|
||||
LC_CTYPE
|
||||
copy "en_US"
|
||||
END LC_CTYPE
|
||||
|
||||
LC_COLLATE
|
||||
copy "iso14651_t1"
|
||||
END LC_COLLATE
|
||||
|
||||
LC_MONETARY
|
||||
int_curr_symbol ""
|
||||
currency_symbol ""
|
||||
mon_decimal_point ","
|
||||
mon_thousands_sep " "
|
||||
mon_grouping 3
|
||||
positive_sign ""
|
||||
negative_sign "-"
|
||||
int_frac_digits 2
|
||||
frac_digits 2
|
||||
p_cs_precedes 1
|
||||
p_sep_by_space 0
|
||||
n_cs_precedes 1
|
||||
n_sep_by_space 0
|
||||
p_sign_posn 1
|
||||
n_sign_posn 1
|
||||
END LC_MONETARY
|
||||
|
||||
LC_NUMERIC
|
||||
decimal_point ","
|
||||
thousands_sep " "
|
||||
grouping 3
|
||||
END LC_NUMERIC
|
||||
|
||||
LC_TIME
|
||||
abday "Sun";"Mon";"Tue";"Wed";"Thu";"Fri";"Sat"
|
||||
day "Sunday";"Monday";"Tuesday";"Wednesday";"Thursday";/
|
||||
"Friday";"Saturday"
|
||||
abmon "Jan";"Feb";"Mar";"Apr";"May";"Jun";"Jul";"Aug";"Sep";/
|
||||
"Oct";"Nov";"Dec"
|
||||
mon "January";"February";"March";"April";"May";"June";"July";/
|
||||
"August";"September";"October";"November";"December"
|
||||
week 7;19971130;4
|
||||
first_weekday 1
|
||||
first_workday 2
|
||||
d_t_fmt "%a %Y-%m-%d %H:%M:%S"
|
||||
d_fmt "%Y-%m-%d"
|
||||
t_fmt "%H:%M:%S"
|
||||
t_fmt_ampm ""
|
||||
am_pm "";""
|
||||
date_fmt "%a %Y-%m-%d %H:%M:%S %Z"
|
||||
END LC_TIME
|
||||
|
||||
|
||||
LC_MESSAGES
|
||||
yesexpr "^[yY]"
|
||||
noexpr "^[nN]"
|
||||
yesstr ""
|
||||
nostr ""
|
||||
END LC_MESSAGES
|
||||
|
||||
LC_PAPER
|
||||
height 297
|
||||
width 210
|
||||
END LC_PAPER
|
||||
|
||||
LC_NAME
|
||||
name_fmt "%p%t%g%t%m%t%f"
|
||||
END LC_NAME
|
||||
|
||||
LC_ADDRESS
|
||||
postal_fmt "%a%N%f%N%d%N%b%N%s %h %e %r%N%C-%z %T%N%c%N"
|
||||
END LC_ADDRESS
|
||||
|
||||
LC_TELEPHONE
|
||||
tel_int_fmt "+%c %a%t%l"
|
||||
END LC_TELEPHONE
|
||||
|
||||
LC_MEASUREMENT
|
||||
measurement 1
|
||||
END LC_MEASUREMENT
|
||||
79
extract
Normal file
79
extract
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Archive eXtractor
|
||||
|
||||
error() {
|
||||
echo "Error: $1" >&2
|
||||
}
|
||||
|
||||
run() {
|
||||
echo "Debug: $@"
|
||||
$@
|
||||
}
|
||||
|
||||
if [ ! -f "$1" ]; then
|
||||
error "No file provided"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BN="${1%.*}"
|
||||
BASE="${1%.*}"
|
||||
TRY=0
|
||||
|
||||
while [ -d "$BASE" ]; do
|
||||
if [ $TRY -gt 10 ]; then
|
||||
error "Already Exists (10)"
|
||||
exit 1
|
||||
fi
|
||||
error "Already exists ($BASE)"
|
||||
TRY=$((TRY + 1))
|
||||
BASE="${BN}_${TRY}"
|
||||
done
|
||||
|
||||
FILE_TYPE=$(file --mime-type -b "$1")
|
||||
EXT="${1##*.}"
|
||||
|
||||
case "$FILE_TYPE" in
|
||||
application/x-tar) FORMAT="tar" ;;
|
||||
application/zip) FORMAT="zip" ;;
|
||||
application/x-7z-compressed) FORMAT="7z" ;;
|
||||
*)
|
||||
case "$EXT" in
|
||||
tar.gz|tar.bz2|tar.xz|tar.zst|tgz|tbz2|txz) FORMAT="tar" ;;
|
||||
gz) FORMAT="gzip" ;;
|
||||
bz2) FORMAT="bzip2" ;;
|
||||
xz) FORMAT="xz" ;;
|
||||
*)
|
||||
error "Unsupported file format"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
run mkdir "$BASE"
|
||||
|
||||
case $FORMAT in
|
||||
tar)
|
||||
run tar -xvf "$1" -C "$BASE"
|
||||
;;
|
||||
zip)
|
||||
run unzip "$1" -d "$BASE"
|
||||
;;
|
||||
7z)
|
||||
run 7z x "$1" -o"$BASE"
|
||||
;;
|
||||
gzip)
|
||||
run gunzip -c "$1" > "$BASE/$(basename "$BN")"
|
||||
;;
|
||||
bzip2)
|
||||
run bunzip2 -c "$1" > "$BASE/$(basename "$BN")"
|
||||
;;
|
||||
xz)
|
||||
run unxz -c "$1" > "$BASE/$(basename "$BN")"
|
||||
;;
|
||||
*)
|
||||
error "Unexpected error occurred"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
68
fallback.py
Normal file
68
fallback.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import RedirectResponse, FileResponse
|
||||
import requests
|
||||
import datetime
|
||||
|
||||
app = FastAPI()
|
||||
hosts = [
|
||||
"SERVER1",
|
||||
"SERVER2",
|
||||
"SERVER3",
|
||||
"SERVER4"
|
||||
]
|
||||
cache = {}
|
||||
|
||||
def find_fit(parent):
|
||||
print(cache)
|
||||
hosts_local = hosts.copy()
|
||||
if parent in cache.keys():
|
||||
print(f"DEBUG: Cache Hit {parent}: {cache[parent]}")
|
||||
idx = hosts.index(cache[parent])
|
||||
if idx:
|
||||
hosts_local[idx], hosts_local[0] = hosts_local[0], hosts_local[idx]
|
||||
|
||||
for host in hosts_local:
|
||||
try:
|
||||
print(f"DEBUG: Tesing for http://{host}/{parent}")
|
||||
if requests.head(f"http://{host}/{parent}", allow_redirects=True).status_code == 200:
|
||||
cache[parent] = host
|
||||
return host
|
||||
else:
|
||||
cache[parent] = ""
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def log(level, status_code, path, header):
|
||||
print(f'{level}: [{datetime.datetime.now()}] "{status_code} {path} "{header.get("User-Agent")}"')
|
||||
|
||||
@app.get("/{path:path}")
|
||||
async def index(request: Request, path: str):
|
||||
if path in [None, "", "favicon.ico", "index.html"]:
|
||||
if path in [None, ""]:
|
||||
path = "index.html"
|
||||
print(f'RETURN: [{datetime.datetime.now()}] "200 {path} "{request.headers.get("User-Agent")}"')
|
||||
return FileResponse(path)
|
||||
|
||||
server = find_fit(path.split("/")[0])
|
||||
|
||||
if server:
|
||||
print(f'RETURN: [{datetime.datetime.now()}] "301 http://{server}/{path}" "{request.headers.get("User-Agent")}"')
|
||||
return RedirectResponse(url=f"http://{server}/{path}")
|
||||
else:
|
||||
print(f'RETURN: [{datetime.datetime.now()}] "404 Not found" "{request.headers.get("User-Agent")}"')
|
||||
raise HTTPException(status_code=404, detail="No available server.")
|
||||
|
||||
if __name__=="__main__":
|
||||
print('Starting Server...')
|
||||
uvicorn.run(
|
||||
"fallback:app",
|
||||
host="0.0.0.0",
|
||||
port=8080,
|
||||
log_level="info",
|
||||
reload=True,
|
||||
)
|
||||
|
||||
48
hls_dl
Normal file
48
hls_dl
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
CURRENT=$(pwd)
|
||||
WORKDIR=$(mktemp -d)
|
||||
|
||||
cd $WORKDIR
|
||||
|
||||
M3U8="$1"
|
||||
NAME="$2"
|
||||
|
||||
curl "$M3U8" > PLAYLIST
|
||||
|
||||
touch MONITOR
|
||||
|
||||
{
|
||||
cat PLAYLIST | \
|
||||
grep -v '^#.*' | \
|
||||
xargs -P 100 -I {} bash -c 'FN=$(echo "{}" | sha1sum | cut -d" " -f1); if [ ! -f $FN ]; then curl "{}" -s --output $FN; fi; echo .';
|
||||
rm MONITOR;
|
||||
} > MONITOR &
|
||||
|
||||
echo DOWNLOADING
|
||||
|
||||
TOTAL=$(cat PLAYLIST | grep -v '^#' | wc -l)
|
||||
|
||||
while [ -f MONITOR ]; do
|
||||
echo $(cat MONITOR | wc -l)/$TOTAL
|
||||
sleep 1;
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
echo DONE
|
||||
|
||||
for i in $(<PLAYLIST); do
|
||||
{ echo $i | grep -v '^#' > /dev/null; } \
|
||||
&& {
|
||||
FN=$(echo $i | sha1sum | cut -d" " -f1);
|
||||
echo $FN;
|
||||
} \
|
||||
|| echo $i;
|
||||
done > MODIFIED_PLAYLIST.m3u8
|
||||
|
||||
ffmpeg -allowed_extensions ALL -extension_picky 0 -i MODIFIED_PLAYLIST.m3u8 -c copy $CURRENT/$NAME
|
||||
|
||||
cd $CURRENT
|
||||
|
||||
rm -r $WORKDIR
|
||||
8
hwplibre
Normal file
8
hwplibre
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
bef=$1
|
||||
aft=${bef%.*}.odt
|
||||
|
||||
zenity --info --text="Converting $bef to $aft.";
|
||||
eval "~/.python38/bin/hwp5odt \"$bef\" --output \"$aft\""
|
||||
|
||||
eval "libreoffice --writer \"$aft\""
|
||||
47
netwatch
Normal file
47
netwatch
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
cleanup () {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch exited. ($$)" | tee -a /var/log/netwatch.log;
|
||||
rm /var/.netwatch.lock
|
||||
}
|
||||
|
||||
if [ -f /var/.netwatch.lock ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch already running. ($$)" | tee -a /var/log/netwatch.log;
|
||||
exit;
|
||||
fi
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
FAIL=0
|
||||
RELOADED=0
|
||||
|
||||
if [ ! -f /var/.netwatch.lock ]; then
|
||||
touch /var/.netwatch.lock
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch started ($$)" | tee -a /var/log/netwatch.log;
|
||||
|
||||
while true; do
|
||||
if [ $FAIL -gt 20 ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch triggered ($$) - $FAIL" | tee -a /var/log/netwatch.log;
|
||||
if [ $RELOADED == 1 ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch triggered ($$) - $FAIL - HARD RESET" | tee -a /var/log/netwatch.log;
|
||||
reboot;
|
||||
fi
|
||||
systemctl restart networking
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $(systemctl status networking; ip a;)" | tee -a /var/log/netwatch.log;
|
||||
FAIL=0
|
||||
RELOADED=1
|
||||
fi
|
||||
|
||||
curl -s https://ping.api.morgan.kr/ >/dev/null && FAIL=0 || FAIL=$(($FAIL+1))
|
||||
ping 1.1.1.1 -c 1 -w 1 >/dev/null && FAIL=0 || FAIL=$(($FAIL+1))
|
||||
|
||||
if [ $FAIL -ne 0 ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch failed ($$) - $FAIL" | tee -a /var/log/netwatch.log;
|
||||
else
|
||||
RELOADED=0
|
||||
fi
|
||||
|
||||
sleep 60
|
||||
done
|
||||
fi
|
||||
131
pkgupdate
Normal file
131
pkgupdate
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/bin/bash
|
||||
|
||||
PKGS=()
|
||||
|
||||
update_librewolf () {
|
||||
PKGNAME="librewolf"
|
||||
TMPDIR="$(mktemp -d)"
|
||||
PWD=$(pwd)
|
||||
|
||||
echo "[*] Updating $PKGNAME..."
|
||||
echo "[*] - Entering $TMPDIR"
|
||||
cd $TMPDIR
|
||||
|
||||
URL=$(curl -L https://gitlab.com/api/v4/projects/44042130/releases/permalink/latest | jq -r '.assets.links[] | select(.name | test("^librewolf-[0-9\\.-]+-linux-x86_64-package\\.tar\\.xz$")) | .url')
|
||||
|
||||
echo "[*] - Downloading from $URL"
|
||||
curl "$URL" -o librewolf.tar.xz
|
||||
|
||||
echo "[*] - Extracting..."
|
||||
sudo tar -xvf librewolf.tar.xz >> log
|
||||
|
||||
echo "[*] - Installing..."
|
||||
sudo cp -r /opt/librewolf ./previous
|
||||
sudo rsync -avx librewolf/ /opt/librewolf/ >> log
|
||||
|
||||
echo "[*] - Running post-script..."
|
||||
sudo chown root:root /opt/librewolf/ /opt/librewolf/librewolf
|
||||
|
||||
echo "[*] - Done!"
|
||||
cd $PWD
|
||||
}
|
||||
|
||||
update_1password () {
|
||||
PKGNAME="1Password"
|
||||
TMPDIR="$(mktemp -d)"
|
||||
PWD=$(pwd)
|
||||
|
||||
echo "[*] Updating $PKGNAME..."
|
||||
echo "[*] - Entering $TMPDIR"
|
||||
cd $TMPDIR
|
||||
|
||||
URL="https://downloads.1password.com/linux/tar/stable/x86_64/1password-latest.tar.gz"
|
||||
|
||||
echo "[*] - Downloading from $URL"
|
||||
curl "$URL" -o 1password-latest.tar.gz
|
||||
|
||||
echo "[*] - Extracting..."
|
||||
sudo tar -xvf 1password-latest.tar.gz >> log
|
||||
|
||||
echo "[*] - Installing..."
|
||||
sudo cp -r /opt/1Password ./.previous
|
||||
sudo rsync -avx 1password-*/ /opt/1Password/ >> log
|
||||
|
||||
echo "[*] - Running post-script..."
|
||||
sudo /opt/1Password/after-install.sh >> log
|
||||
|
||||
echo "[*] - Done!"
|
||||
cd $PWD
|
||||
}
|
||||
|
||||
update_ungoogled_chromium () {
|
||||
PKGNAME="ungoogled-chromium"
|
||||
TMPDIR="$(mktemp -d)"
|
||||
PWD=$(pwd)
|
||||
PROJ_URL="https://api.github.com/repos/ungoogled-software/ungoogled-chromium-portablelinux/releases/latest"
|
||||
URL=$(curl $PROJ_URL | jq -r '.assets.[] | select(.name | endswith(".tar.xz")) | .browser_download_url')
|
||||
|
||||
download () {
|
||||
curl -L "$URL" -o package.tar.xz
|
||||
}
|
||||
|
||||
extract () {
|
||||
sudo tar -xvf package.tar.xz >> log
|
||||
}
|
||||
|
||||
install () {
|
||||
mv ./ungoogled-chromium_* ./NEW
|
||||
sudo mv /opt/ungoogled-chromium/ ./PREV/
|
||||
sudo mv ./NEW /opt/ungoogled-chromium/ | tee log
|
||||
}
|
||||
|
||||
postscript () {
|
||||
sudo chown root:root /opt/ungoogled-chromium/ /opt/ungoogled-chromium/chrome
|
||||
sudo bash -c 'bash <(curl https://raw.githubusercontent.com/morgan9e/chrome-blank-newtab/refs/heads/main/patch.sh) /opt/ungoogled-chromium/resources.pak'
|
||||
}
|
||||
|
||||
echo "[*] Updating $PKGNAME..."
|
||||
echo "[*] - Entering $TMPDIR"
|
||||
cd $TMPDIR
|
||||
echo "[*] - Downloading from $URL"
|
||||
download
|
||||
echo "[*] - Extracting..."
|
||||
extract
|
||||
echo "[*] - Installing..."
|
||||
install
|
||||
echo "[*] - Running post-script..."
|
||||
postscript
|
||||
echo "[*] - Done!"
|
||||
cd $PWD
|
||||
}
|
||||
|
||||
|
||||
PKGS+=("librewolf")
|
||||
PKGS+=("1password")
|
||||
PKGS+=("ungoogled_chromium")
|
||||
|
||||
# main
|
||||
|
||||
if [[ "$1" == "list" ]]; then
|
||||
echo "${PKGS[*]}"
|
||||
exit 0
|
||||
elif [[ -z "$1" ]]; then
|
||||
echo "Usage: $(basename $0) [package_name | all | list]"
|
||||
echo "Available: ${PKGS[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
sudo "$0" "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "all" ]]; then
|
||||
for pkg in "${PKGS[@]}"; do
|
||||
"update_$pkg"
|
||||
done
|
||||
elif [[ -n "$1" ]]; then
|
||||
"update_$1"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
27
snap
Normal file
27
snap
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$EUID" -ne 0 ]
|
||||
then echo "Please run as root."
|
||||
exit
|
||||
fi
|
||||
|
||||
TIME=$(date +%Y_%m_%d-%H_%M)
|
||||
|
||||
echo $TIME > /SNAPSHOT
|
||||
echo $TIME > /home/SNAPSHOT
|
||||
|
||||
mkdir /tmp/btrfsnap-$TIME
|
||||
|
||||
mount $(findmnt / -no SOURCE | cut -d '[' -f 1) /tmp/btrfsnap-$TIME
|
||||
|
||||
btrfs subvol snap -r /tmp/btrfsnap-$TIME/@root /tmp/btrfsnap-$TIME/@snapshots/@root-$TIME
|
||||
|
||||
btrfs subvol snap -r /tmp/btrfsnap-$TIME/@home /tmp/btrfsnap-$TIME/@snapshots/@home-$TIME
|
||||
|
||||
umount /tmp/btrfsnap-$TIME
|
||||
|
||||
rmdir /tmp/btrfsnap-$TIME
|
||||
|
||||
rm /SNAPSHOT
|
||||
|
||||
rm /home/SNAPSHOT
|
||||
Reference in New Issue
Block a user