Alles miku

Erste Anpassungen für Mikudayobot
This commit is contained in:
Akamaru 2016-07-17 13:22:27 +02:00
parent d3c2e99165
commit b7ed1dbc80
173 changed files with 7350 additions and 5016 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
config.lua
*.db
tg
otouto-dev-1.rockspec
tmp/*
last_commit.txt

314
README.md
View File

@ -1,230 +1,109 @@
# Brawlbot v2
[![Build Status](https://travis-ci.org/Brawl345/Brawlbot-v2.svg?branch=master)](https://travis-ci.org/Brawl345/Brawlbot-v2)
The plugin-wielding, multipurpose Telegram bot.
Der multifunktionale Telegram-Bot.
[Public Bot](http://telegram.me/mokubot) | [Official Channel](http://telegram.me/otouto) | [Development Group](http://telegram.me/BotDevelopment)
[Entwickler auf Telegram](http://telegram.me/Brawl) | [Offizieller Kanal](https://telegram.me/brawlbot_updates)
otouto is a plugin-based, IRC-style bot written for the [Telegram Bot API](http://core.telegram.org/bots/api). Originally written in February of 2015 as a set of Lua scripts to run on [telegram-cli](http://github.com/vysheng/tg), otouto was open-sourced and migrated to the bot API later in June that year.
Brawlbot ist ein auf Plugins basierender Bot, der die [offizielle Telegram Bot API](http://core.telegram.org/bots/api) benutzt. Ursprünglich wurde er im Dezember 2014 auf Basis von Yagops [Telegram Bot](https://github.com/yagop/telegram-bot/) entwickelt, da aber die Entwicklung von tg-cli [zum Stillstand](https://brawlbot.tk/posts/ein-neuanfang) gekommen ist, wurden alle Plugins des bisher proprietären Brawlbots im Juni 2016 auf die Bot-API portiert und open-sourced.
**Brawlbot v2 basiert auf [otouto](https://github.com/topkecleon/otouto) von Topkecleon.**
otouto is free software; you are free to redistribute it and/or modify it under the terms of the GNU Affero General Public License, version 3. See **LICENSE** for details.
Brawlbot v2 ist freie Software; du darfst in modifizieren und weiterverbreiten, allerdings musst du dich an die GNU Affero General Public License v3 halten, siehe **LICENSE** für Details.
**The Manual**
##Anleitung
| For Users | For Coders |
| Für User | Für Entwickler|
|:----------------------------------------------|:------------------------------|
| [Setup](#setup) | [Plugins](#plugins) |
| [Control plugins](#control-plugins) | [Bindings](#bindings) |
| [Group Administration](#group-administration) | [Output style](#output-style) |
| [List of plugins](#list-of-plugins) | [Contributors](#contributors) |
| [Bot steuern](#bot-steuern) | [Bindings](#bindings) |
| | [Datenbank](#datenbank)
* * *
# Für User
## Setup
You _must_ have Lua (5.2+), luasocket, luasec, multipart-post, and dkjson installed. You should also have lpeg, though it is not required. It is recommended you install these with LuaRocks.
### Ubuntu und Debian
Ubuntu und Debian liefern Luarocks nur für Lua 5.1 aus. Um Luarocks für Lua 5.2 zu verwenden, folge bitte der [Anleitung auf StackOverflow](http://stackoverflow.com/a/20359102)
To get started, clone the repository and set the following values in `config.lua`:
### Setup
Du benötigst **Lua 5.2+**, eine aktive **Redis-Instanz** und die folgenden **LuaRocks-Module**:
* luasocket
* luasec
* multipart-post
* dkjson
* lpeg
* redis-lua
* fakeredis
* oauth
* xml
* feedparser
* serpent
- `bot_api_key` as your bot authorization token from the BotFather.
- `admin` as your Telegram ID.
Klone danach diese Repo. kopiere die `config.lua.example` nach `config.lua` und trage folgendes ein:
Optionally:
- `bot_api_key`: API-Token vom BotFather
- `admin`: Deine Telegram-ID
- `lang` as the two-letter code representing your language.
Starte danach den Bot mit `./launch.sh`. Um den Bot anzuhalten, führe erst `/halt` über Telegram aus.
Some plugins are not enabled by default. If you wish to enable them, add them to the `plugins` array.
Beim Start werden einige Werte in die Redis-Datenbank unter `telegram:credentials` und `telegram:enabled_plugins` eingetragen. Mit `/plugins enable` kannst du Plugins aktivieren, es sind nicht alle von Haus aus aktiviert.
When you are ready to start the bot, run `./launch.sh`. To stop the bot, send "/halt" through Telegram. If you terminate the bot manually, you risk data loss. If you do you not want the bot to restart automatically, run it with `lua main.lua`.
Note that certain plugins, such as translate.lua and greetings.lua, will require privacy mode to be disabled. Additionally, some plugins may require or make use of various API keys:
- `bing.lua`: [Bing Search API](http://datamarket.azure.com/dataset/bing/search) key (`bing_api_key`)
- `gImages.lua` & `youtube.lua`: Google [API](http://console.developers.google.com) and [CSE](https://cse.google.com/cse) keys (`google_api_key`, `google_cse_key`)
- `weather.lua`: [OpenWeatherMap](http://openweathermap.org) API key (`owm_api_key`)
- `lastfm.lua`: [last.fm](http://last.fm/api) API key (`lastfm_api_key`)
- `bible.lua`: [Biblia](http://api.biblia.com) API key (`biblia_api_key`)
- `cats.lua`: [The Cat API](http://thecatapi.com) API key (optional) (`thecatapi_key`)
- `apod.lua`: [NASA](http://api.nasa.gov) API key (`nasa_api_key`)
- `translate.lua`: [Yandex](http://tech.yandex.com/keys/get) API key (`yandex_key`)
- `chatter.lua`: [SimSimi](http://developer.simsimi.com/signUp) API key (`simsimi_key`)
Einige Plugins benötigen API-Keys, bitte gehe die einzelnen Plugins durch, bevor du sie aktivierst!
* * *
## Control plugins
Some plugins are designed to be used by the bot's owner. Here are some examples, how they're used, and what they do.
## Bot steuern
Ein Administrator kann den Bot über folgende Plugins steuern:
| Plugin | Command | Function |
| Plugin | Kommando | Funktion |
|:----------------|:-----------|:---------------------------------------------------|
| `control.lua` | /reload | Reloads all plugins and configuration. |
| | /halt | Shuts down the bot after saving the database. |
| | /script | Runs a list a bot commands, separated by newlines. |
| `blacklist.lua` | /blacklist | Blocks people from using the bot. |
| `shell.lua` | /run | Executes shell commands on the host system. |
| `luarun.lua` | /lua | Executes Lua commands in the bot's environment. |
| `banhammer.lua` | Siehe /hilfe banhammer| Blockt User vom Bot und kann Whitelist aktivieren
| `control.lua` | /restart | Startet den Bot neu |
| | /halt | Speichert die Datenbank und stoppt den Bot |
| | /script | Führt mehrere Kommandos aus, getrennt mit Zeilenumbrüchen |
| `luarun.lua` | /lua | Führt LUA-Kommandos aus |
| `plugins.lua` | /plugins enable/disable | Aktiviert/deaktiviert Plugins |
| `shell.lua` | /sh | Führt Shell-Kommandos aus |
* * *
## Group Administration
The administration plugin enables self-hosted, single-realm group administration, supporting both normal groups and supergroups whch are owned by the bot owner. This works by sending TCP commands to an instance of tg running on the owner's account.
To get started, run `./tg-install.sh`. Note that this script is written for Ubuntu/Debian. If you're running Arch (the only acceptable alternative), you'll have to do it yourself. If that is the case, note that otouto uses the "test" branch of tg, and the AUR package `telegram-cli-git` will not be sufficient, as it does not have support for supergroups yet.
Once the installation is finished, enable the `administration` plugin in your config file. **The administration plugin must be loaded before the `about` and `blacklist` plugins.** You may have reason to change the default TCP port (4567); if that is the case, remember to change it in `tg-launch.sh` as well. Run `./tg-launch.sh` in a separate screen/tmux window. You'll have to enter your phone number and go through the login process the first time. The script is set to restart tg after two seconds, so you'll need to Ctrl+C after exiting.
While tg is running, you may start/reload otouto with `administration.lua` enabled, and have access to a wide variety of administrative commands and automata. The administration "database" is stored in `administration.json`. To start using otouto to administrate a group (note that you must be the owner (or an administrator)), send `/gadd` to that group. For a list of commands, use `/ahelp`. Below I'll describe various functions now available to you.
| Command | Function | Privilege | Internal? |
|:------------|:------------------------------------------------|:----------|:----------|
| /groups | Returns a list of administrated groups (except the unlisted). | 1 | N |
| /ahelp | Returns a list of accessible administrative commands. | 1 | Y |
| /ops | Returns a list of the moderators and governor of a group. | 1 | Y |
| /desc | Returns detailed information for a group. | 1 | Y |
| /rules | Returns the rules of a group. | 1 | Y |
| /motd | Returns the message of the day of a group. | 1 | Y |
| /link | Returns the link for a group. | 1 | Y |
| /kick | Removes the target from the group. | 2 | Y |
| /ban | Bans the target from the group. | 2 | Y |
| /unban | Unbans the target from the group. | 2 | Y |
| /setmotd | Sets the message of the day for a group. | 2 | Y |
| /changerule | Changes an individual group rule. | 3 | Y |
| /setrules | Sets the rules for a group. | 3 | Y |
| /setlink | Sets the link for a group. | 3 | Y |
| /alist | Returns a list of administrators. | 3 | Y |
| /flags | Returns a list of flags and their states, or toggles one. | 3 | Y |
| /antiflood | Configures antiflood (flag 5) settings. | 3 | Y |
| /mod | Promotes a user to a moderator. | 3 | Y |
| /demod | Demotes a moderator to a user. | 3 | Y |
| /gov | Promotes a user to the governor. | 4 | Y |
| /degov | Demotes the governor to a user. | 4 | Y |
| /hammer | Blacklists and globally bans a user. | 4 | N |
| /unhammer | Unblacklists and globally bans a user. | 4 | N |
| /admin | Promotes a user to an administrator. | 5 | N |
| /deadmin | Demotes an administrator to a user. | 5 | N |
| /gadd | Adds a group to the administrative system. | 5 | N |
| /grem | Removes a group from the administrative system. | 5 | Y |
| /glist | Returns a list of all administrated groups and their governors. | 5 | N |
| /broadcast | Broadcasts a message to all administrated groups. | 5 | N |
Internal commands can only be run within an administrated group.
### Description of Privileges
| # | Title | Description | Scope |
|:-:|:--------------|:------------------------------------------------------------------|:-------|
| 0 | Banned | Cannot enter the group(s). | Either |
| 1 | User | Default rank. | Local |
| 2 | Moderator | Can kick/ban/unban users. Can set MOTD. | Local |
| 3 | Governor | Can set rules/link, promote/demote moderators, modify flags. | Local |
| 4 | Administrator | Can globally ban/unban users, promote/demote governors. | Global |
| 5 | Owner | Can add/remove groups, broadcast, promote/demote administrators. | Global |
Obviously, each greater rank inherits the privileges of the lower, positive ranks.
### Flags
| # | Name | Description |
|:-:|:------------|:---------------------------------------------------------------------------------|
| 1 | unlisted | Removes a group from the /groups listing. |
| 2 | antisquig | Automatically removes users for posting Arabic script or RTL characters. |
| 3 | antisquig++ | Automatically removes users whose names contain Arabic script or RTL characters. |
| 4 | antibot | Prevents bots from being added by non-moderators. |
| 5 | antiflood | Prevents flooding by rate-limiting messages per user. |
| 6 | antihammer | Allows globally-banned users to enter a group. |
#### antiflood
antiflood (flag 5) provides a system of automatic flood protection by removing users who post too much. It is entirely configurable by a group's governor, an administrator, or the bot owner. For each message to a particular group, a user is awarded a certain number of "points". The number of points is different for each message type. When the user reaches 100 points, he is removed. Points are reset each minute. In this way, if a user posts twenty messages within one minute, he is removed.
**Default antiflood values:**
| Type | Points |
|:-----|:------:|
| text | 5 |
| contact | 5 |
| audio | 5 |
| voice | 5 |
| photo | 10 |
| document | 10 |
| location | 10 |
| video | 10 |
| sticker | 20 |
Additionally, antiflood can be configured to automatically ban a user after he has been automatically kicked from a single group a certain number of times in one day. This is configurable as the antiflood value `autoban` and is set to three by default.
## Gruppenadministration über tg-cli
Dieses Feature wird in Brawlbot nicht unterstützt.
* * *
## List of plugins
## Liste aller Plugins
| Plugin | Command | Function | Aliases |
|:----------------------|:------------------------------|:--------------------------------------------------------|:--------|
| `help.lua` | /help [command] | Returns a list of commands or command-specific help. | /h |
| `about.lua` | /about | Returns the about text as configured in config.lua. |
| `ping.lua` | /ping | The simplest plugin ever! |
| `echo.lua` | /echo text | Repeats a string of text. |
| `bing.lua` | /bing query | Returns Bing web results. | /g |
| `gImages.lua` | /images query | Returns a Google image result. | /i |
| `gMaps.lua` | /location query | Returns location data from Google Maps. | /loc |
| `youtube.lua` | /youtube query | Returns the top video result from YouTube. | /yt |
| `wikipedia.lua` | /wikipedia query | Returns the summary of a Wikipedia article. | /w |
| `lastfm.lua` | /np [username] | Returns the song you are currently listening to. |
| `lastfm.lua` | /fmset [username] | Sets your username for /np. /fmset -- will delete it. |
| `hackernews.lua` | /hackernews | Returns the latest posts from Hacker News. | /hn |
| `imdb.lua` | /imdb query | Returns film information from IMDb. |
| `hearthstone.lua` | /hearthstone query | Returns data for Hearthstone cards matching the query. | /hs |
| `calc.lua` | /calc expression | Returns conversions and solutions to math expressions. |
| `bible.lua` | /bible reference | Returns a Bible verse. | /b |
| `urbandictionary.lua` | /urban query | Returns the top definition from Urban Dictionary. | /ud |
| `time.lua` | /time query | Returns the time, date, and a timezone for a location. |
| `weather.lua` | /weather query | Returns current weather conditions for a given location. |
| `nick.lua` | /nick nickname | Set your nickname. /nick - will delete it. |
| `whoami.lua` | /whoami | Returns user and chat info for you or the replied-to user. | /who |
| `eightball.lua` | /8ball | Returns an answer from a magic 8-ball. |
| `dice.lua` | /roll nDr | Returns RNG dice rolls. Uses D&D notation. |
| `reddit.lua` | /reddit [r/subreddit ¦ query] | Returns the top results from a subreddit, query, or r/all. | /r |
| `xkcd.lua` | /xkcd [query] | Returns an xkcd strip and its alt text. |
| `slap.lua` | /slap target | Gives someone a slap (or worse). |
| `commit.lua` | /commit | Returns a commit message from whatthecommit.com. |
| `fortune.lua` | /fortune | Returns a UNIX fortune. |
| `pun.lua` | /pun | Returns a pun. |
| `pokedex.lua` | /pokedex query | Returns a Pokedex entry. | /dex |
| `currency.lua` | /cash [amount] cur to cur | Converts one currency to another. |
| `cats.lua` | /cat | Returns a cat picture. |
| `reactions.lua` | /reactions | Returns a list of emoticons which can be posted by the bot. |
| `apod.lua` | /apod [date] | Returns the NASA Astronomy Picture of the Day. |
| `dilbert.lua` | /dilbert [date] | Returns a Dilbert strip. |
| `patterns.lua` | /s/from/to/ | Search-and-replace using Lua patterns. |
| `me.lua` | /me | Returns user-specific data stored by the bot. |
| `remind.lua` | /remind <duration> <message> | Reminds a user of something after a duration of minutes. |
Brawlbot erhält laufend neue Plugins und wird kontinuierlich weiterentwickelt! Siehe [hier](https://github.com/Brawl345/Brawlbot-v2/tree/master/otouto/plugins) für eine Liste aller Plugins.
* * *
#Für Entwickler
## Plugins
otouto uses a robust plugin system, similar to yagop's [Telegram-Bot](http://github.com/yagop/telegram-bot).
Brawlbot benutzt ein Plugin-System, ähnlich Yagops [Telegram-Bot](http://github.com/yagop/telegram-bot).
Most plugins are intended for public use, but a few are for other purposes, like those for [use by the bot's owner](#control-plugins). See [here](#list-of-plugins) for a list of plugins.
Ein Plugin kann fünf Komponenten haben, aber nur zwei werden benötigt:
A plugin can have five components, and two of them are required:
| Component | Description | Required? |
| Komponente | Beschreibung | Benötigt? |
|:------------------|:---------------------------------------------|:----------|
| `plugin:action` | Main function. Expects `msg` table as an argument. | Y |
| `plugin.triggers` | Table of triggers for the plugin. Uses Lua patterns. | Y |
| `plugin:init` | Optional function run when the plugin is loaded. | N |
| `plugin:cron` | Optional function to be called every minute. | N |
| `plugin.command` | Basic command and syntax. Listed in the help text. | N |
| `plugin.doc` | Usage for the plugin. Returned by "/help $command". | N |
| `plugin.error` | Plugin-specific error message; false for no message. | N |
| `plugin:action` | Hauptfunktion. Benötigt `msg` als Argument, empfohlen wird auch `matches` als drittes Argument nach `config` | J |
| `plugin.triggers` | Tabelle von Triggern, (Lua-Patterns), auf die der Bot reagiert | J |
| `plugin:init` | Optionale Funkion, die beim Start geladen wird | N |
| `plugin:cron` | Wird jede Minute ausgeführt | N |
| `plugin.command` | Einfaches Kommando mit Syntax. Wird bei `/hilfe` gelistet | N |
| `plugin.doc` | Plugin-Hilfe. Wird mit `/help $kommando` gelistet | N |
| `plugin.error` | Plugin-spezifische Fehlermeldung | N |
The `bot:on_msg_receive` function adds a few variables to the `msg` table for your convenience. These are self-explanatory: `msg.from.id_str`, `msg.to.id_str`, `msg.chat.id_str`, `msg.text_lower`, `msg.from.name`.
Die`bot:on_msg_receive` Funktion fügt einige nützte Variablen zur ` msg` Tabelle hinzu. Diese sind:`msg.from.id_str`, `msg.to.id_str`, `msg.chat.id_str`, `msg.text_lower`, `msg.from.name`.
Return values from `plugin:action` are optional, but they do effect the flow. If it returns a table, that table will become `msg`, and `on_msg_receive` will continue with that. If it returns `true`, it will continue with the current `msg`.
Rückgabewerte für `plugin:action` sind optional, aber wenn eine Tabelle zurückgegeben wird, wird diese die neue `msg`,-Tabelle und `on_msg_receive` wird damit fortfahren.
When an action or cron function fails, the exception is caught and passed to the `handle_exception` utilty and is either printed to the console or send to the chat/channel defined in `log_chat` in config.lua.
Interaktionen mit der Bot-API sind sehr einfach. Siehe [Bindings](#bindings) für Details.
Interactions with the bot API are straightforward. See the [Bindings section](#bindings) for details.
Several functions used in multiple plugins are defined in utilities.lua. Refer to that file for usage and documentation.
Einige Funktionen, die oft benötigt werden, sind in `utilites.lua` verfügbar.
* * *
## Bindings
**Diese Sektion wurde noch nicht lokalisiert.**
Calls to the Telegram bot API are performed with the `bindings.lua` file through the multipart-post library. otouto's bindings file supports all standard API methods and all arguments. Its main function, `bindings.request`, accepts four arguments: `self`, `method`, `parameters`, `file`. (At the very least, `self` should be a table containing `BASE_URL`, which is bot's API endpoint, ending with a slash, eg `https://api.telegram.org/bot123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ987654321/`.)
`method` is the name of the API method. `parameters` (optional) is a table of key/value pairs of the method's parameters to be sent with the method. `file` (super-optional) is a table of a single key/value pair, where the key is the name of the parameter and the value is the filename (if these are included in `parameters` instead, otouto will attempt to send the filename as a file ID).
@ -278,53 +157,34 @@ Upon success, bindings will return the deserialized result from the API. Upon fa
* * *
## Output style
otouto plugins should maintain a consistent visual style in their output. This provides a recognizable and comfortable user experience.
## Datenbank
Brawlbot benutzt eine interne Datenbank, wie Otouto sie benutzt und Redis. Die "Datenbank" ist eine Tabelle, auf die über die Variable `database` zugegriffen werden kann (normalerweise `self.database`) und die als JSON-encodierte Plaintext-Datei jede Stunde gespeichert wird oder wenn der Bot gestoppt wird (über `/halt`).
### Titles
Title lines should be **bold**, including any names and trailing punctuation (such as colons). The exception to this rule is if the title line includes a query, which should be _italic_. It is also acceptable to have a link somewhere inside a title, usually within parentheses. eg:
Das ist die Datenbank-Struktur:
> **Star Wars: Episode IV - A New Hope (1977)**
>
> **Search results for** _star wars_**:**
>
> **Changelog for otouto (**[Github](http://github.com/topkecleon/otouto)**):**
```
{
users = {
["55994550"] = {
id = 55994550,
first_name = "Drew",
username = "topkecleon"
}
},
userdata = {
["55994550"] = {
nickname = "Best coder ever",
lastfm = "topkecleon"
}
},
version = "2.1"
}
```
### Lists
Numerated lists should be done with the number and its following punctuation bolded. Unnumbered lists should use the bullet character ( • ). eg:
`database.users` speichert User-Informationen, wie Usernamen, IDs, etc., wenn der Bot den User sieht. Jeder Tabellen-Key ist die User-ID als String.
> **1.** Life as a quick brown fox.
>
> **2.** The art of jumping over lazy dogs.
`database.userdata` speichert Daten von verschiedenen Plugins, hierzu wird aber für Brawlbot-Plugins Redis verwendet.
and
`database.version` speichert die Bot-Version.
> • Life as a quick brown fox.
>
> • The art of jumping over lazy dogs.
### Links
Always name your links. Even then, use them with discretion. Excessive links make a post look messy. Links are reasonable when a user may want to learn more about something, but should be avoided when all desirable information is provided. One appropriate use of linking is to provide a preview of an image, as xkcd.lua and apod.lua do.
### Other Stuff
User IDs should appear within brackets, monospaced (`[123456789]`). Descriptions and information should be in plain text, but "flavor" text should be italic. The standard size for arbitrary lists (such as search results) is eight within a private conversation and four elsewhere. This is a trivial pair of numbers (leftover from the deprecated Google search API), but consistency is noticeable and desirable.
* * *
## Contributors
Everybody is free to contribute to otouto. If you are interested, you are invited to [fork the repo](http://github.com/topkecleon/otouto/fork) and start making pull requests. If you have an idea and you are not sure how to implement it, open an issue or bring it up in the [Bot Development group](http://telegram.me/BotDevelopment).
The creator and maintainer of otouto is [topkecleon](http://github.com/topkecleon). He can be contacted via [Telegram](http://telegram.me/topkecleon), [Twitter](http://twitter.com/topkecleon), or [email](mailto:drew@otou.to).
[List of contributors.](https://github.com/topkecleon/otouto/graphs/contributors)
There are a a few ways to contribute if you are not a programmer. For one, your feedback is always appreciated. Drop me a line on Telegram or on Twitter. Secondly, we are always looking for new ideas for plugins. Most new plugins start with community input. Feel free to suggest them on Github or in the Bot Dev group. You can also donate Bitcoin to the following address:
`1BxegZJ73hPu218UrtiY8druC7LwLr82gS`
Contributions are appreciated in all forms. Monetary contributions will go toward server costs. Donators will be eternally honored (at their discretion) on this page.
| Donators (in chronological order) |
|:----------------------------------------------|
| [n8 c00](http://telegram.me/n8_c00) |
| [Alex](http://telegram.me/sandu) |
| [Brayden](http://telegram.me/bb010g) |
* * *

View File

@ -21,6 +21,10 @@ Sende /hilfe, um zu starten
-- The symbol that starts a command. Usually noted as '/' in documentation.
cmd_pat = '/',
-- false = only whitelisted users can use inline querys
-- NOTE that it doesn't matter, if the chat is whitelisted! The USER must be whitelisted!
enable_inline_for_everyone = true,
errors = { -- Generic error messages used in various plugins.
generic = 'An unexpected error occurred.',
connection = 'Verbindungsfehler.',

View File

@ -1,154 +0,0 @@
--[[
drua-tg
A fork of JuanPotato's lua-tg (https://github.com/juanpotato/lua-tg),
modified to work more naturally from an API bot.
Usage:
drua = require('drua-tg')
drua.IP = 'localhost'
drua.PORT = 4567
drua.message(chat_id, text)
]]--
local SOCKET = require('socket')
local comtab = {
add = { 'chat_add_user %s %s', 'channel_invite %s %s' },
kick = { 'chat_del_user %s %s', 'channel_kick %s %s' },
rename = { 'rename_chat %s "%s"', 'rename_channel %s "%s"' },
link = { 'export_chat_link %s', 'export_channel_link %s' },
photo_set = { 'chat_set_photo %s %s', 'channel_set_photo %s %s' },
photo_get = { [0] = 'load_user_photo %s', 'load_chat_photo %s', 'load_channel_photo %s' },
info = { [0] = 'user_info %s', 'chat_info %s', 'channel_info %s' }
}
local format_target = function(target)
target = tonumber(target)
if target < -1000000000000 then
target = 'channel#' .. math.abs(target) - 1000000000000
return target, 2
elseif target < 0 then
target = 'chat#' .. math.abs(target)
return target, 1
else
target = 'user#' .. target
return target, 0
end
end
local escape = function(text)
text = text:gsub('\\', '\\\\')
text = text:gsub('\n', '\\n')
text = text:gsub('\t', '\\t')
text = text:gsub('"', '\\"')
return text
end
local drua = {
IP = 'localhost',
PORT = 4567
}
drua.send = function(command, do_receive)
local s = SOCKET.connect(drua.IP, drua.PORT)
assert(s, '\nUnable to connect to tg session.')
s:send(command..'\n')
local output
if do_receive then
output = string.match(s:receive('*l'), 'ANSWER (%d+)')
output = s:receive(tonumber(output)):gsub('\n$', '')
end
s:close()
return output
end
drua.message = function(target, text)
target = format_target(target)
text = escape(text)
local command = 'msg %s "%s"'
command = command:format(target, text)
return drua.send(command)
end
drua.send_photo = function(target, photo)
target = format_target(target)
local command = 'send_photo %s %s'
command = command:format(target, photo)
return drua.send(command)
end
drua.add_user = function(chat, target)
local a
chat, a = format_target(chat)
target = format_target(target)
local command = comtab.add[a]:format(chat, target)
return drua.send(command)
end
drua.kick_user = function(chat, target)
-- Get the group info so tg will recognize the target.
drua.get_info(chat)
local a
chat, a = format_target(chat)
target = format_target(target)
local command = comtab.kick[a]:format(chat, target)
return drua.send(command)
end
drua.rename_chat = function(chat, name)
local a
chat, a = format_target(chat)
local command = comtab.rename[a]:format(chat, name)
return drua.send(command)
end
drua.export_link = function(chat)
local a
chat, a = format_target(chat)
local command = comtab.link[a]:format(chat)
return drua.send(command, true)
end
drua.get_photo = function(chat)
local a
chat, a = format_target(chat)
local command = comtab.photo_get[a]:format(chat)
local output = drua.send(command, true)
if output:match('FAIL') then
return false
else
return output:match('Saved to (.+)')
end
end
drua.set_photo = function(chat, photo)
local a
chat, a = format_target(chat)
local command = comtab.photo_set[a]:format(chat, photo)
return drua.send(command)
end
drua.get_info = function(target)
local a
target, a = format_target(target)
local command = comtab.info[a]:format(target)
return drua.send(command, true)
end
drua.channel_set_admin = function(chat, user, rank)
chat = format_target(chat)
user = format_target(user)
local command = 'channel_set_admin %s %s %s'
command = command:format(chat, user, rank)
return drua.send(command)
end
drua.channel_set_about = function(chat, text)
chat = format_target(chat)
text = escape(text)
local command = 'channel_set_about %s "%s"'
command = command:format(chat, text)
return drua.send(command)
end
return drua

View File

@ -2,6 +2,6 @@
while true; do
lua main.lua
echo 'otouto has stopped. ^C to exit.'
echo 'Miku wurde gestoppt. ^C zum beenden.'
sleep 5s
done

View File

@ -1,4 +1,4 @@
local bot = require('otouto.bot')
local bot = require('miku.bot')
local instance = {}
local config = require('config')

View File

@ -27,14 +27,20 @@ function bindings:request(method, parameters, file)
parameters[k] = tostring(v)
end
if file and next(file) ~= nil then
local file_type, file_name = next(file)
local file_file = io.open(file_name, 'r')
local file_data = {
filename = file_name,
local file_type, file_name = next(file)
if not file_name then return false end
if string.match(file_name, '/home/pi/Mikubot-V2/tmp/') then
local file_file = io.open(file_name, 'r')
local file_data = {
filename = file_name,
data = file_file:read('*a')
}
file_file:close()
parameters[file_type] = file_data
}
file_file:close()
parameters[file_type] = file_data
else
local file_type, file_name = next(file)
parameters[file_type] = file_name
end
end
if next(parameters) == nil then
parameters = {''}

378
miku/bot.lua Normal file
View File

@ -0,0 +1,378 @@
local bot = {}
-- Requires are moved to init to allow for reloads.
local bindings -- Load Telegram bindings.
local utilities -- Load miscellaneous and cross-plugin functions.
local redis = (loadfile "./miku/redis.lua")()
bot.version = '2.1'
function bot:init(config) -- The function run when the bot is started or reloaded.
bindings = require('miku.bindings')
utilities = require('miku.utilities')
redis = (loadfile "./miku/redis.lua")()
cred_data = load_cred()
assert(
config.bot_api_key and config.bot_api_key ~= '',
'You did not set your bot token in the config!'
)
self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/'
-- Fetch bot information. Try until it succeeds.
repeat
print('Fetching bot information...')
self.info = bindings.getMe(self)
until self.info
self.info = self.info.result
-- Load the "database"! ;)
if not self.database then
self.database = utilities.load_data(self.info.username..'.db')
end
-- MIGRATION CODE 2.0 -> 2.1
if self.database.users and self.database.version ~= '2.1' then
self.database.userdata = {}
for id, user in pairs(self.database.users) do
self.database.userdata[id] = {}
self.database.userdata[id].nickname = user.nickname
self.database.userdata[id].lastfm = user.lastfm
user.nickname = nil
user.lastfm = nil
user.id_str = nil
user.name = nil
end
end
-- END MIGRATION CODE
-- Table to cache user info (usernames, IDs, etc).
self.database.users = self.database.users or {}
-- Table to store userdata (nicknames, lastfm usernames, etc).
self.database.userdata = self.database.userdata or {}
-- Save the bot's version in the database to make migration simpler.
self.database.version = bot.version
-- Add updated bot info to the user info cache.
self.database.users = self.database.users or {} -- Table to cache userdata.
self.database.users[tostring(self.info.id)] = self.info
self.plugins = {} -- Load plugins.
enabled_plugins = load_plugins()
for k,v in pairs(enabled_plugins) do
local p = require('miku.plugins.'..v)
-- print('loading plugin',v)
table.insert(self.plugins, p)
self.plugins[k].name = v
if p.init then p.init(self, config) end
end
print('Bot wurde erfolgreich gestartet!\n@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')')
self.last_update = self.last_update or 0 -- Set loop variables: Update offset,
self.last_cron = self.last_cron or os.date('%M') -- the time of the last cron job,
self.last_database_save = self.last_database_save or os.date('%H') -- the time of the last database save,
self.is_started = true -- and whether or not the bot should be running.
end
function bot:on_msg_receive(msg, config) -- The fn run whenever a message is received.
-- remove comment to enable debugging
-- vardump(msg)
-- Cache user info for those involved.
if msg.date < os.time() - 5 then return end -- Do not process old messages.
-- Cache user info for those involved.
self.database.users[tostring(msg.from.id)] = msg.from
if msg.reply_to_message then
self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from
elseif msg.forward_from then
self.database.users[tostring(msg.forward_from.id)] = msg.forward_from
elseif msg.new_chat_member then
self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member
elseif msg.left_chat_member then
self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member
end
msg = utilities.enrich_message(msg)
-- Support deep linking.
if msg.text:match('^'..config.cmd_pat..'start .+') then
msg.text = config.cmd_pat .. utilities.input(msg.text)
msg.text_lower = msg.text:lower()
end
-- gsub out user name if multiple bots are in the same group
if msg.text:match(config.cmd_pat..'([A-Za-z0-9-_-]+)@'..self.info.username) then
msg.text = string.gsub(msg.text, config.cmd_pat..'([A-Za-z0-9-_-]+)@'..self.info.username, "/%1")
msg.text_lower = msg.text:lower()
end
msg = pre_process_msg(self, msg, config)
for _, plugin in ipairs(self.plugins) do
match_plugins(self, msg, config, plugin)
end
end
function bot:on_callback_receive(callback, msg, config) -- whenever a new callback is received
-- remove comments to enable debugging
-- vardump(msg)
-- vardump(callback)
if msg.date < os.time() - 1800 then -- Do not process old messages.
utilities.answer_callback_query(self, callback, 'Nachricht älter als eine halbe Stunde, bitte sende den Befehl selbst noch einmal.', true)
return
end
if not callback.data:find(':') or not callback.data:find('@'..self.info.username..' ') then
return
end
callback.data = string.gsub(callback.data, '@'..self.info.username..' ', "")
local called_plugin = callback.data:match('(.*):.*')
local param = callback.data:sub(callback.data:find(':')+1)
print('Callback Query "'..param..'" für Plugin "'..called_plugin..'" ausgelöst von '..callback.from.first_name..' ('..callback.from.id..')')
msg = utilities.enrich_message(msg)
for _, plugin in ipairs(self.plugins) do
if plugin.name == called_plugin then
if is_plugin_disabled_on_chat(plugin.name, msg) then return end
plugin:callback(callback, msg, self, config, param)
end
end
end
-- NOTE: To enable InlineQuerys, send /setinline to @BotFather
function bot:process_inline_query(inline_query, config) -- When an inline query is received
-- remove comment to enable debugging
-- vardump(inline_query)
if not config.enable_inline_for_everyone then
local is_whitelisted = redis:get('whitelist:user#id'..inline_query.from.id)
if not is_whitelisted then return end
end
if inline_query.query == '' then return end
if inline_query.query:match('"') then
inline_query.query = inline_query.query:gsub('"', '\\"')
end
for _, plugin in ipairs(self.plugins) do
match_inline_plugins(self, inline_query, config, plugin)
end
end
function bot:run(config)
bot.init(self, config) -- Actually start the script.
while self.is_started do -- Start a loop while the bot should be running.
local res = bindings.getUpdates(self, { timeout=20, offset = self.last_update+1 } )
if res then
for _,v in ipairs(res.result) do -- Go through every new message.
self.last_update = v.update_id
if v.inline_query then
bot.process_inline_query(self, v.inline_query, config)
elseif v.callback_query then
bot.on_callback_receive(self, v.callback_query, v.callback_query.message, config)
elseif v.message then
bot.on_msg_receive(self, v.message, config)
end
end
else
print('Connection error while fetching updates.')
end
if self.last_cron ~= os.date('%M') then -- Run cron jobs every minute.
self.last_cron = os.date('%M')
utilities.save_data(self.info.username..'.db', self.database) -- Save the database.
for i,v in ipairs(self.plugins) do
if v.cron then -- Call each plugin's cron function, if it has one.
local result, err = pcall(function() v.cron(self, config) end)
if not result then
utilities.handle_exception(self, err, 'CRON: ' .. i, config)
end
end
end
end
if self.last_database_save ~= os.date('%H') then
utilities.save_data(self.info.username..'.db', self.database) -- Save the database.
self.last_database_save = os.date('%H')
end
end
-- Save the database before exiting.
utilities.save_data(self.info.username..'.db', self.database)
print('Halted.')
end
-- Apply plugin.pre_process function
function pre_process_msg(self, msg, config)
for _,plugin in ipairs(self.plugins) do
if plugin.pre_process and msg then
-- print('Preprocess '..plugin.name) -- remove comment to restore old behaviour
new_msg = plugin:pre_process(msg, self, config)
end
end
return new_msg
end
function match_inline_plugins(self, inline_query, config, plugin)
for _, trigger in pairs(plugin.inline_triggers or {}) do
if string.match(string.lower(inline_query.query), trigger) then
local success, result = pcall(function()
for k, pattern in pairs(plugin.inline_triggers) do
matches = match_pattern(pattern, inline_query.query)
if matches then
break;
end
end
print('Inline: '..plugin.name..' triggered')
return plugin.inline_callback(self, inline_query, config, matches)
end)
if not success then
print(result)
end
end
end
end
function match_plugins(self, msg, config, plugin)
for _, trigger in pairs(plugin.triggers or {}) do
if string.match(msg.text_lower, trigger) then
-- Check if Plugin is disabled
if is_plugin_disabled_on_chat(plugin.name, msg) then return end
local success, result = pcall(function()
-- trying to port matches to miku
for k, pattern in pairs(plugin.triggers) do
matches = match_pattern(pattern, msg.text)
if matches then
break;
end
end
print(plugin.name..' triggered')
return plugin.action(self, msg, config, matches)
end)
if not success then
-- If the plugin has an error message, send it. If it does
-- not, use the generic one specified in config. If it's set
-- to false, do nothing.
if plugin.error then
utilities.send_reply(self, msg, plugin.error)
elseif plugin.error == nil then
utilities.send_reply(self, msg, config.errors.generic, true)
end
utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config)
return
end
-- If the action returns a table, make that table the new msg.
if type(result) == 'table' then
msg = result
-- If the action returns true, continue.
elseif result ~= true then
return
end
end
end
end
function is_plugin_disabled_on_chat(plugin_name, msg)
local hash = get_redis_hash(msg, 'disabled_plugins')
local disabled = redis:hget(hash, plugin_name)
-- Plugin is disabled
if disabled == 'true' then
print('Plugin '..plugin_name..' ist in diesem Chat deaktiviert')
return true
else
return false
end
end
function load_plugins()
enabled_plugins = redis:smembers('telegram:enabled_plugins')
if not enabled_plugins[1] then
create_plugin_set()
end
return enabled_plugins
end
-- create plugin set if it doesn't exist
function create_plugin_set()
enabled_plugins = {
'control',
'about',
'id',
'echo',
'banhammer',
'channels',
'plugins',
'help',
'greetings'
}
print ('Aktiviere Plugins und speicher in telegram:enabled_plugins')
for _,plugin in pairs(enabled_plugins) do
redis:sadd("telegram:enabled_plugins", plugin)
end
end
function load_cred()
if redis:exists("telegram:credentials") == false then
-- If credentials hash doesnt exists
print ("Neuer Credentials-Hash in telegram:credentials erstellt.")
create_cred()
end
return redis:hgetall("telegram:credentials")
end
-- create credentials hash with redis
function create_cred()
cred = {
bitly_access_token = "",
cloudinary_apikey = "",
cloudinary_api_secret = "",
cloudinary_public_id = "",
derpibooru_apikey = "",
fb_access_token = "",
flickr_apikey = "",
forecastio_apikey = "",
ftp_site = "",
ftp_username = "",
ftp_password = "",
gender_apikey = "",
golem_apikey = "",
google_apikey = "",
google_cse_id = "",
gitlab_private_token = "",
gitlab_project_id = "",
instagram_access_token = "",
lyricsnmusic_apikey = "",
mal_username = "",
mal_pw = "",
neutrino_userid = "",
neutrino_apikey = "",
owm_apikey = "",
page2images_restkey = "",
plex_token = "",
sankaku_username = "",
sankaku_pw = "",
soundcloud_client_id = "",
tumblr_api_key = "",
tw_consumer_key = "",
tw_consumer_secret = "",
tw_access_token = "",
tw_access_token_secret = "",
x_mashape_key = "",
yandex_translate_apikey = "",
yandex_rich_content_apikey = "",
yourls_site_url = "",
yourls_signature_token = ""
}
redis:hmset("telegram:credentials", cred)
print ('Credentials gespeichert in telegram:credentials')
end
return bot

View File

@ -3,8 +3,8 @@ local ninegag = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
ninegag.command = '9gag'
@ -22,19 +22,20 @@ function ninegag:get_9GAG()
-- random max json table size
local i = math.random(#gag) local link_image = gag[i].src
local title = gag[i].title
local post_url = gag[i].url
return link_image, title, post_url
end
function ninegag:action(msg, config)
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local url, title = ninegag:get_9GAG()
local url, title, post_url = ninegag:get_9GAG()
if not url then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local file = download_to_file(url)
utilities.send_photo(self, msg.chat.id, file, title)
utilities.send_photo(self, msg.chat.id, file, title, msg.message_id, '{"inline_keyboard":[[{"text":"Post aufrufen","url":"'..post_url..'"}]]}')
end
return ninegag

38
miku/plugins/about.lua Normal file
View File

@ -0,0 +1,38 @@
local about = {}
local bot = require('miku.bot')
local utilities = require('miku.utilities')
about.command = 'about'
about.doc = '`Sendet Informationen über den Bot.`'
about.triggers = {
'/about',
'/start'
}
function about:action(msg, config)
-- Filthy hack, but here is where we'll stop forwarded messages from hitting
-- other plugins.
-- disabled to restore old behaviour
-- if msg.forward_from then return end
local output = config.about_text .. '\nBrawlbot v'..bot.version..', basierend auf miku von topkecleon.'
if
(msg.new_chat_member and msg.new_chat_member.id == self.info.id)
or msg.text_lower:match('^'..config.cmd_pat..'about$')
or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower()..'$')
or msg.text_lower:match('^'..config.cmd_pat..'start$')
or msg.text_lower:match('^'..config.cmd_pat..'start@'..self.info.username:lower()..'$')
then
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return
end
return true
end
return about

View File

@ -1,8 +1,8 @@
local adfly = {}
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
local HTTPS = require('ssl.https')
local redis = (loadfile "./otouto/redis.lua")()
local redis = (loadfile "./miku/redis.lua")()
function adfly:init(config)
adfly.triggers = {

123
miku/plugins/afk.lua Normal file
View File

@ -0,0 +1,123 @@
-- original plugin by Akamaru [https://ponywave.de]
-- I added Redis and automatic online switching back in 2015
local afk = {}
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
function afk:init(config)
afk.triggers = {
"^/([A|a][F|f][K|k])$",
"^/([A|a][F|f][K|k]) (.*)$"
}
afk.doc = [[*
]]..config.cmd_pat..[[afk* _[Text]_: Setzt Status auf AFK mit optionalem Text]]
end
afk.command = 'afk [Text]'
function afk:is_offline(hash)
local afk = redis:hget(hash, 'afk')
if afk == "true" then
return true
else
return false
end
end
function afk:get_afk_text(hash)
local afk_text = redis:hget(hash, 'afk_text')
if afk_text ~= nil and afk_text ~= "" and afk_text ~= "false" then
return afk_text
else
return false
end
end
function afk:switch_afk(user_name, user_id, chat_id, timestamp, text)
local hash = 'afk:'..chat_id..':'..user_id
if afk:is_offline(hash) then
local afk_text = afk:get_afk_text(hash)
if afk_text then
return 'Du bist bereits AFK ('..afk_text..')!'
else
return 'Du bist bereits AFK!'
end
end
print('Setting redis hash afk in '..hash..' to true')
redis:hset(hash, 'afk', true)
print('Setting redis hash timestamp in '..hash..' to '..timestamp)
redis:hset(hash, 'time', timestamp)
if text then
print('Setting redis hash afk_text in '..hash..' to '..text)
redis:hset(hash, 'afk_text', text)
return user_name..' ist AFK ('..text..')'
else
return user_name..' ist AFK'
end
end
function afk:pre_process(msg, self)
if msg.chat.type == "private" then
-- Ignore
return
end
local user_name = get_name(msg)
local user_id = msg.from.id
local chat_id = msg.chat.id
local hash = 'afk:'..chat_id..':'..user_id
if afk:is_offline(hash) then
local afk_text = afk:get_afk_text(hash)
-- calculate afk time
local timestamp = redis:hget(hash, 'time')
local current_timestamp = msg.date
local afk_time = current_timestamp - timestamp
local seconds = afk_time % 60
local minutes = math.floor(afk_time / 60)
local minutes = minutes % 60
local hours = math.floor(afk_time / 3600)
if minutes == 00 and hours == 00 then
duration = seconds..' Sekunden'
elseif hours == 00 and minutes ~= 00 then
duration = string.format("%02d:%02d", minutes, seconds)..' Minuten'
elseif hours ~= 00 then
duration = string.format("%02d:%02d:%02d", hours, minutes, seconds)..' Stunden'
end
redis:hset(hash, 'afk', false)
if afk_text then
redis:hset(hash, 'afk_text', false)
local afk_text = afk_text:gsub("%*","")
local afk_text = afk_text:gsub("_","")
utilities.send_message(self, msg.chat.id, user_name..' ist wieder da (war: *'..afk_text..'* für '..duration..')!', true, nil, true)
else
utilities.send_message(self, msg.chat.id, user_name..' ist wieder da (war '..duration..' weg)!')
end
end
return msg
end
function afk:action(msg)
if msg.chat.type == "private" then
utilities.send_reply(self, msg, "Mir ist's egal, ob du AFK bist ._.")
return
end
local user_id = msg.from.id
local chat_id = msg.chat.id
local user_name = get_name(msg)
local timestamp = msg.date
utilities.send_reply(self, msg, afk:switch_afk(user_name, user_id, chat_id, timestamp, matches[2]))
end
return afk

View File

@ -2,12 +2,12 @@ local app_store = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
app_store.triggers = {
"itunes.apple.com/(.*)/app/(.*)/id(%d+)",
"^!itunes (%d+)$",
"^/itunes (%d+)$",
"itunes.apple.com/app/id(%d+)"
}

75
miku/plugins/bImages.lua Normal file
View File

@ -0,0 +1,75 @@
local bImages = {}
local HTTPS = require('ssl.https')
HTTPS.timeout = 10
local URL = require('socket.url')
local JSON = require('dkjson')
local redis = (loadfile "./miku/redis.lua")()
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function bImages:init(config)
if not cred_data.bing_search_key then
print('Missing config value: bing_search_key.')
print('bImages.lua will not be enabled.')
return
end
bImages.triggers = {"^/nil$"}
bImages.inline_triggers = {
"^b (.*)"
}
end
local apikey = cred_data.bing_search_key
local BASE_URL = 'https://api.cognitive.microsoft.com/bing/v5.0'
function bImages:getImages(query)
local url = BASE_URL..'/images/search?q='..URL.escape(query)..'&count=50&mkt=de-de'
local response_body = {}
local request_constructor = {
url = url,
method = "GET",
sink = ltn12.sink.table(response_body),
redirect = false,
headers = {
["Ocp-Apim-Subscription-Key"] = apikey
}
}
local ok, response_code, response_headers = HTTPS.request(request_constructor)
if not ok then return end
local images = JSON.decode(table.concat(response_body)).value
if not images[1] then return end
local results = '['
for n in pairs(images) do
if images[n].encodingFormat == 'jpeg' then -- Inline-Querys MUST use JPEG photos!
local photo_url = images[n].contentUrl
local thumb_url = images[n].thumbnailUrl
results = results..'{"type":"photo","id":"'..math.random(100000000000000000)..'","photo_url":"'..photo_url..'","thumb_url":"'..thumb_url..'","photo_width":'..images[n].width..',"photo_height":'..images[n].height..',"reply_markup":{"inline_keyboard":[[{"text":"Bing aufrufen","url":"'..images[n].webSearchUrl..'"},{"text":"Bild aufrufen","url":"'..photo_url..'"}]]}},'
end
end
local results = results:sub(0, -2)
local results = results..']'
cache_data('bImages', string.lower(query), results, 1209600, 'key')
return results
end
function bImages:inline_callback(inline_query, config, matches)
local query = matches[1]
local results = redis:get('telegram:cache:bImages:'..string.lower(query))
if not results then
results = bImages:getImages(query)
end
if not results then return end
utilities.answer_inline_query(self, inline_query, results, 3600)
end
function bImages:action()
end
return bImages

253
miku/plugins/banhammer.lua Normal file
View File

@ -0,0 +1,253 @@
local banhammer = {}
local bindings = require('miku.bindings')
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
banhammer.command = 'banhammer <nur für Superuser>'
function banhammer:init(config)
banhammer.triggers = {
"^/(whitelist) (enable)$",
"^/(whitelist) (disable)$",
"^/(whitelist) (user) (%d+)$",
"^/(whitelist) (chat)$",
"^/(whitelist) (delete) (user) (%d+)$",
"^/(whitelist) (delete) (chat)$",
"^/(ban) (user) (%d+)$",
"^/(ban) (delete) (%d+)$",
"^/(kick) (%d+)$"
}
banhammer.doc = [[*
]]..config.cmd_pat..[[whitelist* _<enable>_/_<disable>_: Aktiviert/deaktiviert Whitelist
*]]..config.cmd_pat..[[whitelist* user _<user#id>_: Whiteliste User
*]]..config.cmd_pat..[[whitelist* chat: Whiteliste ganze Gruppe
*]]..config.cmd_pat..[[whitelist* delete user _<user#id>_: Lösche User von der Whitelist
*]]..config.cmd_pat..[[whitelist* delete chat: Lösche ganze Gruppe von der Whitelist
*]]..config.cmd_pat..[[ban* user _<user#id>_: Kicke User vom Chat und kicke ihn, wenn er erneut beitritt
*]]..config.cmd_pat..[[ban* delete _<user#id>_: Entbanne User
*]]..config.cmd_pat..[[kick* _<user#id>_: Kicke User aus dem Chat]]
end
function banhammer:kick_user(user_id, chat_id, self, onlykick)
if user_id == tostring(our_id) then
return "Ich werde mich nicht selbst kicken!"
else
local request = bindings.request(self, 'kickChatMember', {
chat_id = chat_id,
user_id = user_id
} )
if onlykick then return end
if not request then return 'User gebannt, aber kicken war nicht erfolgreich. Bin ich Administrator oder ist der User hier überhaupt?' end
return 'User '..user_id..' gebannt!'
end
end
function banhammer:ban_user(user_id, chat_id, self)
if user_id == tostring(our_id) then
return "Ich werde mich nicht selbst kicken!"
else
-- Save to redis
local hash = 'banned:'..chat_id..':'..user_id
redis:set(hash, true)
-- Kick from chat
return banhammer:kick_user(user_id, chat_id, self)
end
end
function banhammer:unban_user(user_id, chat_id, self, chat_type)
local hash = 'banned:'..chat_id..':'..user_id
redis:del(hash)
if chat_type == 'supergroup' then -- how can bots be admins anyway?
local request = bindings.request(self, 'unbanChatMember', {
chat_id = chat_id,
user_id = user_id
} )
end
return 'User '..user_id..' wurde entbannt.'
end
function banhammer:is_banned(user_id, chat_id)
local hash = 'banned:'..chat_id..':'..user_id
local banned = redis:get(hash)
return banned or false
end
function banhammer:is_user_whitelisted(id)
local hash = 'whitelist:user#id'..id
local white = redis:get(hash) or false
return white
end
function banhammer:is_chat_whitelisted(id)
local hash = 'whitelist:chat#id'..id
local white = redis:get(hash) or false
return white
end
function banhammer:pre_process(msg, self, config)
-- SERVICE MESSAGE
if msg.new_chat_member then
local user_id = msg.new_chat_member.id
print('Checking invited user '..user_id)
local banned = banhammer:is_banned(user_id, msg.chat.id)
if banned then
print('User is banned!')
banhammer:kick_user(user_id, msg.chat.id, self, true)
end
-- No further checks
return msg
end
-- BANNED USER TALKING
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
local user_id = msg.from.id
local chat_id = msg.chat.id
local banned = banhammer:is_banned(user_id, chat_id)
if banned then
print('Banned user talking!')
banhammer:ban_user(user_id, chat_id, self)
msg.text = ''
end
end
-- WHITELIST
local hash = 'whitelist:enabled'
local whitelist = redis:get(hash)
local issudo = is_sudo(msg, config)
-- Allow all sudo users even if whitelist is allowed
if whitelist and not issudo then
print('Whitelist enabled and not sudo')
-- Check if user or chat is whitelisted
local allowed = banhammer:is_user_whitelisted(msg.from.id)
local has_been_warned = redis:hget('user:'..msg.from.id, 'has_been_warned')
if not allowed then
print('User '..msg.from.id..' not whitelisted')
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
allowed = banhammer:is_chat_whitelisted(msg.chat.id)
if not allowed then
print ('Chat '..msg.chat.id..' not whitelisted')
else
print ('Chat '..msg.chat.id..' whitelisted :)')
end
else
if not has_been_warned then
utilities.send_reply(self, msg, "Dies ist ein privater Bot, der erst nach einer Freischaltung benutzt werden kann.\nThis is a private bot, which can only be after an approval.")
redis:hset('user:'..msg.from.id, 'has_been_warned', true)
else
print('User has already been warned!')
end
end
else
print('User '..msg.from.id..' allowed :)')
end
if not allowed then
msg.text = ''
msg.text_lower = ''
msg.entities = ''
end
-- else
-- print('Whitelist not enabled or is sudo')
end
return msg
end
function banhammer:action(msg, config, matches)
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
if matches[1] == 'ban' then
local user_id = matches[3]
local chat_id = msg.chat.id
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
if matches[2] == 'user' then
local text = banhammer:ban_user(user_id, chat_id, self)
utilities.send_reply(self, msg, text)
return
end
if matches[2] == 'delete' then
local text = banhammer:unban_user(user_id, chat_id, self, msg.chat.type)
utilities.send_reply(self, msg, text)
return
end
else
utilities.send_reply(self, msg, 'Das ist keine Chat-Gruppe')
return
end
end
if matches[1] == 'kick' then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
banhammer:kick_user(matches[2], msg.chat.id, self, true)
return
else
utilities.send_reply(self, msg, 'Das ist keine Chat-Gruppe')
return
end
end
if matches[1] == 'whitelist' then
if matches[2] == 'enable' then
local hash = 'whitelist:enabled'
redis:set(hash, true)
utilities.send_reply(self, msg, 'Whitelist aktiviert')
return
end
if matches[2] == 'disable' then
local hash = 'whitelist:enabled'
redis:del(hash)
utilities.send_reply(self, msg, 'Whitelist deaktiviert')
return
end
if matches[2] == 'user' then
local hash = 'whitelist:user#id'..matches[3]
redis:set(hash, true)
utilities.send_reply(self, msg, 'User '..matches[3]..' whitelisted')
return
end
if matches[2] == 'chat' then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
local hash = 'whitelist:chat#id'..msg.chat.id
redis:set(hash, true)
utilities.send_reply(self, msg, 'Chat '..msg.chat.id..' whitelisted')
return
else
utilities.send_reply(self, msg, 'Das ist keine Chat-Gruppe!')
return
end
end
if matches[2] == 'delete' and matches[3] == 'user' then
local hash = 'whitelist:user#id'..matches[4]
redis:del(hash)
utilities.send_reply(self, msg, 'User '..matches[4]..' von der Whitelist entfernt!')
return
end
if matches[2] == 'delete' and matches[3] == 'chat' then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
local hash = 'whitelist:chat#id'..msg.chat.id
redis:del(hash)
utilities.send_reply(self, msg, 'Chat '..msg.chat.id..' von der Whitelist entfernt')
return
else
utilities.send_reply(self, msg, 'Das ist keine Chat-Gruppe!')
return
end
end
end
end
return banhammer

View File

@ -2,8 +2,8 @@ local bitly = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
function bitly:init(config)
if not cred_data.bitly_access_token then

View File

@ -4,10 +4,10 @@ local http = require('socket.http')
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
local OAuth = require "OAuth"
local redis = (loadfile "./otouto/redis.lua")()
local redis = (loadfile "./miku/redis.lua")()
function bitly_create:init(config)
if not cred_data.bitly_client_id then
@ -25,7 +25,7 @@ function bitly_create:init(config)
end
bitly_create.triggers = {
"^/short (auth) (.+)$",
"^/short(auth)(.+)$",
"^/short (auth)$",
"^/short (unauth)$",
"^/short (me)$",
@ -93,12 +93,16 @@ function bitly_create:action(msg, config, matches)
if matches[1] == 'auth' and matches[2] then
utilities.send_reply(self, msg, bitly_create:get_bitly_access_token(hash, matches[2]), true)
local message_id = redis:hget(hash, 'bitly_login_msg')
utilities.edit_message(self, msg.chat.id, message_id, '*Anmeldung abgeschlossen!*', true, true)
redis:hdel(hash, 'bitly_login_msg')
return
end
if matches[1] == 'auth' then
utilities.send_reply(self, msg, 'Bitte logge dich ein und folge den Anweisungen:\n[Bei Bitly anmelden](https://bitly.com/oauth/authorize?client_id='..client_id..'&redirect_uri='..redirect_uri..')', true)
return
local result = utilities.send_reply(self, msg, '*Bitte logge dich ein und folge den Anweisungen.*', true, '{"inline_keyboard":[[{"text":"Bei Bitly anmelden","url":"https://bitly.com/oauth/authorize?client_id='..client_id..'&redirect_uri='..redirect_uri..'&state='..self.info.username..'"}]]}')
redis:hset(hash, 'bitly_login_msg', result.result.message_id)
return
end
if matches[1] == 'unauth' and bitly_access_token then
@ -127,7 +131,7 @@ function bitly_create:action(msg, config, matches)
print('Not signed in, will use global bitly access_token')
bitly_access_token = cred_data.bitly_access_token
end
if matches[2] == nil then
long_url = url_encode(matches[1])
domain = 'bit.ly'

51
miku/plugins/br.lua Normal file
View File

@ -0,0 +1,51 @@
local br = {}
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
br.triggers = {
"br.de/nachrichten/(.*).html$"
}
function br:get_br_article(article)
local url = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url=%22http://www.br.de/nachrichten/'..article..'.html%22%20and%20xpath=%22//div[@id=%27content%27]/div[1]/div[2]/div[1]|//div[@class=%27lead_picture%27]/img%22&format=json'
local res,code = https.request(url)
local data = json.decode(res).query.results
if code ~= 200 then return "HTTP-Fehler" end
if not data then return "HTTP-Fehler" end
local subtitle = data.div.h1.em
local title = string.sub(data.div.h1.content, 3)
local teaser = string.sub(data.div.p[1].content, 2)
if data.div.p[3] then
updated = data.div.p[3].content
else
updated = data.div.p[2].content
end
if data.img then
image_url = 'https://www.br.de'..data.img.src
end
local text = '*'..subtitle..' - '..title..'*'..teaser..'\n_'..updated..'_'
if data.img then
return text, image_url
else
return text
end
end
function br:action(msg, config, matches)
local article = URL.escape(matches[1])
local text, image_url = br:get_br_article(article)
if image_url then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url, 'br_teaser.jpg')
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
utilities.send_reply(self, msg, text, true)
end
return br

40
miku/plugins/btc.lua Normal file
View File

@ -0,0 +1,40 @@
local btc = {}
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function btc:init(config)
btc.triggers = {
"^/btc$"
}
btc.doc = [[*
]]..config.cmd_pat..[[btc*: Zeigt aktuellen Bitcoin-Kurs an]]
end
btc.command = 'btc'
-- See https://bitcoinaverage.com/api
function btc:getBTCX()
local base_url = 'https://api.bitcoinaverage.com/ticker/global/'
-- Do request on bitcoinaverage, the final / is critical!
local res,code = https.request(base_url.."EUR/")
if code ~= 200 then return nil end
local data = json.decode(res)
local ask = string.gsub(data.ask, "%.", ",")
local bid = string.gsub(data.bid, "%.", ",")
-- Easy, it's right there
text = 'BTC/EUR\n'..'*Kaufen:* '..ask..'\n'..'*Verkaufen:* '..bid
return text
end
function btc:action(msg, config, matches)
utilities.send_reply(self, msg, btc:getBTCX(cur), true)
end
return btc

39
miku/plugins/calc.lua Normal file
View File

@ -0,0 +1,39 @@
local calc = {}
local URL = require('socket.url')
local http = require('socket.http')
local utilities = require('miku.utilities')
calc.command = 'calc <Ausdruck>'
function calc:init(config)
calc.triggers = {
"^/calc (.*)$"
}
calc.doc = [[*
]]..config.cmd_pat..[[calc* _[Ausdruck]_: Rechnet]]
end
function calc:mathjs(exp)
local exp = string.gsub(exp, ",", "%.")
local url = 'http://api.mathjs.org/v1/'
url = url..'?expr='..URL.escape(exp)
local b,c = http.request(url)
local text = nil
if c == 200 then
text = '= '..string.gsub(b, "%.", ",")
elseif c == 400 then
text = b
else
text = 'Unerwarteter Fehler\n'
..'Ist api.mathjs.org erreichbar?'
end
return text
end
function calc:action(msg, config, matches)
utilities.send_reply(self, msg, calc:mathjs(matches[1]))
end
return calc

39
miku/plugins/cats.lua Normal file
View File

@ -0,0 +1,39 @@
local cats = {}
local HTTP = require('socket.http')
local utilities = require('miku.utilities')
cats.command = 'cat [gif]'
function cats:init(config)
if not cred_data.cat_apikey then
print('Missing config value: cat_apikey.')
print('cats.lua will be enabled, but there are more features with a key.')
end
cats.triggers = {
"^/cat$",
"^/cat (gif)$"
}
cats.doc = [[*
]]..config.cmd_pat..[[cat*: Postet eine zufällige Katze
*]]..config.cmd_pat..[[cat* _gif_: Postet eine zufällige, animierte Katze]]
end
local apikey = cred_data.cat_apikey or "" -- apply for one here: http://thecatapi.com/api-key-registration.html
function cats:action(msg, config)
if matches[1] == 'gif' then
local url = 'http://thecatapi.com/api/images/get?type=gif&apikey='..apikey
local file = download_to_file(url, 'miau.gif')
utilities.send_document(self, msg.chat.id, file, nil, msg.message_id)
else
local url = 'http://thecatapi.com/api/images/get?type=jpg,png&apikey='..apikey
local file = download_to_file(url, 'miau.png')
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
end
return cats

63
miku/plugins/channel.lua Normal file
View File

@ -0,0 +1,63 @@
local channel = {}
local bindings = require('miku.bindings')
local utilities = require('miku.utilities')
channel.command = 'ch <Kanal> \\n <Nachricht>'
channel.doc = [[*
/ch*_ <Kanal>_
_<Nachricht>_
Sendet eine Nachricht in den Kanal. Der Kanal kann per Username oder ID bestimmt werden, Markdown wird unterstützt. Du musst Administrator oder Besitzer des Kanals sein.
Markdown-Syntax:
*Fetter Text*
_Kursiver Text_
[Text](URL)
`Inline-Codeblock`
```Größere Code-Block über mehrere Zeilen```
*Der Kanalname muss mit einem @ beginnen!*]]
function channel:init(config)
channel.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ch', true).table
end
function channel:action(msg, config)
local input = utilities.input(msg.text)
local output
if input then
local chat_id = utilities.get_word(input, 1)
local admin_list, t = bindings.getChatAdministrators(self, { chat_id = chat_id } )
if admin_list then
local is_admin = false
for _, admin in ipairs(admin_list.result) do
if admin.user.id == msg.from.id then
is_admin = true
end
end
if is_admin then
local text = input:match('\n(.+)')
if text then
local success, result = utilities.send_message(self, chat_id, text, true, nil, true)
if success then
output = 'Deine Nachricht wurde versendet!'
else
output = 'Sorry, ich konnte deine Nachricht nicht senden.\n`' .. result.description .. '`'
end
else
output = 'Bitte gebe deine Nachricht ein. Markdown wird unterstützt.'
end
else
output = 'Es sieht nicht so aus, als wärst du der Administrator dieses Kanals.'
end
else
output = 'Sorry, ich konnte die Administratorenliste nicht abrufen. Falls du den Kanalnamen benutzt: Beginnt er mit einem @?\n`' .. t.description .. '`'
end
else
output = channel.doc
end
utilities.send_reply(self, msg, output, true)
end
return channel

90
miku/plugins/channels.lua Normal file
View File

@ -0,0 +1,90 @@
local channels = {}
local bindings = require('miku.bindings')
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
channels.command = 'channel <nur für Superuser>'
function channels:init(config)
channels.triggers = {
"^/channel? (enable)",
"^/channel? (disable)"
}
channels.doc = [[*
]]..config.cmd_pat..[[channel* _<enable>_/_<disable>_: Aktiviert/deaktiviert den Bot im Chat]]
end
-- Checks if bot was disabled on specific chat
function channels:is_channel_disabled(msg)
local hash = 'chat:'..msg.chat.id..':disabled'
local disabled = redis:get(hash)
if not disabled or disabled == "false" then
return false
end
return disabled
end
function channels:enable_channel(msg)
local hash = 'chat:'..msg.chat.id..':disabled'
local disabled = redis:get(hash)
if disabled then
print('Setting redis variable '..hash..' to false')
redis:set(hash, false)
return 'Channel aktiviert'
else
return 'Channel ist nicht deaktiviert!'
end
end
function channels:disable_channel(msg)
local hash = 'chat:'..msg.chat.id..':disabled'
local disabled = redis:get(hash)
if disabled ~= "true" then
print('Setting redis variable '..hash..' to true')
redis:set(hash, true)
return 'Channel deaktiviert'
else
return 'Channel ist bereits deaktiviert!'
end
end
function channels:pre_process(msg, self, config)
-- If is sudo can reeanble the channel
if is_sudo(msg, config) then
if msg.text == "/channel enable" then
channels:enable_channel(msg)
end
end
if channels:is_channel_disabled(msg) then
print('Channel wurde deaktiviert')
msg.text = ''
msg.text_lower = ''
msg.entities = ''
end
return msg
end
function channels:action(msg, config, matches)
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
-- Enable a channel
if matches[1] == 'enable' then
utilities.send_reply(self, msg, channels:enable_channel(msg))
return
end
-- Disable a channel
if matches[1] == 'disable' then
utilities.send_reply(self, msg, channels:disable_channel(msg))
return
end
end
return channels

View File

@ -0,0 +1,35 @@
local cleverbot = {}
local https = require('ssl.https')
local URL = require('socket.url')
local utilities = require('miku.utilities')
local json = require('dkjson')
function cleverbot:init(config)
cleverbot.triggers = {
"^/cbot (.*)$"
}
cleverbot.doc = [[*
]]..config.cmd_pat..[[cbot* _<Text>_*: Befragt den Cleverbot]]
end
cleverbot.command = 'cbot <Text>'
function cleverbot:action(msg, config)
local text = msg.text
local url = "https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text="..URL.escape(text)
local query = https.request(url)
if query == nil then utilities.send_reply(self, msg, 'Ein Fehler ist aufgetreten :(') return end
local decode = json.decode(query)
local answer = string.gsub(decode.clever, "&Auml;", "Ä")
local answer = string.gsub(answer, "&auml;", "ä")
local answer = string.gsub(answer, "&Ouml;", "Ö")
local answer = string.gsub(answer, "&ouml;", "ö")
local answer = string.gsub(answer, "&Uuml;", "Ü")
local answer = string.gsub(answer, "&uuml;", "ü")
local answer = string.gsub(answer, "&szlig;", "ß")
utilities.send_reply(self, msg, answer)
end
return cleverbot

33
miku/plugins/clypit.lua Normal file
View File

@ -0,0 +1,33 @@
local clypit = {}
local http = require('socket.http')
local json = require('dkjson')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
clypit.triggers = {
"clyp.it/([A-Za-z0-9-_-]+)"
}
function clypit:get_clypit_details(shortcode)
local BASE_URL = "http://api.clyp.it"
local url = BASE_URL..'/'..shortcode
local res,code = http.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
local title = data.Title
local duration = data.Duration
local audio = download_to_file(data.Mp3Url)
return audio, title, duration
end
function clypit:action(msg, config, matches)
utilities.send_typing(self, msg.chat.id, 'upload_audio')
local audio, title, duration = clypit:get_clypit_details(matches[1])
if not audio then return utilities.send_reply(self, msg, config.errors.connection) end
utilities.send_audio(self, msg.chat.id, audio, nil, msg.message_id, duration, nil, title)
end
return clypit

View File

@ -1,14 +1,14 @@
local control = {}
local bot = require('otouto.bot')
local utilities = require('otouto.utilities')
local bot = require('miku.bot')
local utilities = require('miku.utilities')
local cmd_pat -- Prevents the command from being uncallable.
function control:init(config)
cmd_pat = config.cmd_pat
control.triggers = utilities.triggers(self.info.username, cmd_pat,
{'^'..cmd_pat..'script'}):t('reload', true):t('halt').table
{'^'..cmd_pat..'script'}):t('restart', true):t('halt').table
end
function control:action(msg, config)
@ -17,25 +17,25 @@ function control:action(msg, config)
return
end
if msg.date < os.time() - 1 then return end
if msg.date < os.time() - 2 then return end
if msg.text_lower:match('^'..cmd_pat..'reload') then
if msg.text_lower:match('^'..cmd_pat..'restart') then
for pac, _ in pairs(package.loaded) do
if pac:match('^otouto%.plugins%.') then
if pac:match('^miku%.plugins%.') then
package.loaded[pac] = nil
end
end
package.loaded['otouto.bindings'] = nil
package.loaded['otouto.utilities'] = nil
package.loaded['miku.bindings'] = nil
package.loaded['miku.utilities'] = nil
package.loaded['config'] = nil
if msg.text_lower:match('%+config') then for k, v in pairs(require('config')) do
config[k] = v
end end
bot.init(self, config)
utilities.send_reply(self, msg, 'Bot reloaded!')
utilities.send_reply(self, msg, 'Bot neu gestartet!')
elseif msg.text_lower:match('^'..cmd_pat..'halt') then
self.is_started = false
utilities.send_reply(self, msg, 'Stopping bot!')
utilities.send_reply(self, msg, 'Stoppe Bot!')
elseif msg.text_lower:match('^'..cmd_pat..'script') then
local input = msg.text_lower:match('^'..cmd_pat..'script\n(.+)')
if not input then
@ -46,7 +46,7 @@ function control:action(msg, config)
for command in input:gmatch('(.-)\n') do
command = utilities.trim(command)
msg.text = command
bot.on_msg_receive(self, msg)
bot.on_msg_receive(self, msg, config)
end
end

View File

@ -1,7 +1,7 @@
local creds_manager = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
function creds_manager:init(config)
creds_manager.triggers = {

64
miku/plugins/currency.lua Normal file
View File

@ -0,0 +1,64 @@
local currency = {}
local HTTPS = require('ssl.https')
local utilities = require('miku.utilities')
currency.command = 'cash [Menge] <von> <zu>'
function currency:init(config)
currency.triggers = {
"^/cash ([A-Za-z]+)$",
"^/cash ([A-Za-z]+) ([A-Za-z]+)$",
"^/cash (%d+[%d%.,]*) ([A-Za-z]+) ([A-Za-z]+)$",
"^(/eur)$"
}
currency.doc = [[*
]]..config.cmd_pat..[[cash* _[Menge]_ _<von>_ _<zu>_
Beispiel: _]]..config.cmd_pat..[[cash 5 USD EUR_]]
end
function currency:action(msg, config)
if not matches[2] then
from = string.upper(matches[1])
to = 'EUR'
amount = 1
elseif matches[3] then
from = string.upper(matches[2])
to = string.upper(matches[3])
amount = matches[1]
else
from = string.upper(matches[1])
to = string.upper(matches[2])
amount = 1
end
local amount = string.gsub(amount, ",", ".")
amount = tonumber(amount)
local result = 1
local BASE_URL = 'https://www.google.com/finance/converter'
if from == to then
utilities.send_reply(self, msg, 'Jaja, sehr witzig...')
return
end
local url = BASE_URL..'?from='..from..'&to='..to..'&a='..amount
local str, res = HTTPS.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
return
end
local str = str:match('<span class=bld>(.*) %u+</span>')
if not str then
utilities.send_reply(self, msg, 'Keine gültige Währung - sieh dir die Währungsliste bei [Google Finanzen](https://www.google.com/finance/converter) an.', true)
return
end
local result = string.format('%.2f', str)
local result = string.gsub(result, "%.", ",")
local amount = tostring(string.gsub(amount, "%.", ","))
local output = amount..' '..from..' = *'..result..' '..to..'*'
utilities.send_reply(self, msg, output, true)
end
return currency

View File

@ -0,0 +1,31 @@
local dailymotion = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
dailymotion.triggers = {
"dailymotion.com/video/([A-Za-z0-9-_-]+)"
}
local BASE_URL = 'https://api.dailymotion.com'
function dailymotion:send_dailymotion_info (dm_code)
local url = BASE_URL..'/video/'..dm_code
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
local title = data.title
local channel = data.channel
local text = '*'..title..'*\nHochgeladen in die Kategorie *'..channel..'*'
return text
end
function dailymotion:action(msg, config, matches)
local text = dailymotion:send_dailymotion_info(matches[1])
if not text then utilities.send_reply(self, msg, config.errors.connection) return end
utilities.send_reply(self, msg, text, true)
end
return dailymotion

View File

@ -0,0 +1,52 @@
local deviantart = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
deviantart.triggers = {
"http://(.*).deviantart.com/art/(.*)"
}
local BASE_URL = 'https://backend.deviantart.com'
function deviantart:get_da_data (da_code)
local url = BASE_URL..'/oembed?url='..da_code
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
return data
end
function deviantart:send_da_data (data)
local title = data.title
local category = data.category
local author_name = data.author_name
local text = title..' von '..author_name..'\n'..category
if data.rating == "adult" then
return title..' von '..author_name..'\n'..category..'\n(NSFW)'
else
local image_url = data.fullsize_url
if image_url == nil then
image_url = data.url
end
local file = download_to_file(image_url)
return text, file
end
end
function deviantart:action(msg, config, matches)
local data = deviantart:get_da_data('http://'..matches[1]..'.deviantart.com/art/'..matches[2])
if not data then utilities.send_reply(self, msg, config.errors.connection) return end
local text, file = deviantart:send_da_data(data)
if file then
utilities.send_photo(self, msg.chat.id, file, text, msg.message_id)
else
utilities.send_reply(self, msg, text)
return
end
end
return deviantart

38
miku/plugins/dhl.lua Normal file
View File

@ -0,0 +1,38 @@
local dhl = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
function dhl:init(config)
dhl.triggers = {
"/dhl (%d+)$"
}
dhl.doc = [[*
]]..config.cmd_pat..[[dhl* _<Sendungsnummer>_: Aktueller Status der Sendung]]
end
local BASE_URL = 'https://mobil.dhl.de'
function dhl:sendungsstatus(id)
local url = BASE_URL..'/shipmentdetails.html?shipmentId='..id
local res,code = https.request(url)
if code ~= 200 then return "Fehler beim Abrufen von mobil.dhl.de" end
local status = string.match(res, "<div id%=\"detailShortStatus\">(.-)</div>")
local status = all_trim(status)
local zeit = string.match(res, "<div id%=\"detailStatusDateTime\">(.-)</div>")
local zeit = all_trim(zeit)
if not zeit or zeit == '<br />' then
return status
end
return '*'..status..'*\n_Stand: '..zeit..'_'
end
function dhl:action(msg, config, matches)
local sendungs_id = matches[1]
if string.len(sendungs_id) < 8 then return end
utilities.send_reply(self, msg, dhl:sendungsstatus(sendungs_id), true)
end
return dhl

39
miku/plugins/dropbox.lua Normal file
View File

@ -0,0 +1,39 @@
-- Doesn't use the API for now, maybe we can integrate some cool features?
local dropbox = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
dropbox.triggers = {
"dropbox.com/s/([a-z0-9]+)/(.*)"
}
function dropbox:action(msg, config, matches)
local folder = matches[1]
local file = string.gsub(matches[2], "?dl=0", "")
local link = 'https://dl.dropboxusercontent.com/s/'..folder..'/'..file
local v,code = https.request(link)
if code == 200 then
if string.ends(link, ".png") or string.ends(link, ".jpe?g")then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(link)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
return
elseif string.ends(link, ".webp") or string.ends(link, ".gif") then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(link)
utilities.send_document(self, msg.chat.id, file, nil, msg.message_id)
return
else
utilities.send_reply(self, msg, link)
end
return
else
return
end
end
return dropbox

42
miku/plugins/echo.lua Normal file
View File

@ -0,0 +1,42 @@
local echo = {}
local utilities = require('miku.utilities')
echo.command = 'echo <Text>'
function echo:init(config)
echo.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('echo', true).table
echo.inline_triggers = {
"^e (.*)"
}
echo.doc = [[*
]]..config.cmd_pat..[[echo* _<Text>_: Gibt den Text aus]]
end
function echo:inline_callback(inline_query, config, matches)
local text = matches[1]
local results = '['
-- enable custom markdown button
if text:match('%[.*%]%(.*%)') or text:match('%*.*%*') or text:match('_.*_') or text:match('`.*`') then
results = results..'{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/custom.jpg","title":"Eigenes Markdown","description":"'..text..'","input_message_content":{"message_text":"'..text..'","parse_mode":"Markdown"}},'
end
local results = results..'{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/fett.jpg","title":"Fett","description":"*'..text..'*","input_message_content":{"message_text":"*'..text..'*","parse_mode":"Markdown"}},{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/kursiv.jpg","title":"Kursiv","description":"_'..text..'_","input_message_content":{"message_text":"_'..text..'_","parse_mode":"Markdown"}},{"type":"article","id":"'..math.random(100000000000000000)..'","thumb_url":"https://anditest.perseus.uberspace.de/inlineQuerys/echo/fixedsys.jpg","title":"Feste Breite","description":"`'..text..'`","input_message_content":{"message_text":"`'..text..'`","parse_mode":"Markdown"}}]'
utilities.answer_inline_query(self, inline_query, results, 0)
end
function echo:action(msg)
local input = utilities.input(msg.text)
if not input then
utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true)
else
local output
if msg.chat.type == 'supergroup' then
output = '*Echo:*\n"' .. utilities.md_escape(input) .. '"'
end
utilities.send_message(self, msg.chat.id, input, true, nil, true)
end
end
return echo

View File

@ -0,0 +1,57 @@
local entergroup = {}
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
entergroup.triggers = {
'/nil'
}
function entergroup:chat_new_user(msg, self)
local user_name = msg.new_chat_member.first_name
local chat_title = msg.chat.title
if msg.from.username then
at_name = ' (@'..msg.from.username..')'
else
at_name = ''
end
if msg.from.id == msg.new_chat_member.id then -- entered through link
added_by = ''
else
added_by = '\n'..msg.from.name..at_name..' hat dich hinzugefügt!'
end
if msg.new_chat_member.id == self.info.id then -- don't say hello to ourselves
return
end
local text = 'Hallo '..user_name..', willkommen bei *'..chat_title..'*!'..added_by
utilities.send_reply(self, msg, text, true)
end
function entergroup:chat_del_user(msg, self)
if msg.left_chat_member.id == msg.from.id then -- silent ignore, if user wasn't kicked
return
end
local user_name = msg.left_chat_member.first_name
if msg.from.username then
at_name = ' (@'..msg.from.username..')'
else
at_name = ''
end
local text = user_name..' wurde von '..msg.from.first_name..at_name..' aus der Gruppe gekickt.'
utilities.send_reply(self, msg, text, true)
end
function entergroup:pre_process(msg, self)
if msg.new_chat_member then
entergroup:chat_new_user(msg, self)
elseif msg.left_chat_member then
entergroup:chat_del_user(msg, self)
end
return msg
end
function entergroup:action(msg)
end
return entergroup

View File

@ -1,7 +1,7 @@
local expand = {}
local http = require('socket.http')
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
function expand:init(config)
expand.triggers = {

View File

@ -4,9 +4,9 @@ local http = require('socket.http')
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
local redis = (loadfile "./miku/redis.lua")()
function facebook:init(config)
if not cred_data.fb_access_token then
@ -53,7 +53,7 @@ function facebook:fb_post (id, story_id)
local message = data.message
local name = data.name
if data.link then
link = '\n'..data.name..':\n'..data.link
link = '\n'..data.name..':\n'..utilities.md_escape(data.link)
else
link = ""
end
@ -76,7 +76,7 @@ function facebook:send_facebook_photo(photo_id, receiver)
local from = '*'..data.from.name..'*'
if data.name then
text = from..' hat ein Bild gepostet:\n'..data.name
text = from..' hat ein Bild gepostet:\n'..utilities.md_escape(data.name)
else
text = from..' hat ein Bild gepostet:'
end
@ -93,12 +93,7 @@ function facebook:send_facebook_video(video_id)
local from = '*'..data.from.name..'*'
local description = data.description
local source = data.source
if data.title then
text = from..' hat ein Video gepostet:\n'..description..'\n['..data.title..']('..source..')'
else
text = from..' hat ein Video gepostet:\n'..description..'\n'..utilities.md_escape(source)
end
return text
return from..' hat ein Video gepostet:\n'..description, source, data.title
end
function facebook:facebook_info(name)
@ -156,9 +151,10 @@ function facebook:action(msg, config, matches)
else
photo_id = matches[4]
end
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local text, image_url = facebook:send_facebook_photo(photo_id, receiver)
local file = download_to_file(image_url)
if not image_url then return end
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url, 'photo.jpg')
utilities.send_reply(self, msg, text, true)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
return
@ -168,8 +164,14 @@ function facebook:action(msg, config, matches)
else
video_id = matches[3]
end
local output = facebook:send_facebook_video(video_id)
utilities.send_reply(self, msg, output, true)
local output, video_url, title = facebook:send_facebook_video(video_id)
if not title then
title = 'Video aufrufen'
else
title = 'VIDEO: '..title
end
if not video_url then return end
utilities.send_reply(self, msg, output, true, '{"inline_keyboard":[[{"text":"'..utilities.md_escape(title)..'","url":"'..video_url..'"}]]}')
return
else
utilities.send_reply(self, msg, facebook:facebook_info(matches[1]), true)

36
miku/plugins/fefe.lua Normal file
View File

@ -0,0 +1,36 @@
local fefe = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
fefe.triggers = {
"blog.fefe.de/%?ts=%w%w%w%w%w%w%w%w"
}
function fefe:post(id)
local url = 'http://'..id
local results, code = https.request(url)
if code ~= 200 then return "HTTP-Fehler" end
if string.match(results, "No entries found.") then return "Eintrag nicht gefunden." end
local line = string.sub( results, string.find(results, "<li><a href[^\n]+"))
local text = line:gsub("<div style=.+", "")
-- remove link at begin
local text = text:gsub("<li><a href=\"%?ts=%w%w%w%w%w%w%w%w\">%[l]</a>", "")
-- replace "<p>" with newline; "<b>" and "</b>" with "*"
local text = text:gsub("<p>", "\n\n"):gsub("<p u>", "\n\n")
local text = text:gsub("<b>", "*"):gsub("</b>", "*")
local text = text:gsub("<i>", "_"):gsub("</i>", "_")
-- format quotes and links markdown-like
local text = text:gsub("<a href=\"", "("):gsub("\">", ")["):gsub("</a>", "]")
local text = text:gsub("<blockquote>", "\n\n> "):gsub("</blockquote>", "\n\n")
return text
end
function fefe:action(msg, config, matches)
utilities.send_reply(self, msg, fefe:post(matches[1]))
end
return fefe

76
miku/plugins/flickr.lua Normal file
View File

@ -0,0 +1,76 @@
local flickr = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function flickr:init(config)
if not cred_data.flickr_apikey then
print('Missing config value: flickr_apikey.')
print('flickr.lua will not be enabled.')
return
end
flickr.triggers = {
"flickr.com/photos/([A-Za-z0-9-_-]+)/([0-9]+)"
}
end
local BASE_URL = 'https://api.flickr.com/services/rest'
local makeOurDate = function(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+) (%d+)%:(%d+)%:(%d+)"
local year, month, day, hours, minutes, seconds = dateString:match(pattern)
return day..'.'..month..'.'..year..' um '..hours..':'..minutes..':'..seconds..' Uhr'
end
function flickr:get_flickr_photo_data (photo_id)
local apikey = cred_data.flickr_apikey
local url = BASE_URL..'/?method=flickr.photos.getInfo&api_key='..apikey..'&photo_id='..photo_id..'&format=json&nojsoncallback=1'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res).photo
return data
end
function flickr:send_flickr_photo_data(data)
local title = data.title._content
local username = data.owner.username
local taken = data.dates.taken
local views = data.views
if data.usage.candownload == 1 then
local text = '"'..title..'", aufgenommen am '..makeOurDate(taken)..' von '..username..' ('..comma_value(data.views)..' Aufrufe)'
local image_url = 'https://farm'..data.farm..'.staticflickr.com/'..data.server..'/'..data.id..'_'..data.originalsecret..'_o_d.'..data.originalformat
if data.originalformat == 'gif' then
return text, image_url, true
else
return text, image_url
end
else
return '"'..title..'", aufgenommen '..taken..' von '..username..' ('..data.views..' Aufrufe)\nBild konnte nicht gedownloadet werden (Keine Berechtigung)'
end
end
function flickr:action(msg, config, matches)
local data = flickr:get_flickr_photo_data(matches[2])
if not data then utilities.send_reply(self, msg, config.errors.connection) return end
local text, image_url, isgif = flickr:send_flickr_photo_data(data)
if image_url then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url)
if isgif then
utilities.send_document(self, msg.chat.id, file, text, msg.message_id)
return
else
utilities.send_photo(self, msg.chat.id, file, text, msg.message_id)
return
end
else
utilities.send_reply(self, msg, text)
return
end
end
return flickr

View File

@ -0,0 +1,53 @@
local flickr_search = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function flickr_search:init(config)
if not cred_data.flickr_apikey then
print('Missing config value: flickr_apikey.')
print('flickr_search.lua will not be enabled.')
return
end
flickr_search.triggers = {
"^/flickr (.*)$"
}
end
flickr_search.command = 'flickr <Suchbegriff>'
local apikey = cred_data.flickr_apikey
local BASE_URL = 'https://api.flickr.com/services/rest'
function flickr_search:get_flickr (term)
local url = BASE_URL..'/?method=flickr.photos.search&api_key='..apikey..'&format=json&nojsoncallback=1&privacy_filter=1&safe_search=3&extras=url_o&text='..term
local b,c = https.request(url)
if c ~= 200 then return nil end
local photo = json.decode(b).photos.photo
-- truly randomize
math.randomseed(os.time())
-- random max json table size
local i = math.random(#photo)
local link_image = photo[i].url_o
return link_image
end
function flickr_search:action(msg, config, matches)
local url = flickr_search:get_flickr(matches[1])
if not url then utilities.send_reply(self, msg, config.errors.results) return end
local file = download_to_file(url)
if string.ends(url, ".gif") then
utilities.send_document(self, msg.chat.id, file, url)
return
else
utilities.send_photo(self, msg.chat.id, file, url)
return
end
end
return flickr_search

View File

@ -3,9 +3,9 @@ local forecast = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
local redis = (loadfile "./miku/redis.lua")()
function forecast:init(config)
if not cred_data.forecastio_apikey then
@ -36,7 +36,7 @@ function forecast:init(config)
]]
end
forecast.command = 'forecast'
forecast.command = 'f [Ort]'
local BASE_URL = "https://api.forecast.io/forecast"
local apikey = cred_data.forecastio_apikey

242
miku/plugins/gImages.lua Normal file
View File

@ -0,0 +1,242 @@
-- You need a Google API key and a Google Custom Search Engine set up to use this, in config.google_api_key and config.google_cse_key, respectively.
-- You must also sign up for the CSE in the Google Developer Console, and enable image results.
local gImages = {}
local HTTPS = require('ssl.https')
HTTPS.timeout = 10
local URL = require('socket.url')
local JSON = require('dkjson')
local redis = (loadfile "./miku/redis.lua")()
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function gImages:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('gImages.lua will not be enabled.')
return
elseif not cred_data.google_cse_id then
print('Missing config value: google_cse_id.')
print('gImages.lua will not be enabled.')
return
end
gImages.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('img', true):t('i', true).table
gImages.doc = [[*
]]..config.cmd_pat..[[img* _<Suchbegriff>_
Sucht Bild mit Google und versendet es (SafeSearch aktiv)
Alias: *]]..config.cmd_pat..[[i*]]
end
gImages.command = 'img <Suchbegriff>'
-- Yes, the callback is copied from below, but I can't think of another method :\
function gImages:callback(callback, msg, self, config, input)
if not msg then return end
utilities.answer_callback_query(self, callback, 'Suche nochmal nach "'..URL.unescape(input)..'"')
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local hash = 'telegram:cache:gImages'
local results = redis:smembers(hash..':'..string.lower(URL.unescape(input)))
if not results[1] then
print('doing web request')
results = gImages:get_image(input)
if results == 403 then
utilities.send_reply(self, msg, config.errors.quotaexceeded, true)
return
elseif not results then
utilities.send_reply(self, msg, config.errors.results, true)
return
end
gImages:cache_result(results, input)
end
-- Random image from table
local i = math.random(#results)
-- Thanks to Amedeo for this!
local failed = true
local nofTries = 0
while failed and nofTries < #results do
if results[i].image then
img_url = results[i].link
mimetype = results[i].mime
context = results[i].image.contextLink
else -- from cache
img_url = results[i]
mimetype = redis:hget(hash..':'..img_url, 'mime')
context = redis:hget(hash..':'..img_url, 'contextLink')
end
-- It's important to save the image with the right ending!
if mimetype == 'image/gif' then
file = download_to_file(img_url, 'img.gif')
elseif mimetype == 'image/png' then
file = download_to_file(img_url, 'img.png')
elseif mimetype == 'image/jpeg' then
file = download_to_file(img_url, 'img.jpg')
else
file = nil
end
if not file then
nofTries = nofTries + 1
i = i+1
if i > #results then
i = 1
end
else
failed = false
end
end
if failed then
utilities.send_reply(self, msg, 'Fehler beim Herunterladen eines Bildes.', true, '{"inline_keyboard":[[{"text":"Nochmal versuchen","callback_data":"@'..self.info.username..' gImages:'..input..'"}]]}')
return
end
if mimetype == 'image/gif' then
result = utilities.send_document(self, msg.chat.id, file, nil, msg.message_id, '{"inline_keyboard":[[{"text":"Seite aufrufen","url":"'..context..'"},{"text":"Bild aufrufen","url":"'..img_url..'"},{"text":"Nochmal suchen","callback_data":"@'..self.info.username..' gImages:'..input..'"}]]}')
else
result = utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id, '{"inline_keyboard":[[{"text":"Seite aufrufen","url":"'..context..'"},{"text":"Bild aufrufen","url":"'..img_url..'"},{"text":"Nochmal suchen","callback_data":"@'..self.info.username..' gImages:'..input..'"}]]}')
end
if not result then
utilities.send_reply(self, msg, config.errors.connection, true, '{"inline_keyboard":[[{"text":"Nochmal versuchen","callback_data":"@'..self.info.username..' gImages:'..input..'"}]]}')
return
end
end
function gImages:get_image(input)
local apikey = cred_data.google_apikey -- 100 requests is RIDICULOUS, Google!
local cseid = cred_data.google_cse_id
local BASE_URL = 'https://www.googleapis.com/customsearch/v1'
local url = BASE_URL..'/?searchType=image&alt=json&num=10&key='..apikey..'&cx='..cseid..'&safe=high'..'&q=' .. input .. '&fields=items(link,mime,image(contextLink))'
local jstr, res = HTTPS.request(url)
local jdat = JSON.decode(jstr).items
if not jdat then
return 'NORESULTS'
end
if jdat.error then
if jdat.error.code == 403 then
return 403
else
return false
end
end
return jdat
end
function gImages:cache_result(results, text)
local cache = {}
for v in pairs(results) do
table.insert(cache, results[v].link)
end
for n, link in pairs(cache) do
redis:hset('telegram:cache:gImages:'..link, 'mime', results[n].mime)
redis:hset('telegram:cache:gImages:'..link, 'contextLink', results[n].image.contextLink)
redis:expire('telegram:cache:gImages:'..link, 1209600)
end
cache_data('gImages', string.lower(text), cache, 1209600, 'set')
end
function gImages:action(msg, config, matches)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gImages.doc, true, msg.message_id, true)
return
end
end
print ('Checking if search contains blacklisted word: '..input)
if is_blacklisted(input) then
utilities.send_reply(self, msg, 'Vergiss es! ._.')
return
end
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local hash = 'telegram:cache:gImages'
local results = redis:smembers(hash..':'..string.lower(input))
if not results[1] then
print('doing web request')
results = gImages:get_image(URL.escape(input))
if results == 403 then
utilities.send_reply(self, msg, config.errors.quotaexceeded, true)
return
elseif not results or results == 'NORESULTS' then
utilities.send_reply(self, msg, config.errors.results, true)
return
end
gImages:cache_result(results, input)
end
-- Random image from table
local i = math.random(#results)
-- Thanks to Amedeo for this!
local failed = true
local nofTries = 0
while failed and nofTries < #results do
if results[i].image then
img_url = results[i].link
mimetype = results[i].mime
context = results[i].image.contextLink
else -- from cache
img_url = results[i]
mimetype = redis:hget(hash..':'..img_url, 'mime')
context = redis:hget(hash..':'..img_url, 'contextLink')
end
-- It's important to save the image with the right ending!
if mimetype == 'image/gif' then
file = download_to_file(img_url, 'img.gif')
elseif mimetype == 'image/png' then
file = download_to_file(img_url, 'img.png')
elseif mimetype == 'image/jpeg' then
file = download_to_file(img_url, 'img.jpg')
else
file = nil
end
if not file then
nofTries = nofTries + 1
i = i+1
if i > #results then
i = 1
end
else
failed = false
end
end
if failed then
utilities.send_reply(self, msg, 'Fehler beim Herunterladen eines Bildes.', true, '{"inline_keyboard":[[{"text":"Nochmal versuchen","callback_data":"@'..self.info.username..' gImages:'..URL.escape(input)..'"}]]}')
return
end
if mimetype == 'image/gif' then
result = utilities.send_document(self, msg.chat.id, file, nil, msg.message_id, '{"inline_keyboard":[[{"text":"Seite aufrufen","url":"'..context..'"},{"text":"Bild aufrufen","url":"'..img_url..'"},{"text":"Nochmal suchen","callback_data":"@'..self.info.username..' gImages:'..URL.escape(input)..'"}]]}')
else
result = utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id, '{"inline_keyboard":[[{"text":"Seite aufrufen","url":"'..context..'"},{"text":"Bild aufrufen","url":"'..img_url..'"},{"text":"Nochmal suchen","callback_data":"@'..self.info.username..' gImages:'..URL.escape(input)..'"}]]}')
end
if not result then
utilities.send_reply(self, msg, config.errors.connection, true, '{"inline_keyboard":[[{"text":"Nochmal versuchen","callback_data":"@'..self.info.username..' gImages:'..URL.escape(input)..'"}]]}')
return
end
end
return gImages

43
miku/plugins/gMaps.lua Normal file
View File

@ -0,0 +1,43 @@
local gMaps = {}
local URL = require('socket.url')
local utilities = require('miku.utilities')
gMaps.command = 'loc <Ort>'
function gMaps:init(config)
gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('loc', true).table
gMaps.doc = [[*
]]..config.cmd_pat..[[loc* _<Ort>_: Sendet Ort via Google Maps]]
end
function gMaps:get_staticmap(area, lat, lon)
local base_api = "https://maps.googleapis.com/maps/api"
local url = base_api .. "/staticmap?size=600x300&zoom=12&center="..URL.escape(area).."&markers=color:red"..URL.escape("|"..area)
local file = download_to_file(url)
return file
end
function gMaps:action(msg, config)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, gMaps.doc, true, msg.message_id, true)
return
end
end
utilities.send_typing(self, msg.chat.id, 'find_location')
local coords = utilities.get_coords(input, config)
if type(coords) == 'string' then
utilities.send_reply(self, msg, coords)
return
end
utilities.send_location(self, msg.chat.id, coords.lat, coords.lon, msg.message_id)
utilities.send_photo(self, msg.chat.id, gMaps:get_staticmap(input, coords.lat, coords.lon), nil, msg.message_id)
end
return gMaps

View File

@ -3,17 +3,15 @@ local gSearch = {}
local HTTPS = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
gSearch.command = 'google <query>'
gSearch.command = 'google <Suchbegriff>'
function gSearch:init(config)
gSearch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('g', true):t('google', true):t('gnsfw', true).table
gSearch.doc = [[```
]]..config.cmd_pat..[[google <Suchbegriff>
Sendet Suchergebnisse von Google
Alias: ]]..config.cmd_pat..[[g
```]]
gSearch.doc = [[*
]]..config.cmd_pat..[[google* _<Suchbegriff>_: Sendet Suchergebnisse von Google
Alias: _]]..config.cmd_pat..[[g_]]
end
function gSearch:googlethat(query, config)
@ -27,8 +25,7 @@ function gSearch:googlethat(query, config)
-- Do the request
local res, code = HTTPS.request(api..parameters)
if code == 403 then
utilities.send_reply(self, msg, config.errors.quotaexceeded)
return
return '403'
end
if code ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
@ -69,8 +66,18 @@ function gSearch:action(msg, config)
end
end
local results, stats = gSearch:googlethat(input, config)
utilities.send_message(self, msg.chat.id, gSearch:stringlinks(results, stats), true, nil, true)
local results, stats = gSearch:googlethat(input, onfig)
if results == '403' then
utilities.send_reply(self, msg, config.errors.quotaexceeded)
return
end
if not results then
utilities.send_reply(self, msg, config.errors.results)
return
end
utilities.send_message(self, msg.chat.id, gSearch:stringlinks(results, stats), true, nil, true, '{"inline_keyboard":[[{"text":"Alle Ergebnisse anzeigen","url":"https://www.google.com/search?q='..URL.escape(input)..'"}]]}')
end

147
miku/plugins/games.lua Normal file
View File

@ -0,0 +1,147 @@
local games = {}
local http = require('socket.http')
local URL = require('socket.url')
local xml = require("xml")
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
games.command = 'game <Spiel>'
function games:init(config)
games.triggers = {
"^/game (.+)$"
}
games.doc = [[*
]]..config.cmd_pat..[[game*_ <Spiel>_: Sendet Infos zum Spiel]]
end
local BASE_URL = 'http://thegamesdb.net/api'
local makeOurDate = function(dateString)
local pattern = "(%d+)%/(%d+)%/(%d+)"
local month, day, year = dateString:match(pattern)
return day..'.'..month..'.'..year
end
function games:get_game_id(game)
local url = BASE_URL..'/GetGamesList.php?name='..game
local res,code = http.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local result = xml.load(res)
if xml.find(result, 'id') then
local game = xml.find(result, 'id')[1]
return game
else
return nil
end
end
function games:send_game_photo(result, self, msg)
local BASE_URL = xml.find(result, 'baseImgUrl')[1]
local images = {}
if xml.find(result, 'fanart') then
local fanart = xml.find(result, 'fanart')[1]
local fanrt_url = BASE_URL..fanart[1]
table.insert(images, fanrt_url)
end
if xml.find(result, 'boxart', 'side', 'front') then
local boxart = xml.find(result, 'boxart', 'side', 'front')[1]
local boxart_url = BASE_URL..boxart
table.insert(images, boxart_url)
end
local i = 0
for k, v in pairs(images) do
i = i+1
local file = download_to_file(v, 'game'..i..'.jpg')
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
end
function games:send_game_data(game_id, self, msg)
local url = BASE_URL..'/GetGame.php?id='..game_id
local res,code = http.request(url)
if code ~= 200 then return nil end
local result = xml.load(res)
local title = xml.find(result, 'GameTitle')[1]
local platform = xml.find(result, 'Platform')[1]
if xml.find(result, 'ReleaseDate') then
date = ', erschienen am '..makeOurDate(xml.find(result, 'ReleaseDate')[1])
else
date = ''
end
if xml.find(result, 'Overview') then
desc = '\n_'..string.sub(xml.find(result, 'Overview')[1], 1, 200) .. '..._'
else
desc = ''
end
if xml.find(result, 'Genres') then
local genres = xml.find(result, 'Genres')
local genre_count = tablelength(genres)-1
if genre_count == 1 then
genre = '\nGenre: '..genres[1][1]
else
local genre_loop = '\nGenres: '
for v in pairs(genres) do
if v == 'xml' then break; end
if v < genre_count then
genre_loop = genre_loop..genres[v][1]..', '
else
genre_loop = genre_loop..genres[v][1]
end
end
genre = genre_loop
end
else
genre = ''
end
if xml.find(result, 'Players') then
players = '\nSpieler: '..xml.find(result, 'Players')[1]
else
players = ''
end
if xml.find(result, 'Youtube') then
video = '\n[Video auf YouTube ansehen]('..xml.find(result, 'Youtube')[1]..')'
else
video = ''
end
if xml.find(result, 'Publisher') then
publisher = '\nPublisher: '..xml.find(result, 'Publisher')[1]
else
publisher = ''
end
local text = '*'..title..'* für *'..platform..'*'..date..desc..genre..players..video..publisher
utilities.send_reply(self, msg, text, true)
if xml.find(result, 'fanrt') or xml.find(result, 'boxart') then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
games:send_game_photo(result, self, msg)
end
return
end
function games:action(msg, config, matches)
local game = URL.escape(matches[1])
local game_id = games:get_game_id(game)
if not game_id then
utilities.send_reply(self, msg, 'Spiel nicht gefunden!')
return
else
games:send_game_data(game_id, self, msg)
end
end
return games

99
miku/plugins/gdrive.lua Normal file
View File

@ -0,0 +1,99 @@
local gdrive = {}
local utilities = require('miku.utilities')
local https = require('ssl.https')
local ltn12 = require('ltn12')
local json = require('dkjson')
local bindings = require('miku.bindings')
function gdrive:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('gdrive.lua will not be enabled.')
return
end
gdrive.triggers = {
"docs.google.com/(.*)/d/([A-Za-z0-9-_-]+)",
"drive.google.com/(.*)/d/([A-Za-z0-9-_-]+)",
"drive.google.com/(open)%?id=([A-Za-z0-9-_-]+)"
}
end
local apikey = cred_data.google_apikey
local BASE_URL = 'https://www.googleapis.com/drive/v2'
function gdrive:get_drive_document_data (docid)
local apikey = cred_data.google_apikey
local url = BASE_URL..'/files/'..docid..'?key='..apikey..'&fields=id,title,mimeType,ownerNames,exportLinks,fileExtension'
local res,code = https.request(url)
local res = string.gsub(res, 'image/', '')
local res = string.gsub(res, 'application/', '')
if code ~= 200 then return nil end
local data = json.decode(res)
return data
end
function gdrive:send_drive_document_data(data, self, msg)
local title = data.title
local mimetype = data.mimeType
local id = data.id
local owner = data.ownerNames[1]
local text = '"'..title..'", freigegeben von '..owner
if data.exportLinks then
if data.exportLinks.png then
local image_url = data.exportLinks.png
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url)
utilities.send_photo(self, msg.chat.id, file, text, msg.message_id)
return
else
local pdf_url = data.exportLinks.pdf
utilities.send_typing(self, msg.chat.id, 'upload_document')
local file = download_to_file(pdf_url)
utilities.send_document(self, msg.chat.id, file, text, msg.message_id)
return
end
else
local get_file_url = 'https://drive.google.com/uc?id='..id
local ext = data.fileExtension
if mimetype == "png" or mimetype == "jpg" or mimetype == "jpeg" or mimetype == "gif" or mimetype == "webp" then
local respbody = {}
local options = {
url = get_file_url,
sink = ltn12.sink.table(respbody),
redirect = false
}
local response = {https.request(options)} -- luasec doesn't support 302 redirects, so we must contact gdrive again
local code = response[2]
local headers = response[3]
local file_url = headers.location
if ext == "jpg" or ext == "jpeg" or ext == "png" then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(file_url)
utilities.send_photo(self, msg.chat.id, file, text, msg.message_id)
return
else
utilities.send_typing(self, msg.chat.id, 'upload_document')
local file = download_to_file(file_url)
utilities.send_document(self, msg.chat.id, file, text, msg.message_id)
return
end
else
local text = '*'..title..'*, freigegeben von _'..owner..'_\n[Direktlink]('..get_file_url..')'
utilities.send_reply(self, msg, text, true)
return
end
end
end
function gdrive:action(msg, config, matches)
local docid = matches[2]
local data = gdrive:get_drive_document_data(docid)
if not data then utilities.send_reply(self, msg, config.errors.connection) return end
gdrive:send_drive_document_data(data, self, msg)
return
end
return gdrive

View File

@ -1,7 +1,7 @@
local get = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
get.command = 'get <Variable>'

113
miku/plugins/getfile.lua Normal file
View File

@ -0,0 +1,113 @@
-- YOU NEED THE FOLLOWING FOLDERS: photo, document, video, voice
-- PLEASE ADJUST YOUR PATH BELOW
-- Save your bot api key in redis set telegram:credentials!
local media_download = {}
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
local ltn12 = require('ltn12')
local HTTPS = require('ssl.https')
local redis = (loadfile "./miku/redis.lua")()
media_download.triggers = {
'/nil'
}
function media_download:download_to_file_permanently(url, file_name)
local respbody = {}
local options = {
url = url,
sink = ltn12.sink.table(respbody),
redirect = false
}
local response = nil
response = {HTTPS.request(options)}
local code = response[2]
local headers = response[3]
local status = response[4]
if code ~= 200 then return false end
-- TODO: Save, when folder doesn't exist
-- Create necessary folders in this folder!
local file_path = "/home/YOURPATH/tmp/"..file_name
file = io.open(file_path, "w+")
file:write(table.concat(respbody))
file:close()
print("Downloaded to: "..file_path)
return true
end
function media_download:pre_process(msg, self)
if msg.photo then
local lv = #msg.photo -- find biggest photo, always the last value
file_id = msg.photo[lv].file_id
file_size = msg.photo[lv].file_size
elseif msg.video then
file_id = msg.video.file_id
file_size = msg.video.file_size
elseif msg.sticker then
file_id = msg.sticker.file_id
file_size = msg.sticker.file_size
elseif msg.voice then
file_id = msg.voice.file_id
file_size = msg.voice.file_size
elseif msg.audio then
file_id = msg.audio.file_id
file_size = msg.audio.file_size
elseif msg.document then
file_id = msg.document.file_id
file_size = msg.document.file_size
else
return
end
if file_size > 19922944 then
print('File is over 20 MB - can\'t download :(')
return
end
-- Check if file has already been downloaded
local already_downloaded = redis:sismember('telegram:file_id', file_id)
if already_downloaded == true then
print('File has already been downloaded in the past, skipping...')
return
end
-- Saving file to the Telegram Cloud
local request = bindings.request(self, 'getFile', {
file_id = file_id
} )
-- Getting file from the Telegram Cloud
if not request then
print('Download failed!')
return
end
-- Use original filename for documents
if msg.document then
file_path = 'document/'..file_id..'-'..msg.document.file_name -- to not overwrite a file
else
file_path = request.result.file_path
end
-- Construct what we want
local download_url = 'https://api.telegram.org/file/bot'..cred_data.bot_api_key..'/'..request.result.file_path
local ok = media_download:download_to_file_permanently(download_url, file_path)
if not ok then
print('Download failed!')
return
end
-- Save file_id to redis to prevent downloading the same file over and over when forwarding
redis:sadd('telegram:file_id', file_id)
return msg
end
function media_download:action(msg)
end
return media_download

36
miku/plugins/gfycat.lua Normal file
View File

@ -0,0 +1,36 @@
-- Thanks to Akamaru for the API entrypoints and the initial idea
local gfycat = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
gfycat.triggers = {
"gfycat.com/([A-Za-z0-9-_-]+)"
}
function gfycat:send_gfycat_video(name, self, msg)
local BASE_URL = "https://gfycat.com"
local url = BASE_URL..'/cajax/get/'..name
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res).gfyItem
utilities.send_typing(self, msg.chat.id, 'upload_video')
local file = download_to_file(data.webmUrl)
if file == nil then
send_reply(self, msg, 'Fehler beim Herunterladen von '..name)
return
else
utilities.send_video(self, msg.chat.id, file, nil, msg.message_id)
return
end
end
function gfycat:action(msg, config, matches)
local name = matches[1]
gfycat:send_gfycat_video(name, self, msg)
return
end
return gfycat

View File

@ -4,9 +4,9 @@ local http = require('socket.http')
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
local redis = (loadfile "./miku/redis.lua")()
function github:init(config)
github.triggers = {
@ -31,9 +31,9 @@ end
function github:send_github_data(data)
if not data.owner then return nil end
local name = '*'..data.name..'*'
local description = '_'..data.description..'_'
local owner = data.owner.login
local name = '*'..utilities.md_escape(data.name)..'*'
local description = '_'..utilities.md_escape(data.description)..'_'
local owner = utilities.md_escape(data.owner.login)
local clone_url = data.clone_url
if data.language == nil or data.language == "" then
language = ''
@ -57,7 +57,7 @@ end
function github:send_gh_commit_data(gh_code, gh_commit_sha, data)
if not data.committer then return nil end
local committer = data.committer.name
local message = data.message
local message = utilities.md_escape(data.message)
local text = '`'..gh_code..'@'..gh_commit_sha..'` von *'..committer..'*:\n'..message
return text
end

68
miku/plugins/golem.lua Normal file
View File

@ -0,0 +1,68 @@
local golem = {}
local http = require('socket.http')
local json = require('dkjson')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function golem:init(config)
if not cred_data.golem_apikey then
print('Missing config value: golem_apikey.')
print('golem.lua will not be enabled.')
return
end
golem.triggers = {
"golem.de/news/([A-Za-z0-9-_-]+)-(%d+).html"
}
end
local BASE_URL = 'http://api.golem.de/api'
function golem:get_golem_data (article_identifier)
local apikey = cred_data.golem_apikey
local url = BASE_URL..'/article/meta/'..article_identifier..'/?key='..apikey..'&format=json'
local res,code = http.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res).data
local url = BASE_URL..'/article/images/'..article_identifier..'/?key='..apikey..'&format=json'
local res,code = http.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local image_data = json.decode(res).data
return data, image_data
end
function golem:send_golem_data(data, image_data)
local headline = '*'..data.headline..'*'
if data.subheadline ~= "" then
subheadline = '\n_'..data.subheadline..'_'
else
subheadline = ""
end
local subheadline = data.subheadline
local abstracttext = data.abstracttext
local text = headline..subheadline..'\n'..abstracttext
if image_data[1] then
image_url = image_data[1].native.url
else
image_url = data.leadimg.url
end
return text, image_url
end
function golem:action(msg, config, matches)
local article_identifier = matches[2]
local data, image_data = golem:get_golem_data(article_identifier)
if not data and not image_data then utilities.send_reply(self, msg, config.errors.connection) return end
local text, image_url = golem:send_golem_data(data, image_data)
if image_url then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
utilities.send_reply(self, msg, text, true)
end
return golem

47
miku/plugins/googl.lua Normal file
View File

@ -0,0 +1,47 @@
local googl = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
function googl:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('googl.lua will not be enabled.')
return
end
googl.triggers = {
"goo.gl/([A-Za-z0-9-_-/-/]+)"
}
end
local BASE_URL = 'https://www.googleapis.com/urlshortener/v1'
local makeOurDate = function(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)"
local year, month, day = dateString:match(pattern)
return day..'.'..month..'.'..year
end
function googl:send_googl_info (shorturl)
local apikey = cred_data.google_apikey
local url = BASE_URL..'/url?key='..apikey..'&shortUrl=http://goo.gl/'..shorturl..'&projection=FULL&fields=longUrl,created,analytics(allTime(shortUrlClicks))'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res)
local longUrl = data.longUrl
local shortUrlClicks = data.analytics.allTime.shortUrlClicks
local created = makeOurDate(data.created)
local text = longUrl..'\n'..shortUrlClicks..' mal geklickt (erstellt am '..created..')'
return text
end
function googl:action(msg, config, matches)
local shorturl = matches[1]
utilities.send_reply(self, msg, googl:send_googl_info(shorturl))
end
return googl

38
miku/plugins/gps.lua Normal file
View File

@ -0,0 +1,38 @@
local gps = {}
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
gps.command = 'gps <Breitengrad>,<Längengrad>'
function gps:init(config)
gps.triggers = {
"^/gps ([^,]*)[,%s]([^,]*)$",
"google.de/maps/@([^,]*)[,%s]([^,]*)",
"google.com/maps/@([^,]*)[,%s]([^,]*)",
"google.de/maps/place/@([^,]*)[,%s]([^,]*)",
"google.com/maps/place/@([^,]*)[,%s]([^,]*)"
}
gps.doc = [[*
]]..config.cmd_pat..[[gps* _<Breitengrad>_,_<Längengrad>_: Sendet Karte mit diesen Koordinaten]]
end
function gps:action(msg, config, matches)
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local lat = matches[1]
local lon = matches[2]
local zooms = {16, 18}
local urls = {}
for i in ipairs(zooms) do
local zoom = zooms[i]
local url = "https://maps.googleapis.com/maps/api/staticmap?zoom=" .. zoom .. "&size=600x300&maptype=hybrid&center=" .. lat .. "," .. lon .. "&markers=color:red%7Clabel:•%7C" .. lat .. "," .. lon
local file = download_to_file(url, 'zoom_'..i..'.png')
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
utilities.send_location(self, msg.chat.id, lat, lon, msg.message_id)
end
return gps

View File

@ -4,7 +4,7 @@
local greetings = {}
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
function greetings:init(config)
config.greetings = config.greetings or {
@ -41,12 +41,16 @@ end
function greetings:action(msg, config)
local nick = self.database.users[msg.from.id_str].nickname or msg.from.first_name
local nick = utilities.build_name(msg.from.first_name, msg.from.last_name)
if self.database.userdata[tostring(msg.from.id)] then
nick = self.database.userdata[tostring(msg.from.id)].nickname or nick
end
for trigger,responses in pairs(config.greetings) do
for _,response in pairs(responses) do
if msg.text_lower:match(response..',? '..self.info.first_name:lower()) then
utilities.send_message(self, msg.chat.id, utilities.latcyr(trigger:gsub('#NAME', nick)))
local output = utilities.char.zwnj .. trigger:gsub('#NAME', nick)
utilities.send_message(self, msg.chat.id, output)
return
end
end

View File

@ -0,0 +1,45 @@
local hackernews = {}
local https = require('ssl.https')
local json = require('dkjson')
local URL = require('socket.url')
local utilities = require('miku.utilities')
hackernews.triggers = {
"news.ycombinator.com/item%?id=(%d+)"
}
local BASE_URL = 'https://hacker-news.firebaseio.com/v0'
function hackernews:send_hackernews_post (hn_code)
local url = BASE_URL..'/item/'..hn_code..'.json'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res)
local by = data.by
local title = data.title
if data.url then
url = '\n[Link besuchen]('..data.url..')'
else
url = ''
end
if data.text then
post = '\n'..unescape_html(data.text)
post = string.gsub(post, '<p>', ' ')
else
post = ''
end
local text = '*'..title..'* von _'..by..'_'..post..url
return text
end
function hackernews:action(msg, config, matches)
local hn_code = matches[1]
utilities.send_reply(self, msg, hackernews:send_hackernews_post(hn_code), true)
end
return hackernews

48
miku/plugins/heise.lua Normal file
View File

@ -0,0 +1,48 @@
local heise = {}
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
heise.triggers = {
"heise.de/newsticker/meldung/(.*).html$"
}
function heise:get_heise_article(article)
local url = 'https://query.yahooapis.com/v1/public/yql?q=select%20content,src,strong%20from%20html%20where%20url=%22http://www.heise.de/newsticker/meldung/'..article..'.html%22%20and%20xpath=%22//div[@id=%27mitte_news%27]/article/header/h2|//div[@id=%27mitte_news%27]/article/div/p[1]/strong|//div[@id=%27mitte_news%27]/article/div/figure/img%22&format=json'
local res,code = https.request(url)
local data = json.decode(res).query.results
if code ~= 200 then return "HTTP-Fehler" end
local title = data.h2
if data.strong then
teaser = '\n'..data.strong
else
teaser = ''
end
if data.img then
image_url = 'https:'..data.img.src
end
local text = '*'..title..'*'..teaser
if data.img then
return text, image_url
else
return text
end
end
function heise:action(msg, config, matches)
local article = URL.escape(matches[1])
local text, image_url = heise:get_heise_article(article)
if image_url then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url, 'heise_teaser.jpg')
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
utilities.send_reply(self, msg, text, true)
end
return heise

13
miku/plugins/hello.lua Normal file
View File

@ -0,0 +1,13 @@
local hello = {}
local utilities = require('miku.utilities')
hello.triggers = {
"^[Ss][Aa][Gg] [Hh][Aa][Ll][Ll][Oo] [Zz][Uu] (.*)$"
}
function hello:action(msg, config, matches)
utilities.send_reply(self, msg, 'Hallo, '..matches[1]..'!')
end
return hello

View File

@ -3,17 +3,22 @@
local help = {}
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
local help_text
function help:init(config)
help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hilfe', true):t('help', true).table
end
function help:action(msg, config)
local commandlist = {}
help_text = '*Verfügbare Befehle:*\n'..config.cmd_pat
for _,plugin in ipairs(self.plugins) do
if plugin.command then
table.insert(commandlist, plugin.command)
--help_text = help_text .. '\n• '..config.cmd_pat .. plugin.command:gsub('%[', '\\[')
end
@ -21,17 +26,9 @@ function help:init(config)
table.insert(commandlist, 'hilfe [Plugin]')
table.sort(commandlist)
help_text = help_text .. table.concat(commandlist, '\n'..config.cmd_pat) .. '\nParameter: <benötigt> [optional]'
help_text = help_text:gsub('%[', '\\[')
help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hilfe', true):t('help', true).table
end
function help:action(msg)
local input = utilities.input(msg.text_lower)
-- Attempts to send the help message via PM.

130
miku/plugins/id.lua Normal file
View File

@ -0,0 +1,130 @@
local id = {}
local redis = (loadfile "./miku/redis.lua")()
local bindings = require('miku.bindings')
local utilities = require('miku.utilities')
id.command = 'id'
function id:init(config)
id.triggers = {
"^/id$",
"^/ids? (chat)$"
}
id.doc = [[```
Returns user and chat info for you or the replied-to message.
Alias: ]]..config.cmd_pat..[[who
```]]
end
function id:get_member_count(self, msg, chat_id)
return bindings.request(self, 'getChatMembersCount', {
chat_id = chat_id
} )
end
function id:user_print_name(user) -- Yes, copied from stats plugin
if user.name then
return user.name
end
local text = ''
if user.first_name then
text = user.last_name..' '
end
if user.lastname then
text = text..user.last_name
end
return text
end
function id:get_user(user_id, chat_id)
local user_info = {}
local uhash = 'user:'..user_id
local user = redis:hgetall(uhash)
user_info.name = id:user_print_name(user)
user_info.id = user_id
return user_info
end
function id:action(msg)
if matches[1] == "/id" then
if msg.reply_to_message then
msg = msg.reply_to_message
msg.from.name = utilities.build_name(msg.from.first_name, msg.from.last_name)
end
local chat_id = msg.chat.id
local user = 'Du bist @%s, auch bekannt als *%s* `[%s]`'
if msg.from.username then
user = user:format(utilities.markdown_escape(msg.from.username), msg.from.name, msg.from.id)
else
user = 'Du bist *%s* `[%s]`,'
user = user:format(msg.from.name, msg.from.id)
end
local group = '@%s, auch bekannt als *%s* `[%s]`.'
if msg.chat.type == 'private' then
group = group:format(utilities.markdown_escape(self.info.username), self.info.first_name, self.info.id)
elseif msg.chat.username then
group = group:format(utilities.markdown_escape(msg.chat.username), msg.chat.title, chat_id)
else
group = '*%s* `[%s]`.'
group = group:format(msg.chat.title, chat_id)
end
local output = user .. ', und du bist in der Gruppe ' .. group
utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true)
elseif matches[1] == "chat" then
if msg.chat.type ~= 'group' and msg.chat.type ~= 'supergroup' then
utilities.send_reply(self, msg, 'Das hier ist keine Gruppe!')
return
end
local chat_name = msg.chat.title
local chat_id = msg.chat.id
-- Users on chat
local hash = 'chat:'..chat_id..':users'
local users = redis:smembers(hash)
local users_info = {}
-- Get user info
for i = 1, #users do
local user_id = users[i]
local user_info = id:get_user(user_id, chat_id)
table.insert(users_info, user_info)
end
-- get all administrators and the creator
local administrators = utilities.get_chat_administrators(self, chat_id)
local admins = {}
for num in pairs(administrators.result) do
if administrators.result[num].status ~= 'creator' then
table.insert(admins, tostring(administrators.result[num].user.id))
else
creator_id = administrators.result[num].user.id
end
end
local result = id:get_member_count(self, msg, chat_id)
local member_count = result.result - 1 -- minus the bot
if member_count == 1 then
member_count = 'ist *1 Mitglied'
else
member_count = 'sind *'..member_count..' Mitglieder'
end
local text = 'IDs für *'..chat_name..'* `['..chat_id..']`\nHier '..member_count..':*\n---------\n'
for k,user in pairs(users_info) do
if table.contains(admins, tostring(user.id)) then
text = text..'*'..user.name..'* `['..user.id..']` _Administrator_\n'
elseif tostring(creator_id) == user.id then
text = text..'*'..user.name..'* `['..user.id..']` _Gruppenersteller_\n'
else
text = text..'*'..user.name..'* `['..user.id..']`\n'
end
end
utilities.send_reply(self, msg, text, true)
end
end
return id

80
miku/plugins/ifttt.lua Normal file
View File

@ -0,0 +1,80 @@
local ifttt = {}
local https = require('ssl.https')
local URL = require('socket.url')
local redis = (loadfile "./miku/redis.lua")()
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function ifttt:init(config)
ifttt.triggers = {
"^/ifttt (!set) (.*)$",
"^/ifttt (!unauth)$",
"^/ifttt (.*)%&(.*)%&(.*)%&(.*)",
"^/ifttt (.*)%&(.*)%&(.*)",
"^/ifttt (.*)%&(.*)",
"^/ifttt (.*)$"
}
ifttt.doc = [[*
]]..config.cmd_pat..[[ifttt* _!set_ _<Key>_: Speichere deinen Schlüssel ein (erforderlich)
*]]..config.cmd_pat..[[ifttt* _!unauth_: Löscht deinen Account aus dem Bot
*]]..config.cmd_pat..[[ifttt* _<Event>_&_<Value1>_&_<Value2>_&_<Value3>_: Führt [Event] mit den optionalen Parametern Value1, Value2 und Value3 aus
Beispiel: `/ifttt DeinFestgelegterName&Hallo&NochEinHallo`: Führt 'DeinFestgelegterName' mit den Parametern 'Hallo' und 'NochEinHallo' aus.]]
end
ifttt.command = 'ifttt <Event>&<Value1>&<Value2>&<Value3>'
local BASE_URL = 'https://maker.ifttt.com/trigger'
function ifttt:set_ifttt_key(hash, key)
print('Setting ifttt in redis hash '..hash..' to '..key)
redis:hset(hash, 'ifttt', key)
return '*Schlüssel eingespeichert!* Das Plugin kann jetzt verwendet werden.'
end
function ifttt:do_ifttt_request(key, event, value1, value2, value3)
if not value1 then
url = BASE_URL..'/'..event..'/with/key/'..key
elseif not value2 then
url = BASE_URL..'/'..event..'/with/key/'..key..'/?value1='..URL.escape(value1)
elseif not value3 then
url = BASE_URL..'/'..event..'/with/key/'..key..'/?value1='..URL.escape(value1)..'&value2='..URL.escape(value2)
else
url = BASE_URL..'/'..event..'/with/key/'..key..'/?value1='..URL.escape(value1)..'&value2='..URL.escape(value2)..'&value3='..URL.escape(value3)
end
local res,code = https.request(url)
if code ~= 200 then return "*Ein Fehler ist aufgetreten!* Aktion wurde nicht ausgeführt." end
return "*Event \""..event.."\" getriggert!*"
end
function ifttt:action(msg, config, matches)
local hash = 'user:'..msg.from.id
local key = redis:hget(hash, 'ifttt')
local event = matches[1]
local value1 = matches[2]
local value2 = matches[3]
local value3 = matches[4]
if event == '!set' then
utilities.send_reply(self, msg, ifttt:set_ifttt_key(hash, value1), true)
return
end
if not key then
utilities.send_reply(self, msg, '*Bitte speichere zuerst deinen Schlüssel ein!* Aktiviere dazu den [Maker Channel](https://ifttt.com/maker) und speichere deinen Schlüssel mit `/ifttt !set KEY` ein', true)
return
end
if event == '!unauth' then
redis:hdel(hash, 'ifttt')
utilities.send_reply(self, msg, '*Erfolgreich ausgeloggt!*', true)
return
end
utilities.send_reply(self, msg, ifttt:do_ifttt_request(key, event, value1, value2, value3), true)
end
return ifttt

21
miku/plugins/images.lua Normal file
View File

@ -0,0 +1,21 @@
local images = {}
local utilities = require('miku.utilities')
images.triggers = {
"(https?://[%w-_%%%.%?%.:,/%+=~&%[%]]+%.[Pp][Nn][Gg])$",
"(https?://[%w-_%%%.%?%.:,/%+=~&%[%]]+%.[Jj][Pp][Ee]?[Gg])$"
}
function images:action(msg)
local url = matches[1]
local file, last_modified, nocache = get_cached_file(url, nil, msg.chat.id, 'upload_photo', self)
local result = utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
if nocache then return end
if not result then return end
-- Cache File-ID und Last-Modified-Header in Redis
cache_file(result, url, last_modified)
end
return images

View File

@ -3,8 +3,8 @@ local imdb = {}
local HTTP = require('socket.http')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
imdb.command = 'imdb <query>'

View File

@ -1,12 +1,16 @@
local imgblacklist = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
imgblacklist.command = 'imgblacklist'
function imgblacklist:init(config)
imgblacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imgblacklist', true).table
imgblacklist.triggers = {
"^/imgblacklist show$",
"^/imgblacklist (add) (.*)$",
"^/imgblacklist (remove) (.*)$"
}
imgblacklist.doc = [[*
]]..config.cmd_pat..[[imgblacklist* _show_: Zeige Blacklist
*]]..config.cmd_pat..[[imgblacklist* _add_ _<Wort>_: Fügt Wort der Blacklist hinzu
@ -47,30 +51,34 @@ function imgblacklist:remove_blacklist(word)
end
end
function imgblacklist:action(msg)
function imgblacklist:action(msg, config, matches)
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local input = utilities.input(msg.text)
local input = string.lower(input)
local action = matches[1]
if matches[2] then word = string.lower(matches[2]) else word = nil end
_blacklist = redis:smembers("telegram:img_blacklist")
if input:match('(add) (.*)') then
local word = input:match('add (.*)')
output = imgblacklist:add_blacklist(word)
elseif input:match('(remove) (.*)') then
local word = input:match('remove (.*)')
output = imgblacklist:remove_blacklist(word)
elseif input:match('(show)') then
output = imgblacklist:show_blacklist()
else
utilities.send_message(self, msg.chat.id, imgblacklist.doc, true, msg.message_id, true)
if action == 'add' and not word then
utilities.send_reply(self, msg, imgblacklist.doc, true)
return
elseif action == "add" and word then
utilities.send_reply(self, msg, imgblacklist:add_blacklist(word), true)
return
end
if action == 'remove' and not word then
utilities.send_reply(self, msg, imgblacklist.doc, true)
return
elseif action == "remove" and word then
utilities.send_reply(self, msg, imgblacklist:remove_blacklist(word), true)
return
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
utilities.send_reply(self, msg, imgblacklist:show_blacklist())
end
return imgblacklist

60
miku/plugins/imgur.lua Normal file
View File

@ -0,0 +1,60 @@
local imgur = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
function imgur:init(config)
if not cred_data.imgur_client_id then
print('Missing config value: imgur_client_id.')
print('imgur.lua will not be enabled.')
return
end
imgur.triggers = {
"imgur.com/([A-Za-z0-9]+).gifv",
"https?://imgur.com/([A-Za-z0-9]+)"
}
end
local client_id = cred_data.imgur_client_id
local BASE_URL = 'https://api.imgur.com/3'
function imgur:get_imgur_data(imgur_code)
local response_body = {}
local request_constructor = {
url = BASE_URL..'/image/'..imgur_code,
method = "GET",
sink = ltn12.sink.table(response_body),
headers = {
Authorization = 'Client-ID '..client_id
}
}
local ok, response_code, response_headers, response_status_line = https.request(request_constructor)
if not ok then
return nil
end
local response_body = json.decode(table.concat(response_body))
if response_body.status ~= 200 then return nil end
return response_body.data.link
end
function imgur:action(msg)
local imgur_code = matches[1]
if imgur_code == "login" then return nil end
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local link = imgur:get_imgur_data(imgur_code)
if link then
local file = download_to_file(link)
if string.ends(link, ".gif") then
utilities.send_document(self, msg.chat.id, file, nil, msg.message_id)
else
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
end
end
return imgur

View File

@ -0,0 +1,80 @@
local instagram = {}
local https = require('ssl.https')
local json = require('dkjson')
local URL = require('socket.url')
local utilities = require('miku.utilities')
function instagram:init(config)
if not cred_data.instagram_access_token then
print('Missing config value: instagram_access_token.')
print('instagram.lua will not be enabled.')
return
end
instagram.triggers = {
"instagram.com/p/([A-Za-z0-9-_-]+)"
}
end
local BASE_URL = 'https://api.instagram.com/v1'
local access_token = cred_data.instagram_access_token
function instagram:get_insta_data(insta_code)
local url = BASE_URL..'/media/shortcode/'..insta_code..'?access_token='..access_token
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res).data
return data
end
function instagram:send_instagram_data(data)
-- Header
local username = data.user.username
local full_name = data.user.full_name
if username == full_name then
header = full_name..' hat ein'
else
header = full_name..' ('..username..') hat ein'
end
if data.type == 'video' then
header = header..' Video gepostet'
else
header = header..' Foto gepostet'
end
-- Caption
if data.caption == nil then
caption = ''
else
caption = ':\n'..data.caption.text
end
-- Footer
local comments = comma_value(data.comments.count)
local likes = comma_value(data.likes.count)
local footer = '\n'..likes..' Likes, '..comments..' Kommentare'
if data.type == 'video' then
footer = '\n'..data.videos.standard_resolution.url..footer
end
-- Image
local image_url = data.images.standard_resolution.url
return header..caption..footer, image_url
end
function instagram:action(msg, config, matches)
local insta_code = matches[1]
local data = instagram:get_insta_data(insta_code)
if not data then utilities.send_reply(self, msg, config.errors.connection) return end
local text, image_url = instagram:send_instagram_data(data)
if not image_url then utilities.send_reply(self, msg, config.errors.connection) return end
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url)
utilities.send_photo(self, msg.chat.id, file, text, msg.message_id)
end
return instagram

92
miku/plugins/ip_info.lua Normal file
View File

@ -0,0 +1,92 @@
local ip_info = {}
local http = require('socket.http')
local json = require('dkjson')
local URL = require('socket.url')
local utilities = require('miku.utilities')
function ip_info:init(config)
ip_info.triggers = {
"^/ip (.*)$",
"^/dns (.*)$"
}
ip_info.doc = [[*
]]..config.cmd_pat..[[ip* _<IP-Adresse>_: Sendet Infos zu dieser IP]]
end
ip_info.command = 'ip <IP-Adresse>'
local BASE_URL = 'http://ip-api.com/json'
function ip_info:get_host_data(host)
local url = BASE_URL..'/'..host..'?lang=de&fields=country,regionName,city,zip,lat,lon,isp,org,as,status,message,reverse,query'
local res,code = http.request(url)
if code ~= 200 then return "HTTP-FEHLER: "..code end
local data = json.decode(res)
if data.status == 'fail' then
return nil
end
local isp = data.isp
local url
if data.lat and data.lon then
lat = tostring(data.lat)
lon = tostring(data.lon)
url = "https://maps.googleapis.com/maps/api/staticmap?zoom=16&size=600x300&maptype=hybrid&center="..lat..","..lon.."&markers=color:red%7Clabel:•%7C"..lat..","..lon
end
if data.query == host then
query = ''
else
query = ' / '..data.query
end
if data.reverse ~= "" and data.reverse ~= host then
host_addr = ' ('..data.reverse..')'
else
host_addr = ''
end
-- Location
if data.zip ~= "" then
zipcode = data.zip..' '
else
zipcode = ''
end
local city = data.city
if data.regionName ~= "" then
region = ', '..data.regionName
else
region = ''
end
if data.country ~= "" then
country = ', '..data.country
else
country = ''
end
local text = host..query..host_addr..' ist bei '..isp..':\n'
local location = zipcode..city..region..country
return text..location, url
end
function ip_info:action(msg, config, matches)
local host = matches[1]
local text, image_url = ip_info:get_host_data(host)
if not text then utilities.send_reply(self, msg, config.errors.connection) return end
if image_url then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url, 'map.png')
utilities.send_photo(self, msg.chat.id, file, text, msg.message_id)
else
utilities.send_reply(self, msg, text)
end
end
return ip_info

85
miku/plugins/isup.lua Normal file
View File

@ -0,0 +1,85 @@
local isup = {}
local http = require('socket.http')
local https = require('ssl.https')
local socket = require('socket')
local URL = require('socket.url')
local utilities = require('miku.utilities')
function isup:init(config)
isup.triggers = {
"^/isup (.*)$",
"^/ping (.*)$"
}
isup.doc = [[*
]]..config.cmd_pat..[[isup* _<URL>_: Prüft, ob die URL up ist]]
end
function isup:is_up_socket(ip, port)
print('Connect to', ip, port)
local c = socket.try(socket.tcp())
c:settimeout(3)
local conn = c:connect(ip, port)
if not conn then
return false
else
c:close()
return true
end
end
function isup:is_up_http(url)
-- Parse URL from input, default to http
local parsed_url = URL.parse(url, { scheme = 'http', authority = '' })
-- Fix URLs without subdomain not parsed properly
if not parsed_url.host and parsed_url.path then
parsed_url.host = parsed_url.path
parsed_url.path = ""
end
-- Re-build URL
local url = URL.build(parsed_url)
local protocols = {
["https"] = https,
["http"] = http
}
local options = {
url = url,
redirect = false,
method = "GET"
}
local response = { protocols[parsed_url.scheme].request(options) }
local code = tonumber(response[2])
if code == nil or code >= 400 then
return false
end
return true
end
function isup:isup(url)
local pattern = '^(%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?):?(%d?%d?%d?%d?%d?)$'
local ip,port = string.match(url, pattern)
local result = nil
-- /isup 8.8.8.8:53
if ip then
port = port or '80'
result = isup:is_up_socket(ip, port)
else
result = isup:is_up_http(url)
end
return result
end
function isup:action(msg, config)
if isup:isup(matches[1]) then
utilities.send_reply(self, msg, matches[1]..' ist UP! ✅')
return
else
utilities.send_reply(self, msg, matches[1]..' ist DOWN! ❌')
return
end
end
return isup

View File

@ -0,0 +1,52 @@
local leave_group = {}
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
leave_group.triggers = {
'/nil'
}
local report_to_admin = true -- set to false to not be notified, when Bot leaves groups without you
function leave_group:check_for_admin(msg, self, config)
local result = bindings.request(self, 'getChatMember', {
chat_id = msg.chat.id,
user_id = config.admin
} )
if not result.ok then
print('Konnte nicht prüfen, ob Admin in Gruppe ist! Verlasse sie sicherheitshalber...')
return false
end
if result.result.status ~= "member" and result.result.status ~= "administrator" and result.result.status ~= "creator" then
return false
else
return true
end
end
function leave_group:pre_process(msg, self, config)
if msg.group_chat_created or msg.new_chat_member then
local admin_in_group = leave_group:check_for_admin(msg, self, config)
if not admin_in_group then
print('Admin ist nicht in der Gruppe, verlasse sie deshalb...')
utilities.send_reply(self, msg, 'Dieser Bot wurde in eine fremde Gruppe hinzugefügt. Dies wird gemeldet!\nThis bot was added to foreign group. This incident will be reported!')
local result = bindings.request(self, 'leaveChat', {
chat_id = msg.chat.id
} )
local chat_name = msg.chat.title
local chat_id = msg.chat.id
local from = msg.from.name
local from_id = msg.from.id
if report_to_admin then
utilities.send_message(self, config.admin, '#WARNUNG: Bot wurde in fremde Gruppe hinzugefügt:\nGruppenname: '..chat_name..' ('..chat_id..')\nHinzugefügt von: '..from..' ('..from_id..')')
end
end
end
return msg
end
function leave_group:action(msg)
end
return leave_group

View File

@ -1,7 +1,7 @@
local loc_manager = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
function loc_manager:init(config)
loc_manager.triggers = {

View File

@ -1,6 +1,6 @@
local luarun = {}
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
local URL = require('socket.url')
local JSON = require('dkjson')
@ -25,9 +25,9 @@ function luarun:action(msg, config)
end
local output = loadstring( [[
local bot = require('otouto.bot')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
local bot = require('miku.bot')
local bindings = require('miku.bindings')
local utilities = require('miku.utilities')
local JSON = require('dkjson')
local URL = require('socket.url')
local HTTP = require('socket.http')

51
miku/plugins/lyrics.lua Normal file
View File

@ -0,0 +1,51 @@
local lyrics = {}
local http = require('socket.http')
local json = require('dkjson')
local utilities = require('miku.utilities')
function lyrics:init(config)
if not cred_data.lyricsnmusic_apikey then
print('Missing config value: lyricsnmusic_apikey.')
print('lyrics.lua will not be enabled.')
return
end
lyrics.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lyrics', true).table
lyrics.doc = [[*
]]..config.cmd_pat..[[lyrics* _<Lied>_: Postet Liedertext]]
end
lyrics.command = 'lyrics <Lied>'
function lyrics:getLyrics(text)
local apikey = cred_data.lyricsnmusic_apikey
local q = url_encode(text)
local b = http.request("http://api.lyricsnmusic.com/songs?api_key="..apikey.."&q=" .. q)
response = json.decode(b)
local reply = ""
if #response > 0 then
-- grab first match
local result = response[1]
reply = result.title .. " - " .. result.artist.name .. "\n" .. result.snippet .. "\n[Ganzen Liedertext ansehen](" .. result.url .. ")"
else
reply = nil
end
return reply
end
function lyrics:action(msg, config, matches)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, lyrics.doc, true, msg.message_id, true)
return
end
end
utilities.send_reply(self, msg, lyrics:getLyrics(input), true)
end
return lyrics

View File

@ -0,0 +1,23 @@
local muschel = {}
local utilities = require('miku.utilities')
muschel.triggers = {
"^[Mm][Aa][Gg][Ii][Ss][Cc][Hh][Ee] [Mm][Ii][Ee][Ss][Mm][Uu][Ss][Cc][Hh][Ee][Ll], (.*)$"
}
function muschel:frag_die_muschel()
local possibilities = {
"Ja",
"Nein",
"Eines Tages vielleicht"
}
local random = math.random(3)
return possibilities[random]
end
function muschel:action(msg, config, matches)
utilities.send_reply(self, msg, muschel:frag_die_muschel())
end
return muschel

View File

@ -1,7 +1,10 @@
local media = {}
local utilities = require('otouto.utilities')
local mimetype = (loadfile "./otouto/mimetype.lua")()
local HTTP = require('socket.http')
local HTTPS = require('ssl.https')
local redis = (loadfile "./miku/redis.lua")()
local utilities = require('miku.utilities')
local mimetype = (loadfile "./miku/mimetype.lua")()
media.triggers = {
"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(gif))$",
@ -28,40 +31,28 @@ function media:action(msg)
local ext = matches[2]
local receiver = msg.chat.id
utilities.send_typing(self, receiver, 'upload_document')
local file = download_to_file(url)
local file, last_modified, nocache = get_cached_file(url, nil, msg.chat.id, 'upload_document', self)
local mime_type = mimetype.get_content_type_no_sub(ext)
if ext == 'gif' then
print('send gif')
utilities.send_document(self, receiver, file, nil, msg.message_id)
return
elseif mime_type == 'text' then
print('send_document')
utilities.send_document(self, receiver, file, nil, msg.message_id)
return
elseif mime_type == 'image' then
print('send_photo')
utilities.send_photo(self, receiver, file, nil, msg.message_id)
return
result = utilities.send_document(self, receiver, file, nil, msg.message_id)
elseif mime_type == 'audio' then
print('send_audio')
utilities.send_audio(self, receiver, file, nil, msg.message_id)
return
result = utilities.send_audio(self, receiver, file, nil, msg.message_id)
elseif mime_type == 'video' then
print('send_video')
utilities.send_video(self, receiver, file, nil, msg.message_id)
return
result = utilities.send_video(self, receiver, file, nil, msg.message_id)
else
print('send_file')
utilities.send_document(self, receiver, file, nil, msg.message_id)
return
result = utilities.send_document(self, receiver, file, nil, msg.message_id)
end
if nocache then return end
if not result then return end
-- Cache File-ID und Last-Modified-Header in Redis
cache_file(result, url, last_modified)
end
return media

View File

@ -0,0 +1,86 @@
local mc_server = {}
local http = require('socket.http')
local ltn12 = require('ltn12')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('miku.utilities')
function mc_server:init(config)
mc_server.triggers = {
"^/mine (.*)$"
}
mc_server.doc = [[*
]]..config.cmd_pat..[[mine* _<IP>_: Sucht Minecraft-Server und sendet Infos. Standard-Port: 25565
*]]..config.cmd_pat..[[mine* _<IP>_ _<Port>_: Sucht Minecraft-Server auf Port und sendet Infos.
]]
end
mc_server.command = "mine <IP> [Port]"
function mc_server:mineSearch(ip, port)
local responseText = ""
local api = "https://mcapi.us/server/status"
local parameters = "?ip="..(URL.escape(ip) or "").."&port="..(URL.escape(port) or "").."&players=true"
local respbody = {}
local body, code, headers, status = http.request{
url = api..parameters,
method = "GET",
redirect = true,
sink = ltn12.sink.table(respbody)
}
local body = table.concat(respbody)
if (status == nil) then return "FEHLER: status = nil" end
if code ~=200 then return "FEHLER: "..code..". Status: "..status end
local jsonData = json.decode(body)
responseText = responseText..ip..":"..port..":\n"
if (jsonData.motd ~= nil and jsonData.motd ~= '') then
local tempMotd = ""
tempMotd = jsonData.motd:gsub('%ยง.', '')
if (jsonData.motd ~= nil) then responseText = responseText.."*MOTD*: "..tempMotd.."\n" end
end
if (jsonData.online ~= nil) then
if jsonData.online == true then
server_online = "Ja"
else
server_online = "Nein"
end
responseText = responseText.."*Online*: "..server_online.."\n"
end
if (jsonData.players ~= nil) then
if (jsonData.players.max ~= nil and jsonData.players.max ~= 0) then
responseText = responseText.."*Slots*: "..jsonData.players.max.."\n"
end
if (jsonData.players.now ~= nil and jsonData.players.max ~= 0) then
responseText = responseText.."*Spieler online*: "..jsonData.players.now.."\n"
end
if (jsonData.players.sample ~= nil and jsonData.players.sample ~= false) then
responseText = responseText.."*Spieler*: "..table.concat(jsonData.players.sample, ", ").."\n"
end
if (jsonData.server.name ~= nil and jsonData.server.name ~= "") then
responseText = responseText.."*Server*: "..jsonData.server.name.."\n"
end
end
return responseText
end
function mc_server:parseText(text, mc_server)
if (text == nil or text == "/mine") then
return mc_server.doc
end
ip, port = string.match(text, "^/mine (.-) (.*)$")
if (ip ~= nil and port ~= nil) then
return mc_server:mineSearch(ip, port)
end
local ip = string.match(text, "^/mine (.*)$")
if (ip ~= nil) then
return mc_server:mineSearch(ip, "25565")
end
return "FEHLER: Keine Input IP!"
end
function mc_server:action(msg, config, matches)
utilities.send_reply(self, msg, mc_server:parseText(msg.text, mc_server), true)
end
return mc_server

View File

@ -0,0 +1,31 @@
local mc_skin = {}
local utilities = require('miku.utilities')
function mc_skin:init(config)
mc_skin.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('skin', true).table
mc_skin.doc = [[*
]]..config.cmd_pat..[[skin* _<Username>_: Sendet Minecraft-Skin dieses Nutzers]]
end
mc_skin.command = 'skin <Username>'
local BASE_URL = 'http://ip-api.com/json'
function mc_skin:action(msg, config, matches)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, mc_skin.doc, true, msg.message_id, true)
return
end
end
local url = 'http://www.minecraft-skin-viewer.net/3d.php?layers=true&aa=true&a=0&w=330&wt=10&abg=330&abd=40&ajg=340&ajd=20&ratio=13&format=png&login='..input..'&headOnly=false&displayHairs=true&randomness=341.png'
local file = download_to_file(url)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
return mc_skin

View File

@ -0,0 +1,228 @@
local mal = {}
local http = require('socket.http')
local URL = require('socket.url')
local xml = require("xml")
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
mal.command = 'anime <Anime>, /manga <Manga>'
function mal:init(config)
if not cred_data.mal_user then
print('Missing config value: mal_user.')
print('myanimelist.lua will not be enabled.')
return
elseif not cred_data.mal_pw then
print('Missing config value: mal_pw.')
print('myanimelist.lua will not be enabled.')
return
end
mal.triggers = {
"^/(anime) (.+)$",
"^/(manga) (.+)$"
}
mal.doc = [[*
]]..config.cmd_pat..[[anime*_ <Anime>_: Sendet Infos zum Anime
*]]..config.cmd_pat..[[manga*_ <Manga>_: Sendet Infos zum Manga
]]
end
local user = cred_data.mal_user
local password = cred_data.mal_pw
local BASE_URL = 'http://'..user..':'..password..'@myanimelist.net/api'
function mal:delete_tags(str)
str = string.gsub( str, '<br />', '')
str = string.gsub( str, '%[i%]', '')
str = string.gsub( str, '%[/i%]', '')
str = string.gsub( str, '&mdash;', '')
return str
end
local makeOurDate = function(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)"
local year, month, day = dateString:match(pattern)
return day..'.'..month..'.'..year
end
function mal:get_mal_info(query, typ)
if typ == 'anime' then
url = BASE_URL..'/anime/search.xml?q='..query
elseif typ == 'manga' then
url = BASE_URL..'/manga/search.xml?q='..query
end
local res,code = http.request(url)
if code ~= 200 then return "HTTP-Fehler" end
local result = xml.load(res)
return result
end
function mal:send_anime_data(result, receiver)
local title = xml.find(result, 'title')[1]
local id = xml.find(result, 'id')[1]
local mal_url = 'http://myanimelist.net/anime/'..id
if xml.find(result, 'synonyms')[1] then
alt_name = '\noder: '..unescape(mal:delete_tags(xml.find(result, 'synonyms')[1]))
else
alt_name = ''
end
if xml.find(result, 'synopsis')[1] then
desc = '\n'..unescape(mal:delete_tags(string.sub(xml.find(result, 'synopsis')[1], 1, 200))) .. '...'
else
desc = ''
end
if xml.find(result, 'episodes')[1] then
episodes = '\nEpisoden: '..xml.find(result, 'episodes')[1]
else
episodes = ''
end
if xml.find(result, 'status')[1] then
status = ' ('..xml.find(result, 'status')[1]..')'
else
status = ''
end
if xml.find(result, 'score')[1] ~= "0.00" then
score = '\nScore: '..string.gsub(xml.find(result, 'score')[1], "%.", ",")
else
score = ''
end
if xml.find(result, 'type')[1] then
typ = '\nTyp: '..xml.find(result, 'type')[1]
else
typ = ''
end
if xml.find(result, 'start_date')[1] ~= "0000-00-00" then
startdate = '\nVeröffentlichungszeitraum: '..makeOurDate(xml.find(result, 'start_date')[1])
else
startdate = ''
end
if xml.find(result, 'end_date')[1] ~= "0000-00-00" then
enddate = ' - '..makeOurDate(xml.find(result, 'end_date')[1])
else
enddate = ''
end
local text = '*'..title..'*'..alt_name..typ..episodes..status..score..startdate..enddate..'_'..desc..'_\n[Auf MyAnimeList ansehen]('..mal_url..')'
if xml.find(result, 'image') then
local image_url = xml.find(result, 'image')[1]
return text, image_url
else
return text
end
end
function mal:send_manga_data(result)
local title = xml.find(result, 'title')[1]
local id = xml.find(result, 'id')[1]
local mal_url = 'http://myanimelist.net/manga/'..id
if xml.find(result, 'type')[1] then
typ = ' ('..xml.find(result, 'type')[1]..')'
else
typ = ''
end
if xml.find(result, 'synonyms')[1] then
alt_name = '\noder: '..unescape(mal:delete_tags(xml.find(result, 'synonyms')[1]))
else
alt_name = ''
end
if xml.find(result, 'chapters')[1] then
chapters = '\nKapitel: '..xml.find(result, 'chapters')[1]
else
chapters = ''
end
if xml.find(result, 'status')[1] then
status = ' ('..xml.find(result, 'status')[1]..')'
else
status = ''
end
if xml.find(result, 'volumes')[1] then
volumes = '\nBände '..xml.find(result, 'volumes')[1]
else
volumes = ''
end
if xml.find(result, 'score')[1] ~= "0.00" then
score = '\nScore: '..xml.find(result, 'score')[1]
else
score = ''
end
if xml.find(result, 'start_date')[1] ~= "0000-00-00" then
startdate = '\nVeröffentlichungszeitraum: '..makeOurDate(xml.find(result, 'start_date')[1])
else
startdate = ''
end
if xml.find(result, 'end_date')[1] ~= "0000-00-00" then
enddate = ' - '..makeOurDate(xml.find(result, 'end_date')[1])
else
enddate = ''
end
if xml.find(result, 'synopsis')[1] then
desc = '\n'..unescape(mal:delete_tags(string.sub(xml.find(result, 'synopsis')[1], 1, 200))) .. '...'
else
desc = ''
end
local text = '*'..title..'*'..alt_name..typ..chapters..status..volumes..score..startdate..enddate..'_'..desc..'_\n[Auf MyAnimeList ansehen]('..mal_url..')'
if xml.find(result, 'image') then
local image_url = xml.find(result, 'image')[1]
return text, image_url
else
return text
end
end
function mal:action(msg, config, matches)
local query = URL.escape(matches[2])
if matches[1] == 'anime' then
local anime_info = mal:get_mal_info(query, 'anime')
if anime_info == "HTTP-Fehler" then
utilities.send_reply(self, msg, 'Anime nicht gefunden!')
return
else
local text, image_url = mal:send_anime_data(anime_info)
if image_url then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
utilities.send_reply(self, msg, text, true)
return
end
elseif matches[1] == 'manga' then
local manga_info = mal:get_mal_info(query, 'manga')
if manga_info == "HTTP-Fehler" then
utilities.send_reply(self, msg, 'Manga nicht gefunden!')
return
else
local text, image_url = mal:send_manga_data(manga_info)
if image_url then
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(image_url)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
utilities.send_reply(self, msg, text, true)
return
end
end
end
return mal

103
miku/plugins/notify.lua Normal file
View File

@ -0,0 +1,103 @@
-- INFO: Stats must be activated, so that it can collect all members of a group and save his/her id to redis.
-- You can deactivate it afterwards.
local notify = {}
local redis = (loadfile "./miku/redis.lua")()
local utilities = require('miku.utilities')
function notify:init(config)
notify.triggers = {
"^/notify (del)$",
"^/notify$"
}
notify.doc = [[*
]]..config.cmd_pat..[[notify* (del): Benachrichtigt dich privat, wenn du erwähnt wirst (bzw. schaltet das Feature wieder aus)]]
end
notify.command = 'notify [del]'
-- See https://stackoverflow.com/a/32854917
function isWordFoundInString(word,input)
return select(2,input:gsub('^' .. word .. '%W+','')) +
select(2,input:gsub('%W+' .. word .. '$','')) +
select(2,input:gsub('^' .. word .. '$','')) +
select(2,input:gsub('%W+' .. word .. '%W+','')) > 0
end
function notify:pre_process(msg, self)
local notify_users = redis:smembers('notify:ls')
-- I call this beautiful lady the "if soup"
if msg.chat.type == 'chat' or msg.chat.type == 'supergroup' then
if msg.text then
for _,user in pairs(notify_users) do
if isWordFoundInString('@'..user, string.lower(msg.text)) then
local chat_id = msg.chat.id
local id = redis:hget('notify:'..user, 'id')
-- check, if user has sent at least one message to the group,
-- so that we don't send the user some private text, when he/she is not
-- in the group.
if redis:sismember('chat:'..chat_id..':users', id) then
-- ignore message, if user is mentioning him/herself
if id == tostring(msg.from.id) then break; end
local send_date = run_command('date -d @'..msg.date..' +"%d.%m.%Y um %H:%M:%S Uhr"')
local send_date = string.gsub(send_date, "\n", "")
local from = string.gsub(msg.from.name, "%_", " ")
local chat_name = string.gsub(msg.chat.title, "%_", " ")
local text = from..' am '..send_date..' in "'..chat_name..'":\n\n'..msg.text
utilities.send_message(self, id, text)
end
end
end
end
end
return msg
end
function notify:action(msg, config, matches)
if not msg.from.username then
utilities.send_reply(self, msg, 'Du hast keinen Usernamen und kannst daher dieses Feature nicht nutzen. Tut mir leid!' )
return
end
local username = string.lower(msg.from.username)
local hash = 'notify:'..username
if matches[1] == "del" then
if not redis:sismember('notify:ls', username) then
utilities.send_reply(self, msg, 'Du wirst noch gar nicht benachrichtigt!')
return
end
print('Setting notify in redis hash '..hash..' to false')
redis:hset(hash, 'notify', false)
print('Removing '..username..' from redis set notify:ls')
redis:srem('notify:ls', username)
utilities.send_reply(self, msg, 'Du erhälst jetzt keine Benachrichtigungen mehr, wenn du angesprochen wirst.')
return
else
if redis:sismember('notify:ls', username) then
utilities.send_reply(self, msg, 'Du wirst schon benachrichtigt!')
return
end
print('Setting notify in redis hash '..hash..' to true')
redis:hset(hash, 'notify', true)
print('Setting id in redis hash '..hash..' to '..msg.from.id)
redis:hset(hash, 'id', msg.from.id)
print('Adding '..username..' to redis set notify:ls')
redis:sadd('notify:ls', username)
local res = utilities.send_message(self, msg.from.id, 'Du erhälst jetzt Benachrichtigungen, wenn du angesprochen wirst, nutze `/notify del` zum Deaktivieren.', true, nil, true)
if not res then
utilities.send_reply(self, msg, 'Bitte schreibe mir [privat](http://telegram.me/' .. self.info.username .. '?start=notify), um den Vorgang abzuschließen.', true)
elseif msg.chat.type ~= 'private' then
utilities.send_reply(self, msg, 'Du erhälst jetzt Benachrichtigungen, wenn du angesprochen wirst, nutze `/notify del` zum Deaktivieren.', true)
end
end
end
return notify

View File

@ -0,0 +1,39 @@
local pagespeed_insights = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
function pagespeed_insights:init(config)
if not cred_data.google_apikey then
print('Missing config value: google_apikey.')
print('pagespeed_insights.lua will not be enabled.')
return
end
pagespeed_insights.triggers = {
"^/speed (https?://[%w-_%.%?%.:/%+=&]+)"
}
pagespeed_insights.doc = [[*
]]..config.cmd_pat..[[speed* _<Seiten-URL>_: Testet Geschwindigkeit der Seite mit PageSpeed Insights]]
end
local BASE_URL = 'https://www.googleapis.com/pagespeedonline/v2'
function pagespeed_insights:get_pagespeed(test_url)
local apikey = cred_data.google_apikey
local url = BASE_URL..'/runPagespeed?url='..test_url..'&key='..apikey..'&fields=id,ruleGroups(SPEED(score))'
local res,code = https.request(url)
if code ~= 200 then return "HTTP-FEHLER" end
local data = json.decode(res)
return data.id..' hat einen PageSpeed-Score von *'..data.ruleGroups.SPEED.score..' Punkten.*'
end
function pagespeed_insights:action(msg, config, matches)
utilities.send_typing(self, msg.chat.id, 'typing')
local text = pagespeed_insights:get_pagespeed(matches[1])
if not text then utilities.send_reply(self, msg, config.errors.connection) return end
utilities.send_reply(self, msg, text, true)
end
return pagespeed_insights

View File

@ -1,7 +1,7 @@
local pasteee = {}
local bot = require('otouto.bot')
local utilities = require('otouto.utilities')
local bot = require('miku.bot')
local utilities = require('miku.utilities')
function pasteee:init(config)
if not cred_data.pasteee_key then

120
miku/plugins/pixabay.lua Normal file
View File

@ -0,0 +1,120 @@
local pixabay = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
function pixabay:init(config)
if not cred_data.pixabay_apikey then
print('Missing config value: pixabay_apikey.')
print('pixabay.lua will not be enabled.')
return
end
pixabay.triggers = {
"^/pix(id) (%d+)",
"^/pix (.*)$",
"(pixabay.com).*%-(%d+)"
}
pixabay.doc = [[*
]]..config.cmd_pat..[[pix* _<Suchbegriff>_: Sendet lizenzfreies Bild]]
end
pixabay.command = 'pix <Suchbegriff>'
local BASE_URL = 'https://pixabay.com/api'
local apikey = cred_data.pixabay_apikey
function pixabay:get_pixabay_directlink(id)
local url = BASE_URL..'/?key='..apikey..'&lang=de&id='..id
local b,c = https.request(url)
if c ~= 200 then return nil end
local data = json.decode(b)
if data.totalHits == 0 then return 'NOPIX' end
local webformatURL = data.hits[1].webformatURL
local image_url = string.gsub(webformatURL, '_640.jpg', '_960.jpg')
-- Link to full, high resolution image
local preview_url = data.hits[1].previewURL
local image_code = string.sub(preview_url, 59)
local image_code = string.sub(image_code, 0, -9)
local full_url = 'https://pixabay.com/de/photos/download/'..image_code..'.jpg'
local user = data.hits[1].user
local tags = data.hits[1].tags
local page_url = data.hits[1].pageURL
-- cache this shit
local hash = 'telegram:cache:pixabay:'..id
print('Caching data in '..hash..' with timeout 1209600')
redis:hset(hash, 'image_url', image_url)
redis:hset(hash, 'full_url', full_url)
redis:hset(hash, 'page_url', page_url)
redis:hset(hash, 'user', user)
redis:hset(hash, 'tags', tags)
redis:expire(hash, 1209600) -- 1209600 = two weeks
return image_url, full_url, page_url, user, tags
end
function pixabay:get_pixabay(term)
local count = 70 -- how many pictures should be returned (3 to 200) NOTE: more pictures = higher load time
local url = BASE_URL..'/?key='..apikey..'&lang=de&safesearch=true&per_page='..count..'&image_type=photo&q='..term
local b,c = https.request(url)
if c ~= 200 then return nil end
local photo = json.decode(b)
if photo.totalHits == 0 then return 'NOPIX' end
local photos = photo.hits
-- truly randomize
math.randomseed(os.time())
-- random max json table size
local i = math.random(#photos)
local webformatURL = photos[i].webformatURL
local image_url = string.gsub(webformatURL, '_640.jpg', '_960.jpg')
-- Link to full, high resolution image
local preview_url = photos[i].previewURL
local image_code = string.sub(preview_url, 59)
local image_code = string.sub(image_code, 0, -9)
local full_url = 'https://pixabay.com/de/photos/download/'..image_code..'.jpg'
local user = photos[i].user
local tags = photos[i].tags
local page_url = photos[i].pageURL
return image_url, full_url, page_url, user, tags
end
function pixabay:action(msg, config, matches)
local term = matches[1]
if matches[2] then
if redis:exists("telegram:cache:pixabay:"..matches[2]) == true then -- if cached
local hash = 'telegram:cache:pixabay:'..matches[2]
url = redis:hget(hash, 'image_url')
full_url = redis:hget(hash, 'full_url')
page_url = redis:hget(hash, 'page_url')
user = redis:hget(hash, 'user')
tags = redis:hget(hash, 'tags')
else
url, full_url, page_url, user, tags = pixabay:get_pixabay_directlink(matches[2])
end
else
url, full_url, page_url, user, tags = pixabay:get_pixabay(term)
end
if url == 'NOPIX' then
utilities.send_reply(self, msg, config.errors.results)
return
else
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(url)
local text = '"'..tags..'" von '..user
utilities.send_photo(self, msg.chat.id, file, text, msg.message_id, '{"inline_keyboard":[[{"text":"Seite aufrufen","url":"'..page_url..'"},{"text":"Volles Bild (Login notwendig)","url":"'..full_url..'"}]]}')
return
end
end
return pixabay

View File

@ -0,0 +1,62 @@
local play_store = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
function play_store:init(config)
if not cred_data.x_mashape_key then
print('Missing config value: x_mashape_key.')
print('play_store.lua will not be enabled.')
return
end
play_store.triggers = {
"play.google.com/store/apps/details%?id=(.*)"
}
end
local BASE_URL = 'https://apps.p.mashape.com/google/application'
function play_store:get_playstore_data (appid)
local apikey = cred_data.x_mashape_key
local url = BASE_URL..'/'..appid..'?mashape-key='..apikey
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res).data
return data
end
function play_store:send_playstore_data(data)
local title = data.title
local developer = data.developer.id
local category = data.category.name
local rating = data.rating.average
local installs = data.performance.installs
local description = data.description
if data.version == "Varies with device" then
appversion = "variiert je nach Gerät"
else
appversion = data.version
end
if data.price == 0 then
price = "Gratis"
else
price = data.price
end
local text = '*'..title..'* von *'..developer..'* aus der Kategorie _'..category..'_, durschnittlich bewertet mit '..rating..' Sternen.\n_'..description..'_\n'..installs..' Installationen, Version '..appversion
return text
end
function play_store:action(msg, config, matches)
local appid = matches[1]
local data = play_store:get_playstore_data(appid)
if data == nil then
return
else
utilities.send_reply(self, msg, play_store:send_playstore_data(data), true)
return
end
end
return play_store

230
miku/plugins/plugins.lua Normal file
View File

@ -0,0 +1,230 @@
local plugin_manager = {}
local bot = require('miku.bot')
local bindings = require('miku.bindings')
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
function plugin_manager:init(config)
plugin_manager.triggers = {
"^/plugins$",
"^/plugins? (enable) ([%w_%.%-]+)$",
"^/plugins? (disable) ([%w_%.%-]+)$",
"^/plugins? (enable) ([%w_%.%-]+) (chat) (%d+)",
"^/plugins? (enable) ([%w_%.%-]+) (chat)",
"^/plugins? (disable) ([%w_%.%-]+) (chat) (%d+)",
"^/plugins? (disable) ([%w_%.%-]+) (chat)",
"^/plugins? (reload)$",
"^/(reload)$"
}
plugin_manager.doc = [[*
]]..config.cmd_pat..[[plugins*: Listet alle Plugins auf
*]]..config.cmd_pat..[[plugins* _enable/disable_ _<Plugin>_: Aktiviert/deaktiviert Plugin
*]]..config.cmd_pat..[[plugins* _enable/disable_ _<Plugin>_ chat: Aktiviert/deaktiviert Plugin im aktuellen Chat
*]]..config.cmd_pat..[[plugins* _enable/disable_ _<Plugin>_ _<chat#id>_: Aktiviert/deaktiviert Plugin in diesem Chat
*]]..config.cmd_pat..[[reload*: Lädt Plugins neu]]
end
plugin_manager.command = 'plugins <nur für Superuser>'
-- Returns the key (index) in the config.enabled_plugins table
function plugin_manager:plugin_enabled(name, chat)
for k,v in pairs(enabled_plugins) do
if name == v then
return k
end
end
-- If not found
return false
end
-- Returns true if file exists in plugins folder
function plugin_manager:plugin_exists(name)
for k,v in pairs(plugins_names()) do
if name..'.lua' == v then
return true
end
end
return false
end
function plugin_manager:list_plugins()
local text = ''
for k, v in pairs(plugins_names()) do
-- ✔ enabled, ❌ disabled
local status = ''
-- Check if is enabled
for k2, v2 in pairs(enabled_plugins) do
if v == v2..'.lua' then
status = ''
end
end
if not only_enabled or status == '' then
-- get the name
v = string.match (v, "(.*)%.lua")
text = text..v..' '..status..'\n'
end
end
return text
end
function plugin_manager:reload_plugins(self, config, plugin_name, status)
for pac, _ in pairs(package.loaded) do
if pac:match('^miku%.plugins%.') then
package.loaded[pac] = nil
end
end
package.loaded['miku.bindings'] = nil
package.loaded['miku.utilities'] = nil
package.loaded['config'] = nil
bot.init(self, config)
if plugin_name then
return 'Plugin '..plugin_name..' wurde '..status
else
return 'Plugins neu geladen'
end
end
function plugin_manager:enable_plugin(self, config, plugin_name)
print('checking if '..plugin_name..' exists')
-- Check if plugin is enabled
if plugin_manager:plugin_enabled(plugin_name) then
return 'Plugin '..plugin_name..' ist schon aktiviert'
end
-- Checks if plugin exists
if plugin_manager:plugin_exists(plugin_name) then
-- Add to redis set
redis:sadd('telegram:enabled_plugins', plugin_name)
print(plugin_name..' saved to redis set telegram:enabled_plugins')
-- Reload the plugins
return plugin_manager:reload_plugins(self, config, plugin_name, 'aktiviert')
else
return 'Plugin '..plugin_name..' existiert nicht'
end
end
function plugin_manager:disable_plugin(self, config, name, chat)
-- Check if plugins exists
if not plugin_manager:plugin_exists(name) then
return 'Plugin '..name..' existiert nicht'
end
local k = plugin_manager:plugin_enabled(name)
-- Check if plugin is enabled
if not k then
return 'Plugin '..name..' ist nicht aktiviert'
end
-- Disable and reload
redis:srem('telegram:enabled_plugins', name)
print(name..' saved to redis set telegram:enabled_plugins')
return plugin_manager:reload_plugins(self, config, name, 'deaktiviert')
end
function plugin_manager:disable_plugin_on_chat(msg, plugin)
if not plugin_manager:plugin_exists(plugin) then
return "Plugin existiert nicht!"
end
if not msg.chat then
hash = 'chat:'..msg..':disabled_plugins'
else
hash = get_redis_hash(msg, 'disabled_plugins')
end
local disabled = redis:hget(hash, plugin)
if disabled ~= 'true' then
print('Setting '..plugin..' in redis hash '..hash..' to true')
redis:hset(hash, plugin, true)
return 'Plugin '..plugin..' für diesen Chat deaktiviert.'
else
return 'Plugin '..plugin..' wurde für diesen Chat bereits deaktiviert.'
end
end
function plugin_manager:reenable_plugin_on_chat(msg, plugin)
if not plugin_manager:plugin_exists(plugin) then
return "Plugin existiert nicht!"
end
if not msg.chat then
hash = 'chat:'..msg..':disabled_plugins'
else
hash = get_redis_hash(msg, 'disabled_plugins')
end
local disabled = redis:hget(hash, plugin)
if disabled == nil then return 'Es gibt keine deaktivierten Plugins für disen Chat.' end
if disabled == 'true' then
print('Setting '..plugin..' in redis hash '..hash..' to false')
redis:hset(hash, plugin, false)
return 'Plugin '..plugin..' wurde für diesen Chat reaktiviert.'
else
return 'Plugin '..plugin..' ist nicht deaktiviert.'
end
end
function plugin_manager:action(msg, config, matches)
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
-- Show the available plugins
if matches[1] == '/plugins' then
utilities.send_reply(self, msg, plugin_manager:list_plugins())
return
end
-- Reenable a plugin for this chat
if matches[1] == 'enable' and matches[3] == 'chat' then
local plugin = matches[2]
if matches[4] then
local id = matches[4]
print("enable "..plugin..' on chat#id'..id)
utilities.send_reply(self, msg, plugin_manager:reenable_plugin_on_chat(id, plugin))
return
else
print("enable "..plugin..' on this chat')
utilities.send_reply(self, msg, plugin_manager:reenable_plugin_on_chat(msg, plugin))
return
end
end
-- Enable a plugin
if matches[1] == 'enable' then
local plugin_name = matches[2]
print("enable: "..matches[2])
utilities.send_reply(self, msg, plugin_manager:enable_plugin(self, config, plugin_name))
return
end
-- Disable a plugin on a chat
if matches[1] == 'disable' and matches[3] == 'chat' then
local plugin = matches[2]
if matches[4] then
local id = matches[4]
print("disable "..plugin..' on chat#id'..id)
utilities.send_reply(self, msg, plugin_manager:disable_plugin_on_chat(id, plugin))
return
else
print("disable "..plugin..' on this chat')
utilities.send_reply(self, msg, plugin_manager:disable_plugin_on_chat(msg, plugin))
return
end
end
-- Disable a plugin
if matches[1] == 'disable' then
print("disable: "..matches[2])
utilities.send_reply(self, msg, plugin_manager:disable_plugin(self, config, matches[2]))
return
end
-- Reload all the plugins!
if matches[1] == 'reload' then
utilities.send_reply(self, msg, plugin_manager:reload_plugins(self, config))
return
end
end
return plugin_manager

149
miku/plugins/pocket.lua Normal file
View File

@ -0,0 +1,149 @@
local pocket = {}
local https = require('ssl.https')
local URL = require('socket.url')
local redis = (loadfile "./miku/redis.lua")()
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function pocket:init(config)
if not cred_data.pocket_consumer_key then
print('Missing config value: pocket_consumer_key.')
print('pocket.lua will not be enabled.')
return
end
pocket.triggers = {
"^/pocket(set)(.+)$",
"^/pocket (add) (https?://.*)$",
"^/pocket (archive) (%d+)$",
"^/pocket (readd) (%d+)$",
"^/pocket (unfavorite) (%d+)$",
"^/pocket (favorite) (%d+)$",
"^/pocket (delete) (%d+)$",
"^/pocket (unauth)$",
"^/pocket$"
}
pocket.doc = [[*
]]..config.cmd_pat..[[pocket*: Postet Liste deiner Links
*]]..config.cmd_pat..[[pocket* add _(url)_: Fügt diese URL deiner Liste hinzu
*]]..config.cmd_pat..[[pocket* archive _[id]_: Archiviere diesen Eintrag
*]]..config.cmd_pat..[[pocket* readd _[id]_: De-archiviere diesen Eintrag
*]]..config.cmd_pat..[[pocket* favorite _[id]_: Favorisiere diesen Eintrag
*]]..config.cmd_pat..[[pocket* unfavorite _[id]_: Entfavorisiere diesen Eintrag
*]]..config.cmd_pat..[[pocket* delete _[id]_: Lösche diesen Eintrag
*]]..config.cmd_pat..[[pocket* unauth: Löscht deinen Account aus dem Bot]]
end
pocket.command = 'pocket <siehe `/hilfe pocket`>'
local BASE_URL = 'https://getpocket.com/v3'
local consumer_key = cred_data.pocket_consumer_key
local headers = {
["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF8",
["X-Accept"] = "application/json"
}
function pocket:set_pocket_access_token(hash, access_token)
if string.len(access_token) ~= 30 then return '*Inkorrekter Access-Token*' end
print('Setting pocket in redis hash '..hash..' to users access_token')
redis:hset(hash, 'pocket', access_token)
return '*Authentifizierung abgeschlossen!*\nDas Plugin kann jetzt verwendet werden.'
end
function pocket:list_pocket_items(access_token)
local items = post_petition(BASE_URL..'/get', 'consumer_key='..consumer_key..'&access_token='..access_token..'&state=unread&sort=newest&detailType=simple', headers)
if items.status == 2 then return 'Keine Elemente eingespeichert.' end
if items.status ~= 1 then return 'Ein Fehler beim Holen der Elemente ist aufgetreten.' end
local text = ''
for element in pairs(items.list) do
title = items.list[element].given_title
if not title or title == "" then title = items.list[element].resolved_title end
text = text..'#'..items.list[element].item_id..': '..title..'\n'..items.list[element].resolved_url..'\n\n'
end
return text
end
function pocket:add_pocket_item(access_token, url)
local result = post_petition(BASE_URL..'/add', 'consumer_key='..consumer_key..'&access_token='..access_token..'&url='..url, headers)
if result.status ~= 1 then return 'Ein Fehler beim Hinzufügen der URL ist aufgetreten :(' end
local given_url = result.item.given_url
if result.item.title == "" or not result.item.title then
title = 'Seite'
else
title = '"'..result.item.title..'"'
end
local code = result.item.response_code
local text = title..' ('..given_url..') hinzugefügt!'
if code ~= "200" and code ~= "0" then text = text..'\nAber die Seite liefert Fehler '..code..' zurück.' end
return text
end
function pocket:modify_pocket_item(access_token, action, id)
local result = post_petition(BASE_URL..'/send', 'consumer_key='..consumer_key..'&access_token='..access_token..'&actions=[{"action":"'..action..'","item_id":'..id..'}]', headers)
if result.status ~= 1 then return 'Ein Fehler ist aufgetreten :(' end
if action == 'readd' then
if result.action_results[1] == false then
return 'Dieser Eintrag existiert nicht!'
end
local url = result.action_results[1].normal_url
return url..' wieder de-archiviert'
end
if result.action_results[1] == true then
return 'Aktion ausgeführt.'
else
return 'Ein Fehler ist aufgetreten.'
end
end
function pocket:action(msg, config, matches)
local hash = 'user:'..msg.from.id
local access_token = redis:hget(hash, 'pocket')
if matches[1] == 'set' then
local access_token = matches[2]
utilities.send_reply(self, msg, pocket:set_pocket_access_token(hash, access_token), true)
local message_id = redis:hget(hash, 'pocket_login_msg')
utilities.edit_message(self, msg.chat.id, message_id, '*Anmeldung abgeschlossen!*', true, true)
redis:hdel(hash, 'pocket_login_msg')
return
end
if not access_token then
local result = utilities.send_reply(self, msg, '*Bitte authentifiziere dich zuerst, indem du dich anmeldest.*', true, '{"inline_keyboard":[[{"text":"Bei Pocket anmelden","url":"https://brawlbot.tk/apis/callback/pocket/connect.php"}]]}')
redis:hset(hash, 'pocket_login_msg', result.result.message_id)
return
end
if matches[1] == 'unauth' then
redis:hdel(hash, 'pocket')
utilities.send_reply(self, msg, 'Erfolgreich ausgeloggt! Du kannst den Zugriff [in deinen Einstellungen](https://getpocket.com/connected_applications) endgültig entziehen.', true)
return
end
if matches[1] == 'add' then
utilities.send_reply(self, msg, pocket:add_pocket_item(access_token, matches[2]))
return
end
if matches[1] == 'archive' or matches[1] == 'delete' or matches[1] == 'readd' or matches[1] == 'favorite' or matches[1] == 'unfavorite' then
utilities.send_reply(self, msg, pocket:modify_pocket_item(access_token, matches[1], matches[2]))
return
end
if msg.chat.type == 'chat' or msg.chat.type == 'supergroup' then
utilities.send_reply(self, msg, 'Ausgeben deiner privaten Pocket-Liste in einem öffentlichen Chat wird feige verweigert. Bitte schreibe mich privat an!', true)
return
else
utilities.send_reply(self, msg, pocket:list_pocket_items(access_token))
return
end
end
return pocket

View File

@ -2,8 +2,8 @@ local pokedex = {}
local HTTP = require('socket.http')
local JSON = require('dkjson')
local bindings = require('otouto.bindings')
local utilities = require('otouto.utilities')
local bindings = require('miku.bindings')
local utilities = require('miku.utilities')
pokedex.command = 'pokedex <query>'

View File

@ -1,7 +1,7 @@
local preview = {}
local HTTP = require('socket.http')
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
preview.command = 'preview <link>'

86
miku/plugins/qr.lua Normal file
View File

@ -0,0 +1,86 @@
local qr = {}
local http = require('socket.http')
local URL = require('socket.url')
local utilities = require('miku.utilities')
function qr:init(config)
qr.triggers = {
'^/qr "(%w+)" "(%w+)" (.+)$',
"^/qr (.+)$"
}
qr.doc = [[*
]]..config.cmd_pat..[[qr* _<Text>_: Sendet QR-Code mit diesem Text
*]]..config.cmd_pat..[[qr* _"[Hintergrundfarbe]"_ _"[Datenfarbe]"_ _[Text]_
Farbe mit Text: red|green|blue|purple|black|white|gray
Farbe als HEX: ("a56729" ist braun)
oder Farbe als Dezimalwert: ("255-192-203" ist pink)]]
end
qr.command = 'qr <Text>'
function qr:get_hex(str)
local colors = {
red = "f00",
blue = "00f",
green = "0f0",
yellow = "ff0",
purple = "f0f",
white = "fff",
black = "000",
gray = "ccc"
}
for color, value in pairs(colors) do
if color == str then
return value
end
end
return str
end
function qr:qr(text, color, bgcolor)
local url = "http://api.qrserver.com/v1/create-qr-code/?"
.."size=600x600" --fixed size otherways it's low detailed
.."&data="..URL.escape(utilities.trim(text))
if color then
url = url.."&color="..qr:get_hex(color)
end
if bgcolor then
url = url.."&bgcolor="..qr:get_hex(bgcolor)
end
local response, code, headers = http.request(url)
if code ~= 200 then
return nil
end
if #response > 0 then
return url
end
return nil
end
function qr:action(msg, config, matches)
local text = matches[1]
local color
local back
if #matches > 1 then
text = matches[3]
color = matches[2]
back = matches[1]
end
local image_url = qr:qr(text, color, back)
if not image_url then utilities.send_reply(self, msg, config.errors.connection) return end
local file = download_to_file(image_url, 'qr.png')
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
return qr

View File

@ -1,9 +1,9 @@
local quotes = {}
local bot = require('otouto.bot')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
require("./otouto/plugins/pasteee")
local bot = require('miku.bot')
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
require("./miku/plugins/pasteee")
function quotes:init(config)
quotes.triggers = {
@ -75,7 +75,7 @@ function quotes:list_quotes(msg)
text = text..num..") "..quote..'\n'
end
if not text or text == "" then
return 'Es wurden noch keine Zitate gespeichert.\nSpeichere doch welche mit !addquote [Zitat]'
return '*Es wurden noch keine Zitate gespeichert.*\nSpeichere doch welche mit `/addquote [Zitat]`', true
else
return upload(text)
end
@ -99,10 +99,10 @@ function quotes:action(msg, config, matches)
elseif matches[1] == "listquotes" then
local link, iserror = quotes:list_quotes(msg)
if iserror then
utilities.send_reply(self, msg, link)
utilities.send_reply(self, msg, link, true)
return
end
utilities.send_reply(self, msg, '[Lise aller Zitate auf Paste.ee ansehen]('..link..')', true)
utilities.send_reply(self, msg, 'Ich habe eine Liste aller Zitate hochgeladen.', false, '{"inline_keyboard":[[{"text":"Alle Zitate abrufen","url":"'..link..'"}]]}')
return
end
utilities.send_reply(self, msg, quotes.doc, true)

67
miku/plugins/random.lua Normal file
View File

@ -0,0 +1,67 @@
local fun = {}
local utilities = require('miku.utilities')
function fun:init(config)
fun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('random', true).table
fun.doc = [[*
]]..config.cmd_pat..[[random* _<Username>_: Schau, was passiert!]]
end
fun.command = 'random <Username>'
function fun:choose_random(user_name, other_user)
randoms = {
user_name..' schlägt '..other_user..' mit einem stinkenden Fisch.',
user_name..' versucht, '..other_user..' mit einem Messer zu töten, bringt sich dabei aber selbst um.',
user_name..' versucht, '..other_user..' mit einem Messer zu töten, stolpert aber und schlitzt sich dabei das Knie auf.',
user_name..' ersticht '..other_user..'.',
user_name..' tritt '..other_user..'.',
user_name..' hat '..other_user..' umgebracht! Möge er in der Hölle schmoren!',
user_name..' hat die Schnauze voll von '..other_user..' und sperrt ihn in einen Schrank.',
user_name..' erwürgt '..other_user..'. BILD sprach als erstes mit der Hand.',
user_name..' schickt '..other_user..' nach /dev/null.',
user_name..' umarmt '..other_user..'.',
user_name..' verschenkt eine Kartoffel an '..other_user..'.',
user_name..' melkt '..other_user..'. *muuh* :D',
user_name..' wirft einen Gameboy auf '..other_user..'.',
user_name..' hetzt die NSA auf '..other_user..'.',
user_name..' ersetzt alle CDs von '..other_user..' durch Nickelback-CDs.',
other_user..' melkt '..user_name..'. *muuh* :D',
user_name..' ist in '..other_user..' verliebt.',
user_name..' schmeißt '..other_user..' in einen Fluss.',
user_name..' klaut '..other_user..' einen Lolli.',
user_name..' hätte gern Sex mit '..other_user..'.',
user_name..' schenkt '..other_user..' ein Foto von seinem Penis.',
user_name..' dreht durch und wirft '..other_user..' in einen Häcksler.',
user_name..' gibt '..other_user..' einen Keks.',
user_name..' lacht '..other_user..' aus.',
user_name..' gibt '..other_user..[[ ganz viel Liebe. ( ͡° ͜ʖ ͡°)]],
user_name..' lädt '..other_user..' zum Essen ein.',
user_name..' schwatzt '..other_user..' Ubuntu auf.',
user_name..' fliegt mit '..other_user..' nach Hawaii.',
user_name..' küsst '..other_user..' leidenschaftlich.'
}
math.randomseed(os.time())
math.randomseed(os.time())
local random = math.random(#randoms)
return randoms[random]
end
function fun:action(msg, config, matches)
local input = utilities.input(msg.text)
if not input then
if msg.reply_to_message and msg.reply_to_message.text then
input = msg.reply_to_message.text
else
utilities.send_message(self, msg.chat.id, fun.doc, true, msg.message_id, true)
return
end
end
local user_name = get_name(msg)
local result = fun:choose_random(user_name, input)
utilities.send_message(self, msg.chat.id, result)
end
return fun

View File

@ -1,19 +1,16 @@
local reddit = {}
local HTTP = require('socket.http')
local https = require('ssl.https')
local URL = require('socket.url')
local JSON = require('dkjson')
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
reddit.command = 'reddit [r/subreddit | query]'
reddit.command = 'reddit [r/subreddit | Suchbegriff]'
function reddit:init(config)
reddit.triggers = utilities.triggers(self.info.username, config.cmd_pat, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table
reddit.doc = [[```
]]..config.cmd_pat..[[reddit [r/subreddit | query]
Returns the top posts or results for a given subreddit or query. If no argument is given, returns the top posts from r/all. Querying specific subreddits is not supported.
Aliases: ]]..config.cmd_pat..[[r, /r/subreddit
```]]
reddit.doc = [[*
]]..config.cmd_pat..[[r* _[r/subreddit | Suchbegriff]_: Gibt Top-Posts oder Ergebnisse eines Subreddits aus. Wenn kein Argument gegeben ist, wird /r/all genommen.]]
end
local format_results = function(posts)
@ -25,8 +22,8 @@ local format_results = function(posts)
title = title:sub(1, 253)
title = utilities.trim(title) .. '...'
end
local short_url = 'redd.it/' .. post.id
local s = '[' .. title .. '](' .. short_url .. ')'
local short_url = 'https://redd.it/' .. post.id
local s = '[' .. unescape(title) .. '](' .. short_url .. ')'
if post.domain and not post.is_self and not post.over_18 then
s = '`[`[' .. post.domain .. '](' .. post.url:gsub('%)', '\\)') .. ')`]` ' .. s
end
@ -35,9 +32,9 @@ local format_results = function(posts)
return output
end
reddit.subreddit_url = 'http://www.reddit.com/%s/.json?limit='
reddit.search_url = 'http://www.reddit.com/search.json?q=%s&limit='
reddit.rall_url = 'http://www.reddit.com/.json?limit='
reddit.subreddit_url = 'https://www.reddit.com/%s/.json?limit='
reddit.search_url = 'https://www.reddit.com/search.json?q=%s&limit='
reddit.rall_url = 'https://www.reddit.com/.json?limit='
function reddit:action(msg, config)
-- Eight results in PM, four results elsewhere.
@ -59,7 +56,7 @@ function reddit:action(msg, config)
source = '*/' .. utilities.md_escape(input) .. '*\n'
else
input = utilities.input(msg.text)
source = '*Results for* _' .. utilities.md_escape(input) .. '_ *:*\n'
source = '*Ergebnisse für* _' .. utilities.md_escape(input) .. '_ *:*\n'
input = URL.escape(input)
url = reddit.search_url:format(input) .. limit
end
@ -67,7 +64,7 @@ function reddit:action(msg, config)
url = reddit.rall_url .. limit
source = '*/r/all*\n'
end
local jstr, res = HTTP.request(url)
local jstr, res = https.request(url)
if res ~= 200 then
utilities.send_reply(self, msg, config.errors.connection)
else

