From 06562c5fe484eba954770819a56dc4227a78aa44 Mon Sep 17 00:00:00 2001 From: Christian Segundo Date: Tue, 24 Dec 2024 19:17:41 +0100 Subject: First commit 🌲 --- .envrc | 1 + .gitignore | 1 + Dockerfile | 23 +++++++++++ Makefile.PL | 7 ++++ app.nix | 10 +++++ flake.lock | 61 +++++++++++++++++++++++++++ flake.nix | 29 +++++++++++++ sync | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 267 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile.PL create mode 100644 app.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100755 sync diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..a5dbbcb --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92b2793 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.direnv diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..55d7385 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM nixos/nix:latest AS builder + +COPY . /tmp/build +WORKDIR /tmp/build + +RUN nix \ + --extra-experimental-features "nix-command flakes" \ + --option filter-syscalls false \ + build -L .#docker + +# no quotes, we actually want to split +RUN mkdir /tmp/nix-store-closure && \ + cp -R $(nix-store -qR result/) /tmp/nix-store-closure + +WORKDIR /tmp/app + +FROM scratch + +COPY --from=builder /tmp/nix-store-closure /nix/store +COPY --from=builder /tmp/build/result / +USER 1000 +ENV PATH=/bin +ENTRYPOINT ["perl", "/bin/sync"] diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..167d52e --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,7 @@ +#!perl +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'slack2gitlab-emoji-sync', + EXE_FILES => [ 'sync' ], +); diff --git a/app.nix b/app.nix new file mode 100644 index 0000000..9728a92 --- /dev/null +++ b/app.nix @@ -0,0 +1,10 @@ +{ pkgs }: with pkgs; with perlPackages; +buildPerlPackage { + pname = "slack2gitlab-emoji-sync"; + version = "0.000"; + src = ./.; + propagatedBuildInputs = [ JSON LWP LWPProtocolHttps PathTiny ]; + outputs = [ "out" ]; +} + + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..8be89b0 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "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" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1734820311, + "narHash": "sha256-YsLK4ZiGY5CZmmgzsfU76OHVUTDeZJgirKzNO+et0UQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "7e4a1594489d41bf8e16046b28e14a0e264c9baa", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b616a91 --- /dev/null +++ b/flake.nix @@ -0,0 +1,29 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + outputs = + { self + , nixpkgs + , flake-utils + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; }; + app = pkgs.callPackage ./app.nix { inherit pkgs; }; + deps = with pkgs; with perlPackages; [ + perl JSON LWP LWPProtocolHttps PathTiny + ]; + in + with pkgs; { + packages.docker = symlinkJoin { + name = "docker"; + paths = [ perl app ]; + meta.priority = 5; + }; + devShells.default = mkShell { buildInputs = deps; }; + } + ); +} diff --git a/sync b/sync new file mode 100755 index 0000000..fa604e3 --- /dev/null +++ b/sync @@ -0,0 +1,135 @@ +#!perl +use strict; +use warnings; +use autodie; + +use JSON qw(decode_json encode_json); +use LWP::UserAgent qw(); +use Path::Tiny qw(path); + +$| = 1; + +my $gitlab_group_path = $ENV{GITLAB_GROUP_PATH} or die "GITLAB_GROUP_PATH is not set"; +my $gitlab_url = $ENV{GITLAB_URL} or die "GITLAB_URL is not set"; +my $gitlab_token = $ENV{GITLAB_TOKEN} or die "GITLAB_TOKEN is not set"; +my $slack_workspace = $ENV{SLACK_WORKSPACE} or die "SLACK_WORKSPACE is not set"; +my $slack_logs_dir = $ENV{SLACK_LOGS_DIR} // q{}; +my $gitlab_graphql_api = "${gitlab_url}/api/graphql"; + +die "Usage: $0 " if scalar @ARGV != 1; +my $filter = qr/$ARGV[0]/; + +my %slack_emojis; +my %gitlab_emojis; + +my $ua = LWP::UserAgent->new(timeout => 5); +$ua->default_header('Authorization' => "Bearer $gitlab_token"); +$ua->default_header('Content-Type' => 'application/json; charset=utf-8'); + +if (!$slack_logs_dir) { + if ($^O eq "darwin") { + my $username = $ENV{LOGNAME} || $ENV{USER} || getpwuid($<); + $slack_logs_dir = "/Users/$username/Library/Application Support/Slack/logs/default"; + } else { + die "SLACK_LOGS_DIR is not set"; + } +} + +if (!path($slack_logs_dir)->exists or !path($slack_logs_dir)->is_dir) { + die "Slack logs directory not found: $slack_logs_dir"; +} + +foreach (path($slack_logs_dir)->children(qr/\.log$/)) { + open my ($log_file), $_; + foreach (<$log_file>) { + if (m{fetch (https://emoji\.slack-edge\.com/$slack_workspace/([a-zA-Z-_0-9]+)\/\w+\.(png|gif|jpg))}gm) { + $slack_emojis{$2} = $1; + } + } + close $log_file; +} + +{ + my $next_cursor = ""; + my $has_next_page = 1; + while($has_next_page) { + my $content = encode_json({ + query => qq{ + { + group(fullPath: "$gitlab_group_path") { + customEmoji(first: 50, after: "$next_cursor") { + nodes { id name url } + pageInfo { endCursor hasNextPage } + } + } + } + } + }); + + my $res = $ua->post( $gitlab_graphql_api, Content => $content ); + die "failed" if $res->is_error or $res->code != 200; + + my $data = decode_json($res->decoded_content); + $next_cursor = $data->{data}{group}{customEmoji}{pageInfo}{endCursor}; + $has_next_page = $data->{data}{group}{customEmoji}{pageInfo}{hasNextPage}; + foreach(@{$data->{data}{group}{customEmoji}{nodes}}) { + $gitlab_emojis{$_->{name}} = { + url => $_->{url}, + id => $_->{id}, + }; + } + } +} + +foreach (sort keys %slack_emojis) { + next if $_ !~ $filter; + print "Emoji: $_\n"; + if (exists $gitlab_emojis{$_}) { + print " - already exists in GitLab\n"; + if ($slack_emojis{$_} eq $gitlab_emojis{$_}{url}) { + print " - skipping, URL is the same\n"; + next; + } + my $content = encode_json({ + query => qq{ + mutation { + destroyCustomEmoji(input: { + id: "$gitlab_emojis{$_}{id}" + }) { errors } + } + } + }); + my $res = $ua->post( $gitlab_graphql_api, Content => $content ); + if ($res->is_error or $res->code != 200) { + print " - failed to delete\n"; + next; + } + my $data = decode_json($res->decoded_content); + if ($data->{errors}) { + print " - failed to delete\n"; + next; + } + } + my $content = encode_json({ + query => qq{ + mutation { + createCustomEmoji(input: { + groupPath: "$gitlab_group_path", + name: "$_", + url: "$slack_emojis{$_}" + }) { errors } + } + } + }); + my $res = $ua->post( $gitlab_graphql_api, Content => $content ); + if ($res->is_error or $res->code != 200) { + print " - failed to create\n"; + } else { + my $data = decode_json($res->decoded_content); + if ($data->{errors}) { + print " - failed to create\n"; + } else { + print " - created\n"; + } + } +} -- cgit v1.2.3