dotcode-decoder 1.0.0__py3-none-any.whl

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.
dotcode/codes.py ADDED
@@ -0,0 +1,247 @@
1
+ """
2
+ DotCode encoding/decoding logic
3
+ """
4
+
5
+ from .rs import DOT_TO_VAL, rs_correct, solve_ND_NC, mask_step, P
6
+
7
+ def decode_codesets(vals):
8
+ msg = []
9
+ cs = "C"
10
+ i = 0
11
+ shift_to = None
12
+ shift_n = 0
13
+ upper_A = False
14
+ upper_B = False
15
+
16
+ while i < len(vals):
17
+ v = vals[i]
18
+
19
+ if v == 107:
20
+ break
21
+
22
+ if v == 108:
23
+ msg.append("<FNC2>")
24
+ i += 1
25
+ continue
26
+
27
+ if v == 109:
28
+ msg.append("<FNC3>")
29
+ i += 1
30
+ continue
31
+
32
+ if v == 110:
33
+ upper_A = True
34
+ i += 1
35
+ continue
36
+
37
+ if v == 111:
38
+ upper_B = True
39
+ i += 1
40
+ continue
41
+
42
+ if v == 112:
43
+ return None
44
+
45
+ if v > 112:
46
+ break
47
+
48
+ eff = shift_to if shift_n > 0 and shift_to else cs
49
+
50
+ if eff == "C" and v == 100:
51
+ break
52
+
53
+ if eff in ("A", "B") and v >= 107 and msg:
54
+ break
55
+
56
+ def tick():
57
+ nonlocal shift_n, shift_to
58
+ if shift_n > 0:
59
+ shift_n -= 1
60
+ if shift_n == 0:
61
+ shift_to = None
62
+
63
+ def uc(ch):
64
+ nonlocal upper_A, upper_B
65
+ if upper_A or upper_B:
66
+ ch = chr((ord(ch) + 128) % 256)
67
+ upper_A = False
68
+ upper_B = False
69
+ return ch
70
+
71
+ if eff == "C":
72
+ if v == 101:
73
+ cs = "A"
74
+ shift_to = None
75
+ shift_n = 0
76
+ elif v == 102:
77
+ shift_to = "B"
78
+ shift_n = 1
79
+ elif v == 103:
80
+ shift_to = "B"
81
+ shift_n = 2
82
+ elif v == 104:
83
+ shift_to = "B"
84
+ shift_n = 3
85
+ elif v == 105:
86
+ shift_to = "B"
87
+ shift_n = 4
88
+ elif v == 106:
89
+ cs = "B"
90
+ shift_to = None
91
+ shift_n = 0
92
+ elif 0 <= v <= 99:
93
+ msg.append(f"{v:02d}")
94
+ tick()
95
+
96
+ elif eff == "A":
97
+ if v == 101:
98
+ shift_to = "B"
99
+ shift_n = 1
100
+ elif v == 102:
101
+ cs = "B"
102
+ shift_to = None
103
+ shift_n = 0
104
+ elif v == 103:
105
+ shift_to = "C"
106
+ shift_n = 2
107
+ elif v == 104:
108
+ shift_to = "C"
109
+ shift_n = 3
110
+ elif v == 105:
111
+ shift_to = "C"
112
+ shift_n = 4
113
+ elif v == 106:
114
+ cs = "C"
115
+ shift_to = None
116
+ shift_n = 0
117
+ elif 0 <= v <= 63:
118
+ msg.append(uc(chr(32 + v)))
119
+ tick()
120
+ elif 64 <= v <= 95:
121
+ msg.append(uc(chr(v - 64)))
122
+ tick()
123
+ else:
124
+ tick()
125
+
126
+ elif eff == "B":
127
+ if v == 101:
128
+ shift_to = "A"
129
+ shift_n = 1
130
+ elif v == 102:
131
+ cs = "A"
132
+ shift_to = None
133
+ shift_n = 0
134
+ elif v == 103:
135
+ shift_to = "C"
136
+ shift_n = 2
137
+ elif v == 104:
138
+ shift_to = "C"
139
+ shift_n = 3
140
+ elif v == 105:
141
+ shift_to = "C"
142
+ shift_n = 4
143
+ elif v == 106:
144
+ cs = "C"
145
+ shift_to = None
146
+ shift_n = 0
147
+ elif 0 <= v <= 95:
148
+ msg.append(uc(chr(32 + v)))
149
+ tick()
150
+ elif v == 96:
151
+ msg.append("\r\n")
152
+ tick()
153
+ elif v == 97:
154
+ msg.append("\t")
155
+ tick()
156
+ elif v == 98:
157
+ msg.append(chr(28))
158
+ tick()
159
+ elif v == 99:
160
+ msg.append(chr(29))
161
+ tick()
162
+ elif v == 100:
163
+ msg.append(chr(30))
164
+ tick()
165
+ else:
166
+ tick()
167
+
168
+ i += 1
169
+
170
+ result = "".join(msg)
171
+
172
+ if len(result) >= 12:
173
+ for pad in ["27", "28", "29", "00", "99", "14"]:
174
+ if result.endswith(pad):
175
+ result = result[:-2]
176
+ break
177
+
178
+ return result if result else None
179
+
180
+ def decode_bits(bitstr):
181
+ s = "".join(bitstr.split())
182
+
183
+ if not all(c in "01" for c in s) or len(s) < 20:
184
+ return None
185
+
186
+ ones = s.count("1") / len(s) * 100
187
+ if ones > 78 or ones < 8:
188
+ return None
189
+
190
+ mask_id = (int(s[0]) << 1) | int(s[1])
191
+ cw_bits = [s[2+k*9:2+(k+1)*9] for k in range((len(s)-2)//9)]
192
+ vals = []
193
+ era = []
194
+
195
+ for idx, b in enumerate(cw_bits):
196
+ v = None
197
+ if len(b) == 9 and b.count("1") == 5:
198
+ v = DOT_TO_VAL.get(b)
199
+
200
+ if v is None:
201
+ vals.append(None)
202
+ era.append(idx)
203
+ else:
204
+ vals.append(v)
205
+
206
+ NW = len(vals)
207
+ ND, NC = solve_ND_NC(NW)
208
+
209
+ if ND is None:
210
+ return None
211
+
212
+ if all(v is not None for v in vals[:ND]):
213
+ step = mask_step(mask_id)
214
+ dv = [(vals[i] - step * i) % P for i in range(ND)]
215
+ msg = decode_codesets(dv)
216
+
217
+ if msg and len(msg) >= 4:
218
+ if not any(c in msg for c in ['/', '\\', '\x00', '\xff', ')', '(', '<']):
219
+ return msg
220
+
221
+ res = rs_correct(mask_id, vals, ND, NC, era)
222
+
223
+ if res is None:
224
+ return None
225
+
226
+ corrected, _ = res
227
+ step = mask_step(mask_id)
228
+ dv = [(corrected[i] - step * i) % P for i in range(ND)]
229
+ msg = decode_codesets(dv)
230
+
231
+ if msg and len(msg) >= 4:
232
+ if not any(c in msg for c in ['/', '\\', '\x00', '\xff', ')', '(', '<']):
233
+ return msg
234
+
235
+ return None
236
+
237
+ def is_valid_message(msg):
238
+ if msg is None or len(msg) < 4:
239
+ return False
240
+
241
+ if msg.isdigit() and len(msg) > 20:
242
+ return False
243
+
244
+ if any(c in msg for c in ['/', '\\', '\x00', '\xff', ')', '(', '<']):
245
+ return False
246
+
247
+ return True