View File

@ -0,0 +1,55 @@
local reddit_post = {}
local https = require('ssl.https')
local URL = require('socket.url')
local json = require('dkjson')
local utilities = require('miku.utilities')
reddit_post.triggers = {
"reddit.com/r/([A-Za-z0-9-/-_-.]+)/comments/([A-Za-z0-9-/-_-.]+)"
}
local BASE_URL = 'https://www.reddit.com'
function reddit_post:get_reddit_data(subreddit, reddit_code)
local url = BASE_URL..'/r/'..subreddit..'/comments/'..reddit_code..'.json'
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
return data
end
function reddit_post:send_reddit_data(data)
local title = utilities.md_escape(data[1].data.children[1].data.title)
local author = utilities.md_escape(data[1].data.children[1].data.author)
local subreddit = utilities.md_escape(data[1].data.children[1].data.subreddit)
if string.len(data[1].data.children[1].data.selftext) > 300 then
selftext = string.sub(unescape(data[1].data.children[1].data.selftext:gsub("%b<>", "")), 1, 300) .. '...'
else
selftext = unescape(data[1].data.children[1].data.selftext:gsub("%b<>", ""))
end
if not data[1].data.children[1].data.is_self then
url = data[1].data.children[1].data.url
else
url = ''
end
local score = comma_value(data[1].data.children[1].data.score)
local comments = comma_value(data[1].data.children[1].data.num_comments)
local text = '*'..author..'* in */r/'..subreddit..'* _('..score..' Upvotes - '..comments..' Kommentare)_:\n'..title..'\n'..selftext..url
return text
end
function reddit_post:action(msg, config, matches)
local subreddit = matches[1]
local reddit_code = matches[2]
local data = reddit_post:get_reddit_data(subreddit, reddit_code)
if not data then utilities.send_reply(self, msg, config.errors.connection) return end
local text = reddit_post:send_reddit_data(data)
if not text then utilities.send_reply(self, msg, config.errors.connection) return end
utilities.send_reply(self, msg, text, true)
end
return reddit_post

