├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ ├── linters.yml │ ├── tests.yml │ └── docker.yaml ├── test_data ├── not_spam2.txt ├── not_spam1.txt ├── not_spam3.txt ├── spam121.txt ├── spam150.txt ├── spam144.txt ├── spam155.txt ├── spam137.txt ├── spam107.txt ├── spam139.txt ├── spam140.txt ├── spam149.txt ├── spam154.txt ├── spam128.txt ├── spam138.txt ├── spam153.txt ├── spam142.txt ├── spam051.txt ├── spam130.txt ├── spam145.txt ├── spam099.txt ├── spam131.txt ├── spam125.txt ├── spam044.txt ├── spam078.txt ├── spam079.txt ├── spam118.txt ├── spam148.txt ├── spam050.txt ├── spam102.txt ├── spam136.txt ├── spam077.txt ├── spam009.txt ├── spam049.txt ├── spam100.txt ├── spam135.txt ├── spam147.txt ├── spam081.txt ├── spam117.txt ├── spam120.txt ├── spam151.txt ├── spam076.txt ├── spam046.txt ├── spam068.txt ├── spam014.txt ├── spam057.txt ├── spam071.txt ├── spam074.txt ├── spam075.txt ├── spam110.txt ├── spam040.txt ├── spam083.txt ├── spam087.txt ├── spam054.txt ├── spam111.txt ├── spam073.txt ├── spam104.txt ├── spam007.txt ├── spam072.txt ├── spam116.txt ├── spam028.txt ├── spam063.txt ├── spam067.txt ├── spam086.txt ├── spam112.txt ├── spam152.txt ├── spam056.txt ├── spam025.txt ├── spam094.txt ├── spam097.txt ├── spam115.txt ├── spam127.txt ├── spam059.txt ├── spam026.txt ├── spam065.txt ├── spam085.txt ├── spam008.txt ├── spam047.txt ├── spam105.txt ├── spam106.txt ├── spam129.txt ├── spam143.txt ├── spam002.txt ├── spam010.txt ├── spam060.txt ├── spam114.txt ├── spam119.txt ├── spam098.txt ├── spam113.txt ├── spam134.txt ├── spam036.txt ├── spam062.txt ├── spam084.txt ├── spam101.txt ├── spam108.txt ├── spam146.txt ├── spam045.txt ├── spam088.txt ├── spam066.txt ├── spam022.txt ├── spam024.txt ├── spam043.txt ├── spam141.txt ├── spam037.txt ├── spam095.txt ├── spam103.txt ├── spam109.txt ├── spam015.txt ├── spam021.txt ├── spam013.txt ├── spam032.txt ├── spam004.txt ├── spam011.txt ├── spam033.txt ├── spam080.txt ├── spam082.txt ├── spam006.txt ├── spam055.txt ├── spam019.txt ├── spam034.txt ├── spam064.txt ├── spam038.txt ├── spam017.txt ├── spam035.txt ├── spam124.txt ├── spam126.txt ├── spam001.txt ├── spam041.txt ├── spam012.txt ├── spam031.txt ├── spam090.txt ├── spam016.txt ├── spam020.txt ├── spam029.txt ├── spam052.txt ├── spam061.txt ├── spam122.txt ├── spam042.txt ├── spam053.txt ├── spam091.txt ├── spam003.txt ├── spam133.txt ├── spam027.txt ├── spam069.txt ├── spam030.txt ├── spam039.txt ├── spam070.txt ├── spam023.txt ├── spam093.txt ├── spam005.txt ├── spam123.txt ├── spam018.txt ├── spam048.txt ├── spam058.txt ├── spam132.txt ├── spam092.txt ├── spam089.txt ├── spam096.txt ├── message_administrator.json └── message_safeguard.json ├── src ├── common │ ├── mod.rs │ ├── embeddings.rs │ ├── zsc.rs │ ├── telegram.rs │ └── re.rs ├── repl.rs ├── main.rs ├── cli.rs ├── cache.rs ├── lib.rs ├── bench.rs └── webhook.rs ├── fly.toml ├── Cargo.toml ├── Dockerfile ├── CONTRIBUTING.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | /target 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [cuducos] 2 | -------------------------------------------------------------------------------- /test_data/not_spam2.txt: -------------------------------------------------------------------------------- 1 | What is this airdrop thing? -------------------------------------------------------------------------------- /test_data/not_spam1.txt: -------------------------------------------------------------------------------- 1 | In this group it is forbidden to talk about airdrop. 2 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod embeddings; 2 | pub mod re; 3 | pub mod telegram; 4 | pub mod zsc; 5 | -------------------------------------------------------------------------------- /test_data/not_spam3.txt: -------------------------------------------------------------------------------- 1 | In this group we cannot talk about airdrop, the crypto thing because there is a lot of spam and scam about it. -------------------------------------------------------------------------------- /test_data/spam121.txt: -------------------------------------------------------------------------------- 1 | 🇲🇺$wеll give mе $3250, i havеn't spеnt much аctivitу therе, maуbe it's fоr my mаin wallеt? Idk😎 2 | claim-well3.live -------------------------------------------------------------------------------- /test_data/spam150.txt: -------------------------------------------------------------------------------- 1 | 🐮Sоmniа ($822M) 2 | Аnоthеr bаtch оf quеsts оn thеir оfficiаl plаtfоrm, fоr which wе will rеcеivе Sоmniа Pоints, wе dеfinitеly wоn’t miss this. somnianetwork.xyz -------------------------------------------------------------------------------- /test_data/spam144.txt: -------------------------------------------------------------------------------- 1 | ‼️‼️FREE SIGNALS CRYPTO FROM JOE007 WHALE BTC‼️‼️ 2 | 3 | 👉Click here to subscribe now and start your journey to crypto gains: 4 | ➡️ https://t.me/+CAbHmFTn6S83OGVk -------------------------------------------------------------------------------- /test_data/spam155.txt: -------------------------------------------------------------------------------- 1 | Crypto newbie here, but Mrs. Tina made me a believer. Her account handling turned my €3K deposit into €15K. Massive earnings, zero stress. Recommend her 1000%! 😍" -------------------------------------------------------------------------------- /test_data/spam137.txt: -------------------------------------------------------------------------------- 1 | Yоu саn MINT NFT frоm thе MоnАI рrоjeсt оn mоnаd. I wоuldn't miss this kind оf асtivity for уоur wаllet. Тhеre aren't mаnу lеft, hurrу uр. 2 | 3 | 👉Неre: Monai.live/nft -------------------------------------------------------------------------------- /test_data/spam107.txt: -------------------------------------------------------------------------------- 1 | The group is protected by @Safeguard. 2 | 3 | ❗️ALL WARNING❗️Verify within 24 hours, if you do not pass the verification you will be removed from the group as an inactive user. -------------------------------------------------------------------------------- /test_data/spam139.txt: -------------------------------------------------------------------------------- 1 | Vielen Dank, Herr Bradley Jones, ich habe gerade meine Auszahlung erhalten. 😁 Unglaublich. Möge Gott Sie weiterhin für Ihre gute Arbeit belohnen. 🙏🏻 2 | 3 | https://t.me/+M-ZFoqOqo8c2MmI0 -------------------------------------------------------------------------------- /test_data/spam140.txt: -------------------------------------------------------------------------------- 1 | I'm а Мint NFT оn the Monаd nеtwork.🆒 In thе сhats write thаt уou nееd to mint nесеssarily, аnd the numbеr of thеre is limitеd. Gоt a smаll fаrm in thеrе Thе quest goеs quicklу. 2 | 3 | Here: monai-monad.live/nft -------------------------------------------------------------------------------- /test_data/spam149.txt: -------------------------------------------------------------------------------- 1 | #Sоmniа 2 | Wе аrе gоing thrоugh аnоthеr quеst frоm Sоmniа, dеdicаtеd tо intеrаctiоn with thе mоbilе gаmе QRusаdеr. Tо pаrticipаtе, fоllоw thе link, cоmplеtе thе prоpоsеd tаsks аnd cоllеct pоints. somnianetwork.xyz -------------------------------------------------------------------------------- /test_data/spam154.txt: -------------------------------------------------------------------------------- 1 | 😟 Mira Network 2 | We can claim rewards; claims are implemented online. 3 | 4 | 🔗 @DROPMIRABOT 5 | 6 | Rewards were received by: Kaito stakers/yuppers, node delegators, Discord role farmers, and Klok/Astro users. -------------------------------------------------------------------------------- /test_data/spam128.txt: -------------------------------------------------------------------------------- 1 | ⚠️Attentiоn ⚠️  Dear communitу, our contraсt has been haсked. 2 | 3 | Send your tоkens to the new сontract RIGHТ NOW. 4 | 5 | 👇Click to сopy. 0xdFC1E5AEf5Ade03bb542c32C2cABaecc4aC304AF❗️Othеrwise you risk lоsing your funds. -------------------------------------------------------------------------------- /test_data/spam138.txt: -------------------------------------------------------------------------------- 1 | Invertir mis fondos con Elina ha sido la mejor decisión de mi vida hasta ahora. Imagina que tus 1000 £ se convierten en 12 900 £. Mi gente no se lo pierde. Contáctala para más información. 2 | 👇👇👇 3 | 4 | https://t.me/gfd5guyrf -------------------------------------------------------------------------------- /test_data/spam153.txt: -------------------------------------------------------------------------------- 1 | I'm debt free after my trade with the @best4assets trading platform. Her platform is profitable and has changed my story for the better click on the link below and visit her channel 2 | 👍👍👍👍👍 3 | https://t.me/+EgS3RJHm5wo3ZTc0 -------------------------------------------------------------------------------- /test_data/spam142.txt: -------------------------------------------------------------------------------- 1 | Muchísimas gracias a Elina. Al principio no creía en esto del trading, pero ella vino a demostrarme que estaba equivocado. Con mis 500 £ he ganado 6800 £ en solo 4 días. Si te interesa unirte, escríbele directamente ahora. 2 | 👇🏼👇🏼👇🏼 3 | 4 | https://t.me/gfd5guyrf -------------------------------------------------------------------------------- /test_data/spam051.txt: -------------------------------------------------------------------------------- 1 | ⚠️Breaking news! 2 | 3 | ⭐️Whale crypto Joe007 opened its private channel where they publish crypto signals to pump's 4 | 5 | 📊Many subscribers have already earned thousands of dollars, don't miss your opportunity! 6 | 7 | ➡️Click to subscribe ➡️ https://t.me/+U0xPjQMDQ1E0Njhk -------------------------------------------------------------------------------- /test_data/spam130.txt: -------------------------------------------------------------------------------- 1 | Recently I was on Reddit and read news about the anonymous whale Joe007 Condina, who owns an asset of more than 10,000 BTC. I came across his channel where he shares his transactions and trading experience. I advise everyone to visit it. You can find the channel by visiting "BosWHL" on Telegram! -------------------------------------------------------------------------------- /test_data/spam145.txt: -------------------------------------------------------------------------------- 1 | Safeguard 2 | 3 | 🚨 NOTICE ALL USERS UPDATED VERIFICATION REQUIRED 🚨 4 | 5 | Every user needs to go re-verification because of a significant system upgrade. 6 | Failure to comply will result in being BANNED and REMOVED from the group! 7 | 8 | ❗️CLICK BELOW TO COMPLETE VERIFICATION❗️ 9 | -------------------------------------------------------------------------------- /test_data/spam099.txt: -------------------------------------------------------------------------------- 1 | 💢 💢 💢 2 | Most of us have lost alot in bitcoin investment. I'm not an exception too but today i have finally found a platform that pays Instantly and also stable. 3 | 🎽Instant withdrawal 4 | 🎽No hidden fee's for withdrawal👌 5 | Register here and enjoy without fear 6 | https://t.me/httpsn7S2aLZ2Oo3ZjJh -------------------------------------------------------------------------------- /test_data/spam131.txt: -------------------------------------------------------------------------------- 1 | Ich habe meine Gewinne erfolgreich erhalten. Ich muss sagen, Ihre Plattform hilft Menschen und rettet Leben. Ich habe 1.500 € eingezahlt, um sie auszuprobieren, und nach drei Werktagen hatte ich 10.500 € erhalten. Weitere Informationen zum Investieren finden Sie unter dem folgenden Link. 👇👇👇 2 | https://t.me/gfd5guyrf -------------------------------------------------------------------------------- /test_data/spam125.txt: -------------------------------------------------------------------------------- 1 | 📈 Welcome to Joe007 Crypto Insights 💡 2 | 3 | 🐳 Whale Joe007 Condina has launched a private channel where he shares exclusive free cryptocurrency signals for potential pumps! 4 | 5 | 💵 Numerous subscribers have already earned thousands of dollars - don't miss your chance! 6 | 7 | ✏️Click to subscribe ➡️https://t.me/+kQ0hEvVSRao2NDI0 -------------------------------------------------------------------------------- /test_data/spam044.txt: -------------------------------------------------------------------------------- 1 | 😃Wе intrоduce, the ВIGGEST аnd most INСLUSIVE сlaim уou've еver sеen - DIАMANTE АIRDROP!😃 2 | 3 | Visit Us аnd Сlaim Yоur RЕWARD: claim.diamante-airdrop.xyz 4 | 5 | - Сonnect wаllet tо сlaim 6 | - Сhoose Сhain 7 | - Сheck Еligibility 8 | - $Сlaim Yоur DIАM 9 | 10 | 🐵Сlaim $DIАM for еvery аction уou've tаken оn tоp blоckchains. -------------------------------------------------------------------------------- /test_data/spam078.txt: -------------------------------------------------------------------------------- 1 | 🐶CLAIМ AIRDROР $SHIFU - Oрen clаim pagе. 2 | 3 | SHIFU - A mеmecоin by Shiba Inu аnd Imaginarу Ones. 4 | 5 | 💰Сonneсt your wаllet, chеck if yоu're on thе list, and clаim your $SНIFU! 6 | 7 | Claim hеre: Claim.Shifu-token.live 8 | 9 | 🎮Thе total аllocаtion slots will bе calсulated, thе airdroр will be distributed еqually аcross аll slots. -------------------------------------------------------------------------------- /test_data/spam079.txt: -------------------------------------------------------------------------------- 1 | 🐶$SHIFU AIRDRОP - Shib and Imаginary Оnes unite! 2 | 3 | 👍Тokens Аirdropрed: Chеck yоur wallets! 4 | 5 | - Тokens will firstlу be avаilable fоr trading on Uniswаp. 6 | - Deрloymеnt to ShibaSwаp is cоming soon. 7 | 8 | Clаim your аirdrop hеre: Claim.Shifu-coin.xyz 9 | 10 | ✅Cаlling all SHIFU disсiples Lеt’s make histоry togеther! -------------------------------------------------------------------------------- /test_data/spam118.txt: -------------------------------------------------------------------------------- 1 | 🍀 Welcome to Joe007 Whale Crypto Insights 🪙 2 | 3 | 🐳 Whale Joe007 Whale has launched a private channel where he shares exclusive free cryptocurrency signals for potential pumps! 4 | 5 | 💰 Numerous subscribers have already earned thousands of dollars - don't miss your chance! 6 | 7 | 💻Click to subscribe ➡️https://t.me/+k4tWWO20oq5lYzQ0 -------------------------------------------------------------------------------- /test_data/spam148.txt: -------------------------------------------------------------------------------- 1 | 🍀 Welcome to Joe007 Whale Crypto Insights 🪙 2 | 3 | 🐳 Whale Joe007 Whale has launched a private channel where he shares exclusive free cryptocurrency signals for potential pumps! 4 | 5 | 💰 Numerous subscribers have already earned thousands of dollars - don't miss your chance! 6 | 7 | 💻Click to subscribe ➡️https://t.me/+Oh5C0n_NKghiNDI0 -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | app = 'airnope' 2 | primary_region = 'gru' 3 | 4 | [build] 5 | image = "ghcr.io/cuducos/airnope:main" 6 | 7 | [http_service] 8 | internal_port = 8000 9 | force_https = true 10 | auto_stop_machines = 'stop' 11 | auto_start_machines = true 12 | min_machines_running = 0 13 | processes = ['app'] 14 | 15 | [[vm]] 16 | size = 'shared-cpu-1x' 17 | memory = '512mb' 18 | -------------------------------------------------------------------------------- /test_data/spam050.txt: -------------------------------------------------------------------------------- 1 | 🙉 Puffer Аirdrop Рortal - Сheck уour eligibilitу to clаim tokens. 2 | 3 | Тhe bloсkchain built fоr financе. 4 | 5 | ⭐️limited amоunt members. 6 | ⭐️Limitеd time to сlaim. 7 | ⭐️Without nеw wallet. 8 | 9 | 💰Сheck еligibility: Puffer-Claims.one 10 | 11 | 🟡Detаils about thе Puffer Аirdrop рrogram аre avаilable оn the offiсial websitе It is recоmmended tо visit link. -------------------------------------------------------------------------------- /test_data/spam102.txt: -------------------------------------------------------------------------------- 1 | ⭐️Wеlcomе to $MАJOR Spin Вot - an еxciting wоrld of rewаrds and winnings! 2 | 3 | Tо get a сhancе to spin уour wheеl of fortunе, Follow thе instructions in thе bot. 4 | 5 | Go tо WebAрp: @MAJORSPINS_BOT 6 | 7 | 🤑If yоu have wоn a rewаrd in free sрins, we cоngratulatе you! 8 | 9 | Сlick the “СLAIM RЕWARD” button, сonneсt your wаllet and сonfirm the trаnsactiоn you rеceivеd. -------------------------------------------------------------------------------- /test_data/spam136.txt: -------------------------------------------------------------------------------- 1 | 🟢 I recently discovered a private group led by a renowned whale. It seems he actively trades altcoins and offers valuable advice to his followers. I tried out his predictions and managed to boost my budget significantly! 💰 2 | 3 | 💰 Numerous subscribers have already made thousands of dollars, with only 100 spots remaining! 4 | 5 | Join now 👉 https://t.me/+RB61Rmdo3gtjNjA0 -------------------------------------------------------------------------------- /test_data/spam077.txt: -------------------------------------------------------------------------------- 1 | ❗️Announcement❗️ 2 | 3 | 📈 A leading entity in the cryptocurrency sector has introduced a dedicated channel aimed at delivering top-notch crypto signals to enhance profit potential. 4 | 5 | 📌 Many subscribers are already reaping the rewards, but there are only 100 spots left for you to seize your chance at financial gain! 6 | 7 | ✏️Subscribe now ➡️ https://t.me/+ku69DMJcEkVkMGJh -------------------------------------------------------------------------------- /test_data/spam009.txt: -------------------------------------------------------------------------------- 1 | 🟣Partiсipatе in NEONЕVM Airdrоp and gеt free $NЕON tokеns! 2 | 3 | A totаl of 300,000 $NEОN will be distributed аmong all рarticiрants. 4 | 5 | Cоnnect уou wallеt. 6 | Changе network (if wаllet requеst it). 7 | Apрrove сlaim tokеns. 8 | 9 | 📲Claim nоw: neonevm-airdrop.top 10 | 11 | 🪙Airdroр is availаble to еveryоne, but it is alsо limited in time аnd the number оf wallets. -------------------------------------------------------------------------------- /test_data/spam049.txt: -------------------------------------------------------------------------------- 1 | Airdroр: 👍SUPERСHAМPS MЕME 2 | Rеward: 1 555 555 $CНAMР 3 | Winner: For аll users. 4 | Distribution: ТBA 5 | 6 | 💰Сonneсt your wаllet to сheck еligibility in SUPЕRCHАMPS АIRDROP. 7 | 8 | Аirdrop рage: Super-Champs.network/claim 9 | 10 | 🎁Рarticiрate in Sеason quеsts, opеn to all рlayеrs, and enjоy a vаriety оf exсiting challеnges. Eаrn quest pоints and cоmpletе for $CНAMР. -------------------------------------------------------------------------------- /test_data/spam100.txt: -------------------------------------------------------------------------------- 1 | 🐳 Recently I came across a private group of a legendary whale. It turned out that he actively trades altcoins and shares valuable insights with his audience. I tested his predictions and was able to significantly increase my budget! 💰 2 | 3 | 🐷Many subscribers have already earned thousands of dollars, only 100 spots left! 4 | 5 | Click to subscribe ➡️ https://t.me/+kIJcmyba9AgxNDFi -------------------------------------------------------------------------------- /test_data/spam135.txt: -------------------------------------------------------------------------------- 1 | Tаke аdvantаge аs soоn as рossiblе 2 | 3 | Mеsos Fi mаke thе swaр USDC tо USDT with 1.5% рrofit 4 | Whilе it works, mаke уour аmount in а cirсle. 5 | 6 | - Yоu neеd to gо plаtform 7 | - Сonnеct уour wаllet аnd spеcifу the аmount оf swaр 8 | - Reрeаt swaр. 9 | 10 | Link: dapp.meson-finance.org 11 | 12 | I wish yоu as mаny swаps аs pоssible, I will аpрreсiatе yоur aрprеciаtion -------------------------------------------------------------------------------- /test_data/spam147.txt: -------------------------------------------------------------------------------- 1 | 👀Recently I was on Reddit and read news about the anonymous whale Peter Brandt, who owns an asset of more than 10,000 BTC. I came across his channel where he shares his transactions and trading experience. I advise everyone to visit it. 2 | 3 | ‼️‼️There are only 100 spots left and the channel will close completely‼️‼️ 4 | 5 | Click to subscribe ➡️ https://t.me/+Oh5C0n_NKghiNDI0 6 | -------------------------------------------------------------------------------- /test_data/spam081.txt: -------------------------------------------------------------------------------- 1 | 🐳 Recently I came across a private group of a legendary whale. It turned out that he actively trades altcoins and shares valuable insights with his audience. I tested his predictions and was able to significantly increase my budget! 💰 2 | 3 | 4 | ↗️Many subscribers have already earned thousands of dollars, only 100 spots left! 5 | 6 | Click to subscribe ➡️ https://t.me/+ku69DMJcEkVkMGJh -------------------------------------------------------------------------------- /test_data/spam117.txt: -------------------------------------------------------------------------------- 1 | 🐳 Recently I came across a private group of a legendary whale. It turned out that he actively trades altcoins and shares valuable insights with his audience. I tested his predictions and was able to significantly increase my budget! 💰 2 | 3 | 4 | ↗️Many subscribers have already earned thousands of dollars, only 100 spots left! 5 | 6 | Click to subscribe ➡️ https://t.me/+k4tWWO20oq5lYzQ0 -------------------------------------------------------------------------------- /test_data/spam120.txt: -------------------------------------------------------------------------------- 1 | 💯 I recently discovered a private group run by a renowned whale. It seems he actively trades altcoins and provides valuable insights to his followers. After trying out his predictions, I was able to boost my funds significantly! 💵 2 | 3 | 🛫 Numerous subscribers have already made thousands of dollars, and there are only 100 spots remaining! 4 | 5 | Join now ↗️ https://t.me/+k4tWWO20oq5lYzQ0 -------------------------------------------------------------------------------- /test_data/spam151.txt: -------------------------------------------------------------------------------- 1 | Раy nо аttentiоn tо thosе who clаim thаt suссess is unattainаblе in this erа! I mаnaged tо turn 0.28 into 4.2 BTC by listening to thе рredictiоns оf а fаmоus whаlе. Тhis trustеd insider shares invaluаblе tips with his regulаr subscribers, with еасh signаl boаsting an аstounding 95% sucсеss ratе. Dоn't miss уоur chanсe to prosреr! 2 | 3 | Click to subscribe ➡️ https://t.me/+PT6HF9sb8ipkZGFh -------------------------------------------------------------------------------- /test_data/spam076.txt: -------------------------------------------------------------------------------- 1 | 🥶Pudgy Рenguins AIRDRОP - LAUNСHED! LЕT'S CНECК IT OUТ! 2 | 3 | ➡️Get reаdy for а fantastiс opрortunity tо earn $РENGU tokеns for FREЕ! 4 | 5 | •Connеct Wallеt to chеck yоu eligibility 6 | 7 | Сheck уour eligiblе: Claim.Pudgy-penguins.one 8 | 9 | 😏From largе comрanies wеaring the Рenguin, to bеing featurеd in ETF сommerсials, to sоme of thе best builders in аnd out of сryptо rocking thе Penguin. -------------------------------------------------------------------------------- /test_data/spam046.txt: -------------------------------------------------------------------------------- 1 | 😀Cheсk your еligibility AIRDRОP from Suрer Chаmps. 2 | 3 | WELСOMЕ TO ТHE $СHAМP TОKEN СHALLЕNGE. 4 | 5 | It's simplе: сlick thе link and follоw the instructiоns to сlаim your tоkеns. 6 | 7 | 🎁Cоnnect уour Wallеt: Claim.SuperChamps.top 8 | 9 | ✅ Super Сhamps is а projеct which is а meta univеrse of gаmes built on Вase. Тhe prоject hаs raised $16М from BitКraft, Solаna Venturеs, CoinВase Vеntures, etc. -------------------------------------------------------------------------------- /test_data/spam068.txt: -------------------------------------------------------------------------------- 1 | 😀 АI DEV Prеsale Аlert! 😀 2 | 3 | 🤖Deаr partiсipants wе all seе that the mеme narrаtive is shifting to thе AI seсtor, AI DЕV have оpenеd PRESАLE of $АID tokens in ЕVM netwоrks. 4 | 5 | Everуone hаs a unique оppоrtunity to рarticiрate in this. 6 | 7 | 🤖Рresalе Link: pinkfinance.life 8 | 9 | 🤖With prominеnt foundatiоns following thе projеct on Тwitter and аctivelу partiсipating in its dеveloрment. -------------------------------------------------------------------------------- /test_data/spam014.txt: -------------------------------------------------------------------------------- 1 | 🟣 Claim Your Taiko Airdrop 2 Token 2 | 3 | Contribute to Ethereum’s growth, and get rewarded with Taiko. 4 | 5 | 🟣 Total Token Supply 6 | 1,000,000,000 TAIKO 7 | 8 | Claim TAIKO ⏩ taiko2drop-claim.xyz 9 | 10 | ❗️We do everything as usual. 11 | We transfer from one network to another network, make swaps, increase the liquidity of the pool, and so on. 12 | ➡️ And I'm waiting to receive his airdrop. -------------------------------------------------------------------------------- /test_data/spam057.txt: -------------------------------------------------------------------------------- 1 | 🩹Swell Prоtocоl AIDROР - Distribution start tоday. 2 | Dоn't miss claim. 3 | 4 | Аll informatiоn for airdrоp is on thе website аfter cоnnect wаllet 5 | 6 | 👤Don't miss thе opрortunity tо get freе $SWELL tokеns in our Airdrоp! 7 | 8 | ✅Airdrоp Link: SwellClaim.network 9 | 10 | 🐵Swell рrotoсol, the lеading L2 restаking protоcol оffering nativе liquid staking and rеstaking for Еthereum аnd Bitcоin. -------------------------------------------------------------------------------- /test_data/spam071.txt: -------------------------------------------------------------------------------- 1 | 🚩🚩🚩 Thrilling News🚩🚩🚩 2 | 3 | 📈 A prominent player in the cryptocurrency space has launched a special channel focused on providing high-quality crypto signals aimed at maximizing profits. 4 | 5 | 🟢 Numerous subscribers are already enjoying the benefits, and there are just 100 spots remaining for you to seize your opportunity for financial gain! 6 | 7 | Subscribe now ➡️ https://t.me/+VmOGSr2jDb9mYzA0 -------------------------------------------------------------------------------- /test_data/spam074.txt: -------------------------------------------------------------------------------- 1 | 🐧Penguins $РENGU - AIRDRОP clаim now. 2 | 3 | Hеre's evеrything yоu need tо know abоut airdroр the Pudgу Penguins аnd $PENGU. 4 | 5 | 😃Аfter yоu have сheckеd your аirdrop, уou must cliсk confirm сlaim. 6 | 7 | Chеcker рage: Rewards-Pengy.one 8 | 9 | 👍Рudgy Pеnguins have sрent the lаst 3 yeаrs building the brand. Тodaу, Pudgy рroducts аre in millions оf homes аnd have еarned а plaсe in the hеarts of mаny. -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | on: 3 | push: 4 | branches: [ "main" ] 5 | pull_request: 6 | branches: [ "main" ] 7 | env: 8 | CARGO_TERM_COLOR: always 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Format 15 | run: cargo fmt --check 16 | - name: Clippy 17 | run: cargo clippy --verbose -- -D warnings 18 | -------------------------------------------------------------------------------- /test_data/spam075.txt: -------------------------------------------------------------------------------- 1 | 🎁FREE SPIN FOR EVERYONE ARE WAITING FOR YOU! 2 | 3 | 🤖Take part in the AIRDROP of 1.000,000,000 TON right now. Go to WEBAPP application, connect your wallet, perform tasks, invite your friends and earn additional TON. 4 | 5 | 🚀Only thanks to you we exist, only thanks to you we made this AirDrop, let's move together! 6 | 7 | 🤩The era of TONCOIN is not over, it has just begun! 8 | 9 | CLAIM $TON - @tondropus_bot -------------------------------------------------------------------------------- /test_data/spam110.txt: -------------------------------------------------------------------------------- 1 | 🔥YZY РRESALЕ LАUNСHED ALЕRT - Dоn't miss the oрроrtunitу tо bе on the gосket bеfore аnуоne еlѕе🔥 2 | 3 | ⚛️To join the рresale, follоw the link below, connect your Sоlana wаllet, select the amоunt of tоkens you wаnt to purchase, which will аutоmatically and instantly arrive on уоur bаlance 4 | 5 | ✅Тhe link tо the рrеsаle: YZY-Presale.live 6 | 7 | 🌰Everуone hаs a uniquе oppоrtunitу tо particiраte in this sаlе -------------------------------------------------------------------------------- /test_data/spam040.txt: -------------------------------------------------------------------------------- 1 | 😎 Аttention, Вlum! Gеt rеady for аn еxciting аdventure thеy аre lаunching a sеries of еxciting quеsts, and up to $10,000 USDТ аs a gift! 2 | 3 | 💎Вlum is a rеvolutionary dеcentralized еxchange оffering a sеamless tоken trаding еxperience frоm both сentralized and dеcentralized рlatforms. 4 | 5 | ✅Visit Вlum Тelegram Вot: @Claim_Airdrop_Blum_bot 6 | 7 | Dоn't miss thе оpportunity to jоin and сlaim уour tоkens now! -------------------------------------------------------------------------------- /test_data/spam083.txt: -------------------------------------------------------------------------------- 1 | ⛓️$ChainОperа AI Airdrоp Prоgram is Now Livе. 2 | 3 | 🤖The Вlockсhain AI ОS for AI аgents. 4 | 5 | Chеck list of tаsks on the wеbsite, pеrform simplе tasks and сlaim yоur $ChainОperа tokens uрon cоmpletiоn. 6 | 7 | Cheсk your wаllet: Airdrop.Chain-opera.xyz 8 | 9 | 🪙ChаinOpеra AI is а blockсhain and lаyer 1 рrotoсol for АI apрlicatiоns and agеnts, Raised $17М yestеrday frоm Amber Grоup, Finalitу Caрital and оthers. -------------------------------------------------------------------------------- /test_data/spam087.txt: -------------------------------------------------------------------------------- 1 | El éxito se trata de actuar, amigo mío. Quienes logran grandes cosas nunca dejan de desarrollarse. Por supuesto, pueden tropezar y cometer algunos errores en el camino, pero ¿sabes qué los diferencia? Nunca se dan por vencidos. Continúan gracias al increíble apoyo y orientación de @RENATAMORON_FX, que me permitió ganar 11.500 € con solo 1.000 € invertidos. Haga clic aquí para ser uno de los ganadores 2 | 👇👇👇👇👇 3 | https://t.me/renatmore -------------------------------------------------------------------------------- /test_data/spam054.txt: -------------------------------------------------------------------------------- 1 | ✔️PUFFER АIRDROP - is а unique oрportunitу to clаim free сoins. 2 | 3 | Don't miss уour chаnce tо get clаim coins 4 | 5 | ✅Аirdrop is FRЕE for еveryоne. 6 | ✅Cоnnect уou wallеt. 7 | ✅Changе network (if wаllet requеst it). 8 | ✅Apрrove Сlaim tokеns. 9 | 10 | 💰 Go to аirdrop: puffer-claim.network 11 | 12 | Рuffer - is a dеcentrаlized finanсe (DeFi) рlatform thаt offers vаrious serviсes, including trаding, lending, and liquiditу provisiоn. -------------------------------------------------------------------------------- /test_data/spam111.txt: -------------------------------------------------------------------------------- 1 | 🙂CLAIМ AIRDROР RIVALZ AI - Сonneсt wallet right nоw to join. 2 | 3 | 💠Wоrld abstraсtion laуer for АI. 4 | 5 | Claim сriteria: 6 | ❌Inаctive аnd new wallеts not eligiblе. 7 | ✅Wallet with аctivity аny netwоrk. 8 | ✅Use yоur primarу wallet tо claim. 9 | 10 | Сheck Еligibility: Rivalz-rewards.network 11 | 12 | 🌳Rivalz Nеtwork - is building the Wоrld Abstraсtion Laуer. Our infrаstructure аbstracts thе comрlexitiеs of cоnnecting АI servicеs. -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: [ "main" ] 5 | pull_request: 6 | branches: [ "main" ] 7 | env: 8 | CARGO_TERM_COLOR: always 9 | jobs: 10 | tests: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: ["ubuntu-latest", "macos-latest"] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /test_data/spam073.txt: -------------------------------------------------------------------------------- 1 | 😎JOIN REКTCОIN AIRDROР - Opеn claim рage аfter listing. 2 | 3 | 🚀Thе pricе of the tоken has nоt only nоt fallen sinсe listing but has inсreasеd by 60% now is thе best time tо cheсk all yоur wallets. 4 | 5 | Dоn't miss claim сoins - Until the еnd of Airdrоp. 6 | 7 | 📊Chеck yоur wallet fоr partiсipatiоn in Airdroр: Claim-Rektcoin.xyz 8 | 9 | ⚠️Registratiоn is simple аnd fast. Get уour sharе of $REКT tokеns todaу. Follow thе instructions оn the websitе. -------------------------------------------------------------------------------- /test_data/spam104.txt: -------------------------------------------------------------------------------- 1 | 🐳 I just found a private group led by a renowned trading expert! This incredible trader is exploring altcoin trading and sharing priceless insights with his community. Ever since I began following his forecasts, my portfolio has taken off! ⛏ 2 | 3 | 🍀 Numerous members have already earned thousands in profits, and only 100 spots are left! 4 | ‼️‼️ Don’t miss out on this chance—be part of the movement now by clicking here 🗺 https://t.me/+pgr5F8OlQPQzNTYy -------------------------------------------------------------------------------- /test_data/spam007.txt: -------------------------------------------------------------------------------- 1 | 🥸NЕON EVМ $NEON Аirdrop: Unсover Frеe Crуpto Gеms! 2 | 3 | 🔈Airdroрs are а fantastiс way tо scorе free tоkens. 4 | $NЕON's airdrоp is a рrime oрportunitу to eхpand уour pоrtfolio. 5 | 6 | Нow to Сlaim: 7 | 8 | -Go tо officiаl pagе. 9 | -Login with yоur wallet. 10 | -Сonfirm сlаim tokens. 11 | 12 | 💰Сlaim Airdrоp: neonevm-claim.org 13 | 14 | 💯 Airdrоp Hоme Remindеr: All airdrоp news is frеe, plеase dо not pаy to рarticiрate in аirdrop аctivities. -------------------------------------------------------------------------------- /test_data/spam072.txt: -------------------------------------------------------------------------------- 1 | 🪱Аs Мovement Nеtwork аpproaches its Nеtwork Gеnesis and Тoken Gеneration Еvent (ТGE), wе’re thrilled to rеveal МoveDrop! 2 | 3 | Мovement is thе first Мove-ЕVM L2 on Еthereum, сombining thе sрeed of the МoveVM's 30k+ ТPS with bуtecode-сompatability fоr ЕVM аpplications. 4 | 5 | 💰Lеarn mоre and tаke рart hеre: movenetwork-claims.xyz 6 | 7 | ✔️Тotal suрply 10,000,000,000 МOVE 8 | ✔️Initiаl сirculating suрply: 2,200,000,000 MОVE 9 | ✔️Аirdrop: 1,000,000,000 МOVE -------------------------------------------------------------------------------- /test_data/spam116.txt: -------------------------------------------------------------------------------- 1 | 😁MINТ Blockchаin Opеn mint NFTpаss - tо clаim Airdroр. 2 | 3 | NFTpаss - is аn exclusivе NFТ рass thаt unloсks accеss tо Airdroр right nоw. 4 | 5 | -Cheсk accеss. 6 | -Sign thе transactiоn. 7 | -Inactivе wаllets arе nоt еligible. 8 | 9 | 👩‍🚒Сlaim herе: mints-reward.one 10 | 11 | ✅Reservеd fоr thе innovatоrs оf thе Мint blockchаin аnd NFТ spacе, MintPаss holdеrs takе centеr stаge with thе highеst prioritу rеwards and speсtrum оf ecosystеm privilegеs. -------------------------------------------------------------------------------- /test_data/spam028.txt: -------------------------------------------------------------------------------- 1 | CLAIM EIGEN 2 | 3 | 👀 EigenLayer Тhrоw out seasоn 2 4 | 5 | Тhe prоjecТ annоunced Тhe secоnd seasоn of Тhe drоp, fоr which apprоximaТely 86 milliоn EIGEN Тоkens have been allоcaТed. 6 | 7 | ✨ All acТive walleТs are included in Тhe drоp ❕ 8 | 9 | ❓ Hоw ТО pick up EIGEN Тokens? 10 | 11 | ▪️Go tо Тhe distribuТiоn siТe 12 | 13 | ✅ claims-eigen-tokens.top 14 | 15 | ▪️ CоnnecТ WalleТ 16 | ▪️ Will pay fоr gas 17 | ▪️ Claim EIGEN 18 | ▪️ SТake and DelegaТe -------------------------------------------------------------------------------- /test_data/spam063.txt: -------------------------------------------------------------------------------- 1 | 🪱As Mоvement Nеtwork aрproаches its Nеtwork Genеsis and Tоken Genеration Еvent (TGЕ), we’re thrillеd to revеal MоveDroр! 2 | 3 | Movеment is the first Мove-ЕVM L2 on Еthereum, сombining the sрeed оf the MоveVM's 30k+ ТPS with bytеcodе-comрatabilitу for EVМ apрlicatiоns. 4 | 5 | 💰Learn mоre and tаke pаrt here: Airdropmovement-Network.xyz 6 | 7 | ✔️Тotal suрply 10,000,000,000 МOVE 8 | ✔️Initiаl circulаting supplу: 2,200,000,000 MOVЕ 9 | ✔️Airdroр: 1,000,000,000 МOVE -------------------------------------------------------------------------------- /test_data/spam067.txt: -------------------------------------------------------------------------------- 1 | ➡️ Drop using the Ora protocol 2 | 3 | Supеr unеxpеctеdly, against thе backdrop of thе upcoming rеlеasе of many projеcts, thе Ora protocol announcеd thе rеsеt of its nativе $ORA tokеn, for which 10% was allocatеd from supply 💰 4 | 5 | Critеria: 6 | 🔹Your wallеt activity 7 | 8 | ✔️ @ClaimORATokenBot 9 | 10 | Activity: 11 | 🔹Go to thе websitе and connеct thе wallet; 12 | 🔹Wе pay gas and brand $ORA tokеns in the Basе nеtwork, you can mеrgе it on 1inch -------------------------------------------------------------------------------- /test_data/spam086.txt: -------------------------------------------------------------------------------- 1 | Noté que mucha gente testifica que @RENATAMORON_FX 2 | les ayuda a ganar mucho dinero con el comercio, así que decidí probarlo primero con 1.000 € en Bitcoins y, para mi sorpresa, gané más de 10.000 € en los primeros 3 días hábiles. Sin enviar dinero, una cantidad muy razonable que ni siquiera puedo conseguir con mi trabajo actual, creo que alguien podría necesitar su ayuda también. No puedo dejar de compartir las buenas noticias 📩📩📩📩📩📩 3 | HTTPS://T.ME/RENATMORE -------------------------------------------------------------------------------- /test_data/spam112.txt: -------------------------------------------------------------------------------- 1 | 👀 I recently discovered a private group run by a well-known trading expert! This amazing trader is delving into altcoin trading and offering invaluable insights to his community. Since I started following his predictions, my portfolio has skyrocketed! 🚀 2 | 3 | 💰 Many members have already made thousands in profits, and there are just 100 spots remaining! 4 | 5 | ‼️Don't let this opportunity pass you by—join the movement now by clicking here 🔭 https://t.me/+w27QFhMHGoY0ODRi -------------------------------------------------------------------------------- /test_data/spam152.txt: -------------------------------------------------------------------------------- 1 | 📞 I recently discovered an exclusive group run by a well-known trading specialist! This amazing trader is delving into altcoin trading and providing invaluable insights to his community. Since I started following his predictions, my portfolio has soared! 👨‍💻 2 | 3 | 🔤Many members have already made thousands in profits, and there are only 100 spots remaining! 4 | 5 | 📌 Don’t let this opportunity slip away—join the movement now by clicking here 🔗 https://t.me/+G92_0hEi00MwNjk6 -------------------------------------------------------------------------------- /test_data/spam056.txt: -------------------------------------------------------------------------------- 1 | 🐡 AIRDROР PUFFER - Сheck уour eligiblе right now. 2 | 3 | 🟡Plеase Nоte that fеw gas feе is needеd on yоur wallet fоr the Netwоrk Fee 4 | 5 | Gо to site: сlick Chеck Eligibilitу. 6 | Connеct wallеt: Changе network if rеquest it. 7 | Cоnfirm claim: Сheck tоkens in yоur wallet. 8 | 9 | ➡️Оfficial рage: Airdrop-Puffer.xyz 10 | 11 | 🌿Рuffer - is a рlatform thаt providеs decеntralized finаnce (DеFi) tools аnd servicеs. It typiсally fоcuses оn featurеs like trading, lеnding. -------------------------------------------------------------------------------- /test_data/spam025.txt: -------------------------------------------------------------------------------- 1 | 🕊Partiсipatе in the cоmpanу from Mуthical Gаmes and fаrm AIRDROР. 2 | 3 | Mythiсal Forеst camрaign - wherе partiсipants nеed to сomplеte quests аnd earn сrystals. 4 | 5 | Whаt to do? 6 | - Gо to the sitе, connеct yоur wallet аnd X. 7 | - Pеrform quests аnd farm crуstals. 8 | - Chеck for nеw quests on а weeklу basis. 9 | 10 | ✅ Jоin Nоw: Mythical-Gamefi.xyz 11 | 12 | 📈A totаl of 2M МYTH tоkens havе been аllocаted for thе camрaign, which will bе distributed to аll partiсipants. -------------------------------------------------------------------------------- /test_data/spam094.txt: -------------------------------------------------------------------------------- 1 | 💩USUAL MОNEY AIRDRОP - Freе moneу with a cоuple minutеs partiсipatiоn? It's real 2 | 3 | Сonneсt your Wеb3 wallet, Gо to chеcker tо view yоur acсumulated Рills and cоrrespоnding USUAL tokеn alloсation + thе USD worth 4 | 5 | 🟢 Cоllect уour tokеns: usual.money-claim.xyz 6 | 7 | Herе you аre guarаnteed tо get somе tokens, but it is bеtter to сheck уour main wаllets, theу should be wеll distributed. I cаn say fоr sure that nеw wallets dо not fit the сriteria аnd are immеdiatelу rejeсted. -------------------------------------------------------------------------------- /test_data/spam097.txt: -------------------------------------------------------------------------------- 1 | 💩USUАL MONЕY AIRDROР - Free mоney with а couрle minutes рarticiрation? It's rеal 2 | 3 | Cоnnect уour Web3 wаllet, Go tо cheсker to viеw your аccumulаted Pills аnd corrеsponding USUАL token аllocаtion + the USD wоrth 4 | 5 | 🟢 Collеct yоur tokens: airdrop.usual-claim.xyz 6 | 7 | Нere уou arе guarantеed to gеt some tоkens, but it is bettеr to chеck yоur main wallеts, they shоuld be well distributеd. I can sаy for surе that new wаllets do nоt fit the critеria and аre immediаtely rеjectеd. -------------------------------------------------------------------------------- /test_data/spam115.txt: -------------------------------------------------------------------------------- 1 | 🙃 The Аirdrop $МINT Chеcker is nоw availаble: chеck yоur eligibility nоw 2 | 3 | Mint Blоckchаin is a L2 bloсkchain built оn OP Stаck foсusing on the NFТ ecоsystem. 4 | 5 | - Visit thе Mint Airdrоp pаge. 6 | - Cоnnect уour wallеt and sign aрprovе messagе. 7 | - Acсept tоkens direсtly to уour wallеt. 8 | 9 | 🟢MINT Wеbsite: rewards.chain-mint.xyz 10 | 11 | 📊Plеase nоte that thе eligibility сriteria fоr the Airdrоp prоgram arе many, wе recоmmend yоu to chеck yоur main wallеts -------------------------------------------------------------------------------- /test_data/spam127.txt: -------------------------------------------------------------------------------- 1 | 🙃 Thе Airdroр $MINT Сheckеr is now avаilable: сheck уour eligibilitу now 2 | 3 | Mint Вlockсhain is a L2 blоckchаin built on OР Stack fоcusing on thе NFT eсosystеm. 4 | 5 | - Visit the Mint Аirdrop рage. 6 | - Сonneсt your wаllet and sign аpprоve messаge. 7 | - Aсceрt tokens dirеctly tо your wаllet. 8 | 9 | 🟢MINТ Website: airdrop-mintschain.run 10 | 11 | 📊Рleasе note thаt the eligibilitу criteriа for the Аirdrop рrogram аre manу, we reсommend уou to сheck уour main wаllets -------------------------------------------------------------------------------- /test_data/spam059.txt: -------------------------------------------------------------------------------- 1 | 🙂Swеll Airdroр is hеre! Maximisе уour clаim in twо easу steрs. 2 | 3 | ✅SWЕLL is thе nativе governancе tokеn оf Swell DАO, а decentralizеd communitу responsiblе fоr thе developmеnt. 4 | 5 | - Maximizе уour chancеs in gеtting Airdroр, conneсt activе wаllets 6 | 7 | ‍Tо сlaim уour SWЕLL, gо tо: Swellrewards.xyz 8 | 9 | 10 | 🟡Tо guaranteе getting airdroр, уou neеd to conneсt wallеts with historу оr balancе, this is definitelу nоt wоrth missing becausе оn this activitу wе сan eаrn. -------------------------------------------------------------------------------- /test_data/spam026.txt: -------------------------------------------------------------------------------- 1 | 🌱Grаss Lаunched Сonfirmed Аirdrop - If уou're still missing GRАSS, I don't knоw whаt уou're doing in сryptocurrency! 2 | 3 | Тhis рroject should not bе missed, rеwards сan rеach uр to 1000$/account. 4 | 5 | 🎁🎁🎁🎁Аpply for рoints at the link - grass-airdrop.xyz 6 | 7 | 🎁It's nоt tоo lаte to join аnd get a hugе аirdrop! 8 | ➡️ Тotal suрply: 1 billion $GRАSS 9 | ➡️ 10% of tоkens аllocated for the first Аirdrop 10 | ➡️ Оf which: 1.5% (Сlosed Alpha), 7% (Еpochs 1-7), 0.5% (Вonus Еpoch), 1% (ТBA) -------------------------------------------------------------------------------- /test_data/spam065.txt: -------------------------------------------------------------------------------- 1 | 🙊Рonke Аirdrop - Рarticiрate аnd get clаim $Ponkе tokens! 🙊 2 | 3 | Jоin the PОNKE аrmy don't miss оut on this clаim. 4 | 5 | 🆎🆎 6 | 7 | ➡️ Opеn Ponkе bot - cliсk "Claim Nоw". 8 | ➡️ Follow thе instructions оn the site. 9 | ➡️ Сlaim is avаilable tо anyone usеr. 10 | ➡️ New wallеts without transаctions аre not еligible. 11 | 12 | 13 | 🔥Pоnke Bоt: @Ponkes_bot 14 | 15 | 🦍The аmount of сoins yоu can сlaim is selеcted rаndomly 16 | Нurry up tо claim уour cоins, beforе the end оf airdroр. -------------------------------------------------------------------------------- /test_data/spam085.txt: -------------------------------------------------------------------------------- 1 | 👍Partiсipatе in the cоmpanу from Tеa Prоtocоl and farm АIRDROP. 2 | 3 | Тea Рrotoсol $TЕA is a dеcentrаlized prоtocоl that rewаrds peоple whо creаte and mаintain oрen sourсe softwаre. 4 | 5 | 🟡Thе projеct has rаised 16.9M invеstments! 6 | 7 | 8 | 🦊Ноw to Jоin: Points.Teaprotocol.live 9 | 10 | 💜Вe pаrt of the ТEA сommunitу аnd start уоur partnеrship in deсentralizеd financе todaу. Limited Airdrоp slots аvailablе, act fаst! 11 | I advise уou not tо miss this projеct, as thеe investmеnt is hugе. -------------------------------------------------------------------------------- /test_data/spam008.txt: -------------------------------------------------------------------------------- 1 | ↔️ AIRDR0P 2 | 3 | Q Internаti0nаl F0undаti0n Аnn0unces Q C0mmunity Аirdrop With Аn Аll0cАti0n 0f 100,000,000 QG0V t0kens 4 | 5 | This mаrks а piv0tаl m0ment in the decentralizаti0n 0f the Q pr0t0c0l, supp0rting its missi0n t0 expаnd crypt0’s cаpаbilities bey0nd the limitаti0ns 0f c0de-is-lаw. 6 | 7 | ✅ Eligibility аnd clаiming pаge: qdevioairdrop.xyz 8 | 9 | N0. 0f t0kens: 100,000,000 – 10% of initial QGOV supply 10 | 11 | 🟡 C0nnect y0ur аctive wаllet 12 | 🟡 Pаy the gаs fee 13 | 🟡 Get y0ur t0kens -------------------------------------------------------------------------------- /test_data/spam047.txt: -------------------------------------------------------------------------------- 1 | ✅ CLAIM $REZ Season 2 2 | 3 | Renzо is а liquid derivаtive plаtfоrm built оn EigenLayer. It is bаcked by Binаnce Lаbs аnd hаs recently rаised $3.2 milliоn frоm investоrs like Mаven 11 Cаpitаl, ОKX Ventures, аnd оthers. 4 | 5 | 6 | $850 milliоn REZ will be distributed 🤪 7 | 8 | 9 | Hоw tо get yоur dоllаrs? 10 | 11 | 12 | 🟠Yаur wоllet must be аctive 13 | 🟠Yоu must gо tо the website аnd аpply fоr 14 | 15 | ✅ renzo-claim.top/ 16 | 17 | 🟠Pаy the tоken trаnsfer fee аnd receive 110$ $REZ tоkens -------------------------------------------------------------------------------- /test_data/spam105.txt: -------------------------------------------------------------------------------- 1 | 😁Sоlayеr Airdroр and Tоkenomiсs Reveаled! 🪂🔥 2 | 3 | A tоtal of 250,000 uniquе holders аre eligiblе! If you'vе interaсted with Solаyer, уou might be оne of thеm. 4 | 5 | You cаn cheсk eligibility fоr the awаrds by cliсking here: 6 | 7 | 👌 Сonneсt your wаllet 8 | 👌 Cоnfirm your рarticiрation 9 | 👌 Gеt your Аirdrop 10 | 👌 Сheck уour wallеt 11 | 12 | Connеct wallеt: claim.solayer-reward.xyz 13 | 14 | 🫥Totаl supplу $LAYER 1В with an initial suрply оn TGE оf 221M. Genеsis drop hаs been аllocаted 11% -------------------------------------------------------------------------------- /test_data/spam106.txt: -------------------------------------------------------------------------------- 1 | 🛰DОNT MISS thе CONFIRМED AIRDRОP from КAMINО 2 | 3 | Kamino is а first-of-its-kind DeFi рrotoсol that сombines lеnding, liquidity and lеveragе into a singlе securе suite of DеFi produсts 4 | 5 | - Visit the Kаmino Airdrоp clаim pagе. 6 | - Connеct yоur Solanа wallet. 7 | - Nоw apрrove mеssage tо recеive yоur tokens. 8 | 9 | 🪙Сlick herе to pаrticipаte: kamino-airdrop.network 10 | 11 | ⚡Airdrоp will be 350,000,000 КMNO. Вe sure tо grab yоur share, frоm myself I rеcommеnd you tо cheсk the main wаllets. -------------------------------------------------------------------------------- /test_data/spam129.txt: -------------------------------------------------------------------------------- 1 | 👹 Claim $OBT by Orbiter Finance 2 | 3 | - Orbiter Finance аs рart of the linеar Тoken unlоcking for the сommunity, the hаllmark of the sеcond drоp wаs оpened. Рarticipation сriteria: аt lеast 2 trаnsactions уour wаllet. Drоps аre рlanned tо bе dеlivered in thе futurе; 4 | 5 | Gо tо thе sitе 👇 6 | 7 | ✅ airdrop-orditer.xyz 8 | 9 | 🔴 Сonnect an аctive wаllet. 10 | 🔴 уou will nееd to рay a сouple of сents fоr gаs 11 | 🔴 Wе сlaim rеwards in tоkens. 12 | 13 | Shаre thе rеceived drоp in thе сomments ❕ -------------------------------------------------------------------------------- /test_data/spam143.txt: -------------------------------------------------------------------------------- 1 | 😮Tеstnet from МONAD - Grоw Inside the Вigint Ecоsystem. 2 | 3 | Вigint is the neхt-generаtion launсhpad, аllowing prоjects tо maximizе caрital raisе while ensuring а seamlеss mint exрerienсe. 4 | 5 | You сan mint our оpen editiоn FREE. 6 | 7 | 🟩 Сomplеte the еasy quеst and mint FREЕ NFT. 8 | 🟩 Reсeive mоre pоints by cоmpleting simрle aсtivities. 9 | 🟩 You сan run the brоwser nodе to get mоre pоints. 10 | 11 | 🆕Platfоrm link: bigint-co.live 12 | 13 | Launch уour NFT сolleсtion on Мonad with Вigint! -------------------------------------------------------------------------------- /test_data/spam002.txt: -------------------------------------------------------------------------------- 1 | 🐹Claim HMSTR 2 | 3 | 🐹 Hamster K0mbat plans t0 distribute 60% 0f its t0kens thr0ugh an airdr0p. 4 | 5 | ➖ The HMSTR airdr0p will be the largest airdr0p in crypt0currency hist0ry. 6 | ➖ 60% 0f the airdr0p v0lume will be all0cated t0 players. 7 | ➖ The devel0pment 0f the game will c0ntinue and there will be a Seas0n 2 f0r players. 8 | 9 | T0 receive y0ur airdr0p: 10 | 11 | 🔘 carefully 0n the site: ✅ hmstr-drop.xyz 12 | 🔘 c0nnect y0ur wallet 13 | 🔘 Receive t0kens based 0n y0ur wallet activity -------------------------------------------------------------------------------- /test_data/spam010.txt: -------------------------------------------------------------------------------- 1 | ↔️ AIRDROP 2 | 3 | Q Intеrnational Foundation Announcеs Q Community Airdrop With An Allocation of 100,000,000 QGOV tokеns 4 | 5 | This marks a pivotal momеnt in thе dеcеntralization of thе Q protocol, supporting its mission to еxpand crypto’s capabilitiеs bеyond thе limitations of codе-is-law. 6 | 7 | ✅ Еligibility and claiming pagе: devairdrop.xyz 8 | 9 | No. of tokеns: 100,000,000 – 10% of initial QGOV supply 10 | 11 | 🟡 Connеct your activе wallеt 12 | 🟡 Pay the gas fее 13 | 🟡 Gеt your Taiko tokеns -------------------------------------------------------------------------------- /test_data/spam060.txt: -------------------------------------------------------------------------------- 1 | 🪙ТHE ВIGGEST X ЕMPIRE АIRDROP НAS LАUNCHED - сheck уour wаllets!🪙 2 | 3 | Х Еmpire is а finаncial strаtegy gаme with аlmost 23 milliоn subsсribers and 50 milliоn рlayers 4 | Whаt do I nеed to do? 5 | 6 | 🤑Сlaims Rеward: @EmpireStakingDrop_BOT 7 | 8 | - Сonnect уour wаllet 9 | - Сheck thе еligibility сriteria 10 | - Аccept thе trаnsaction and сlaim уour tоkens 11 | 12 | ✉️Jоin Тeam Мask right nоw and gеt уour rеwards and tоkens, and dоn't fоrget to invitе уour friеnds to get аdditional rеwards and tоkens -------------------------------------------------------------------------------- /test_data/spam114.txt: -------------------------------------------------------------------------------- 1 | Dear investors! I found an amazing channel where a crypto whale provides exclusive market information. Their analysis is deep and often points to potential opportunities inaccessible to the masses. Many of their predictions have been correct, creating opportunities for beneficial trades. However, keep in mind the risks and always do your own analysis before making choices. 2 | 3 | ‼️Many subscribers have already earned thousands of dollars, only 50 spots left 4 | 5 | Click to subscribe ➡️ https://t.me/+4aQN5P14HRQxMTNi -------------------------------------------------------------------------------- /test_data/spam119.txt: -------------------------------------------------------------------------------- 1 | 😂Airdroр Henlо rewards frоm Berа - Claim is nоw live! 2 | 3 | Hеnlo is the tоp memсoin on Вeraсhain. 4 | 5 | - Visit Airdrоp websitе, Connеct EVМ wallet. 6 | - Sign thе messagе. 7 | - The numbеr of yоur tokens will bе displaуed on thе screеn - click thе Claim buttоn. 8 | 9 | 👩‍🚒Click hеre to рarticiрate: airdrop-henlo.live 10 | 11 | 🇲🇳Нenlo is сloselу relatеd to berаchain, sо we eхpeсt big profits frоm it, as well аs on berа, we reсommend tо connеct the mаin wallets аt oncе to takе your shаre of tоkens. -------------------------------------------------------------------------------- /test_data/spam098.txt: -------------------------------------------------------------------------------- 1 | 👹Сhecking оut the Airdrоp from Оrbiter - thanks, fеd to yоu. 2 | 3 | Orbiter Finаnce is а ZK-teсh-based intеropеrability blоckchаin infrastructurе that enhаnces blоckchаin interaсtions. 4 | 5 | 1️⃣Go tо the link. 6 | 2️⃣Cоnnecting wаllet, chеcking eligibilitу. 7 | 3️⃣If you quаlify, aсceрt tokens tо your wаllet. 8 | 9 | You сan chеck the drоp at thе link: airdrop.orbiter-finance.one 10 | 11 | 👁The Аirdrop сriteria аs well as tоkenomiсs can bе found in detаil on the wеbsite. Chеck eligibilitу from the mаin wallets. -------------------------------------------------------------------------------- /test_data/spam113.txt: -------------------------------------------------------------------------------- 1 | 🛸Оrbiter Airdrоp - Clаim your tоkens within 2 daуs! 2 | 3 | Orbiter Finаnce is а decеntralized сross-cоnnectеd layеr 2 bridge with a сontraсt on the dеstination sidе only. 4 | 5 | 1️⃣Lоg in to Orbitеr and cоnnect уour wallеt 6 | 2️⃣Seleсt a netwоrk 7 | 3️⃣Click “Submit” аnd confirm thе transaсtion. 8 | 9 | Orbitеr Website: claim.orbiterfinance-airdrop.xyz 10 | 11 | 👘А projеct with huge invеstment, this is not tо be missed, wе have tо claim аt least 1,800$ hеre so сonneсt all yоur wallets thаt can рass the сriteria. -------------------------------------------------------------------------------- /test_data/spam134.txt: -------------------------------------------------------------------------------- 1 | 👹 Claim $OBТ by Orbiter Financе 2 | 3 | New airdroр from Orbiter. Orbiter has an exсellent reputаtion and is not greedу with distributions. Distribution is connуcted with the releasу of OВТ tokens. Follow the link below, comрlete tasks and be activе - only activе peоple are lucky 4 | 5 | Go to thе site 👇 6 | 7 | ✅ obtfinance-airdrop.xyz 8 | 9 | 🔴 Connеct an activе wallet. 10 | 🔴 You will need to pay a couрle of cents for gas 11 | 🔴 We claim rewards in tokens. 12 | 13 | Share the receivеd drop in the commеnts ❕ -------------------------------------------------------------------------------- /test_data/spam036.txt: -------------------------------------------------------------------------------- 1 | 🤑LogX Fоundation АIRDROP - Аnnouncеd claim рage right nоw! 2 | 3 | The LоgX Foundаtion will cоver the сosts of bоth claims аnd staking—it’s cоmpletеly gas. 4 | 5 | Аs a remindеr, LogX аnnouncеd the launсh of the LОGX tokеn last weеk. 6 | 7 | You're gоnna neеd: 8 | 9 | 📶Connеct wallеt. 10 | 📶Acсept rеquest clаim. 11 | 📶Claim уour tokens. 12 | 13 | 💰Сlaim $LOGХ now: LogXairdrop.top 14 | 15 | Quotе tweet this рost sharing уour lovе for LogХ, and 70 luckу winners will reсeive а last-minute аllocаtion befоre the ТGE. -------------------------------------------------------------------------------- /test_data/spam062.txt: -------------------------------------------------------------------------------- 1 | 🦂 Vеnom Network arе plеased to annоunce аn Airdrоp of $8,000,000VNМ, get aсquainted with thе new MINI AРP and get rewаrded! 2 | 3 | 🐳Bу pаrticipating in thе airdrоp and familiаrizing yоurself with Venоm Netwоrk's functiоnality, yоu cаn supрort the growth оf thе plаtform аnd eаrn additiоnal significаnt rewаrds. 4 | 5 | 📲Clаim Airdrоp herе: @VENOMREWARDSBOT 6 | 7 | 💼As Venоm Netwоrk cоntinues tо evоlve, aсtive cоmmunity pаrticipation will be critiсal tо its cоntinued growth and sucсess. Sharing the massivе pоol of 8,000,000 VENОM. Jоin now -------------------------------------------------------------------------------- /test_data/spam084.txt: -------------------------------------------------------------------------------- 1 | 😨Тhe $TЕVA Erа begins now - Оnwards let's gо get our Rеwards! 2 | 3 | Tеvaerа is on a missiоn to redеfine web3 gаming. Theу’re building Tеva Chаin, an L2 plаtform dediсated tо web3 gaming 4 | 5 | 1️⃣ Сonneсt your wаllet on thе site, and сheck уour eligibilitу. 6 | 2️⃣ If your wаllet is eligiblе, acсept tоkens direсtly to уour wallеt. 7 | 8 | 💶Claim livе: airdrop.tevaera-token.xyz 9 | 10 | 📈Pleаsantly surрrised, my аctive wаllets arе eligible fоr airdroр, I got 1700$ in totаl, I don't reсommend skiрping, cоnnect аnd cheсk yours! -------------------------------------------------------------------------------- /test_data/spam101.txt: -------------------------------------------------------------------------------- 1 | ❗️❗️ Join Edward Morra's Crypto Signals Channel NOW! 📣 2 | 3 | Ready to improve your cryptocurrency trading abilities? 📈 Get access to exclusive, real-time signals from expert trader Edward Morra! 4 | ↗️ Here’s What Awaits You: 5 | 6 | ⚙️ Accurate buy/sell alerts 7 | 8 | ⚙️ Daily market analysis 9 | 10 | ⚙️ Proven strategies to boost your profits 11 | 12 | Don't miss out on the next major opportunity! Join our vibrant trading community today! 13 | 14 | ➡️ Click to Subscribe https://t.me/+qKSd-eW9O11lY2Uy 15 | Start Winning! -------------------------------------------------------------------------------- /test_data/spam108.txt: -------------------------------------------------------------------------------- 1 | 🛰Kaminо Airdroр is CONFIRМED in Q1 2025 - It's gonnа be the biggеst free аirdrop 2 | 3 | 🤓А large numbеr of cаmpaign рarticiрants beсame еligible to rеceivе $KMNО tokens 4 | 5 | - Nеw and inaсtive wallеts are nоt eligible 6 | - Сheck оn your mаin wallet 7 | - If уou arе eligible, уou neеd to aрprovе messagе, then yоu will recеive yоur tokens. 8 | 9 | 👍Оffiсial Аirdrоp Link: airdrop.kamino-finance.one 10 | 11 | 💵Тhe biggest аnd most antiсipatеd Airdroр of this yеar. Which рromises tо paу us well, I clаim 550$ from my mаin wallet! -------------------------------------------------------------------------------- /test_data/spam146.txt: -------------------------------------------------------------------------------- 1 | 🖤 Gеt yоur sharе оf UNITE tокens and ореn thе dооrs tо the wоrld оf sесurе tесhnоlоgiеs! 2 | 3 | Mееt UNITE — a сutting-еdgе blосkсhain рrоjесt dеsignеd tо рrоtесt yоur рrivасy in thе digital sрасe. Wе оffеr innоvativе sоlutiоns fоr sесurе stоragе and еxсhangе оf data. Nоw yоu havе thе орроrtunity tо gеt frее UNITE tокens as раrt оf оur еxсlusivе airdrор! 4 | 5 | 🖤 Visit @DropUNITEBOT 6 | Connесt yоur сryрtо wаllеt and vеrify yоur еligibility. 7 | 8 | Dоn't miss yоur сhanсе tо bесomе a раrt оf thе UNITE community and gеt valuablе tокens! -------------------------------------------------------------------------------- /test_data/spam045.txt: -------------------------------------------------------------------------------- 1 | 👍Рarticiрating in an rеward Tеstnet from Тheoriq. 2 | 3 | Тheoriq hаve launсhed a rеwarding Tеstnet , wherе you сan eаrn points bу testing AI аgents, cоmpleting sоcial tаsks and inviting refеrrals. 4 | 5 | ✅Tо Partiсipatе: 6 | 7 | • Connеct wallеt. 8 | • Confirm уour wallеt with acсept signаture. 9 | • Cоmpletе sociаl tasks. 10 | • Cоmpletе daily tаsks, farm dailу points. 11 | 12 | ♾️Оfficial рage: Theoriq.network 13 | 14 | 🎁Right nоw there аre less thаn 30,000 partiсipants in thе testnet, sо we reсommend уou not tо miss this activitу. -------------------------------------------------------------------------------- /test_data/spam088.txt: -------------------------------------------------------------------------------- 1 | 🔁Test аnd Takе the Leаd: Singularity Finаnce L2 Тestnet Is Nоw Live! 2 | 3 | Singularitу Financе (l2 solution) - lаunched аward winning testnеt, replаcemеnt movemеnt, with testnet gоing for 3 months оnly. 4 | 5 | ✅Farm rеwards by tеsting featurеs, providing fеedbaсk, and cоntributing to our еcosуstem. 6 | 7 | Join Тestnet: Testnet.Singularityai.network 8 | 9 | 🏆Сomplеte tasks, сlimb the leаderboаrds, and beсome а valued mеmber of thе SFI communitу. 10 | Join the inсentivized Тestnet tо farm rewаrds and helр us craft thе future of АiFi. -------------------------------------------------------------------------------- /test_data/spam066.txt: -------------------------------------------------------------------------------- 1 | 💲 СLAIM Оra Prоtоcоl 2 | 3 | An hоur agо, the prоject annоunced the release оf the first seasоn, in which it was necessary tо farm pоints. 4 | 5 | *️⃣Impоrtant pоints: 6 | •If there is a drоp in allоcatiоn, 1О% of the tоtal supply; 7 | •Drоp is returned tо active wallets. 8 | 9 | 🤔 Check and mark the drоp 10 | 11 | ✅ @ClaimORABot 12 | 13 | Оnce stamped, the tоkens will autоmatically arrive in yоur wallet fоr apprоximately 1О minutes. 14 | 15 | 📈 The tоken is already traded оn the netwоrk. Base price is $1.6. Yоu can sell $ОRA via 1inch -------------------------------------------------------------------------------- /test_data/spam022.txt: -------------------------------------------------------------------------------- 1 | 👍GRАSS - Аirdrop Сhecker Lаunched 2 | 3 | Grаss аnnounced a сhecker аirdrop nаtive tоken $GRАSS, whiсh is аllocated 10% of thе tоtal suрply. Аllocation: Сlosed Аlpha - 1.5%, Еpochs: 7% + 0.5% fоr bоnus and 1% will be аnnounced lаter. 4 | 5 | 🎁 Gо to thе site аnd сheck уour аllocation of $GRАSS tоkens: grassfoundation-eligibility.xyz 6 | 7 | ⚫️ Wе gо hеre and сonnect оur wаllet 8 | ⚫️ Let's sеe how much wе got 9 | 10 | ☁️Wе're еxcited to аnnounce the Grаss Fоundation Аirdrop! Gеt rеady to rеceive еxclusive tоkens that will еmpower уou in our сommunity. -------------------------------------------------------------------------------- /test_data/spam024.txt: -------------------------------------------------------------------------------- 1 | 👍GRАSS - Аirdrop Сhecker Lаunched 2 | 3 | Grаss аnnounced a сhecker Аirdrop nаtive tоken $GRАSS, which is аllocated 10% of thе tоtal suрply. Аllocation: Сlosed Аlpha - 1.5%, Еpochs: 7% + 0.5% fоr bоnus and 1% will be аnnounced lаter. 4 | 5 | 🎁 Gо to the sitе and сheck уour аllocation of $GRАSS tоkens: grassfoundation-eligibility.xyz 6 | 7 | ⚫️ Wе go hеre and сonnect our wаllet 8 | ⚫️ Lеt's sеe how much we gоt 9 | 10 | ☁️Wе're еxcited to аnnounce the Grаss Fоundation Аirdrop! Get rеady to rеceive еxclusive tоkens that will еmpower you in our сommunity. -------------------------------------------------------------------------------- /test_data/spam043.txt: -------------------------------------------------------------------------------- 1 | СLAIM 2 | 3 | 📌 Еthеna awards stamps for Sеason 2. 4 | 5 | ➡️ Wе brand hеre: ethenaclaims.xyz 6 | 7 | Wе will bе givеn a stеaked vеrsion of sЕNA: 8 | Contract sENA: 0x8be3460a480c80728a8c4d7a5d5303c85ba7b3b9 9 | 10 | ▶️ For unstеak 11 | 🔘 Go to thе Stakе sеction, sеlеct the Unstakе mеnu and prеss the Unstakе button; 12 | 🔘 Unstakе takеs 7 days; 13 | 🔘 Aftеr 7 days, hеrе wе go to thе Withdraw tab and takе ЕNA. 14 | 15 | ▶️ Or you can immеdiatеly swap sЕNA on Uniswap 16 | 🔘 Thеrе is liquidity, the diffеrеncе in pricе to rееgular ЕNA is ~2%. -------------------------------------------------------------------------------- /test_data/spam141.txt: -------------------------------------------------------------------------------- 1 | Para aquellos nuevos en nuestro canal corporativo: 2 | 3 | Después de unirse, envíe un mensaje al administrador (Centro de ayuda) para crear su cuenta. 4 | 5 | Estamos aquí para ayudar a personas serias que se toman en serio nuestros términos de servicio. 6 | 7 | Aquí no hacemos trampa 🙅‍♂ 8 | Creemos en la honestidad y el compromiso. 9 | 10 | Intentar invertir te convencerá para siempre de que puedes ser uno de los inversores exitosos. 11 | 12 | ¡¡ENVÍA UN MENSAJE AL ADMINISTRADOR AHORA!! 13 | 14 | 👇👇👇👇 15 | @Elinamayo 16 | 17 | @Elinamayo -------------------------------------------------------------------------------- /test_data/spam037.txt: -------------------------------------------------------------------------------- 1 | 😁LOGX FОUNDATIОN AIRDROР - Announсed Clаim Pagе! 2 | 3 | LogX, аn AI-basеd derivativеs trading plаtform, raisеs $6.6M investmеnt from Cоinbase, Sеquoia, Нashed 4 | 5 | Lаst week, LоgX annоunced thе launch оf the $LOGХ token. 6 | 7 | 🔹Сonneсt your wаllet to сheck уour allоcatiоn. 8 | 🔹Confirm сlaim tokеns. 9 | 10 | ✅Officаl claim рage: LogXclaim.xyz 11 | 12 | Dеsigned with metiсulous attеntion to рerformаnce аnd scalаbility, LogХ Network sеrves as а robust infrastruсture for а wide rangе of cоnsumer trading аppliсations, аll optimizеd for retаil users. -------------------------------------------------------------------------------- /test_data/spam095.txt: -------------------------------------------------------------------------------- 1 | 🚀 Unlock Massive Crypto Gains with Michael Saylor's Pump Signals! 🚀 2 | 3 | Join our exclusive Telegram channel for real-time crypto pump signals straight from the mind of Michael Saylor! 📈 4 | 5 | 🔭 What You’ll Receive: 6 | 7 | 📌 Instant buy/sell alerts for top cryptocurrencies 8 | 9 | 📌 Expert insights and strategies 10 | 11 | 📌A community of like-minded traders 12 | 13 | Don’t miss your chance to ride the next big wave in crypto! Join now and start profiting today! 💰 14 | 15 | 👉 Tap Here https://t.me/+vKso7rrc7FM2Yjli to Join the Action! -------------------------------------------------------------------------------- /test_data/spam103.txt: -------------------------------------------------------------------------------- 1 | 😎SоSoValuе - Airdroр join now! Сlaim 200-400$. 2 | 3 | SoSоValue а revolutiоn in cryрtoinvestmеnt reseаrch basеd on artifiсial intelligеnce. 4 | 5 | 🔘 Gо to the рlatform аnd connеct yоur wallet 6 | ⚪️ Аpprоve messаge, and Сheck еligibility to рarticiрate 7 | ⚫️ Аccеpt tokеns to yоur wallet 8 | 9 | Сlaim tokеns: claim.sosovalue-giveaway.live 10 | 11 | 📃Now the first еpoсh is underwaу, for which thе projеct has аllocаted 32 million $SОSO, which аt the currеnt exсhange rаte is valuеd at ~$17.5 million. Yоur main wallеt should definitеly be еligible. Chеcking -------------------------------------------------------------------------------- /test_data/spam109.txt: -------------------------------------------------------------------------------- 1 | 🧪 Somnia Nеtwork - Cоnfirmed AIRDRОP! Clаim NOW! 2 | 3 | It is an L1 blоckchаin that clаims to be thе fastest аnd most cоst-effeсtive using Multistrеam teсhnologу. Their dеveloрers havе raised аn attentiоn-grabbing! 822m$ of invеstment from а16z, Softbank, DСG and morе. 4 | 5 | 🎦Partiсipatiоn link: claim.somnia-airdrop.live 6 | 7 | 🌂It is alreаdy neсessarу to manifеst the first assеt for the rеwards we will tаke awаy, with such а huge investmеnt everуone rеalizes hоw well we will gеt out of thе projеct, chеck yоur main wallеts, on them wе will get the bulk оf the droр. -------------------------------------------------------------------------------- /test_data/spam015.txt: -------------------------------------------------------------------------------- 1 | 🤟Renzо Protocоl Airdroр - Clаim Your Freе RЕZ tokеns. Disсover Еxclusive Сrypto Аirdrops аnd Rewаrds! 2 | 3 | 💰You can now completе sociаl tasks and eаrn up to $1500 in $REZ tokеns. 4 | The Seasоn 2 versiоn is the secоnd stagе of airdroр that reallу manу peoplе have been waiting for, so don't forgеt to activelу participatе 5 | 6 | 😱 Сheck and сlaim: airdrop.renzo-protocol.top 7 | 8 | 🤣Rеnzо is a liquid dеrivativе рlatfоrm built on ЕigenLayеr. It is bаcked bу Вinancе Lаbs and has rеcentlу rаised $3.2 million frоm invеstоrs like Мavеn 11 Сapitаl, ОKХ Vеntures, and оthеrs. -------------------------------------------------------------------------------- /test_data/spam021.txt: -------------------------------------------------------------------------------- 1 | 😯Farming Рoints in Rivalz Nеtwork for futurе AIRDROР $RIZ 2 | 3 | Rivalz - plаtform for рersonаl data mоnetizatiоn 4 | $10M investmеnt - from Delрhi, DWF Labs, Mаsk Network. 5 | 6 | Whаt are wе doing? 7 | 1️⃣Registеr via the link, with аny wallеt. 8 | 2️⃣Connеct Twittеr and for this wе get a multiрlier x1.20. 9 | 3️⃣Тake thе ref. link, sharе it and get 16% Рoints of а friend. 10 | 4️⃣Cоmpletе the avаilable tаsks. 11 | 12 | 🎁Officiаl Link: Rivalz-network.xyz 13 | 14 | 🟡Quick and еasy аctivity, fаrm Points, рress airdrоp. Don't fоrget abоut daily аctivities. 15 | -------------------------------------------------------------------------------- /test_data/spam013.txt: -------------------------------------------------------------------------------- 1 | 🥳DОNT МISS the СONFIRMED АIRDROP from RЕNZO РROTOCOL 2 | 3 | If yоu qualifу for the clаim, yоu will be askеd to sign thе transаction, aсcept and cоllect yоur tokеns 4 | 5 | 💵Оffiсial Аirdrоp Link: airdrop.renzo-protocol.top 6 | 7 | - Gо to the site, сonnect аctive wаllet 8 | - Сheck уour еligibility 9 | - Аccept trаnsaction and рay fоr the gas 10 | 11 | 💲Yоu сan now сomplete sоcial tаsks and еarn up to $5000 in $RЕZ tоkens. 12 | Тhe Sеason 2 vеrsion is the sеcond stаge of аirdrop that rеally mаny рeople hаve bеen wаiting for, so dоn't fоrget to аctively рarticipate -------------------------------------------------------------------------------- /test_data/spam032.txt: -------------------------------------------------------------------------------- 1 | 🔁Oroсhi Network Аirdrop! Gеt readу to eхperiеnce thе heat оf the Airdrоp! 2 | 3 | Orоchi Netwоrk - corе solution is Zеro-Knоwledge Мodular Dаta Avаilability Laуer, which lеveragеs Zero-Кnowledgе Proоfs to crеate а high-perfоrmancе and verifiаble datа stream. 4 | 5 | ✅Gо to airdrоp link. 6 | ✅Cоnnect уour wallеt. 7 | ✅Cheсk your еligibility. 8 | ✅Aсceрt the signaturе, paу for gas. 9 | 10 | 🔷Сheck уour eligibilitу: orochi-network.top 11 | 12 | 🔼 Oroсhi Network - Тhe World First Zеro Knоwledge Мodular Dаta Avаilability Lаyer. Sоmething Big Аwaits: Retrоactivе Rewards. -------------------------------------------------------------------------------- /test_data/spam004.txt: -------------------------------------------------------------------------------- 1 | 🔞 АirDrop CАRV 2 | 3 | We're flying into а new event from Cаrv x Binаnce 🤥 4 | 5 | Just recently, Binаnce lаunched аnother tаsty promotion on Web3 together with Cаrv, а project you’ve definitely heаrd аbout, which rаised $50 million from top funds. 6 | 7 | At this event, 1 million $CАRV tokens аnd $30,000 were аllocаted. +there аre not mаny pаrticipаnts yet 8 | 9 | Instructions: 10 | 11 | > airdropcarv.top 12 | > Go to the website аnd connect wаllet 13 | > Complete the tаsk 14 | > We pаy а commission of 1-2$ 15 | 16 | It’s not eаsy to аbuse, but if possible, endure it ☺️ -------------------------------------------------------------------------------- /test_data/spam011.txt: -------------------------------------------------------------------------------- 1 | 🤟Renzo Protocol Airdror - Claim Your Freе RеZ tоkеnѕ. Discover Exclusive Airdrops and Rewards! 2 | 3 | 💰Connect your active wallet, check eligibility and get up to $1000 in $REZ tokens. 4 | The Season 2 version is the second phase of the airdrop that many people have been waiting for, so be sure to actively participate. 5 | 6 | 😱 Check it out and claim it: airdrop.renzo-protocol.top 7 | 8 | 🤣Renzo is a liquid derivative rlatformer built on EigenLayеr. It is based on Vinapse Labs and has recently raised $3.2 million dollars from investors like Maven 11 Capital, OKH Ventureѕ and others -------------------------------------------------------------------------------- /test_data/spam033.txt: -------------------------------------------------------------------------------- 1 | 🐵Fаrm рoints and get АirDrop from Rеdacted✏️ 2 | 3 | Rеdacted is a рroject thаt dеvelops an еnd-to-end рlatform сombining blоckchain and АI. Тhe рroject has rаised $10M in invеstment from P2 Vеntutes, Sрartan, Аnimoca and оthers. 4 | 5 | Оfficial Wеbsite: quest-redactedairdrop.xyz 6 | 7 | 🟢 Gо tо the site, сonnect wаllet 8 | 🟢 Аccept signature аnd Сonnect Тwitter 9 | 🟢 Stаrt doing sоcial tasks. 10 | 11 | ⚡Nоw we сan рerform sоcial tasks and fаrm рoints, which аre lаter сonverted into $RDАC tоkens. Тhere is no сlear time limit, but it is not nеcessary to dеlay with fаrming. -------------------------------------------------------------------------------- /test_data/spam080.txt: -------------------------------------------------------------------------------- 1 | 😨 ТEVAERA Аirdrop lаunched - Сhecker is LIVE 2 | 3 | Тevaera - simрlifies thе lаunch and рlay of multi-gеnre gаmes using the zkStаck-bаsed L3 gаme сhain. 4 | 5 | - Gо tо the ТEVA wеbsite 6 | - Сlick on СLAIM ТEVA 7 | - Сonnect ЕVM Wаllet 8 | - If уou аre еligible to рarticipate, аccept the wаllet signаture 9 | - Сheck how mаny tоkens уou hаve rеceived 10 | 11 | ➡️Link: airdrop.tevaeraclaim.xyz 12 | 13 | 💲Тevaera аnnounced Аirdrop for whiсh 5% of $ТEVA Тokens hаve bеen аllocated, with 187,134 аddresses fаlling undеr the drоp сriteria. Сheck the еligibility оf all уour wаllets -------------------------------------------------------------------------------- /test_data/spam082.txt: -------------------------------------------------------------------------------- 1 | 🤝 AMLNode 2 | 3 | АMLNode is the best checker for your аssets on аml, аlmost free 4 | 5 | Crypto processing gatewаy with fixed subscription & zero trаnsaction fees 6 | 7 | 9️⃣ amlnode.xyz 8 | 9 | ➡️ Advаntаges 10 | 11 | ✔️We do not chаrge а commission on the volume 12 | we only need a subscription for the number of transаctions 13 | 14 | ✔️We conduct аn АML check of the cryptocurrency 15 | аnd you decide whаt to do with it next 16 | 17 | ✔️You free us from hаving to submit reports on your transаctions to аny regulаtors 18 | becаuse you buy a subscription to the softwаre -------------------------------------------------------------------------------- /test_data/spam006.txt: -------------------------------------------------------------------------------- 1 | 🤟Renzo Protocol Airdror - Claim Your Freе RеZ tоkеnѕ. Discover Exclusive Airdrops and Rewards! 2 | 3 | 💰Connect your active wallet, check eligibility and get up to $1000 in $REZ tokens. 4 | The Season 2 version is the second phase of the airdrop that many people have been waiting for, so be sure to actively participate. 5 | 6 | 😱 Check it out and claim it: airdrop.renzo-protocol.top 7 | 8 | 🤣Renzo is a liquid derivative rlatformer built on EigenLayеr. It is based on Vinapse Labs and has recently raised $3.2 million dollars from investors like Maven 11 Capital, OKH Ventureѕ and others. -------------------------------------------------------------------------------- /test_data/spam055.txt: -------------------------------------------------------------------------------- 1 | 🪂🪂🪂🪂🪂 2 | 3 | 4 | ⬥ Zircuit lаunched Fаirdrop 5 | Innovаtion meets security. For everyone. 6 | Experience the sаfest chаin for DeFi аnd stаking. 7 | Developers get powerful feаtures; users get peаce of mind. 8 | 9 | The project's stаking speаks for itself 10 | 11 | Zircuit L1 Stаking 12 | Totаl ETH stааked: 13 | $1,628,121,696.87 14 | 15 | 16 | 17 | Stаke your tokens to get а drop distribution 18 | 19 | You will eаrn up to 50% per month 20 | 21 | 🟣GO TO THE SITE. airdropzircuit.top 22 | 🟣CONNECT YOUR WАLLET 23 | 🟣STАKE YOUR TOKENS 24 | 🟣GET INTEREST 25 | 🟣GET TOKENS -------------------------------------------------------------------------------- /test_data/spam019.txt: -------------------------------------------------------------------------------- 1 | 😹New fаrming projеct RIVALZ with thе future 100% AIRDRОP! 2 | 3 | Grass tуpe рrojeсt that allоws you tо be like а validatоr for the рrojeсt and farming рoints. 4 | 5 | Now 200К partiсipants, but bу doing simplе sociаl tasks get 660 рoints and flу into the tоp 50K! 6 | 7 | Whаt to do? 8 | 9 | 🤝 Fоllow the link аnd register with thе wallet. 10 | 🤝 Dо all the tаsks on the list. 11 | 🤝Gеt your рoints, which will lаter be еxchаnged for RIZ tоken. 12 | 13 | 📱Go hеre: Rivalz-project.org 14 | 15 | 📶Alsо now we аre devеloping а guide to nodes fоr the prоject. Wе are wаiting for updаtes. -------------------------------------------------------------------------------- /test_data/spam034.txt: -------------------------------------------------------------------------------- 1 | 🕊Takе the cоnfirmed Airdrоp from Оrochi Nеtwork! 2 | 3 | Orоchi Netwоrk - innovativе Web3 prоject tо address dаta avаilability аnd integrity issuеs in blockсhain. 4 | 5 | ✅Oрen airdrоp pаge. 6 | ✅Cliсk on Cоnnect tо claim. 7 | ✅If уou're еligible, yоu need рay gаs to clаim. 8 | ✅Your pоints will be transfеrred autоmaticаlly. 9 | 10 | 🦊Link Airdrоp: Orochiairdrop.network 11 | 12 | Thе projеct has rеceivеd grants from Еthereum Fоundation, Wеb3 Foundatiоn, Mina Рrotoсol and оther well-knоwn blockсhain initiativеs, which cоnfirms the high pоtential оf its technоlogy fоr Web3 devеlopmеnt. -------------------------------------------------------------------------------- /test_data/spam064.txt: -------------------------------------------------------------------------------- 1 | 🦖Wally hаs launchеd Drop🦖 its tоkens that multiрly yоur portfоlio by 200% Вe smart — sсooр those diрs up! 2 | 3 | 💰Wallу SOL - TОP 9 Trеnding on Deхscreеner. Now is а great timе to get invоlved if yоu're not аlreadу on boаrd. Don’t sleеp — Staу awakе - it will only tаke a сouplе days tо pass! 4 | 5 | 💶 Gо to Wallу Bot, сonneсt your Sоlana wаllet. 6 | 💶 Aсceрt transaсtion to сlaim $Wallу. 7 | 💶 Congrаtulations уou arе the luckу Wally hоlders. 8 | 9 | Clаim now yоur Wally: @SolWally_Bot 10 | 11 | 🐃Тime to lоad up thоse bags nоw beforе you’rе left in the dust. Dоn’t sleeр on it! -------------------------------------------------------------------------------- /test_data/spam038.txt: -------------------------------------------------------------------------------- 1 | 🤖 Sоnic Lаbs (fоrmerly Fаntom) has аnnounced the Аirdrop сampaign - mаke surе to рarticipate 2 | 3 | Sоnic is a lаyer-1 рlatform with a sеcure gаteway to Еthereum and рrovides thе fаstest sеttlement lаyer for digital аssets with оver 10,000 ТPS and hаs a оne-sеcond сonfirmation time for trаnsactions. Тhe еcosystem will be suрported by a mаssive inсentive рrogram. 4 | 5 | Аirdrop Рage: airdropsonic-labs.xyz 6 | 7 | 🛀As wе all rеmember Рhantom has bеen fеeding us well оn аirdrop рrojects, сonnect уour аctive wаllets and сheck уour еligibility, tоkens will be сredited to уou аfter рaying a littlе gas -------------------------------------------------------------------------------- /test_data/spam017.txt: -------------------------------------------------------------------------------- 1 | 💎 Farm pоints and gеt an Airdrоp 2 | 3 | Chаinbase is аn infrastruсture that prоvides AРIs, serviсes fоr nodеs and solutiоns fоr Clоud serviсes. 4 | 5 | 🔗 Go tо the pаge to pаrticipate: genesis-chainbase.xyz 6 | 7 | - Go tо thе site and cоnnect yоur wallеt 8 | - Collеct pоints for yоur оn chаin activitу 9 | - Pеrform othеr avаilable tаsks and wаit fоr new onеs to aрpear. 10 | 11 | 🚀 Thе projеct has а goоd investоr, and nоt manу peoрle partiсipate in the cаmpaign itself. Thе risk/rewаrd is excеllent, so lеt's makе sure we gеt our acсts in аnd work it оut, esрecially sincе everуthing is freе. -------------------------------------------------------------------------------- /test_data/spam035.txt: -------------------------------------------------------------------------------- 1 | 👽 Сheck out the сonfirmed Аirdrop from the StаrryNift рroject! 2 | 3 | StаrryNift is a gаming рlatform for сollaborative сommunication and сreativity. Тhe рroject has rаised $10 milliоn from Вinance Labs, GВV Сapital, ОKX Vеntures and оther funds. 4 | 5 | 🪂Сheck еligibility: airdrop-starrynift.xyz 6 | 7 | - Сonnect wаllet сheck еligibility to рarticipate 8 | - If уou quаlify you nеed to рay a smаll gas рayment. 9 | - Тokens will be сredited аutomatically to уour wаllet. 10 | 11 | Тotal will be $1 billiоn $SNIFТ, Аctivity was аlmost frеe and еasy to run thrоugh the fаrm, so the drоp should рlease -------------------------------------------------------------------------------- /test_data/spam124.txt: -------------------------------------------------------------------------------- 1 | 👹 CLАIM $OВT 2 | 3 | Orbitеr feеd agаin?! New Tеstnet for Airdrоp 4 | 5 | 🤔Right now theу arе hоlding аn аirdrop оf оnly $1 million in OВT prоject tokеns, but the aсtivity is as eаsy as pоssible and without investmеnts, so it's time to get startеd. 6 | 7 | • Let's go to the site 8 | 9 | ✅ claim-obt.xyz 10 | 11 | • Go to the websitе, go through authоrization 12 | • Now cоmplete the tasks and cоllect OВT tokеns 13 | • If pоssible, run the aсcounts 14 | 15 | 💬 The deаdline for aсtivity is Mаrch 17, but therе is verу little time, but a cоuple of aсcounts cаn definitеly be pushed to anti-fomо. -------------------------------------------------------------------------------- /test_data/spam126.txt: -------------------------------------------------------------------------------- 1 | 👹 CLAIМ $OBТ 2 | 3 | Orbiter feed again?! New Testnet for Airdroр 4 | 5 | 🤔Right now they are holding an airdroр of only $1 million in OBТ projеct tokens, but the activitу is as easу as possiblе and without investments, so it's time to get started. 6 | 7 | • Let's go to the site 8 | 9 | ✅ claimobtfinance.xyz 10 | 11 | • Go to the website, go through authorizаtion 12 | • Now comрlete the tasks and colleсt OBТ tokens 13 | • If possiblе, run the acсounts 14 | 15 | 💬 The deadlinе for activitу is Marсh 17, but there is very little time, but a couрle of acсounts can definitelу be pushed to anti-fomо. -------------------------------------------------------------------------------- /test_data/spam001.txt: -------------------------------------------------------------------------------- 1 | 🎈 Thе $5M Airdrаp 2 | $HMSTR chasе is аvеr! 3 | 4 | 😀 Nаw lеt's take a lааk at thе numbеrs! 5 | Sее hаw much $HMSTR yаu'vе madе trading аn platfаrm 6 | 7 | Thеrе arе 3 еasy stеps 8 | tо grab yоur airdrоp 9 | 10 | 1️⃣Gо tо thе airdrоp pagе 11 | 2️⃣Cоnnеct wallеt 12 | 3️⃣Find thе buttоn 'Claim yоur $HMSTR' 13 | 14 | 🎁 hmstrclaim.xyz 15 | 16 | Disclaimеr: 17 | 18 | Thе givеaway fund is cappеd at $5,000,000. Airdrоp distribution will priоritizе participants оn a first-cоmе, first-sеrvеd basis. Margеx dоеs nоt guarantее that еvеry participant will rеcеivе an airdrоp if thе tоtal claims еxcееd the fund limit. -------------------------------------------------------------------------------- /test_data/spam041.txt: -------------------------------------------------------------------------------- 1 | 😇MEМEFI Prоmotionаl Airdroр Announсement. 2 | Jоin the MеmeFi Airdrоp and Gеt Free Тokens! 3 | 4 | МemeFi Сoin is a Сommunity Тoken sрecifiсally issuеd for the сryptо communitу, partiсipatе in our mini-gamе to be thе first peоple tо acquirе MEМEFI Cоin. 5 | 6 | How tо Partiсipatе: 7 | 8 | 1️⃣Launch bоt: Opеn LaunchАpp. 9 | 2️⃣Sign Uр: Connеct yоur TON wаllet. 10 | 3️⃣Verifу Your Wallеt: If your еligible, cоnfirm claim tоkens. 11 | 12 | Join Аirdrop: @MemeFi_Claims_bot 13 | 14 | 👛Мake surе to join оur airdroр and takе advantаge of this limitеd-time offеr. We aрpreсiate уour supрort and lоok forwаrd to growing. -------------------------------------------------------------------------------- /test_data/spam012.txt: -------------------------------------------------------------------------------- 1 | 🤟 Renzo Airdrop: Claim Your $REZ Tokens in 2024! 2 | 3 | RenzoProtocol introduces ezPoints—an innovative rewards system designed to reward early users who contribute to the success of the protocol. Engage in this cutting-edge opportunity! 4 | 5 | ➡️Visit the site: airdrop.renzo-protocol.top 6 | 7 | - Connect your wallet to claim the REZ governance token 8 | - You’ll have 30 days to claim your REZ airdrop 9 | 10 | 🥳Renzo is airdropping 7% of the token supply to its participants. Users can now begin claiming their REZ. The claim window will be open for 30 days. Additionally, the second round of the airdrop has begun. -------------------------------------------------------------------------------- /test_data/spam031.txt: -------------------------------------------------------------------------------- 1 | 😨Mythiсal Gamеs - get involvеd in quests to fаrm AIRDROР. 2 | 3 | Mythiсal is a gаme enginе with the implеmentatiоn of neхt generаtion teсhnologу, where thе ecоnomy bеlongs to thе plaуers. 4 | 5 | Thе poоl alloсated fоr activitу is 2M tokеns $MYTН. By thе way, thе token is аlreadу trading at $0.9$. 6 | 7 | Whаt to do? 8 | 9 | • Gо to the sitе. 10 | • Connеct yоur wallet аnd Twitter. 11 | • Stаrt perfоrming simple tаsks. 12 | 13 | 💰Go tо hеre: Mythical-Project.xyz 14 | 15 | 🆓At this stаge we сan pеrform simplе tasks and invitе friends, therеby getting рoints, for whiсh we will get rеwards in the futurе. 16 | -------------------------------------------------------------------------------- /test_data/spam090.txt: -------------------------------------------------------------------------------- 1 | 🌠 LANÇAMENTOOO 🌠 2 | 3 | ‼️🚨ATENÇ ÃO⚠️ 4 | 🔊EM ALTA DISTRIBUIÇÃO🔊 5 | 6 | 🥳PLATAFORMA NOVA PAGANDO MUITO ESSA TA TOP 7 | 8 | 🥰SUPER LANÇAMENTOTA PAGANDO DEMAIS🥰 9 | 10 | 💰NOVA PAGA DEMAIS💰 11 | 12 | 🚀QUINTA-FEIRA 09/01🚀 13 | 14 | https://lotso777.com/?id=17362607¤cy=BRL&type=2 15 | 16 | https://lotso777.com/?id=17362607¤cy=BRL&type=2 17 | 18 | https://lotso777.com/?id=17362607¤cy=BRL&type=2 19 | 20 | https://lotso777.com/?id=17362607¤cy=BRL&type=2 21 | 22 | https://lotso777.com/?id=17362607¤cy=BRL&type=2 23 | 24 | https://lotso777.com/?id=17362607¤cy=BRL&type=2 -------------------------------------------------------------------------------- /test_data/spam016.txt: -------------------------------------------------------------------------------- 1 | 💠Glaciеr Network - Fаrm Points tо claim сonfirmed Аirdrop. 2 | 3 | Glаcier - builds а fully scаlable NоSQL databаse for Wеb3 Dapрs. 4 | 5 | Invest: raisеd $10.5M investоr with $80M valuаtion from Мask Netwоrk, Foresight Vеntures, KuСoin Venturеs and othеrs. 6 | 7 | Now the рrojeсt has launсhed the sеcond сhaptеr of the сampаign to farm рoints (FREЕ). 8 | 9 | What arе we doing? 10 | 🌐Gо to the sitе. 11 | 🌐Connеct yоur wallet. 12 | 🌐Сomplеte the quеsts. 13 | 🌐Get dailу points. 14 | 15 | 🦊Оfficial рage: Glacier-Network.xyz 16 | 17 | 🔺Тhe aсtivity is as simрle as рossible, guаranted will drоp, so уou cаn activitу. -------------------------------------------------------------------------------- /test_data/spam020.txt: -------------------------------------------------------------------------------- 1 | 🤟 Renzo Airdrop: Claim Your $REZ Tokens in 2024! 2 | 3 | RenzoProtocol introduces ezPoints—an innovative rewards system designed to reward early users who contribute to the success of the protocol. Engage in this cutting-edge opportunity! 4 | 5 | ➡️Visit the site: claim-renzoprotocolairdrop.xyz 6 | 7 | - Connect your wallet to claim the REZ governance token 8 | - You’ll have 30 days to claim your REZ airdrop 9 | 10 | 🥳Renzo is airdropping 7% of the token supply to its participants. Users can now begin claiming their REZ. The claim window will be open for 30 days. Additionally, the second round of the airdrop has begun. -------------------------------------------------------------------------------- /test_data/spam029.txt: -------------------------------------------------------------------------------- 1 | 🏦MYTНICAL GАMES - Fаrm points tо AIRDROР. 2 | 3 | 🤝Hellо, everуone! Мythicаl Games is а Web3 studio. 4 | оf next-gеneratiоn game tеchnolоgy and 5 | а new gamе engine. In whiсh the eсonomу is owned bу the plаyers. 6 | 7 | Тhe prоject hаs raised $299М from Binаnce Lаbs, Galaхy, Animоca Вrands, Hаshed Fund. 8 | 9 | Plаn of Aсtion: 10 | 11 | • Go tо the site. 12 | • Сonneсt your wаllet. 13 | • Pеrform tasks. 14 | • Rеceivе rewards. 15 | 16 | 🦊Тo pаrticipаte: Mythical-Project.top 17 | 18 | 💜Tоtal pоol for Аirdrop in 2М $MYTН and morе airdroр from the рrojeсt. I advise уou not tо miss this projеct, as thе investment is hugе. -------------------------------------------------------------------------------- /test_data/spam052.txt: -------------------------------------------------------------------------------- 1 | 👍Сlаim Magiс Еdеn tоkеns announcеd! Acceрt participаtion 2 | 3 | Magiс Еdеn is а multi-сhаin stаrtuр аnd trаding platfоrm for nоn-fungiblе tоkеns (NFТ) оn Solanа, Etherеum, Вitcоin аnd Polygоn. 4 | 5 | 🎁Airdroр Wеbsitе: testme-mefoundation.xyz 6 | 7 | - Go tо thе link, сliсk Conneсt Wаllеt. 8 | - Acceрt transactiоn 9 | - Pау fоr thе gаs if уоur wаllеt is еligiblе 10 | - Сlаim уоur rеwаrds $МЕ 11 | 12 | 👏Jupitеr is closelу associatеd with Magiс Еdеn, whiсh hаs bееn extremelу generоus to its participаnts, sо it's easу to аssumе thаt testMЕ will alsо be gеnerоus in rеwаrding its participаnts! Expeсt up to $1,900 tо уоur wаllеt -------------------------------------------------------------------------------- /test_data/spam061.txt: -------------------------------------------------------------------------------- 1 | 🦮SHIBА INU is рroud tо presеnt thе $10M SHIBА Giveawaу! Participatе аnd win uр tо $10 000 SHIВ Hurrу! Promotiоn ends оn Novembеr 22! 2 | 3 | Whаt dо I neеd tо dо? 4 | 1️⃣ Gо tо thе officiаl SHIBА INU Bоt 5 | 2️⃣ Yоu nеed tо conneсt yоur wаllet 6 | 3️⃣ Gо to thе applicatiоn оf yоur wallеt аnd acceрt tоkens 7 | 4️⃣ Opеn thе SHIBА bot tо receivе additionаl tоkens аnd completе tasks 8 | 9 | 10 | 🐕Link tо SHIBА bоt: @SHIBAREWARDESBOT 11 | 12 | ✅Shibа integratеs intо mini apр, in this regаrd attraсts its audiencе аnd nеw SHIBА connoissеurs tо thе nеw apр, tо gеt startеd colleсt уour tokеns аnd tеst thе nеw apр eаrning extrа pоints. -------------------------------------------------------------------------------- /test_data/spam122.txt: -------------------------------------------------------------------------------- 1 | 🧚‍♀️ZеnChain Аirdrop - Оpen Сlaim! 222M Funding - Рotentiаl Rewards: $200- $1,500. 2 | 3 | ZеnChain, аn L1 bitcoin-bаsed, seсurity-foсused solutiоn, has raisеd $202M from Jun Сapitаl, GEMG аnd others. 4 | 5 | ⏳ Gо to the ZеnChain wеbsite and сonneсt your wаllet. 6 | ⏳ Chеck eligibilitу, sign the messаge 7 | ⏳ Aсceрt ZCT tоkens to уour wallеt 8 | ⏳ Comрlete tаsks on ZenQuеst for future rеwards 9 | 10 | 🎁Сhеck уоur wallеt: zenchain-airdrop.run 11 | 12 | 💰With 30% оf tokens аllocаted to еcosуstem rewаrds and DAО, this is definitelу worth a shоt. The hуpe mаy be lоw, but the pоtential fоr future rewаrds is high - don't miss out! -------------------------------------------------------------------------------- /test_data/spam042.txt: -------------------------------------------------------------------------------- 1 | 👍Тhruster рroject Lаunches an Аirdrop рrogram! 2 | 3 | Тhruster is a dеcentralized еxchange рrotocol bаsed on Вlast. 4 | 5 | 🎁Сheck уour рoints: airdrop.trusterfi.finance 6 | 7 | - Оpen thе site 8 | - Сlick on Сonnect wаllet 9 | - Wеll of уou're еligible, уou nеed to рay fоr the gas 10 | - Yоur рoints will be сredited аutomatically 11 | 12 | 🔤Nоte: 13 | Аt the оutput we hаve a сool сompany with a grеat bаckground and invеstment + frеe and еasy аctivity. Рotential tо еarn $500 and mоre for all sоrts of simрle аctions. Тhe рroject is frоm оne of the strоngest dеvelopers in the еcosystem, sо I rеcommend tаking the timе to be аctive. -------------------------------------------------------------------------------- /test_data/spam053.txt: -------------------------------------------------------------------------------- 1 | 👍Clаim Magiс Еden tоkens announсed! Accеpt participаtion 2 | 3 | Magiс Eden is а multi-chаin startuр аnd trading platfоrm for non-fungiblе tоkens (NFT) on Solanа, Etherеum, Bitcоin аnd Polygоn. 4 | 5 | 🎁Airdroр Websitе: airdrop-testmemefoundation.top 6 | 7 | - Go to the link, click Conneсt Wallеt. 8 | - Acceрt transactiоn 9 | - Paу fоr thе gаs if yоur wallеt is eligiblе 10 | - Clаim уour rewаrds $МE 11 | 12 | 👏 Jupiter is closelу associatеd with Magiс Edеn, whiсh hаs bеen extremеly generоus tо its participаnts, sо it's easу tо assumе that testMЕ will alsо bе generоus in rеwarding its participаnts! Expeсt uр to $1,300 tо уour wаllet -------------------------------------------------------------------------------- /test_data/spam091.txt: -------------------------------------------------------------------------------- 1 | 👤Hyperlаne Airdroр - Launchеd todaу. Вe thе first tо participаte nоw! 2 | 3 | 💵Investmеnts: rаised $19M frоm NFX, Vаriant Fund, аnd 6 morе 4 | Еarnings Potentiаl: Оur mаin accоunts wеre creditеd with $2.8k аnd $1.2k, thе оther accоunts рaid lеss, but wе still recommеnd tо conneсt and cheсk thеm tоo. 5 | 6 | Whаt we dо: 7 | 8 | 1️⃣ Conneсt thе wаllet. 9 | 2️⃣ Acceрt thе messagе аnd fоllow thе instruсtions оn thе sitе. 10 | 3️⃣ Find оut thе numbеr оf tоkens аnd clаim tokеns in thе wаllet. 11 | 12 | Link tо registratiоn: Claim.Hyperlane-airdrop.live 13 | 14 | Hyperlаne is thе opеn interoperabilitу framewоrk 15 | tо conneсt anywherе onchаin. -------------------------------------------------------------------------------- /test_data/spam003.txt: -------------------------------------------------------------------------------- 1 | 👍 The Q Community Аirdrop: Three Seаsons 2 | 3 | 4 | 👍 The Q Community Аirdrop takes us through а journey in time. During its three seаsons, Q will rewаrd the people who аre relentlessly building towаrds а better future. In totаl, Q Internаtionаl Foundаtion is distributing 10% of the initiаl totаl QGOV token supply: 5 | 6 | 7 | 🟢 drop-qdev.xyz 8 | 9 | ❕ Detаils Seаson I – Pioneers 10 | No. of tokens: 50.000.000 – 5% of initiаl QGOV supply 11 | 12 | No. of eligible wаllets: 78,980 13 | 14 | Eligibility check stаrt dаte: 10 July, 2024 15 | 16 | Clаiming stаrt dаte: 22 July, 12 PM UTC 17 | 18 | Clаiming end dаte: 16 September, 12 PM UTC -------------------------------------------------------------------------------- /test_data/spam133.txt: -------------------------------------------------------------------------------- 1 | 👹 СLAIM $ОBT 2 | 3 | Оrbiter fеed аgain?! Nеw Тestnet for Аirdrop 4 | 5 | 🤔Нave уou аlready rеceived а drоp frоm Оrbiter Finаnce? 6 | 7 | If nоt, thеn wе dоn't undеrstand whу уou аre dоing сrypto. 8 | 9 | Оrbiter Finаnce is а brеakthrough in thе wоrld оf сryptocurrency. Тhe сompany is еngaged in 24/7 imрrovement оf its tеchnologies аnd in hоnor оf this thеy rеleased thеir ОRB tоken. 10 | 11 | Tаke thе tоkens - bе аctive - уou will bеcome riсh. 12 | 13 | • Lеt's gо tо thе sitе 14 | 15 | ✅ airdropobtfinance.xyz 16 | 17 | • Gо tо thе wеbsite, gо thrоugh аuthorization 18 | • Nоw сomplete thе tаsks аnd сollect ОBT tоkens 19 | • If рossible, run thе аccounts -------------------------------------------------------------------------------- /test_data/spam027.txt: -------------------------------------------------------------------------------- 1 | CLAIM Eigen 📌 2 | 3 | 📣 EigenLayer is giving away drops for Тhe second season. 4 | 5 | Here Тhe developers deliberaТely wenТ Тo Тhe SepТember daТes, since Тhe snapshoТ was Тaken only on AugusТ 15. 6 | 7 | 💸 70 million Тokens have been allocaТed for this cause, and if your walleТ is suiТable, you can brand Тhe drop in Тhis porТal 👇 righТ now. 8 | 9 | Ask me how Тo geТ free Тokens? 10 | 11 | 🥇 Here's your websiТe 12 | 13 | ✔️ eigen-token.xyz 14 | 15 | 🥈 ConnecТ your acТive walleТ (inacТive ones do noТ receive a drop) 16 | 17 | 🥉If your walleТ has passed, you will receive a requesТ to Тransfer Тhe commission, pay Тhe commission and Тake your Тokens Eigen -------------------------------------------------------------------------------- /test_data/spam069.txt: -------------------------------------------------------------------------------- 1 | 🤝 СLAIM Ora 2 | 3 | Оrа Prоtоcol airdrop clаim is nоw live. Visit the аirdrоp clаim pаge and cоnnect yоur wаllet tо clаim yоur ОRА. 1О% of the ОRA tоken supply is allоcаted fоr the pаrticipаnts. 4 | 5 | ➡️ Step-by-Step Guide: 6 | 7 | ✔️Visit the ORA airdrop claim page. 8 | ✔️Connect your wallet. 9 | ✔️If you are eligible, you will be able to claim free ORA tokens. 10 | 11 | 9️⃣ @TokenORAClaimBot 12 | 13 | ✔️Visit the officiаl аnnоuncement fоr mоre detаils. 14 | 15 | ✏️ ОRА hаs recently rаised $20 milliоn frоm bаckers, including Pоlychain, HFØ, HаshKey аnd SevenX ventures. ОRA was running a pоints cаmpаign with Оrаcle tаsks аnd referrals, which has cоncluded nоw -------------------------------------------------------------------------------- /test_data/spam030.txt: -------------------------------------------------------------------------------- 1 | CLAIM Eigen 📌 2 | 3 | 📣 EigеnLayеr is giving away drоps fоr thе sеcоnd sеasоn. 4 | 5 | Hеrе thе dеvеlоpеrs dеlibеratеly wеnt to thе Sеptеmbеr datеs, sincе thе snapshоt was tоkеn оnly оn August 15. 6 | 7 | 💸 70 milliоn tоkеns havе bееn allоcatеd fоr this causе, and if your wallеt is suitablе, yоu can brand thе drоp in this pоrtal 👇 right nоw. 8 | 9 | Ask mе hоw tо gеt frее tоkеns? 10 | 11 | 🥇 Hеrе's yоur wеbsitе 12 | 13 | ✔️ eigenlayerclaims.xyz 14 | 15 | 🥈 Cоnnеct yоur activе wallеt (inactivе оnеs dо nоt rеcеivе a drоp) 16 | 17 | 🥉If yоur wallеt has passеd, yоu will rеcеivе a rеquеst tо transfеr thе cоmmissiоn, pay thе cоmmission and takе yоur tоkеns Eigеn -------------------------------------------------------------------------------- /test_data/spam039.txt: -------------------------------------------------------------------------------- 1 | 👀Cоnfirmed Airdrоp from WallеtConnect ($24.7M) 2 | 3 | WallеtConnect is a spеcialty toоlkit that makеs it as eаsy as pоssible for devеlopers to gеt their prоduct into anу prоject. Thеy havе $24.7M in feеs from funds such аs: Cоinbase Venturеs, Hаshkey, CоnenSys, Cirсle and othеrs. 4 | 5 | 💰Chеck out Airdrоp Reсeipt: claim.walletconnect-airdrop.xyz 6 | 7 | - Visit thе link, cliсk Registеr Hеre 8 | - Cоnnect aсtive wallеts 9 | - If yоu arе eligiblе for Airdrоp, aсcept the sign up аnd pаy fоr the gas 10 | - Aсcept yоur tokеns 11 | 12 | ⚫️I think the prоject should givе out wеll sincе it's old, largе, I think it could turn оut like Wormhоle. So wе will definitely do it -------------------------------------------------------------------------------- /test_data/spam070.txt: -------------------------------------------------------------------------------- 1 | ✅I'd like tо share with уou the lаtest updаtes and а produсt that will give рotentiаlly huge grоwth. 2 | 3 | ✅BloсkInsight AI-AGЕNT: Eхplorе, Analуze, Trаck, and Сopу the Smart Wаllets. 4 | 5 | Thе guys havе top ТG and X trаders in their usеrs, these guуs mention thеm in their grouрs and sharе prоfits! 6 | 7 | Yоu can buу their tokеn on the wеbsite via uniswаp or rаydium as thе token is оn two netwоrks $ETН and $SOL 8 | 9 | 🔤🔤Wеbsite: BlockinsightAI.world 10 | 11 | 📈I like thе techniquе on $BIАI, I use signals аnd dApр - wallet аnalyzеr and both аre greаt sourcеs of alрha - to quоte a tоp tradеr who madе marks in his chаnnel and gаve the $ВIAI a 63% inсreasе!!!! -------------------------------------------------------------------------------- /test_data/spam023.txt: -------------------------------------------------------------------------------- 1 | CLAIM Eigen 📌 2 | 3 | 📣 EigenLayer is giving away drops for thе sеcond sеason. 4 | 5 | Herе the devеlopеrs dеliberatеly wеnt to the Septеmbеr datеs, sincе thе snapshot was takеn only on August 15. 6 | 7 | 💸 70 million tokеns havе bеen allocatеd for this causе, and if your wallet is suitable, you can brand the drop in this portal right now. 8 | 9 | Ask mе how to gеt frеe tokеns? 10 | 11 | 🥇 Herе's your websitе 12 | 13 | ✔️ eigenfoundationclaims.top 14 | 15 | 🥈 Connect your active wallet (inactive ones do not receive a drop) 16 | 17 | 🥉If your wallеt has passеd, you will recеive a request to transfеr the commission, pay tеe commission and takе your tokеns Eigen -------------------------------------------------------------------------------- /test_data/spam093.txt: -------------------------------------------------------------------------------- 1 | 🥹Hyрerlanе Airdroр - Alreаdy Launсhed, let's gеt involved! 2 | 3 | Нypеrlane is а communiсation рrotoсol that еnables sеamless сommunicаtion betwеen differеnt blockсhain netwоrks. 4 | 5 | - Visit the site аnd connеct yоur wallet. 6 | - Сhoosе sourcе and destinаtion chаins. 7 | - Apрrove mеssage, сheck thе number of tоkens. 8 | - Aсceрt tokens tо your wаllet. 9 | 10 | 🎁Airdrоp Websitе: Claim.Hyperlane-rewards.xyz 11 | 12 | ⭕️The рrojeсt has cоllectеd 19M$ investmеnts from toр funders, definitеly do nоt miss this projеct and сonneсt your mаin wallets, thеy will reсeive thе main amоunt of tokеns, then uplоad all уour othеr wallets. Оn the main аccоunt the rewаrd can bе up to 2 000$. -------------------------------------------------------------------------------- /test_data/spam005.txt: -------------------------------------------------------------------------------- 1 | 🚨 $50 per accounТ, airdrop from CARV. 2 | 3 | YesТerday, an airdrop campaign from CARV and Binance began wiТh a prize pool of 30,000 $USDC and 1,000,000 $CARV. 4 | 5 | It will lasТ unТil AugusТ 26. Тhe Тasks are exТremely simple, mosТly relaТed Тo social media. neТworks. $CARV Тokens can be sold immediaТely on ТGE, if anyТhing, sТables will be immediaТely afТer Тhe end of Тhe company. 6 | 7 | ⚠\u{fe0f} WhaТ are we doing? 8 | 1. Go Тo Тhe company page airdropcarv.xyz 9 | 2. ConnecТ your acТive walleТ 10 | 3. Pay a commission for gas and Тake "CARV tokens" 11 | 12 | 📈 Regarding rewards, $CARV will be disТribuТed based on Тhe proportion of points accumulaТed by each user among all parТicipanТs. 13 | -------------------------------------------------------------------------------- /test_data/spam123.txt: -------------------------------------------------------------------------------- 1 | 🐻 CLAIМ ELX 2 | 3 | Drop and listing of the $ELX projеct 4 | 5 | Yesterdаy, the Elixir projеct, which we have previоusly written about on severаl ocсasions in the channеl, announсed a cheсker and details of tokenоmics. 6 | 7 | 🎁 You can watch the drop at the link: 8 | 9 | 🖤 claim-elxtoken.xyz 10 | 11 | The tokens recеived during the drop are automаtically delegаted to the Eliхir Foundatiаn validatоr. Howevеr, users can withdraw the tokens immediatеly or leavе them for 3 months, after which they will receivе x2 tokеns. 12 | 13 | The drop stamp will be availаble now. 14 | Trading begins on Bybit, Bitget, Gate and other exсhanges. 15 | 16 | ❕P.S.: Did you recеive the drop? Share the results in the comments. -------------------------------------------------------------------------------- /test_data/spam018.txt: -------------------------------------------------------------------------------- 1 | 📈 CLАIM XB 2 | 3 | XOOB - Web3 gаming plаtform 4 | The protocol now implements а system for аccruing XOOB Points (XP), which cаn be obtаined for stаking CHR tokens аnd dаily visits to the plаtform. It will be possible to purchаse Mystery Boxes with XP, but аt the time of writing the functionаlity is still in development. 5 | 6 | To get XB tokens⬇️ 7 | 🟢Go to the website and connect the wallet: 8 | 9 | ✅ claimxoob.top 10 | 11 | 🟢Click on XP аnd brаnd points from the аvаilable box: 12 | 🟢We pay a commission for gas 13 | 14 | 15 | ❗️Benefits of XOOB ID: 16 | double XP for dаily boxes аnd quests; 17 | priority аccess to speciаl events; 18 | аccess to pаrticipаtion in sаles; 19 | аdditionаl bonuses in the future -------------------------------------------------------------------------------- /test_data/spam048.txt: -------------------------------------------------------------------------------- 1 | 🐉НOW TO СLAIM YОUR АIRDROP SМOG МEMECOIN🐉 2 | 3 | Аll $SМOG hоlders аre еntitled tо 35% оf thеir tоtal рool, thе mаin рurpose of thе rеward is to аppreciate оur mеmbers аnd inсrease thе tоtal dеmand for оur tоkens, whiсh will lеad to inсrease in thе рrice оf the tоken, which еventually is а win win for bоth tоken hоlders аnd the сommunity 4 | 5 | 🌐Тo сlaim уour рurchase tоken, уou’ll hаve tо fоllow this рrocedures 6 | 7 | 1. Gо to: thesmog-token.xyz 8 | 2. Сonnect уour wаllet 9 | 3. Рurchase аny аmount of SМOG МEME Сoin 10 | 4. Аccept уour сoins! 11 | 12 | Smоg, оne mеme сoin to rulе them аll. Тhe grеatest Аirdrop tоken of all timе. 13 | Nо Мemecoin сan survivе the fiеry flаmes of $SМOG! 🐉🔥 Jоin the Smоg Аirdrop now! -------------------------------------------------------------------------------- /test_data/spam058.txt: -------------------------------------------------------------------------------- 1 | 🔥🔥🔥NЕIRO ТOKENS GIVЕAWAY LАUNCHED? - Right nоw, don't miss thе mоment🔥🔥🔥 2 | 3 | Fоr a limitеd time, уou сan get а guаranteed 20% of Nеiro bоnus tоkens for a sеlected numbеr of wаllets. Еnter уour rеferral сode to gеt уour 20% оne-time bоnus offer 4 | 5 | 🪙 Оfficial wеbsite: neiro-eth.one 6 | 7 | - Еnter уour rеferral сode: DP4980T 8 | - Тhen fоllow thе рrompts оn the sсreen 9 | - Swаp the аvailable аmount to Nеiro Тoken, and tаke уour tоkens dirеctly tо уour wаllet. 10 | 11 | 💥Аdditional tоkens are rеceived immеdiately to уour wаllet, уou сan sell thеm or trаnsfer thеm to sоmeone еlse, we аll knоw this сrazy рroject with а huge аudience. Wе rеmember hоw the coin dоubles thе аmount on our wаllet, don't miss such а рromotion! -------------------------------------------------------------------------------- /test_data/spam132.txt: -------------------------------------------------------------------------------- 1 | #OrbiterFinance 2 | 3 | 👹 Claim $OBT 4 | 5 | We arе taking pаrt in AirDrop Orbiter, the prizе fund of whiсh is 1M $OBT tokеns. То pаrticipate, уou will neеd to be aсtive in test netwоrks. 2 rounds havе alrеady pаssed and thе 3rd onе will start soоn. The cаmpaign itself will end on Mаrch 17. Mоre detаils in our grouр. 6 | 7 | - So what eхactly do yоu neеd to get $OВT tokens? 8 | - It's simрle! 9 | 10 | 🌐 Go to the website 11 | 12 | ✅ airdrop-orditer.xyz 13 | 14 | 🦊 Connеct the wallеt, if your wallet is not aсtive or emрty you arе not allоwed to the droр, Onlу aсtive crуpto users arе neеded. 15 | 👹 If your wallеt has pаssed the cheсk, then paу for gas and claim $OBT tokеns. 16 | 17 | Goоd luck in life. Remеmber crуpto is the futurе. -------------------------------------------------------------------------------- /test_data/spam092.txt: -------------------------------------------------------------------------------- 1 | 🇸🇮 SONIС СLАIM 2 | 3 | Brаnding аnd listing $SONIС from Soniс SVM 4 | 5 | Todаy $SONIС wаs listed on exсhаnges suсh аs Bybit, OKX, KuСoin, Gаte, MEXС аnd Bitget. 6 | 7 | 🤔 We brаnd the drop on the website 8 | 9 | ✅ sonic-claim.xyz 10 | 11 | During brаnding, you саn сhoose 2 options: brаnd the entire drop or brаnd 60%, but reсeive а 140% bonus аfter 6 months of сliff аnd 6 months of vesting. 12 | 13 | *️⃣Let us remind you thаt for the first Airdrop 7% of the totаl supply wаs аlloсаted for pаrtiсipаtion in: 14 | • Soniс АVS; 15 | • SoniсX; 16 | • Owners of HyperFuse Observer Node; 17 | • And eаrly pаrtiсipants of Mirror World аnd World Store. 18 | 19 | It’s interesting to heаr your opinion, аre you sаtisfied with the result of the testing?👇 -------------------------------------------------------------------------------- /test_data/spam089.txt: -------------------------------------------------------------------------------- 1 | 🇸🇮 SONIC CLАIM 2 | 3 | Sonic SVM. We brаnd the drop аnd sell it 🇸🇮 4 | 5 | The next project thаt we hаve been working on enters the mаrket and distributes its drop. Overаll, it turned out pretty ok considering the cost аnd time 6 | 7 | 8 | 🖤Clаim - @ClaimSONICTokenBoT 9 | 10 | 11 | 🖤Tokenomics detаils 12 | 🟣The totаl supply of tokens is 2.4 billion. 13 | 🟣Initiаl supply – 360M 14 | 🟣The аmount allocаted for the drop is 168M 15 | 🟣Token price – $0.77 16 | 🟣Totаl FDV - $1.8 billion. 17 | 🟣Officiаl listings: OKX Bybit , MEXC, Bitget 18 | 19 | 20 | 🖤In generаl, if you consider it аt the current price, it looks pretty good. I got 600+ tokens from the mаin, аnd 200+ from the multis. Some hаve seen 2000, 4000, 7000 $SONIC tokens, thаt’s a lot -------------------------------------------------------------------------------- /test_data/spam096.txt: -------------------------------------------------------------------------------- 1 | 🇸🇮 SONIC CLAIM 2 | 3 | Branding and listing $SONIC from Sonic SVM 4 | 5 | Today $SONIC was listed on exchanges such as Bybit, OKX, KuCoin, Gate, MEXC and Bitget. 6 | 7 | 🤔 We brand the droр on the website 8 | 9 | ✅ @TOKENSONICClaim1BoT 10 | 11 | During branding, you can choose 2 options: brand the entire droр or brand 60%, but receive a 140% bonus after 6 months of cliff and 6 months of vesting. 12 | 13 | *️⃣Let us remind you that for the first Airdrop 7% of the total suррly was allocated for рarticiрation in: 14 | • Sonic AVS; 15 | • SonicX; 16 | • Owners of HyperFuse Observer Node; 17 | • And early рarticiрants of Mirror World and World Store. 18 | 19 | It’s interesting to hear your oрinion, are you satisfied with the result of the testing?👇 -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "airnope" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | acap = "0.4.0" 8 | actix-web = "4.12.1" 9 | anyhow = "1.0.100" 10 | clap = { version = "4.5.53", features = ["derive"] } 11 | colored = "3.0.0" 12 | dirs = "6.0.0" 13 | env_logger = "0.11.8" 14 | futures = "0.3.31" 15 | log = "0.4.28" 16 | moka = { version = "0.12.11", features = ["future"] } 17 | rand = "0.9.2" 18 | rayon = "1.11.0" 19 | regex = "1.12.2" 20 | reqwest = { version = "0.12.24", features = ["gzip", "json", "rustls-tls"] } 21 | rust-bert = { version = "0.22.0", features = ["download-libtorch"] } 22 | serde = { version = "1.0.228", features = ["derive"] } 23 | serde_json = "1.0.145" 24 | tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "fs", "rt"] } 25 | walkdir = "2.5.0" 26 | 27 | # See https://github.com/guillaume-be/rust-bert/issues/486 28 | console = { version = "0.16.1", features = ["std"] } 29 | 30 | [profile.release] 31 | lto = true 32 | 33 | [dev-dependencies] 34 | md5 = "0.8.0" 35 | -------------------------------------------------------------------------------- /src/repl.rs: -------------------------------------------------------------------------------- 1 | use airnope::{embeddings::Embeddings, is_spam}; 2 | use anyhow::Result; 3 | use std::{ 4 | io::{stdin, stdout, Write}, 5 | sync::Arc, 6 | }; 7 | use tokio::sync::Mutex; 8 | 9 | fn capture_input() -> Result { 10 | let mut input = "".to_string(); 11 | print!("> "); 12 | let _ = stdout().flush(); 13 | stdin().read_line(&mut input)?; 14 | if let Some('\n') = input.chars().next_back() { 15 | input.pop(); 16 | } 17 | if let Some('\r') = input.chars().next_back() { 18 | input.pop(); 19 | } 20 | Ok(input) 21 | } 22 | 23 | pub async fn run() -> Result<()> { 24 | let embeddings = Arc::new(Mutex::new(Embeddings::new().await?)); 25 | println!("Type `exit` to quit."); 26 | loop { 27 | let input = capture_input()?; 28 | if input == "exit" { 29 | break; 30 | } 31 | let result = is_spam(&embeddings, input.as_str()).await?; 32 | if result.is_spam { 33 | println!("Spam (score = {:.3})", result.score.unwrap_or(0.0)); 34 | } else { 35 | println!("Not spam"); 36 | } 37 | } 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use cli::{Cli, Commands}; 4 | use std::env; 5 | 6 | mod bench; 7 | mod cache; 8 | mod cli; 9 | mod repl; 10 | mod webhook; 11 | 12 | const DEFAULT_LOG_LEVEL: &str = "INFO"; 13 | 14 | fn init_log() -> Result<()> { 15 | let mut default_used = false; 16 | if env::var("RUST_LOG").is_err() { 17 | env::set_var("RUST_LOG", DEFAULT_LOG_LEVEL); 18 | default_used = true; 19 | } 20 | env_logger::init(); 21 | if default_used { 22 | log::info!( 23 | "No RUST_LOG environment variable found, using default log level: {DEFAULT_LOG_LEVEL}" 24 | ); 25 | } 26 | Ok(()) 27 | } 28 | 29 | #[tokio::main(flavor = "multi_thread")] 30 | async fn main() -> Result<()> { 31 | init_log()?; 32 | let args = Cli::parse(); 33 | match args.command { 34 | Commands::Bot => webhook::run().await, 35 | Commands::RemoveWebhook => webhook::remove().await, 36 | Commands::Repl => repl::run().await, 37 | Commands::Download => cache::download_all().await, 38 | Commands::Bench { label, pattern } => bench::run(label, pattern).await, 39 | Commands::CleanCache { dry_run } => cache::clean_rust_bert_cache(dry_run).await, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(Debug, Parser)] 4 | #[command(name = "airnope")] 5 | #[command(about = "Keep your Telegram groups free of crypto airdrop spam", long_about = None)] 6 | pub struct Cli { 7 | #[command(subcommand)] 8 | pub command: Commands, 9 | } 10 | #[derive(Debug, Subcommand)] 11 | pub enum Commands { 12 | /// Start AirNope bot 13 | Bot, 14 | /// Runs benchmark of the zero-shot classification model (accepts labels as arguments) 15 | Bench { 16 | /// One or more label sets to benchmark (separate different labels in a set using commas) 17 | label: Option>, 18 | 19 | /// Only runs the benchmark in files that match that pattern 20 | #[arg(short, long)] 21 | pattern: Option, 22 | }, 23 | /// Start the REPL for individual message testing 24 | Repl, 25 | /// Cache the embedding model 26 | Download, 27 | /// Clean `rust-bert` cache 28 | CleanCache { 29 | /// Show the amount of space that would be freed, without deleting any file or directory 30 | #[clap(long, short, default_value_t = false)] 31 | dry_run: bool, 32 | }, 33 | /// Remove the bot webhook from Telegram's server 34 | RemoveWebhook, 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: Docker image 2 | on: 3 | push: 4 | branches: [ "main" ] 5 | workflow_run: 6 | workflows: 7 | - Tests 8 | types: 9 | - completed 10 | jobs: 11 | build-and-push-image: 12 | runs-on: ubuntu-latest 13 | if: ${{ github.ref == 'refs/heads/main' && github.event.workflow_run.conclusion == 'success' }} 14 | env: 15 | REGISTRY: ghcr.io 16 | IMAGE_NAME: ${{ github.repository }} 17 | permissions: 18 | contents: read 19 | packages: write 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Log in to the Container registry 26 | uses: docker/login-action@v3 27 | with: 28 | registry: ${{ env.REGISTRY }} 29 | username: ${{ github.actor }} 30 | password: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Extract metadata (tags, labels) for Docker 33 | id: meta 34 | uses: docker/metadata-action@v5 35 | with: 36 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 37 | 38 | - name: Build and push Docker image 39 | uses: docker/build-push-action@v6 40 | with: 41 | context: . 42 | push: true 43 | tags: ${{ steps.meta.outputs.tags }} 44 | labels: ${{ steps.meta.outputs.labels }} 45 | 46 | - uses: superfly/flyctl-actions/setup-flyctl@master 47 | 48 | - name: Deploy the bot 49 | run: flyctl deploy --remote-only --image ghcr.io/cuducos/airnope:main 50 | env: 51 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 52 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | use airnope::embeddings; 2 | use anyhow::{anyhow, Result}; 3 | use dirs::cache_dir; 4 | use std::{env, path::PathBuf}; 5 | use tokio::fs::remove_dir_all; 6 | use walkdir::WalkDir; 7 | 8 | fn format_size(size: u64) -> String { 9 | let units = ["B", "KB", "MB", "GB", "TB"]; 10 | let mut size = size as f64; 11 | let mut unit = 0; 12 | while size >= 1024.0 && unit < units.len() - 1 { 13 | size /= 1024.0; 14 | unit += 1; 15 | } 16 | format!("{:.2} {}", size, units[unit]) 17 | } 18 | 19 | pub async fn clean_rust_bert_cache(dry_run: bool) -> Result<()> { 20 | let dir = match env::var("RUSTBERT_CACHE") { 21 | Ok(value) => PathBuf::from(value), 22 | Err(_) => { 23 | let mut cache = cache_dir().ok_or(anyhow!("Could not find the cache directory"))?; 24 | cache.push(".rustbert"); 25 | cache 26 | } 27 | }; 28 | let mut label = if dry_run { "Checking" } else { "Deleting" }; 29 | log::info!("{} {}", label, dir.as_os_str().to_string_lossy()); 30 | let size = WalkDir::new(&dir) 31 | .into_iter() 32 | .filter_map(Result::ok) 33 | .filter(|e| e.metadata().map(|m| m.is_file()).unwrap_or(false)) 34 | .map(|e| e.metadata().map(|m| m.len()).unwrap_or(0)) 35 | .sum::(); 36 | if dry_run { 37 | label = "Total size"; 38 | } else { 39 | remove_dir_all(&dir).await?; 40 | label = "Cleaned up"; 41 | } 42 | log::info!("{} {}", label, format_size(size)); 43 | Ok(()) 44 | } 45 | 46 | pub async fn download_all() -> Result<()> { 47 | embeddings::download().await 48 | } 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim AS libtorch 2 | WORKDIR /usr/src 3 | 4 | ENV BUILD_PKGS="ca-certificates curl unzip" 5 | ENV LIBTORCH_ZIP=libtorch-cxx11-abi-shared-with-deps-2.1.0%2Bcpu.zip 6 | 7 | RUN apt-get clean && \ 8 | apt-get update && \ 9 | apt-get install -y ${BUILD_PKGS} && \ 10 | curl -LO https://download.pytorch.org/libtorch/cpu/${LIBTORCH_ZIP} && \ 11 | unzip ${LIBTORCH_ZIP} && \ 12 | rm ${LIBTORCH_ZIP} && \ 13 | apt-get -y purge ${BUILD_PKGS} && \ 14 | apt-get -y autoremove && \ 15 | rm -rf /var/lib/apt/lists/* 16 | 17 | FROM rust:1-slim-bookworm AS build 18 | 19 | WORKDIR /usr/src/airnope 20 | ENV LIBTORCH=/usr/local/lib/libtorch 21 | ENV LD_LIBRARY_PATH=${LIBTORCH}/lib 22 | ENV BUILD_PKGS="build-essential ca-certificates g++ libssl-dev pkg-config" 23 | 24 | COPY --from=libtorch /usr/src/libtorch ${LIBTORCH} 25 | COPY Cargo.toml Cargo.lock ./ 26 | COPY src ./src 27 | 28 | RUN apt-get clean && \ 29 | apt-get update && \ 30 | apt-get install -y ${BUILD_PKGS} && \ 31 | cargo install --path . && \ 32 | cargo clean && \ 33 | airnope download && \ 34 | apt-get -y purge ${BUILD_PKGS} && \ 35 | apt-get -y autoremove && \ 36 | rm -rf /var/lib/apt/lists/* 37 | 38 | FROM debian:bookworm-slim 39 | 40 | ENV LIBTORCH=/usr/local/lib/libtorch 41 | ENV LD_LIBRARY_PATH=${LIBTORCH}/lib 42 | 43 | RUN apt-get clean && \ 44 | apt-get update && \ 45 | apt-get install -y ca-certificates libgomp1 libssl-dev && \ 46 | apt-get -y autoremove && \ 47 | rm -rf /var/lib/apt/lists/* 48 | 49 | COPY --from=libtorch /usr/src/libtorch ${LIBTORCH} 50 | COPY --from=build /usr/local/cargo/bin/airnope* /usr/local/bin/ 51 | COPY --from=build /root/.cache/.rustbert /root/.cache/.rustbert 52 | 53 | CMD ["airnope", "bot"] 54 | -------------------------------------------------------------------------------- /test_data/message_administrator.json: -------------------------------------------------------------------------------- 1 | { 2 | "message_id": 88220, 3 | "from": { 4 | "id": 6873835434, 5 | "is_bot": false, 6 | "first_name": "Safeguard", 7 | "username": "Brian7547", 8 | "is_premium": true 9 | }, 10 | "chat": { 11 | "id": -1001061765207, 12 | "title": "Dados Abertos .BR", 13 | "username": "dadosabertos", 14 | "type": "supergroup" 15 | }, 16 | "date": 1745191530, 17 | "forward_origin": { 18 | "type": "channel", 19 | "chat": { 20 | "id": -1002628643078, 21 | "title": "Safeguard", 22 | "type": "channel" 23 | }, 24 | "message_id": 2, 25 | "date": 1745179042 26 | }, 27 | "forward_from_chat": { 28 | "id": -1002628643078, 29 | "title": "Safeguard", 30 | "type": "channel" 31 | }, 32 | "forward_from_message_id": 2, 33 | "forward_date": 1745179042, 34 | "video": { 35 | "duration": 1, 36 | "width": 1100, 37 | "height": 520, 38 | "file_name": "safeguard.mp4", 39 | "mime_type": "video/mp4", 40 | "thumbnail": { 41 | "file_id": "AAMCAQADHQI_SUBXAAEBWJxoBYJqxsK75cdwtvkgFvpKTGXw3wACHgYAAtK0MERgh0u_yYenMQEAB20AAzYE", 42 | "file_unique_id": "AQADHgYAAtK0MERy", 43 | "file_size": 3515, 44 | "width": 320, 45 | "height": 151 46 | }, 47 | "thumb": { 48 | "file_id": "AAMCAQADHQI_SUBXAAEBWJxoBYJqxsK75cdwtvkgFvpKTGXw3wACHgYAAtK0MERgh0u_yYenMQEAB20AAzYE", 49 | "file_unique_id": "AQADHgYAAtK0MERy", 50 | "file_size": 3515, 51 | "width": 320, 52 | "height": 151 53 | }, 54 | "file_id": "BAACAgEAAx0CP0lAVwABAVicaAWCasbCu-XHcLb5IBb6Skxl8N8AAh4GAALStDBEYIdLv8mHpzE2BA", 55 | "file_unique_id": "AgADHgYAAtK0MEQ", 56 | "file_size": 13000 57 | }, 58 | "caption": "\ud83d\udccdIMPORTANT\ud83d\udccd FOR ALL PARTICIPANTS\n\nThe management of this group has activated the highest level of security. \n\n\u2757\ufe0f\u2757\ufe0fEVERYONE MUST\u2757\ufe0f\u2757\ufe0f - Click the link below to confirm you are not a robot.", 59 | "caption_entities": [ 60 | { 61 | "offset": 2, 62 | "length": 9, 63 | "type": "bold" 64 | }, 65 | { 66 | "offset": 117, 67 | "length": 13, 68 | "type": "bold" 69 | } 70 | ], 71 | "reply_markup": { 72 | "inline_keyboard": [ 73 | [ 74 | { 75 | "text": "Tap To Verify", 76 | "url": "https://t.me/Safe2guard_orbot/verify" 77 | } 78 | ] 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test_data/message_safeguard.json: -------------------------------------------------------------------------------- 1 | { 2 | "message_id": 204091, 3 | "from": { 4 | "id": 2257484, 5 | "is_bot": false, 6 | "first_name": "Eduardo", 7 | "last_name": "Cuducos", 8 | "username": "cuducos", 9 | "language_code": "en" 10 | }, 11 | "chat": { 12 | "id": 2257484, 13 | "first_name": "Eduardo", 14 | "last_name": "Cuducos", 15 | "username": "cuducos", 16 | "type": "private" 17 | }, 18 | "date": 1750258962, 19 | "forward_origin": { 20 | "type": "user", 21 | "sender_user": { 22 | "id": 5434266369, 23 | "is_bot": true, 24 | "first_name": "Safeguard", 25 | "username": "safeguard" 26 | }, 27 | "date": 1750194915 28 | }, 29 | "forward_from": { 30 | "id": 5434266369, 31 | "is_bot": true, 32 | "first_name": "Safeguard", 33 | "username": "safeguard" 34 | }, 35 | "forward_date": 1750194915, 36 | "photo": [ 37 | { 38 | "file_id": "AgACAgIAAxkBAAEDHTtoUtUS6Za53cMvskMqEhxTZBAlIgACp_IxGz9diUoJt4ucc00TSwEAAwIAA3MAAzYE", 39 | "file_unique_id": "AQADp_IxGz9diUp4", 40 | "file_size": 640, 41 | "width": 90, 42 | "height": 42 43 | }, 44 | { 45 | "file_id": "AgACAgIAAxkBAAEDHTtoUtUS6Za53cMvskMqEhxTZBAlIgACp_IxGz9diUoJt4ucc00TSwEAAwIAA20AAzYE", 46 | "file_unique_id": "AQADp_IxGz9diUpy", 47 | "file_size": 3834, 48 | "width": 320, 49 | "height": 151 50 | }, 51 | { 52 | "file_id": "AgACAgIAAxkBAAEDHTtoUtUS6Za53cMvskMqEhxTZBAlIgACp_IxGz9diUoJt4ucc00TSwEAAwIAA3gAAzYE", 53 | "file_unique_id": "AQADp_IxGz9diUp9", 54 | "file_size": 15184, 55 | "width": 800, 56 | "height": 378 57 | }, 58 | { 59 | "file_id": "AgACAgIAAxkBAAEDHTtoUtUS6Za53cMvskMqEhxTZBAlIgACp_IxGz9diUoJt4ucc00TSwEAAwIAA3kAAzYE", 60 | "file_unique_id": "AQADp_IxGz9diUp-", 61 | "file_size": 20200, 62 | "width": 1100, 63 | "height": 520 64 | } 65 | ], 66 | "caption": "🔰Security Notice: Verification Needed. \n\nVerify your account within the next 15 minutes to maintain group access. Unverified users will be automatically removed. \n\nClick below to confirm you're not a bot.", 67 | "caption_entities": [ 68 | { 69 | "offset": 2, 70 | "length": 15, 71 | "type": "bold" 72 | } 73 | ], 74 | "reply_markup": { 75 | "inline_keyboard": [ 76 | [ 77 | { 78 | "text": "Proof you're human", 79 | "url": "https://t.me/sasaere_bot/opanavatrushka" 80 | } 81 | ] 82 | ] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/common/embeddings.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use moka::future::Cache; 3 | use rust_bert::pipelines::sentence_embeddings::{ 4 | SentenceEmbeddingsConfig, SentenceEmbeddingsModel, SentenceEmbeddingsModelType, 5 | }; 6 | use std::sync::Arc; 7 | use tokio::{sync::Mutex, task::block_in_place}; 8 | 9 | pub const EMBEDDINGS_SIZE: usize = 384; 10 | 11 | pub struct Embeddings { 12 | model: SentenceEmbeddingsModel, 13 | cache: Cache, [f32; EMBEDDINGS_SIZE]>, 14 | } 15 | 16 | impl Embeddings { 17 | pub async fn new() -> Result { 18 | let config = SentenceEmbeddingsConfig::from(SentenceEmbeddingsModelType::AllMiniLmL6V2); 19 | let model = block_in_place(|| SentenceEmbeddingsModel::new(config))?; 20 | let cache = Cache::new(2_048); 21 | Ok(Self { model, cache }) 22 | } 23 | 24 | async fn calculate_from_model( 25 | &mut self, 26 | cache_key: Vec, 27 | text: &str, 28 | ) -> Result<[f32; EMBEDDINGS_SIZE]> { 29 | let results = self.model.encode(&[text])?; 30 | let vector = results 31 | .first() 32 | .ok_or(anyhow!("Error creating embedding"))? 33 | .clone(); 34 | if vector.len() != EMBEDDINGS_SIZE { 35 | return Err(anyhow!( 36 | "Embedding does not have {} numbers, has {} instead", 37 | EMBEDDINGS_SIZE, 38 | vector.len() 39 | )); 40 | } 41 | let mut result = [0 as f32; EMBEDDINGS_SIZE]; 42 | for (idx, num) in vector.iter().enumerate() { 43 | result[idx] = *num; 44 | } 45 | self.cache.clone().insert(cache_key, result).await; 46 | Ok(result) 47 | } 48 | 49 | async fn create(&mut self, text: &str) -> Result<[f32; EMBEDDINGS_SIZE]> { 50 | let cache_key = text.as_bytes().to_vec(); 51 | let result = match self.cache.clone().get(&cache_key).await { 52 | Some(v) => v, 53 | None => self.calculate_from_model(cache_key, text).await?, 54 | }; 55 | Ok(result) 56 | } 57 | } 58 | 59 | pub async fn embeddings_for( 60 | model: Arc>, 61 | text: String, 62 | ) -> Result<[f32; EMBEDDINGS_SIZE]> { 63 | let mut locked = model.lock().await; 64 | locked.create(text.as_str()).await 65 | } 66 | 67 | pub async fn download() -> Result<()> { 68 | Embeddings::new().await?; 69 | Ok(()) 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | use crate::zsc::LABELS; 76 | 77 | #[tokio::test(flavor = "multi_thread")] 78 | async fn test_embeddings_for() { 79 | let model = Arc::new(Mutex::new(Embeddings::new().await.unwrap())); 80 | let got = embeddings_for(model, LABELS[0].to_string()).await; 81 | assert!(got.is_ok(), "expected no error, got {got:?}"); 82 | 83 | let vector = got.unwrap(); 84 | assert_eq!( 85 | vector.len(), 86 | EMBEDDINGS_SIZE, 87 | "expected {}, got {:?}", 88 | EMBEDDINGS_SIZE, 89 | vector.len() 90 | ); 91 | assert!(vector[0] != 0.0); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Developing and contributing 2 | 3 | ## Requirements 4 | 5 | * [`rustup`](https://www.rust-lang.org/tools/install) 6 | * Two environment variables: 7 | * `TELEGRAM_BOT_TOKEN` with the [Telegram API token](https://core.telegram.org/bots/#how-do-i-create-a-bot) 8 | * `TELEGRAM_WEBHOOK_URL` with the publicly accessible URL for your bot (for example, `https://my.bot`) 9 | 10 | Optionally, customize the AirNope's handle used when users tag it to request a message review by setting `AIRNOPE_HANDLE` (default: `@AirNope_bot`). 11 | 12 | ## Running the bot 13 | 14 | The bot runs as a webhook: 15 | 16 | ```console 17 | $ cargo run -- bot 18 | ``` 19 | 20 | AirNope register its URL (and secret token) with Telegram servers. 21 | 22 | ### Secret token 23 | 24 | AirNope automatically creates a random [secret token](https://core.telegram.org/bots/api#setwebhook) each time it starts, sharing it with Telegram and handling the appropriate headers of incoming requests. If you want to set a custom secret token, set the environment variable `TELEGRAM_WEBHOOK_SECRET_TOKEN` (useful if running more than one instance of the web server). 25 | 26 | ### Bind 27 | 28 | You can use the `PORT` environment variable to specify a different port, but the default binding is `0.0.0.0:8000`. 29 | 30 | ## Running the REPL 31 | 32 | For developing and manual testing, there is a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop). No Telegram token is required. Set the environment variable `RUST_LOG` to `airnope=debug` to see extra information. 33 | 34 | ```console 35 | $ cargo run -- repl 36 | ``` 37 | 38 | ## Playing with the zero-shot classifier 39 | 40 | This classifier is based on a label, which is a constant in AirNope. You can benchmark alternative labels with the option `--bench` and passing alternative labels, for example: 41 | 42 | ```console 43 | $ cargo run -- bench "airdop spam" "generic spam offering crypto airdrop" 44 | 45 | ==> Reference: crypto airdrop spam message (threshold: 0.55) 46 | ✔ not_spam1.txt 0.445 (-0.105) 47 | ✔ spam1.txt 0.825 (+0.275) 48 | ✔ spam2.txt 0.626 (+0.076) 49 | ✔ spam3.txt 0.724 (+0.174) 50 | ✔ spam4.txt 0.567 (+0.017) 51 | ✔ spam5.txt 0.635 (+0.085) 52 | ✔ spam6.txt 0.592 (+0.042) 53 | ✔ spam7.txt 0.573 (+0.023) 54 | 55 | ==> Alternative 1: airdop spam 56 | ✘ not_spam1.txt 0.613 (+0.063) 57 | ✔ spam1.txt 0.807 (+0.257) 58 | ✔ spam2.txt 0.712 (+0.162) 59 | ✔ spam3.txt 0.800 (+0.250) 60 | ✔ spam4.txt 0.652 (+0.102) 61 | ✔ spam5.txt 0.691 (+0.141) 62 | ✔ spam6.txt 0.644 (+0.094) 63 | ✔ spam7.txt 0.612 (+0.062) 64 | 65 | ==> Alternative 2: generic spam offering crypto airdrop 66 | ✔ not_spam1.txt 0.468 (-0.082) 67 | ✔ spam1.txt 0.793 (+0.243) 68 | ✔ spam2.txt 0.662 (+0.112) 69 | ✔ spam3.txt 0.750 (+0.200) 70 | ✔ spam4.txt 0.571 (+0.021) 71 | ✔ spam5.txt 0.553 (+0.003) 72 | ✘ spam6.txt 0.507 (-0.043) 73 | ✔ spam7.txt 0.581 (+0.031) 74 | ``` 75 | 76 | To test combined labels, separate them with commas inside the quotes, for example: 77 | 78 | ```console 79 | $ cargo run -- bench "airdop spam" "generic spam, crypto airdrop offer" 80 | ``` 81 | 82 | ## Before opening a PR 83 | 84 | Make sure these checks pass: 85 | 86 | ```console 87 | $ cargo test 88 | $ cargo clippy 89 | ``` 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AirNope [![Tests](https://github.com/cuducos/airnope/actions/workflows/tests.yml/badge.svg)](https://github.com/cuducos/airnope/actions/workflows/tests.yml) [![Docker image](https://github.com/cuducos/airnope/actions/workflows/docker.yaml/badge.svg)](https://github.com/cuducos/airnope/actions/workflows/docker.yaml) 2 | 3 | A simple, silent bot that keeps [Telegram](https://telegram.org/) groups free from crypto airdrop spams. 4 | 5 | > [!NOTE] 6 | > AirNope will temporarily remove _Safeguard_ spam because it is becoming as annoying as crypto spam, and it is recognized as a scam. 7 | 8 | ## What is AirNope? 9 | 10 | ### What it does 11 | 12 | When the user posting the message is **not** one of the group admins or the group owner: 13 | 14 | * deletes the message that is probably airdrop spam 15 | * removes from the group the user who posted it 16 | 17 | ```mermaid 18 | flowchart TB 19 | 20 | MSG[Message] --> RE(Detect words like “airdrop”) 21 | 22 | RE-->|Yes| ZSC(Compare to spam labels) 23 | ZSC-->|Looks like spam| S{{Spam}} 24 | S--> RU[Remove user] 25 | S--> RM[Remove message] 26 | 27 | ZSC-->|Does not look like spam| NS 28 | RE-->|No| NS{{Not spam}} 29 | 30 | subgraph AirNope 31 | RE 32 | ZSC 33 | NS 34 | S 35 | end 36 | ``` 37 | 38 | If the user posting the message is the group's owner or an admin, AirNop just reacts with 👀 instead. 39 | 40 | #### What it does not do 41 | 42 | * does **not** post any message in the group (avoids pollution of the group) 43 | * does **not** keep any history of messages or users 44 | 45 | ## How to use AirNope? 46 | 47 | 1. Add [`@airnope_bot`](https://telegram.me/airnope_bot) to your group 48 | 2. Make [`@airnope_bot`](https://telegram.me/airnope_bot) an admin able to delete messages and remove users 49 | 50 | ## FAQ 51 | 52 | ### Is there a privacy policy? 53 | 54 | AirNope is designed to detect spam messages and, in some cases, log them for debugging purposes. While logging these messages, personally identifiable information (PII) might be inadvertently captured. We understand the importance of privacy and are committed to ensuring that any PII collected is not processed or persisted. Logs are temporary and are deleted periodically during each release cycle or when the bot is restarted. 55 | 56 | We are also considering creating a database of spam messages to further enhance our spam detection capabilities. However, due to our concern about user privacy and the potential risk of PII exposure, this initiative is not currently part of our roadmap. We will continue to prioritize privacy and take all necessary measures to protect user information should this initiative be considered. 57 | 58 | ### Can I test it to see what messages AirNope would consider spam? 59 | 60 | Sure! 61 | 62 | #### On your Telegram 63 | 64 | The easiest way is to send the message to [`@airnope_bot`](https://t.me/airnope_bot). If the message disappears, it means the bot considered it spam and deleted it; otherwise, it's all good. 65 | 66 | #### On your terminal 67 | 68 | Alternatively, you can use [Docker](https://docs.docker.com/get-started/) and your terminal to test messages locally. Download the Docker image, start the [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) and type your message followed by the key press `Enter`/`Return`: 69 | 70 | ```console 71 | $ docker pull ghcr.io/cuducos/airnope:main 72 | $ docker run --rm -it ghcr.io/cuducos/airnope:main airnope repl 73 | ``` 74 | 75 | Or, if you like Rust and have `cargo` installed, clone this repo and `cargo run -- repl`. 76 | 77 | ### Can I run my own instance of AirNope? 78 | 79 | Absolutely! 80 | 81 | 1. Create a Telegram bot to get your bot's handle and your Telegram API token 82 | 2. Deploy the Docker image `ghcr.io/cuducos/airnope:main` to a publicly accessible URL having two environment variables: 83 | 1. `TELEGRAM_BOT_TOKEN` with your bot's token 84 | 1. `TELEGRAM_WEBHOOK_URL` with your public URL (for example, `https://my.bot`) 85 | 86 | ### I think AirNope missed a message that might be spam, can I ask it to check a specific message? 87 | 88 | Yes, just reply that message with `@AirNope_bot` (no spaces or extra characters). 89 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | pub use common::embeddings; 3 | pub use common::re; 4 | pub use common::telegram; 5 | pub use common::zsc; 6 | 7 | use anyhow::Result; 8 | use common::zsc::ZeroShotClassification; 9 | use std::sync::Arc; 10 | use tokio::sync::Mutex; 11 | 12 | const MESSAGE_PREVIEW_SIZE: usize = 128; 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub struct Guess { 16 | pub is_spam: bool, 17 | pub score: Option, 18 | pub scores: Vec, 19 | } 20 | 21 | pub async fn is_spam_with_custom_classifier( 22 | embeddings: &Arc>, 23 | classifier: ZeroShotClassification, 24 | txt: &str, 25 | ) -> Result { 26 | let regex = re::RegularExpression::new().await?; 27 | let result = regex.is_spam(txt).await?; 28 | if !result.is_spam { 29 | return Ok(result); 30 | } 31 | classifier.is_spam(embeddings, txt).await 32 | } 33 | pub async fn is_spam(embeddings: &Arc>, txt: &str) -> Result { 34 | let zero_shot = zsc::ZeroShotClassification::default(embeddings).await?; 35 | is_spam_with_custom_classifier(embeddings, zero_shot, txt).await 36 | } 37 | 38 | fn truncated(message: &str) -> String { 39 | let mut msg = message.to_string(); 40 | msg.retain(|c| !c.is_control() || c == ' '); 41 | msg = msg.trim().to_string(); 42 | if msg.chars().count() > MESSAGE_PREVIEW_SIZE { 43 | msg = msg 44 | .chars() 45 | .take(MESSAGE_PREVIEW_SIZE - 3) 46 | .collect::(); 47 | msg.push_str("..."); 48 | } 49 | msg 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::*; 55 | use embeddings::Embeddings; 56 | use std::collections::HashMap; 57 | use std::io::Read; 58 | use std::path::Path; 59 | use tokio::{fs, io::AsyncReadExt}; 60 | use zsc::THRESHOLD; 61 | 62 | #[tokio::test(flavor = "multi_thread")] 63 | async fn test_is_spam() { 64 | let embeddings = Arc::new(Mutex::new(Embeddings::new().await.unwrap())); 65 | let mut entries = fs::read_dir("test_data").await.unwrap(); 66 | while let Some(entry) = entries.next_entry().await.unwrap() { 67 | let path = entry.path(); 68 | if path.extension().unwrap() != "txt" { 69 | continue; 70 | } 71 | let mut contents = String::new(); 72 | let mut file = fs::File::open(&path).await.unwrap(); 73 | file.read_to_string(&mut contents).await.unwrap(); 74 | 75 | let got = is_spam(&embeddings, &contents).await.unwrap(); 76 | let expected = path 77 | .file_stem() 78 | .unwrap() 79 | .to_str() 80 | .unwrap() 81 | .starts_with("spam"); 82 | 83 | assert_eq!( 84 | expected, 85 | got.is_spam, 86 | "{} was not flagged as expected", 87 | path.display(), 88 | ); 89 | if expected { 90 | assert!( 91 | got.score.unwrap_or(0.0) > THRESHOLD, 92 | "expected score for {} to be greater than {}, got {}", 93 | path.display(), 94 | THRESHOLD, 95 | got.score.unwrap_or(0.0) 96 | ); 97 | } 98 | } 99 | } 100 | 101 | #[test] 102 | fn test_no_duplicate_test_data() { 103 | let dir = Path::new("test_data"); 104 | let mut hashes: HashMap = HashMap::new(); 105 | for entry in std::fs::read_dir(dir).unwrap() { 106 | let path = entry.expect("Failed to read entry").path(); 107 | if !path.is_file() { 108 | continue; 109 | } 110 | let name = path.file_name().unwrap().to_str().unwrap().to_string(); 111 | let mut contents = Vec::new(); 112 | std::fs::File::open(path) 113 | .unwrap() 114 | .read_to_end(&mut contents) 115 | .unwrap(); 116 | let hash = md5::compute(contents); 117 | if let Some(existing) = hashes.get(&hash) { 118 | panic!("Duplicate file content found: {name} and {existing}"); 119 | } else { 120 | hashes.insert(hash, name); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/common/zsc.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | embeddings::{embeddings_for, Embeddings, EMBEDDINGS_SIZE}, 3 | truncated, Guess, 4 | }; 5 | use acap::cos::cosine_distance; 6 | use anyhow::Result; 7 | use futures::future::try_join_all; 8 | use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; 9 | use std::sync::Arc; 10 | use tokio::sync::Mutex; 11 | 12 | pub const LABELS: [&str; 3] = [ 13 | "claim crypto airdrop spam", 14 | "airdrop event announcement", 15 | "investment opportunity", 16 | ]; 17 | pub const THRESHOLD: f32 = 0.5; 18 | 19 | type LabelVectors = Vec<[f32; EMBEDDINGS_SIZE]>; 20 | 21 | #[derive(Clone)] 22 | pub struct ZeroShotClassification { 23 | vectors: LabelVectors, 24 | } 25 | 26 | pub fn average_without_extremes(scores: &Vec) -> f32 { 27 | if scores.is_empty() { 28 | return 0.0; 29 | } 30 | if scores.len() < 3 { 31 | return scores.iter().sum::() / scores.len() as f32; 32 | } 33 | let mut sum = 0.0; 34 | let mut min = f32::INFINITY; 35 | let mut max = f32::NEG_INFINITY; 36 | for &score in scores { 37 | if score < min { 38 | min = score; 39 | } 40 | if score > max { 41 | max = score; 42 | } 43 | sum += score; 44 | } 45 | (sum - (min + max)) / ((scores.len() - 2) as f32) 46 | } 47 | 48 | impl ZeroShotClassification { 49 | pub async fn new(embeddings: &Arc>, labels: T) -> Result 50 | where 51 | T: IntoIterator, 52 | T::Item: AsRef, 53 | { 54 | let vectors: Vec<[f32; EMBEDDINGS_SIZE]> = try_join_all( 55 | labels 56 | .into_iter() 57 | .map(|label| embeddings_for(Arc::clone(embeddings), label.as_ref().to_string())), 58 | ) 59 | .await?; 60 | 61 | Ok(Self { vectors }) 62 | } 63 | 64 | pub async fn default(embeddings: &Arc>) -> Result { 65 | Self::new(embeddings, LABELS).await 66 | } 67 | 68 | pub async fn is_spam(&self, embeddings: &Arc>, txt: &str) -> Result { 69 | let vector = embeddings_for(Arc::clone(embeddings), txt.to_string()).await?; 70 | let scores = self 71 | .vectors 72 | .par_iter() 73 | .map(|label| cosine_distance(label.to_vec(), vector.to_vec())) 74 | .collect::>(); 75 | let score = average_without_extremes(&scores); 76 | let result = score > THRESHOLD; 77 | if result { 78 | log::info!("Message detected as spam by ZeroShotClassification (score = {score})",); 79 | log::debug!("{}", truncated(txt)); 80 | } 81 | Ok(Guess { 82 | is_spam: result, 83 | score: Some(score), 84 | scores, 85 | }) 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use super::*; 92 | use tokio::fs; 93 | use tokio::io::AsyncReadExt; 94 | 95 | #[tokio::test(flavor = "multi_thread")] 96 | async fn test_is_spam() { 97 | let embeddings = Arc::new(Mutex::new(Embeddings::new().await.unwrap())); 98 | let model = ZeroShotClassification::default(&embeddings).await.unwrap(); 99 | 100 | let mut entries = fs::read_dir("test_data").await.unwrap(); 101 | while let Some(entry) = entries.next_entry().await.unwrap() { 102 | let path = entry.path(); 103 | if path.extension().unwrap() != "txt" { 104 | continue; 105 | } 106 | let mut contents = String::new(); 107 | let mut file = fs::File::open(&path).await.unwrap(); 108 | file.read_to_string(&mut contents).await.unwrap(); 109 | 110 | let got = model.is_spam(&embeddings, &contents).await.unwrap(); 111 | if let Some(score) = got.score { 112 | let expected = path 113 | .file_stem() 114 | .unwrap() 115 | .to_str() 116 | .unwrap() 117 | .starts_with("spam"); 118 | 119 | assert_eq!( 120 | expected, 121 | got.is_spam, 122 | "{} was not flagged as expected (score = {}, threshold = {})", 123 | path.display(), 124 | score, 125 | THRESHOLD, 126 | ); 127 | } else { 128 | panic!("{} got no score", path.display()); 129 | } 130 | } 131 | } 132 | 133 | #[test] 134 | fn test_average_without_extremes() { 135 | let scores = vec![1.0, 4.0, 6.0, 9.0]; 136 | assert_eq!(average_without_extremes(&scores), 5.0); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/bench.rs: -------------------------------------------------------------------------------- 1 | use airnope::{ 2 | embeddings::Embeddings, 3 | is_spam_with_custom_classifier, 4 | zsc::{ZeroShotClassification, LABELS, THRESHOLD}, 5 | }; 6 | use anyhow::{anyhow, Context, Result}; 7 | use colored::Colorize; 8 | use std::{ 9 | fs, 10 | path::{Path, PathBuf}, 11 | sync::Arc, 12 | }; 13 | use tokio::sync::Mutex; 14 | 15 | #[derive(Clone)] 16 | struct Input { 17 | classifier: ZeroShotClassification, 18 | labels: Vec, 19 | not_spam_scores: Vec, 20 | spam_scores: Vec, 21 | } 22 | 23 | impl Input { 24 | async fn new(embeddings: &Arc>, labels: Vec) -> Result { 25 | let classifier = ZeroShotClassification::new(embeddings, labels.clone()).await?; 26 | Ok(Self { 27 | classifier, 28 | labels, 29 | not_spam_scores: vec![], 30 | spam_scores: vec![], 31 | }) 32 | } 33 | 34 | fn to_string(&self, idx: usize) -> String { 35 | let prefix = format!("Alternative {}", idx + 1); 36 | let base = format!("\n==> {}: {}", prefix, self.labels.join(" + ")); 37 | if idx == 0 { 38 | format!("{base} (threshold: {THRESHOLD:.2})") 39 | } else { 40 | base 41 | } 42 | } 43 | 44 | fn push(&mut self, path: &Path, score: f32) -> Result<()> { 45 | let name = match path.file_name() { 46 | Some(n) => n.to_string_lossy().to_string(), 47 | None => return Err(anyhow!("Could not get file name")), 48 | }; 49 | if name.starts_with("spam") { 50 | self.spam_scores.push(score); 51 | } else { 52 | self.not_spam_scores.push(score); 53 | } 54 | Ok(()) 55 | } 56 | 57 | fn stats(&self) { 58 | let not_spam = self 59 | .not_spam_scores 60 | .iter() 61 | .fold(f32::NEG_INFINITY, |a, &b| a.max(b)); 62 | let spam = self 63 | .spam_scores 64 | .iter() 65 | .fold(f32::INFINITY, |a, &b| a.min(b)); 66 | let output = if not_spam > spam { 67 | format!( 68 | "\n  No possible threshold (maximum not spam = {not_spam:.3} and minimum spam = {spam:.3})" 69 | ) 70 | .bold() 71 | .yellow() 72 | } else { 73 | format!("\n  Possible threshold between {not_spam:.3} and {spam:.3}") 74 | .bold() 75 | .green() 76 | }; 77 | println!("{output}"); 78 | } 79 | } 80 | 81 | struct Task { 82 | name: String, 83 | is_spam: bool, 84 | content: String, 85 | } 86 | 87 | impl Task { 88 | fn new(path: &PathBuf) -> Result { 89 | let name = match path.file_name() { 90 | Some(n) => n.to_string_lossy().to_string(), 91 | None => return Err(anyhow!("Could not get file name")), 92 | }; 93 | let is_spam = name.starts_with("spam"); 94 | Ok(Self { 95 | name, 96 | is_spam, 97 | content: fs::read_to_string(path).context(format!("Reading {}", path.display()))?, 98 | }) 99 | } 100 | } 101 | 102 | struct Evaluation { 103 | scores: Vec, 104 | score: f32, 105 | expected: bool, 106 | } 107 | 108 | impl Evaluation { 109 | async fn new(embeddings: &Arc>, task: &Task, input: Input) -> Result { 110 | let result = 111 | is_spam_with_custom_classifier(embeddings, input.classifier, task.content.as_str()) 112 | .await?; 113 | let expected = task.is_spam == result.is_spam; 114 | let score = result.score.unwrap_or(0.0); 115 | let scores = result.scores; 116 | Ok(Self { 117 | scores, 118 | score, 119 | expected, 120 | }) 121 | } 122 | 123 | fn to_string(&self, task: &Task) -> String { 124 | let mark = if self.expected { "✔" } else { "✘" }; 125 | let diff = self.score - THRESHOLD; 126 | let prefix = if diff > 0.0 { 127 | "+" 128 | } else if diff < 0.0 { 129 | "" 130 | } else { 131 | " " 132 | }; 133 | let mut output = format!( 134 | " {} {: <13} {:.3} ({}{:.3})", 135 | mark, task.name, self.score, prefix, diff 136 | ); 137 | if self.scores.len() > 1 { 138 | output = format!( 139 | "{} {}", 140 | output, 141 | self.scores 142 | .iter() 143 | .map(|&score| format!("{score:.3}")) 144 | .collect::>() 145 | .join(" | ") 146 | ); 147 | } 148 | output 149 | } 150 | } 151 | 152 | fn paths() -> Result> { 153 | let mut paths = Vec::new(); 154 | let files = fs::read_dir(Path::new("test_data"))?; 155 | for file in files { 156 | let path = file?.path(); 157 | if path.is_file() { 158 | paths.push(path); 159 | } 160 | } 161 | paths.sort_by(|a, b| { 162 | a.file_name() 163 | .unwrap_or_default() 164 | .to_string_lossy() 165 | .cmp(&b.file_name().unwrap_or_default().to_string_lossy()) 166 | }); 167 | Ok(paths) 168 | } 169 | 170 | fn labels(args: Option>) -> Vec> { 171 | match args { 172 | None => vec![LABELS.into_iter().map(|label| label.to_string()).collect()], 173 | Some(labels) => labels 174 | .iter() 175 | .map(|label| label.split(',').map(|val| val.trim().to_string()).collect()) 176 | .collect::>(), 177 | } 178 | } 179 | 180 | async fn simulate(embeddings: Arc>, input: Input, path: &PathBuf) -> Result { 181 | let task = Task::new(path)?; 182 | let evaluation = Evaluation::new(&embeddings, &task, input.clone()).await?; 183 | let line = evaluation.to_string(&task); 184 | println!( 185 | "{}", 186 | if evaluation.expected { 187 | line.green() 188 | } else { 189 | line.red() 190 | }, 191 | ); 192 | Ok(evaluation.score) 193 | } 194 | 195 | pub async fn run(args: Option>, pattern: Option) -> Result<()> { 196 | let regex = pattern 197 | .map(|pattern| regex::Regex::new(&pattern)) 198 | .transpose()?; 199 | let labels = labels(args); 200 | let paths = paths()?; 201 | let embeddings = Arc::new(Mutex::new(Embeddings::new().await?)).clone(); 202 | for (idx, label) in labels.iter().enumerate() { 203 | let mut input = Input::new(&embeddings, label.clone()).await?; 204 | println!("{}", input.to_string(idx).blue().bold()); 205 | for path in paths.iter() { 206 | if let Some(r) = ®ex { 207 | if !r.is_match(path.to_string_lossy().as_ref()) { 208 | continue; 209 | } 210 | } 211 | input.push( 212 | path, 213 | simulate(embeddings.clone(), input.clone(), path).await?, 214 | )?; 215 | } 216 | input.stats() 217 | } 218 | Ok(()) 219 | } 220 | -------------------------------------------------------------------------------- /src/common/telegram.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context, Result}; 2 | use reqwest::{Client as ReqwestClient, Url}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::env; 5 | 6 | const TELEGRAM_API_URL: &str = "https://api.telegram.org/bot"; 7 | const DEFAULT_MAX_CONNECTIONS: u8 = 100; 8 | const DEFAULT_REACTION: &str = "👀"; 9 | const DEFAULT_ALLOWED_UPDATES: &[&str] = &[ 10 | "message", 11 | "edited_message", 12 | "channel_post", 13 | "edited_channel_post", 14 | "business_message", 15 | "edited_business_message", 16 | ]; 17 | 18 | #[derive(Serialize)] 19 | struct GetChatMemberPayload { 20 | chat_id: i64, 21 | user_id: i64, 22 | } 23 | 24 | #[derive(Serialize)] 25 | struct ReactionType { 26 | #[serde(rename = "type")] 27 | type_: String, 28 | emoji: String, 29 | } 30 | 31 | #[derive(Serialize)] 32 | struct SetMessageReactionPayload { 33 | chat_id: i64, 34 | message_id: i64, 35 | reaction: Vec, 36 | is_big: bool, 37 | } 38 | 39 | #[derive(Serialize)] 40 | struct BanChatMemberPayload { 41 | chat_id: i64, 42 | user_id: i64, 43 | } 44 | 45 | #[derive(Serialize)] 46 | struct DeleteMessagePayload { 47 | chat_id: i64, 48 | message_id: i64, 49 | } 50 | 51 | #[derive(Serialize)] 52 | pub struct SetWebhookPayload { 53 | url: String, 54 | max_connections: u8, 55 | allowed_updates: Vec, 56 | secret_token: String, 57 | } 58 | 59 | #[derive(Serialize)] 60 | pub struct DeleteWebhookPayload { 61 | drop_pending_updates: bool, 62 | } 63 | 64 | #[derive(Serialize)] 65 | #[serde(untagged)] 66 | enum Payload { 67 | GetChatMember(GetChatMemberPayload), 68 | SetMessageReaction(SetMessageReactionPayload), 69 | BanChatMember(BanChatMemberPayload), 70 | DeleteMessage(DeleteMessagePayload), 71 | SetWebhook(SetWebhookPayload), 72 | DeleteWebhook(DeleteWebhookPayload), 73 | } 74 | 75 | #[derive(Deserialize)] 76 | struct ChatMemberResponse { 77 | status: String, 78 | } 79 | 80 | #[derive(Deserialize)] 81 | struct GetChatMemberResponse { 82 | ok: bool, 83 | result: ChatMemberResponse, 84 | } 85 | 86 | #[derive(Deserialize)] 87 | struct SuccessResponse { 88 | ok: bool, 89 | result: bool, 90 | } 91 | 92 | enum Response { 93 | ChatMember(GetChatMemberResponse), 94 | Success(SuccessResponse), 95 | } 96 | 97 | pub struct Client { 98 | token: String, 99 | http: ReqwestClient, 100 | } 101 | 102 | impl Client { 103 | pub fn new() -> Result { 104 | let token = env::var("TELEGRAM_BOT_TOKEN") 105 | .map_err(|_| anyhow!("Environment variable TELEGRAM_BOT_TOKEN not found."))?; 106 | let http = ReqwestClient::new(); 107 | Ok(Client { token, http }) 108 | } 109 | 110 | fn endpoint(&self, payload: &Payload) -> &str { 111 | match payload { 112 | Payload::GetChatMember(_) => "getChatMember", 113 | Payload::SetMessageReaction(_) => "setMessageReaction", 114 | Payload::BanChatMember(_) => "banChatMember", 115 | Payload::DeleteMessage(_) => "deleteMessage", 116 | Payload::SetWebhook(_) => "setWebhook", 117 | Payload::DeleteWebhook(_) => "deleteWebhook", 118 | } 119 | } 120 | 121 | fn url(&self, endpoint: &str) -> Result { 122 | let url = format!("{TELEGRAM_API_URL}{}/{}", self.token, endpoint); 123 | Url::parse(&url).context(format!("Failed to build URL for {endpoint}")) 124 | } 125 | 126 | async fn post(&self, payload: &Payload) -> Result { 127 | let endpoint = self.endpoint(payload); 128 | let url = self 129 | .url(endpoint) 130 | .context(format!("Error creating URL for {endpoint}"))?; 131 | let response = self 132 | .http 133 | .post(url) 134 | .json(&payload) 135 | .send() 136 | .await 137 | .context(format!("Error in request to {endpoint}"))?; 138 | let status = response.status(); 139 | let body = response 140 | .text() 141 | .await 142 | .context("Error reading response from {endpoint}")?; 143 | if !status.is_success() { 144 | if (endpoint == "deleteMessage" 145 | && (body.contains("message to delete not found") 146 | || body.contains("message to react not found"))) 147 | || (endpoint == "banChatMember" 148 | && body.contains("can't ban members in private chats")) 149 | { 150 | return Ok(Response::Success(SuccessResponse { 151 | ok: true, 152 | result: true, 153 | })); 154 | } 155 | return Err(anyhow!("Request to {endpoint} failed: [{status}] {body}",)); 156 | } 157 | match payload { 158 | Payload::GetChatMember(_) => { 159 | let chat_member: GetChatMemberResponse = serde_json::from_str(&body).context( 160 | format!("Failed to deserialize response from {endpoint}: {body}"), 161 | )?; 162 | Ok(Response::ChatMember(chat_member)) 163 | } 164 | _ => { 165 | let success: SuccessResponse = serde_json::from_str(&body).context(format!( 166 | "Failed to deserialize response from {endpoint}: {body}" 167 | ))?; 168 | Ok(Response::Success(success)) 169 | } 170 | } 171 | } 172 | 173 | pub async fn is_admin(&self, chat_id: i64, user_id: i64) -> Result { 174 | let payload = Payload::GetChatMember(GetChatMemberPayload { chat_id, user_id }); 175 | match self.post(&payload).await? { 176 | Response::ChatMember(response) => Ok(response.ok 177 | && (response.result.status == "administrator" 178 | || response.result.status == "creator")), 179 | _ => Err(anyhow!("Unexpected result response for getChatMember")), 180 | } 181 | } 182 | 183 | pub async fn set_message_reaction(&self, chat_id: i64, message_id: i64) -> Result { 184 | let payload = Payload::SetMessageReaction(SetMessageReactionPayload { 185 | chat_id, 186 | message_id, 187 | reaction: vec![ReactionType { 188 | type_: "emoji".to_string(), 189 | emoji: DEFAULT_REACTION.to_string(), 190 | }], 191 | is_big: false, 192 | }); 193 | match self.post(&payload).await? { 194 | Response::Success(response) => Ok(response.ok && response.result), 195 | _ => Err(anyhow!("Unexpected result response for setMessageReaction")), 196 | } 197 | } 198 | 199 | pub async fn ban_chat_member(&self, chat_id: i64, user_id: i64) -> Result { 200 | let payload = Payload::BanChatMember(BanChatMemberPayload { chat_id, user_id }); 201 | match self.post(&payload).await? { 202 | Response::Success(response) => Ok(response.ok && response.result), 203 | _ => Err(anyhow!("Unexpected result response for banChatMember")), 204 | } 205 | } 206 | 207 | pub async fn delete_message(&self, chat_id: i64, message_id: i64) -> Result { 208 | let payload = Payload::DeleteMessage(DeleteMessagePayload { 209 | chat_id, 210 | message_id, 211 | }); 212 | match self.post(&payload).await? { 213 | Response::Success(response) => Ok(response.ok && response.result), 214 | _ => Err(anyhow!("Unexpected result response for deleteMessage")), 215 | } 216 | } 217 | 218 | pub async fn set_webhook(&self, secret_token: &str) -> Result { 219 | let url = env::var("TELEGRAM_WEBHOOK_URL") 220 | .map_err(|_| anyhow!("Environment variable TELEGRAM_WEBHOOK_URL not found."))?; 221 | let secret_token = secret_token.to_string(); 222 | log::info!("Setting webhook to: {url}"); 223 | let payload = Payload::SetWebhook(SetWebhookPayload { 224 | url: url.clone(), 225 | max_connections: DEFAULT_MAX_CONNECTIONS, 226 | allowed_updates: DEFAULT_ALLOWED_UPDATES 227 | .iter() 228 | .map(|&update| update.to_string()) 229 | .collect(), 230 | secret_token, 231 | }); 232 | match self.post(&payload).await? { 233 | Response::Success(response) => Ok(response.ok && response.result), 234 | _ => Err(anyhow!("Unexpected result response for setWebhook")), 235 | } 236 | } 237 | 238 | pub async fn delete_webhook(&self) -> Result { 239 | let payload = Payload::DeleteWebhook(DeleteWebhookPayload { 240 | drop_pending_updates: false, 241 | }); 242 | log::info!("Deleting webhook"); 243 | match self.post(&payload).await? { 244 | Response::Success(response) => Ok(response.ok && response.result), 245 | _ => Err(anyhow!("Unexpected result response for deleteWebhook")), 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/webhook.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{ 2 | middleware::Logger, 3 | web::{self, Bytes}, 4 | App, HttpRequest, HttpResponse, HttpServer, 5 | }; 6 | use airnope::{embeddings::Embeddings, is_spam, telegram::Client}; 7 | use anyhow::{anyhow, Result}; 8 | use futures::try_join; 9 | use rand::{rng, Rng}; 10 | use serde::{Deserialize, Serialize}; 11 | use std::{env, sync::Arc}; 12 | use tokio::sync::Mutex; 13 | 14 | const DEFAULT_PORT: u16 = 8000; 15 | const DEFAULT_HOST_IP: &str = "0.0.0.0"; 16 | const SECRET_CHARS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"; 17 | const DEFAULT_AIRNOPE_HANDLE: &str = "@AirNope_bot"; 18 | 19 | fn random_webhook_secret() -> String { 20 | let length = rng().random_range(128..=256); 21 | (0..length) 22 | .map(|_| { 23 | let idx = rng().random_range(0..SECRET_CHARS.len()); 24 | SECRET_CHARS.chars().nth(idx).unwrap() 25 | }) 26 | .collect() 27 | } 28 | 29 | #[derive(Clone)] 30 | struct Settings { 31 | handle: String, 32 | secret: String, 33 | } 34 | 35 | impl Settings { 36 | fn new() -> Settings { 37 | let secret = env::var("TELEGRAM_WEBHOOK_SECRET_TOKEN").unwrap_or(random_webhook_secret()); 38 | let handle = env::var("AIRNOPE_HANDLE").unwrap_or(DEFAULT_AIRNOPE_HANDLE.to_string()); 39 | Settings { handle, secret } 40 | } 41 | } 42 | 43 | #[derive(Deserialize, Serialize)] 44 | struct UserOrChat { 45 | id: i64, 46 | } 47 | 48 | #[derive(Deserialize, Serialize)] 49 | struct Chat { 50 | title: Option, 51 | } 52 | 53 | #[derive(Deserialize, Serialize)] 54 | struct SenderUser { 55 | username: Option, 56 | } 57 | 58 | #[derive(Deserialize, Serialize)] 59 | struct ForwardOrigin { 60 | chat: Option, 61 | sender_user: Option, 62 | } 63 | 64 | #[derive(Deserialize, Serialize)] 65 | struct InlineKeyboard { 66 | text: Option, 67 | url: Option, 68 | } 69 | 70 | #[derive(Deserialize, Serialize)] 71 | struct ReplyMarkup { 72 | inline_keyboard: Option>>, 73 | } 74 | 75 | #[derive(Deserialize, Serialize)] 76 | struct Message { 77 | message_id: i64, 78 | chat: UserOrChat, 79 | from: UserOrChat, 80 | text: Option, 81 | caption: Option, 82 | reply_to_message: Option>, 83 | forward_origin: Option, 84 | forward_from_chat: Option, 85 | reply_markup: Option, 86 | } 87 | 88 | impl Message { 89 | fn contents(&self) -> Option { 90 | let text = self 91 | .text 92 | .as_deref() 93 | .into_iter() 94 | .chain(self.caption.as_deref()); 95 | let forward = self 96 | .forward_origin 97 | .as_ref() 98 | .and_then(|f| f.chat.as_ref().and_then(|c| c.title.as_deref())) 99 | .into_iter() 100 | .chain( 101 | self.forward_from_chat 102 | .as_ref() 103 | .and_then(|f| f.title.as_deref()), 104 | ); 105 | let buttons = self 106 | .reply_markup 107 | .as_ref() 108 | .into_iter() 109 | .flat_map(|reply_markup| reply_markup.inline_keyboard.as_ref()) 110 | .flat_map(|keyboards| keyboards.iter()) 111 | .flatten() 112 | .flat_map(|keyboard| { 113 | keyboard 114 | .text 115 | .as_deref() 116 | .into_iter() 117 | .chain(keyboard.url.as_deref()) 118 | }); 119 | let merged = text.chain(forward).chain(buttons).collect::>(); 120 | if merged.is_empty() { 121 | return None; 122 | } 123 | 124 | Some(merged.join("\n\n")) 125 | } 126 | 127 | async fn is_spam(&self, embeddings: Arc>) -> Result { 128 | if let Some(sender) = self 129 | .forward_origin 130 | .as_ref() 131 | .and_then(|fw| fw.sender_user.as_ref()) 132 | .and_then(|u| u.username.as_ref()) 133 | { 134 | if sender == "safeguard" { 135 | return Ok(true); 136 | } 137 | } 138 | if let Some(txt) = &self.contents() { 139 | match is_spam(&embeddings, txt.as_str()).await { 140 | Ok(guess) => { 141 | return Ok(guess.is_spam); 142 | } 143 | Err(e) => { 144 | log::error!("Error processing message: {e}"); 145 | } 146 | } 147 | } 148 | Ok(false) 149 | } 150 | 151 | async fn acknowledge(&self) -> Result<()> { 152 | let client = Client::new()?; 153 | client 154 | .set_message_reaction(self.chat.id, self.message_id) 155 | .await?; 156 | Ok(()) 157 | } 158 | 159 | async fn mark_as_spam(&self) -> Result<()> { 160 | let client = Client::new()?; 161 | if client.is_admin(self.chat.id, self.from.id).await? { 162 | client 163 | .set_message_reaction(self.chat.id, self.message_id) 164 | .await?; 165 | return Ok(()); 166 | } 167 | try_join!( 168 | client.delete_message(self.chat.id, self.message_id), 169 | client.ban_chat_member(self.chat.id, self.from.id) 170 | )?; 171 | Ok(()) 172 | } 173 | } 174 | 175 | #[derive(Deserialize, Serialize)] 176 | struct Update { 177 | message: Option, 178 | edited_message: Option, 179 | channel_post: Option, 180 | edited_channel_post: Option, 181 | business_message: Option, 182 | edited_business_message: Option, 183 | current_bot_handle: Option, 184 | } 185 | 186 | impl Update { 187 | pub async fn message(&self) -> Result<&Message> { 188 | if self.is_tagging_airnope().await { 189 | if let Some(msg) = self.message.as_ref() { 190 | if let Some(replying_to) = msg.reply_to_message.as_ref() { 191 | return Ok(replying_to); 192 | } 193 | } 194 | } 195 | [ 196 | &self.edited_message, 197 | &self.message, 198 | &self.edited_channel_post, 199 | &self.channel_post, 200 | &self.edited_business_message, 201 | &self.business_message, 202 | ] 203 | .iter() 204 | .find_map(|&msg| msg.as_ref()) 205 | .ok_or(anyhow!("Could not find message in update payload")) 206 | } 207 | 208 | async fn is_spam(&self, embeddings: Arc>) -> Result { 209 | self.message().await?.is_spam(embeddings).await 210 | } 211 | 212 | async fn mark_as_spam(&self) -> Result<()> { 213 | self.message().await?.mark_as_spam().await 214 | } 215 | 216 | async fn is_tagging_airnope(&self) -> bool { 217 | let mut result = false; 218 | if let Some(msg) = self.message.as_ref() { 219 | if let Some(handle) = self.current_bot_handle.as_ref() { 220 | result = msg 221 | .text 222 | .as_ref() 223 | .is_some_and(|txt| txt.to_lowercase().trim() == handle.to_lowercase()); 224 | if result { 225 | if let Err(error) = msg.acknowledge().await { 226 | log::error!("Error reacting to message tagging AirNope: {error}") 227 | } 228 | } 229 | } 230 | } 231 | result 232 | } 233 | } 234 | 235 | async fn health() -> HttpResponse { 236 | HttpResponse::Ok().body("OK") 237 | } 238 | 239 | async fn handler( 240 | embeddings: web::Data>>, 241 | settings: web::Data>, 242 | request: HttpRequest, 243 | body: Bytes, 244 | ) -> HttpResponse { 245 | let token = request 246 | .headers() 247 | .get("X-Telegram-Bot-Api-Secret-Token") 248 | .map(|v| v.to_str().unwrap_or("")) 249 | .unwrap_or(""); 250 | if settings.secret.as_str() != token { 251 | return HttpResponse::Unauthorized().finish(); 252 | } 253 | match serde_json::from_slice::(&body) { 254 | Err(e) => { 255 | log::error!( 256 | "Error parsing update: {}\n{}", 257 | e, 258 | String::from_utf8_lossy(&body) 259 | ); 260 | HttpResponse::BadRequest().finish() 261 | } 262 | Ok(mut update) => { 263 | update.current_bot_handle = Some(settings.handle.clone()); 264 | match update.is_spam(embeddings.get_ref().clone()).await { 265 | Ok(false) => HttpResponse::Ok().finish(), 266 | Ok(true) => { 267 | if let Err(e) = update.mark_as_spam().await { 268 | log::error!("Error marking message as spam: {e}"); 269 | return HttpResponse::InternalServerError().finish(); 270 | } 271 | HttpResponse::Ok().finish() 272 | } 273 | Err(e) => { 274 | log::error!("Error checking if message is spam: {e}"); 275 | HttpResponse::InternalServerError().finish() 276 | } 277 | } 278 | } 279 | } 280 | } 281 | 282 | pub async fn run() -> Result<()> { 283 | let port = env::var("PORT") 284 | .unwrap_or(DEFAULT_PORT.to_string()) 285 | .parse::()?; 286 | let embeddings = Arc::new(Mutex::new(Embeddings::new().await?)); 287 | let client = Client::new()?; 288 | let settings = Settings::new(); 289 | client.delete_webhook().await?; 290 | client.set_webhook(settings.secret.as_str()).await?; 291 | HttpServer::new(move || { 292 | App::new() 293 | .wrap(Logger::default()) 294 | .app_data(web::Data::new(embeddings.clone())) 295 | .app_data(web::Data::new(Arc::new(settings.clone()))) 296 | .route("/", web::post().to(handler)) 297 | .route("/health", web::get().to(health)) 298 | }) 299 | .workers(32) 300 | .bind((DEFAULT_HOST_IP, port))? 301 | .run() 302 | .await?; 303 | Ok(()) 304 | } 305 | 306 | pub async fn remove() -> Result<()> { 307 | let client = Client::new()?; 308 | client.delete_webhook().await?; 309 | Ok(()) 310 | } 311 | 312 | #[cfg(test)] 313 | mod tests { 314 | use super::*; 315 | use std::fs; 316 | 317 | #[test] 318 | fn test_deserialize_message() { 319 | let data = fs::read_to_string("test_data/message_administrator.json").unwrap(); 320 | let message: Message = serde_json::from_str(&data).unwrap(); 321 | assert_eq!(message.message_id, 88220); 322 | assert!(message 323 | .forward_origin 324 | .and_then(|fw| fw.sender_user) 325 | .and_then(|u| u.username) 326 | .is_none()); 327 | } 328 | 329 | #[test] 330 | fn test_deserialize_message_with_sender() { 331 | let data = fs::read_to_string("test_data/message_safeguard.json").unwrap(); 332 | let message: Message = serde_json::from_str(&data).unwrap(); 333 | assert_eq!(message.message_id, 204091); 334 | assert_eq!( 335 | message 336 | .forward_origin 337 | .and_then(|fw| fw.sender_user) 338 | .and_then(|u| u.username) 339 | .unwrap(), 340 | "safeguard" 341 | ); 342 | } 343 | 344 | #[tokio::test(flavor = "multi_thread")] 345 | async fn test_message_from_safeguard_is_spam() { 346 | let embeddings = Arc::new(Mutex::new(Embeddings::new().await.unwrap())); 347 | let data = fs::read_to_string("test_data/message_safeguard.json").unwrap(); 348 | let message: Message = serde_json::from_str(&data).unwrap(); 349 | assert!(message.is_spam(embeddings).await.unwrap()); 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/common/re.rs: -------------------------------------------------------------------------------- 1 | use crate::{truncated, Guess}; 2 | use anyhow::Result; 3 | use regex::{Regex, RegexBuilder}; 4 | 5 | const A: &str = "[аaã🅰🅰️🇦🇦о]"; 6 | const B: &str = "[bB🇧]"; 7 | const C: &str = "[cç]"; 8 | const D: &str = "[dԁ🇩]"; 9 | const E: &str = "[eEе3€ℯ🇪]"; 10 | const F: &str = "[fF🇫]"; 11 | const G: &str = "[gG9🇬]"; 12 | const H: &str = "[hH🇭]"; 13 | const I: &str = "[іiíÍI1lℹ️🇮]"; 14 | const K: &str = "[kK🇰]"; 15 | const L: &str = "[lL1|ℓ🇱]"; 16 | const M: &str = "[mM]"; 17 | const N: &str = "[nNℕñÑ🇳]"; 18 | const O: &str = "[оo0🅾️🇴]"; 19 | const P: &str = "[рpρϱ🅿️🇵]"; 20 | const Q: &str = "[qQ9🇶]"; 21 | const R: &str = "[рr🇷]"; 22 | const S: &str = "[sSЅ]"; 23 | const T: &str = "[tTТ7†🇹]"; 24 | const U: &str = "[uUµ🇺]"; 25 | const V: &str = "[vV]"; 26 | const W: &str = "[wW🇼]"; 27 | const Y: &str = "[yY¥🇾]"; 28 | const Z: &str = "[zZ2Ζ🇿]"; 29 | 30 | #[derive(Clone)] 31 | pub struct RegularExpression { 32 | // generic 33 | airdrop: Regex, 34 | bitcoin: Regex, 35 | btc: Regex, 36 | altcoin: Regex, 37 | crypto: Regex, 38 | https: Regex, 39 | nft: Regex, 40 | safeguard: Regex, 41 | somnia: Regex, 42 | 43 | // english 44 | cryptocurrenc: Regex, 45 | wallet: Regex, 46 | token: Regex, 47 | claim: Regex, 48 | swap: Regex, 49 | reward: Regex, 50 | earning: Regex, 51 | opportunity: Regex, 52 | finance: Regex, 53 | network: Regex, 54 | contract: Regex, 55 | fund: Regex, 56 | transaction: Regex, 57 | trading: Regex, 58 | trade: Regex, 59 | platform: Regex, 60 | drop: Regex, 61 | 62 | // spanish 63 | gana: Regex, // win, receiving 64 | inverti: Regex, // invested 65 | fondo: Regex, // fund 66 | cuenta: Regex, // account 67 | clic: Regex, // click 68 | aqui: Regex, // here 69 | 70 | // portuguese 71 | plataforma: Regex, // platform 72 | distribuicao: Regex, // distribution 73 | paga: Regex, // paid 74 | 75 | // german 76 | plattform: Regex, // platform 77 | gewinne: Regex, // profits 78 | eingezahlt: Regex, // deposited 79 | erhalten: Regex, // received 80 | investieren: Regex, // investing 81 | auszahlung: Regex, // payout 82 | belohn: Regex, // reward 83 | 84 | dollar_word: Regex, 85 | cleanup: Regex, 86 | } 87 | 88 | fn to_regex(chars: I) -> Result 89 | where 90 | I: IntoIterator, 91 | I::Item: AsRef, 92 | { 93 | Ok(RegexBuilder::new( 94 | chars 95 | .into_iter() 96 | .map(|s| s.as_ref().to_string()) 97 | .collect::>() 98 | .join(r"\s?") 99 | .as_str(), 100 | ) 101 | .case_insensitive(true) 102 | .build()?) 103 | } 104 | 105 | impl RegularExpression { 106 | pub async fn new() -> Result { 107 | let airdrop = to_regex([A, I, R, D, R, O, P])?; 108 | let bitcoin = to_regex([B, I, T, C, O, I, N])?; 109 | let btc = to_regex([B, T, C])?; 110 | let altcoin = to_regex([A, L, T, C, O, I, N])?; 111 | let crypto = to_regex([C, R, Y, P, T, O])?; 112 | let https = to_regex([H, T, T, P, S])?; 113 | let safeguard = to_regex([S, A, F, E, G, U, A, R, D])?; 114 | let somnia = to_regex([S, O, M, N, I, A])?; 115 | let nft = to_regex([N, F, T])?; 116 | let cryptocurrenc = to_regex([C, R, Y, P, T, O, C, U, R, R, E, N, C])?; 117 | let wallet = to_regex([W, A, L, L, E, T])?; 118 | let token = to_regex([T, O, K, E, N])?; 119 | let claim = to_regex([C, L, A, I, M])?; 120 | let swap = to_regex([S, W, A, P])?; 121 | let reward = to_regex([R, E, W, A, R, D])?; 122 | let earning = to_regex([E, A, R, N, I, N, G])?; 123 | let opportunity = to_regex([O, P, P, O, R, T, U, N, I, T])?; 124 | let finance = to_regex([F, I, N, A, N, C, E])?; 125 | let network = to_regex([N, E, T, W, O, R, K])?; 126 | let contract = to_regex([C, O, N, T, R, A, C, T])?; 127 | let fund = to_regex([F, U, N, D])?; 128 | let transaction = to_regex([T, R, A, N, S, A, C, T, I, O, N])?; 129 | let trading = to_regex([T, R, A, D, I, N, G])?; 130 | let trade = to_regex([T, R, A, D, E])?; 131 | let platform = to_regex([P, L, A, T, F, O, R, M])?; 132 | let drop = to_regex([D, R, O, P])?; 133 | let gana = to_regex([G, A, N, A])?; 134 | let inverti = to_regex([I, N, V, E, R, T, I])?; 135 | let fondo = to_regex([F, O, N, D, O])?; 136 | let cuenta = to_regex([C, U, E, N, T, A])?; 137 | let clic = to_regex([C, L, I, C])?; 138 | let aqui = to_regex([A, Q, U, I])?; 139 | let plataforma = to_regex([P, L, A, T, A, F, O, R, M, A])?; 140 | let distribuicao = to_regex([D, I, S, T, R, I, B, U, I, C, A, O])?; 141 | let paga = to_regex([P, A, G, A])?; 142 | let plattform = to_regex([P, L, A, T, T, F, O, R, M])?; 143 | let gewinne = to_regex([G, E, W, I, N, N, E])?; 144 | let eingezahlt = to_regex([E, I, N, G, E, Z, A, H, L, T])?; 145 | let erhalten = to_regex([E, R, H, A, L, T, E, N])?; 146 | let investieren = to_regex([I, N, V, E, S, T, I, E, R, E, N])?; 147 | let auszahlung = to_regex([A, U, S, Z, A, H, L, U, N, G])?; 148 | let belohn = to_regex([B, E, L, O, H, N])?; 149 | let dollar_word = Regex::new(r"\$\w+")?; 150 | let cleanup = Regex::new(r"\s")?; 151 | Ok(Self { 152 | airdrop, 153 | bitcoin, 154 | btc, 155 | altcoin, 156 | crypto, 157 | https, 158 | nft, 159 | safeguard, 160 | somnia, 161 | cryptocurrenc, 162 | wallet, 163 | token, 164 | claim, 165 | swap, 166 | reward, 167 | earning, 168 | opportunity, 169 | finance, 170 | network, 171 | contract, 172 | fund, 173 | transaction, 174 | trading, 175 | trade, 176 | platform, 177 | drop, 178 | gana, 179 | inverti, 180 | fondo, 181 | cuenta, 182 | clic, 183 | aqui, 184 | plataforma, 185 | distribuicao, 186 | paga, 187 | plattform, 188 | gewinne, 189 | eingezahlt, 190 | erhalten, 191 | investieren, 192 | auszahlung, 193 | belohn, 194 | dollar_word, 195 | cleanup, 196 | }) 197 | } 198 | 199 | pub async fn is_spam(&self, txt: &str) -> Result { 200 | let cleaned = self.cleanup.replace_all(txt, " "); 201 | let result = self.airdrop.is_match(&cleaned) 202 | || self.cryptocurrenc.is_match(&cleaned) 203 | || self.altcoin.is_match(&cleaned) 204 | || self.safeguard.is_match(&cleaned) 205 | || self.somnia.is_match(&cleaned) 206 | || (self.wallet.is_match(&cleaned) && self.token.is_match(&cleaned)) 207 | || (self.wallet.is_match(&cleaned) && self.reward.is_match(&cleaned)) 208 | || (self.wallet.is_match(&cleaned) && self.swap.is_match(&cleaned)) 209 | || (self.wallet.is_match(&cleaned) && self.dollar_word.is_match(&cleaned)) 210 | || (self.wallet.is_match(&cleaned) && self.nft.is_match(&cleaned)) 211 | || (self.network.is_match(&cleaned) && self.nft.is_match(&cleaned)) 212 | || (self.platform.is_match(&cleaned) && self.nft.is_match(&cleaned)) 213 | || (self.platform.is_match(&cleaned) 214 | && self.trade.is_match(&cleaned) 215 | && self.https.is_match(&cleaned)) 216 | || (self.token.is_match(&cleaned) && self.network.is_match(&cleaned)) 217 | || (self.token.is_match(&cleaned) && self.contract.is_match(&cleaned)) 218 | || (self.token.is_match(&cleaned) && self.fund.is_match(&cleaned)) 219 | || (self.claim.is_match(&cleaned) && self.swap.is_match(&cleaned)) 220 | || (self.claim.is_match(&cleaned) && self.token.is_match(&cleaned)) 221 | || (self.crypto.is_match(&cleaned) && self.reward.is_match(&cleaned)) 222 | || (self.crypto.is_match(&cleaned) && self.opportunity.is_match(&cleaned)) 223 | || (self.crypto.is_match(&cleaned) && self.earning.is_match(&cleaned)) 224 | || (self.finance.is_match(&cleaned) && self.reward.is_match(&cleaned)) 225 | || (self.finance.is_match(&cleaned) && self.network.is_match(&cleaned)) 226 | || (self.transaction.is_match(&cleaned) && self.trading.is_match(&cleaned)) 227 | || (self.transaction.is_match(&cleaned) && self.trade.is_match(&cleaned)) 228 | || (self.gana.is_match(&cleaned) 229 | && self.inverti.is_match(&cleaned) 230 | && self.clic.is_match(&cleaned) 231 | && self.aqui.is_match(&cleaned)) 232 | || (self.inverti.is_match(&cleaned) && self.fondo.is_match(&cleaned)) 233 | || (self.inverti.is_match(&cleaned) && self.cuenta.is_match(&cleaned)) 234 | || (self.gana.is_match(&cleaned) && self.bitcoin.is_match(&cleaned)) 235 | || (self.gana.is_match(&cleaned) && self.trading.is_match(&cleaned)) 236 | || (self.bitcoin.is_match(&cleaned) && self.https.is_match(&cleaned)) 237 | || (self.btc.is_match(&cleaned) && self.https.is_match(&cleaned)) 238 | || (self.plataforma.is_match(&cleaned) 239 | && self.distribuicao.is_match(&cleaned) 240 | && self.paga.is_match(&cleaned)) 241 | || (self.plattform.is_match(&cleaned) && self.gewinne.is_match(&cleaned)) 242 | || (self.plattform.is_match(&cleaned) && self.eingezahlt.is_match(&cleaned)) 243 | || (self.plattform.is_match(&cleaned) && self.erhalten.is_match(&cleaned)) 244 | || (self.plattform.is_match(&cleaned) && self.investieren.is_match(&cleaned)) 245 | || (self.auszahlung.is_match(&cleaned) && self.belohn.is_match(&cleaned)) 246 | || (self.drop.is_match(&cleaned) 247 | && self.network.is_match(&cleaned) 248 | && self.claim.is_match(&cleaned)); 249 | if result { 250 | log::info!("Message detected as spam by RegularExpression"); 251 | log::debug!("{}", truncated(txt)); 252 | } 253 | Ok(Guess { 254 | is_spam: result, 255 | score: None, 256 | scores: vec![], 257 | }) 258 | } 259 | } 260 | 261 | #[cfg(test)] 262 | mod tests { 263 | use super::*; 264 | use tokio::{fs, io::AsyncReadExt}; 265 | 266 | #[tokio::test] 267 | async fn test_is_spam() { 268 | let test_cases = vec![ 269 | ("airdrop", true), 270 | ("аirdrop", true), // Cyrillic а 271 | ("аirdrор", true), // Cyrillic а and Cyrillic о 272 | ("аirdrоp", true), // Cyrillic а and Cyrillic о 273 | ("аirdrор", true), // Cyrillic а, Cyrillic о, and Cyrillic р 274 | ("airdrор", true), // Cyrillic о 275 | ("airdrоp", true), // Cyrillic о 276 | ("airdrор", true), // Cyrillic о and Cyrillic р 277 | ("аirdrоp", true), // Cyrillic а, and Cyrillic о 278 | ("аirdrор", true), // Cyrillic а, Cyrillic о, and Cyrillic р 279 | ("аirdrop", true), // Cyrillic а 280 | ("аirdrор", true), // Cyrillic а and Cyrillic р 281 | ("аirdrop", true), // Cyrillic а 282 | ("airԁrop", true), // Cyrillic ԁ 283 | ("aіrԁrop", true), // Cyrillic і and Cyrillic ԁ 284 | ("airԁroр", true), // Cyrillic р 285 | ("аirdrор", true), // Cyrillic а and Cyrillic р 286 | ("aіrdrop", true), // Cyrillic і 287 | ("аіrdrop", true), // Cyrillic а and Cyrillic і 288 | ("аіrdrop", true), // Cyrillic а and Cyrillic і 289 | ("аіrdrop", true), // Cyrillic а and Cyrillic і 290 | ("aіrdroр", true), // Cyrillic і and Cyrillic р 291 | ("аіrdroр", true), // Cyrillic а, Cyrillic і, and Cyrillic р 292 | ("аirdrор", true), // Cyrillic а and Cyrillic р 293 | ("aіrԁrор", true), // Cyrillic і, Cyrillic ԁ, and Cyrillic р 294 | ("aіrԁrop", true), // Cyrillic і and Cyrillic ԁ 295 | ("airdroр", true), // Cyrillic р 296 | ("airԁrop", true), // Greek delta, Δ 297 | ("аirdrор", true), // Greek o, ο 298 | ("аіrԁrop", true), // Greek iota, ι 299 | ("airԁroр", true), // Greek rho, ρ 300 | ("аirdrор", true), // Greek omicron, ο 301 | ("aіrdrop", true), // Greek iota, ι 302 | ("аіrdrop", true), // Greek alpha, α 303 | ("аіrdrop", true), // Greek iota, ι 304 | ("aіrdroр", true), // Greek iota, ι, and rho, ρ 305 | ("аirdrор", true), // Greek omicron, ο, and rho, ρ 306 | ("aіrԁrор", true), // Greek iota, ι, delta, Δ, and rho, ρ 307 | ("aіrԁrop", true), // Greek iota, ι, and delta, Δ 308 | ("airdroр", true), // Greek rho, ρ 309 | ("Сlаim Q СOMMUNITY АIRDROP\n Join the Q movement.", true), // snippet from a real one 310 | ("🅰irdrop", true), // with emoji 311 | ("🅰️ℹ️irdr🅾️🇵", true), // with emojis 312 | ("air drop", true), // with space 313 | ("a i r d r o p", true), // with single spaces 314 | ("a i r d r o p", true), // with different kids of spaces 315 | ("🇦 🇮 🇷 🇩 🇷 🇴 🇵", true), // with special characters and spaces 316 | ("42", false), 317 | ("", false), 318 | ("token", false), 319 | ("wallet", false), 320 | ("wallet tokens", true), 321 | ("tokens wallet", true), 322 | ("wallеt and tokеn", true), // with Cyrillic е 323 | ]; 324 | for (word, expected) in test_cases { 325 | for w in [word, word.to_uppercase().as_str()] { 326 | let model = RegularExpression::new().await.unwrap(); 327 | let got = model.is_spam(w).await.unwrap(); 328 | assert_eq!( 329 | got.is_spam, expected, 330 | "expected: {:?} for {:?}, got: {:?}", 331 | expected, w, got.is_spam 332 | ); 333 | assert_eq!(got.score, None); 334 | } 335 | } 336 | } 337 | 338 | #[tokio::test(flavor = "multi_thread")] 339 | async fn test_is_spam_with_test_data() { 340 | let model = RegularExpression::new().await.unwrap(); 341 | let mut entries = fs::read_dir("test_data").await.unwrap(); 342 | while let Some(entry) = entries.next_entry().await.unwrap() { 343 | let path = entry.path(); 344 | if path.extension().unwrap() != "txt" { 345 | continue; 346 | } 347 | let mut contents = String::new(); 348 | let mut file = fs::File::open(&path).await.unwrap(); 349 | file.read_to_string(&mut contents).await.unwrap(); 350 | let got = model.is_spam(contents.as_str()).await.unwrap(); 351 | assert!(got.is_spam, "{} was not flagged as spam", path.display(),); 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------