395 Commits
Author SHA1 Message Date
rich fe26d9e2ed update link at top 2026-07-02 13:06:24 +00:00
rich 831ee9cbae printstat now shows status for both printers, old printstat function preserved as octostat. updated info command and readme as well 2026-02-28 16:39:05 -05:00
rich 545958b474 changing default bot status flip to 7 seconds 2026-02-28 16:24:19 -05:00
rich 367ef5b7af reworking the bot status display when two print jobs are running 2026-02-28 16:01:54 -05:00
rich 2a156b3a3c terminating ffpmeg caputre in event of timeout reached 2026-02-27 16:37:51 -05:00
rich 8ac70c645b updating "3D Print" to OctoPi Print 2026-02-27 14:28:08 -05:00
rich 4bdf61a018 matrix bot will auto join upgraded rooms 2026-02-22 12:13:27 -05:00
rich cd6955a567 readme update 2026-02-21 18:59:14 -05:00
rich eb922b4271 formatting 2026-02-21 17:55:28 -05:00
rich f51025d5f7 prettifying 2026-02-21 17:50:07 -05:00
rich 1f06251ca4 cant send raw 2026-02-21 17:46:17 -05:00
rich 70ea6508e2 fix for printer images 2026-02-21 17:44:32 -05:00
rich e79abf7715 fixing crash after bambustat call in matrix 2026-02-21 17:40:23 -05:00
rich 945e2464f7 what a buggy mess 2026-02-21 17:34:49 -05:00
rich 93b6995554 verify sessions 2026-02-21 17:32:36 -05:00
rich b45f617c73 more fixes 2026-02-21 17:22:22 -05:00
rich d872725af0 change to direct login 2026-02-21 17:18:27 -05:00
rich c18c89ac41 update for e2e 2026-02-21 17:06:20 -05:00
rich 787b0a2fb1 reworking to support e2e rooms 2026-02-21 17:03:44 -05:00
rich 31270db12b debugging 2026-02-21 16:56:17 -05:00
rich a2e1a42fe6 more matrix debugging 2026-02-21 16:45:57 -05:00
rich 1572eabac5 more matrix debugging 2026-02-21 16:41:24 -05:00
rich baf4905beb adding debugging 2026-02-21 16:38:58 -05:00
rich 123aa22abf trying to fix matrix 2026-02-21 16:32:28 -05:00
rich c2490d2159 adding safe handling of sync error 2026-02-21 16:25:32 -05:00
rich 36b8eccb6c initial add of matrix compatibility 2026-02-21 15:15:10 -05:00
rich e104538b5f removing ds_store 2026-02-20 18:20:14 -05:00
rich 516ba09548 mor minor removals for cleanup 2026-02-20 18:19:34 -05:00
rich 6f984b3d4e adding .gitignore 2026-02-20 18:16:07 -05:00
rich bc4854e6f4 updating readme 2026-02-20 18:01:13 -05:00
rich 6362f2558e removing tls_verify option 2026-02-20 17:38:21 -05:00
rich a750628097 adding code to capture ffmpeg errors 2026-02-20 17:35:56 -05:00
rich ce31a2e5df reworking bambu image to get from rtsp stream 2026-02-20 17:27:37 -05:00
rich 570e9d14a6 added option to enable bambu camera retrieval 2026-02-20 16:44:19 -05:00
rich 23fe33e3ad added camera 2026-02-20 16:38:19 -05:00
rich 54c16d81dc fixing the fix for the unconfirmed bug 2026-02-20 16:29:56 -05:00
rich 04d7d5c37f working around unconfirmed bug 2026-02-20 16:27:33 -05:00
rich fbc3ceb3c4 update requirements 2026-02-20 16:23:30 -05:00
rich e1703ae326 adding requirements 2026-02-20 16:19:54 -05:00
rich 65a1fe413d inital commit for adding $bambustat 2026-02-20 16:15:03 -05:00
rich 451938772a more clean up 2026-02-20 15:57:01 -05:00
rich 591d44904e fixing up some issues 2026-02-20 15:48:53 -05:00
rich 84dda24e9d adding discord.py version to $info 2026-02-20 15:00:54 -05:00
rich ebb9865109 reworking 2026-02-20 14:52:12 -05:00
rich e6cf3c6984 fix hanging if octopi is unavailable 2026-02-20 14:47:09 -05:00
rich 01e102af17 clearing voice session before joining channel 2026-02-20 14:42:28 -05:00
rich 790c224dcc using await for 1 second delay 2026-02-20 14:36:51 -05:00
rich 14104b4f39 Discord API now requires INTENTS 2024-03-20 13:29:36 -04:00
rich 1184102860 Added livestream URL to printstat output 2023-02-16 20:13:19 +00:00
rich 4e3d735b1d forgot to replace output variable 2023-02-12 15:47:11 +00:00
rich b074306696 ugh, need ==, not = 2023-02-11 23:13:45 +00:00
rich 0d5fd6c0cf rework time remaining for status 2023-02-11 23:12:34 +00:00
rich 8647451b24 setting to 999 2023-02-11 23:07:41 +00:00
rich c89bfaeff1 retrying leading zero 2023-02-11 19:38:00 +00:00
rich 74f6e0a32c adding leading zero (hopefully) 2023-02-11 19:35:49 +00:00
rich be9173a7ff adding time left to logging output 2023-02-11 19:26:31 +00:00
rich 1666e8fbf9 changeup wording 2023-02-11 19:23:30 +00:00
rich b950886c2d adding time remaining to status 2023-02-11 19:22:10 +00:00
rich 9a627c8a83 remove unused library 2023-02-10 22:51:44 +00:00
rich 9f11775db8 Fix missing paren, add comment explaining need for unknowns 2023-02-10 22:42:44 +00:00
rich d994b9f8fe forgot the f in the f-string 2023-02-10 20:50:50 +00:00
rich a7e5b3bea2 meh, round to tenth place 2023-02-10 20:14:15 +00:00
rich ed2e00ac3c change rounding to whole number 2023-02-10 20:12:53 +00:00
rich 0fd568fd87 minor logging message changes 2023-02-10 20:07:42 +00:00
rich 2801e1559f commenting out few lines that are updating bot status when $printstat is called. No longer need to do this. 2023-02-10 19:56:17 +00:00
rich 3007aa56f5 add message if print job is present but at 100% complete 2023-02-10 19:19:43 +00:00
rich 669333e2c7 dont want to watch print job if it's already at 100% 2023-02-10 19:14:46 +00:00
rich bf88d3d7c1 change location of last_error assignment 2023-02-10 14:48:49 +00:00
rich 3751a29dfb updating logging output 2023-02-10 14:21:23 +00:00
rich 2aae88ed22 forgot await async 2023-02-10 14:19:18 +00:00
rich dc2ea9a7e3 fixing loop 2023-02-10 14:16:43 +00:00
rich 7285c4da85 Adding loop to autodetect 3D printer jobs 2023-02-10 14:09:51 +00:00
rich e0c34659a2 removing references to StatPing and MMR 2023-02-08 23:27:15 +00:00
rich 32bb958ac0 Remove all StatPing and MMR_Checker code 2023-02-08 23:22:36 +00:00
rich a80f62f8d4 fixing some minor bugs 2022-12-10 00:22:15 +00:00
rich a9efb888bd some cleanup and re-org of help command 2022-12-10 00:15:27 +00:00
rich 59fdac5803 added $nice 2022-05-14 00:33:09 +00:00
rich 64173df84d Adding We butter the bread with butter - N!CE 2022-05-14 00:30:00 +00:00
rich 5859bbdac8 updating emote 2022-03-25 01:50:52 +00:00
rich 5b6555b8da adding ascii emote 2022-03-25 01:48:48 +00:00
rich 0e287c369d Added $breee command 2022-03-25 01:36:00 +00:00
rich 98f7ced3bf Adding BREEEEEEEEEEEE 2022-03-25 01:33:06 +00:00
rich dce4303ca1 Merge branch 'testing' into 'dev'
Fixing some stuff & things

See merge request Bots/r0b3BOT!64
2021-04-27 22:36:37 +00:00
rich 1153d1919c fixing indents 2021-04-04 15:01:48 -04:00
rich 499d86459b Merge branch 'testing' of https://gitlab.rickelobe.com/Bots/r0b3BOT into testing 2021-04-04 14:54:55 -04:00
rich c053b58733 not properly handling non jason response 2021-04-04 14:54:38 -04:00
rich e018cfa05b Merge branch 'master' of https://gitlab.rickelobe.com/Bots/r0b3BOT into testing 2021-03-23 19:11:00 -04:00
rich 9f58177559 Merge branch 'dev' into 'master'
Bring in fix for displaying correct version number

See merge request Bots/r0b3BOT!63
2021-03-22 14:40:18 +00:00
rich 58e1781397 correctly fixing the version now (wish the documentation was better) 2021-03-22 00:10:35 +00:00
rich f08eb06db4 Fixing the version information displayed for the info command 2021-03-22 00:06:43 +00:00
rich 807bf17f61 Merge branch 'dev' into 'master'
Merge MMR checker into master for release

See merge request Bots/r0b3BOT!62
2021-03-21 23:42:04 +00:00
rich 3e354d3655 Merge MMR checker into master for release 2021-03-21 23:42:04 +00:00
rich fa38734869 Merge branch 'testing' into 'dev'
Merge MMR Checker into Dev

See merge request Bots/r0b3BOT!61
2021-03-21 23:29:08 +00:00
rich 66ed4fd4d1 Merge MMR Checker into Dev 2021-03-21 23:29:07 +00:00
rich c528e0f638 Added new feature 2021-03-21 17:19:27 +00:00
rich 41b1c73ff3 adding back bot.remove_command entry 2021-03-21 13:10:16 -04:00
rich b1d1a7b1cc Cleanup of code 2021-03-21 13:07:25 -04:00
rich 8bb132be24 Organizing 2021-03-21 12:44:49 -04:00
rich 15d0a01dcb typo 2021-03-21 12:37:56 -04:00
rich ad05b2f427 updating help command details 2021-03-21 12:37:20 -04:00
rich 2cbaef99c0 working on mmrstatus 2021-03-21 12:26:25 -04:00
rich a53cab8343 working on mmrstatus 2021-03-21 12:24:03 -04:00
rich e00da20870 Cleaning up output text 2021-03-21 12:17:00 -04:00
rich 45d8d5702c found typo 2021-03-21 12:11:24 -04:00
rich 86e7944837 editing output for clarity 2021-03-21 12:05:31 -04:00
rich e9a2165f78 found some issues 2021-03-21 11:41:45 -04:00
rich 9421b22cff was using wrong varibale... missed ong 2021-03-21 07:54:10 -04:00
rich 5767d2d606 was using wrong varibale 2021-03-21 07:53:06 -04:00
rich 3b5ecf4b6b more debug 2021-03-21 07:46:56 -04:00
rich d961698d40 typo 2021-03-21 07:45:15 -04:00
rich 37e9512d82 troubleshooting -list command 2021-03-21 07:44:27 -04:00
rich 5e1164d98c typo 2021-03-20 19:25:50 -04:00
rich 1e64e992dd let's try it this way 2021-03-20 19:25:05 -04:00
rich 667126c916 dunno 2021-03-20 19:18:31 -04:00
rich 5ada6cfb36 troubleshooting response check: welp, had that backwards 2021-03-20 19:13:08 -04:00
rich b6d5ce6836 troubleshooting response check: fixing syntax 2021-03-20 19:11:16 -04:00
rich 0077f1a80b troubleshooting response check 2021-03-20 19:09:34 -04:00
rich dfa82dbe96 correcting variable being used 2021-03-20 19:06:59 -04:00
rich ba014f4c98 fixing... 2021-03-20 19:05:27 -04:00
rich 6823161897 fixing... 2021-03-20 19:03:52 -04:00
rich fcb0051c87 fixing 2021-03-20 19:03:15 -04:00
rich 403aa9a362 check if handle was actually found 2021-03-20 19:00:42 -04:00
rich 8d91336b43 Added mmrstatus command 2021-03-20 18:31:41 -04:00
rich 25abfb2a59 updating info command 2021-03-20 18:23:30 -04:00
rich 3f9a976742 clarifying alert text 2021-03-20 17:17:49 -04:00
rich 8f41991a3d fixing assignment again... hopefully for the last time, again 2021-03-20 16:31:09 -04:00
rich 1785162cea fixing assignment again... hopefully for the last time 2021-03-20 16:30:15 -04:00
rich 0207424d32 fixing assignment again 2021-03-20 16:28:50 -04:00
rich ab7009e43f fixing assignment 2021-03-20 16:25:36 -04:00
rich 5c0e61d466 adding newlines 2021-03-20 12:51:43 -04:00
rich 2cabbbf6f1 more tweaks to output 2021-03-20 12:49:19 -04:00
rich 803e8d552d more tweaks to output 2021-03-20 12:48:19 -04:00
rich 0950092f6d more tweaks to output 2021-03-20 12:46:47 -04:00
rich f6e15d9c35 output formatting 2021-03-20 12:38:21 -04:00
rich 5b917e032f fixing minor typo 2021-03-20 12:16:36 -04:00
rich 7628f8d1ae setting variable to int 2021-03-20 12:14:52 -04:00
rich 1e496efbee added interval option for mmr checker, cleaned up some output 2021-03-20 12:11:30 -04:00
rich 0cf348570b tweaks 2021-03-20 11:39:17 -04:00
rich 55419dbafb correcting syntax error 2021-03-20 10:03:45 -04:00
rich cf3757ce11 added enable/disable check before starting mmr checker and statping monitor 2021-03-20 10:02:08 -04:00
rich 4cf45b2684 added enable/disable options for mmr checker and statping monitor 2021-03-20 10:01:34 -04:00
rich 544f7b5833 I think all of the necessary changes have been made to add the mmr checker... needs to test now? 2021-03-19 22:43:02 -04:00
rich fa2a34c951 adding mmr api variable 2021-03-19 22:24:04 -04:00
rich affbf366a1 adding base code for mmr_checker 2021-02-14 21:37:17 -05:00
rich 4056f60cd8 duplicated statping functions for mmr monitor 2021-01-20 21:55:42 -05:00
rich a7a36f123c Merge branch 'dev' of https://gitlab.rickelobe.com/Bots/r0b3BOT into mmr_checker 2021-01-20 21:27:43 -05:00
rich 6865041707 Added missing playtime value for rs command 2020-09-02 09:47:06 -04:00
rich 520758e2f2 Added Wiz Khalifa clip $big 2020-07-01 09:10:58 -04:00
rich cd74624747 added iran sound clip 2020-07-01 08:58:15 -04:00
rich 525f13b3e8 Merge branch 'dev' into 'master'
changing play delay to check if file is still being played