View File

@ -1,17 +1,15 @@
local remind = {}
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
remind.command = 'remind <duration> <message>'
remind.command = 'remind <Länge> <Nachricht>'
function remind:init(config)
self.database.reminders = self.database.reminders or {}
remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table
remind.doc = [[```
]]..config.cmd_pat..[[remind <duration> <message>
Repeats a message after a duration of time, in minutes.
```]]
remind.doc = [[*
]]..config.cmd_pat..[[remind* _<Länge>_ _<Nachricht>_: Erinnert dich in X Minuten an die Nachricht]]
end
function remind:action(msg)
@ -44,10 +42,10 @@ function remind:action(msg)
self.database.reminders[msg.chat.id_str] = self.database.reminders[msg.chat.id_str] or {}
-- Limit group reminders to 10 and private reminders to 50.
if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 9 then
utilities.send_reply(self, msg, 'Sorry, this group already has ten reminders.')
utilities.send_reply(self, msg, 'Diese Gruppe hat schon zehn Erinnerungen!')
return
elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 49 then
utilities.send_reply(msg, 'Sorry, you already have fifty reminders.')
utilities.send_reply(msg, 'Du hast schon 50 Erinnerungen!')
return
end
-- Put together the reminder with the expiration, message, and message to reply to.
@ -56,11 +54,11 @@ function remind:action(msg)
message = message
}
table.insert(self.database.reminders[msg.chat.id_str], reminder)
local output = 'I will remind you in ' .. duration
local output = 'Ich werde dich in ' .. duration
if duration == 1 then
output = output .. ' minute!'
output = output .. ' Minute erinnern!'
else
output = output .. ' minutes!'
output = output .. ' Minuten erinnern!'
end
utilities.send_reply(self, msg, output)
end
@ -75,7 +73,7 @@ function remind:cron()
-- If the reminder is past-due, send it and nullify it.
-- Otherwise, add it to the replacement table.
if time > reminder.time then
local output = '*Reminder:*\n"' .. utilities.md_escape(reminder.message) .. '"'
local output = '*ERINNERUNG:*\n"' .. utilities.md_escape(reminder.message) .. '"'
local res = utilities.send_message(self, chat_id, output, true, nil, true)
-- If the message fails to send, save it for later.
if not res then
@ -94,4 +92,4 @@ function remind:cron()
end
end
return remind
return remind

