diff --git a/.gitignore b/.gitignore index 4dd8c40..feae736 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ data/* !data/cars.txt !data/fun/ !data/fun/*.txt +!data/weapons.txt .env .env.bak .act-runner/ diff --git a/README.md b/README.md index 9148d9d..22d3985 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,8 @@ cmd/irc-bot/ application entrypoint bot/ IRC connection, config, queue, stats, and interfaces plugins/ built-in plugins and tests data/foods/ local food, cuisine, and beer suggestion lists -data/fun/ local family-friendly joke, pun, and wisdom catalogs +data/fun/ local joke, pun, one-liner, and wisdom catalogs +data/weapons.txt local high-level firearm and weapons-name catalog data/sports.txt local sports suggestion list data/cars.txt local car make/model suggestion list storage/ BoltDB wrapper used by stateful plugins diff --git a/config.yaml b/config.yaml index e89ae07..99874a4 100644 --- a/config.yaml +++ b/config.yaml @@ -83,8 +83,10 @@ plugins: status: {enabled: true} define: {enabled: true, timeout_seconds: 8, max_length: 240} calc: {enabled: true} - # Local, family-friendly text catalogs; one line per entry under data/fun. + # Local, operator-editable text catalogs; one line per entry under data/fun. fun: {enabled: true, data_dir: "data/fun", max_length: 240} + # High-level local firearm/weapons catalog; identification only, no instructions. + weapons: {enabled: true, data_file: "data/weapons.txt", max_length: 240} # Public GitHub lookups; token is optional and should be supplied out-of-band. github: {enabled: true, timeout_seconds: 8, max_length: 360, token: ""} reddit: {enabled: true, timeout_seconds: 8, max_length: 360} diff --git a/data/fun/wisdom.txt b/data/fun/wisdom.txt index 3a3f8a8..6658549 100644 --- a/data/fun/wisdom.txt +++ b/data/fun/wisdom.txt @@ -60,3 +60,105 @@ Do not optimize a process that should be removed. Good humor opens a door; good judgment knows when not to walk through it. Choose a sustainable pace before urgency chooses one for you. A clear “no” can protect a better “yes.” +Begin with the smallest honest description of the problem. +The best habit is one that still works on an ordinary day. +Write down the decision while the reasons are still fresh. +A kind correction is easier to remember than a sharp one. +Ask what would make the next step safer and simpler. +Good preparation reduces drama without reducing ambition. +The person closest to the work often knows where the friction is. +An idea becomes useful when someone can try it. +Leave enough margin for the plan to survive reality. +When a rule creates confusion, explain its purpose before repeating it. +Attention is a resource; spend it where it changes something. +Small acts of maintenance are how good things stay good. +The fastest way to improve a draft is to let another person read it. +Trust grows when promises are precise and follow-through is visible. +Do not make a permanent decision to solve a temporary mood. +A reliable process should be understandable by someone new. +If the same question returns, the answer may need a better home. +Good tools amplify care; they do not replace it. +The clearest boundary is one that can be stated without anger. +Make the important path easy to find. +Patience is not passivity; it is room for better information. +An error report is an invitation to improve the system. +When a plan changes, preserve the goal and update the route. +You can be firm without making someone feel small. +The useful version of certainty includes what could change your mind. +A good archive is a gift to the future. +Do not let a full calendar hide an empty priority list. +The most helpful answer may be a question that reveals the missing fact. +Clarity is a form of kindness. +If the first solution is fragile, keep looking for the one that is boringly stable. +Make room for rest before exhaustion makes the decision for you. +The quality of a conversation is measured by what becomes possible afterward. +A simple checklist can protect attention for harder thinking. +The best lesson is the one that changes the next attempt. +If you want a different result, change the repeated part of the process. +Good leadership makes it easier for others to do good work. +The right pace is fast enough to learn and slow enough to notice. +Do not confuse a loud opinion with a complete argument. +The strongest plan can explain its assumptions. +If a task has no owner, it has no reliable future. +Make feedback actionable, timely, and humane. +The smallest useful measurement is better than the largest unused dashboard. +Protect the relationship while solving the problem. +An honest limit is more useful than an impressive promise. +You do not need to win every argument to improve the outcome. +When something works, make it repeatable before making it fancy. +The best question is often “what are we optimizing for?” +Keep a record of what you learned, not only what you finished. +A pause can be a decision to think, not a decision to avoid. +The future benefits from notes written in the present tense. +Good systems make recovery normal instead of heroic. +An explanation should reduce confusion, not display vocabulary. +Do not spend tomorrow fixing what a five-minute check could catch today. +The best standards leave room for judgment and responsibility. +When people feel safe to ask, problems surface earlier. +Build habits that make the right action convenient. +The goal of a disagreement is understanding before resolution. +If you cannot say why a shortcut is safe, it may not be one. +Kindness is not a lack of standards; it is a better way to hold them. +Let evidence change the plan before pride makes the plan expensive. +The work is easier to share when the context is written down. +A small improvement repeated is a quiet revolution. +Give new ideas a small experiment before demanding a grand proof. +The best apology changes the conditions that caused the harm. +An open question can be more productive than a fast conclusion. +Make it easy to stop a process that is causing damage. +Progress is not invalidated by needing a second attempt. +The safest default is the one you have tested in normal conditions. +Good documentation answers the question a tired person will ask. +If the plan depends on everyone remembering, write it down. +The strongest teams share credit and surface risk. +Do not confuse familiarity with truth. +The right tool is the one that solves the problem without creating a larger one. +Leave a little energy for tomorrow's work. +A clear example can teach what a paragraph leaves abstract. +When the result matters, verify it independently. +Make the easy thing the ethical thing whenever you can. +The best time to define success is before the work begins. +Small reliable signals beat dramatic guesses. +If you are stuck, describe the obstacle to someone who is not inside it. +Good questions are reusable tools. +The most durable confidence comes from knowing how to check. +Do not make a person carry a system's missing memory. +A healthy process can survive one busy day. +Use precision for important facts and generosity for imperfect people. +The next good step is still good even when it is not the final one. +If a task keeps growing, define its smallest acceptable finish. +The best improvements are visible in the ordinary moments. +Take responsibility for the effect, not only the intention. +An idea that welcomes questions is stronger than one that fears them. +When you teach, leave behind a method, not just an answer. +Good judgment includes knowing when to ask for help. +The shortest path to trust is consistent, observable care. +Choose a standard you can maintain when nobody is watching. +If the system is confusing, the confusion is data. +Make the next person’s first experience easier than your own. +The best correction preserves dignity and improves accuracy. +Leave a little room for delight in a serious plan. +The most valuable resource may be the attention you return to someone. +Do not wait for perfect conditions to practice a good habit. +An accurate small promise is better than an inspiring vague one. +You can change direction without declaring the earlier road worthless. diff --git a/data/fun/yo_momma.txt b/data/fun/yo_momma.txt index 04a72cc..be77bd6 100644 --- a/data/fun/yo_momma.txt +++ b/data/fun/yo_momma.txt @@ -62,3 +62,255 @@ is so good at solving mysteries, the clues leave breadcrumbs for her. is so good at sharing, the last slice volunteers. is so good at making mornings better, the alarm clock uses a gentler ringtone. is so good at finishing tasks, the to-do list asks for an autograph. +is so good at improvising, the backup plan asks her for backup. +is so good at finding a seat, chairs move closer when she enters. +is so good at making soup, the vegetables volunteer for the pot. +is so good at folding laundry, shirts line up for inspection. +is so good at packing lunches, sandwiches arrive with itineraries. +is so good at keeping calm, the traffic jam starts using turn signals. +is so good at explaining things, the user manual takes her class. +is so good at making lists, the list of lists has a table of contents. +is so good at gardening, the tomatoes ask her when to ripen. +is so good at finding parking, the parking lot saves her a space. +is so good at hosting, the guest book writes a review first. +is so good at making decisions, the coin asks her for a ruling. +is so good at noticing hunger, the snack drawer sends an alert. +is so good at making beds, the blankets tuck themselves in. +is so good at keeping receipts, the cash register asks for a copy. +is so good at learning names, introductions arrive with name tags. +is so good at making toast, the bread gets up before the alarm. +is so good at stretching a budget, coupons form a savings committee. +is so good at finding a typo, the typo applies for witness protection. +is so good at making a room cozy, the furniture wears slippers. +is so good at remembering errands, the errands remind her first. +is so good at making a playlist, silence asks for a guest appearance. +is so good at making people laugh, the joke book marks her as a favorite. +is so good at taking notes, the pen keeps a backup pen. +is so good at avoiding spoilers, trailers ask her permission. +is so good at fixing a zipper, the jacket stops complaining. +is so good at finding a recipe, the ingredients organize themselves. +is so good at staying hydrated, the water bottle checks on her. +is so good at making a queue, even line-cutters take a number. +is so good at solving riddles, the answer arrives with snacks. +is so good at making friends, the houseplants ask for a group photo. +is so good at planning parties, the confetti arrives early. +is so good at making coffee, the beans wake up before everyone else. +is so good at making a budget, the calculator says thank you. +is so good at finding a shortcut, the long way asks for directions. +is so good at keeping appointments, the calendar wears a wristwatch. +is so good at checking weather, clouds carry umbrellas around her. +is so good at making a sandwich, the bread gets a standing ovation. +is so good at caring for pets, the goldfish remembers its password. +is so good at wrapping gifts, the ribbon ties itself in a bow. +is so good at making a room quiet, the squeaky floor whispers. +is so good at spotting a bargain, the price tag negotiates first. +is so good at washing dishes, the plates dry themselves out of respect. +is so good at keeping plants happy, the fern sends a thank-you card. +is so good at giving directions, the compass asks her which way to point. +is so good at finding a book, the library puts one on hold for her. +is so good at making a deadline, the clock requests an extension. +is so good at organizing cables, the chargers form a parade. +is so good at baking cookies, the oven keeps a fan club. +is so good at making a room bright, the lamp takes notes. +is so good at spotting a plot hole, the characters patch it themselves. +is so good at making a list, the checklist checks itself twice. +is so good at being polite, the automatic door opens early. +is so good at finding a missing file, the recycle bin returns it. +is so good at packing a suitcase, luggage labels fill themselves out. +is so good at making a plan, the surprise party asks for a schedule. +is so good at making a phone call, the hold music apologizes. +is so good at cleaning a window, the view sends a postcard. +is so good at making a sandwich, the condiments stand at attention. +is so good at making a room comfortable, the thermostat takes a nap. +is so good at reading instructions, the small print enlarges itself. +is so good at finding a lost key, the lock leaves a clue. +is so good at making a timetable, the trains ask her to dispatch them. +is so good at keeping a secret, even the echo uses a whisper. +is so good at starting a conversation, the awkward silence introduces itself. +is so good at remembering passwords, the password manager asks for lessons. +is so good at making a grocery list, the pantry reports its inventory. +is so good at choosing a gift, the wrapping paper gets excited. +is so good at fixing a loose button, the shirt signs a contract. +is so good at finding a sunny spot, the window follows her around. +is so good at managing a meeting, the agenda takes minutes. +is so good at making a joke, the punchline brings its own drumroll. +is so good at keeping a promise, the calendar circles her word. +is so good at making soup, the spoon asks for a promotion. +is so good at keeping a desk tidy, the paperclips form neat rows. +is so good at answering questions, the quiz asks her to write the answer key. +is so good at making a playlist, the songs audition for her. +is so good at finding a quiet moment, the notifications take a number. +is so good at saving files, the cloud brings an umbrella. +is so good at making a bed, the pillows fluff themselves. +is so good at planning a route, the detours send apologies. +is so good at cheering people up, the gloomy weather changes its mind. +is so good at keeping a plant alive, the cactus asks for a second opinion. +is so good at noticing a detail, the footnote waves hello. +is so good at making a recipe work, substitutions ask for her approval. +is so good at finding a movie, the streaming service stops scrolling. +is so good at keeping a budget, the coins refuse to wander. +is so good at making a welcome, the doormat rolls out a red carpet. +is so good at knowing when to rest, the couch reserves her a seat. +is so good at finishing a puzzle, the box asks for a rematch. +is so good at making a backup, the backup makes a backup. +is so good at finding the right word, the dictionary turns to her. +is so good at keeping a queue moving, the line applauds politely. +is so good at making breakfast, the toast sets an alarm. +is so good at keeping a room cool, the fan follows her lead. +is so good at making an idea clear, the fog takes a day off. +is so good at writing a reminder, the reminder remembers her. +is so good at noticing a pattern, the socks start matching themselves. +is so good at solving a problem, the problem files a resignation. +is so good at sharing credit, the trophy asks for more names. +is so good at taking a break, the task list waits respectfully. +is so good at making a plan B, plan C brings snacks. +is so good at keeping the peace, the debate uses indoor voices. +is so good at making a schedule, free time sends a thank-you note. +is so good at finding a good song, the radio tunes itself. +is so good at making a repair, the broken part writes a testimonial. +is so good at giving useful advice, fortune cookies quote her accurately. +is so good at staying curious, the question mark follows her around. +is so good at making a room welcoming, the chairs introduce themselves. +is so good at leaving notes, the sticky notes stick around for the story. +is so good at spotting a shortcut, the map folds itself to the right page. +is so good at making a deadline, tomorrow asks for a head start. +is so good at finding a calm answer, the argument runs out of batteries. + +# Size and shape +is so big, her smartwatch asks whether it should track miles or square miles. +is so skinny, her shadow looks like it missed lunch. +is so tall, she uses the clouds as sticky notes. +is so short, she uses a thimble as a hot tub. +is so big, the family photo needs panorama mode. +is so big, her shadow has its own zip code. +is so big, the scale says, "Please enter a number." +is so skinny, she can hide behind a bookmark. +is so skinny, her belt has more holes than belt. +is so skinny, she uses a ruler as a full-length mirror. +is so tall, she has to duck under the weather forecast. +is so tall, her selfies need a scroll bar. +is so short, she can limbo under a speed bump. +is so short, her driver's license says, "See reverse side." + +# Age and experience +is so old, her first selfie was cave art. +is so old, she remembers when the Dead Sea was only sick. +is so old, her birth certificate is written on a scroll. +is so old, her password hint is "the first password." +is so old, she knew the dinosaurs by their usernames. +is so experienced, the instruction manual asks her for advice. +is so experienced, she can tell an update is coming by the way the printer sighs. + +# Smell and cleanliness +is so smelly, the air freshener filed for remote work. +is so smelly, skunks spray themselves before visiting. +is so smelly, her socks have their own weather system. +is so smelly, the garbage truck takes the long way around. +is so smelly, the nose on the emoji turns itself off. +is so clean, dust bunnies bring lint rollers when they visit. +is so clean, the soap asks her for a demonstration. +is so clean, the mop has nothing left to do but update its résumé. + +# Silly, forgetful, and confused +is so stupid, she stared at the orange juice because it said, "concentrate." +is so silly, she brought a ladder to a low point in the conversation. +is so confused, she tried to charge her phone with a bookmark. +is so forgetful, she put her phone in the fridge so she would not lose it. +is so forgetful, she set a reminder to remember where the reminders are. +is so confused, she returned a library book to the grocery store. +is so silly, she tried to catch a cloud for her weather app. +is so confused, she asked the keyboard where the space bar was. +is so silly, she brought a map to the internet. +is so forgetful, she made a to-do list and forgot where she put it. +is so confused, she thought a shortcut was a smaller jacket. +is so silly, she tried to alphabetize the alphabet. +is so forgetful, she lost her place in a one-page document. +is so confused, she thought the recycle bin was a trash can with a second chance. +is so silly, she asked the elevator what floor it was on. + +# Loud, quiet, and messy +is so loud, the mute button asks for ear protection. +is so loud, her whisper needs a microphone. +is so loud, the echo asks her to lower the volume. +is so loud, noise-canceling headphones call her for backup. +is so quiet, even her footsteps use indoor voices. +is so quiet, the echo has to start the conversation. +is so messy, the lost-and-found is just her bedroom. +is so messy, the vacuum cleaner needs a map. +is so clumsy, she tripped over a wireless connection. +is so clumsy, she drops a pillow on the floor and misses. + +# Money, work, and habits +is so broke, she checks the couch cushions for a coupon code. +is so broke, she puts an IOU in the tip jar and asks for change. +is so broke, her piggy bank sends her a payment reminder. +is so broke, the penny makes the decisions. +is so busy, her calendar needs a calendar. +is so lazy, she waits for the remote to walk back. +is so lazy, she orders takeout from the kitchen. +is so hardworking, the coffee machine clocks in early for her. +is so good at work, the deadline asks for an extension. +is so bad at work, her out-of-office message takes a personal day. + +# Hair and appearance +is so hairy, the barber uses hedge clippers and still needs a lunch break. +is so hairy, the sweater asks her for knitting advice. +is so bald, the sun uses her head as a charging pad. +is so stylish, the mannequins ask her what is trending. +is so fashionable, the closet has a waiting list. +is so graceful, the furniture moves out of her way. + +# Food and cooking +is so hungry, the menu asks her what it can do to help. +is so hungry, she reads a cookbook with a fork. +is so hungry, the snack drawer keeps emergency snacks for her emergencies. +is so good at cooking, the timer asks her for the recipe. +is so bad at cooking, the smoke alarm has a reserved seat. +is so bad at cooking, the leftovers file a missing-person report. +is so good at baking, the cookies line up for photographs. +is so picky, the menu needs a version history. +is so spicy, the hot sauce wears oven mitts around her. +is so sweet, the dessert asks her to tone it down. +is so bad at making toast, the bread applies for witness protection. + +# School, sports, and travel +is so smart, the answer key checks her work. +is so smart, the quiz asks her to write the questions. +is so bad at math, her calculator rounds up out of sympathy. +is so good at sports, the scoreboard keeps her autograph. +is so bad at sports, the participation trophy asks for a rematch. +is so competitive, she races the microwave countdown. +is so good at directions, the compass follows her. +is so bad at directions, the GPS says, "I believe in you, but turn around." +is so well-traveled, the airport announces her layovers personally. +is so bad at packing, the suitcase checks her luggage. + +# Technology and modern life +is so bad with technology, her calculator asks for tech support. +is so good with technology, the update waits for her approval. +is so online, the loading spinner knows her by name. +is so offline, the Wi-Fi sends a wellness check. +is so good at passwords, the password manager takes notes. +is so bad at texting, autocorrect requests a second opinion. +is so good at troubleshooting, the bug closes its own ticket. +is so bad at video calls, the mute button files a complaint. +is so good at taking selfies, the camera asks her to choose a favorite. + +# Personality and everyday chaos +is so dramatic, the weather app adds a plot twist. +is so patient, the loading bar asks her to wait for it. +is so impatient, she refreshes the clock. +is so lucky, the coin lands on both sides for her. +is so unlucky, her four-leaf clover has a bad day. +is so generous, the last slice volunteers itself. +is so nosy, the privacy policy asks her to look away. +is so polite, the automatic door opens before she reaches it. +is so stubborn, the GPS reroutes itself around the argument. +is so optimistic, even the error message says, "Try again!" +is so pessimistic, the loading bar starts at ninety-nine percent just to disappoint her. +is so competitive, she argues with the autocorrect suggestion and wins. +is so curious, the question mark follows her home. +is so organized, her junk drawer has an org chart. +is so chaotic, the calendar asks for a seat belt. +is so friendly, the waiting room starts a group chat. +is so persuasive, the vending machine gives her two snacks. diff --git a/data/weapons.txt b/data/weapons.txt new file mode 100644 index 0000000..44abdb7 --- /dev/null +++ b/data/weapons.txt @@ -0,0 +1,343 @@ +# High-level reference catalog only. One category and item per line. +# This file intentionally contains names and broad types, not instructions. +pistol|.22 rimfire target pistol +pistol|Beretta 21A Bobcat +pistol|Beretta 92 +pistol|Beretta APX +pistol|Browning Buck Mark +pistol|Colt M1911 +pistol|CZ 75 +pistol|CZ P-10 +pistol|FN Five-seveN +pistol|FN Five-seveN MRD +pistol|FN FNP +pistol|FN FNS +pistol|Glock 17 +pistol|Glock 19 +pistol|Glock 26 +pistol|Glock 34 +pistol|Glock 43 +pistol|Glock 48 +pistol|Heckler & Koch VP9 +pistol|Heckler & Koch USP +pistol|Heckler & Koch P30 +pistol|IWI Jericho 941 +pistol|Kimber Custom II +pistol|SIG Sauer P220 +pistol|SIG Sauer P226 +pistol|SIG Sauer P320 +pistol|SIG Sauer P365 +pistol|SIG Sauer P938 +pistol|Smith & Wesson M&P9 +pistol|Smith & Wesson Shield +pistol|Springfield Armory XD +pistol|Springfield Armory Hellcat +pistol|Steyr M9-A1 +pistol|Taurus G3 +pistol|Walther PPQ +pistol|Walther PPK +pistol|Walther PDP +pistol|Ruger Mark IV +pistol|Ruger Security-9 +pistol|Ruger LCP +pistol|Desert Eagle +pistol|Browning Hi-Power +pistol|Tokarev TT-33 +pistol|Makarov PM +pistol|Webley Self-Loading Pistol +revolver|Colt Python +revolver|Colt Detective Special +revolver|Colt Single Action Army +revolver|Colt Anaconda +revolver|Smith & Wesson Model 10 +revolver|Smith & Wesson Model 29 +revolver|Smith & Wesson Model 60 +revolver|Smith & Wesson Model 686 +revolver|Smith & Wesson Model 500 +revolver|Ruger GP100 +revolver|Ruger Blackhawk +revolver|Ruger Redhawk +revolver|Ruger Super Redhawk +revolver|Ruger LCR +revolver|Taurus Judge +revolver|Manurhin MR 73 +revolver|Nagant M1895 +revolver|Webley Mk VI +revolver|Schofield revolver +revolver|Remington Model 1858 +revolver|LeMat revolver +handgun|derringer +handgun|pepperbox pistol +handgun|single-shot hunting pistol +handgun|target pistol +handgun|service pistol +handgun|compact defensive pistol +handgun|competition pistol +handgun|flintlock pistol +handgun|percussion-cap pistol +rifle|.22 rimfire sporting rifle +rifle|AR-15 pattern rifle +rifle|AKM +rifle|AK-47 +rifle|AK-74 +rifle|AK-103 +rifle|AK-12 +rifle|AR-10 pattern rifle +rifle|AUG +rifle|Barrett REC7 +rifle|Beretta ARX160 +rifle|Browning BAR +rifle|Browning BLR +rifle|Colt M16 +rifle|Colt M4 +rifle|CZ Bren 2 +rifle|Daewoo K2 +rifle|FN FAL +rifle|FN SCAR-L +rifle|FN SCAR-H +rifle|FN FNC +rifle|FAMAS +rifle|Galil ACE +rifle|Galil ARM +rifle|G3 +rifle|HK 416 +rifle|HK 417 +rifle|HK G36 +rifle|HK G3 +rifle|IWI Tavor X95 +rifle|IWI Galil +rifle|Keltec SU-16 +rifle|L85A2 +rifle|Lee-Enfield +rifle|M1 Garand +rifle|M14 +rifle|M1 Carbine +rifle|MAS-49 +rifle|Mosin-Nagant +rifle|PTR-91 +rifle|Remington 700 +rifle|Ruger American Rifle +rifle|Ruger 10/22 +rifle|Ruger Mini-14 +rifle|Ruger Precision Rifle +rifle|Savage Model 110 +rifle|SKS +rifle|SIG SG 550 +rifle|SIG MCX +rifle|SIG 516 +rifle|Springfield M1903 +rifle|Steyr AUG +rifle|StG 44 +rifle|Tikka T3x +rifle|Type 56 +rifle|VZ. 58 +rifle|Winchester Model 70 +rifle|Winchester 1894 +rifle|Winchester 1907 +rifle|Weatherby Mark V +rifle|Zastava M70 +carbine|M1 Carbine +carbine|M4 Carbine +carbine|M4A1 +carbine|HK 416C +carbine|HK 53 +carbine|SIG MCX Rattler +carbine|SIG SG 553 +carbine|Kel-Tec SUB-2000 +carbine|Ruger PC Carbine +carbine|Marlin Camp Carbine +carbine|Beretta Cx4 Storm +carbine|CZ Scorpion Carbine +carbine|Kel-Tec SU-16 +carbine|Hi-Point 995TS +carbine|M1A SOCOM +carbine|Mosin-Nagant carbine +shotgun|12-gauge sporting shotgun +shotgun|20-gauge field shotgun +shotgun|Beretta 1301 +shotgun|Benelli M2 +shotgun|Benelli M4 +shotgun|Benelli Super Black Eagle +shotgun|Browning Auto-5 +shotgun|Browning Citori +shotgun|Browning BPS +shotgun|CZ Drake +shotgun|Franchi Affinity +shotgun|Ithaca 37 +shotgun|Mossberg 500 +shotgun|Mossberg 590 +shotgun|Mossberg 930 +shotgun|Mossberg 940 +shotgun|Mossberg Maverick 88 +shotgun|Remington 870 +shotgun|Remington 1100 +shotgun|Remington V3 +shotgun|Saiga-12 +shotgun|Stoeger Coach Gun +shotgun|Winchester Model 12 +shotgun|Winchester SXP +shotgun|Winchester 101 +shotgun|Keltec KS7 +shotgun|Keltec KSG +shotgun|SPAS-12 +shotgun|Blaser F3 +shotgun|Darne side-by-side +shotgun|drilling combination gun +shotgun|double-barrel shotgun +shotgun|lever-action shotgun +shotgun|pump-action shotgun +shotgun|semi-automatic shotgun +smg|Beretta PM12 +smg|B&T APC9 +smg|B&T MP9 +smg|CZ Scorpion EVO 3 +smg|FN P90 +smg|FN PS90 +smg|HK MP5 +smg|HK MP7 +smg|HK UMP +smg|HK Uzi +smg|Ingram MAC-10 +smg|Ingram MAC-11 +smg|KRISS Vector +smg|PPSh-41 +smg|PPS-43 +smg|Sa vz. 23 +smg|Sterling submachine gun +smg|Sten gun +smg|Steyr TMP +smg|SIG MPX +smg|Thompson submachine gun +smg|Uzi +smg|Vigneron M2 +smg|Škorpion vz. 61 +machine-gun|Browning M2 +machine-gun|Browning M1919 +machine-gun|Bren light machine gun +machine-gun|DShK +machine-gun|FN MAG +machine-gun|FN Minimi +machine-gun|HK21 +machine-gun|HK MG4 +machine-gun|HK MG5 +machine-gun|M60 +machine-gun|M240 +machine-gun|M249 +machine-gun|MG 08 +machine-gun|MG 34 +machine-gun|MG 42 +machine-gun|PK machine gun +machine-gun|RPD +machine-gun|RPK +machine-gun|RPK-74 +machine-gun|Vickers machine gun +machine-gun|Ultimax 100 +machine-gun|Negev +machine-gun|Kord machine gun +sniper|Accuracy International Arctic Warfare +sniper|Accuracy International AWM +sniper|Accuracy International AXMC +sniper|Barrett M82 +sniper|Barrett M107 +sniper|Barrett MRAD +sniper|CheyTac M200 Intervention +sniper|Dragunov SVD +sniper|FN Ballista +sniper|HK PSG1 +sniper|M24 Sniper Weapon System +sniper|M40 rifle +sniper|M2010 Enhanced Sniper Rifle +sniper|M110 Semi-Automatic Sniper System +sniper|McMillan TAC-50 +sniper|Remington MSR +sniper|Ruger Precision Rifle +sniper|Savage 110 BA +sniper|Steyr SSG 69 +sniper|SV-98 +sniper|Tikka T3x TAC A1 +sniper|Walther WA 2000 +sniper|Zastava M93 +launcher|M79 grenade launcher +launcher|M203 grenade launcher +launcher|M320 grenade launcher +launcher|Milkor MGL +launcher|Mk 19 grenade launcher +launcher|Panzerfaust +launcher|RPG-7 +launcher|Carl Gustaf recoilless rifle +launcher|AT4 +launcher|M72 LAW +launcher|Javelin +launcher|NLAW +launcher|Stinger +launcher|FIM-92 Stinger +launcher|FGM-148 Javelin +launcher|TOW missile system +launcher|Bofors 40 mm gun +launcher|M134D Minigun +launcher|XM25 CDTE +launcher|flare gun +launcher|signal pistol +historical|Brown Bess musket +historical|Charleville musket +historical|Springfield Model 1861 +historical|Whitworth rifle +historical|Henry rifle +historical|Spencer repeating rifle +historical|Sharps rifle +historical|Winchester 1873 +historical|Colt Walker +historical|Colt Navy 1851 +historical|Mauser C96 +historical|Luger P08 +historical|Walther P38 +historical|Browning Hi-Power +historical|M1 Garand +historical|M3 Grease Gun +historical|Sten Mk II +historical|MP 40 +historical|PPSh-41 +historical|Type 100 submachine gun +historical|Johnson rifle +historical|FG 42 +historical|Gewehr 98 +historical|Karabiner 98k +historical|Lee-Metford +historical|Arisaka Type 99 +historical|Mosin-Nagant +historical|Vetterli rifle +grenade|M67 fragmentation grenade +grenade|M18 smoke grenade +grenade|M84 stun grenade +grenade|RGD-5 hand grenade +grenade|F1 hand grenade +grenade|Mk 2 grenade +grenade|RGO hand grenade +grenade|RKG-3 anti-tank grenade +grenade|M24 stick grenade +grenade|No. 36 Mills bomb +grenade|Type 97 grenade +grenade|DM51 grenade +grenade|V40 miniature grenade +grenade|incendiary grenade +grenade|smoke grenade +grenade|stun grenade +grenade|training grenade +grenade|riot-control gas grenade +grenade|rifle grenade +explosive|dynamite — historical term +explosive|TNT — historical explosive compound +explosive|plastic explosive — high-level term +explosive|demolition charge — high-level term +explosive|shaped charge — high-level term +explosive|depth charge +explosive|aerial bomb +explosive|general-purpose bomb +explosive|guided bomb +explosive|cluster munition — historical term +explosive|land mine +explosive|naval mine +explosive|improvised explosive device — reference term only +explosive|firecracker +explosive|signal flare +explosive|vehicle-borne explosive device — reference term only diff --git a/docs/configuration.md b/docs/configuration.md index c7dfa64..73938f2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -66,6 +66,7 @@ plugins: define: {enabled: true, timeout_seconds: 8, max_length: 240} calc: {enabled: true} fun: {enabled: true, data_dir: "data/fun", max_length: 240} + weapons: {enabled: true, data_file: "data/weapons.txt", max_length: 240} github: {enabled: true, timeout_seconds: 8, max_length: 360, token: ""} reddit: {enabled: true, timeout_seconds: 8, max_length: 360} foods: {enabled: true, data_dir: "data/foods", max_length: 240} @@ -76,11 +77,13 @@ plugins: horoscope: {enabled: true, max_summary_length: 360} ``` -`status`, `calc`, `foods`, `sports`, and `car` are local. `define` uses the public English dictionary -service, `reddit` uses Reddit's public post and subreddit JSON endpoints with -an RSS fallback, and `horoscope` uses a public daily horoscope API. `foods` -and `sports` use local text files only. `fun` uses local family-friendly text -catalogs under `data/fun`, while `github` uses read-only public GitHub API +`status`, `calc`, `foods`, `sports`, `car`, and `weapons` are local. `define` uses +the public English dictionary service, `reddit` uses Reddit's public post and +subreddit JSON endpoints with an RSS fallback, and `horoscope` uses a public +daily horoscope API. `foods` and `sports` use local text files only. `fun` uses +local operator-editable text catalogs under `data/fun`, and `weapons` uses the +high-level reference catalog under `data/weapons.txt`; it does not provide +instructions or tactical guidance. `github` uses read-only public GitHub API lookups. Their output limits prevent a slow or unusually large response from holding up the bot or flooding IRC. Disable any of them with `enabled: false` if they are not wanted. diff --git a/docs/plugins.md b/docs/plugins.md index d5e6b49..933ff45 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -21,12 +21,13 @@ Plugins are enabled or disabled under plugins..enabled in config.yaml. - define: short English dictionary definitions - calc: safe local arithmetic and unit conversion - github: compact public GitHub repository, issue, release, user, commit, and search lookups +- weapons: high-level local firearm and weapons-name catalog - status: local connection, uptime, and counter status - xkcd: latest or numbered XKCD comics - lastfm: current or recently played Last.fm tracks - cats: short cat facts - eightball: customizable Magic 8-Ball answers -- fun: local family-friendly yo-momma jokes, one-liners, puns, and wisdom +- fun: local yo-momma jokes, one-liners, puns, and wisdom - cheer: family-friendly cheers - seen, tell, karma, and dice: channel utilities - quote, choose, and time: lightweight utilities @@ -460,8 +461,11 @@ readable without color. Each request produces one bounded message. ## Fun text catalogs -The `fun` plugin provides local, family-friendly text without making an -external request. Each command returns one randomly selected, bounded line: +The `fun` plugin provides local text without making an external request. Each +command returns one randomly selected, bounded line. The yo-momma catalog is +organized around classic joke categories such as size, age, smell, silliness, +work, food, technology, and everyday mishaps. It is playful and non-targeted: +operators should remove any entries that do not fit their channel's tone. ~~~text !yomomma @@ -476,7 +480,9 @@ external request. Each command returns one randomly selected, bounded line: The catalogs are stored under `data/fun/`: -- `yo_momma.txt`: light, non-abusive “yo momma” jokes +- `yo_momma.txt`: categorized yo-momma jokes, including fat, skinny, tall, + short, old, smelly, stupid/silly, loud, broke, clumsy, food, technology, + school, sports, and other classic joke themes - `one_liners.txt`: short original one-liners - `puns.txt`: short original puns - `wisdom.txt`: original/adapted wisdom lines without questionable attribution @@ -496,6 +502,37 @@ plugins: The catalogs are local and editable, so operators can remove a line or adapt the tone of a channel without changing Go code. +## Firearm and weapons catalog + +The `weapons` plugin is a lightweight reference/randomizer for firearm and +weapons terminology. It returns names and broad categories only; it does not +provide instructions, construction details, acquisition advice, or tactical +guidance. Generic commands choose from the whole catalog, while category +aliases narrow the result: + +~~~text +!firearm +!guns pistol +!rifle +!shotgun +!smg +!grenade +!explosives +~~~ + +The catalog includes civilian, historical, sporting, military, launcher, +grenade, and high-level explosive terms. Keep the default output limit in +place to ensure one short IRC response. Operators can edit `data/weapons.txt` +or disable the plugin with `plugins.weapons.enabled: false`. + +~~~yaml +plugins: + weapons: + enabled: true + data_file: "data/weapons.txt" + max_length: 240 +~~~ + ## Cheers ~~~text diff --git a/plugins/fun.go b/plugins/fun.go index 6239a2d..f082790 100644 --- a/plugins/fun.go +++ b/plugins/fun.go @@ -10,7 +10,7 @@ import ( "github.com/variablenix/GoBot/storage" ) -// Fun serves short, local, family-friendly text from operator-editable files. +// Fun serves short, local text from operator-editable files. // Keeping these catalogs local avoids external API failures and lets operators // curate the tone for their own channels. type Fun struct { @@ -34,7 +34,7 @@ func (p *Fun) Commands() []string { } func (p *Fun) Help() string { - return "!yomomma/!yo — family-friendly joke; !oneliner/!one — random one-liner; !pun — random pun; !wisdom/!wise — short wisdom line" + return "!yomomma/!yo — random yo-momma joke; !oneliner/!one — random one-liner; !pun — random pun; !wisdom/!wise — short wisdom line" } func (p *Fun) Init(c bot.PluginConfig, _ *storage.DB) error { diff --git a/plugins/plugins.go b/plugins/plugins.go index a3c9a32..d6a6f0e 100644 --- a/plugins/plugins.go +++ b/plugins/plugins.go @@ -41,5 +41,6 @@ func All() []bot.Plugin { &Help{}, &Alias{}, &Fun{}, + &Weapons{}, } } diff --git a/plugins/weapons.go b/plugins/weapons.go new file mode 100644 index 0000000..86b006a --- /dev/null +++ b/plugins/weapons.go @@ -0,0 +1,170 @@ +package plugins + +import ( + "fmt" + "math/rand" + "path/filepath" + "strings" + + "github.com/variablenix/GoBot/bot" + "github.com/variablenix/GoBot/storage" +) + +// Weapons serves a high-level, local reference catalog of firearm and weapons +// names. It deliberately provides identification only: no instructions, +// construction details, acquisition guidance, or tactical advice. +type Weapons struct { + items []weaponEntry + byCategory map[string][]weaponEntry + maxLength int +} + +type weaponEntry struct { + category string + name string +} + +var weaponCategories = []string{ + "pistol", "revolver", "handgun", "rifle", "carbine", "shotgun", "smg", + "machine-gun", "sniper", "launcher", "historical", "explosive", "grenade", +} + +var weaponAliases = map[string]string{ + "pistol": "pistol", "pistols": "pistol", + "revolver": "revolver", "revolvers": "revolver", + "handgun": "handgun", "handguns": "handgun", + "rifle": "rifle", "rifles": "rifle", + "carbine": "carbine", "carbines": "carbine", + "shotgun": "shotgun", "shotguns": "shotgun", + "smg": "smg", "submachine": "smg", "submachinegun": "smg", "submachine-gun": "smg", + "machinegun": "machine-gun", "machine-gun": "machine-gun", "machineguns": "machine-gun", + "sniper": "sniper", "sniperrifle": "sniper", "sniper-rifle": "sniper", + "launcher": "launcher", "launchers": "launcher", + "historical": "historical", "classic": "historical", + "explosive": "explosive", "explosives": "explosive", + "grenade": "grenade", "grenades": "grenade", +} + +func (p *Weapons) Name() string { return "weapons" } + +func (p *Weapons) Commands() []string { + return []string{ + "firearm", "firearms", "gun", "guns", "weapon", "weapons", "arms", + "pistol", "pistols", "handgun", "handguns", "rifle", "rifles", + "carbine", "carbines", "shotgun", "shotguns", "smg", "submachinegun", + "machinegun", "sniper", "sniperrifle", "launcher", "launchers", + "grenade", "grenades", "explosive", "explosives", + } +} + +func (p *Weapons) Help() string { + return "!firearm [category] — suggest a high-level firearm or weapons term; aliases include !guns, !weapons, !pistol, !rifle, !shotgun, !grenade" +} + +func (p *Weapons) Init(c bot.PluginConfig, _ *storage.DB) error { + p.maxLength = c.Int("max_length", 240) + if p.maxLength < 100 || p.maxLength > 400 { + p.maxLength = 240 + } + p.items = nil + p.byCategory = make(map[string][]weaponEntry) + path := c.String("data_file", filepath.Join("data", "weapons.txt")) + for _, line := range readFoodList(path) { + parts := strings.SplitN(line, "|", 2) + category, name := "all", strings.TrimSpace(line) + if len(parts) == 2 { + category = normalizeWeaponCategory(parts[0]) + name = strings.TrimSpace(parts[1]) + } + if name == "" { + continue + } + entry := weaponEntry{category: category, name: name} + p.items = append(p.items, entry) + p.byCategory[category] = append(p.byCategory[category], entry) + p.byCategory["all"] = append(p.byCategory["all"], entry) + } + if len(p.items) == 0 { + p.items = weaponFallbacks + p.byCategory["all"] = append([]weaponEntry(nil), weaponFallbacks...) + for _, item := range weaponFallbacks { + p.byCategory[item.category] = append(p.byCategory[item.category], item) + } + } + return nil +} + +func (p *Weapons) Handle(b *bot.Bot, m bot.Message) bool { + cmd, arg, ok := bot.IsCommand(m, b.Config.CommandPrefix) + if !ok { + return false + } + command := strings.ToLower(cmd) + if !isWeaponCommand(command) { + return false + } + + category := normalizeWeaponCategory(command) + if category == "" { + category = "all" + } + arg = strings.TrimSpace(arg) + target := "" + if category == "all" && arg != "" { + parts := strings.Fields(arg) + if selected := normalizeWeaponCategory(parts[0]); selected != "" { + category = selected + arg = strings.TrimSpace(strings.TrimPrefix(arg, parts[0])) + } + } + if arg != "" { + target = truncateRunes(cleanExternalText(arg), 60) + } + + choices := p.byCategory[category] + if len(choices) == 0 { + choices = p.byCategory["all"] + } + if len(choices) == 0 { + b.Send(m.ReplyTarget(), ircColor(ircYellow, "the weapons catalog is unavailable")) + return true + } + item := choices[rand.Intn(len(choices))] + result := fmt.Sprintf("Arsenal pick [%s]: %s", displayWeaponCategory(item.category), item.name) + if target != "" { + result = fmt.Sprintf("Arsenal pick for %s [%s]: %s", target, displayWeaponCategory(item.category), item.name) + } + b.Send(m.ReplyTarget(), truncateRunes(ircColor(ircCyan, result), p.maxLength)) + return true +} + +func isWeaponCommand(command string) bool { + if command == "firearm" || command == "firearms" || command == "gun" || command == "guns" || command == "weapon" || command == "weapons" || command == "arms" { + return true + } + _, ok := weaponAliases[command] + return ok +} + +func normalizeWeaponCategory(value string) string { + value = strings.ToLower(strings.TrimSpace(value)) + if value == "firearm" || value == "firearms" || value == "gun" || value == "guns" || value == "weapon" || value == "weapons" || value == "arms" { + return "all" + } + return weaponAliases[value] +} + +func displayWeaponCategory(category string) string { + if category == "all" || category == "" { + return "General" + } + return strings.Title(strings.ReplaceAll(category, "-", " ")) +} + +var weaponFallbacks = []weaponEntry{ + {category: "pistol", name: ".22 rimfire pistol"}, + {category: "rifle", name: "bolt-action sporting rifle"}, + {category: "shotgun", name: "over-under shotgun"}, + {category: "revolver", name: "double-action revolver"}, + {category: "grenade", name: "training grenade — identification only"}, +} diff --git a/plugins/weapons_test.go b/plugins/weapons_test.go new file mode 100644 index 0000000..01cab99 --- /dev/null +++ b/plugins/weapons_test.go @@ -0,0 +1,48 @@ +package plugins + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/variablenix/GoBot/bot" +) + +func TestWeaponsLoadsCategoriesAndAliases(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "weapons.txt") + content := "pistol|.22 rimfire pistol\nrifle|sporting rifle\ngrenade|smoke grenade\n" + if err := os.WriteFile(path, []byte(content), 0600); err != nil { + t.Fatal(err) + } + + var weapons Weapons + if err := weapons.Init(bot.PluginConfig{"data_file": path}, nil); err != nil { + t.Fatal(err) + } + if len(weapons.items) != 3 || len(weapons.byCategory["pistol"]) != 1 || len(weapons.byCategory["all"]) != 3 { + t.Fatalf("unexpected weapon catalog: %#v", weapons) + } + for _, command := range []string{"firearm", "guns", "weapons", "pistol", "rifle", "grenade"} { + if !isWeaponCommand(command) { + t.Fatalf("expected %q to be a weapon command", command) + } + } + if got := normalizeWeaponCategory("shotguns"); got != "shotgun" { + t.Fatalf("unexpected shotgun category %q", got) + } + if !strings.Contains(weapons.Help(), "!guns") { + t.Fatal("help should mention the short alias") + } +} + +func TestWeaponsFallback(t *testing.T) { + var weapons Weapons + if err := weapons.Init(bot.PluginConfig{"data_file": filepath.Join(t.TempDir(), "missing.txt")}, nil); err != nil { + t.Fatal(err) + } + if len(weapons.items) == 0 || len(weapons.byCategory["all"]) == 0 { + t.Fatal("expected weapon fallbacks") + } +}