+
تبلیغگر +
سرعت 💠 دقت 💠 قدرت +
+ +تبلیغگر رباتی هوشمند, بسیار ساده و البته رایگان جهت امور تبلیغاتی در تلگرام است.
+نوشته شده توسط ناجی برپایه اخرین نسخه تلگرام.
+
+
+git clone https://github.com/i-naji/tabchi.git -b persian +cd tabchi +chmod +x bot +./bot install ++
+./bot create
+
++دستوری برای راه اندازی مجدد ردیس# +sudo service redis-server restart +دستوری برای راه اندازی ردیس# +sudo service redis-server start + +./bot 1 ++
+screen ./bot 1
+
+
+screen -r [screen name]
+
+| مخصوص مدیر | +||||
|---|---|---|---|---|
| دستور | +عملکرد | ++ | فقط در گروه (✤) فقط در ریپلای (↻) |
+ |
| reload/ | +بارگذاری مجدد ربات و راهاندازی دوباره | +|||
| بروزرسانی ربات | +بروزرسانی ربات به آخرین نسخه و بارگذاری مجدد |
+ |||
| تازه سازی | +تازه سازی آمار تبیلغگر (حدکثر یک بار در روز) |
+ |||
| ریپورت | +اطلاع از وضعیت ریپورت ربات | +|||
| تازه سازی ربات | +تازه سازی اطلاعات فردی تبلیغگر بعد از تغییر در مشخصات فردی تبلیغگر مانند اسم انجام گیرد |
+ |||
| افزودن مدیر | +شناسه | +افزودن مدیر با شناسه وارد شد | +||
| افزودن مدیرکل | +شناسه | +افزودن مدیرکل با شناسه وارد شد | +||
| حذف مدیر | +شناسه | +حذف مدیر یا مدیر کل با شناسه خاص | +||
| ترک کردن | +خارج شدن از گروه و حذف از اطلاعات گروهها |
+ ✤ | +||
| افزودن همه مخاطبین | +افزودن تمام مخاطبین و افراد در گفتوگوی شخصی به گروه |
+ ✤ | +||
| شناسه من | +ارسال شناسه عددی شما | +|||
| بگو | +متن | +ارسال متن | +||
| ارسال کن | +"شناسه" متن | +ارسال متن به شناسه گروه یا فرد | +||
| تنظیم نام | +"نام" نامخانوادگی | +تغییر نام | +||
| تنطیم نام کاربری | +نام کاربری | +جایگزینی نام کاربری داده شده (دفعات استفاده محدود در زمان کوتاه) |
+ ||
| حذف نام کاربری | +حذف نام کاربری (دفعات استفاده محدود در زمان کوتاه) |
+ |||
| افزودن با شماره | +روشن | +تغییر وضعیت اشتراک شماره تبلیغگر در جواب شماره به اشتراک گذاشته شده |
+ ||
| خاموش | +||||
| افزودن با پیام | +روشن | +تغییر وضعیت ارسال پیام در جواب شماره به اشتراک گذاشته شده |
+ ||
| خاموش | +||||
| تنظیم پیام افزودن مخاطب | +متن | +تنظیم متن داده شده به عنوان جواب شماره به اشتراک گذاشته شده |
+ ||
| پاسخگوی خودکار | +روشن | +تغییر وضعیت پاسخ گویی خودکار | +||
| خاموش | +||||
| تنظیم جواب | +"متن" جواب | +تنظیم جوابی به عنوان پاسخ خودکار به پیام وارد شده مطابق با متن |
+ ||
| حذف جواب | +متن | +حذف جواب مربوط به متن | +||
| لیست | +مخاطبین | +ارسال لیستی از مورد خواسته شده در قالب پرونده متنی یا پیام |
+ ||
| خصوصی | +||||
| گروه | +||||
| سوپرگروه | +||||
| لینک | +||||
| پاسخ های خودکار | +||||
| مسدودیت | +شناسه | +مسدودکردن(بلاک) کاربر با شناسه داده شده | +||
| رفع مسدودیت | +شناسه | +رفع مسدودیت کاربر با شناسه داده شده | +||
| وضعیت مشاهده | +روشن | +خاموش و یا روشنکردن وضعیت مشاهده پیامها (فعال و غیرفعالکردن تیک دوم) |
+ ||
| خاموش | +||||
| امار | +ارسال آمار و وضعیت تبلیغگر | +|||
| وضعیت | +ارسال وضعیت اجرایی تبلیغگر | +|||
| ارسال به | +همه | +ارسال پیام جواب داده شده به مورد خواسته شده |
+ ↻ | +|
| خصوصی | +||||
| گروه | +||||
| سوپرگروه | +||||
| ارسال به سوپرگروه | +متن | +ارسال متن داده شده به تمام سوپرگروه ها | +||
| ترک کردن | +شناسه | +خروج از گروه باشناسه وارد شده و حذف از اطلاعات گروه |
+ ||
| افزودن به همه | +شناسه | +دعوت کردن کاربر با شناسه وارد شده به تمامی گروه ها و سوپرگروه ها |
+ ||
| راهنما | +دریافت راهنمای تبلیغگر | +|||
| استفاده از اطلاعات تبچی | +||||
| همگام سازی با تبچی | +به آموزش موجود در کانال سر بزنید.. | +|||
"..tostring(botid).." انجام شد.")
+ elseif text:match("^(لیست) (.*)$") then
+ local matches = text:match("^لیست (.*)$")
+ local naji
+ if matches == "مخاطبین" then
+ return tdcli_function({
+ ID = "SearchContacts",
+ query_ = nil,
+ limit_ = 999999999
+ },
+ function (I, Naji)
+ local count = Naji.total_count_
+ local text = "مخاطبین : \n"
+ for i =0 , tonumber(count) - 1 do
+ local user = Naji.users_[i]
+ local firstname = user.first_name_ or ""
+ local lastname = user.last_name_ or ""
+ local fullname = firstname .. " " .. lastname
+ text = tostring(text) .. tostring(i) .. ". " .. tostring(fullname) .. " [" .. tostring(user.id_) .. "] = " .. tostring(user.phone_number_) .. " \n"
+ end
+ writefile("botBOT-ID_contacts.txt", text)
+ tdcli_function ({
+ ID = "SendMessage",
+ chat_id_ = I.chat_id,
+ reply_to_message_id_ = 0,
+ disable_notification_ = 0,
+ from_background_ = 1,
+ reply_markup_ = nil,
+ input_message_content_ = {ID = "InputMessageDocument",
+ document_ = {ID = "InputFileLocal",
+ path_ = "botBOT-ID_contacts.txt"},
+ caption_ = "مخاطبین تبلیغگر شماره BOT-ID"}
+ }, dl_cb, nil)
+ return io.popen("rm -rf botBOT-ID_contacts.txt"):read("*all")
+ end, {chat_id = msg.chat_id_})
+ elseif matches == "پاسخ های خودکار" then
+ local text = "لیست پاسخ های خودکار :\n\n"
+ local answers = redis:smembers("botBOT-IDanswerslist")
+ for k,v in pairs(answers) do
+ text = tostring(text) .. "l" .. tostring(k) .. "l " .. tostring(v) .. " : " .. tostring(redis:hget("botBOT-IDanswers", v)) .. "\n"
+ end
+ if redis:scard('botBOT-IDanswerslist') == 0 then text = " EMPTY" end
+ return send(msg.chat_id_, msg.id_, text)
+ elseif matches == "مسدود" then
+ naji = "botBOT-IDblockedusers"
+ elseif matches == "شخصی" then
+ naji = "botBOT-IDusers"
+ elseif matches == "گروه" then
+ naji = "botBOT-IDgroups"
+ elseif matches == "سوپرگروه" then
+ naji = "botBOT-IDsupergroups"
+ elseif matches == "لینک" then
+ naji = "botBOT-IDsavedlinks"
+ elseif matches == "مدیر" then
+ naji = "botBOT-IDadmin"
+ else
+ return true
+ end
+ local list = redis:smembers(naji)
+ local text = tostring(matches).." : \n"
+ for i, v in pairs(list) do
+ text = tostring(text) .. tostring(i) .. "- " .. tostring(v).."\n"
+ end
+ writefile(tostring(naji)..".txt", text)
+ tdcli_function ({
+ ID = "SendMessage",
+ chat_id_ = msg.chat_id_,
+ reply_to_message_id_ = 0,
+ disable_notification_ = 0,
+ from_background_ = 1,
+ reply_markup_ = nil,
+ input_message_content_ = {ID = "InputMessageDocument",
+ document_ = {ID = "InputFileLocal",
+ path_ = tostring(naji)..".txt"},
+ caption_ = "لیست "..tostring(matches).." های تبلیغ گر شماره BOT-ID"}
+ }, dl_cb, nil)
+ return io.popen("rm -rf "..tostring(naji)..".txt"):read("*all")
+ elseif text:match("^(وضعیت مشاهده) (.*)$") then
+ local matches = text:match("^وضعیت مشاهده (.*)$")
+ if matches == "روشن" then
+ redis:set("botBOT-IDmarkread", true)
+ return send(msg.chat_id_, msg.id_, "وضعیت پیام ها >> خوانده شده ✔️✔️\n(تیک دوم فعال)")
+ elseif matches == "خاموش" then
+ redis:del("botBOT-IDmarkread")
+ return send(msg.chat_id_, msg.id_, "وضعیت پیام ها >> خوانده نشده ✔️\n(بدون تیک دوم)")
+ end
+ elseif text:match("^(افزودن با پیام) (.*)$") then
+ local matches = text:match("^افزودن با پیام (.*)$")
+ if matches == "روشن" then
+ redis:set("botBOT-IDaddmsg", true)
+ return send(msg.chat_id_, msg.id_, "پیام افزودن مخاطب فعال شد")
+ elseif matches == "خاموش" then
+ redis:del("botBOT-IDaddmsg")
+ return send(msg.chat_id_, msg.id_, "پیام افزودن مخاطب غیرفعال شد")
+ end
+ elseif text:match("^(افزودن با شماره) (.*)$") then
+ local matches = text:match("افزودن با شماره (.*)$")
+ if matches == "روشن" then
+ redis:set("botBOT-IDaddcontact", true)
+ return send(msg.chat_id_, msg.id_, "ارسال شماره هنگام افزودن مخاطب فعال شد")
+ elseif matches == "خاموش" then
+ redis:del("botBOT-IDaddcontact")
+ return send(msg.chat_id_, msg.id_, "ارسال شماره هنگام افزودن مخاطب غیرفعال شد")
+ end
+ elseif text:match("^(تنظیم پیام افزودن مخاطب) (.*)") then
+ local matches = text:match("^تنظیم پیام افزودن مخاطب (.*)")
+ redis:set("botBOT-IDaddmsgtext", matches)
+ return send(msg.chat_id_, msg.id_, "پیام افزودن مخاطب ثبت شد :\n🔹 "..matches.." 🔹")
+ elseif text:match('^(تنظیم جواب) "(.*)" (.*)') then
+ local txt, answer = text:match('^تنظیم جواب "(.*)" (.*)')
+ redis:hset("botBOT-IDanswers", txt, answer)
+ redis:sadd("botBOT-IDanswerslist", txt)
+ return send(msg.chat_id_, msg.id_, "جواب برای | " .. tostring(txt) .. " | تنظیم شد به :\n" .. tostring(answer))
+ elseif text:match("^(حذف جواب) (.*)") then
+ local matches = text:match("^حذف جواب (.*)")
+ redis:hdel("botBOT-IDanswers", matches)
+ redis:srem("botBOT-IDanswerslist", matches)
+ return send(msg.chat_id_, msg.id_, "جواب برای | " .. tostring(matches) .. " | از لیست جواب های خودکار پاک شد.")
+ elseif text:match("^(پاسخگوی خودکار) (.*)$") then
+ local matches = text:match("^پاسخگوی خودکار (.*)$")
+ if matches == "روشن" then
+ redis:set("botBOT-IDautoanswer", true)
+ return send(msg.chat_id_, 0, "پاسخگویی خودکار تبلیغ گر فعال شد")
+ elseif matches == "خاموش" then
+ redis:del("botBOT-IDautoanswer")
+ return send(msg.chat_id_, 0, "حالت پاسخگویی خودکار تبلیغ گر غیر فعال شد.")
+ end
+ elseif text:match("^(تازه سازی)$")then
+ local list = {redis:smembers("botBOT-IDsupergroups"),redis:smembers("botBOT-IDgroups")}
+ tdcli_function({
+ ID = "SearchContacts",
+ query_ = nil,
+ limit_ = 999999999
+ }, function (i, naji)
+ redis:set("botBOT-IDcontacts", naji.total_count_)
+ end, nil)
+ for i, v in pairs(list) do
+ for a, b in pairs(v) do
+ tdcli_function ({
+ ID = "GetChatMember",
+ chat_id_ = b,
+ user_id_ = bot_id
+ }, function (i,naji)
+ if naji.ID == "Error" then rem(i.id)
+ end
+ end, {id=b})
+ end
+ end
+ return send(msg.chat_id_,msg.id_,"تازهسازی آمار تبلیغگر شماره BOT-ID با موفقیت انجام شد.")
+ elseif text:match("^(وضعیت)$") then
+ local s = redis:get("botBOT-IDmaxjoin") and redis:ttl("botBOT-IDmaxjoin") or 0
+ local ss = redis:get("botBOT-IDmaxlink") and redis:ttl("botBOT-IDmaxlink") or 0
+ local msgadd = redis:get("botBOT-IDaddmsg") and "☑️" or "❎"
+ local numadd = redis:get("botBOT-IDaddcontact") and "✅" or "❎"
+ local txtadd = redis:get("botBOT-IDaddmsgtext") or "اددی گلم خصوصی پیام بده"
+ local autoanswer = redis:get("botBOT-IDautoanswer") and "✅" or "❎"
+ local wlinks = redis:scard("botBOT-IDwaitelinks")
+ local glinks = redis:scard("botBOT-IDgoodlinks")
+ local links = redis:scard("botBOT-IDsavedlinks")
+ local txt = "⚙️ وضعیت اجرایی تبلیغگر BOT-ID ⛓\n\n" .. tostring(autoanswer) .." حالت پاسخگویی خودکار 🗣 \n" .. tostring(numadd) .. " افزودن مخاطب با شماره 📞 \n" .. tostring(msgadd) .. " افزودن مخاطب با پیام 🗞\n〰〰〰ا〰〰〰\n📄 پیام افزودن مخاطب :\n📍 " .. tostring(txtadd) .. " 📍\n〰〰〰ا〰〰〰\n📁 لینک های ذخیره شده : " .. tostring(links) .. "\n⏲ لینک های در انتظار عضویت : " .. tostring(glinks) .. "\n🕖 " .. tostring(s) .. " ثانیه تا عضویت مجدد\n❄️ لینک های در انتظار تایید : " .. tostring(wlinks) .. "\n🕑️ " .. tostring(ss) .. " ثانیه تا تایید لینک مجدد\n 😼 سازنده : @i_naji"
+ return send(msg.chat_id_, 0, txt)
+ elseif text:match("^(امار)$") or text:match("^(آمار)$") then
+ local gps = redis:scard("botBOT-IDgroups")
+ local sgps = redis:scard("botBOT-IDsupergroups")
+ local usrs = redis:scard("botBOT-IDusers")
+ local links = redis:scard("botBOT-IDsavedlinks")
+ local glinks = redis:scard("botBOT-IDgoodlinks")
+ local wlinks = redis:scard("botBOT-IDwaitelinks")
+ tdcli_function({
+ ID = "SearchContacts",
+ query_ = nil,
+ limit_ = 999999999
+ }, function (i, naji)
+ redis:set("botBOT-IDcontacts", naji.total_count_)
+ end, nil)
+ local contacts = redis:get("botBOT-IDcontacts")
+ local text = [[
+📈 وضعیت و آمار تبلیغ گر 📊
+
+👤 گفت و گو های شخصی :
+]] .. tostring(usrs) .. [[
+👥 گروها :
+]] .. tostring(gps) .. [[
+🌐 سوپر گروه ها :
+]] .. tostring(sgps) .. [[
+📖 مخاطبین دخیره شده :
+]] .. tostring(contacts)..[[
+📂 لینک های ذخیره شده :
+]] .. tostring(links)..[[
+ 😼 سازنده : @i_naji]]
+ return send(msg.chat_id_, 0, text)
+ elseif (text:match("^(ارسال به) (.*)$") and msg.reply_to_message_id_ ~= 0) then
+ local matches = text:match("^ارسال به (.*)$")
+ local naji
+ if matches:match("^(همه)$") then
+ naji = "botBOT-IDall"
+ elseif matches:match("^(خصوصی)") then
+ naji = "botBOT-IDusers"
+ elseif matches:match("^(گروه)$") then
+ naji = "botBOT-IDgroups"
+ elseif matches:match("^(سوپرگروه)$") then
+ naji = "botBOT-IDsupergroups"
+ else
+ return true
+ end
+ local list = redis:smembers(naji)
+ local id = msg.reply_to_message_id_
+ for i, v in pairs(list) do
+ tdcli_function({
+ ID = "ForwardMessages",
+ chat_id_ = v,
+ from_chat_id_ = msg.chat_id_,
+ message_ids_ = {[0] = id},
+ disable_notification_ = 1,
+ from_background_ = 1
+ }, dl_cb, nil)
+ end
+ return send(msg.chat_id_, msg.id_, "با موفقیت فرستاده شد")
+ elseif text:match("^(ارسال به سوپرگروه) (.*)") then
+ local matches = text:match("^ارسال به سوپرگروه (.*)")
+ local dir = redis:smembers("botBOT-IDsupergroups")
+ for i, v in pairs(dir) do
+ tdcli_function ({
+ ID = "SendMessage",
+ chat_id_ = v,
+ reply_to_message_id_ = 0,
+ disable_notification_ = 0,
+ from_background_ = 1,
+ reply_markup_ = nil,
+ input_message_content_ = {
+ ID = "InputMessageText",
+ text_ = matches,
+ disable_web_page_preview_ = 1,
+ clear_draft_ = 0,
+ entities_ = {},
+ parse_mode_ = nil
+ },
+ }, dl_cb, nil)
+ end
+ return send(msg.chat_id_, msg.id_, "با موفقیت فرستاده شد")
+ elseif text:match("^(مسدودیت) (%d+)$") then
+ local matches = text:match("%d+")
+ rem(tonumber(matches))
+ redis:sadd("botBOT-IDblockedusers",matches)
+ tdcli_function ({
+ ID = "BlockUser",
+ user_id_ = tonumber(matches)
+ }, dl_cb, nil)
+ return send(msg.chat_id_, msg.id_, "کاربر مورد نظر مسدود شد")
+ elseif text:match("^(رفع مسدودیت) (%d+)$") then
+ local matches = text:match("%d+")
+ add(tonumber(matches))
+ redis:srem("botBOT-IDblockedusers",matches)
+ tdcli_function ({
+ ID = "UnblockUser",
+ user_id_ = tonumber(matches)
+ }, dl_cb, nil)
+ return send(msg.chat_id_, msg.id_, "مسدودیت کاربر مورد نظر رفع شد.")
+ elseif text:match('^(تنظیم نام) "(.*)" (.*)') then
+ local fname, lname = text:match('^تنظیم نام "(.*)" (.*)')
+ tdcli_function ({
+ ID = "ChangeName",
+ first_name_ = fname,
+ last_name_ = lname
+ }, dl_cb, nil)
+ return send(msg.chat_id_, msg.id_, "نام جدید با موفقیت ثبت شد.")
+ elseif text:match("^(تنظیم نام کاربری) (.*)") then
+ local matches = text:match("^تنظیم نام کاربری (.*)")
+ tdcli_function ({
+ ID = "ChangeUsername",
+ username_ = tostring(matches)
+ }, dl_cb, nil)
+ return send(msg.chat_id_, 0, 'تلاش برای تنظیم نام کاربری...')
+ elseif text:match("^(حذف نام کاربری)$") then
+ tdcli_function ({
+ ID = "ChangeUsername",
+ username_ = ""
+ }, dl_cb, nil)
+ return send(msg.chat_id_, 0, 'نام کاربری با موفقیت حذف شد.')
+ elseif text:match('^(ارسال کن) "(.*)" (.*)') then
+ local id, txt = text:match('^ارسال کن "(.*)" (.*)')
+ send(id, 0, txt)
+ return send(msg.chat_id_, msg.id_, "ارسال شد")
+ elseif text:match("^(بگو) (.*)") then
+ local matches = text:match("^بگو (.*)")
+ return send(msg.chat_id_, 0, matches)
+ elseif text:match("^(شناسه من)$") then
+ return send(msg.chat_id_, msg.id_, "" .. msg.sender_user_id_ .."")
+ elseif text:match("^(ترک کردن) (.*)$") then
+ local matches = text:match("^ترک کردن (.*)$")
+ send(msg.chat_id_, msg.id_, 'تبلیغگر از گروه مورد نظر خارج شد')
+ tdcli_function ({
+ ID = "ChangeChatMemberStatus",
+ chat_id_ = matches,
+ user_id_ = bot_id,
+ status_ = {ID = "ChatMemberStatusLeft"},
+ }, dl_cb, nil)
+ return rem(matches)
+ elseif text:match("^(افزودن به همه) (%d+)$") then
+ local matches = text:match("%d+")
+ local list = {redis:smembers("botBOT-IDgroups"),redis:smembers("botBOT-IDsupergroups")}
+ for a, b in pairs(list) do
+ for i, v in pairs(b) do
+ tdcli_function ({
+ ID = "AddChatMember",
+ chat_id_ = v,
+ user_id_ = matches,
+ forward_limit_ = 50
+ }, dl_cb, nil)
+ end
+ end
+ return send(msg.chat_id_, msg.id_, "کاربر مورد نظر به تمام گروه های من دعوت شد")
+ elseif (text:match("^(انلاین)$") and not msg.forward_info_)then
+ return tdcli_function({
+ ID = "ForwardMessages",
+ chat_id_ = msg.chat_id_,
+ from_chat_id_ = msg.chat_id_,
+ message_ids_ = {[0] = msg.id_},
+ disable_notification_ = 0,
+ from_background_ = 1
+ }, dl_cb, nil)
+ elseif text:match("^(راهنما)$") then
+ local txt = '📍راهنمای دستورات تبلیغ گر📍\n\nانلاین\nاعلام وضعیت تبلیغ گر ✔️\n❤️ حتی اگر تبلیغگر شما دچار محدودیت ارسال پیام شده باشد بایستی به این پیام پاسخ دهد❤️\n/reload\nl🔄 بارگذاری مجدد ربات 🔄l\nI⛔️عدم استفاده بی جهت⛔️I\nبروزرسانی ربات\nبروزرسانی ربات به آخرین نسخه و بارگذاری مجدد 🆕\n\nافزودن مدیر شناسه\nافزودن مدیر جدید با شناسه عددی داده شده 🛂\n\nافزودن مدیرکل شناسه\nافزودن مدیرکل جدید با شناسه عددی داده شده 🛂\n\n(⚠️ تفاوت مدیر و مدیرکل دسترسی به اعطا و یا گرفتن مقام مدیریت است⚠️)\n\nحذف مدیر شناسه\nحذف مدیر یا مدیرکل با شناسه عددی داده شده ✖️\n\nترک گروه\nخارج شدن از گروه و حذف آن از اطلاعات گروه ها 🏃\n\nافزودن همه مخاطبین\nافزودن حداکثر مخاطبین و افراد در گفت و گوهای شخصی به گروه ➕\n\nشناسه من\nدریافت شناسه خود 🆔\n\nبگو متن\nدریافت متن 🗣\n\nارسال کن "شناسه" متن\nارسال متن به شناسه گروه یا کاربر داده شده 📤\n\nتنظیم نام "نام" فامیل\nتنظیم نام ربات ✏️\n\nتازه سازی ربات\nتازهسازی اطلاعات فردی ربات🎈\n(مورد استفاده در مواردی همچون پس از تنظیم نام📍جهت بروزکردن نام مخاطب اشتراکی تبلیغگر📍)\n\nتنظیم نام کاربری اسم\nجایگزینی اسم با نام کاربری فعلی(محدود در بازه زمانی کوتاه) 🔄\n\nحذف نام کاربری\nحذف کردن نام کاربری ❎\n\nافزودن با شماره روشن|خاموش\nتغییر وضعیت اشتراک شماره تبلیغگر در جواب شماره به اشتراک گذاشته شده 🔖\n\nافزودن با پیام روشن|خاموش\nتغییر وضعیت ارسال پیام در جواب شماره به اشتراک گذاشته شده ℹ️\n\nتنظیم پیام افزودن مخاطب متن\nتنظیم متن داده شده به عنوان جواب شماره به اشتراک گذاشته شده 📨\n\nلیست مخاطبین|خصوصی|گروه|سوپرگروه|پاسخ های خودکار|لینک|مدیر\nدریافت لیستی از مورد خواسته شده در قالب پرونده متنی یا پیام 📄\n\nمسدودیت شناسه\nمسدودکردن(بلاک) کاربر با شناسه داده شده از گفت و گوی خصوصی 🚫\n\nرفع مسدودیت شناسه\nرفع مسدودیت کاربر با شناسه داده شده 💢\n\nوضعیت مشاهده روشن|خاموش 👁\nتغییر وضعیت مشاهده پیامها توسط تبلیغگر (فعال و غیرفعالکردن تیک دوم)\n\nامار\nدریافت آمار و وضعیت تبلیغ گر 📊\n\nوضعیت\nدریافت وضعیت اجرایی تبلیغگر⚙️\n\nتازه سازی\nتازهسازی آمار تبلیغگر🚀\n🎃مورد استفاده حداکثر یک بار در روز🎃\n\nارسال به همه|خصوصی|گروه|سوپرگروه\nارسال پیام جواب داده شده به مورد خواسته شده 📩\n(😄توصیه ما عدم استفاده از همه و خصوصی😄)\n\nارسال به سوپرگروه متن\nارسال متن داده شده به همه سوپرگروه ها ✉️\n(😜توصیه ما استفاده و ادغام دستورات بگو و ارسال به سوپرگروه😜)\n\nتنظیم جواب "متن" جواب\nتنظیم جوابی به عنوان پاسخ خودکار به پیام وارد شده مطابق با متن باشد 📝\n\nحذف جواب متن\nحذف جواب مربوط به متن ✖️\n\nپاسخگوی خودکار روشن|خاموش\nتغییر وضعیت پاسخگویی خودکار تبلیغ گر به متن های تنظیم شده 📯\n\nافزودن به همه شناسه\nافزودن کابر با شناسه وارد شده به همه گروه و سوپرگروه ها ➕➕\n\nترک کردن شناسه\nعملیات ترک کردن با استفاده از شناسه گروه 🏃\n\nراهنما\nدریافت همین پیام 🆘\n〰〰〰ا〰〰〰\nهمگام سازی با تبچی\nهمگام سازی اطلاعات تبلیغ گر با اطلاعات تبچی از قبل نصب شده 🔃 (جهت این امر حتما به ویدیو آموزشی کانال مراجعه کنید)\n〰〰〰ا〰〰〰\nسازنده : @I_Naji \nکانال : @I_Advertiser\nآدرس سورس تبلیغ گر (کاملا فارسی) :\nhttps://github.com/i-Naji/tabchi/tree/persian\nآخرین اخبار و رویداد های تبلیغ گر را در کانال ما پیگیری کنید.'
+ return send(msg.chat_id_,msg.id_, txt)
+ elseif tostring(msg.chat_id_):match("^-") then
+ if text:match("^(ترک کردن)$") then
+ rem(msg.chat_id_)
+ return tdcli_function ({
+ ID = "ChangeChatMemberStatus",
+ chat_id_ = msg.chat_id_,
+ user_id_ = bot_id,
+ status_ = {ID = "ChatMemberStatusLeft"},
+ }, dl_cb, nil)
+ elseif text:match("^(افزودن همه مخاطبین)$") then
+ tdcli_function({
+ ID = "SearchContacts",
+ query_ = nil,
+ limit_ = 999999999
+ },function(i, naji)
+ local users, count = redis:smembers("botBOT-IDusers"), naji.total_count_
+ for n=0, tonumber(count) - 1 do
+ tdcli_function ({
+ ID = "AddChatMember",
+ chat_id_ = i.chat_id,
+ user_id_ = naji.users_[n].id_,
+ forward_limit_ = 50
+ }, dl_cb, nil)
+ end
+ for n=1, #users do
+ tdcli_function ({
+ ID = "AddChatMember",
+ chat_id_ = i.chat_id,
+ user_id_ = users[n],
+ forward_limit_ = 50
+ }, dl_cb, nil)
+ end
+ end, {chat_id=msg.chat_id_})
+ return send(msg.chat_id_, msg.id_, "در حال افزودن مخاطبین به گروه ...")
+ end
+ end
+ end
+ if redis:sismember("botBOT-IDanswerslist", text) then
+ if redis:get("botBOT-IDautoanswer") then
+ if msg.sender_user_id_ ~= bot_id then
+ local answer = redis:hget("botBOT-IDanswers", text)
+ send(msg.chat_id_, 0, answer)
+ end
+ end
+ end
+ elseif msg.content_.ID == "MessageContact" then
+ local id = msg.content_.contact_.user_id_
+ if not redis:sismember("botBOT-IDaddedcontacts",id) then
+ redis:sadd("botBOT-IDaddedcontacts",id)
+ local first = msg.content_.contact_.first_name_ or "-"
+ local last = msg.content_.contact_.last_name_ or "-"
+ local phone = msg.content_.contact_.phone_number_
+ local id = msg.content_.contact_.user_id_
+ tdcli_function ({
+ ID = "ImportContacts",
+ contacts_ = {[0] = {
+ phone_number_ = tostring(phone),
+ first_name_ = tostring(first),
+ last_name_ = tostring(last),
+ user_id_ = id
+ },
+ },
+ }, dl_cb, nil)
+ if redis:get("botBOT-IDaddcontact") and msg.sender_user_id_ ~= bot_id then
+ local fname = redis:get("botBOT-IDfname")
+ local lnasme = redis:get("botBOT-IDlname") or ""
+ local num = redis:get("botBOT-IDnum")
+ tdcli_function ({
+ ID = "SendMessage",
+ chat_id_ = msg.chat_id_,
+ reply_to_message_id_ = msg.id_,
+ disable_notification_ = 1,
+ from_background_ = 1,
+ reply_markup_ = nil,
+ input_message_content_ = {
+ ID = "InputMessageContact",
+ contact_ = {
+ ID = "Contact",
+ phone_number_ = num,
+ first_name_ = fname,
+ last_name_ = lname,
+ user_id_ = bot_id
+ },
+ },
+ }, dl_cb, nil)
+ end
+ end
+ if redis:get("botBOT-IDaddmsg") then
+ local answer = redis:get("botBOT-IDaddmsgtext") or "اددی گلم خصوصی پیام بده"
+ send(msg.chat_id_, msg.id_, answer)
+ end
+ elseif msg.content_.ID == "MessageChatDeleteMember" and msg.content_.id_ == bot_id then
+ return rem(msg.chat_id_)
+ elseif msg.content_.ID == "MessageChatJoinByLink" and msg.sender_user_id_ == bot_id then
+ return add(msg.chat_id_)
+ elseif msg.content_.ID == "MessageChatAddMembers" then
+ for i = 0, #msg.content_.members_ do
+ if msg.content_.members_[i].id_ == bot_id then
+ add(msg.chat_id_)
+ end
+ end
+ elseif msg.content_.caption_ then
+ return find_link(msg.content_.caption_)
+ end
+ if redis:get("botBOT-IDmarkread") then
+ tdcli_function ({
+ ID = "ViewMessages",
+ chat_id_ = msg.chat_id_,
+ message_ids_ = {[0] = msg.id_}
+ }, dl_cb, nil)
+ end
+ elseif data.ID == "UpdateOption" and data.name_ == "my_id" then
+ tdcli_function ({
+ ID = "GetChats",
+ offset_order_ = 9223372036854775807,
+ offset_chat_id_ = 0,
+ limit_ = 20
+ }, dl_cb, nil)
+ end
+end
diff --git a/redis.lua b/redis.lua
new file mode 100644
index 0000000..77aea79
--- /dev/null
+++ b/redis.lua
@@ -0,0 +1,1140 @@
+local redis = {
+ _VERSION = 'redis-lua 2.0.4',
+ _DESCRIPTION = 'A Lua client library for the redis key value storage system.',
+ _COPYRIGHT = 'Copyright (C) 2009-2012 Daniele Alessandri',
+}
+
+-- The following line is used for backwards compatibility in order to keep the `Redis`
+-- global module name. Using `Redis` is now deprecated so you should explicitly assign
+-- the module to a local variable when requiring it: `local redis = require('redis')`.
+Redis = redis
+
+local unpack = _G.unpack or table.unpack
+local network, request, response = {}, {}, {}
+
+local defaults = {
+ host = '127.0.0.1',
+ port = 6379,
+ tcp_nodelay = true,
+ path = nil
+}
+
+local function merge_defaults(parameters)
+ if parameters == nil then
+ parameters = {}
+ end
+ for k, v in pairs(defaults) do
+ if parameters[k] == nil then
+ parameters[k] = defaults[k]
+ end
+ end
+ return parameters
+end
+
+local function parse_boolean(v)
+ if v == '1' or v == 'true' or v == 'TRUE' then
+ return true
+ elseif v == '0' or v == 'false' or v == 'FALSE' then
+ return false
+ else
+ return nil
+ end
+end
+
+local function toboolean(value) return value == 1 end
+
+local function sort_request(client, command, key, params)
+ --[[ params = {
+ by = 'weight_*',
+ get = 'object_*',
+ limit = { 0, 10 },
+ sort = 'desc',
+ alpha = true,
+ } ]]
+ local query = { key }
+
+ if params then
+ if params.by then
+ table.insert(query, 'BY')
+ table.insert(query, params.by)
+ end
+
+ if type(params.limit) == 'table' then
+ -- TODO: check for lower and upper limits
+ table.insert(query, 'LIMIT')
+ table.insert(query, params.limit[1])
+ table.insert(query, params.limit[2])
+ end
+
+ if params.get then
+ if (type(params.get) == 'table') then
+ for _, getarg in pairs(params.get) do
+ table.insert(query, 'GET')
+ table.insert(query, getarg)
+ end
+ else
+ table.insert(query, 'GET')
+ table.insert(query, params.get)
+ end
+ end
+
+ if params.sort then
+ table.insert(query, params.sort)
+ end
+
+ if params.alpha == true then
+ table.insert(query, 'ALPHA')
+ end
+
+ if params.store then
+ table.insert(query, 'STORE')
+ table.insert(query, params.store)
+ end
+ end
+
+ request.multibulk(client, command, query)
+end
+
+local function zset_range_request(client, command, ...)
+ local args, opts = {...}, { }
+
+ if #args >= 1 and type(args[#args]) == 'table' then
+ local options = table.remove(args, #args)
+ if options.withscores then
+ table.insert(opts, 'WITHSCORES')
+ end
+ end
+
+ for _, v in pairs(opts) do table.insert(args, v) end
+ request.multibulk(client, command, args)
+end
+
+local function zset_range_byscore_request(client, command, ...)
+ local args, opts = {...}, { }
+
+ if #args >= 1 and type(args[#args]) == 'table' then
+ local options = table.remove(args, #args)
+ if options.limit then
+ table.insert(opts, 'LIMIT')
+ table.insert(opts, options.limit.offset or options.limit[1])
+ table.insert(opts, options.limit.count or options.limit[2])
+ end
+ if options.withscores then
+ table.insert(opts, 'WITHSCORES')
+ end
+ end
+
+ for _, v in pairs(opts) do table.insert(args, v) end
+ request.multibulk(client, command, args)
+end
+
+local function zset_range_reply(reply, command, ...)
+ local args = {...}
+ local opts = args[4]
+ if opts and (opts.withscores or string.lower(tostring(opts)) == 'withscores') then
+ local new_reply = { }
+ for i = 1, #reply, 2 do
+ table.insert(new_reply, { reply[i], reply[i + 1] })
+ end
+ return new_reply
+ else
+ return reply
+ end
+end
+
+local function zset_store_request(client, command, ...)
+ local args, opts = {...}, { }
+
+ if #args >= 1 and type(args[#args]) == 'table' then
+ local options = table.remove(args, #args)
+ if options.weights and type(options.weights) == 'table' then
+ table.insert(opts, 'WEIGHTS')
+ for _, weight in ipairs(options.weights) do
+ table.insert(opts, weight)
+ end
+ end
+ if options.aggregate then
+ table.insert(opts, 'AGGREGATE')
+ table.insert(opts, options.aggregate)
+ end
+ end
+
+ for _, v in pairs(opts) do table.insert(args, v) end
+ request.multibulk(client, command, args)
+end
+
+local function mset_filter_args(client, command, ...)
+ local args, arguments = {...}, {}
+ if (#args == 1 and type(args[1]) == 'table') then
+ for k,v in pairs(args[1]) do
+ table.insert(arguments, k)
+ table.insert(arguments, v)
+ end
+ else
+ arguments = args
+ end
+ request.multibulk(client, command, arguments)
+end
+
+local function hash_multi_request_builder(builder_callback)
+ return function(client, command, ...)
+ local args, arguments = {...}, { }
+ if #args == 2 then
+ table.insert(arguments, args[1])
+ for k, v in pairs(args[2]) do
+ builder_callback(arguments, k, v)
+ end
+ else
+ arguments = args
+ end
+ request.multibulk(client, command, arguments)
+ end
+end
+
+local function parse_info(response)
+ local info = {}
+ local current = info
+
+ response:gsub('([^\r\n]*)\r\n', function(kv)
+ if kv == '' then return end
+
+ local section = kv:match('^# (%w+)$')
+ if section then
+ current = {}
+ info[section:lower()] = current
+ return
+ end
+
+ local k,v = kv:match(('([^:]*):([^:]*)'):rep(1))
+ if k:match('db%d+') then
+ current[k] = {}
+ v:gsub(',', function(dbkv)
+ local dbk,dbv = kv:match('([^:]*)=([^:]*)')
+ current[k][dbk] = dbv
+ end)
+ else
+ current[k] = v
+ end
+ end)
+
+ return info
+end
+
+local function load_methods(proto, commands)
+ local client = setmetatable ({}, getmetatable(proto))
+
+ for cmd, fn in pairs(commands) do
+ if type(fn) ~= 'function' then
+ redis.error('invalid type for command ' .. cmd .. '(must be a function)')
+ end
+ client[cmd] = fn
+ end
+
+ for i, v in pairs(proto) do
+ client[i] = v
+ end
+
+ return client
+end
+
+local function create_client(proto, client_socket, commands)
+ local client = load_methods(proto, commands)
+ client.error = redis.error
+ client.network = {
+ socket = client_socket,
+ read = network.read,
+ write = network.write,
+ }
+ client.requests = {
+ multibulk = request.multibulk,
+ }
+ return client
+end
+
+-- ############################################################################
+
+function network.write(client, buffer)
+ local _, err = client.network.socket:send(buffer)
+ if err then client.error(err) end
+end
+
+function network.read(client, len)
+ if len == nil then len = '*l' end
+ local line, err = client.network.socket:receive(len)
+ if not err then return line else client.error('connection error: ' .. err) end
+end
+
+-- ############################################################################
+
+function response.read(client)
+ local payload = client.network.read(client)
+ local prefix, data = payload:sub(1, -#payload), payload:sub(2)
+
+ -- status reply
+ if prefix == '+' then
+ if data == 'OK' then
+ return true
+ elseif data == 'QUEUED' then
+ return { queued = true }
+ else
+ return data
+ end
+
+ -- error reply
+ elseif prefix == '-' then
+ return client.error('redis error: ' .. data)
+
+ -- integer reply
+ elseif prefix == ':' then
+ local number = tonumber(data)
+
+ if not number then
+ if res == 'nil' then
+ return nil
+ end
+ client.error('cannot parse '..res..' as a numeric response.')
+ end
+
+ return number
+
+ -- bulk reply
+ elseif prefix == '$' then
+ local length = tonumber(data)
+
+ if not length then
+ client.error('cannot parse ' .. length .. ' as data length')
+ end
+
+ if length == -1 then
+ return nil
+ end
+
+ local nextchunk = client.network.read(client, length + 2)
+
+ return nextchunk:sub(1, -3)
+
+ -- multibulk reply
+ elseif prefix == '*' then
+ local count = tonumber(data)
+
+ if count == -1 then
+ return nil
+ end
+
+ local list = {}
+ if count > 0 then
+ local reader = response.read
+ for i = 1, count do
+ list[i] = reader(client)
+ end
+ end
+ return list
+
+ -- unknown type of reply
+ else
+ return client.error('unknown response prefix: ' .. prefix)
+ end
+end
+
+-- ############################################################################
+
+function request.raw(client, buffer)
+ local bufferType = type(buffer)
+
+ if bufferType == 'table' then
+ client.network.write(client, table.concat(buffer))
+ elseif bufferType == 'string' then
+ client.network.write(client, buffer)
+ else
+ client.error('argument error: ' .. bufferType)
+ end
+end
+
+function request.multibulk(client, command, ...)
+ local args = {...}
+ local argsn = #args
+ local buffer = { true, true }
+
+ if argsn == 1 and type(args[1]) == 'table' then
+ argsn, args = #args[1], args[1]
+ end
+
+ buffer[1] = '*' .. tostring(argsn + 1) .. "\r\n"
+ buffer[2] = '$' .. #command .. "\r\n" .. command .. "\r\n"
+
+ local table_insert = table.insert
+ for _, argument in pairs(args) do
+ local s_argument = tostring(argument)
+ table_insert(buffer, '$' .. #s_argument .. "\r\n" .. s_argument .. "\r\n")
+ end
+
+ client.network.write(client, table.concat(buffer))
+end
+
+-- ############################################################################
+
+local function custom(command, send, parse)
+ command = string.upper(command)
+ return function(client, ...)
+ send(client, command, ...)
+ local reply = response.read(client)
+
+ if type(reply) == 'table' and reply.queued then
+ reply.parser = parse
+ return reply
+ else
+ if parse then
+ return parse(reply, command, ...)
+ end
+ return reply
+ end
+ end
+end
+
+local function command(command, opts)
+ if opts == nil or type(opts) == 'function' then
+ return custom(command, request.multibulk, opts)
+ else
+ return custom(command, opts.request or request.multibulk, opts.response)
+ end
+end
+
+local define_command_impl = function(target, name, opts)
+ local opts = opts or {}
+ target[string.lower(name)] = custom(
+ opts.command or string.upper(name),
+ opts.request or request.multibulk,
+ opts.response or nil
+ )
+end
+
+local undefine_command_impl = function(target, name)
+ target[string.lower(name)] = nil
+end
+
+-- ############################################################################
+
+local client_prototype = {}
+
+client_prototype.raw_cmd = function(client, buffer)
+ request.raw(client, buffer .. "\r\n")
+ return response.read(client)
+end
+
+-- obsolete
+client_prototype.define_command = function(client, name, opts)
+ define_command_impl(client, name, opts)
+end
+
+-- obsolete
+client_prototype.undefine_command = function(client, name)
+ undefine_command_impl(client, name)
+end
+
+client_prototype.quit = function(client)
+ request.multibulk(client, 'QUIT')
+ client.network.socket:shutdown()
+ return true
+end
+
+client_prototype.shutdown = function(client)
+ request.multibulk(client, 'SHUTDOWN')
+ client.network.socket:shutdown()
+end
+
+-- Command pipelining
+
+client_prototype.pipeline = function(client, block)
+ local requests, replies, parsers = {}, {}, {}
+ local table_insert = table.insert
+ local socket_write, socket_read = client.network.write, client.network.read
+
+ client.network.write = function(_, buffer)
+ table_insert(requests, buffer)
+ end
+
+ -- TODO: this hack is necessary to temporarily reuse the current
+ -- request -> response handling implementation of redis-lua
+ -- without further changes in the code, but it will surely
+ -- disappear when the new command-definition infrastructure
+ -- will finally be in place.
+ client.network.read = function() return '+QUEUED' end
+
+ local pipeline = setmetatable({}, {
+ __index = function(env, name)
+ local cmd = client[name]
+ if not cmd then
+ client.error('unknown redis command: ' .. name, 2)
+ end
+ return function(self, ...)
+ local reply = cmd(client, ...)
+ table_insert(parsers, #requests, reply.parser)
+ return reply
+ end
+ end
+ })
+
+ local success, retval = pcall(block, pipeline)
+
+ client.network.write, client.network.read = socket_write, socket_read
+ if not success then client.error(retval, 0) end
+
+ client.network.write(client, table.concat(requests, ''))
+
+ for i = 1, #requests do
+ local reply, parser = response.read(client), parsers[i]
+ if parser then
+ reply = parser(reply)
+ end
+ table_insert(replies, i, reply)
+ end
+
+ return replies, #requests
+end
+
+-- Publish/Subscribe
+
+do
+ local channels = function(channels)
+ if type(channels) == 'string' then
+ channels = { channels }
+ end
+ return channels
+ end
+
+ local subscribe = function(client, ...)
+ request.multibulk(client, 'subscribe', ...)
+ end
+ local psubscribe = function(client, ...)
+ request.multibulk(client, 'psubscribe', ...)
+ end
+ local unsubscribe = function(client, ...)
+ request.multibulk(client, 'unsubscribe')
+ end
+ local punsubscribe = function(client, ...)
+ request.multibulk(client, 'punsubscribe')
+ end
+
+ local consumer_loop = function(client)
+ local aborting, subscriptions = false, 0
+
+ local abort = function()
+ if not aborting then
+ unsubscribe(client)
+ punsubscribe(client)
+ aborting = true
+ end
+ end
+
+ return coroutine.wrap(function()
+ while true do
+ local message
+ local response = response.read(client)
+
+ if response[1] == 'pmessage' then
+ message = {
+ kind = response[1],
+ pattern = response[2],
+ channel = response[3],
+ payload = response[4],
+ }
+ else
+ message = {
+ kind = response[1],
+ channel = response[2],
+ payload = response[3],
+ }
+ end
+
+ if string.match(message.kind, '^p?subscribe$') then
+ subscriptions = subscriptions + 1
+ end
+ if string.match(message.kind, '^p?unsubscribe$') then
+ subscriptions = subscriptions - 1
+ end
+
+ if aborting and subscriptions == 0 then
+ break
+ end
+ coroutine.yield(message, abort)
+ end
+ end)
+ end
+
+ client_prototype.pubsub = function(client, subscriptions)
+ if type(subscriptions) == 'table' then
+ if subscriptions.subscribe then
+ subscribe(client, channels(subscriptions.subscribe))
+ end
+ if subscriptions.psubscribe then
+ psubscribe(client, channels(subscriptions.psubscribe))
+ end
+ end
+ return consumer_loop(client)
+ end
+end
+
+-- Redis transactions (MULTI/EXEC)
+
+do
+ local function identity(...) return ... end
+ local emptytable = {}
+
+ local function initialize_transaction(client, options, block, queued_parsers)
+ local table_insert = table.insert
+ local coro = coroutine.create(block)
+
+ if options.watch then
+ local watch_keys = {}
+ for _, key in pairs(options.watch) do
+ table_insert(watch_keys, key)
+ end
+ if #watch_keys > 0 then
+ client:watch(unpack(watch_keys))
+ end
+ end
+
+ local transaction_client = setmetatable({}, {__index=client})
+ transaction_client.exec = function(...)
+ client.error('cannot use EXEC inside a transaction block')
+ end
+ transaction_client.multi = function(...)
+ coroutine.yield()
+ end
+ transaction_client.commands_queued = function()
+ return #queued_parsers
+ end
+
+ assert(coroutine.resume(coro, transaction_client))
+
+ transaction_client.multi = nil
+ transaction_client.discard = function(...)
+ local reply = client:discard()
+ for i, v in pairs(queued_parsers) do
+ queued_parsers[i]=nil
+ end
+ coro = initialize_transaction(client, options, block, queued_parsers)
+ return reply
+ end
+ transaction_client.watch = function(...)
+ client.error('WATCH inside MULTI is not allowed')
+ end
+ setmetatable(transaction_client, { __index = function(t, k)
+ local cmd = client[k]
+ if type(cmd) == "function" then
+ local function queuey(self, ...)
+ local reply = cmd(client, ...)
+ assert((reply or emptytable).queued == true, 'a QUEUED reply was expected')
+ table_insert(queued_parsers, reply.parser or identity)
+ return reply
+ end
+ t[k]=queuey
+ return queuey
+ else
+ return cmd
+ end
+ end
+ })
+ client:multi()
+ return coro
+ end
+
+ local function transaction(client, options, coroutine_block, attempts)
+ local queued_parsers, replies = {}, {}
+ local retry = tonumber(attempts) or tonumber(options.retry) or 2
+ local coro = initialize_transaction(client, options, coroutine_block, queued_parsers)
+
+ local success, retval
+ if coroutine.status(coro) == 'suspended' then
+ success, retval = coroutine.resume(coro)
+ else
+ -- do not fail if the coroutine has not been resumed (missing t:multi() with CAS)
+ success, retval = true, 'empty transaction'
+ end
+ if #queued_parsers == 0 or not success then
+ client:discard()
+ assert(success, retval)
+ return replies, 0
+ end
+
+ local raw_replies = client:exec()
+ if not raw_replies then
+ if (retry or 0) <= 0 then
+ client.error("MULTI/EXEC transaction aborted by the server")
+ else
+ --we're not quite done yet
+ return transaction(client, options, coroutine_block, retry - 1)
+ end
+ end
+
+ local table_insert = table.insert
+ for i, parser in pairs(queued_parsers) do
+ table_insert(replies, i, parser(raw_replies[i]))
+ end
+
+ return replies, #queued_parsers
+ end
+
+ client_prototype.transaction = function(client, arg1, arg2)
+ local options, block
+ if not arg2 then
+ options, block = {}, arg1
+ elseif arg1 then --and arg2, implicitly
+ options, block = type(arg1)=="table" and arg1 or { arg1 }, arg2
+ else
+ client.error("Invalid parameters for redis transaction.")
+ end
+
+ if not options.watch then
+ watch_keys = { }
+ for i, v in pairs(options) do
+ if tonumber(i) then
+ table.insert(watch_keys, v)
+ options[i] = nil
+ end
+ end
+ options.watch = watch_keys
+ elseif not (type(options.watch) == 'table') then
+ options.watch = { options.watch }
+ end
+
+ if not options.cas then
+ local tx_block = block
+ block = function(client, ...)
+ client:multi()
+ return tx_block(client, ...) --can't wrap this in pcall because we're in a coroutine.
+ end
+ end
+
+ return transaction(client, options, block)
+ end
+end
+
+-- MONITOR context
+
+do
+ local monitor_loop = function(client)
+ local monitoring = true
+
+ -- Tricky since the payload format changed starting from Redis 2.6.
+ local pattern = '^(%d+%.%d+)( ?.- ?) ?"(%a+)" ?(.-)$'
+
+ local abort = function()
+ monitoring = false
+ end
+
+ return coroutine.wrap(function()
+ client:monitor()
+
+ while monitoring do
+ local message, matched
+ local response = response.read(client)
+
+ local ok = response:gsub(pattern, function(time, info, cmd, args)
+ message = {
+ timestamp = tonumber(time),
+ client = info:match('%d+.%d+.%d+.%d+:%d+'),
+ database = tonumber(info:match('%d+')) or 0,
+ command = cmd,
+ arguments = args:match('.+'),
+ }
+ matched = true
+ end)
+
+ if not matched then
+ client.error('Unable to match MONITOR payload: '..response)
+ end
+
+ coroutine.yield(message, abort)
+ end
+ end)
+ end
+
+ client_prototype.monitor_messages = function(client)
+ return monitor_loop(client)
+ end
+end
+
+-- ############################################################################
+
+local function connect_tcp(socket, parameters)
+ local host, port = parameters.host, tonumber(parameters.port)
+ local ok, err = socket:connect(host, port)
+ if not ok then
+ redis.error('could not connect to '..host..':'..port..' ['..err..']')
+ end
+ socket:setoption('tcp-nodelay', parameters.tcp_nodelay)
+ return socket
+end
+
+local function connect_unix(socket, parameters)
+ local ok, err = socket:connect(parameters.path)
+ if not ok then
+ redis.error('could not connect to '..parameters.path..' ['..err..']')
+ end
+ return socket
+end
+
+local function create_connection(parameters)
+ if parameters.socket then
+ return parameters.socket
+ end
+
+ local perform_connection, socket
+
+ if parameters.scheme == 'unix' then
+ perform_connection, socket = connect_unix, require('socket.unix')
+ assert(socket, 'your build of LuaSocket does not support UNIX domain sockets')
+ else
+ if parameters.scheme then
+ local scheme = parameters.scheme
+ assert(scheme == 'redis' or scheme == 'tcp', 'invalid scheme: '..scheme)
+ end
+ perform_connection, socket = connect_tcp, require('socket').tcp
+ end
+
+ return perform_connection(socket(), parameters)
+end
+
+-- ############################################################################
+
+function redis.error(message, level)
+ error(message, (level or 1) + 1)
+end
+
+function redis.connect(...)
+ local args, parameters = {...}, nil
+
+ if #args == 1 then
+ if type(args[1]) == 'table' then
+ parameters = args[1]
+ else
+ local uri = require('socket.url')
+ parameters = uri.parse(select(1, ...))
+ if parameters.scheme then
+ if parameters.query then
+ for k, v in parameters.query:gmatch('([-_%w]+)=([-_%w]+)') do
+ if k == 'tcp_nodelay' or k == 'tcp-nodelay' then
+ parameters.tcp_nodelay = parse_boolean(v)
+ end
+ end
+ end
+ else
+ parameters.host = parameters.path
+ end
+ end
+ elseif #args > 1 then
+ local host, port = unpack(args)
+ parameters = { host = host, port = port }
+ end
+
+ local commands = redis.commands or {}
+ if type(commands) ~= 'table' then
+ redis.error('invalid type for the commands table')
+ end
+
+ local socket = create_connection(merge_defaults(parameters))
+ local client = create_client(client_prototype, socket, commands)
+
+ return client
+end
+
+function redis.command(cmd, opts)
+ return command(cmd, opts)
+end
+
+-- obsolete
+function redis.define_command(name, opts)
+ define_command_impl(redis.commands, name, opts)
+end
+
+-- obsolete
+function redis.undefine_command(name)
+ undefine_command_impl(redis.commands, name)
+end
+
+-- ############################################################################
+
+-- Commands defined in this table do not take the precedence over
+-- methods defined in the client prototype table.
+
+redis.commands = {
+ -- commands operating on the key space
+ exists = command('EXISTS', {
+ response = toboolean
+ }),
+ del = command('DEL'),
+ type = command('TYPE'),
+ rename = command('RENAME'),
+ renamenx = command('RENAMENX', {
+ response = toboolean
+ }),
+ expire = command('EXPIRE', {
+ response = toboolean
+ }),
+ pexpire = command('PEXPIRE', { -- >= 2.6
+ response = toboolean
+ }),
+ expireat = command('EXPIREAT', {
+ response = toboolean
+ }),
+ pexpireat = command('PEXPIREAT', { -- >= 2.6
+ response = toboolean
+ }),
+ ttl = command('TTL'),
+ pttl = command('PTTL'), -- >= 2.6
+ move = command('MOVE', {
+ response = toboolean
+ }),
+ dbsize = command('DBSIZE'),
+ persist = command('PERSIST', { -- >= 2.2
+ response = toboolean
+ }),
+ keys = command('KEYS', {
+ response = function(response)
+ if type(response) == 'string' then
+ -- backwards compatibility path for Redis < 2.0
+ local keys = {}
+ response:gsub('[^%s]+', function(key)
+ table.insert(keys, key)
+ end)
+ response = keys
+ end
+ return response
+ end
+ }),
+ randomkey = command('RANDOMKEY', {
+ response = function(response)
+ if response == '' then
+ return nil
+ else
+ return response
+ end
+ end
+ }),
+ sort = command('SORT', {
+ request = sort_request,
+ }),
+
+ -- commands operating on string values
+ set = command('SET'),
+ setnx = command('SETNX', {
+ response = toboolean
+ }),
+ setex = command('SETEX'), -- >= 2.0
+ psetex = command('PSETEX'), -- >= 2.6
+ mset = command('MSET', {
+ request = mset_filter_args
+ }),
+ msetnx = command('MSETNX', {
+ request = mset_filter_args,
+ response = toboolean
+ }),
+ get = command('GET'),
+ mget = command('MGET'),
+ getset = command('GETSET'),
+ incr = command('INCR'),
+ incrby = command('INCRBY'),
+ incrbyfloat = command('INCRBYFLOAT', { -- >= 2.6
+ response = function(reply, command, ...)
+ return tonumber(reply)
+ end,
+ }),
+ decr = command('DECR'),
+ decrby = command('DECRBY'),
+ append = command('APPEND'), -- >= 2.0
+ substr = command('SUBSTR'), -- >= 2.0
+ strlen = command('STRLEN'), -- >= 2.2
+ setrange = command('SETRANGE'), -- >= 2.2
+ getrange = command('GETRANGE'), -- >= 2.2
+ setbit = command('SETBIT'), -- >= 2.2
+ getbit = command('GETBIT'), -- >= 2.2
+
+ -- commands operating on lists
+ rpush = command('RPUSH'),
+ lpush = command('LPUSH'),
+ llen = command('LLEN'),
+ lrange = command('LRANGE'),
+ ltrim = command('LTRIM'),
+ lindex = command('LINDEX'),
+ lset = command('LSET'),
+ lrem = command('LREM'),
+ lpop = command('LPOP'),
+ rpop = command('RPOP'),
+ rpoplpush = command('RPOPLPUSH'),
+ blpop = command('BLPOP'), -- >= 2.0
+ brpop = command('BRPOP'), -- >= 2.0
+ rpushx = command('RPUSHX'), -- >= 2.2
+ lpushx = command('LPUSHX'), -- >= 2.2
+ linsert = command('LINSERT'), -- >= 2.2
+ brpoplpush = command('BRPOPLPUSH'), -- >= 2.2
+
+ -- commands operating on sets
+ sadd = command('SADD'),
+ srem = command('SREM'),
+ spop = command('SPOP'),
+ smove = command('SMOVE', {
+ response = toboolean
+ }),
+ scard = command('SCARD'),
+ sismember = command('SISMEMBER', {
+ response = toboolean
+ }),
+ sinter = command('SINTER'),
+ sinterstore = command('SINTERSTORE'),
+ sunion = command('SUNION'),
+ sunionstore = command('SUNIONSTORE'),
+ sdiff = command('SDIFF'),
+ sdiffstore = command('SDIFFSTORE'),
+ smembers = command('SMEMBERS'),
+ srandmember = command('SRANDMEMBER'),
+
+ -- commands operating on sorted sets
+ zadd = command('ZADD'),
+ zincrby = command('ZINCRBY'),
+ zrem = command('ZREM'),
+ zrange = command('ZRANGE', {
+ request = zset_range_request,
+ response = zset_range_reply,
+ }),
+ zrevrange = command('ZREVRANGE', {
+ request = zset_range_request,
+ response = zset_range_reply,
+ }),
+ zrangebyscore = command('ZRANGEBYSCORE', {
+ request = zset_range_byscore_request,
+ response = zset_range_reply,
+ }),
+ zrevrangebyscore = command('ZREVRANGEBYSCORE', { -- >= 2.2
+ request = zset_range_byscore_request,
+ response = zset_range_reply,
+ }),
+ zunionstore = command('ZUNIONSTORE', { -- >= 2.0
+ request = zset_store_request
+ }),
+ zinterstore = command('ZINTERSTORE', { -- >= 2.0
+ request = zset_store_request
+ }),
+ zcount = command('ZCOUNT'),
+ zcard = command('ZCARD'),
+ zscore = command('ZSCORE'),
+ zremrangebyscore = command('ZREMRANGEBYSCORE'),
+ zrank = command('ZRANK'), -- >= 2.0
+ zrevrank = command('ZREVRANK'), -- >= 2.0
+ zremrangebyrank = command('ZREMRANGEBYRANK'), -- >= 2.0
+
+ -- commands operating on hashes
+ hset = command('HSET', { -- >= 2.0
+ response = toboolean
+ }),
+ hsetnx = command('HSETNX', { -- >= 2.0
+ response = toboolean
+ }),
+ hmset = command('HMSET', { -- >= 2.0
+ request = hash_multi_request_builder(function(args, k, v)
+ table.insert(args, k)
+ table.insert(args, v)
+ end),
+ }),
+ hincrby = command('HINCRBY'), -- >= 2.0
+ hincrbyfloat = command('HINCRBYFLOAT', {-- >= 2.6
+ response = function(reply, command, ...)
+ return tonumber(reply)
+ end,
+ }),
+ hget = command('HGET'), -- >= 2.0
+ hmget = command('HMGET', { -- >= 2.0
+ request = hash_multi_request_builder(function(args, k, v)
+ table.insert(args, v)
+ end),
+ }),
+ hdel = command('HDEL'), -- >= 2.0
+ hexists = command('HEXISTS', { -- >= 2.0
+ response = toboolean
+ }),
+ hlen = command('HLEN'), -- >= 2.0
+ hkeys = command('HKEYS'), -- >= 2.0
+ hvals = command('HVALS'), -- >= 2.0
+ hgetall = command('HGETALL', { -- >= 2.0
+ response = function(reply, command, ...)
+ local new_reply = { }
+ for i = 1, #reply, 2 do new_reply[reply[i]] = reply[i + 1] end
+ return new_reply
+ end
+ }),
+
+ -- connection related commands
+ ping = command('PING', {
+ response = function(response) return response == 'PONG' end
+ }),
+ echo = command('ECHO'),
+ auth = command('AUTH'),
+ select = command('SELECT'),
+
+ -- transactions
+ multi = command('MULTI'), -- >= 2.0
+ exec = command('EXEC'), -- >= 2.0
+ discard = command('DISCARD'), -- >= 2.0
+ watch = command('WATCH'), -- >= 2.2
+ unwatch = command('UNWATCH'), -- >= 2.2
+
+ -- publish - subscribe
+ subscribe = command('SUBSCRIBE'), -- >= 2.0
+ unsubscribe = command('UNSUBSCRIBE'), -- >= 2.0
+ psubscribe = command('PSUBSCRIBE'), -- >= 2.0
+ punsubscribe = command('PUNSUBSCRIBE'), -- >= 2.0
+ publish = command('PUBLISH'), -- >= 2.0
+
+ -- redis scripting
+ eval = command('EVAL'), -- >= 2.6
+ evalsha = command('EVALSHA'), -- >= 2.6
+ script = command('SCRIPT'), -- >= 2.6
+
+ -- remote server control commands
+ bgrewriteaof = command('BGREWRITEAOF'),
+ config = command('CONFIG', { -- >= 2.0
+ response = function(reply, command, ...)
+ if (type(reply) == 'table') then
+ local new_reply = { }
+ for i = 1, #reply, 2 do new_reply[reply[i]] = reply[i + 1] end
+ return new_reply
+ end
+
+ return reply
+ end
+ }),
+ client = command('CLIENT'), -- >= 2.4
+ slaveof = command('SLAVEOF'),
+ save = command('SAVE'),
+ bgsave = command('BGSAVE'),
+ lastsave = command('LASTSAVE'),
+ flushdb = command('FLUSHDB'),
+ flushall = command('FLUSHALL'),
+ monitor = command('MONITOR'),
+ time = command('TIME'), -- >= 2.6
+ slowlog = command('SLOWLOG', { -- >= 2.2.13
+ response = function(reply, command, ...)
+ if (type(reply) == 'table') then
+ local structured = { }
+ for index, entry in ipairs(reply) do
+ structured[index] = {
+ id = tonumber(entry[1]),
+ timestamp = tonumber(entry[2]),
+ duration = tonumber(entry[3]),
+ command = entry[4],
+ }
+ end
+ return structured
+ end
+
+ return reply
+ end
+ }),
+ info = command('INFO', {
+ response = parse_info,
+ }),
+}
+
+-- ############################################################################
+
+return redis