View File

@ -1,8 +1,8 @@
local respond = {}
local https = require('ssl.https')
local utilities = require('otouto.utilities')
local bindings = require('otouto.bindings')
local utilities = require('miku.utilities')
local bindings = require('miku.bindings')
function respond:init(config)
respond.triggers = {

View File

@ -1,6 +1,6 @@
local roll = {}
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
roll.command = 'roll'

View File

@ -3,19 +3,30 @@ local rss = {}
local http = require('socket.http')
local https = require('ssl.https')
local url = require('socket.url')
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local bindings = require('miku.bindings')
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
local feedparser = require("feedparser")
rss.command = 'rss <sub/del>'
function rss:init(config)
rss.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rss', true).table
rss.triggers = {
"^/(rss) @(.*)$",
"^/rss$",
"^/rss (sub) (https?://[%w-_%.%?%.:/%+=&%~]+) @(.*)$",
"^/rss (sub) (https?://[%w-_%.%?%.:/%+=&%~]+)$",
"^/rss (del) (%d+) @(.*)$",
"^/rss (del) (%d+)$",
"^/rss (del)",
"^/rss (sync)$"
}
rss.doc = [[*
]]..config.cmd_pat..[[rss*: Feed-Abonnements anzeigen
*]]..config.cmd_pat..[[rss* _sub_ _<URL>_: Diesen Feed abonnieren
*]]..config.cmd_pat..[[rss* _del_ _<#>_: Diesen Feed deabonnieren
*]]..config.cmd_pat..[[rss* _sync_: Feeds syncen (nur Superuser)]]
]]..config.cmd_pat..[[rss* _@[Kanalname]_: Feed-Abonnements anzeigen
*]]..config.cmd_pat..[[rss* _sub_ _<URL>_ _@[Kanalname]_: Diesen Feed abonnieren
*]]..config.cmd_pat..[[rss* _del_ _<#>_ _@[Kanalname]_: Diesen Feed deabonnieren
*]]..config.cmd_pat..[[rss* _sync_: Feeds syncen (nur Superuser)
Der Kanalname ist optional]]
end
function tail(n, k)
@ -193,58 +204,106 @@ function rss:print_subs(id, chat_name)
if not subs[1] then
return 'Keine Feeds abonniert!'
end
local keyboard = '{"keyboard":[['
local keyboard_buttons = ''
local text = '*'..chat_name..'* hat abonniert:\n---------\n'
for k,v in pairs(subs) do
text = text .. k .. ") " .. v .. '\n'
if k == #subs then
keyboard_buttons = keyboard_buttons..'{"text":"/rss del '..k..'"}'
break;
end
keyboard_buttons = keyboard_buttons..'{"text":"/rss del '..k..'"},'
end
return text
local keyboard = keyboard..keyboard_buttons..']], "one_time_keyboard":true, "selective":true, "resize_keyboard":true}'
return text, keyboard
end
function rss:action(msg, config)
local input = utilities.input(msg.text)
function rss:action(msg, config, matches)
local id = "user#id" .. msg.from.id
if msg.chat.type == 'channel' then
print('Kanäle werden momentan nicht unterstützt')
end
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
id = 'chat#id'..msg.chat.id
end
if not input then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
chat_name = msg.chat.title
else
chat_name = msg.chat.first_name
-- For channels
if matches[1] == 'sub' and matches[2] and matches[3] then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local id = '@'..matches[3]
local result = utilities.get_chat_info(self, id)
if not result then
utilities.send_reply(self, msg, 'Diesen Kanal gibt es nicht!')
return
end
local output = rss:subscribe(id, matches[2])
utilities.send_reply(self, msg, output, true)
return
elseif matches[1] == 'del' and matches[2] and matches[3] then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local id = '@'..matches[3]
local result = utilities.get_chat_info(self, id)
if not result then
utilities.send_reply(self, msg, 'Diesen Kanal gibt es nicht!')
return
end
local output = rss:unsubscribe(id, matches[2])
utilities.send_reply(self, msg, output, true)
return
elseif matches[1] == 'rss' and matches[2] then
local id = '@'..matches[2]
local result = utilities.get_chat_info(self, id)
if not result then
utilities.send_reply(self, msg, 'Diesen Kanal gibt es nicht!')
return
end
local chat_name = result.result.title
local output = rss:print_subs(id, chat_name)
utilities.send_reply(self, msg, output, true)
return
end
if input:match('(sub) (https?://[%w-_%.%?%.:/%+=&%~]+)$') then
if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then
chat_name = msg.chat.title
else
chat_name = msg.chat.first_name
end
if matches[1] == 'sub' and matches[2] then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local rss_url = input:match('(https?://[%w-_%.%?%.:/%+=&%~]+)$')
local output = rss:subscribe(id, rss_url)
local output = rss:subscribe(id, matches[2])
utilities.send_reply(self, msg, output, true)
elseif input:match('(del) (%d+)$') then
return
elseif matches[1] == 'del' and matches[2] then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local rss_url = input:match('(%d+)$')
local output = rss:unsubscribe(id, rss_url)
utilities.send_reply(self, msg, output, true)
elseif input:match('(sync)$') then
local output = rss:unsubscribe(id, matches[2])
utilities.send_reply(self, msg, output, true, '{"hide_keyboard":true}')
return
elseif matches[1] == 'del' and not matches[2] then
local list_subs, keyboard = rss:print_subs(id, chat_name)
utilities.send_reply(self, msg, list_subs, true, keyboard)
return
elseif matches[1] == 'sync' then
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
rss:cron(self)
return
end
local output = rss:print_subs(id, chat_name)
utilities.send_reply(self, msg, output, true)
return
end
@ -285,7 +344,7 @@ function rss:cron(self_plz)
else
content = ''
end
text = text..'\n*'..title..'*\n'..content..' [Weiterlesen]('..link..')\n'
text = text..'\n[[#RSS]] *'..title..'*\n'..utilities.trim(utilities.markdown_escape_simple(content))..' [Weiterlesen]('..link..')\n'
end
if text ~= '' then
local newlast = newentr[1].id
@ -299,4 +358,4 @@ function rss:cron(self_plz)
end
end
return rss
return rss

