| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- #include <sys/stat.h>
- #include <pngwriter.h>
- #include "instaserv.h"
- static ServiceReference<InstaServCore> instaServ("InstaServService", "InstaServ");
- class ISCreate;
- struct RGB
- {
- RGB();
- RGB(long value);
- int r, g, b;
- };
- struct ARGB: public RGB
- {
- ARGB();
- ARGB(long value);
- double a;
- };
- RGB::RGB()
- {
- r = g = b = 0;
- }
- RGB::RGB(long value)
- {
- const double factor = 65535.d / 255.d;
- r = ((unsigned char)(value >> 16)) * factor;
- g = ((unsigned char) (value >> 8)) * factor;
- b = ((unsigned char) value) * factor;
- }
- ARGB::ARGB()
- {
- a = r = g = b = 0;
- }
- ARGB::ARGB(long value): RGB(value)
- {
- a = ((unsigned char)(value >> 24) / 255.d);
- }
- struct TextItem {
- std::string text;
- int px;
- int py;
- int width;
- int height;
- TextItem(pngwriter &writer, pngwriterfont& font, const std::string& str, int strFrom, int strTo, int px, int py, int fontSize)
- {
- text = str.substr(strFrom, strTo -strFrom);
- this->px = px;
- this->py = py;
- width = writer.get_text_width_utf8(font, fontSize, str.c_str());
- height = fontSize;
- }
- };
- class TextLines
- {
- public:
- TextLines(char _fontSize): fontSize(_fontSize), lineSpacing(_fontSize *.55d){}
- void push_back(const TextItem& line)
- {
- lines.push_back(line);
- height += line.height;
- }
- std::vector<TextItem> lines;
- int getHeight() { return height + (lines.empty() ? 0 : (lineSpacing*(lines.size() -1))); }
- int getFontSize() { return fontSize; }
- int getLineSpacing() { return lineSpacing; }
- private:
- int height = {};
- const int lineSpacing;
- const int fontSize;
- };
- class ImageWriter
- {
- public:
- ImageWriter& SetBackground(const Anope::string& path);
- ImageWriter& SetText(const std::vector<Anope::string>& text);
- ImageWriter& SetQuotePosition(size_t top, size_t left, size_t bottom, size_t right);
- ImageWriter& SetOutputPath(const Anope::string& filename);
- ImageWriter& SetBgColor(long);
- ImageWriter& SetFgColor(long);
- ImageWriter& SetFontSize(char, char);
- ImageWriter& SetOffsetTop(long);
- ImageWriter& SetFontPath(const Anope::string &);
- bool Build(User* u);
- protected:
- Anope::string background;
- std::vector<Anope::string> text;
- Anope::string outputName;
- ARGB bgColor;
- RGB fgColor;
- long offsetTop;
- size_t top;
- size_t left;
- size_t bottom;
- size_t right;
- char minFontSize;
- char maxFontSize;
- Anope::string fontPath;
- void WriteText(pngwriter &writer, pngwriterfont &font, char fontSize, const TextItem& line, int offsetY, const RGB &rgb) const;
- bool WriteDryRun(pngwriter &writer, pngwriterfont& font, char fontSize, const Anope::string &str, TextLines& lines, const RGB &rgb);
- TextLines *WriteDryRun(pngwriter &writer, pngwriterfont& font, char minFontSize, char maxFontSize, const std::vector<Anope::string>& lines, const RGB &rgb);
- int GetTextLength(pngwriter &writer, pngwriterfont &font, char fontSize, const std::string &str, int maxWidth) const;
- };
- ImageWriter& ImageWriter::SetFontSize(char min, char max)
- {
- Log(LOG_DEBUG) << "Font size: [" << (unsigned) min << "; " << (unsigned) max << "]";
- minFontSize = min;
- maxFontSize = max;
- return *this;
- }
- ImageWriter& ImageWriter::SetFontPath(const Anope::string& value)
- {
- Log(LOG_DEBUG) << "Font path: " << value;
- fontPath = value;
- return *this;
- }
- ImageWriter& ImageWriter::SetBackground(const Anope::string& path)
- {
- Log(LOG_DEBUG) << "Background path: " << path;
- background = path;
- return *this;
- }
- ImageWriter& ImageWriter::SetText(const std::vector<Anope::string>& txt)
- {
- text = txt;
- return *this;
- }
- ImageWriter& ImageWriter::SetOutputPath(const Anope::string& path)
- {
- Log(LOG_DEBUG) << "Output path: " << path;
- outputName = path;
- return *this;
- }
- ImageWriter& ImageWriter::SetBgColor(long value)
- {
- Log(LOG_DEBUG) << "Color for background: " << value;
- bgColor = ARGB(value);
- return *this;
- }
- ImageWriter& ImageWriter::SetFgColor(long value)
- {
- Log(LOG_DEBUG) << "Color for text: " << value;
- fgColor = RGB(value);
- return *this;
- }
- ImageWriter& ImageWriter::SetOffsetTop(long value)
- {
- Log(LOG_DEBUG) << "Offset Top: " << value;
- offsetTop = value;
- return *this;
- }
- ImageWriter& ImageWriter::SetQuotePosition(size_t top, size_t left, size_t right, size_t bottom)
- {
- Log(LOG_DEBUG) << "Position: " << top << ',' << left << ',' << right << ',' << bottom;
- this->top = top;
- this->left = left;
- this->bottom = bottom;
- this->right = right;
- return *this;
- }
- int ImageWriter::GetTextLength(pngwriter &writer, pngwriterfont &font, char fontSize, const std::string &str, int maxWidth) const
- {
- const unsigned len = str.size();
- for (unsigned i =1; i < len; ++i) {
- const int w = writer.get_text_width_utf8(font, fontSize, str.substr(0, i).c_str());
- if (w > maxWidth)
- return i -1;
- }
- return len;
- }
- void ImageWriter::WriteText(pngwriter &writer, pngwriterfont &font, char fontSize, const TextItem& line, int offsetY, const RGB &rgb) const
- {
- writer.plot_text_utf8(font, fontSize, line.px, offsetY +line.py, 0, const_cast<char*>(line.text.c_str()), rgb.r, rgb.g, rgb.b);
- }
- bool ImageWriter::WriteDryRun(pngwriter &writer, pngwriterfont& font, char fontSize, const Anope::string &str, TextLines& lines, const RGB &rgb)
- {
- const char marginSize = lines.getLineSpacing();
- int maxLen = GetTextLength(writer, font, fontSize, str.c_str(), right -left -2*marginSize);
- int written = 0;
- do {
- int py = top -lines.getHeight() -fontSize;
- if (!lines.lines.empty())
- py -= lines.getLineSpacing();
- lines.push_back(TextItem(writer, font, str.c_str(), written, written +maxLen, left +marginSize, py, fontSize));
- if (lines.getHeight() +lines.getLineSpacing() > top -bottom)
- return false;
- written += maxLen;
- } while (written < str.length());
- return true;
- }
- TextLines *ImageWriter::WriteDryRun(pngwriter &writer, pngwriterfont& font, char minFontSize, char maxFontSize, const std::vector<Anope::string> &lines, const RGB &rgb)
- {
- TextLines *prev = nullptr;
- char currentFontSize = minFontSize;
- do {
- TextLines lines(currentFontSize);
- for (const Anope::string& str: text)
- if (!WriteDryRun(writer, font, currentFontSize, str, lines, fgColor))
- return prev;
- delete prev;
- prev = new TextLines(lines);
- } while (currentFontSize++ < maxFontSize);
- return prev;
- }
- bool ImageWriter::Build(User *u)
- {
- std::string err;
- pngwriterfont font(fontPath.c_str(), err);
- if (!font.ready())
- {
- Log(LOG_MODULE) << "Error: cannot load font (" << err << ")";
- if (u)
- u->SendMessage((*instaServ)->GetBotInfo(), "Error: Cannot load font (" +err +")");
- return false;
- }
- pngwriter writer(1, 1, 0, outputName.c_str());
- if (!writer.readfromfile(background.c_str()))
- {
- Log(LOG_MODULE) << "Error: cannot load background image";
- if (u)
- u->SendMessage((*instaServ)->GetBotInfo(), "Error: Cannot load background image");
- return false;
- }
- writer.filledsquare_blend(left, top, right, bottom,
- bgColor.a, bgColor.r, bgColor.g, bgColor.b);
- size_t i =0;
- TextLines *lines;
- lines = WriteDryRun(writer, font, minFontSize, maxFontSize, text, fgColor);
- if (!lines)
- {
- Log(LOG_MODULE) << "Error: Too much text does not fit in image";
- if (u)
- u->SendMessage((*instaServ)->GetBotInfo(), "Error: Too much text does not fit in image");
- return false;
- }
- Log(LOG_DEBUG) << "Performing real write with font size = " << (unsigned) lines->getFontSize();
- int offsetY = -((top +offsetTop -bottom -lines->getHeight() -lines->getLineSpacing()) /2);
- if (offsetY > 0)
- offsetY = 0;
- for (const TextItem& line: lines->lines)
- WriteText(writer, font, lines->getFontSize(), line, offsetY, fgColor);
- delete lines;
- writer.close();
- chmod(outputName.c_str(), 0644);
- return true;
- }
- class InstaMessageBufferImpl: public InstaMessageBuffer, public Timer
- {
- std::vector<Anope::string> lines;
- User *u;
- ISCreate *module;
- public:
- InstaMessageBufferImpl(ISCreate *m, User *user);
- void Tick(time_t t) anope_override;
- void Add(Anope::string &msg) anope_override;
- size_t LineCount() const anope_override;
- void OnEndBuffer() anope_override;
- };
- class FileRemover: public Timer
- {
- const std::string path;
- public:
- FileRemover(const std::string &path);
- void Tick(time_t t) anope_override;
- };
- class CommandISCreate : public Command
- {
- ISCreate *creator;
- public:
- CommandISCreate(ISCreate *c, const Anope::string &sname = "instaserv/create") : Command((Module*) c, sname, 0, 0), creator(c)
- {
- this->SetDesc(_("Create a new quote"));
- this->SetSyntax("");
- }
- void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override
- {
- if (!source.GetUser())
- return;
- source.Reply("Type in your quote, end with a line only containing `EOF'");
- instaServ->SetBuffer(source.GetUser(), new InstaMessageBufferImpl(creator, source.GetUser()));
- }
- bool OnHelp(CommandSource &source, const Anope::string &) anope_override
- {
- this->SendSyntax(source);
- source.Reply(_(" \n"
- "Create a new quote image"));
- return true;
- }
- };
- class ISCreate : public Module
- {
- CommandISCreate commandiscreate;
- public:
- ISCreate(const Anope::string &modname, const Anope::string &creator);
- size_t GetMaxLines()
- {
- return 25;
- }
- };
- ISCreate::ISCreate(const Anope::string &modname, const Anope::string &creator): Module(modname, creator, VENDOR), commandiscreate(this)
- {}
- FileRemover::FileRemover(const std::string &_path): Timer(*instaServ, Config->GetModule("is_create")->Get<time_t>("expireafter", "30m")), path(_path)
- {}
- void FileRemover::Tick(time_t t)
- {
- Log(LOG_DEBUG) << "InstaServ: removing " << path;
- ::unlink(path.c_str());
- }
- InstaMessageBufferImpl::InstaMessageBufferImpl(ISCreate *m, User *user) :Timer(*instaServ, Config->GetModule("is_create")->Get<time_t>("typingtimeout", "5m")), u(user), module(m)
- {}
- void InstaMessageBufferImpl::Tick(time_t t)
- {
- u->SendMessage((*instaServ)->GetBotInfo(), "Error: timeout reached.");
- (*instaServ)->OnExpire(u);
- }
- void InstaMessageBufferImpl::Add(Anope::string &msg)
- {
- if (LineCount() < module->GetMaxLines())
- lines.emplace_back(msg);
- else
- {
- std::stringstream ss;
- ss << "Cannot paste more than " << module->GetMaxLines() << " lines";
- u->SendMessage((*instaServ)->GetBotInfo(), ss.str());
- }
- }
- size_t InstaMessageBufferImpl::LineCount() const
- {
- return lines.size();
- }
- static Anope::string hash(const Anope::string& username)
- {
- unsigned long long int hash = time(NULL);
- const char* userstr = username.c_str();
- do {
- hash = (hash << 5) +hash +*userstr;
- ++userstr;
- } while (*userstr);
- std::stringstream ss;
- ss << std::hex << hash;
- return ss.str();
- }
- static unsigned readConfigColor(const Configuration::Block& config, const Anope::string& key, unsigned def)
- {
- const Anope::string value = config.Get<Anope::string>(key, "");
- if (value == "")
- return def;
- std::stringstream ss;
- ss << std::hex << value.c_str();
- long intVal;
- ss >> intVal;
- return intVal;
- }
- void InstaMessageBufferImpl::OnEndBuffer()
- {
- ImageWriter builder;
- const Configuration::Block* config = Config->GetModule("is_create");
- const Anope::string filename = hash(u->nick) +".png";
- const Anope::string path = config->Get<Anope::string>("output", "") +"/" +filename;
- builder.SetBackground(config->Get<Anope::string>("background", ""))
- .SetText(lines)
- .SetOutputPath(path)
- .SetOffsetTop(config->Get<long>("offsetTop", "0"))
- .SetQuotePosition(
- config->Get<unsigned>("quoteTop", "0"),
- config->Get<unsigned>("quoteLeft", "0"),
- config->Get<unsigned>("quoteRight", "0"),
- config->Get<unsigned>("quoteBottom", "0"))
- .SetBgColor(readConfigColor(*config, "bgColor", 0xAAA3A5A7))
- .SetFgColor(readConfigColor(*config, "fgColor", 0x474255))
- .SetFontPath(config->Get<Anope::string>("font", ""))
- .SetFontSize(config->Get<unsigned>("minFontSize", 16), config->Get<unsigned>("maxFontSize", 32));
- if (builder.Build(u))
- {
- u->SendMessage((*instaServ)->GetBotInfo(), "Image exported as " +config->Get<Anope::string>("httpRoot", "") +filename);
- new FileRemover(path.c_str());
- }
- (*instaServ)->OnExpire(u);
- TimerManager::DelTimer(this);
- }
- MODULE_INIT(ISCreate)
|