Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

14 changed files with 18 additions and 314 deletions

View file

@ -1,54 +0,0 @@
on:
push:
branches:
- 'main'
tags:
- 'v*'
paths:
- '**.go'
- '**.html'
- 'Dockerfile'
- 'Dockerfile.*'
- 'docker-bake.hcl'
workflow_dispatch:
jobs:
bake:
runs-on: docker
steps:
- name: Prepare Registry FQDN
id: registry
run: |
registry=${{ github.server_url }}
registry=${registry##http*://}
echo "registry=${registry}" >> "$GITHUB_OUTPUT"
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ steps.registry.outputs.registry }}
username: ${{ github.repository_owner }}
password: ${{ secrets.TOKEN }}
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build and push
uses: docker/bake-action@v6
with:
source: .
env:
TAG: ${{ steps.meta.outputs.tags }}

View file

@ -3,7 +3,6 @@ package tweeter
import (
"bytes"
"encoding/json"
"html"
"log"
"net/http"
"strings"
@ -81,7 +80,7 @@ func (app App) SendToWebhook(tweets []*ts.Tweet) {
tweetText = strings.ReplaceAll(tweetText, video.URL, "")
}
mainEmbed.SetText(html.UnescapeString(strings.TrimSpace(tweetText)))
mainEmbed.SetText(strings.TrimSpace(tweetText))
for _, data := range webhooksToSend {
err := sendRequest(app.config.Webhook, data)

View file

@ -1,19 +1,12 @@
username = ""
password = "asd123"
#proxyaddr = "socks5://localhost:5555"
#usewebserver = false
indextarget = "https://discord.gg/Kw4MFGxYEj" # Optional
hostURL = "https://my.domain.tld" # Can be omitted if not using webserver
webhook = "https://domain.tld/api/webhooks/"
hostURL = "https://my.domain.tld"
channels = [
"NinEverything",
"NintendoEurope",
"NintendoAmerica",
]
# Binary representation for efficient filtering
# Bit from left to right (most to least significant bit): IsSelfThread, IsRetweet, IsReply, IsPin, IsQuoted
filter = [

View file

@ -8,8 +8,8 @@ variable "TAG" {
function "generate_tags" {
params = [images, versions]
result = distinct(flatten(
[for i in split(",", replace(images, "\n", ",")) :
[for v in split(",", replace(versions, "\n", ",")) :
[for i in split(",", images) :
[for v in split(",", versions) :
"${i}:${v}"
]
]))
@ -24,8 +24,8 @@ target "default" {
}
target "prod" {
inherits = ["default"]
platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/riscv64"]
#platforms = ["linux/amd64", "linux/arm64"]
//platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/riscv64"]
platforms = ["linux/amd64", "linux/arm64"]
dockerfile = "Dockerfile.multiarch"
output = ["type=registry"]
attest = [

View file

@ -1,21 +0,0 @@
{ pkgs ? (
let
inherit (builtins) fetchTree fromJSON readFile;
inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix;
in
import (fetchTree nixpkgs.locked) {
overlays = [
(import "${fetchTree gomod2nix.locked}/overlay.nix")
];
}
)
, buildGoApplication ? pkgs.buildGoApplication
}:
buildGoApplication {
pname = "discord-tweeter";
version = "0.1";
pwd = ./.;
src = ../.;
modules = ./gomod2nix.toml;
}

85
nix/flake.lock generated
View file

@ -1,85 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gomod2nix": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1742209644,
"narHash": "sha256-jMy1XqXqD0/tJprEbUmKilTkvbDY/C0ZGSsJJH4TNCE=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "8f3534eb8f6c5c3fce799376dc3b91bae6b11884",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "gomod2nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1742288794,
"narHash": "sha256-Txwa5uO+qpQXrNG4eumPSD+hHzzYi/CdaM80M9XRLCo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b6eaf97c6960d97350c584de1b6dcff03c9daf42",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,24 +0,0 @@
{
description = "NixOS flake for discord-tweeter";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.gomod2nix.url = "github:nix-community/gomod2nix";
inputs.gomod2nix.inputs.nixpkgs.follows = "nixpkgs";
inputs.gomod2nix.inputs.flake-utils.follows = "flake-utils";
outputs = { self, nixpkgs, flake-utils, gomod2nix }:
(flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = nixpkgs.legacyPackages.${system};
callPackage = pkgs.darwin.apple_sdk_11_0.callPackage or pkgs.callPackage;
in
{
nixosModules.default = callPackage ./services.nix { inherit self; };
packages.default = callPackage ./. { inherit (gomod2nix.legacyPackages.${system}) buildGoApplication; };
devShells.default = callPackage ./shell.nix { inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix; };
})
);
}

View file

@ -1,21 +0,0 @@
schema = 3
[mod]
[mod."github.com/AlexEidt/Vidio"]
version = "v1.5.1"
hash = "sha256-WdnCYjxbFqDmh/IVaVCCc1TzvklKdJtDMdIgn6m6NTM="
[mod."github.com/BurntSushi/toml"]
version = "v1.5.0"
hash = "sha256-wX8bEVo7swuuAlm0awTIiV1KNCAXnm7Epzwl+wzyqhw="
[mod."github.com/imperatrona/twitter-scraper"]
version = "v0.0.16"
hash = "sha256-nbv9fI6/3OxnC8NaLO82UfcwbJMz1jxvfNjZPOrILzM="
[mod."github.com/jmoiron/sqlx"]
version = "v1.4.0"
hash = "sha256-0H132+A983nBr2zEyCKsJoBCZlC9pG+ylEcGysxKL4M="
[mod."github.com/mattn/go-sqlite3"]
version = "v1.14.24"
hash = "sha256-taGKFZFQlR5++5b2oZ1dYS3RERKv6yh1gniNWhb4egg="
[mod."golang.org/x/net"]
version = "v0.37.0"
hash = "sha256-sZKbJISVdBwyuYRQgrraTKxeIORWlzK5hScceQ2dE58="

View file

@ -1,51 +0,0 @@
{config, lib, pkgs, ...}:
with lib;
let
cfg = config.services.discord-tweeter;
format = pkgs.formats.toml { };
configFile = format.generate "config.toml" cfg.settings;
in
{
options.services.discord-tweeter = {
enable = mkEnableOption (lib.mdDoc "discord-tweeter");
settings = mkOption {
type = format.type;
default = {
username = "";
password = "";
proxyaddr = "";
webhook = "";
DbPath = "/var/lib/discord-tweeter/";
CookiePath = "/var/lib/discord-tweeter/";
channels = [ ];
filter = [ 0b11111 ];
UseWebServer = true;
HostURL = "";
WebPort = 8080;
UserAgents = [ "discordbot" "curl" "httpie" "lwp-request" "wget" "python-requests" "openbsd ftp" "powershell" ];
NitterBase = "https://xcancel.com";
};
description = lib.mdDoc ''
'';
};
};
config = mkIf cfg.enable {
systemd.services.discord-tweeter = {
description = "Send tweets to Discord by scraping Twitter (now X)";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
DynamicUser = true;
ExecStart = "${self.packages.${pkgs.system}.default}/bin/discord-tweeter ${configFile}";
Restart = "on-failure";
};
};
};
}

View file

@ -1,24 +0,0 @@
{ pkgs ? (
let
inherit (builtins) fetchTree fromJSON readFile;
inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix;
in
import (fetchTree nixpkgs.locked) {
overlays = [
(import "${fetchTree gomod2nix.locked}/overlay.nix")
];
}
)
, mkGoEnv ? pkgs.mkGoEnv
, gomod2nix ? pkgs.gomod2nix
}:
let
goEnv = mkGoEnv { pwd = ../.; };
in
pkgs.mkShell {
packages = [
goEnv
gomod2nix
];
}

View file

@ -20,7 +20,6 @@ type Config struct {
WebPort uint16
UserAgents []string
NitterBase string
IndexTarget string
}
func ConfigFromFile(filePath string) (*Config, error) {

View file

@ -5,8 +5,8 @@
<meta name="theme-color" content="#26a7de">
<link rel="canonical" href="{{ .URL }}">
<meta property="twitter:site" content="@{{ .Username }}">
<meta property="twitter:creator" content="@{{ .Username }}">
<meta property="twitter:site" content="{{ .Username }}">
<meta property="twitter:creator" content="{{ .Username }}">
<meta property="twitter:title" content="{{ .Title }}">
{{- range $idx, $e := .Images }}
<meta property="twitter:image" content="{{ $e }}">

View file

@ -6,8 +6,8 @@
<link rel="canonical" href="{{ .URL }}">
{{- if .Videos }}
<meta property="twitter:site" content="@{{ .Username }}">
<meta property="twitter:creator" content="@{{ .Username }}">
<meta property="twitter:site" content="{{ .Username }}">
<meta property="twitter:creator" content="{{ .Username }}">
{{- range $idx, $e := .Videos }}
<meta property="twitter:player:stream" content="{{ $e }}">
<meta property="og:video" content="{{ $e }}">

View file

@ -23,7 +23,7 @@ const (
//go:embed templates/*.html
var templateFiles embed.FS
type Response struct {
type Reponse struct {
StatusCode int
ContentType string
Content string
@ -34,7 +34,7 @@ type WebServer struct {
scraper *ts.Scraper
templates *template.Template
avatarCache *cache.Cache[string, string]
responseCache *cache.Cache[string, Response]
responseCache *cache.Cache[string, Reponse]
Server *http.Server
}
@ -64,7 +64,7 @@ func New(config *config.Config, scraper *ts.Scraper) (*WebServer, error) {
scraper,
tmpl,
cache.New[string, string](),
cache.New[string, Response](),
cache.New[string, Reponse](),
&http.Server{
Handler: sm,
Addr: fmt.Sprintf(":%d", config.WebPort),
@ -72,19 +72,12 @@ func New(config *config.Config, scraper *ts.Scraper) (*WebServer, error) {
WriteTimeout: 30 * time.Second,
},
}
sm.HandleFunc("GET /", ws.handleIndex)
sm.HandleFunc("GET /avatar/{username}", ws.handleAvatar)
sm.HandleFunc("GET /tweet/{id}", ws.handleTweet)
sm.HandleFunc("GET /video/{id}", ws.handleVideo)
return ws, nil
}
func (ws WebServer) handleIndex(w http.ResponseWriter, r *http.Request) {
if ws.config.IndexTarget != "" {
http.Redirect(w, r, ws.config.IndexTarget, http.StatusPermanentRedirect)
}
}
func (ws WebServer) handleAvatar(w http.ResponseWriter, r *http.Request) {
username := r.PathValue("username")
@ -137,7 +130,7 @@ func (ws WebServer) handleTemplate(w http.ResponseWriter, r *http.Request, id st
}
if !slices.Contains(ws.config.Channels, tweet.Username) {
res := Response{http.StatusBadRequest, "text/plain", "Bad Request"}
res := Reponse{http.StatusBadRequest, "text/plain", "Bad Request"}
ws.responseCache.Set(template+"-"+id, res, TweetCacheTime)
response(w, res)
return
@ -189,7 +182,7 @@ func (ws WebServer) handleTemplate(w http.ResponseWriter, r *http.Request, id st
return
}
res := Response{http.StatusOK, "text/html", tpl.String()}
res := Reponse{http.StatusOK, "text/html", tpl.String()}
ws.responseCache.Set(template+"-"+id, res, TweetCacheTime)
response(w, res)
}
@ -204,14 +197,14 @@ func validUserAgent(ua string, uas []string) bool {
}
func badRequest(w http.ResponseWriter) {
response(w, Response{http.StatusBadRequest, "text/plain", "Bad Request"})
response(w, Reponse{http.StatusBadRequest, "text/plain", "Bad Request"})
}
func serverError(w http.ResponseWriter) {
response(w, Response{http.StatusInternalServerError, "text/plain", "Internal Server Error"})
response(w, Reponse{http.StatusInternalServerError, "text/plain", "Internal Server Error"})
}
func response(w http.ResponseWriter, res Response) {
func response(w http.ResponseWriter, res Reponse) {
w.WriteHeader(res.StatusCode)
w.Header().Set("Content-Type", res.ContentType)
fmt.Fprint(w, res.Content)