View File

@ -1,7 +1,7 @@
local set = {}
local utilities = require('otouto.utilities')
local redis = (loadfile "./otouto/redis.lua")()
local utilities = require('miku.utilities')
local redis = (loadfile "./miku/redis.lua")()
set.command = 'set <Variable> <Wert>'
@ -35,7 +35,7 @@ end
function set:action(msg)
local input = utilities.input(msg.text)
if not input:match('([^%s]+) (.+)') then
if not input or not input:match('([^%s]+) (.+)') then
utilities.send_message(self, msg.chat.id, set.doc, true, msg.message_id, true)
return
end

View File

@ -1,6 +1,6 @@
local shell = {}
local utilities = require('otouto.utilities')
local utilities = require('miku.utilities')
function shell:init(config)
shell.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('sh', true).table
@ -10,6 +10,7 @@ function shell:action(msg, config)
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
return
end
local input = utilities.input(msg.text)

View File

@ -0,0 +1,28 @@
local site_header = {}
local utilities = require('miku.utilities')
function site_header:init(config)
site_header.triggers = {
"^/(head) ([%w-_%.%?%.:,/%+=&#!]+)$",
"^/(dig) ([%w-_%.%?%.:,/%+=&#!]+)$"
}
end
function site_header:action(msg, config, matches)
if msg.from.id ~= config.admin then
utilities.send_reply(self, msg, config.errors.sudo)
end
local url = matches[2]
if matches[1] == 'head' then
input = 'curl --head '..url
elseif matches[1] == 'dig' then
input = 'dig '..url..' ANY'
end
local output = io.popen(input):read('*all')
output = '```\n' .. output .. '\n```'
utilities.send_reply(self, msg, output, true)
end
return site_header

