number_station 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.asciidoc +72 -17
- data/lib/number_station/cli.rb +115 -0
- data/lib/number_station/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28bb32733b0cc2c4dd9a9258985a762e6cf558b9ec3a05d7438a5d4b53332dfe
|
|
4
|
+
data.tar.gz: dc4f7d34bd69879eb4eda6ac0774f2fa20c65433d6d841a98a7f97968562daf8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a86c1b266dd7d5837bf99d1eafecb545fe36ebb86649013dee3bb9a5cff9d507021f257c8595452a0a0f2c91a96cbb579769cadf0245f3983bf9f31371e82891
|
|
7
|
+
data.tar.gz: a8be3d5d110b854ac1254c9fdfb38b062dbb7fbbdbc828617c7c63fdfc40426627a6d1f086c772dfe6c6360a4a88f9eefdaea111d0ec1e6d7e98c53d308d236d
|
data/README.asciidoc
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
== number_station ==
|
|
2
|
+
|
|
2
3
|
This gem contains utilities to aid in the running of a number station.
|
|
3
4
|
|
|
4
5
|
image:https://badge.fury.io/rb/number_station.svg["Gem Version", link="https://badge.fury.io/rb/number_station"]
|
|
5
6
|
|
|
6
7
|
=== Installation ===
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
[source,bash]
|
|
10
|
+
----
|
|
11
|
+
gem install number_station
|
|
12
|
+
----
|
|
9
13
|
|
|
10
14
|
=== Usage ===
|
|
11
15
|
|
|
@@ -13,7 +17,10 @@ image:https://badge.fury.io/rb/number_station.svg["Gem Version", link="https://b
|
|
|
13
17
|
|
|
14
18
|
Create the initial configuration:
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
[source,bash]
|
|
21
|
+
----
|
|
22
|
+
number_station create_config
|
|
23
|
+
----
|
|
17
24
|
|
|
18
25
|
This creates `~/number_station/conf.yaml` and copies template files (`intro_message.txt`, `outro_message.txt`, `repeat_message.txt`) to the same directory.
|
|
19
26
|
|
|
@@ -22,64 +29,112 @@ This creates `~/number_station/conf.yaml` and copies template files (`intro_mess
|
|
|
22
29
|
Manage agents using the `agents` subcommand:
|
|
23
30
|
|
|
24
31
|
*Create an agent:*
|
|
25
|
-
|
|
32
|
+
|
|
33
|
+
[source,bash]
|
|
34
|
+
----
|
|
35
|
+
number_station agents create NAME [--location LOCATION] [--handler HANDLER]
|
|
36
|
+
----
|
|
26
37
|
|
|
27
38
|
*Activate/Deactivate:*
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
|
|
40
|
+
[source,bash]
|
|
41
|
+
----
|
|
42
|
+
number_station agents activate NAME [--start-date DATE]
|
|
43
|
+
number_station agents deactivate NAME [--end-date DATE]
|
|
44
|
+
----
|
|
30
45
|
|
|
31
46
|
*Update handler codeword:*
|
|
32
|
-
|
|
47
|
+
|
|
48
|
+
[source,bash]
|
|
49
|
+
----
|
|
50
|
+
number_station agents update-handler NAME HANDLER
|
|
51
|
+
----
|
|
33
52
|
|
|
34
53
|
*View agents:*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
|
|
55
|
+
[source,bash]
|
|
56
|
+
----
|
|
57
|
+
number_station agents list # Active agents only
|
|
58
|
+
number_station agents list-all # All agents with history
|
|
59
|
+
number_station agents stats # Detailed statistics
|
|
60
|
+
----
|
|
38
61
|
|
|
39
62
|
==== One-Time Pads ====
|
|
40
63
|
|
|
41
64
|
*Create pads:*
|
|
42
|
-
|
|
65
|
+
|
|
66
|
+
[source,bash]
|
|
67
|
+
----
|
|
68
|
+
number_station pad create [--name AGENT] [--numpads NUM] [--length LENGTH]
|
|
69
|
+
----
|
|
43
70
|
|
|
44
71
|
If `--name` is provided, creates pad in `~/number_station/pads/AGENT/` directory.
|
|
45
72
|
Defaults: 500 pads, 500 characters (rounded up to nearest multiple of 5).
|
|
46
73
|
|
|
47
74
|
*View pad statistics:*
|
|
48
|
-
|
|
75
|
+
|
|
76
|
+
[source,bash]
|
|
77
|
+
----
|
|
78
|
+
number_station pad stats [--path PATH]
|
|
79
|
+
----
|
|
49
80
|
|
|
50
81
|
Shows pad statistics grouped by agent, including unconsumed pad counts.
|
|
51
82
|
|
|
52
83
|
==== Message Encryption/Decryption ====
|
|
53
84
|
|
|
54
85
|
*Encrypt:*
|
|
55
|
-
|
|
86
|
+
|
|
87
|
+
[source,bash]
|
|
88
|
+
----
|
|
89
|
+
number_station encrypt [MESSAGE] [--file FILE] [--agent AGENT] [--padpath PADPATH] [--numpad NUMPAD]
|
|
90
|
+
----
|
|
56
91
|
|
|
57
92
|
If `--agent` is provided, searches for oldest pad in agent-specific directory.
|
|
58
93
|
Only active agents can encrypt messages.
|
|
59
94
|
|
|
60
95
|
*Decrypt:*
|
|
61
|
-
|
|
96
|
+
|
|
97
|
+
[source,bash]
|
|
98
|
+
----
|
|
99
|
+
number_station decrypt [MESSAGE] [--file FILE] [--padpath PADPATH] [--numpad NUMPAD]
|
|
100
|
+
----
|
|
62
101
|
|
|
63
102
|
==== Phonetic Conversion ====
|
|
64
103
|
|
|
65
104
|
*Convert to phonetic:*
|
|
66
|
-
|
|
105
|
+
|
|
106
|
+
[source,bash]
|
|
107
|
+
----
|
|
108
|
+
number_station convert_to_phonetic FILE [--intro INTRO_FILE] [--outro OUTRO_FILE] [--repeat REPEAT_FILE]
|
|
109
|
+
----
|
|
67
110
|
|
|
68
111
|
Converts encrypted message to phonetic alphabet. Output saved as `FILENAME_phonetic.txt`.
|
|
69
112
|
Intro, outro, and repeat messages are included as-is (not converted).
|
|
70
113
|
|
|
71
114
|
*Convert to espeak XML:*
|
|
72
|
-
|
|
115
|
+
|
|
116
|
+
[source,bash]
|
|
117
|
+
----
|
|
118
|
+
number_station convert_to_espeak PHONETIC_FILE
|
|
119
|
+
----
|
|
73
120
|
|
|
74
121
|
Generates GLaDOS-style espeak XML file. Output saved as `FILENAME.xml`.
|
|
75
122
|
|
|
76
123
|
*Generate MP3:*
|
|
77
|
-
|
|
124
|
+
|
|
125
|
+
[source,bash]
|
|
126
|
+
----
|
|
127
|
+
number_station convert_to_mp3 XML_FILE
|
|
128
|
+
----
|
|
78
129
|
|
|
79
130
|
Converts XML to MP3 using espeak and ffmpeg. Output saved as `FILENAME.mp3`.
|
|
80
131
|
|
|
81
132
|
*Play audio:*
|
|
82
|
-
|
|
133
|
+
|
|
134
|
+
[source,bash]
|
|
135
|
+
----
|
|
136
|
+
number_station espeak XML_FILE
|
|
137
|
+
----
|
|
83
138
|
|
|
84
139
|
Plays the XML file using espeak with GLaDOS voice settings.
|
|
85
140
|
|
data/lib/number_station/cli.rb
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
require 'thor'
|
|
23
23
|
require 'fileutils'
|
|
24
24
|
require 'date'
|
|
25
|
+
require 'json'
|
|
25
26
|
|
|
26
27
|
module NumberStation
|
|
27
28
|
class Pad < Thor
|
|
@@ -676,6 +677,120 @@ module NumberStation
|
|
|
676
677
|
puts "Generated GLaDOS espeak XML: #{output_path}"
|
|
677
678
|
end
|
|
678
679
|
|
|
680
|
+
desc "convert_pad_to_asciidoc --padpath PADPATH", "Convert a pad file to AsciiDoc format"
|
|
681
|
+
long_desc <<-CONVERT_PAD_TO_ASCIIDOC_LONG_DESC
|
|
682
|
+
Convert a one-time pad file to AsciiDoc format with each pad as a chapter.
|
|
683
|
+
|
|
684
|
+
Parameters:
|
|
685
|
+
--padpath PADPATH - Path to pad file (e.g., ~/number_station/pads/Shadow/Shadow-2026-01-12.json)
|
|
686
|
+
|
|
687
|
+
Each pad in the file is converted to groups of 5 characters, separated by spaces.
|
|
688
|
+
Output saved as FILENAME.asciidoc in the same directory.
|
|
689
|
+
|
|
690
|
+
Example:
|
|
691
|
+
number_station convert_pad_to_asciidoc --padpath ~/number_station/pads/Shadow/Shadow-2026-01-12.json
|
|
692
|
+
CONVERT_PAD_TO_ASCIIDOC_LONG_DESC
|
|
693
|
+
option :padpath, type: :string, required: true
|
|
694
|
+
def convert_pad_to_asciidoc
|
|
695
|
+
ensure_config_loaded
|
|
696
|
+
|
|
697
|
+
pad_path = options[:padpath]
|
|
698
|
+
unless File.exist?(pad_path)
|
|
699
|
+
raise Thor::Error, "Pad file not found: #{pad_path}"
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# Read pad file
|
|
703
|
+
pad_data = JSON.parse(File.read(pad_path))
|
|
704
|
+
pad_id = pad_data["id"]
|
|
705
|
+
pads_hash = pad_data["pads"]
|
|
706
|
+
|
|
707
|
+
if pads_hash.nil? || pads_hash.empty?
|
|
708
|
+
raise Thor::Error, "No pads found in file: #{pad_path}"
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
# Generate output filename: replace .json extension with .asciidoc
|
|
712
|
+
input_basename = File.basename(pad_path, File.extname(pad_path))
|
|
713
|
+
output_filename = "#{input_basename}.asciidoc"
|
|
714
|
+
output_path = File.join(File.dirname(pad_path), output_filename)
|
|
715
|
+
|
|
716
|
+
# Build AsciiDoc content
|
|
717
|
+
asciidoc_content = []
|
|
718
|
+
asciidoc_content << "= One-Time Pad: #{input_basename}"
|
|
719
|
+
asciidoc_content << ""
|
|
720
|
+
|
|
721
|
+
# Process each pad
|
|
722
|
+
pads_hash.sort_by { |k, v| k.to_i }.each do |pad_num, pad_info|
|
|
723
|
+
hex_key = pad_info["key"]
|
|
724
|
+
|
|
725
|
+
# Convert hex string to groups of 5 characters, separated by spaces
|
|
726
|
+
# Remove any existing formatting and group by 5
|
|
727
|
+
cleaned_hex = hex_key.gsub(/[\s\n\r]/, '')
|
|
728
|
+
grouped_hex = cleaned_hex.chars.each_slice(5).map(&:join).join(' ')
|
|
729
|
+
|
|
730
|
+
# Add chapter heading for this pad
|
|
731
|
+
asciidoc_content << "== Pad #{pad_num}"
|
|
732
|
+
asciidoc_content << ""
|
|
733
|
+
asciidoc_content << "[source]"
|
|
734
|
+
asciidoc_content << "----"
|
|
735
|
+
asciidoc_content << grouped_hex
|
|
736
|
+
asciidoc_content << "----"
|
|
737
|
+
asciidoc_content << ""
|
|
738
|
+
|
|
739
|
+
# Add page break after every 2nd pad (after pad 1, 3, 5, etc.)
|
|
740
|
+
if pad_num.to_i % 2 == 1
|
|
741
|
+
asciidoc_content << "<<<"
|
|
742
|
+
asciidoc_content << ""
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
# Write AsciiDoc file
|
|
747
|
+
File.write(output_path, asciidoc_content.join("\n"))
|
|
748
|
+
puts "Generated AsciiDoc file: #{output_path}"
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
desc "convert_to_pdf ASCIIDOC_FILE", "Convert an AsciiDoc file to PDF using asciidoctor-pdf"
|
|
752
|
+
long_desc <<-CONVERT_TO_PDF_LONG_DESC
|
|
753
|
+
Convert an AsciiDoc file (typically generated by convert_pad_to_asciidoc) to PDF format.
|
|
754
|
+
|
|
755
|
+
Parameters:
|
|
756
|
+
ASCIIDOC_FILE - Path to AsciiDoc file (e.g., Shadow-2026-01-12.asciidoc)
|
|
757
|
+
|
|
758
|
+
The command checks if asciidoctor-pdf is available before executing.
|
|
759
|
+
Output saved as FILENAME.pdf in the same directory.
|
|
760
|
+
|
|
761
|
+
Example:
|
|
762
|
+
number_station convert_to_pdf Shadow-2026-01-12.asciidoc
|
|
763
|
+
CONVERT_TO_PDF_LONG_DESC
|
|
764
|
+
def convert_to_pdf(asciidoc_file)
|
|
765
|
+
ensure_config_loaded
|
|
766
|
+
|
|
767
|
+
unless File.exist?(asciidoc_file)
|
|
768
|
+
raise Thor::Error, "File not found: #{asciidoc_file}"
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
# Check if asciidoctor-pdf is available
|
|
772
|
+
# Check if command exists first (most reliable)
|
|
773
|
+
unless NumberStation.command?('asciidoctor-pdf')
|
|
774
|
+
raise Thor::Error, "asciidoctor-pdf is not available. Please install it with: gem install asciidoctor-pdf"
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
# Generate output filename: replace .asciidoc extension with .pdf
|
|
778
|
+
input_basename = File.basename(asciidoc_file, File.extname(asciidoc_file))
|
|
779
|
+
output_filename = "#{input_basename}.pdf"
|
|
780
|
+
output_path = File.join(File.dirname(asciidoc_file), output_filename)
|
|
781
|
+
|
|
782
|
+
# Convert using asciidoctor-pdf command
|
|
783
|
+
cmd = "asciidoctor-pdf #{asciidoc_file} -o #{output_path}"
|
|
784
|
+
NumberStation.log.info "Running: #{cmd}"
|
|
785
|
+
system(cmd)
|
|
786
|
+
|
|
787
|
+
unless $?.success?
|
|
788
|
+
raise Thor::Error, "PDF conversion failed with exit code #{$?.exitstatus}"
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
puts "Generated PDF file: #{output_path}"
|
|
792
|
+
end
|
|
793
|
+
|
|
679
794
|
desc "espeak XML_FILE", "Use espeak to read an XML file"
|
|
680
795
|
long_desc <<-ESPEAK_LONG_DESC
|
|
681
796
|
Use the espeak utility to read an XML file with GLaDOS-style voice settings.
|