See merge request Bots/r0b3BOT!60
2020-06-22 11:36:18 -04:00
rich c82dc32ca2 changing play delay to check if file is still being played 2020-06-19 11:42:37 -04:00
rich ccb260c3f9 Merge branch 'master' into 'testing'
Make testing master again!

See merge request Bots/r0b3BOT!59
2020-04-15 15:27:09 -04:00
rich fa9fb6b27d Merge branch 'dev' into 'master'
Version 0.3

See merge request Bots/r0b3BOT!58
2020-04-15 15:09:06 -04:00
rich b222b2342e Update README.md - added links to octoprint and statping pages 2020-04-15 15:05:43 -04:00
rich a0d5c63aee moved image 2020-04-15 14:56:58 -04:00
rich 0976c2c9e3 Update README.md 2020-04-15 14:55:40 -04:00
rich 82647b2597 Screenshot of command list 2020-04-15 14:53:58 -04:00
rich e20ac2c849 Update README.md 2020-04-15 14:53:12 -04:00
rich 37a689e58e added screenshot of service alert 2020-04-15 14:46:01 -04:00
rich 79043feb91 Upload screenshot of service alert 2020-04-15 14:44:40 -04:00
rich c2f905229f Updated README.md, listing more features 2020-04-15 14:43:31 -04:00
rich 114e6aeb9b Merge branch 'testing' into 'dev'
Merge changes to fix Issue #8

See merge request Bots/r0b3BOT!57
2020-04-14 22:02:58 -04:00
rich 83f7270d6b updated help message 2020-04-14 21:59:10 -04:00
rich 63602bc19c wasnt saving the result, doh 2020-04-14 21:57:31 -04:00
rich 65a608395b adding code to allow inserting _ in place of spaces and will revert 2020-04-14 21:55:00 -04:00
rich f11c076258 trying removal of print statement 2020-04-14 21:52:41 -04:00
rich 7b7f57f4c0 trying more ' 2020-04-14 21:50:55 -04:00
rich e2c100c5bb removing " from service variable 2020-04-14 21:48:16 -04:00
rich 57e5c37f1a Merge branch 'testing' into 'dev'
changed channel to currentsub_req[1]

See merge request Bots/r0b3BOT!56
2020-04-14 21:24:36 -04:00
rich d53cc3b3f6 changed channel to currentsub_req[1] 2020-04-14 21:18:00 -04:00
rich fc7ef5eb69 Merge branch 'testing' into 'dev'
testing changes ready for dev

See merge request Bots/r0b3BOT!54
2020-04-14 20:48:21 -04:00
rich ccba129bb2 Merge branch '7-info-should-list-a-version-identifier' into 'testing'
Resolve "$info should list a version identifier"

See merge request Bots/r0b3BOT!53
2020-04-14 20:46:45 -04:00
rich d9f52bdafd Merge branch 'testing' into 'dev'
testing of version information completed, merge into dev

See merge request Bots/r0b3BOT!52
2020-04-14 20:40:23 -04:00
rich f0615c51e8 Merge branch '7-info-should-list-a-version-identifier-2' into 'testing'
Resolve "$info should list a version identifier"

See merge request Bots/r0b3BOT!51
2020-04-14 20:37:19 -04:00
rich 6deb4455ed minor console output text change 2020-04-14 20:31:37 -04:00
rich 86d7e414c2 forgot {} 2020-04-14 20:27:40 -04:00
rich 19304b5d27 adding version to info command 2020-04-14 20:23:42 -04:00
rich f87a07f6bd Merge branch 'testing' into 'dev'
Fixed redundant Statping Monitor starts

See merge request Bots/r0b3BOT!50
2020-04-14 13:38:15 -04:00
rich 6b3490557c move start of statping monitor out from bot.on_ready. Think it was being start multiple times when bot reconnected to discord. 2020-04-14 13:38:15 -04:00
rich 006468bd35 Merge branch 'dev' into 'testing'
# Conflicts:
#   r0b3bot.py
2020-04-14 13:37:43 -04:00
rich 2cf516dd1f move notice of statping mon start 2020-04-14 13:29:16 -04:00
rich 0467fde122 missing attribute 2020-04-14 13:27:35 -04:00
rich d9362fb819 rearrange to fix 2020-04-14 13:22:50 -04:00
rich b165e67676 update 2020-04-14 13:20:52 -04:00
rich 1dc54fa5f2 testing output 2020-04-14 13:19:33 -04:00
rich 91063db833 change criteria 2020-04-14 13:15:43 -04:00
rich c9f2d07f4b syntax 2020-04-14 13:12:59 -04:00
rich 290bdc3903 adding debug output 2020-04-14 13:12:27 -04:00
rich 93734ec773 pemdas 2020-04-14 13:11:14 -04:00
rich 37a6b08fe2 changed logic 2020-04-14 13:07:39 -04:00
rich f1e35bae27 added sleep 2020-04-14 12:57:40 -04:00
rich 5c2b426748 moving 2020-04-14 12:56:10 -04:00
rich ed6606f8ac case? 2020-04-14 12:54:30 -04:00
rich be4b46a81d changed to global, though I dont think that's the cause of the issue 2020-04-14 12:52:52 -04:00
rich f0c65454d3 moved start of statping monitor back to on_ready, but am now checking started state variable. 2020-04-14 09:01:19 -04:00
rich 646081286f move start of statping monitor out from bot.on_ready. Think it was being start multiple times when bot reconnected to discord. 2020-04-14 08:47:07 -04:00
rich e36d8727c5 added print statements for sending alerts 2020-04-14 08:39:16 -04:00
rich e8cf0f2c24 Merge branch 'testing' into 'dev'
Testing