View File

@ -0,0 +1,41 @@
local soundcloud = {}
local http = require('socket.http')
local json = require('dkjson')
local utilities = require('miku.utilities')
soundcloud.triggers = {
"soundcloud.com/([A-Za-z0-9-/-_-.]+)"
}
local BASE_URL = 'http://api.soundcloud.com/resolve.json'
local client_id = cred_data.soundcloud_client_id
function soundcloud:send_soundcloud_info(sc_url)
local url = BASE_URL..'?url=http://soundcloud.com/'..sc_url..'&client_id='..client_id
local res,code = http.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
local title = data.title
local description = data.description
local user = data.user.username
local user = 'Unbekannt'
local genre = data.genre
local playback_count = data.playback_count
local milliseconds = data.duration
local totalseconds = math.floor(milliseconds / 1000)
local duration = makeHumanTime(totalseconds)
local text = '*'..title..'* von _'..user..'_\n_(Tag: '..genre..', '..duration..'; '..playback_count..' mal angehört)_\n'..description
return text
end
function soundcloud:action(msg, config, matches)
local text = soundcloud:send_soundcloud_info(matches[1])
if not text then utilities.send_reply(self, msg, config.errors.connection) return end
utilities.send_reply(self, msg, text, true)
end
return soundcloud

View File

