') #Enable cursor
75 | return(1)
76 |
77 | ##########
78 | # TML tag
79 | ##########
80 | t_mono = {'GRABFRAME':(lambda c,path:Grabframe(c,path,None),[('c','_C'),('path','')])}
--------------------------------------------------------------------------------
/docs/atrst-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/atrst-map.png
--------------------------------------------------------------------------------
/docs/chiptune_streaming.md:
--------------------------------------------------------------------------------
1 | # Chiptune Streaming Protocol:
2 |
3 | ## Host Side
4 |
5 | ### SID Streaming
6 | 1. Using *SIDDump* or *SIDDumpR* get a dump of SID registers used for each frame.
*HVSC* song length files are used to know the playtime. If no song length file is found the default is 3 minutes.
7 | 2. The dump is interpreted and a list of values to transmit is generated for each frame. Along with a corresponding bitmap defining which registers are used each frame.
If *SIDDumpHR* is used, additional information about hardrestart for each voice is transmitted. This is backwards compatible and only interpreted by terminals supporting it.
8 |
9 | 3. Each frame register list is built as a data packet taking this form:
10 |
11 | |Position | Length (bytes)| Description
12 | |:---:|:---:|---
13 | | 1 | 1 | Length of this data packet, not counting this byte (max 29)
14 | | 2 | 4 | Bitmap of SID registers sent.
1 bit = 1 register (Big endian)
bits 26-28 indicate ADSR hardrestart for each voice
bits 29-31 indicate Gate Hardrestart for each voice
15 | | 6 onwards | 0 to 25 | Values of each register, in incremental order by default
16 |
17 | ### PSG Streaming
18 | 1. PSG music files are already a register dump, parsing the file will also usually provide metadata, including playtime.
19 | 2. The register dump is interpreted and a list of values to transmit is generated for each frame. Along with a corresponding bitmap defining which registers are used each frame.
20 | 3. Each frame register list is built as a data packet taking this form:
21 |
22 | |Position | Length (bytes)| Description
23 | |:---:|:---:|---
24 | | 1 | 1 | Length of this data packet, not counting this byte (max 16)
25 | | 2 | 2 | Bitmap of PSG registers sent.
1 bit = 1 register (Big endian)
26 | | 4 onwards | 0 to 14 | Values of each register, in incremental order by default
27 |
28 |
29 | 4. Send the packets according to this flowchart:
30 |
31 | ```mermaid
32 | %%{ init: { 'flowchart': { 'curve': 'linear' } } }%%
33 | flowchart TD
34 | id1([Start])
35 | id2{{Send Chiptune streaming command}}
36 | id3[Set count to 100]
37 | id4[/Send Packet/]
38 | id5{Last Packet?}
39 | id6[Decrement count]
40 | id7{Count == 0?}
41 | id8[/Receive client sync/]
42 | id9{Sync == $FF?}
43 | id10[/Send $00/]
44 | id11[Flush receive buffer]
45 | id12([End])
46 | id13[/Send $FF sync byte/]
47 | id1-->id2-->id3-->id4-->id13-->id5
48 | id5-- No -->id6-->id7
49 | id7-- No -->id4
50 | id7-- Yes -->id8-->id9
51 | id9-- No -->id3
52 | id5 & id9-- Yes -->id10-->id11-->id12
53 |
54 | ```
55 |
56 |
57 | ---
58 | ## Client side flowchart:
59 |
60 | ```mermaid
61 | %%{ init: { 'flowchart': { 'curve': 'linear' } } }%%
62 | flowchart TD
63 | id1([Receive streaming command])
64 | id2{{Set count to 50}}
65 | id3[/Read data packet/]
66 | id4[Write registers]
67 | id5[Decrement count]
68 | id6{count == 0?}
69 | id7{user cancel?}
70 | id8[/Send $00 sync/]
71 | id9[Set count to 100]
72 | id10{Packet size == 0?}
73 | id11([End streaming])
74 | id12[/Send $FF sync/]
75 | id1 ---> id2 --> id3 --> id10
76 | id10 -- No --> id4 --> id5 --> id6
77 | id10 -- Yes --------> id11
78 | id6 -- Yes --> id7
79 | id6 -- No --> id3
80 | id7 -- No --> id8
81 | id7 -- Yes --> id12
82 | id8 & id12 --> id9
83 | id9 --> id3
84 | ```
--------------------------------------------------------------------------------
/docs/cp437-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/cp437-map.png
--------------------------------------------------------------------------------
/docs/msx-charmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/msx-charmap.png
--------------------------------------------------------------------------------
/docs/msx-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/msx-map.png
--------------------------------------------------------------------------------
/docs/pet-screencodes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/pet-screencodes.png
--------------------------------------------------------------------------------
/docs/petscii-c128-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/petscii-c128-map.png
--------------------------------------------------------------------------------
/docs/petscii-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/petscii-map.png
--------------------------------------------------------------------------------
/docs/petscii-p4-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/petscii-p4-map.png
--------------------------------------------------------------------------------
/docs/retrobbs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/retrobbs.png
--------------------------------------------------------------------------------
/docs/tml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/tml.png
--------------------------------------------------------------------------------
/docs/turbo56k.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 
5 |
6 |
7 |
8 | # Turbo56K v0.7
9 |
10 |
11 | **Turbo56K** was created by **Jorge Castillo** as a simple protocol to provide high speed file transfer functionality to his bit-banging `57600bps` **RS232** routine for the **Commodore 64**.
12 |
13 | Over time, the protocol has been extended to include `4-bit` **PCM** audio streaming, bitmap graphics transfer and display, **SID** and **PSG** music streaming and more.
14 |
15 | A typical **Turbo56K** command sequence consists of a command start character ( **CMDON** : `$FF` ) followed by the command itself (a character with it's 7th bit set) and the parameters it requires.
16 |
17 | The sequence ends with the command end character ( **CMDOFF** : `$FE` )
18 |
19 | Some commands will exit *command mode* automatically without needing a `CMDOFF` character, but is good practice to include it anyway.
20 |
21 | For example the following byte sequence enters command mode, sets the screen to Hires mode on page 0 with blue border and then exits command mode:
22 |
23 | $FF $90 $00 $06 $FE
24 |
25 |
26 | ---
27 |
28 |
29 |
30 | ## Reserved Characters
31 |
32 | | Hex | Dec | Description
33 | |:---:|:---:|------------
34 | | `$FF` | `255` |Enters command node
35 | | `$FE` | `254` |Exits command node
36 |
37 |
38 |
39 | ## Commands
40 |
41 |
42 |
43 | ### Data Transfer
44 |
45 | | Hex | Dec | Description
46 | |:---:|:---:|------------
47 | | `$80` | `128` | Sets the memory pointer for the next transfer **Parameters**
- Destination Address : 2 bytes : low \| high
48 | | `$81` | `129` | Selects preset address for the next transfer
**Parameters**
- Preset Number : 1 byte
49 | | `$82` | `130` | Start a memory transfer
**Parameters**
- Transfer Size : 2 bytes : low \| high
50 | | `$83` | `131` | Starts audio streaming until receiving a `$00` character
51 | | `$84` | `132` | Starts chiptune streaming until receiving a data block with size `0`, or interrupted by the user
52 | | `$85` | `133` | `New v0.6`
Sets the stream and write order of the registers for SID streaming
**Parameters**
- Stream : 25 bytes
53 | | `$86` | `134` | `New v0.7`
Starts a file transfer (to be saved on a storage device client side)
54 |
55 |
56 |
57 | ### Graphics Mode
58 |
59 | | Hex | Dec | Description
60 | |:---:|:---:|------------
61 | | `$90` | `144` | Returns to the default text mode
**Parameters**
- Page Number : 1 byte
- Border Color : 1 byte
- Background Color : 1 byte
62 | | `$91` | `145` | Switches to hi-res bitmap mode
**Parameters**
- Page Number : 1 byte
- Border Color : 1 byte
63 | | `$92` | `146` | Switches to multicolor bitmap mode
**Parameters**
- Page Number : 1 byte
- Border Color : 1 byte
- Background Color : 1 byte
**Only for Plus/4:**
- Multicolor 3 color : 1 byte
64 |
65 |
66 |
67 | ### Drawing Primitives
68 |
69 | | Hex | Dec | Description
70 | |:---:|:---:|------------
71 | | `$98` | `152` | `New v0.8` Clears graphic screen
72 | | `$99` | `153` | `New v0.8` Set pen color
**Parameters**
- Pen Number: 1 byte
- Color index: 1 byte
73 | | `$9A` | `154` | `New v0.8` Plot point
**Parameters**
- Pen Number: 1 byte
- X coordinate: 2 bytes
- Y coordinate: 2 bytes
74 | | `$9B` | `155` | `New v0.8` Line
**Parameters**
- Pen Number: 1 byte
- X1: 2 bytes
- Y1: 2 bytes
- X2: 2 bytes
- Y2: 2 bytes
75 | | `$9C` | `156` | `New v0.8` Box
**Parameters**
- Pen Number: 1 byte
- X1: 2 bytes
- Y1: 2 bytes
- X2: 2 bytes
- Y2: 2 bytes
- Fill: 1 byte
76 | | `$9D` | `157` | `New v0.8` Circle/Ellipse
**Parameters**
- Pen Number: 1 byte
- X: 2 bytes
- Y: 2 bytes
- r1: 2 bytes
- r2: 2bytes
77 | | `$9E` | `158` | `New v0.8` Fill
**Parameters**
- Pen Number: 1 byte
- X: 2 bytes
- Y: 2 bytes
78 |
79 |
80 |
81 | ### Connection Management
82 |
83 | | Hex | Dec | Description
84 | |:---:|:---:|------------
85 | | `$A0` | `160` | Selects the screen as the output for the received characters, exits command mode
86 | | `$A1` | `161` | Selects the optional hardware voice synthesizer as the output for the received characters, exits command mode.
(*Valid only for the microsint + rs232 / Wi-Fi board*)
87 | | `$A2` | `162` | Request terminal ID and version
88 | | `$A3` | `163` | `New v0.6`
Query if the command passed as parameter is implemented in the terminal. If the returned value has its 7th bit clear then the value is the number of parameters required by the command.
(*Max 8 in the current Retroterm implementation*)
If the 7th bit is set the command is not implemented.
89 | | `$A4` | `164` | `New v0.8`
Query the client's setup. The single byte parameter indicates which 'subsystem' is being queried. Client must reply with at least 1 byte indicating the reply length. Zero meaning not implemented. See below for the subsystem parameters.
90 |
91 |
92 |
93 | ### Screen Management
94 |
95 | | Hex | Dec | Description
96 | |:---:|:---:|------------
97 | | `$B0` | `176` | Moves the text cursor
**Parameters**
- Column : 1 byte
- Row : 1 byte
Exits command mode
98 | | `$B1` | `177` | Fills a text screen row with a given
character, text cursor is not moved
**Parameters**
- Screen Row : 1 byte
- Fill Character : 1 byte : *C64 Screen Code*
99 | | `$B2` | `178` | Enables or disables the text cursor
**Parameters**
- Enable : 1 byte
100 | | `$B3` | `179` | Screen split
**Parameters**
- Modes : 1 byte
`Bit 0 - 4` : Split Row `1 - 24`
`Bit 7` : Bitmap Graphics Mode in top section
`0` : Hires
`1` : Multicolor
- Background Color : 1 byte
`Bit 0 - 3` : Top Section
`Bit 4 - 7` : Bottom Section
101 | | `$B4` | `180` | `New v0.7`
Get text cursor position, returns 2 characters, column and row.
102 | | `$B5` | `181` | Set text window
**Parameters**
- Top Row : 1 byte : `0 - 23`
- Bottom Row : 1 byte : `1 - 24`
103 | | `$B6` | `182` | `New v0.7`
Scroll the text window up or down x rows
**Parameters**
- Row count: 1 byte -128/+127
104 | | `$B7` | `183` | `New v0.7`
Set ink color
**Parameters**
- Color index: 1 byte
105 |
106 |
107 | ### Preset Addresses
108 |
109 | *For command `$81`*
110 |
111 | #### Commodore 64 & Plus/4
112 | | Hex | Dec | Description
113 | |:---:|:---:|:------------
114 | | `$00` | `0` | Text page `0`
115 | | `$10` | `16` | Bitmap page `0`
116 | | `$20` | `32` | Color RAM
117 |
118 | *The current versions of **Retroterm** supports only a single text / bitmap page.*
*Values other than `0` for bits `0 - 3` will be ignored.*
119 |
120 | #### MSX1
121 |
122 | | Hex | Dec | Description
123 | |:---:|:---:|:------------
124 | | `$00` | `0` | Text/name table page `0`
125 | | `$10` | `16` | Pattern table page `0`
126 | | `$20` | `32` | Color table
127 |
128 | *Any other value will set the address to $4000 (RAM Page 1) -Subject to changes-*
129 |
130 | ### "Subsystems"
131 |
132 | *For command `$A4`*
133 |
134 | ##### `$00`: Platform/Refresh rate
135 |
136 | Reply length: 2 bytes
137 |
138 | | Position | Value
139 | |:---:|:---
140 | | 0 | 1
141 | | 1 | bits 0-6: platform
bit 7: Refresh rate
142 |
143 | ###### Platform:
144 |
145 | | Value | Platform
146 | |:---:|:---
147 | | 0 | C64
148 | | 1 | Plus/4
149 | | 2 | MSX
150 | | 3 | `reserved` C128
151 | | 4 | `reserved` VIC20
152 | | 5 | `reserved` ZX Spectrum
153 | | 6 | `reserved` Atari
154 | | 7 | `reserved` Apple II
155 | | 8 | `reserved` Amstrad
156 | | 9 | `reserved` Amiga
157 | | 10 | `reserved` PET
158 | |
159 |
160 | ###### Refresh rate:
161 |
162 | | Value | Meaning
163 | |:---:|:---
164 | | 0 | 50Hz
165 | | 1 | 60Hz
166 |
167 |
168 | ##### `$01`: Text screen size
169 |
170 | Reply length: 3 bytes
171 |
172 | | Position | Value
173 | |:---:|:---
174 | | 0 | 2
175 | | 1 | Columns
176 | | 2 | Rows
177 | |
178 |
179 | ##### `$02`: Connection speed
180 |
181 | Reply length: 2 bytes
182 |
183 | | Position | Value
184 | |:---:|:---
185 | | 0 | 1
186 | | 1 |
0: Network
1: 300bps
2: 600bps
3: 1200bps
4: 1800bps
5: 2400bps
6: 4800bps
7: 9600bps
8: 19200bps
9: 28800bps
10: 38400bps
11: 57600bps
12: 76800bps
13: 115200bps
187 | |
188 |
189 | ##### `$03`: RAM size
190 |
191 | Reply length: 3 bytes
192 |
193 | | Position | Value
194 | |:---:|:---
195 | | 0 | 2
196 | | 1-2 | RAM size in Kilobytes (big-endian)
197 | |
198 |
199 | ##### `$04`: VRAM size
200 |
201 | Reply length: 3 bytes
202 |
203 | | Position | Value
204 | |:---:|:---
205 | | 0 | 2
206 | | 1-2 | VRAM size in Kilobytes (big-endian)
207 | |
208 |
209 | ##### `$05`: Graphic modes (platform dependent)
210 |
211 | Reply length: 2 bytes
212 |
213 | | Position | Value
214 | |:---:|:---
215 | | 0 | 1
216 | | 1 | Graphic modes available
217 | |
218 |
219 | ###### C64:
220 |
221 | In addition to Hires and Multicolour
222 |
223 | | bit | Mode
224 | |:---:|:---
225 | | 0 | FLI
226 | | 1 | AFLI
227 | |
228 | ###### C128:
229 |
230 | In addition to Hires and Multicolour
231 |
232 | | bit | Mode
233 | |:---:|:---
234 | | 0 | FLI
235 | | 1 | AFLI
236 | | 2 | VDC
237 | | 3 | VDCI
238 |
239 | ###### MSX:
240 |
241 | In addition to Screen2
242 |
243 | | bit | Mode
244 | |:---:|:---
245 | | 0 | Screen 3
246 | | 1 | Screen 4
247 | | 2 | Screen 5
248 | | 3 | Screen 6
249 | | 4 | Screen 7
250 | | 5 | Screen 8
251 | | 6 | Screen 10
252 | | 7 | Screen 12
253 |
254 | ###### Amiga:
255 |
256 | | Value | Chipset
257 | |:---:|:---
258 | | 0 | OCS
259 | | 1 | ECS
260 | | 2 | AGA
261 |
262 | ##### `$06`: Audio (platform dependent)
263 |
264 | Reply length: 3 bytes
265 |
266 | | Position | Value
267 | |:---:|:---
268 | | 0 | 2
269 | | 1 | Synthesizers
270 | | 2 | PCM
271 |
272 | ###### Synthesizers
273 |
274 | ###### -Commodore 64/128
275 |
276 | | bit | Meaning
277 | |:---:|:---
278 | | 0-3 | Installed SID(s)-1
279 | | 4 | OPL present
280 | | 5 | microSynth present
281 | | 6 | Magic Voice present
282 |
283 | ###### -MSX
284 |
285 | | bit | Meaning
286 | |:---:|:---
287 | | 0 | MSX Audio present
288 | | 1 | MSX Music present
289 | | 2 | OPL3 present
290 |
291 | ###### PCM
292 |
293 | | bit | Meaning
294 | |:---:|:---
295 | | 0-1 | bits per sample (1(PWM)/4/8/16)
296 | | 2 | Channels (1/2)
297 | | 3 | Connection speed dependent sample rate
298 | | 4 | 11025Hz sample rate
299 | | 5 | 16000Hz sample rate
300 | | 6 | 22050Hz sample rate
301 | | 7 | Delta compression
302 |
--------------------------------------------------------------------------------
/docs/turbo56k.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/turbo56k.png
--------------------------------------------------------------------------------
/docs/vt-semigfx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/docs/vt-semigfx.png
--------------------------------------------------------------------------------
/images/apollo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/apollo.jpg
--------------------------------------------------------------------------------
/images/bbsintrologo.ocp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/bbsintrologo.ocp
--------------------------------------------------------------------------------
/images/bbspic1.ocp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/bbspic1.ocp
--------------------------------------------------------------------------------
/images/bbspic2.ocp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/bbspic2.ocp
--------------------------------------------------------------------------------
/images/bbspic3.ocp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/bbspic3.ocp
--------------------------------------------------------------------------------
/images/c64.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/c64.jpg
--------------------------------------------------------------------------------
/images/c64badge.kla:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/c64badge.kla
--------------------------------------------------------------------------------
/images/creation_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/creation_logo.png
--------------------------------------------------------------------------------
/images/puente_del_inca.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/puente_del_inca.jpg
--------------------------------------------------------------------------------
/images/readme.txt:
--------------------------------------------------------------------------------
1 | Insert your .jpg, .png, .gif, .kla, .koa, .ocp and .art image files here
--------------------------------------------------------------------------------
/images/retrotermlogo.art:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/retrotermlogo.art
--------------------------------------------------------------------------------
/images/shugart.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/shugart.jpg
--------------------------------------------------------------------------------
/images/thierry.kla:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/thierry.kla
--------------------------------------------------------------------------------
/images/venus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/images/venus.png
--------------------------------------------------------------------------------
/plugins/3dgraph.py:
--------------------------------------------------------------------------------
1 | import common.turbo56k as TT
2 | from common.connection import Connection
3 |
4 | from math import sin, pi, sqrt
5 | import numpy as np
6 |
7 | ###############
8 | # Plugin setup
9 | ###############
10 | def setup():
11 | fname = "3DGRAPH" #UPPERCASE function name for config.ini
12 | parpairs = [] #config.ini Parameter pairs (name,defaultvalue)
13 | return(fname,parpairs)
14 |
15 |
16 | ###################################
17 | # Plugin function
18 | ###################################
19 | def plugFunction(conn:Connection):
20 |
21 | valid = conn.QueryFeature(TT.LINE)|conn.QueryFeature(TT.HIRES)|conn.QueryFeature(TT.SCNCLR)
22 | if valid < 0x80:
23 | if 'PET' in conn.mode:
24 | scwidth = 320
25 | scheight = 200
26 | elif 'MSX' in conn.mode:
27 | scwidth = 256
28 | scheight = 192
29 | vx = [0]*150
30 | vy = [0]*150
31 | xmin = -4
32 | xmax = 4
33 | ymin = -4
34 | ymax = 4
35 | zmin = -1
36 | zmax = 1
37 | xsteps= np.linspace(xmin,xmax,16, endpoint=False) #(xmax-xmin)/150
38 | xscale=20
39 | ysteps= np.linspace(ymin,ymax,16, endpoint=False) #(ymax-ymin)/150
40 | yscale=20
41 | zscale=50
42 | _vx = 0
43 | _vy = 0
44 | xo = (scwidth/2)-(((xmax-xmin)*xscale)/2) #100
45 | yo = (scheight/2)-(((ymax-ymin)*yscale)/2) #50
46 | zo = 0
47 |
48 | pen0 = conn.encoder.colors.get('BLACK',1)
49 | pen1 = conn.encoder.colors.get('WHITE',1)
50 | conn.SendTML(f'')
51 | conn.Sendallbin(bytes([TT.CMDON]))
52 |
53 | my = 0
54 | for y in ysteps:
55 | i = 0
56 | for x in xsteps:
57 | z=sin(sqrt((x*x)+(y*y)))
58 | x1=xo+int((x-xmin)*xscale)
59 | y1=yo+int((y-ymin)*yscale)
60 | z1=zo+int((z-zmin)*zscale)
61 | xp=int(x1-z1*.3)
62 | yp=int(y1-z1*.5)
63 | if x == xsteps[0]:
64 | _vy = yp
65 | _vx = xp
66 | if y == ysteps[0]:
67 | vy[i] = yp
68 | vx[i] = xp
69 | _x1 = xp.to_bytes(2,'little',signed=True)
70 | _y1 = yp.to_bytes(2,'little',signed=True)
71 | _y2 = my.to_bytes(2,'little',signed=True)
72 | if yp < my:
73 | conn.Sendallbin(bytes([TT.LINE,0,_x1[0],_x1[1],_y1[0],_y1[1],_x1[0],_x1[1],_y2[0],_y2[1]])) # linexp,yp,xp,199,0
74 | # print(f'line(0,{xp},{yp},{xp},{my})')
75 | _x2 = vx[i].to_bytes(2,'little',signed=True)
76 | _y2 = vy[i].to_bytes(2,'little',signed=True)
77 | conn.Sendallbin(bytes([TT.LINE,1,_x1[0],_x1[1],_y1[0],_y1[1],_x2[0],_x2[1],_y2[0],_y2[1]]))# linexp,yp,vx(i),vy(i),1
78 | # print(f'line(1,{xp},{yp},{vx[i]},{vy[i]})')
79 | _x2 = _vx.to_bytes(2,'little',signed=True)
80 | _y2 = _vy.to_bytes(2,'little',signed=True)
81 | conn.Sendallbin(bytes([TT.LINE,1,_x1[0],_x1[1],_y1[0],_y1[1],_x2[0],_x2[1],_y2[0],_y2[1]]))# linexp,yp,vx,vy,1
82 | # print(f'line(1,{xp},{yp},{_vx},{_vy})')
83 | if yp > my:
84 | my = yp
85 | vy[i] = yp
86 | vx[i] = xp
87 | _vy = yp
88 | _vx = xp
89 | i += 1
90 | conn.Sendallbin(bytes([TT.CMDOFF]))
91 | conn.ReceiveKey()
92 | conn.SendTML('')
93 | ...
94 | else:
95 | conn.SendTML('ERROR: Your Terminal does not support drawing functions')
96 | return
97 | ...
--------------------------------------------------------------------------------
/plugins/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/plugins/__init__.py
--------------------------------------------------------------------------------
/plugins/apod.py:
--------------------------------------------------------------------------------
1 | #Retrieves an APOD and converts it to c64 gfx format
2 |
3 | import requests
4 | import random
5 | from datetime import datetime
6 | from PIL import Image
7 | from io import BytesIO
8 |
9 | from common import turbo56k as TT
10 | from common.style import RenderMenuTitle
11 | from common import filetools as FT
12 | from common.helpers import formatX, More, text_displayer
13 | from common.bbsdebug import _LOG,bcolors
14 | from common.connection import Connection
15 | from common.imgcvt import gfxmodes
16 |
17 | url = 'https://api.nasa.gov/planetary/apod'
18 |
19 | ###############
20 | # Plugin setup
21 | ###############
22 | def setup():
23 | fname = "APOD"
24 | parpairs= []
25 | return(fname,parpairs)
26 |
27 | start_date = datetime.today().replace(day=16, month=6, year=1995).toordinal()
28 | end_date = datetime.today().toordinal()
29 |
30 | ###################################
31 | # Plugin function
32 | ###################################
33 | def plugFunction(conn:Connection):
34 |
35 | apod_lang = {'en':['Connecting with NASA',f"
Converting...
press " \
36 | + f"[RETURN]" \
37 | + f" for a new
random image
Or " \
38 | + f"[" \
39 | + f"] to exit",
40 | f"A Turbo56K compatible terminal is required to view this image
"\
41 | + f"[RETURN]" \
42 | + f" for a new
random image
Or " \
43 | + f"[" \
44 | + f"] to exit"],
45 | 'es':['Conectando con la NASA',f"
Convirtiendo...
presione " \
46 | + f"[RETURN]" \
47 | + f" para mostrar otra imagen al azar
O " \
48 | + f"[" \
49 | + f"] para volver",
50 | f"Se requiere una terminal compatible con Turbo56K para ver ésta imagen
"\
51 | + f"[RETURN]" \
52 | + f" para una nueva imagen al azar
O " \
53 | + f"[" \
54 | + f"] para volver"]}
55 | loop = True
56 | rdate = datetime.today()
57 | while loop == True:
58 | # Text mode
59 | conn.SendTML(f'')
60 | RenderMenuTitle(conn,'APOD')
61 | conn.SendTML(apod_lang.get(conn.bbs.lang,'en')[0]+'...')
62 | i = 0
63 | idata = None
64 | _LOG("Receiving APOD info",id=conn.id,v=4)
65 | while idata == None and i<5:
66 | idata = apod_info(rdate,conn.bbs.PlugOptions.get('nasakey','DEMO_KEY'))
67 | rdate = datetime.fromordinal(random.randint(start_date, end_date))
68 | if idata == None:
69 | _LOG(bcolors.OKBLUE+"APOD Retrying..."+bcolors.ENDC,id=conn.id,v=3)
70 | i += 1
71 | conn.Sendall(".")
72 | conn.SendTML(f'')
73 | if idata != None:
74 | scwidth,scheight = conn.encoder.txt_geo
75 | if conn.QueryFeature(TT.SET_WIN) >= 0x80:
76 | barline = 3
77 | else:
78 | barline = scheight-1
79 | if conn.QueryFeature(TT.SCROLL) >= 0x80 and not conn.encoder.features['scrollback']:
80 | crsr = ''
81 | else:
82 | if set(('CRSRU','CRSRD')) <= conn.encoder.ctrlkeys.keys():
83 | crsr = 'crsr'
84 | else:
85 | crsr = 'a/z'
86 | if set(('F1','F3')) <= conn.encoder.ctrlkeys.keys():
87 | pages = 'F1/F3'
88 | else:
89 | pages = 'p/n'
90 | conn.SendTML(conn.templates.GetTemplate('main/navbar',**{'barline':barline,'crsr':crsr,'pages':pages,'keys':[('v','view')]}))
91 | # if 'MSX' in conn.mode:
92 | # bcode = 0xDB
93 | # rcrsr = ''
94 | # else:
95 | # bcode = 0xA0
96 | # rcrsr = ''
97 | # if conn.QueryFeature(TT.LINE_FILL) < 0x80:
98 | # conn.SendTML(f'')
99 | # else:
100 | # conn.SendTML(f' ')
101 | # conn.SendTML(f'{pages}{crsr}:movev:view{rcrsr}:exit')
102 | if conn.QueryFeature(TT.SET_WIN) >= 0x80:
103 | conn.SendTML('
')
104 | date = idata["date"]
105 | _LOG("Showing APOD info for "+date,id=conn.id,v=4)
106 | imurl = idata["url"]
107 | title = idata["title"]
108 | desc = idata["explanation"]
109 | if "copyright" in idata:
110 | autor = idata["copyright"]
111 | else:
112 | autor = ''
113 | texto = formatX(title,scwidth)
114 | #Date
115 | tdate = formatX('\n'+date+'\n\n',scwidth)
116 | tdate[0] = f''+tdate[0]
117 | texto += tdate
118 | #Author
119 | if autor != '':
120 | at = formatX(autor,scwidth)
121 | at[0] = f''+at[0]
122 | else:
123 | at = ['
']
124 | #Description
125 | tdesc = formatX(desc,scwidth)
126 | tdesc[0] = f''+tdesc[0]
127 | texto += at+tdesc
128 | conn.SendTML(f'')
129 | tecla = text_displayer(conn,texto,scheight-4,ekeys='v')
130 | conn.SendTML('')
131 | back = conn.encoder.back
132 | if conn.connected == False:
133 | return()
134 | if tecla == back or tecla == '':
135 | loop = False
136 | if loop == True:
137 | if conn.QueryFeature(TT.PRADDR) < 0x80 or (conn.T56KVer == 0 and len(conn.encoder.gfxmodes) > 0):
138 | conn.SendTML(apod_lang.get(conn.bbs.lang,'en')[1])
139 | _LOG("Downloading and converting image",id=conn.id,v=4)
140 | try:
141 | img = apod_img(conn, imurl)
142 | FT.SendBitmap(conn, img)
143 | except:
144 | _LOG(bcolors.WARNING+"Error receiving APOD image"+bcolors.ENDC,id=conn.id,v=2)
145 | conn.SendTML("
ERROR, unable to receive image")
146 | else:
147 | conn.SendTML(apod_lang.get(conn.bbs.lang,'en')[2])
148 | tecla = conn.ReceiveKey([conn.encoder.nl,back])
149 | conn.SendTML('')
150 | if conn.connected == False:
151 | _LOG(bcolors.WARNING+"ShowAPOD - Disconnect"+bcolors.ENDC,id=conn.id,v=1)
152 | return()
153 | if tecla == back or tecla == '':
154 | loop = False
155 | else:
156 | conn.SendTML("
ERROR, unable to connect with NASA")
157 | _LOG(bcolors.WARNING+"Error while reaching NASA"+bcolors.ENDC,id=conn.id,v=2)
158 | loop = False
159 |
160 | #####################################################
161 | # Retrieve APOD data
162 | #####################################################
163 | def apod_info(idate, key='DEMO_KEY', retry = False):
164 | global url
165 |
166 | date = idate.strftime("%Y-%m-%d")
167 | resp = None
168 | while resp == None:
169 | try :
170 | param = {'api_key': key, 'date': date}
171 | resp = requests.get(url, params=param, timeout=8).json()
172 | #apod_url = resp["hdurl"]
173 | if "media_type" in resp:
174 | m_type = resp["media_type"]
175 | else:
176 | m_type = ''
177 | if m_type != 'image' and retry == True:
178 | _LOG('APOD - Not an image, retrying...')
179 | resp = None
180 | date = datetime.fromordinal(random.randint(start_date, end_date)).strftime("%Y-%m-%d")
181 | except :
182 | if retry == True:
183 | _LOG('APOD - Error, retrying...')
184 | else:
185 | m_type = ''
186 | resp = -1
187 | if m_type != 'image':
188 | resp = None
189 | return(resp)
190 |
191 | ###################################
192 | # Retrieve APOD image
193 | ###################################
194 | def apod_img(conn:Connection,url):
195 | cv_img = None
196 | bitmap = None
197 | screen = None
198 | colorRAM = None
199 | background = 0
200 | try:
201 | apod_im = requests.get(url, allow_redirects=True)
202 | _LOG('APOD - Image retrieved', id=conn.id, v=4)
203 | except:
204 | _LOG('APOD - Error retreiving image', id=conn.id, v=2)
205 | return(cv_img, bitmap, screen, colorRAM, background)
206 | try:
207 | img = Image.open(BytesIO(apod_im.content))
208 | img = img.convert("RGB")
209 | except:
210 | _LOG('APOD - Error converting image', id=conn.id, v=1)
211 | return (img)
212 |
213 |
--------------------------------------------------------------------------------
/plugins/maps_dragons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/plugins/maps_dragons.png
--------------------------------------------------------------------------------
/plugins/maps_intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/plugins/maps_intro.png
--------------------------------------------------------------------------------
/plugins/maps_intro_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retrocomputacion/retrobbs/7d139af9aa978e23793002176a60d3f2244bdec3/plugins/maps_intro_256.png
--------------------------------------------------------------------------------
/plugins/mindle_words/valid3.txt:
--------------------------------------------------------------------------------
1 | aah
2 | aas
3 | abs
4 | ace
5 | act
6 | add
7 | ado
8 | ads
9 | adz
10 | aft
11 | age
12 | ago
13 | aha
14 | aid
15 | ail
16 | aim
17 | air
18 | ais
19 | alb
20 | ale
21 | all
22 | amp
23 | and
24 | ant
25 | any
26 | ape
27 | app
28 | apt
29 | arc
30 | are
31 | ark
32 | arm
33 | ars
34 | art
35 | ash
36 | ask
37 | asp
38 | ass
39 | ate
40 | awe
41 | awl
42 | awn
43 | axe
44 | aye
45 | ays
46 | baa
47 | bad
48 | bag
49 | bam
50 | ban
51 | bar
52 | bat
53 | bay
54 | bed
55 | bee
56 | beg
57 | ben
58 | bet
59 | bib
60 | bid
61 | big
62 | bin
63 | bit
64 | boa
65 | bob
66 | bog
67 | boo
68 | bop
69 | bow
70 | box
71 | boy
72 | bra
73 | bud
74 | bug
75 | bum
76 | bun
77 | bus
78 | but
79 | buy
80 | bye
81 | cab
82 | cad
83 | cam
84 | can
85 | cap
86 | car
87 | cat
88 | caw
89 | cay
90 | cee
91 | chi
92 | cis
93 | cob
94 | cod
95 | cog
96 | con
97 | coo
98 | cop
99 | cot
100 | cow
101 | coy
102 | cry
103 | cub
104 | cud
105 | cue
106 | cup
107 | cur
108 | cut
109 | cwm
110 | dab
111 | dad
112 | dam
113 | daw
114 | day
115 | dee
116 | den
117 | dew
118 | dey
119 | did
120 | die
121 | dig
122 | dim
123 | din
124 | dip
125 | doe
126 | dog
127 | don
128 | dos
129 | dot
130 | dox
131 | dry
132 | dub
133 | dud
134 | due
135 | dug
136 | duo
137 | dye
138 | dzo
139 | ear
140 | eat
141 | ebb
142 | eek
143 | eel
144 | eff
145 | efs
146 | egg
147 | ego
148 | eke
149 | elf
150 | elk
151 | ell
152 | elm
153 | els
154 | ems
155 | emu
156 | end
157 | ens
158 | eon
159 | era
160 | erg
161 | err
162 | ess
163 | eta
164 | eve
165 | ewe
166 | eye
167 | fad
168 | fan
169 | far
170 | fat
171 | fax
172 | fed
173 | fee
174 | fen
175 | few
176 | fib
177 | fig
178 | fin
179 | fir
180 | fit
181 | fix
182 | flu
183 | fly
184 | foe
185 | fog
186 | foh
187 | fop
188 | for
189 | fox
190 | fro
191 | fry
192 | fun
193 | fur
194 | gab
195 | gad
196 | gag
197 | gal
198 | gam
199 | gap
200 | gas
201 | gay
202 | gee
203 | gel
204 | gem
205 | get
206 | gig
207 | gin
208 | gnu
209 | gob
210 | god
211 | goo
212 | gos
213 | got
214 | gum
215 | gun
216 | gut
217 | guy
218 | gym
219 | had
220 | hag
221 | ham
222 | hap
223 | has
224 | hat
225 | haw
226 | hay
227 | hem
228 | hen
229 | her
230 | hes
231 | het
232 | hew
233 | hex
234 | hey
235 | hic
236 | hid
237 | him
238 | hip
239 | his
240 | hit
241 | hoe
242 | hog
243 | hop
244 | hot
245 | how
246 | hub
247 | hue
248 | hug
249 | huh
250 | hum
251 | hut
252 | ice
253 | ick
254 | icy
255 | ide
256 | ids
257 | ifs
258 | ilk
259 | ill
260 | imp
261 | ink
262 | inn
263 | ins
264 | ion
265 | ire
266 | irk
267 | ism
268 | its
269 | ivy
270 | jab
271 | jag
272 | jam
273 | jar
274 | jaw
275 | jay
276 | jet
277 | jib
278 | jig
279 | job
280 | jog
281 | jot
282 | joy
283 | jug
284 | jut
285 | kaf
286 | kat
287 | kay
288 | kea
289 | keg
290 | kep
291 | key
292 | kin
293 | kip
294 | kit
295 | lab
296 | lad
297 | lag
298 | lam
299 | lap
300 | law
301 | lax
302 | lay
303 | lea
304 | led
305 | lee
306 | leg
307 | lek
308 | let
309 | leu
310 | lev
311 | lid
312 | lie
313 | lip
314 | lit
315 | lob
316 | log
317 | loo
318 | lop
319 | lot
320 | low
321 | lox
322 | lug
323 | lux
324 | lye
325 | mad
326 | man
327 | map
328 | mar
329 | mas
330 | mat
331 | maw
332 | may
333 | mem
334 | men
335 | met
336 | mew
337 | mid
338 | mil
339 | mix
340 | mob
341 | mom
342 | moo
343 | mop
344 | mow
345 | mud
346 | mug
347 | mum
348 | mus
349 | nab
350 | nag
351 | nap
352 | nay
353 | net
354 | new
355 | nib
356 | nil
357 | nip
358 | nit
359 | nix
360 | nob
361 | nod
362 | nor
363 | nos
364 | not
365 | now
366 | nub
367 | nun
368 | nus
369 | nut
370 | oaf
371 | oak
372 | oar
373 | oat
374 | odd
375 | ode
376 | off
377 | oft
378 | ohm
379 | ohs
380 | oil
381 | old
382 | one
383 | ooh
384 | ops
385 | opt
386 | orb
387 | ore
388 | ors
389 | our
390 | out
391 | ova
392 | owe
393 | owl
394 | own
395 | ows
396 | pad
397 | pal
398 | pan
399 | pap
400 | par
401 | pat
402 | paw
403 | pay
404 | pea
405 | peg
406 | pen
407 | pep
408 | per
409 | pet
410 | pew
411 | phi
412 | pie
413 | pig
414 | pin
415 | pip
416 | pis
417 | pit
418 | ply
419 | pod
420 | pop
421 | pot
422 | pow
423 | pox
424 | pry
425 | psi
426 | pub
427 | pug
428 | pun
429 | pup
430 | pus
431 | put
432 | qat
433 | qis
434 | qof
435 | qua
436 | rag
437 | ram
438 | ran
439 | rap
440 | rat
441 | raw
442 | rax
443 | ray
444 | red
445 | ref
446 | rev
447 | rho
448 | rib
449 | rid
450 | rig
451 | rim
452 | rip
453 | rob
454 | rod
455 | roe
456 | rot
457 | row
458 | rub
459 | rue
460 | rug
461 | rum
462 | run
463 | rut
464 | rye
465 | sac
466 | sad
467 | sag
468 | sap
469 | sat
470 | saw
471 | sax
472 | say
473 | sea
474 | see
475 | set
476 | sew
477 | sex
478 | she
479 | shy
480 | sic
481 | sim
482 | sin
483 | sip
484 | sir
485 | sit
486 | six
487 | ski
488 | sky
489 | sly
490 | sob
491 | sod
492 | sol
493 | som
494 | son
495 | sop
496 | sot
497 | sow
498 | sox
499 | soy
500 | spa
501 | spy
502 | sty
503 | sub
504 | sue
505 | sum
506 | sun
507 | suq
508 | tab
509 | tad
510 | tag
511 | tam
512 | tan
513 | tap
514 | tar
515 | tau
516 | tav
517 | taw
518 | tax
519 | tea
520 | tee
521 | ten
522 | tet
523 | the
524 | thy
525 | tic
526 | tie
527 | tin
528 | tip
529 | toe
530 | tom
531 | ton
532 | too
533 | top
534 | tot
535 | tow
536 | toy
537 | try
538 | tub
539 | tug
540 | tux
541 | two
542 | ugh
543 | uhs
544 | uke
545 | ump
546 | ups
547 | urn
548 | use
549 | van
550 | vat
551 | vav
552 | vee
553 | vet
554 | vex
555 | via
556 | vie
557 | vim
558 | voe
559 | vow
560 | vug
561 | wad
562 | wag
563 | wan
564 | war
565 | was
566 | waw
567 | wax
568 | way
569 | web
570 | wed
571 | wee
572 | wet
573 | who
574 | why
575 | wig
576 | win
577 | wit
578 | wiz
579 | woe
580 | wok
581 | won
582 | woo
583 | wow
584 | wry
585 | wye
586 | wys
587 | xis
588 | yah
589 | yak
590 | yam
591 | yap
592 | yar
593 | yaw
594 | yay
595 | yea
596 | yeh
597 | yen
598 | yep
599 | yes
600 | yet
601 | yew
602 | yex
603 | yin
604 | yip
605 | yod
606 | yon
607 | you
608 | yow
609 | yuk
610 | yum
611 | yup
612 | zag
613 | zap
614 | zas
615 | zax
616 | zed
617 | zee
618 | zen
619 | zep
620 | zho
621 | zig
622 | zip
623 | zit
624 | zoa
625 | zoo
626 | zuz
--------------------------------------------------------------------------------
/plugins/newsfeed.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import textwrap
3 | from bs4 import BeautifulSoup
4 | import feedparser
5 | from PIL import Image
6 | from io import BytesIO
7 | from urllib.parse import urlparse,urljoin
8 |
9 | from common.bbsdebug import _LOG,bcolors
10 | from common.imgcvt import gfxmodes
11 | from common import helpers as H
12 | from common import style as S
13 | from common.connection import Connection
14 | from common import turbo56k as TT
15 | from common import filetools as FT
16 |
17 | ### User Agent string used for some stingy content sources
18 | hdrs = {'User-Agent':'Mozilla/5.0 (X11; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0'}
19 |
20 | ###############
21 | # Plugin setup
22 | ###############
23 | def setup():
24 | fname = "NEWSFEED" #UPPERCASE function name for config.ini
25 | parpairs = [('url','')] #config.ini Parameter pairs (name,defaultvalue)
26 | return(fname,parpairs)
27 | #############################
28 |
29 | #######################################
30 | # Plugin callable function
31 | #######################################
32 | def plugFunction(conn:Connection,url):
33 |
34 | kdecos = {'VT52':('[',']'),'VidTex':('[',']'),'ASCII':('[',']'),
35 | 'ATRSTM':(' ',' '),'ATRSTL':(' ',' '),
36 | 'default':('','')}
37 |
38 | if conn.menu != -1:
39 | conn.MenuStack.append([conn.MenuDefs,conn.menu])
40 | conn.menu = -1
41 | colors = conn.encoder.colors
42 | scwidth,scheight = conn.encoder.txt_geo
43 | menucolors = [[colors.get('LIGHT_BLUE',colors.get('BLUE',colors.get('WHITE',0))),colors.get('LIGHT_GREY',colors.get('WHITE',0))],[colors.get('CYAN',colors.get('GREEN',colors.get('WHITE',0))),colors.get('YELLOW',colors.get('WHITE',0))]]
44 | MenuDic = {
45 | conn.encoder.decode(conn.encoder.back): (H.MenuBack,(conn,),"Previous menu",0,False),
46 | conn.encoder.nl: (plugFunction,(conn,url),"",0,False)
47 | }
48 | # Text mode
49 | conn.SendTML(f'')
50 | nfeed = feedparser.parse(url)
51 | try:
52 | lines = 5
53 | _LOG('NewsFeeds - Feed: '+nfeed.feed.get('title','-no title-'),id=conn.id,v=2)
54 | conn.SendTML("Recent from:
")
55 | title = H.formatX(nfeed.feed.get('title','No title'),scwidth)
56 | for t in title:
57 | conn.SendTML(t)
58 | if len(t)')
60 | lines +=len(title)
61 | for i,e in enumerate(nfeed.entries):
62 | text = textwrap.shorten(e.get('title','No title'),width=72,placeholder='...')
63 | text = H.formatX(text,columns=scwidth-3)
64 | lines+=len(text)
65 | if lines>scheight-3:
66 | break
67 | conn.SendTML(conn.templates.GetTemplate('main/keylabel',**{'key':H.valid_keys[i],'label':'','toggle':i%2==0}))
68 | x = 0
69 | for t in text:
70 | conn.SendTML(f'{t}')
71 | x=1
72 | MenuDic[H.valid_keys[i]] = (feedentry,(conn,e,nfeed.feed.get('title','No title')),H.valid_keys[i],0,False)
73 | if i == len(H.valid_keys)-1:
74 | break
75 | conn.SendTML(conn.templates.GetTemplate('main/keylabel',**{'key':conn.encoder.back,'label':'Back','toggle':i%2==0})+
76 | '
Your choice: ')
77 | return MenuDic
78 | except:
79 | _LOG('Newsfeed - '+bcolors.FAIL+'failed'+bcolors.ENDC, id=conn.id,v=1)
80 |
81 | ###############################################
82 | # Parse an RSS/Atom feed entry
83 | ###############################################
84 | def feedentry(conn:Connection,entry,feedname):
85 | _LOG('NewsFeeds - Entry: '+entry.get('title','-no title-'),id=conn.id,v=4)
86 | mtitle = textwrap.shorten(feedname,width=38-(len(conn.bbs.name)+7),placeholder='...')
87 | scwidth,scheight = conn.encoder.txt_geo
88 | if webarticle(conn,entry.link,mtitle) == False:
89 | e_title = entry.get('title','')
90 | S.RenderMenuTitle(conn,mtitle)
91 | renderBar(conn)
92 | conn.SendTML(f'')
93 | e_text = ''
94 | content = entry.get('content',[]) #Atom
95 | for c in content:
96 | if 'html' in c['type']: #Get first (x)html content entry
97 | e_text = c['value']
98 | continue
99 | if len(e_text) == 0:
100 | e_text = entry.get('description','') #RSS
101 | soup= BeautifulSoup(e_text, "html.parser")
102 | texts = soup.find_all(text=True)
103 | e_text = " ".join(t.strip() for t in texts)
104 | body = H.formatX(e_text,scwidth)
105 | title = H.formatX(e_title,scwidth)
106 | title[0] = ''+title[0]
107 | title.append(f'')
108 | title.append('
')
109 | text = title + body
110 | H.text_displayer(conn,text,scheight-4)
111 | conn.SendTML(f'')
112 |
113 | #############################################################
114 | # Try to scrap data from wordpress and some other CMS sites,
115 | # returns False if entry title or body cannot be found
116 | #############################################################
117 | def webarticle(conn:Connection,url, feedname):
118 | conn.SendTML('')
119 | resp = requests.get(url, allow_redirects = False, headers = hdrs)
120 | r = 0 # Redirect loop disconnector
121 | while resp.status_code == 301 or resp.status_code == 302 and r < 10:
122 | url = resp.headers['Location']
123 | resp = requests.get(url, allow_redirects = False, headers = hdrs)
124 | r += 1
125 | purl = urlparse(url)
126 | top_url = purl.scheme + '://' + purl.netloc
127 | if resp.status_code == 200:
128 | scwidth,scheight = conn.encoder.txt_geo
129 | soup= BeautifulSoup(resp.content, "html.parser")
130 | # Remove unwanted sections
131 | for div in soup.find_all(['div','nav','aside','header'],
132 | {'class':['author-bio','post-thumbnail','mg-featured-slider','random',
133 | 'wp-post-nav','upprev_thumbnail','primary-sidebar','related-posts-list',
134 | 'footer-widget-area','sidebar','sticky','titlewrapper','widget-title',
135 | 'PopularPosts']}):
136 | div.decompose()
137 | # Replace tags
138 | for br in soup.find_all("br"):
139 | br.replace_with('\n')
140 | ##### Title #####
141 | try:
142 | a_title = soup.find(['h1','h2','h3'],{'class':['entry-title','post-title','title']}).get_text()
143 | except:
144 | a_title = None
145 | if a_title == None:
146 | t_soup = soup.find('div',{'class':['view-item','artikel_titel']})
147 | if t_soup != None:
148 | a_title = t_soup.find('a').get_text()
149 | if a_title == None:
150 | _LOG('Newsfeed - '+bcolors.WARNING+'webscrapping failed - no title - defaulting to rss data'+bcolors.ENDC, id=conn.id,v=2)
151 | return(False)
152 | else:
153 | _LOG('Newsfeed - '+bcolors.WARNING+'webscrapping failed - no title - defaulting to rss data'+bcolors.ENDC, id=conn.id,v=2)
154 | return(False)
155 | ##### Author #####
156 | a_author = None
157 | a_soup = soup.find('a',{'rel':'author'})
158 | if a_soup != None:
159 | a_author = a_soup.get_text()
160 | else:
161 | a_soup = soup.find('a',{'class':'author-name'})
162 | if a_soup == None:
163 | a_soup = soup.find(['div','span','p'],[{'class':'author'},{'class':'lead'}])
164 | if a_soup != None:
165 | a_author = a_soup.find('a').get_text()
166 | else:
167 | a_author = a_soup.get_text()
168 | ##### Body #####
169 | a_body = soup.find('div',{'class':['entry','entry-content','the-content','entry-inner','post-content','node-content','article-body','artikel_tekst']})
170 | if a_body == None:
171 | a_body = soup.find('article')
172 | if a_body == None:
173 | _LOG('Newsfeed - '+bcolors.WARNING+'webscrapping failed - no body - defaulting to rss data'+bcolors.ENDC, id=conn.id,v=2)
174 | return(False)
175 | a_headers = a_body.find_all(['h2','h4'])
176 | body = []
177 | if len(a_headers) != 0:
178 | for h in a_headers:
179 | h2 = H.formatX(h.get_text(),scwidth)
180 | h2[0] = f''+h2[0]
181 | body += h2
182 | for el in h.next_siblings:
183 | if el.name and el.name.startswith('h'):
184 | break
185 | if el.name == 'p':
186 | p = H.formatX(el.get_text(),scwidth)
187 | if len(p)>0:
188 | p[0] = f''+p[0]
189 | body += p
190 | body.append('
')
191 | else:
192 | a_paras = a_body.find_all(['p'])
193 | for p in a_paras:
194 | body += H.formatX(p.get_text(),scwidth)+['
']
195 | if body == []:
196 | body = H.formatX(a_body.get_text(),scwidth)
197 | ##### Entry image #####
198 | if conn.QueryFeature(TT.PRADDR) < 0x80 or (conn.T56KVer == 0 and len(conn.encoder.gfxmodes) > 0):
199 | d_img = soup.find('div',{'class':'entry-featured-image'})
200 | if d_img != None:
201 | a_img = d_img.find('img')
202 | else:
203 | a_img = None
204 | if a_img == None:
205 | a_img = soup.find('img',{'class':['wp-post-image','header','news_image']})
206 | if a_img == None:
207 | a_img = a_body.find('img')
208 | if a_img != None:
209 | conn.SendTML('')
210 | FT.SendBitmap(conn,getImg(top_url,a_img))
211 | conn.ReceiveKey()
212 | conn.SendTML(f'')
213 | S.RenderMenuTitle(conn,feedname)
214 | renderBar(conn)
215 | conn.SendTML(f'')
216 | title = H.formatX(a_title,scwidth)
217 | title[0] = ''+title[0]
218 | title.append(f'')
219 | if a_author != None:
220 | title.append(f'by: {H.crop(a_author,scwidth-3,conn.encoder.ellipsis)}
')
221 | title.append('
')
222 | body[0] = ''+body[0]
223 | text = title + body
224 | H.text_displayer(conn,text,scheight-4)
225 | conn.SendTML(f'')
226 | else:
227 | conn.SendTML(f'{resp.status_code}')
228 | _LOG('Newsfeed - '+bcolors.WARNING+'webscrapping failed - defaulting to rss description'+bcolors.ENDC, id=conn.id,v=2)
229 | return(False)
230 | return(True)
231 |
232 | #######################
233 | # Get entry image
234 | #######################
235 | def getImg(url,img_t):
236 | src = img_t['src']
237 | src = urljoin(url, src)
238 | scrap_im = requests.get(src, allow_redirects=True, headers=hdrs, timeout=10)
239 | try:
240 | img = Image.open(BytesIO(scrap_im.content))
241 | except:
242 | img = Image.new("RGB",(320,200),"red")
243 | return(img)
244 |
245 | def renderBar(conn:Connection):
246 | scwidth,scheight = conn.encoder.txt_geo
247 | if conn.QueryFeature(TT.SET_WIN) >= 0x80:
248 | barline = 3
249 | else:
250 | barline = scheight-1
251 | if conn.QueryFeature(TT.SCROLL) >= 0x80 and not conn.encoder.features['scrollback']:
252 | crsr = ''
253 | else:
254 | if set(('CRSRU','CRSRD')) <= conn.encoder.ctrlkeys.keys():
255 | crsr = 'crsr'
256 | else:
257 | crsr = 'a/z'
258 | if set(('F1','F3')) <= conn.encoder.ctrlkeys.keys():
259 | pages = 'F1/F3'
260 | else:
261 | pages = 'p/n'
262 | conn.SendTML(conn.templates.GetTemplate('main/navbar',**{'barline':barline,'pages':pages,'crsr':crsr}))
263 | if conn.QueryFeature(TT.SET_WIN) >= 0x80:
264 | conn.SendTML('
')
265 |
266 |
--------------------------------------------------------------------------------
/plugins/oneliner.py:
--------------------------------------------------------------------------------
1 | import json
2 | import string
3 | import os
4 | from math import ceil
5 |
6 | from common import style as S
7 | from common.connection import Connection
8 | from common import turbo56k as TT
9 | from common.helpers import crop
10 |
11 | ###############
12 | # Plugin setup
13 | ###############
14 | def setup():
15 | fname = "ONELINER" #UPPERCASE function name for config.ini
16 | parpairs = [] #config.ini Parameter pairs (name,defaultvalue)
17 | return(fname,parpairs)
18 | #############################
19 |
20 | ###################################
21 | # Plugin function
22 | ###################################
23 | def plugFunction(conn:Connection):
24 |
25 | def header():
26 | conn.SendTML(conn.templates.GetTemplate('oneliner/title',**{}))
27 | if conn.QueryFeature(TT.LINE_FILL) < 0x80:
28 | if 'MSX' in conn.mode:
29 | conn.SendTML(f'')
30 | else:
31 | conn.SendTML(f'') # Window borders
32 | else:
33 | conn.SendTML(f'')
34 | if conn.T56KVer > 0:
35 | conn.SendTML(f'')
36 | else:
37 | conn.SendTML('')
38 |
39 |
40 | _dec = conn.encoder.decode
41 | keys = string.ascii_letters + string.digits + " !?';:[]()*/@+-_,.$%&"
42 | scwidth,scheight = conn.encoder.txt_geo
43 | if conn.T56KVer > 0:
44 | header()
45 | refr = True
46 | while conn.connected:
47 | if conn.T56KVer == 0 and refr:
48 | header()
49 | if refr == True:
50 | onelines = getOneliners()
51 | sendOneliners(conn, onelines)
52 | refr = False
53 | if conn.T56KVer > 0:
54 | conn.SendTML(f'')
55 | else:
56 | conn.SendTML('
')
57 | conn.SendTML(f'new message exit')
58 | if conn.userclass == 10: # Admin
59 | conn.SendTML(' elete')
60 | adm = 'd'
61 | else:
62 | adm = ''
63 | back = conn.encoder.decode(conn.encoder.back)
64 | comm = conn.ReceiveKey(back+conn.encoder.nl+adm)
65 | if comm == back:
66 | break
67 | elif comm == conn.encoder.nl:
68 | if conn.encoder.features['windows'] > 0:
69 | conn.SendTML('')
70 | else:
71 | conn.SendTML('
')
72 | if conn.userclass > 0:
73 | nick = conn.username
74 | else:
75 | conn.SendTML('Nick: ')
76 | nick = _dec(conn.ReceiveStr(keys,20))
77 | if nick != '':
78 | if conn.T56KVer > 0:
79 | conn.SendTML('')
80 | else:
81 | conn.SendTML('
')
82 | conn.SendTML('Message:
')
83 | line = _dec(conn.ReceiveStr(keys, scwidth-1))
84 | if line != '':
85 | onelines = getOneliners()
86 | onelines.append([nick,line])
87 | if len(onelines) > 9:
88 | onelines.pop(0) #If there's more than 9 onelines, remove the oldest.
89 | saveOneliners(onelines)
90 | refr = True
91 | elif conn.encoder.features['windows'] == 0:
92 | refr = True
93 | else: # Admin delete messages
94 | onelines = getOneliners()
95 | if conn.encoder.features['windows'] > 0:
96 | conn.SendTML(f'')
97 | else:
98 | conn.SendTML('
')
99 | for i,l in enumerate(onelines):
100 | line = crop(f'{l[0]} - {l[1]}', conn.encoder.txt_geo[0]-4, conn.encoder.ellipsis)
101 | conn.SendTML(f'{i}: {line}
')
102 | conn.SendTML('Select message to delete: ')
103 | msg = conn.ReceiveKey(list(str(i) for i in range(len(onelines)-1)))
104 | conn.SendTML(f'{msg}
Are you sure (Y/N)?')
105 | if conn.ReceiveKey('yn') == 'y':
106 | del(onelines[int(msg)])
107 | saveOneliners(onelines)
108 | refr = True
109 |
110 |
111 | conn.SendTML(f'')
112 |
113 | ##########################################
114 | # Send oneliners to connection
115 | ##########################################
116 | def sendOneliners(conn:Connection,lines):
117 | # count = ceil(conn.encoder.txt_geo[0]/3)-1
118 | conn.SendTML(f'')
119 | if conn.T56KVer > 0:
120 | conn.SendTML('')
121 | for i,l in enumerate(lines):
122 | if 'MSX' in conn.mode:
123 | txtc = ''
124 | else:
125 | txtc = ''
126 | line = crop(l[1],conn.encoder.txt_geo[0],conn.encoder.ellipsis)
127 | conn.SendTML(f'{l[0]} says:
{txtc}{line}')
128 | if (i<8) and (len(line)')
130 | if i == 8: #Just in case the json file has more than 9 entries
131 | break
132 |
133 | ######################################
134 | # Get the oneliners
135 | ######################################
136 | def getOneliners():
137 | try: # Refresh oneliners in case another user posted in the meanwhile
138 | olf = open('plugins/oneliners.json','r')
139 | onelines = json.load(olf)
140 | olf.close()
141 | except:
142 | onelines = []
143 | return onelines
144 |
145 | ######################################
146 | # Save the oneliners
147 | ######################################
148 | def saveOneliners(onelines):
149 | with open('plugins/oneliners.json','w') as olf:
150 | json.dump(onelines,olf,indent=4)
151 | olf.flush()
152 | os.fsync(olf.fileno()) # Make sure the file is updated on disk
153 |
--------------------------------------------------------------------------------
/plugins/oneliners.json:
--------------------------------------------------------------------------------
1 | [["Durandal once more", "23:01 aqui"], ["pastbytes", "conectando desde VICE..."], ["Durandal", "Test"], ["dURANDAL", "Nuevos RSS agreegados"], ["durandal", "conectado desde c128 con ultimo firmwar"], ["durandal", "ahora con la Drean"], ["EDSEL", "OK///"], ["Thierry64", "test desde C64 RELOADED MK1+SIDFX"], ["durandal", "testing"]]
--------------------------------------------------------------------------------
/plugins/podcast.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Podcast Plugin 20240826 written by Emanuele Laface #
3 | # #
4 | # This plugin requires feedpareser to work and it is a Python #
5 | # implementation of https://performance-partners.apple.com/search-api #
6 | # #############################################################################
7 |
8 | from common import turbo56k as TT
9 | from common import filetools as FT
10 | from common import audio as AA
11 | from common.imgcvt import cropmodes, PreProcess, gfxmodes, dithertype
12 | from common.helpers import crop
13 | from common.connection import Connection
14 |
15 | import string
16 | import feedparser
17 | import requests
18 | import io
19 | from PIL import Image
20 | import numpy
21 |
22 | ###############
23 | # Plugin setup
24 | ###############
25 | def setup():
26 | fname = "PODCAST"
27 | parpairs = []
28 | return(fname,parpairs)
29 |
30 | ###################################
31 | # Plugin callable function
32 | ###################################
33 | def plugFunction(conn:Connection):
34 |
35 | _dec = conn.encoder.decode
36 | columns,lines = conn.encoder.txt_geo
37 | tml = f''
38 |
39 | def PodcastTitle(conn:Connection):
40 | conn.SendTML(f'Search internet podcasts
')
41 | if conn.QueryFeature(TT.LINE_FILL) < 0x80:
42 | if 'MSX' in conn.mode:
43 | conn.SendTML('')
44 | else:
45 | conn.SendTML('')
46 | else:
47 | conn.SendTML(f'')
48 | conn.SendTML(f'') #Set Text Window
49 | ecolors = conn.encoder.colors
50 | conn.SendTML(f'')
51 | loop = True
52 | while loop == True:
53 | PodcastTitle(conn)
54 | conn.SendTML('
Search:
( to exit)')
55 | keys = string.ascii_letters + string.digits + ' +-_,.$%&'
56 | back = conn.encoder.decode(conn.encoder.back)
57 | if back not in keys:
58 | keys += back
59 | termino = ''
60 | #Receive search term
61 | while termino == '':
62 | termino = _dec(conn.ReceiveStr(keys, columns-10, False))
63 | if conn.connected == False :
64 | return()
65 | if termino == back:
66 | conn.SendTML(f'')
67 | return()
68 | conn.SendTML('
Searching...')
69 | searchRes = searchPodcast(termino)
70 | if searchRes == False:
71 | conn.SendTML('Service unavailable...')
72 | continue
73 | elif searchRes['resultCount'] == 0:
74 | conn.SendTML('No results...')
75 | continue
76 | page = 0
77 | npodcasts = searchRes['resultCount']
78 | pcount = lines-10
79 | if 'MSX' in conn.mode:
80 | grey = ''
81 | else:
82 | grey = ''
83 | while True:
84 | conn.SendTML('
Results:
')
85 | for i in range(pcount*page, min(pcount*(page+1),npodcasts)):
86 | if i > 9:
87 | pos = str(i)
88 | else:
89 | pos = " "+str(i)
90 | podcastName = crop(searchRes['results'][i]['collectionName'],columns-10,conn.encoder.ellipsis).ljust(columns-10)
91 | conn.SendTML(f' {pos} {grey}{podcastName}
')
92 | if npodcasts < pcount:
93 | conn.SendTML(f'
{grey}Exit
')
94 | conn.SendTML(f'{grey}Search Again
')
95 | else:
96 | conn.SendTML(f'
P{grey}rev Page,')
97 | conn.SendTML(f'N{grey}ext Page,')
98 | conn.SendTML(f'{grey}Exit
')
99 | conn.SendTML(f'{grey}Search Again
')
100 | conn.SendTML('
Select:')
101 | sel = _dec(conn.ReceiveStr(keys, 10, False))
102 | if sel.upper() == 'P':
103 | page = max(0,page-1)
104 | if sel.upper() == 'N':
105 | page = min(npodcasts//pcount, page+1)
106 | if sel == '':
107 | conn.SendTML(f'')
108 | break
109 | if sel == back:
110 | conn.SendTML(f'')
111 | return()
112 | if sel.isdigit() and int(sel) < npodcasts:
113 | episodes = getEpisodes(searchRes['results'][int(sel)])
114 | if episodes == False:
115 | conn.SendTML('Service unavailable...')
116 | continue
117 | elif len(episodes) == 0:
118 | conn.SendTML('No episodes...