See merge request Bots/r0b3BOT!48
2020-04-13 22:42:40 -04:00
rich cd4b3bbb38 Testing 2020-04-13 22:42:40 -04:00
rich 2a5ced954b added new commands to help 2020-04-13 22:39:19 -04:00
rich ea2a76bf81 adding breaks 2020-04-13 22:30:09 -04:00
rich 1be2590f43 adding another break 2020-04-13 22:09:54 -04:00
rich f37fc95ddc added break 2020-04-13 22:07:37 -04:00
rich 2515eb83a5 changed when assigning service name 2020-04-13 22:04:34 -04:00
rich 47e5c364a9 calling wrong variable 2020-04-13 22:00:46 -04:00
rich a03efbd2a9 fix indent 2020-04-13 21:59:24 -04:00
rich d49d368895 saving updated file 2020-04-13 21:58:55 -04:00
rich ba792ff1f5 moving indent out 2020-04-13 21:57:15 -04:00
rich faad238f8c fix wrong vairable assignment 2020-04-13 21:54:43 -04:00
rich cc1368258c fixing typo 2020-04-13 21:53:10 -04:00
rich 7c1b3c207d changing option 2020-04-13 21:51:55 -04:00
rich 8f9af7d10c addd -del option 2020-04-13 21:48:57 -04:00
rich 4ec07fdf77 changed info command prefix to variable 2020-04-13 21:34:45 -04:00
rich 7e9be22734 changed spalert to spstatus 2020-04-13 21:32:31 -04:00
rich d85149e5f1 seriously if this was it... ugh 2020-04-13 21:29:41 -04:00
rich 0e393b089f typo 2020-04-13 21:25:32 -04:00
rich c16fb46b78 does this work? 2020-04-13 21:24:13 -04:00
rich f051fe0087 undo 2020-04-13 21:20:01 -04:00
rich bf2ac43a88 typo 2020-04-13 21:18:50 -04:00
rich 87f82af697 ugh 2020-04-13 21:18:17 -04:00
rich 9036557154 trying a different method 2020-04-13 21:08:32 -04:00
rich c9c67f32c9 typo? 2020-04-13 21:05:53 -04:00
rich cc90375376 using .update to append 2020-04-13 21:05:02 -04:00
rich 6df3f8a55f doing it wrong 2020-04-13 21:01:16 -04:00
rich 0bf0134a86 changin variable 2020-04-13 20:59:54 -04:00
rich 576f21b6fc was not handling when serivce was not present in file 2020-04-13 20:58:36 -04:00
rich 824c952ad9 moved statement outside of loop 2020-04-13 20:54:17 -04:00
rich eeae88595d adding more console output 2020-04-13 20:52:30 -04:00
rich a709f25ea3 added consonle output 2020-04-13 20:48:37 -04:00
rich 4c43ab0e8e added placeholder 2020-04-13 20:44:33 -04:00
rich d97a2d1fe3 need to query before use 2020-04-13 20:43:51 -04:00
rich 10baa139a1 fix statement 2020-04-13 20:39:35 -04:00
rich be04db6237 fixing indents 2020-04-13 20:39:06 -04:00
rich ce319f8696 added spsub- list 2020-04-13 20:38:29 -04:00
rich b26139fdac fixing append of channel id 2020-04-13 20:13:41 -04:00
rich 98cf67381b adding response 2020-04-13 20:07:28 -04:00
rich 132267f5d4 adding more console output, fixed missing ' 2020-04-13 20:05:03 -04:00
rich 1a204d303b fixing iteration 2020-04-13 20:02:47 -04:00
rich f82227ea0d adding notice to channel of duplicate 2020-04-13 19:57:56 -04:00
rich 238bb9f5ea fixing dict creation 2020-04-13 19:55:48 -04:00
rich f026c88817 change how saving channel 2020-04-13 19:00:27 -04:00
rich fa2fb34e41 moved print 2020-04-13 18:56:24 -04:00
rich 85cf5f5ce5 added confirmation message 2020-04-13 18:53:08 -04:00
rich 1e62920925 typo 2020-04-13 18:51:09 -04:00
rich d58c7245e7 clearing up console output 2020-04-13 18:50:25 -04:00
rich 861397b455 fixing append 2020-04-13 18:41:11 -04:00
rich a265de4a62 fixing typo 2020-04-13 18:34:44 -04:00
rich d9883b6f97 fixing iteration 2020-04-13 18:33:45 -04:00
rich 64d80c79ac change to ctx.channel 2020-04-13 18:31:11 -04:00
rich 91def4cd4d working on fixing iterations 2020-04-13 18:26:47 -04:00
rich 6a46983293 changed wording for clarity 2020-04-13 18:23:09 -04:00
rich 0544d870e7 fixed call 2020-04-13 18:22:27 -04:00
rich e1327961b6 fixed 2020-04-13 18:19:44 -04:00
rich 153bc39e63 moved to single line 2020-04-13 18:18:38 -04:00
rich f94f69aa59 change debug 2020-04-13 18:10:38 -04:00
rich 9dfae705e5 now accounting for status not found response 2020-04-13 17:23:42 -04:00
rich 32144c5657 fixed comparison 2020-04-13 17:18:52 -04:00
rich 5235df2259 init status 2020-04-13 17:08:58 -04:00
rich eca6fae897 removed use of new_ 2020-04-13 17:04:48 -04:00
rich 1e58d5017d adding debug 2020-04-13 16:59:14 -04:00
rich 75121c5a79 printing status varibale 2020-04-13 16:28:56 -04:00
rich 71231d3a9b testing variable 2020-04-13 16:24:56 -04:00
rich 3b6c82d6f5 fixed iteration 2020-04-13 16:19:14 -04:00
rich 1fc96f6b3a adding debug print 2020-04-13 16:17:00 -04:00
rich c7216563e6 changing assignment type 2020-04-12 23:56:36 -04:00
rich 4a27acb4ac updated for loop 2020-04-12 23:53:42 -04:00
rich e0d77bf82d forgot closing ] 2020-04-12 23:49:50 -04:00
rich 84e6f8ed6d changing assignment 2020-04-12 23:49:02 -04:00
rich 67309f5925 adding debug output 2020-04-12 23:45:26 -04:00
rich 8480215db9 changing assignment 2020-04-12 23:43:16 -04:00
rich 7bab9a1238 setting protocol to 4 2020-04-12 23:38:56 -04:00
rich e08621d317 saving pickled dict 2020-04-12 23:38:09 -04:00
rich 204acac19c now adding key 2020-04-12 23:35:07 -04:00
rich 077e82bda2 reworked StatPing_Montor to use dict 2020-04-12 23:28:05 -04:00
rich 9af4a52927 reqrote spsub_T to use/create a nested dictionary 2020-04-12 23:11:49 -04:00
rich 2970adc634 using pickle again 2020-04-12 21:08:47 -04:00
rich a95b88d99f no bytes 2020-04-12 20:52:14 -04:00
rich 3832d13200 saving just channel id and now appending 2020-04-12 20:50:38 -04:00
rich ca06fea13f trying different library 2020-04-12 15:28:06 -04:00
rich 5575dbb054 case update 2020-04-12 15:18:45 -04:00
rich 8d02152697 more typo fixes 2020-04-12 15:16:54 -04:00
rich ae601c45f5 typo 2020-04-12 15:15:52 -04:00
rich 7391e68778 replace pickle with aiofile 2020-04-12 15:14:12 -04:00
rich 0f607f67ee more awaits? 2020-04-12 15:04:55 -04:00
rich 142a7fcf48 forgot awaits 2020-04-12 14:59:07 -04:00
rich 2ef6cf6053 Added new test functions to read in spsubs from file 2020-04-12 14:54:35 -04:00
rich dd0dbb0f27 formatting 2020-04-12 11:13:56 -04:00
rich f85a637566 added read in of command prefix and updated bot creation to use config. Also moved bot object creation to after read in of prefix config 2020-04-11 12:20:09 -04:00
rich bb711b8167 fixed missing ' in console output 2020-04-11 12:15:34 -04:00
rich 9f4ee76bb7 added bot_command_prefix to config file 2020-04-11 12:15:13 -04:00
rich 4464d589d9 change notices to embed w/ color 2020-04-10 14:26:09 -04:00
rich de815af682 wording changes 2020-04-10 14:12:33 -04:00
rich 46dc4190a0 added send to channel notifying service is now being monitored 2020-04-10 14:11:10 -04:00
rich c8414ba000 replaced sleep w/ more friendly versions 2020-04-10 14:07:45 -04:00
rich 9eaf1d8029 added some console outputs 2020-04-10 14:02:59 -04:00
rich b26af574ae fixed variable name 2020-04-10 13:24:51 -04:00
rich 51692a17db should probably rework this if elif statements 2020-04-10 13:22:13 -04:00
rich b90b5fc8f4 type 2020-04-10 13:20:13 -04:00
rich 6e0642a11b cant assign when declaring 2020-04-10 13:19:27 -04:00
rich fba8cc680c initial test of alert subscriptions 2020-04-10 13:15:59 -04:00
rich 1547784a40 removed ctx 2020-04-10 12:12:51 -04:00
rich 3b65435899 moving outside of loop 2020-04-10 12:10:57 -04:00
rich bd98d1bdcb more reworking of functions for future needs 2020-04-10 12:08:16 -04:00
rich d946067bd4 forgot await 2020-04-10 11:51:30 -04:00
rich d15267ea53 reworking functions 2020-04-10 11:49:43 -04:00
rich 05b7520a3f help text grammar 2020-04-09 23:56:36 -04:00
rich f8e04ea086 for mendo 2020-04-09 23:50:43 -04:00
rich e594d27beb removed uptime command and added spalert to $help 2020-04-09 23:40:19 -04:00
rich 427937e8d5 added $ hint in status 2020-04-09 23:37:50 -04:00
rich ff189b7357 added reply if service was not found 2020-04-09 23:26:57 -04:00
rich 92542b4405 code cleanup 2020-04-09 23:23:25 -04:00
rich 351fb9c090 commenting out debug 2020-04-09 23:13:28 -04:00
rich af6862dfe5 forgot : 2020-04-09 23:12:52 -04:00
rich 4e562c5c66 reworked if else statements 2020-04-09 23:12:31 -04:00
rich 25d0aad3cb fixed indent 2020-04-09 23:07:35 -04:00
rich 82743a0d66 dump status of all services 2020-04-09 23:06:24 -04:00
rich ce51483344 remove empty send 2020-04-09 23:05:33 -04:00
rich 40806125a5 added send for debug 2020-04-09 22:59:19 -04:00
rich bb33e9f82b moved to beginning of cmd 2020-04-09 22:54:01 -04:00
rich 7fdbcdf951 assigning properly 2020-04-09 22:52:26 -04:00
rich 369b3bf867 didnt mean to remove this 2020-04-09 22:51:11 -04:00
rich b62f864167 change back to break added spalert value to else 2020-04-09 22:49:33 -04:00
rich 5b748dc207 return 2020-04-09 22:45:54 -04:00
rich cbf96086d3 added break in for loop 2020-04-09 22:43:57 -04:00
rich 29b7cd93da .json() 2020-04-09 22:42:44 -04:00
rich 7987fe186a added .json() 2020-04-09 22:39:42 -04:00
rich 3fd8c5de0e removed slash 2020-04-09 22:33:27 -04:00
rich 82fda0be94 added contenty type 2020-04-09 22:32:37 -04:00
rich 3f9cbf8964 commented json load 2020-04-09 22:30:16 -04:00
rich d86f634827 dump to separate line 2020-04-09 22:26:31 -04:00
rich 82b08ce80b extra ) removed 2020-04-09 22:24:56 -04:00
rich 7015ac4f7e dumping json to channel for debug 2020-04-09 22:24:15 -04:00
rich bf8a306870 making global? 2020-04-09 22:20:22 -04:00
rich 2a3fc39da6 loading as json 2020-04-09 22:17:35 -04:00
rich 079770da7b corrected variable name 2020-04-09 22:15:07 -04:00
rich cabf2d3ba8 added await to cmd call 2020-04-09 22:13:09 -04:00
rich 9f8864683d fixed incorrect variable call 2020-04-09 22:09:57 -04:00
rich 56685916f0 fixed typo 2020-04-09 22:08:25 -04:00
rich 991e0f2429 ugh this is shameful, another syntax issue 2020-04-09 22:07:26 -04:00
rich 89235209e1 fixed yet another syntax error else if to elif 2020-04-09 22:06:16 -04:00
rich 66ff3375ff fixed unexpected indent 2020-04-09 22:04:47 -04:00
rich 993adfc196 fixed typo 2020-04-09 22:03:17 -04:00
rich fdbe058c2a Adding ability to query statping for service status 2020-04-09 22:00:04 -04:00
rich 4471e5930d Change server to url 2020-04-09 21:27:41 -04:00
rich 1c7c739942 added statping_server as a config variable 2020-04-07 21:01:19 -04:00
rich 0a7eec666d added comment for statping api key 2020-04-07 17:17:12 -04:00
rich 41c640ca10 Added statping API key variables 2020-04-07 17:12:09 -04:00
rich 2e961db66f added $heavy command 2020-03-30 21:24:09 -04:00
rich c0d817fb69 Added yaw command 2020-03-30 21:01:26 -04:00
rich 6d1b82d3c3 added uptime to $info command 2020-03-30 12:38:22 -04:00
rich 8805587576 add gitlab link to $info command 2020-03-29 19:00:46 -04:00
rich a8e62c5ce9 Merge branch 'dev' into 'master'
Version 0.2

See merge request Bots/r0b3BOT!47
2020-03-29 18:20:14 -04:00
rich dca02881c8 Changed uptime output to WW:DD:HH:MM:SS 2020-03-29 17:39:45 -04:00
rich fabd6efe69 updating help details 2020-03-29 17:28:11 -04:00
rich fe2d9c21d3 added $uptime to help command 2020-03-29 17:21:08 -04:00
rich 582a5c05e6 added week/day/hour... text back 2020-03-29 17:19:10 -04:00
rich 1be37c1b4d replaced minute w/ second for second variable 2020-03-29 17:18:09 -04:00
rich f45e3d654a convert to int 2020-03-29 17:16:51 -04:00
rich 8204d41193 Added uptime command 2020-03-29 17:12:32 -04:00
rich 5f3a1a9f3f increased to 1 second 2020-03-29 16:50:21 -04:00
rich 13d9bad9e6 added half second pause between joining channel and playing sound 2020-03-29 16:48:19 -04:00
rich 1c7e26b26e syntax problemo 2020-03-22 16:02:48 -04:00
rich 5cc9d98475 Setting as global 2020-03-22 15:59:50 -04:00
rich 46d3b7b95c fixed grammar 2020-03-22 15:44:26 -04:00
rich 13ec709616 Added command to display real error message. Also udpated help output. 2020-03-22 15:40:25 -04:00
Cooper Nance f1dc6e2975 Removed Dice Stuff. My Bad. 2020-03-21 19:55:24 -04:00
Cooper Nance 4cf2cb0864 Help Typo 2020-03-21 17:51:48 -04:00
Cooper Nance d34a8e5387 Troubleshooting on Prod. What could go wrong! 2020-03-21 17:48:52 -04:00
Cooper Nance 8814ac1013 Added Help 2020-03-21 17:30:23 -04:00
Cooper Nance e3c197e22b Added Dice Roll. 2020-03-21 17:25:24 -04:00
rich efbdcace68 fixed type 2020-03-17 14:21:03 -04:00
rich 23c0343917 added playtime variable 2020-03-17 14:19:47 -04:00
rich e9a72ae15c extended time 2020-03-17 14:15:26 -04:00
rich ef77651512 added eia command and soundfile 2020-03-17 14:10:03 -04:00
rich e62bc7ea36 Merge branch 'master' into 'dev'
Master

See merge request Bots/r0b3BOT!45
2020-02-28 13:53:28 -05:00
rich bc10fac9f0 Merge branch 'master' of http://gitlab.rickelobe.com/Bots/r0b3BOT 2020-02-28 13:52:15 -05:00
rich 81deee6692 corrected missing var 2020-02-28 13:52:04 -05:00
rich b44075c37d Merge branch 'dev' into 'master'
Dev

See merge request Bots/r0b3BOT!44
2020-02-28 13:45:10 -05:00
rich 99a6d362d5 Added aww cmd to play Awww_Bitch.mp3 2020-02-28 13:43:17 -05:00
rich 4bd4bf56d3 Adding Awww_Bitch.mp3 2020-02-28 13:39:11 -05:00
rich 9be971ce64 changed unknown w/ 999 and added if statement to change status accordingly 2019-12-10 22:14:30 -05:00
rich efc899be71 reverting the if statement outside the while - this seemed to have been working 2019-12-10 22:06:01 -05:00
rich 277c3b3b36 fixing str int error when comparing unknown to 5 2019-12-10 22:03:09 -05:00
rich e4c69be8f2 Merge branch 'dev' into 'master'
Feature Request #4 completed, requesting merge into master

See merge request Bots/r0b3BOT!43
2019-12-06 08:38:21 -05:00
rich 1fe66ee2da Feature Request #4 completed, requesting merge into master 2019-12-06 08:38:21 -05:00
rich 96ea3cf3f3 Merge branch '4-users-are-requesting-a-command-to-display-smudge-in-chat' into 'dev'
changed filename to smudge.png

Closes #4

See merge request Bots/r0b3BOT!42
2019-12-06 08:32:18 -05:00
rich 503117f242 changed filename to smudge.png 2019-12-06 08:31:52 -05:00
rich 57bd92cc7d Merge branch '4-users-are-requesting-a-command-to-display-smudge-in-chat' into 'dev'
shrunk image, added file name, removed comment

Closes #4

See merge request Bots/r0b3BOT!41
2019-12-06 08:28:00 -05:00
rich 298eed22cc shrunk image, added file name, removed comment 2019-12-06 08:27:22 -05:00
rich 5005602292 Merge branch '4-users-are-requesting-a-command-to-display-smudge-in-chat' into 'dev'
fixed misuse of function

Closes #4

See merge request Bots/r0b3BOT!40
2019-12-06 08:17:12 -05:00
rich 66472f0f06 fixed misuse of function 2019-12-06 08:16:13 -05:00
rich 2a79e312be Merge branch '4-users-are-requesting-a-command-to-display-smudge-in-chat' into 'dev'
Resolve "Users are requesting a command to display Smudge in chat"

Closes #4

See merge request Bots/r0b3BOT!39
2019-12-06 08:09:51 -05:00
rich 248e598f77 adding smudge command and image 2019-12-06 08:06:39 -05:00
rich bb265b750e Merge branch 'dev' into 'master'
only deploy when merged w/ dev and master branches

See merge request Bots/r0b3BOT!38
2019-12-05 23:08:38 -05:00
rich 97a71f2bda only deploy when merged w/ dev and master branches 2019-12-05 23:06:49 -05:00
18 changed files with 1250 additions and 141 deletions

No files matched your search