@ -0,0 +1,17 @@
local speedtest = {}
local utilities = require('miku.utilities')
speedtest.triggers = {
"speedtest.net/my%-result/(%d+)",
"speedtest.net/my%-result/i/(%d+)"
}
function speedtest:action(msg, config, matches)
local url = 'http://www.speedtest.net/result/'..matches[1]..'.png'
utilities.send_typing(self, msg.chat.id, 'upload_photo')
local file = download_to_file(url)
utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id)
end
return speedtest

52
miku/plugins/spotify.lua Normal file
View File

@ -0,0 +1,52 @@
local spotify = {}
local https = require('ssl.https')
local json = require('dkjson')
local utilities = require('miku.utilities')
spotify.triggers = {
"open.spotify.com/track/([A-Za-z0-9-]+)",
"play.spotify.com/track/([A-Za-z0-9-]+)"
}
local BASE_URL = 'https://api.spotify.com/v1'
function spotify:get_track_data(track)
local url = BASE_URL..'/tracks/'..track
local res,code = https.request(url)
if code ~= 200 then return nil end
local data = json.decode(res)
return data
end
function spotify:send_track_data(data, self, msg)
local name = data.name
local album = data.album.name
local artist = data.artists[1].name
local preview = data.preview_url
local milliseconds = data.duration_ms
-- convert s to mm:ss
local totalseconds = math.floor(milliseconds / 1000)
local duration = makeHumanTime(totalseconds)
local text = '*'..name..'* von *'..artist..'* aus dem Album *'..album..'* _('..duration..')_'
if preview then
utilities.send_typing(self, msg.chat.id, 'upload_audio')
local file = download_to_file(preview, name..'.mp3')
utilities.send_audio(self, msg.chat.id, file, msg.message_id, totalseconds, artist, name)
return
else
utilities.send_reply(self, msg, text, true)
return
end
end
function spotify:action(msg, config, matches)
local data = spotify:get_track_data(matches[1])
if not data then utilities.send_reply(self, msg, config.errors.connection) return end
spotify:send_track_data(data, self, msg)
return
end
return spotify

Some files were not shown because too many files have changed in this diff Show More