+4
View File
@@ -1 +1,5 @@
.DS_Store
.vscode/launch.json
__pycache__
bot_config.conf
.claude
-6
View File
@@ -1,6 +0,0 @@
stages:
- deploy
deploy:
stage: deploy
script: /var/lib/gitlab-runner/deploy_r0b3bot.sh
+80 -13
View File
@@ -1,19 +1,86 @@
*** This project lives @ https://gitlab.rickelobe.com/Bots/R0b3BOT ***
[![pipeline status](http://gitlab.rickelobe.com/Bots/r0b3BOT/badges/master/pipeline.svg)](http://gitlab.rickelobe.com/Bots/r0b3BOT/commits/master)
# Screenshot
![](images/Discord_Printstat_Command.png)
*** This project lives @ https://git.rickelobe.com/rich/R0b3BOT ***
# Description
This is R0b3's Discord Bot. It's creation was basically a challenge posed a coworker who said "Hey, you can write Discord Bots in Python... Make one to display your 3D printer status!".
This is R0b3's bot, originally built as a Discord bot to display 3D printer status. It now also runs as a Matrix bot, both operating simultaneously in the same process.
# Features
* Query & Monitor OctoPrint 3D Printer Status using the `$octostat` command (Requires that your printer is connected to [Octoprint](https://octoprint.org/))<br>
![](images/Discord_Printstat_Command.png)
* Query & Monitor Bambu Lab Printer Status using the `$bambustat` command (Requires a Bambu Lab printer accessible on the local network via MQTT)
* Displays print state, progress percentage, time remaining, current layer, bed temp, nozzle temp, and chamber temp
* Optionally captures and attaches a camera snapshot from the printer's RTSPS stream (requires [ffmpeg](https://ffmpeg.org/) on the host)
* Updates the bot's Discord status to show live print progress; alternates between OctoPi and Bambu status every 30 seconds when both printers are running simultaneously
* Query both printers at once using the `$printstat` command
* Play a selection of sound clips to voice channel members using the various commands (`$trololo`, `$oops`, `$promoted`, etc) — Discord only
* **Matrix support** — the bot can connect to a self-hosted Matrix homeserver (Synapse/Dendrite) and respond to the same text commands in E2E-encrypted rooms
# Commands
![Screenshot of $help](images/command_list.png)
All text commands are available on both Discord and Matrix. Sound/voice commands are Discord-only.
# Requirements
1. discord.py version 1.2.4 or greater<br>
<code> pip install discord.py </code>
Install all Python dependencies using the provided `requirements.txt`:
```
pip install -r requirements.txt
```
Dependencies:
- `discord.py[voice]` >= 2.6.4 — Discord bot framework (voice support included)
- `requests` >= 2.32.3 — HTTP client for HomeAssistant REST API calls
- `bambu-connect` >= 0.3.1 — Bambu Lab printer integration via local MQTT
- `paho-mqtt` >= 2.0 — MQTT client library (required by bambu-connect)
- `matrix-nio[e2e]` >= 0.21 — Matrix bot client with E2E encryption support (optional, only needed if using Matrix)
**Optional: Camera snapshot support**
To enable camera snapshots with `$bambustat`, [ffmpeg](https://ffmpeg.org/) must be installed on the host running the bot. Set `bambu_camera_enable = true` in `bot_config.conf` once ffmpeg is available.
**Optional: Matrix E2E encryption**
Matrix support with E2E encryption requires the `libolm` system library:
```
apt-get install libolm3 # Debian/Ubuntu
```
# Configuration
1. Create a Discord Bot via Developer tools<br>
2. Enter various configuration parameters in bot_config.conf.example<br>
3. Enter OctoPrint config parameters in octoapi.conf.example<br>
4. Rename *.example files as *.conf<br>
1. Create a Discord Bot via the [Discord Developer Portal](https://discord.com/developers/applications)
2. Copy `bot_config.conf.example` to `bot_config.conf` and fill in your values (see below)
3. Rename `octoapi.conf.example` to `octoapi.conf` and configure OctoPrint settings (if using OctoPrint)
## bot_config.conf Keys
### [bot-config]
| Key | Description |
|-----|-------------|
| `discord_auth_token` | Bot token from the Discord Developer Console |
| `bot_command_prefix` | Command prefix (e.g. `$`, `!`, `/`) |
| `hass_url` | URL to your Home Assistant instance |
| `hass_token` | Long-lived access token for Home Assistant REST API |
| `hass_light` | Entity ID of the light at your printer |
| `octoprint_ip_address` | IP address of your OctoPrint/OctoPi host |
| `bambu_ip_address` | IP address of your Bambu Lab printer on the local network |
| `bambu_access_code` | Access code shown on the printer screen (Settings > WLAN) |
| `bambu_serial_number` | Serial number of your Bambu Lab printer |
| `bambu_camera_enable` | Set to `true` to capture camera snapshots with `$bambustat` (requires ffmpeg) |
| `statping_enable` | Set to `true` to enable StatPing service status integration |
| `statping_url` | URL of your StatPing instance |
| `statping_api_key` | API key for StatPing |
| `mmr_checker_enable` | Set to `true` to enable MMR checker |
| `mmr_checker_interval` | Seconds between MMR checks |
| `mmr_api_url` | MMR API URL |
### [matrix-config] (optional)
Adding this section enables the Matrix bot. All five keys are required to activate it.
| Key | Description |
|-----|-------------|
| `matrix_homeserver` | Full URL of your Matrix homeserver (e.g. `https://matrix.example.org`) |
| `matrix_user_id` | Full Matrix user ID for the bot account (e.g. `@r0b3bot:example.org`) |
| `matrix_password` | Password for the bot account. Used on first run to register the device; credentials are then saved to `matrix_store_path` and reused on subsequent restarts |
| `matrix_room_ids` | Comma-separated list of room IDs to listen in (e.g. `!abc123:example.org,!def456:example.org`) — use room IDs, not aliases |
| `matrix_store_path` | Directory for the E2E crypto store (Olm sessions, device keys). Default: `./matrix_store/` |
+41 -1
View File
@@ -3,14 +3,54 @@
# Replace the placeholders with your information. Do not use quotes.
#
# discord_auth_token - this is the bot token generated via the Discord development console.
# bot_command_prefix - The prefix used identify commands for this bot
# hass_url - URL to your home assistant instance. This is used to turn on the light by your 3d printer
# hass_token - Long Lived Access Token used to authenticate with the REST API for your Home assistant instance
# hass_light - Entity name of the light you want to control
# octoprint_ip_address - IP Address of you OctoPrint (or OctoPi) host. Used to get 3D printer stats.
# octoprint_ip_address - IP Address of you OctoPrint (or OctoPi) host. Used to get 3D printer stats.
# bambu_ip_address - IP Address of your Bambu Lab printer on the local network
# bambu_access_code - Access code displayed on the printer's screen (Settings > WLAN)
# bambu_serial_number - Serial number of your Bambu Lab printer
# bambu_camera_enable - Set to true to capture a camera snapshot with $bambustat via ffmpeg RTSPS stream.
# Requires ffmpeg to be installed on the bot host.
# statping_url - Server hostname of the StatPing instance we will be querying w/ the REST API
# statping_api_key - API Key for Statping to query for service status
#
discord_auth_token = <Discord Auth Token>
bot_command_prefix = <!/$/#/etc>
hass_url = <URL to HomeAssistant>
hass_token = <HomeAssistant Long Lived Access Token>
hass_light = <Entity_ID of the light @ your printer>
octoprint_ip_address = <OctoPrint IP Address>
bambu_ip_address = <Bambu Lab Printer IP Address>
bambu_access_code = <Bambu Lab Access Code>
bambu_serial_number = <Bambu Lab Serial Number>
bambu_camera_enable = false
statping_enable = <trues/false>
statping_url = <Statping URL>
statping_api_key = <Statping API Key>
mmr_checker_enable = <true/false>
mmr_checker_interval = <number of seconds between checks>
mmr_api_url = <MMR API URL>
[matrix-config]
#
# Optional Matrix bot support. All four keys must be present to enable the Matrix bot.
# The bot will join the specified rooms and respond to the same command prefix as Discord.
# Voice/sound commands are Discord-only and will return a friendly message on Matrix.
#
# matrix_homeserver - Full URL of your Matrix homeserver (e.g. https://matrix.example.org)
# matrix_user_id - Full Matrix user ID for the bot account (e.g. @r0b3bot:example.org)
# matrix_password - Password for the bot account. The bot logs in via matrix-nio on first
# start and saves the access token + device ID to matrix_store_path so
# the same Matrix device is reused across restarts (required for E2E).
# matrix_room_ids - Comma-separated list of Matrix room IDs the bot should listen in.
# Use room IDs (!abc123:example.org), not aliases — IDs are stable.
# matrix_store_path - Directory for the E2E crypto store (Olm sessions, device keys).
# Default: ./matrix_store/
#
matrix_homeserver = https://matrix.example.org
matrix_user_id = @r0b3bot:example.org
matrix_password = <Matrix account password>
matrix_room_ids = !abc123:example.org,!def456:example.org
matrix_store_path = ./matrix_store/
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+589
View File
@@ -0,0 +1,589 @@
#!/usr/bin/python3
"""
Matrix bot support for r0b3BOT.
Runs alongside the Discord bot in the same asyncio event loop (via asyncio.gather).
Enabled by adding a [matrix-config] section to bot_config.conf.
Supported commands (same prefix as Discord):
greetings, cat, explain, holyshit, smudge, printstat, bambustat, info, help, last_error
Voice/sound commands respond with a friendly Discord-only message.
Discord embeds are rendered as formatted HTML (org.matrix.custom.html).
"""
import asyncio
import io
import json
import os
import random
import sys
import time
import urllib.request
import octoapi
import requests
from nio import AsyncClient, AsyncClientConfig, JoinError, LoginError, MatrixRoom, MegolmEvent, RoomMessage, RoomMessageText, RoomUpgradeEvent, SyncError, UploadResponse
class MatrixBot:
def __init__(
self,
homeserver,
user_id,
password,
command_prefix,
room_ids,
get_bambu_status,
get_last_error,
hass_url,
hass_token,
hass_light,
octoprint_ip,
bambu_ip,
bambu_access_code,
bambu_camera_enable,
capture_bambu_frame=None,
store_path="./matrix_store/",
):
# Create the crypto store directory if it doesn't exist.
# matrix-nio[e2e] uses this to persist Olm/Megolm sessions and device keys
# across restarts so the bot can decrypt messages in E2E-encrypted rooms.
os.makedirs(store_path, exist_ok=True)
nio_config = AsyncClientConfig(store_sync_tokens=True)
self.client = AsyncClient(homeserver, user_id, store_path=store_path, config=nio_config)
self.prefix = command_prefix
self.room_ids = set(room_ids)
self.password = password
# Credentials file — persists access_token + device_id across restarts so we
# reuse the same Matrix device (and its Olm keys) rather than registering a new
# one on every bot startup.
self.credentials_file = os.path.join(store_path, "r0b3bot_credentials.json")
# Shared state — lambdas that read from r0b3bot.py module-level globals
self._get_bambu_status = get_bambu_status
self._get_last_error = get_last_error
# Configuration
self.hass_url = hass_url
self.hass_token = hass_token
self.hass_light = hass_light
self.octoprint_ip = octoprint_ip
self.bambu_ip = bambu_ip
self.bambu_access_code = bambu_access_code
self.bambu_camera_enable = bambu_camera_enable
self._capture_bambu_frame = capture_bambu_frame
self._start_time = time.time()
async def _login(self):
"""Authenticate with the homeserver.
Tries to restore a previously saved session first (same device_id, same Olm
keys). Falls back to a fresh password login if no credentials are saved or if
the saved token has expired. On a fresh login the credentials are persisted to
disk so the same device is reused on subsequent restarts — this is essential for
E2E encryption: the homeserver and other clients must be able to establish a
stable Olm session with a consistent device.
"""
if os.path.exists(self.credentials_file):
try:
with open(self.credentials_file) as f:
creds = json.load(f)
self.client.access_token = creds["access_token"]
self.client.user_id = creds["user_id"]
self.client.device_id = creds["device_id"]
self.client.load_store()
print(f">> [Matrix] Restored session: {creds['user_id']} / device {creds['device_id']}")
return True
except Exception as e:
print(f" ! [Matrix] Could not restore session ({e}), doing fresh login")
# Remove the stale credentials so we don't retry a broken session
try:
os.remove(self.credentials_file)
except OSError:
pass
# Fresh password login — matrix-nio registers a new device and uploads Olm keys
resp = await self.client.login(self.password, device_name="r0b3bot")
if isinstance(resp, LoginError):
print(f" !! [Matrix] Login failed: {resp.message}")
print(f" !! [Matrix] Check matrix_password in bot_config.conf")
return False
print(f">> [Matrix] Logged in as {self.client.user_id} / device {self.client.device_id}")
self.client.load_store()
# Persist credentials for next restart
with open(self.credentials_file, "w") as f:
json.dump({
"access_token": self.client.access_token,
"user_id": self.client.user_id,
"device_id": self.client.device_id,
}, f, indent=2)
print(f">> [Matrix] Credentials saved to {self.credentials_file}")
return True
async def start(self):
print(">> [Matrix] Starting Matrix bot client")
if not await self._login():
print(" !! [Matrix] Matrix bot will NOT start.")
return
# Upload Olm one-time keys if this is the first run with a fresh Olm account.
# On subsequent restarts with a restored session this is a no-op.
if self.client.should_upload_keys:
print(">> [Matrix] Uploading Olm keys to homeserver")
await self.client.keys_upload()
# Initial sync with no timeout to consume backlog before registering callbacks,
# so we don't replay old messages on startup.
try:
resp = await self.client.sync(timeout=0)
if isinstance(resp, SyncError):
print(f" ! [Matrix] Initial sync error: {resp.message}")
else:
print(f">> [Matrix] Initial sync OK, next_batch={self.client.next_batch!r}")
except Exception as e:
print(f" ! [Matrix] Initial sync exception: {e}")
# Join all configured rooms. The bot account must be a member of a room
# to receive its events in the sync stream — having the room ID in config
# alone is not sufficient.
for room_id in self.room_ids:
resp = await self.client.join(room_id)
if isinstance(resp, JoinError):
print(f" ! [Matrix] Failed to join {room_id}: {resp.message}")
else:
print(f">> [Matrix] Joined (or already in) room {room_id}")
self.client.add_event_callback(self._on_message, RoomMessageText)
self.client.add_event_callback(self._on_any_message, RoomMessage)
self.client.add_event_callback(self._on_encrypted, MegolmEvent)
self.client.add_event_callback(self._on_room_upgrade, RoomUpgradeEvent)
print(">> [Matrix] Listening for commands")
# Use a short poll timeout (5 s) to stay well under any reverse-proxy
# idle timeout. A 30 s long-poll through nginx/caddy will often get a
# 504 which matrix-nio can't parse, causing the "next_batch required"
# loop and silencing all callbacks. 5 s is safe for all common setups.
await self.client.sync_forever(timeout=5000)
# -------------------------------------------------------------------------
# Event handler
# -------------------------------------------------------------------------
async def _on_any_message(self, room: MatrixRoom, event: RoomMessage):
"""DEBUG — fires for any m.room.message msgtype, not just m.text."""
print(f" [Matrix DEBUG RoomMessage] type={type(event).__name__} from {event.sender} in {room.room_id!r}")
async def _on_encrypted(self, room: MatrixRoom, event: MegolmEvent):
"""Fires for encrypted messages the bot can't yet decrypt (missing session key).
Normal on first run — the sender's client needs to establish an Olm session with
the bot's device and share the room key. Sending another message from Element
triggers this automatically. We also send a key request to speed it up."""
print(f" ! [Matrix] Could not decrypt message from {event.sender} in {room.room_id!r} "
f"(session {event.session_id!r} not yet received — send another message to trigger key sharing)")
try:
await self.client.request_room_key(event)
except Exception as e:
print(f" ! [Matrix] Key request failed: {e}")
async def _on_room_upgrade(self, room: MatrixRoom, event: RoomUpgradeEvent):
"""Handle a room upgrade (tombstone). Join the replacement room and swap
the room ID in the active set so commands keep working without a restart.
The old room ID is left in bot_config.conf — update it to the new one."""
new_room_id = event.replacement_room
print(f">> [Matrix] Room {room.room_id} upgraded → {new_room_id}, joining replacement")
resp = await self.client.join(new_room_id)
if isinstance(resp, JoinError):
print(f" ! [Matrix] Failed to join upgraded room {new_room_id}: {resp.message}")
return
self.room_ids.discard(room.room_id)
self.room_ids.add(new_room_id)
print(f">> [Matrix] Now listening in {new_room_id}")
print(f" ! [Matrix] Update matrix_room_ids in bot_config.conf: replace {room.room_id} with {new_room_id}")
async def _on_message(self, room: MatrixRoom, event: RoomMessageText):
# DEBUG — remove once commands are confirmed working
print(f" [Matrix DEBUG] event from {event.sender} in {room.room_id!r} body={event.body!r}")
print(f" [Matrix DEBUG] configured room_ids={self.room_ids}")
# Ignore own messages
if event.sender == self.client.user_id:
print(" [Matrix DEBUG] ignored: own message")
return
# Only respond in configured rooms
if room.room_id not in self.room_ids:
print(f" [Matrix DEBUG] ignored: room_id not in configured list")
return
# Only respond to messages that start with the command prefix
if not event.body.startswith(self.prefix):
print(f" [Matrix DEBUG] ignored: no command prefix {self.prefix!r}")
return
body = event.body[len(self.prefix):]
parts = body.split()
if not parts:
return
command = parts[0].lower()
print(f">> [Matrix] {event.sender} in {room.room_id} called {self.prefix}{command}")
await self._dispatch(room.room_id, command)
# -------------------------------------------------------------------------
# Command dispatcher
# -------------------------------------------------------------------------
_SOUND_COMMANDS = {
"aww", "bitch", "big", "boom", "breee", "cowbell", "eia", "heavy",
"iran", "leeroy", "nice", "oops", "promoted", "rs", "trololo",
"wow", "yaw",
}
async def _dispatch(self, room_id, command):
handlers = {
"greetings": self._cmd_greetings,
"cat": self._cmd_cat,
"explain": self._cmd_explain,
"holyshit": self._cmd_holyshit,
"smudge": self._cmd_smudge,
"printstat": self._cmd_printstat,
"bambustat": self._cmd_bambustat,
"info": self._cmd_info,
"help": self._cmd_help,
"last_error": self._cmd_last_error,
}
if command in handlers:
await handlers[command](room_id)
elif command in self._SOUND_COMMANDS:
await self._send_text(
room_id,
"Sound commands are only available on Discord (voice channels)."
)
# -------------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------------
async def _share_session(self, room_id):
"""Share the Megolm outbound group session with all current room members.
Must be called before every room_send() in encrypted rooms. This establishes
Olm sessions with each member's devices (claiming one-time keys as needed) and
sends them the Megolm session key so they can decrypt our messages.
ignore_unverified_devices=True lets the bot still send even when some devices
have unverifiable keys (e.g. stale bot devices with mismatched Olm keys).
"""
try:
await self.client.share_group_session(room_id, ignore_unverified_devices=True)
except Exception as e:
print(f" ! [Matrix] share_group_session for {room_id}: {e}")
async def _send_text(self, room_id, text, html=None):
await self._share_session(room_id)
content = {"msgtype": "m.text", "body": text}
if html:
content["format"] = "org.matrix.custom.html"
content["formatted_body"] = html
await self.client.room_send(room_id, "m.room.message", content)
async def _send_image(self, room_id, file_path):
await self._share_session(room_id)
with open(file_path, "rb") as f:
data = f.read()
# Encrypted rooms require the attachment itself to be encrypted before upload.
# matrix-nio handles this via encrypt=True on upload(), which returns the key
# info needed to build the m.image "file" field so clients can decrypt it.
room = self.client.rooms.get(room_id)
encrypt = room is not None and room.encrypted
resp, keys = await self.client.upload(
io.BytesIO(data),
content_type="image/jpeg",
filename=os.path.basename(file_path),
encrypt=encrypt,
)
if not isinstance(resp, UploadResponse):
print(f" ! [Matrix] Image upload failed: {resp}")
return
if encrypt and keys:
content = {
"msgtype": "m.image",
"body": os.path.basename(file_path),
"file": {"url": resp.content_uri, **keys},
}
else:
content = {
"msgtype": "m.image",
"url": resp.content_uri,
"body": os.path.basename(file_path),
"info": {"mimetype": "image/jpeg"},
}
await self.client.room_send(room_id, "m.room.message", content)
def _format_html(self, title, description, fields):
"""Render a status block as a heading + two-column table."""
rows = "".join(
f"<tr><td><strong>{name}</strong></td><td>{value}</td></tr>"
for name, value in fields
)
desc_html = f"<p>{description}</p>" if description else ""
return f"<h4>{title}</h4>{desc_html}<table>{rows}</table>"
def _format_text(self, title, description, fields):
"""Plain-text fallback — pad label column for alignment."""
max_len = max((len(name) for name, _ in fields), default=0)
parts = [title]
if description:
parts.append(description)
for name, value in fields:
parts.append(f" {name:<{max_len}} {value}")
return "\n".join(parts)
# -------------------------------------------------------------------------
# Command implementations
# -------------------------------------------------------------------------
async def _cmd_greetings(self, room_id):
await self._send_text(room_id, "Hello there!")
async def _cmd_cat(self, room_id):
await self._send_text(room_id, "https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif")
async def _cmd_explain(self, room_id):
await self._send_text(room_id, "https://media.giphy.com/media/A8NNZlVuA1LoY/giphy.gif")
async def _cmd_holyshit(self, room_id):
await self._send_text(room_id, "https://media.giphy.com/media/QcGTswMKyBNgA/giphy.gif")
async def _cmd_smudge(self, room_id):
print(" - [Matrix] Uploading Smudge to Matrix room")
await self._send_image(room_id, "./images/smudge.png")
async def _cmd_last_error(self, room_id):
await self._send_text(room_id, f"Last error recorded: {self._get_last_error()}")
async def _cmd_info(self, room_id):
# Uptime
elapsed = time.time() - self._start_time
m, s = divmod(int(elapsed), 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
w, d = divmod(d, 7)
uptime = f"{w:02d}:{d:02d}:{h:02d}:{m:02d}:{s:02d}"
# Git version tag
try:
proc = await asyncio.create_subprocess_exec(
"git", "describe", "--tags",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5)
bot_version = stdout.decode().strip() or "unknown"
except Exception:
bot_version = "unknown"
source_url = "https://gitlab.rickelobe.com/Bots/r0b3BOT"
fields = [
("Author", "R0b3"),
("Uptime [WW:DD:HH:MM:SS]", uptime),
("Version", bot_version),
("Python", sys.version.split()[0]),
("Source", f'<a href="{source_url}">{source_url}</a>'),
]
await self._send_text(
room_id,
self._format_text("r0b3BOT — Derpy Derp of a Bot", None, fields),
html=self._format_html("r0b3BOT — Derpy Derp of a Bot", None, fields),
)
async def _cmd_help(self, room_id):
p = self.prefix
commands = [
(f"{p}greetings", "Hello!"),
(f"{p}cat", "GIF"),
(f"{p}explain", "GIF"),
(f"{p}holyshit", "GIF"),
(f"{p}smudge", "Sends the Smudge image"),
(f"{p}printstat", "OctoPrint status + snapshot"),
(f"{p}bambustat", "Bambu Lab printer status + camera"),
(f"{p}info", "Bot info"),
(f"{p}last_error", "Last recorded error"),
]
rows = "".join(
f"<tr><td><code>{cmd}</code></td><td>{desc}</td></tr>"
for cmd, desc in commands
)
html = (
f"<h4>r0b3BOT Commands</h4>"
f"<table>{rows}</table>"
f"<p><em>Sound commands are Discord-only.</em></p>"
)
text_lines = [f" {cmd:<18} {desc}" for cmd, desc in commands]
text = "r0b3BOT Commands\n" + "\n".join(text_lines) + "\n (Sound commands are Discord-only)"
await self._send_text(room_id, text, html=html)
async def _cmd_printstat(self, room_id):
loop = asyncio.get_running_loop()
hass_headers = {
"Authorization": f"Bearer {self.hass_token}",
"content-type": "application/json",
}
hass_state_url = self.hass_url + "/api/states/" + self.hass_light
# Light control — same logic as the Discord version
turned_on_light = False
try:
resp = await loop.run_in_executor(
None, lambda: requests.get(hass_state_url, headers=hass_headers)
)
lights = resp.json()
if lights.get("state") == "off":
turned_on_light = True
await self._send_text(room_id, "Turning on a light, give me a moment...")
hass_on_url = self.hass_url + "/api/services/switch/turn_on"
payload = f'{{"entity_id": "{self.hass_light}"}}'
await loop.run_in_executor(
None, lambda: requests.post(hass_on_url, headers=hass_headers, data=payload)
)
await asyncio.sleep(3)
except Exception as e:
print(f" ! [Matrix] Hass light check failed: {e}")
await self._send_text(room_id, "Hmm, I can't tell if the light is on... oh well [Hass API error]")
# Capture webcam snapshot
file_name = str(random.randrange(1, 10000)) + ".jpg"
try:
print(" - [Matrix] Getting image from Webcam")
await loop.run_in_executor(
None,
lambda: urllib.request.urlretrieve(
f"http://{self.octoprint_ip}:8080/?action=snapshot", file_name
),
)
await self._send_image(room_id, file_name)
os.remove(file_name)
except Exception as e:
print(f" ! [Matrix] Webcam capture failed: {e}")
await self._send_text(room_id, f"Unable to capture snapshot: {e}")
# Turn the light back off if we turned it on
if turned_on_light:
try:
hass_off_url = self.hass_url + "/api/services/switch/turn_off"
payload = f'{{"entity_id": "{self.hass_light}"}}'
await loop.run_in_executor(
None, lambda: requests.post(hass_off_url, headers=hass_headers, data=payload)
)
except Exception as e:
print(f" ! [Matrix] Unable to turn off light: {e}")
# Fetch printer stats from OctoPrint
try:
operational = await loop.run_in_executor(None, octoapi.get_printer_dict)
except Exception:
operational = False
if operational:
try:
print_filename = await loop.run_in_executor(None, octoapi.get_printFileName)
except Exception:
print_filename = ""
try:
print_completion = round(
await loop.run_in_executor(None, octoapi.get_completion), 2
)
except Exception:
print_completion = "Unknown"
try:
print_seconds = await loop.run_in_executor(None, octoapi.get_printTime)
print_secondsleft = await loop.run_in_executor(None, octoapi.get_printTimeLeft)
print_hours = int(print_seconds / 3600)
print_min = int((print_seconds / 60) - (print_hours * 60))
print_hoursleft = int(print_secondsleft / 3600)
print_minleft = int((print_secondsleft / 60) - (print_hoursleft * 60))
time_elapsed = f"{print_hours}h {print_min}m"
time_remaining = f"{print_hoursleft}h {print_minleft}m"
except Exception:
time_elapsed = time_remaining = "Unknown"
printer_bed = f"{operational['temperature']['bed']['actual']}°C"
printer_hotend = f"{operational['temperature']['tool0']['actual']}°C"
fields = [
("Progress", f"{print_completion}%"),
("Elapsed", time_elapsed),
("Remaining", time_remaining),
("Bed Temp", printer_bed),
("Hotend", printer_hotend),
]
await self._send_text(
room_id,
self._format_text("R0b3's 3D Printer Status", f"Current Job: {print_filename}", fields),
html=self._format_html("R0b3's 3D Printer Status", f"Current Job: {print_filename}", fields),
)
else:
await self._send_text(room_id, "Printer is Offline")
async def _cmd_bambustat(self, room_id):
status = self._get_bambu_status()
if status is None:
await self._send_text(
room_id,
"Bambu printer status is not available yet. The printer may be offline or still connecting.",
)
return
gcode_state = status.gcode_state or "Unknown"
gcode_file = status.gcode_file or "Unknown"
mc_percent = status.mc_percent if status.mc_percent is not None else "Unknown"
remaining_mins = status.mc_remaining_time or 0
time_remaining = f"{remaining_mins // 60}h {remaining_mins % 60:02d}m"
nozzle_temp = (
f"{status.nozzle_temper:.1f}°C" if status.nozzle_temper is not None else "Unknown"
)
bed_temp = (
f"{status.bed_temper:.1f}°C" if status.bed_temper is not None else "Unknown"
)
chamber_temp = (
f"{status.chamber_temper:.1f}°C" if status.chamber_temper is not None else "Unknown"
)
layer_info = "Unknown"
if status.layer_num is not None and status.total_layer_num is not None:
layer_info = f"{status.layer_num} / {status.total_layer_num}"
fields = [
("State", gcode_state),
("Progress", f"{mc_percent}%"),
("Remaining", time_remaining),
("Layer", layer_info),
("Nozzle", nozzle_temp),
("Bed", bed_temp),
("Chamber", chamber_temp),
]
await self._send_text(
room_id,
self._format_text("Bambu Lab Printer Status", f"File: {gcode_file}", fields),
html=self._format_html("Bambu Lab Printer Status", f"File: {gcode_file}", fields),
)
# Optional camera snapshot via ffmpeg RTSPS
if self.bambu_camera_enable and self._capture_bambu_frame:
image_filename = f"bambu_{random.randrange(1, 10000)}.jpg"
try:
print(" - [Matrix] Capturing Bambu camera frame via RTSPS")
success = await self._capture_bambu_frame(image_filename)
if success:
await self._send_image(room_id, image_filename)
os.remove(image_filename)
else:
print(" ! [Matrix] ffmpeg exited with non-zero code, skipping image")
except asyncio.TimeoutError:
print(" ! [Matrix] ffmpeg timed out capturing Bambu camera frame")
except Exception as e:
print(f" ! [Matrix] Unable to capture Bambu camera frame: {e}")
+518 -121
View File
@@ -1,23 +1,42 @@
#!/usr/bin/python3
import discord
from discord.ext import commands
from discord.ext import commands, tasks
from datetime import datetime
import urllib.request
import random
import os
import sys
import octoapi
import time
from datetime import datetime
import configparser
import json
from discord import FFmpegPCMAudio
from discord.utils import get
from discord.voice_client import VoiceClient
import requests
import asyncio
from bambu_connect import BambuClient
bot = commands.Bot(command_prefix='$', description='A derpy derp of a bot.')
# Monkey-patch every dataclass in bambu-connect's models module to tolerate
# unknown fields. The Bambu printer firmware sends extra keys that the
# bambu-connect 0.3.1 models don't define (e.g. Upload.file_size,
# Online.ext), causing TypeErrors that crash the MQTT thread.
# Filtering kwargs to known fields before calling the original __init__
# fixes this for all nested classes at once.
import dataclasses as _dc
import inspect as _inspect
from bambu_connect.utils import models as _bambu_models
def _make_permissive(cls):
known = {f.name for f in _dc.fields(cls)}
orig = cls.__init__
def __init__(self, **kwargs):
orig(self, **{k: v for k, v in kwargs.items() if k in known})
cls.__init__ = __init__
for _name, _cls in _inspect.getmembers(_bambu_models, _inspect.isclass):
if _dc.is_dataclass(_cls):
_make_permissive(_cls)
#Record the time the bot started
start_time = time.time()
config = configparser.RawConfigParser()
configFilePath = r'bot_config.conf'
@@ -27,7 +46,18 @@ try:
except:
print("Error loading config! Please check config file 'bot-config.conf'")
# Setup HomeAssistant URL and Headers
# Last error message variable used by $last_error command to display in discord
last_error_msg = "No errors have been recorded."
#
# Assign bot config from setting in config file
#
# Assign Bot core settings
DISCORD_AUTH_TOKEN = config.get('bot-config', 'discord_auth_token')
BOT_COMMAND_PREFIX = config.get('bot-config', 'bot_command_prefix')
# 3D Printer Setup - HomeAssistant URL and Headers & OctoPrint
HASS_TOKEN = config.get('bot-config', 'hass_token')
HASS_URL = config.get('bot-config', 'hass_url')
HASS_LIGHT = config.get('bot-config', 'hass_light')
@@ -36,28 +66,187 @@ hassHEADERS = {
'Authorization': f"Bearer {HASS_TOKEN}",
'content-type': 'application/json',
}
OCTOPRINT_IP_ADDRESS = config.get('bot-config', 'octoprint_ip_address')
DISCORD_AUTH_TOKEN = config.get('bot-config', 'discord_auth_token')
# Bambu Lab Printer config (optional — feature is skipped if not set)
BAMBU_IP_ADDRESS = config.get('bot-config', 'bambu_ip_address', fallback=None)
BAMBU_ACCESS_CODE = config.get('bot-config', 'bambu_access_code', fallback=None)
BAMBU_SERIAL_NUMBER = config.get('bot-config', 'bambu_serial_number', fallback=None)
BAMBU_CAMERA_ENABLE = config.get('bot-config', 'bambu_camera_enable', fallback='false').lower() == 'true'
# Matrix bot config (optional — all four keys required to enable)
MATRIX_HOMESERVER = config.get('matrix-config', 'matrix_homeserver', fallback=None)
MATRIX_USER_ID = config.get('matrix-config', 'matrix_user_id', fallback=None)
MATRIX_PASSWORD = config.get('matrix-config', 'matrix_password', fallback=None)
_matrix_room_ids_raw = config.get('matrix-config', 'matrix_room_ids', fallback=None)
MATRIX_ROOM_IDS = [r.strip() for r in _matrix_room_ids_raw.split(',')] if _matrix_room_ids_raw else []
MATRIX_STORE_PATH = config.get('matrix-config', 'matrix_store_path', fallback='./matrix_store/')
# Module-level cache for Bambu printer status, updated by the MQTT watch callback
bambu_status = None
bambu_client = None
_status_slot = 0 # 0 = OctoPi, 1 = Bambu; toggled when both are printing
def _on_bambu_update(status):
"""Callback invoked by bambu-connect's MQTT thread with the latest PrinterStatus."""
global bambu_status
bambu_status = status
# Configure bot
bot = commands.Bot(intents=discord.Intents.all(),command_prefix=BOT_COMMAND_PREFIX, description='A derpy derp of a bot.')
bot.remove_command('help')
# Load opus once at startup rather than on every sound command
discord.opus.load_opus("libopus.so")
# Printing configuration details to console
print(f"HomeAssistant URL: {hassURL}")
print(f"HomeAssistant URL HEADERS: {hassHEADERS}")
print(f"HomeAssistant Light: {HASS_LIGHT}")
print(f"OctoPrint IP Address: {OCTOPRINT_IP_ADDRESS}")
print("=== 3D Printer Status config ===")
print(f" HomeAssistant URL: {hassURL}")
print(f" HomeAssistant URL HEADERS: {hassHEADERS}")
print(f" HomeAssistant Light: {HASS_LIGHT}")
print(f" OctoPrint IP Address: {OCTOPRINT_IP_ADDRESS}")
async def _update_bambu_activity(status):
remaining_mins = status.mc_remaining_time or 0
remaining_hours = remaining_mins // 60
remaining_mins_part = remaining_mins % 60
activity = discord.Activity(
name=f"Bambu @ {status.mc_percent}%, {remaining_hours}h{remaining_mins_part:02d}m to go",
type=discord.ActivityType.watching,
)
await bot.change_presence(activity=activity)
async def _capture_bambu_frame(filename):
"""Capture a single JPEG frame from the Bambu RTSPS stream via ffmpeg.
Returns True on success, raises on timeout or ffmpeg error."""
rtsp_url = f"rtsps://bblp:{BAMBU_ACCESS_CODE}@{BAMBU_IP_ADDRESS}/streaming/live/1"
proc = await asyncio.create_subprocess_exec(
"ffmpeg", "-y",
"-rtsp_transport", "tcp",
"-i", rtsp_url,
"-vframes", "1",
"-q:v", "2",
filename,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.PIPE,
)
try:
_, stderr = await asyncio.wait_for(proc.communicate(), timeout=30)
except asyncio.TimeoutError:
proc.kill()
_, stderr = await proc.communicate()
print(f" ! ffmpeg stderr on timeout: {stderr.decode(errors='replace').strip()}")
raise
if proc.returncode != 0:
print(f" ! ffmpeg stderr: {stderr.decode(errors='replace').strip()}")
return proc.returncode == 0
async def _reset_bot_activity():
activity = discord.Activity(
name=f"your {BOT_COMMAND_PREFIX}commands",
type=discord.ActivityType.listening,
)
await bot.change_presence(activity=activity)
async def _set_octo_activity(completion):
"""Update Discord presence with the current OctoPrint job status."""
loop = asyncio.get_running_loop()
try:
secs_left = await loop.run_in_executor(None, octoapi.get_printTimeLeft)
hours_left = int(secs_left / 3600)
mins_left = int((secs_left % 3600) / 60)
time_left = f"{hours_left:02d}:{mins_left:02d}"
except Exception:
time_left = "??"
activity = discord.Activity(
name=f"OctoPi @ {completion}%, {time_left} to go",
type=discord.ActivityType.watching,
)
await bot.change_presence(activity=activity)
# Unified background task: polls both printers every 30s and updates presence,
# alternating between them fairly when both are printing simultaneously.
@tasks.loop(seconds=7)
async def update_presence():
global _status_slot
loop = asyncio.get_running_loop()
# Check OctoPrint
try:
completion = round(await loop.run_in_executor(None, octoapi.get_completion), 1)
octo_printing = completion < 100
except Exception:
completion = None
octo_printing = False
# Check Bambu (from MQTT cache updated by _on_bambu_update)
bambu_printing = bambu_status is not None and bambu_status.gcode_state == "RUNNING"
print(f">> Presence update — OctoPi printing: {octo_printing}, Bambu printing: {bambu_printing}")
if octo_printing and bambu_printing:
# Both printing: alternate each tick for equal representation
_status_slot = 1 - _status_slot
if _status_slot == 0:
await _set_octo_activity(completion)
else:
await _update_bambu_activity(bambu_status)
elif octo_printing:
_status_slot = 0
await _set_octo_activity(completion)
elif bambu_printing:
_status_slot = 1
await _update_bambu_activity(bambu_status)
else:
await _reset_bot_activity()
# On Ready
@bot.event
async def on_ready():
print("\n:::: Config Loaded ::::\n")
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
print("Setting activity to 'Listenting to your commands")
activity = discord.Activity(name="your commands",type=discord.ActivityType.listening)
print(f" User: {bot.user.name}")
print(f" ID: {bot.user.id}")
print(f"\n\n")
print(f"Setting activity to 'Listenting to your {BOT_COMMAND_PREFIX}commands'")
activity = discord.Activity(name=f"your {BOT_COMMAND_PREFIX}commands",type=discord.ActivityType.listening)
await bot.change_presence(activity=activity)
print(">> Startup complete.")
# Clear any stale voice states left over from a previous session.
# Discord holds onto the bot's voice state across restarts; if not cleared,
# the first voice connect attempt gets a 4006 (session no longer valid).
print(">> Clearing stale voice states across all guilds")
for guild in bot.guilds:
try:
await guild.change_voice_state(channel=None)
except Exception as e:
print(f" ! Could not clear voice state for {guild.name}: {e}")
# Start the unified presence task (guard against double-start on reconnects)
if not update_presence.is_running():
update_presence.start()
# Start the Bambu watch client if config is present
global bambu_client
if BAMBU_IP_ADDRESS and BAMBU_ACCESS_CODE and BAMBU_SERIAL_NUMBER:
if bambu_client is None:
print(">> Connecting to Bambu printer MQTT watch client")
bambu_client = BambuClient(BAMBU_IP_ADDRESS, BAMBU_ACCESS_CODE, BAMBU_SERIAL_NUMBER)
bambu_client.start_watch_client(_on_bambu_update)
print(">> Bambu watch client started")
else:
print(">> Bambu printer config not found, skipping Bambu watch client")
@bot.command()
async def greetings(ctx):
await ctx.send(":smiley: :wave: Hello there!")
@@ -65,11 +254,13 @@ async def greetings(ctx):
@bot.event
async def on_command_error(ctx, error):
global last_error_msg
last_error_msg = error
if isinstance(error, commands.CommandOnCooldown):
seconds = round(error.retry_after,1)
print(f" - Error, user tried to use command while in cooldown (wait is: {seconds}")
print(f" - Error, user tried to use command while in cooldown (wait is: {seconds})")
await ctx.send(f"This command is ratelimited per user, please try again in {seconds}s")
if isinstance(error, commands.CommandInvokeError):
elif isinstance(error, commands.CommandInvokeError):
print(" - ERROR: encountered 'CommandInvokeError'")
print(f" - Dumping error: {error}")
await ctx.send("Error, unable to complete your request.")
@@ -77,6 +268,12 @@ async def on_command_error(ctx, error):
print('Oopsie, I found an error...')
print(f"Error: {error}")
#
# Debug commands
#
@bot.command()
async def last_error(ctx):
await ctx.send(f"Last error recorded: {last_error_msg}")
#
# Giphy Commands
@@ -95,10 +292,18 @@ async def holyshit(ctx):
###
#
# Image Commands
#
@bot.command()
async def smudge(ctx):
print(" - Uploading Smudge to chat channel")
file = discord.File("./images/smudge.png", filename="smudge.png")
await ctx.send(file=file)
#
# Voice Commands
#
@bot.command()
@commands.cooldown(rate=1, per=30.0, type=commands.BucketType.user)
async def bitch(ctx, member : discord.Member="NONE"):
@@ -151,6 +356,50 @@ async def promoted(ctx, member : discord.Member="NONE"):
async def wow(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/wow.mp3", "$wow")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def aww(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/Awww_Bitch.mp3", "$aww")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def eia(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/awesome.mp3", "$eia")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def yaw(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/yaw.mp3", "$yaw")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def heavy(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/heavy.mp3", "$heavy")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def iran(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/iran.mp3", "$iran")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def big(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/big.mp3", "$big")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def breee(ctx, member : discord.Member="NONE"):
await ctx.send(f"\m/ ( ゚Д゚)<BREEEEE!! \m/")
await play_sound(ctx, member, "./sounds/wbbwb_metal.mp3", "$breee")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def nice(ctx, member : discord.Member="NONE"):
await ctx.send(f"\m/ ( ゚Д゚)<N!CE! \m/")
await play_sound(ctx, member, "./sounds/wbtbwb-nice.mp3", "$nice")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def rs(ctx, member : discord.Member="NONE"):
@@ -165,8 +414,6 @@ async def rs(ctx, member : discord.Member="NONE"):
async def play_sound(ctx, member : discord.Member, soundFile, command):
discord.opus.load_opus("libopus.so")
datestring = datetime.now()
datestring = datestring.strftime("%m/%d/%Y-%H:%M:%S")
@@ -177,7 +424,7 @@ async def play_sound(ctx, member : discord.Member, soundFile, command):
if member != "NONE":
try:
channel = member.voice.channel
except:
except AttributeError:
await ctx.send(f"{member.display_name} is not in a voice channel!")
print(f" ! {member.display_name} is not in a voice channel")
return
@@ -187,7 +434,7 @@ async def play_sound(ctx, member : discord.Member, soundFile, command):
if member == "NONE":
try:
channel = ctx.message.author.voice.channel
except:
except AttributeError:
await ctx.send("You are not connected to a voice channel.")
print("User is not connected to a voice channel")
return
@@ -205,13 +452,17 @@ async def play_sound(ctx, member : discord.Member, soundFile, command):
else:
print(f" - Joining Channel '{channel}''")
await ctx.send(f"Joining '{channel}' for just a moment")
voice = await channel.connect()
# reconnect=False disables discord.py's internal retry loop so it doesn't
# hammer the same invalid session repeatedly (which was causing "attempt 4").
voice = await channel.connect(reconnect=False)
print(" - Creating Player")
audio_source = discord.FFmpegPCMAudio(soundFile)
print(f" - Playing {soundFile}")
await asyncio.sleep(1)
voice.play(audio_source, after=None)
time.sleep(5)
while voice.is_playing():
await asyncio.sleep(1)
print(f" - Disconnecting from '{channel}'")
await voice.disconnect()
###
@@ -220,32 +471,29 @@ async def play_sound(ctx, member : discord.Member, soundFile, command):
# OctoPrint Commands
#
@bot.command()
async def printstat(ctx):
async def octostat(ctx):
datestring = datetime.now()
datestring = datestring.strftime("%m/%d/%Y-%H:%M:%S")
print(f"[{datestring}]: {ctx.message.author.display_name} called $printstat")
print(f"[{datestring}]: {ctx.message.author.display_name} called $octostat")
#print("========== Command Called at %s ==========" % (time.strftime("%Y%m%d-%H%M%S")))
# We haven't yet changed the state of the light, setting this to False
turned_on_light = False
# No Data from Printer?
operational = True
# Try to get the status of the 3D printer light
print(" - Getting Printer Light Status")
work_lights = requests.get(hassURL, headers=hassHEADERS)
print(f" JSON Response from HomeAssistant: {work_lights.text}")
loop = asyncio.get_running_loop()
work_lights_resp = await loop.run_in_executor(None, lambda: requests.get(hassURL, headers=hassHEADERS))
print(f" JSON Response from HomeAssistant: {work_lights_resp.text}")
work_lights = json.loads(work_lights.text)
work_lights = json.loads(work_lights_resp.text)
# Making sure that the API request succeeded. If it has, there will be a state attribute added to
# work_lights.
# work_lights.
try:
work_lights["state"]
except:
except KeyError:
print(" ! Unable to get light status from Homeassistant, notifying chat channel")
await ctx.send("Hmm, I can't tell if the light is on... oh well [Hass API error]")
@@ -267,19 +515,17 @@ async def printstat(ctx):
await ctx.send(f"{random.choice(lightMessages)}")
try:
# REST API call to home assistant to turn the light off.
# REST API call to home assistant to turn the light on.
hassAPIURL = HASS_URL + "/api/services/switch/turn_on"
payload = f'{{"entity_id": "{HASS_LIGHT}"}}'
#print(f" - Payload String: {payload}")
response = requests.post(hassAPIURL, headers=hassHEADERS, data=payload)
#print(f" - POST Response: {response.text}")
await loop.run_in_executor(None, lambda: requests.post(hassAPIURL, headers=hassHEADERS, data=payload))
except Exception as e:
print(f" ! Unable to turn on light; ERROR: {e}")
await ctx.send("Sorry, I was unable to turn on the light.")
pass
time.sleep(3)
await asyncio.sleep(3)
# Randomly generate a filename to save the image to
file_name = random.randrange(1,10000)
@@ -287,7 +533,8 @@ async def printstat(ctx):
# Get an image from OctoPrint and save it
print(" - Getting image from Webcam")
urllib.request.urlretrieve("http://%s:8080/?action=snapshot" % (OCTOPRINT_IP_ADDRESS), full_file_name)
await loop.run_in_executor(None, lambda: urllib.request.urlretrieve(
"http://%s:8080/?action=snapshot" % (OCTOPRINT_IP_ADDRESS), full_file_name))
# If we turned on the light, let's be nice and turn it back off
if turned_on_light:
@@ -296,8 +543,7 @@ async def printstat(ctx):
# REST API call to home assistant to turn the light off.
hassAPIURL = HASS_URL + "/api/services/switch/turn_off"
payload = f'{{"entity_id": "{HASS_LIGHT}"}}'
response = requests.post(hassAPIURL, headers=hassHEADERS, data=payload)
#print(f" - POST Response: {response.text}")
await loop.run_in_executor(None, lambda: requests.post(hassAPIURL, headers=hassHEADERS, data=payload))
except Exception as e:
print(f" ! Unable to turn on light; ERROR: {e}")
@@ -316,75 +562,78 @@ async def printstat(ctx):
print(" - Connecting to OctoPrint API")
# Testing OctoPrint API Status. If printer is off, I won't receive JSON data.
try:
operational = json.loads(octoapi.get_printer_dict())
except:
operational = await loop.run_in_executor(None, octoapi.get_printer_dict)
except Exception:
# Printer is Offline
operational = False
if not operational:
if operational:
print(" - 3D Printer is on and printing, getting details")
# Get the name of the file currently being printed
try:
print(" - Getting Filename")
print_filename = octoapi.get_printFileName()
except:
print_filename = await loop.run_in_executor(None, octoapi.get_printFileName)
except Exception:
print_filename = ""
try:
print(" - Getting % Completion")
# Try to get the % of Completion
print_completion = round(octoapi.get_completion(), 2)
except:
print_completion = round(await loop.run_in_executor(None, octoapi.get_completion), 2)
except Exception:
print_completion = "Unknown"
# Get the current print time in seconds
try:
print(" - Getting Seconds Elapsed")
print_seconds = octoapi.get_printTime()
except:
print_seconds = await loop.run_in_executor(None, octoapi.get_printTime)
except Exception:
print("Unable to get Seconds data from OctoPrint")
print_seconds = 0
# Get the time remaining for current print job in seconds
try:
print(" - Getting Seconds Left")
print_secondsleft = octoapi.get_printTimeLeft()
except:
print_secondsleft = await loop.run_in_executor(None, octoapi.get_printTimeLeft)
except Exception:
print(" ! Unable to get Seconds Left from OctoPrint")
print_secondsleft = 0
try:
print(" - Converting Seconds to Hours")
# Try to convert seconds to hours
print_hours = int(((print_seconds / 60) / 60))
except:
except (TypeError, ValueError):
print_hours = "Unknown"
try:
print(" - Converting Seconds left to Hours left")
# Try to convert seconds left to hours left
print_hoursleft = int(((print_secondsleft / 60) / 60))
except:
except (TypeError, ValueError):
print_hoursleft = "Unknown"
try:
print(" - Calculating Minutes Elapsed")
# Using the total number of minutes minus the total number of whole hours to get the minutes remaining
print_min = int(((print_seconds / 60) - (print_hours * 60)))
except:
except (TypeError, ValueError):
print_min = "Unknown"
try:
print(" - Calculating Minutes Left")
# Same as above but for the minutes left in print job
print_minleft = int(((print_secondsleft / 60) - (print_hoursleft * 60)))
except:
except (TypeError, ValueError):
print_minleft = "Unknown"
time_elapsed = "%s Hours %s Minutes" % (print_hours, print_min)
time_remaining = "%s Hours %s Minutes" % (print_hoursleft, print_minleft)
printer_bed = "%s°C" % (octoapi.get_printer_dict()["temperature"]["bed"]["actual"])
printer_hotend = "%s°C" % (octoapi.get_printer_dict()["temperature"]["tool0"]["actual"])
# operational dict already fetched above — use it directly instead of calling again
printer_bed = "%s°C" % (operational["temperature"]["bed"]["actual"])
printer_hotend = "%s°C" % (operational["temperature"]["tool0"]["actual"])
embed = discord.Embed(title="R0b3's 3D Printer Status", description="Current Job: " + print_filename, color=0xF5A623)
@@ -393,11 +642,14 @@ async def printstat(ctx):
embed.add_field(name="Time Remaining: ", value=time_remaining)
embed.add_field(name="Bed Temp.: ", value=printer_bed)
embed.add_field(name="Hotend Temp.: ", value=printer_hotend)
embed.add_field(name="Livestream URL: ",value="https://rickelobe.com/printer/?action=stream")
print(" - Sending details to chat channel")
await ctx.send(embed=embed)
activity = discord.Activity(name=f"3D Print @ {str(print_completion)}%",type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
await updateStatus()
## No longer need to update the bot status here since there's a check happening every 2 mins in on_ready
#activity = discord.Activity(name=f"3D Print @ {str(print_completion)}%",type=discord.ActivityType.watching)
#await bot.change_presence(activity=activity)
#await updateStatus() # No longer need to call this since it the bot now checks for a print job every 2 mins.
else:
print(" - 3D Printer is not on or is not printing")
@@ -405,42 +657,85 @@ async def printstat(ctx):
print(" - Notifying chat channel")
await ctx.send(embed=embed)
async def updateStatus():
# Initialize variables
print_completion = 0
unknowns = 0
#While the print status is less than 100 % complete, keep updating
while print_completion < 100:
#
# Bambu Lab Printer Commands
#
@bot.command()
async def bambustat(ctx):
datestring = datetime.now().strftime("%m/%d/%Y-%H:%M:%S")
print(f"[{datestring}]: {ctx.message.author.display_name} called $bambustat")
if bambu_status is None:
await ctx.send("Bambu printer status is not available yet. The printer may be offline or still connecting.")
return
status = bambu_status
gcode_state = status.gcode_state or "Unknown"
gcode_file = status.gcode_file or "Unknown"
mc_percent = status.mc_percent if status.mc_percent is not None else "Unknown"
# mc_remaining_time is in minutes
remaining_mins = status.mc_remaining_time or 0
remaining_hours = remaining_mins // 60
remaining_mins_part = remaining_mins % 60
time_remaining = f"{remaining_hours}h {remaining_mins_part:02d}m"
nozzle_temp = f"{status.nozzle_temper:.1f}°C" if status.nozzle_temper is not None else "Unknown"
bed_temp = f"{status.bed_temper:.1f}°C" if status.bed_temper is not None else "Unknown"
chamber_temp = f"{status.chamber_temper:.1f}°C" if status.chamber_temper is not None else "Unknown"
layer_info = "Unknown"
if status.layer_num is not None and status.total_layer_num is not None:
layer_info = f"{status.layer_num} / {status.total_layer_num}"
embed = discord.Embed(
title="Bambu Lab Printer Status",
description=f"File: {gcode_file}",
color=0x00B4D8,
)
embed.add_field(name="State:", value=gcode_state, inline=False)
embed.add_field(name="Percent Complete:", value=f"{mc_percent}%")
embed.add_field(name="Time Remaining:", value=time_remaining)
embed.add_field(name="Layer:", value=layer_info)
embed.add_field(name="Nozzle Temp:", value=nozzle_temp)
embed.add_field(name="Bed Temp:", value=bed_temp)
embed.add_field(name="Chamber Temp:", value=chamber_temp)
# Try to grab a snapshot via ffmpeg RTSPS (only if enabled in config)
image_filename = None
if BAMBU_CAMERA_ENABLE and BAMBU_IP_ADDRESS and BAMBU_ACCESS_CODE:
image_filename = f"bambu_{random.randrange(1, 10000)}.jpg"
try:
print(" - Getting % Completion")
# Try to get the % of Completion
print_completion = round(octoapi.get_completion(), 2)
except:
print_completion = "Unknown"
unknowns += 1
#Set the activity to the new percent complete value
activity = discord.Activity(name=f"3D Print @ {str(print_completion)}%",type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
if (unknowns >= 5):
break
await asyncio.sleep(30)
# Flash the status @ 100% before changing back to listening to your commands, only if uknowns !5.
if unknowns != 5:
for x in range(20):
activity = discord.Activity(name="-",type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
await asyncio.sleep(1)
activity = discord.Activity(name=f"3D Print @ {str(print_completion)}%",type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
await asyncio.sleep(1)
print(" - Capturing Bambu camera frame via RTSPS")
success = await _capture_bambu_frame(image_filename)
if not success:
print(" ! ffmpeg exited with non-zero code, skipping image")
image_filename = None
except asyncio.TimeoutError:
print(" ! ffmpeg timed out capturing Bambu camera frame")
image_filename = None
except Exception as e:
print(f" ! Unable to capture Bambu camera frame: {e}")
image_filename = None
print(" - Done watching 3D Printer status")
await asyncio.sleep(3)
activity = discord.Activity(name="your commands",type=discord.ActivityType.listening)
await bot.change_presence(activity=activity)
print(" - Sending Bambu details to chat channel")
if image_filename:
file = discord.File(image_filename, filename=image_filename)
await ctx.send(file=file, embed=embed)
os.remove(image_filename)
else:
await ctx.send(embed=embed)
@bot.command()
async def printstat(ctx):
datestring = datetime.now().strftime("%m/%d/%Y-%H:%M:%S")
print(f"[{datestring}]: {ctx.message.author.display_name} called $printstat")
await octostat.callback(ctx)
await bambustat.callback(ctx)
@bot.command()
@@ -456,28 +751,130 @@ async def info(ctx):
# give users a link to invite thsi bot to their server
embed.add_field(name="Invite", value="[Invite link](https://discordapp.com/api/oauth2/authorize?client_id=416312716677087233&permissions=51264&scope=bot)")
# Bot Uptime
second = time.time() - start_time
minute, second = divmod(second, 60)
hour, minute = divmod(minute, 60)
day, hour = divmod(hour, 24)
week, day = divmod(day, 7)
week = int(week)
day = int(day)
hour = int(hour)
minute = int(minute)
second = int(second)
embed.add_field(name="Uptime [WW:DD:HH:MM:SS]:", value=f"{week:02d}:{day:02d}:{hour:02d}:{minute:02d}:{second:02d}", inline=False)
print("Getting git describe data to display as version in info embed")
bot_version = await run('git describe --tags')
embed.add_field(name="Version:", value=f"{bot_version}", inline=False)
embed.add_field(name="discord.py Version:", value=f"{discord.__version__}", inline=False)
# Gitlab link to source
embed.add_field(name="Source Code:", value="[R0b3Bot on Gitlab](https://gitlab.rickelobe.com/Bots/r0b3BOT)", inline=False)
await ctx.send(embed=embed)
bot.remove_command('help')
#
#
#
#@bot.command()
#async def commands(ctx):
# # Redirect to the 'help' command
# await help(ctx)
@bot.command()
async def help(ctx):
embed = discord.Embed(title="R0b3BOT", description="List of commands are:", color=0xeee657)
embed.add_field(name="$printstat", value="Uploads a snapshot of Rich's 3D printer and current stats", inline=False)
embed.add_field(name="$explain", value="Displays Dalek EXPLAIN gif", inline=False)
embed.add_field(name="$holyshit", value="Displays Marty McFly HOLY SHIT gif", inline=False)
embed.add_field(name="$greetings", value="Gives a nice greet message", inline=False)
embed.add_field(name="$cat", value="Gives a cute cat gif to lighten up the mood.", inline=False)
embed.add_field(name="$bitch @member", value="Plays 'monkey_bitch.mp3' to the users voice channel, if no user is specified it will play in your own voice channel", inline=False)
embed.add_field(name="$boom @member", value="Plays 'BoomBitch.mp3' to the users voice channel, if no user is specified it will play in your own voice channel", inline=False)
embed.add_field(name="$cowbell @member", value="Plays 'More_cowbell.mp3' to the users voice channel, if no user is specified it will play in your own voice channel", inline=False)
embed.add_field(name="$oops @member", value="Plays 'Oops.mp3' to the users voice channel, if no user is specified it will play in your own voice channel", inline=False)
embed.add_field(name="$promoted @member", value="Plays 'Promoted.mp3' to the users voice channel, if no user is specified it will play in your own voice channel", inline=False)
embed.add_field(name="$trololo @member", value="Plays 'Trololo.mp3' to the users voice channel, if no user is specified it will play in your own voice channel", inline=False)
embed.add_field(name="$leeroy @member", value="Plays 'leeroy.mp3' to the users voice channel, if no user is specified it will play in your own voice channel", inline=False)
embed.add_field(name="$info", value="Gives a little info about the bot", inline=False)
embed.add_field(name="$help", value="Gives this message", inline=False)
embed = discord.Embed(title='Core & Debug Commands:', description="Core commands for info and debug", color=0xeee657)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}info", value="Gives a little info about the bot", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}help", value="Gives this message", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}last_error", value="Will display the real error message the bot has last encountered for additional debugging info")
embed.add_field(name=f"{BOT_COMMAND_PREFIX}greetings", value="Gives a nice greeting message", inline=False)
await ctx.send(embed=embed)
bot.run(DISCORD_AUTH_TOKEN)
embed = discord.Embed(title="GIF Commands", description="Commands to call up a GIF image", color=0xeee657)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}explain", value="Displays Dalek EXPLAIN gif", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}holyshit", value="Displays Marty McFly HOLY SHIT gif", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}cat", value="Gives a cute cat gif to lighten up the mood.", inline=False)
await ctx.send(embed=embed)
embed = discord.Embed(title="Sound Commands", description="To play a sound in a chat channel, use one of the following:", color=0xeee657)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}aww @member", value="Plays Rick & Morty clip 'Awww bitch'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}bitch @member", value="Plays clip of BigEric420 saying 'maybe you shouldn't be such a bitch.mp3'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}big @member", value="Plays 'Wiz Khalifa clip'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}boom @member", value="Plays 'Boom Bitch' sound clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}breee @member", value="Plays 'BREEEEE' clip from a WBTBWB song", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}cowbell @member", value="Plays 'SNL More Cowbell' sound clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}eia @member", value="Plays 'Everything is Awesome' song clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}heavy @member", value="Plays 'Poppy ooo heavy'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}iran @member", value="Plays Trump 'I Ran'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}leeroy @member", value="Plays Leeeerrroooyyy Jenkins clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}nice @member", value="Plays clip from WBTBWB - Nice!", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}oops @member", value="Plays 'Oops.mp3'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}promoted @member", value="Plays Battlefield Friends 'PROMOTED!' sound clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}trololo @member", value="Plays a clip of Trololo song", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}wow @member", value="Plays clip of Owen Wilson saying 'Woooow'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}yaw @member", value="Plays clip of Busta Ryhmes 'Yaw Yaw Yaw'", inline=False)
await ctx.send(embed=embed)
embed = discord.Embed(title="A note about sound clips:", description="Sound clips are only played in voice channels. If a user is not specified when calling the command, the sound will played in the channel of that issuing user is currently joined to. When a username is specified, the bot will play the sound in the channel that user is currently in.")
await ctx.send(embed=embed)
embed = discord.Embed(title="OctoPi Printer Status", color=0xeee657)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}octostat", value="Uploads a snapshot of Rich's OctoPrint 3D printer and current stats", inline=False)
await ctx.send(embed=embed)
embed = discord.Embed(title="Bambu Lab Printer Status", color=0x00B4D8)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}bambustat", value="Displays the current status of the Bambu Lab printer (temps, progress, time remaining)", inline=False)
await ctx.send(embed=embed)
embed = discord.Embed(title="All Printers Status", color=0x9B59B6)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}printstat", value="Shows status of both the OctoPi and Bambu Lab printers in one command", inline=False)
await ctx.send(embed=embed)
# Function to run shell command and return stdout or sterr
async def run(cmd):
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
print(f'[{cmd!r} exited with {proc.returncode}]')
if stdout:
#print(f'[stdout]\n{stdout.decode()}')
return stdout.decode()
if stderr:
#print(f'[stderr]\n{stderr.decode()}')
return stderr.decode()
async def main():
tasks = [bot.start(DISCORD_AUTH_TOKEN)]
if all([MATRIX_HOMESERVER, MATRIX_USER_ID, MATRIX_PASSWORD, MATRIX_ROOM_IDS]):
from matrix_bot import MatrixBot
matrix_bot = MatrixBot(
homeserver=MATRIX_HOMESERVER,
user_id=MATRIX_USER_ID,
password=MATRIX_PASSWORD,
command_prefix=BOT_COMMAND_PREFIX,
room_ids=MATRIX_ROOM_IDS,
get_bambu_status=lambda: bambu_status,
get_last_error=lambda: last_error_msg,
hass_url=HASS_URL,
hass_token=HASS_TOKEN,
hass_light=HASS_LIGHT,
octoprint_ip=OCTOPRINT_IP_ADDRESS,
bambu_ip=BAMBU_IP_ADDRESS,
bambu_access_code=BAMBU_ACCESS_CODE,
bambu_camera_enable=BAMBU_CAMERA_ENABLE,
capture_bambu_frame=_capture_bambu_frame,
store_path=MATRIX_STORE_PATH,
)
print(">> Matrix bot configured, adding to event loop")
tasks.append(matrix_bot.start())
else:
print(">> Matrix config not found, skipping Matrix bot")
await asyncio.gather(*tasks)
asyncio.run(main())
+18
View File
@@ -0,0 +1,18 @@
# r0b3BOT Python dependencies
# Install with: pip install -r requirements.txt
# Discord bot framework — [voice] pulls in PyNaCl for voice channel encryption
discord.py[voice]>=2.6.4
# HTTP client — used for HomeAssistant REST API calls
requests>=2.32.3
# Bambu Lab printer integration via local MQTT
# bambu-connect 0.3.1 uses mqtt.CallbackAPIVersion.VERSION1, which requires paho-mqtt 2.x
bambu-connect>=0.3.1
paho-mqtt>=2.0
# Matrix bot support (optional — only needed if matrix-config section is present in bot_config.conf)
# [e2e] extra adds Olm/Megolm encryption support for encrypted rooms.
# Requires the libolm system library: apt-get install libolm3 (Debian/Ubuntu)
matrix-nio[e2e]>=0.21
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.