ct 0.10.8.114__py3-none-any.whl → 0.10.8.116__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. cantools/__init__.py +38 -13
  2. cantools/_db.py +1 -1
  3. cantools/cfg.py +13 -0
  4. cantools/config.py +12 -4
  5. cantools/db/__init__.py +3 -0
  6. cantools/db/gae/model.py +1 -2
  7. cantools/db/wp.py +1 -1
  8. cantools/scripts/index.py +2 -8
  9. cantools/scripts/init.py +1 -1
  10. cantools/scripts/pubsub/actor.py +3 -1
  11. cantools/scripts/pubsub/bots.py +11 -13
  12. cantools/scripts/start.py +2 -2
  13. cantools/util/__init__.py +2 -0
  14. cantools/util/admin.py +10 -18
  15. cantools/util/ai/__init__.py +8 -0
  16. cantools/util/ai/tox/__init__.py +15 -0
  17. cantools/util/ai/tox/duck.py +80 -0
  18. cantools/util/ai/tox/fzn.py +22 -0
  19. cantools/util/ai/tox/g4free.py +73 -0
  20. cantools/util/ai/vox.py +84 -0
  21. cantools/util/apper/__init__.py +1 -0
  22. cantools/util/apper/ander.py +101 -0
  23. cantools/util/apper/data.py +167 -0
  24. cantools/util/system.py +2 -23
  25. cantools/web/__init__.py +6 -2
  26. cantools/web/bw.py +70 -0
  27. cantools/web/gae_server.py +1 -6
  28. cantools/web/util.py +6 -409
  29. {ct-0.10.8.114.dist-info → ct-0.10.8.116.dist-info}/METADATA +7 -8
  30. ct-0.10.8.116.dist-info/RECORD +56 -0
  31. cantools/web/dez_server/__init__.py +0 -1
  32. cantools/web/dez_server/controller.py +0 -129
  33. cantools/web/dez_server/cron.py +0 -115
  34. cantools/web/dez_server/daemons.py +0 -64
  35. cantools/web/dez_server/mail.py +0 -24
  36. cantools/web/dez_server/response.py +0 -63
  37. cantools/web/dez_server/routes.py +0 -21
  38. cantools/web/dez_server/server.py +0 -229
  39. cantools/web/dez_server/sms.py +0 -12
  40. ct-0.10.8.114.dist-info/RECORD +0 -55
  41. {ct-0.10.8.114.dist-info → ct-0.10.8.116.dist-info}/LICENSE +0 -0
  42. {ct-0.10.8.114.dist-info → ct-0.10.8.116.dist-info}/WHEEL +0 -0
  43. {ct-0.10.8.114.dist-info → ct-0.10.8.116.dist-info}/entry_points.txt +0 -0
  44. {ct-0.10.8.114.dist-info → ct-0.10.8.116.dist-info}/top_level.txt +0 -0
cantools/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.10.8.114"
1
+ __version__ = "0.10.8.116"
2
2
 
3
3
  from . import util, hooks
4
4
  from . import config as cfgmod
@@ -10,15 +10,40 @@ if config.web.server == "gae":
10
10
  util.init_gae()
11
11
  else:
12
12
  util.init_basic()
13
- from . import geo
14
- from .scripts import builder, deploy, init, pubsub, start, index, migrate, doc, bench, util
15
-
16
- ctstart = start.go
17
- ctdeploy = deploy.run
18
- ctpubsub = pubsub.get_addr_and_start
19
- ctinit = init.parse_and_make
20
- ctindex = index.go
21
- ctmigrate = migrate.go
22
- ctdoc = doc.build
23
- ctbench = bench.run
24
- ctutil = util.run
13
+ #from . import geo
14
+ from .scripts import builder, pubsub
15
+
16
+ def ctstart():
17
+ from .scripts import start
18
+ start.go()
19
+
20
+ def ctdeploy():
21
+ from .scripts import deploy
22
+ deploy.run()
23
+
24
+ def ctpubsub():
25
+ pubsub.get_addr_and_start()
26
+
27
+ def ctinit():
28
+ from .scripts import init
29
+ init.parse_and_make()
30
+
31
+ def ctindex():
32
+ from .scripts import index
33
+ index.go()
34
+
35
+ def ctmigrate():
36
+ from .scripts import migrate
37
+ migrate.go()
38
+
39
+ def ctdoc():
40
+ from .scripts import doc
41
+ doc.build()
42
+
43
+ def ctbench():
44
+ from .scripts import bench
45
+ bench.run()
46
+
47
+ def ctutil():
48
+ from .scripts import util
49
+ util.run()
cantools/_db.py CHANGED
@@ -69,7 +69,7 @@ def response():
69
69
  keys = cgi_get("keys", required=False)
70
70
  exporter = cgi_get("exporter", default="export")
71
71
  if mname:
72
- order = cgi_get("order", default="index")
72
+ order = cgi_get("order", required=False)
73
73
  if config.web.server == "gae":
74
74
  order = getattr(get_model(mname), order)
75
75
  res = get_page(mname, int(cgi_get("limit")), int(cgi_get("offset")), order,
cantools/cfg.py CHANGED
@@ -69,6 +69,7 @@ cfg = {
69
69
  "protocol": "http",
70
70
  "xorigin": False,
71
71
  "shield": False,
72
+ "debug": False,
72
73
  "csp": None,
73
74
  "log": None,
74
75
  "errlog": None,
@@ -78,6 +79,9 @@ cfg = {
78
79
  "blacklist": [],
79
80
  "whitelist": []
80
81
  },
82
+ "proxy": { # user, gateway, minport, maxport
83
+ "active": False
84
+ },
81
85
  "ssl": {
82
86
  "verify": True,
83
87
  "certfile": None,
@@ -97,6 +101,15 @@ cfg = {
97
101
  "alter": False, # add new columns to tables - sqlite only!
98
102
  "echo": False,
99
103
  "public": True, # read from db without credentials via _db.py web handler
104
+ "nopoly": False,
105
+ "jsontext": True,
106
+ "arraytext": True,
107
+ "stringsize": 500,
108
+ "flatkeysize": 80,
109
+ "index": {
110
+ "key": False,
111
+ "named": False
112
+ },
100
113
  "pool": {
101
114
  "null": True,
102
115
  "size": 10,
cantools/config.py CHANGED
@@ -40,6 +40,9 @@ gcpath = os.path.join(os.path.expanduser("~"), "ct.cfg")
40
40
  if os.path.isfile(gcpath):
41
41
  print("loading global configuration at: %s"%(gcpath,))
42
42
  lines = read(gcpath, True) + lines
43
+ if os.path.isfile("extra.cfg"):
44
+ print("loading extra configuration at: extra.cfg")
45
+ lines = lines + read("extra.cfg", True)
43
46
  for line in lines:
44
47
  if line.startswith("#"):
45
48
  continue
@@ -57,9 +60,9 @@ for line in lines:
57
60
  items.append([key, val])
58
61
 
59
62
  for key, val in items:
60
- if key in ["ENCODE", "DB_ECHO", "DB_PUBLIC", "DB_REFCOUNT", "DB_CACHE", "DB_POOL_NULL", "GEO_TEST", "REL_VERBOSE", "REL_LOUDLISTEN", "MEMCACHE_REQUEST", "MEMCACHE_DB", "PUBSUB_ECHO", "PUBSUB_META", "PUBSUB_B64", "SSL_VERIFY", "ADMIN_MONITOR_LOG", "WEB_XORIGIN", "LOG_TIMESTAMP", "BUILD_PROD_CLOSURE", "BUILD_PROD_B64", "GMAILER", "MAILHTML", "MAILOUD"]:
63
+ if key in ["ENCODE", "DB_ECHO", "DB_PUBLIC", "DB_REFCOUNT", "DB_CACHE", "DB_JSONTEXT", "DB_ARRAYTEXT", "DB_NOPOLY", "DB_POOL_NULL", "DB_INDEX_KEYS", "DB_INDEX_NAMED", "PROXY_ACTIVE", "GEO_TEST", "REL_VERBOSE", "REL_LOUDLISTEN", "MEMCACHE_REQUEST", "MEMCACHE_DB", "PUBSUB_ECHO", "PUBSUB_META", "PUBSUB_B64", "SSL_VERIFY", "ADMIN_MONITOR_LOG", "WEB_DEBUG", "WEB_XORIGIN", "LOG_TIMESTAMP", "BUILD_PROD_CLOSURE", "BUILD_PROD_B64", "GMAILER", "MAILHTML", "MAILOUD"]:
61
64
  val = val == "True"
62
- elif key in ["PUBSUB_HISTORY", "MEMPAD", "WEB_SHIELD_CHUNK", "WEB_SHIELD_LIMIT", "WEB_SHIELD_INTERVAL", "MEMCACHE_PROX_TIMEOUT", "DB_POOL_SIZE", "DB_POOL_OVERFLOW", "DB_POOL_RECYCLE", "LOG_OPENFILES", "LOG_TRACEMALLOC", "MAILSCANTICK"]:
65
+ elif key in ["PUBSUB_HISTORY", "MEMPAD", "WEB_SHIELD_CHUNK", "WEB_SHIELD_LIMIT", "WEB_SHIELD_INTERVAL", "PROXY_MINPORT", "PROXY_MAXPORT", "MEMCACHE_PROX_TIMEOUT", "DB_POOL_SIZE", "DB_POOL_OVERFLOW", "DB_POOL_RECYCLE", "DB_STRINGSIZE", "DB_FLATKEYSIZE", "LOG_OPENFILES", "LOG_TRACEMALLOC", "MAILSCANTICK"]:
63
66
  val = int(val)
64
67
  elif key in ["REL_SLEEP", "REL_TURBO"]:
65
68
  val = float(val)
@@ -92,11 +95,16 @@ for key, val in items:
92
95
  c.update(target, val)
93
96
 
94
97
  config.update("cache", pc)
95
- config.db.update("main", config.db[config.web.server])
98
+ if not config.db.main:
99
+ config.db.update("main", config.db[config.web.server])
100
+ if "DBPW" in config.db.main:
101
+ config.db.update("main", config.db.main.replace("DBPW", config.cache("database password? ")))
96
102
  for prop in ["deep", "flush", "timestamp", "allow"]:
97
103
  confyg.log.update(prop, config.log[prop])
98
- for prop in ["cache", "refcount", "main", "test", "blob", "alter", "echo"]:
104
+ for prop in ["cache", "refcount", "main", "test", "blob", "alter", "echo", "jsontext", "arraytext", "stringsize", "flatkeysize"]:
99
105
  dbcfg.update(prop, config.db[prop])
106
+ for prop in ["key", "named"]:
107
+ dbcfg.index.update(prop, config.db.index[prop])
100
108
  for prop in ["null", "size", "recycle", "overflow"]:
101
109
  dbcfg.pool.update(prop, config.db.pool[prop])
102
110
 
cantools/db/__init__.py CHANGED
@@ -5,12 +5,15 @@ if config.web.server == "gae":
5
5
  from .gae.model import *
6
6
  elif config.web.server == "dez":
7
7
  from rel import tick
8
+ if not config.db.nopoly:
9
+ from databae.poly import ModelBase, TimeStampedBase
8
10
  from databae import *
9
11
  from cantools.web import set_pre_close, cgi_dump
10
12
  def scoper(threadId):
11
13
  if threadId == "MainThread":
12
14
  threadId = tick()
13
15
  return "%s%s"%(threadId, cgi_dump())
16
+ session = seshman.get()
14
17
  set_scoper(scoper)
15
18
  set_pre_close(seshman.close)
16
19
  else:
cantools/db/gae/model.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from datetime import datetime
2
2
  from .properties import *
3
- from six import with_metaclass
4
3
 
5
4
  class CTMeta(ndb.MetaModel):
6
5
  def __new__(cls, name, bases, attrs):
@@ -22,7 +21,7 @@ class CTMeta(ndb.MetaModel):
22
21
  modelsubs[lname] = super(CTMeta, cls).__new__(cls, name, bases, attrs)
23
22
  return modelsubs[lname]
24
23
 
25
- class ModelBase(with_metaclass(CTMeta, ndb.Model)):
24
+ class ModelBase(ndb.Model, metaclass=CTMeta):
26
25
  index = Integer()
27
26
 
28
27
  def __eq__(self, other):
cantools/db/wp.py CHANGED
@@ -45,7 +45,7 @@ def trydb(attempts=5, wait=0.4):
45
45
  db = setdb()
46
46
  if db:
47
47
  return db
48
- log("trydb failed retry #" + attempt, important=True)
48
+ log("trydb failed retry #%s"%(attempt,), important=True)
49
49
  time.sleep(wait)
50
50
 
51
51
  def getdb(subdb=False):
cantools/scripts/index.py CHANGED
@@ -44,16 +44,9 @@ Run this in 'index' mode on a database with lots of missing index values.
44
44
  from getpass import getpass
45
45
  from optparse import OptionParser
46
46
  from fyg.util import log, error, batch
47
- from cantools.db import get_schema, get_model, put_multi, delete_multi, unpad_key
47
+ from cantools.db import get_schema, get_model, put_multi, delete_multi, unpad_key, session, func
48
48
  from cantools.web import fetch
49
49
  from cantools import config
50
- if config.web.server == "dez":
51
- from cantools.db import session, func, refresh_counter
52
-
53
- try:
54
- input = raw_input # py2/3 compatibility
55
- except NameError:
56
- pass
57
50
 
58
51
  counts = { "_counters": 0 }
59
52
  RETRIES = 5
@@ -93,6 +86,7 @@ def refmap():
93
86
  return rmap
94
87
 
95
88
  def do_batch(chunk, reference):
89
+ from databae.lookup import refresh_counter
96
90
  log("refreshing %s %s keys"%(len(chunk), reference), 1)
97
91
  i = 0
98
92
  rc = []
cantools/scripts/init.py CHANGED
@@ -248,7 +248,7 @@ class Builder(object):
248
248
  for dname, fnames in list(mod.init.syms.items()):
249
249
  for fname in fnames:
250
250
  sym(os.path.join(mod.__ct_mod_path__, dname, fname),
251
- os.path.join(dname, fname))
251
+ os.path.join(dname, fname.strip("./")))
252
252
 
253
253
  def vcignore(self):
254
254
  log("configuring version control path exclusion", 1)
@@ -1,4 +1,6 @@
1
- class Actor(object):
1
+ from fyg.util import Named
2
+
3
+ class Actor(Named):
2
4
  def data(self):
3
5
  return {
4
6
  "name": self.name,
@@ -1,13 +1,7 @@
1
1
  import datetime, json, os
2
2
  from cantools import config
3
3
  from cantools.util import log, write
4
- from cantools.web import fetch, send_mail
5
4
  from .actor import Actor
6
- try:
7
- import psutil
8
- except ImportError as e:
9
- pass # google crap engine (get it if you need it!)
10
- from six import with_metaclass
11
5
 
12
6
  class BotMeta(type):
13
7
  def __new__(cls, name, bases, attrs):
@@ -17,7 +11,7 @@ class BotMeta(type):
17
11
  config.pubsub.bots.update(name.lower(), bc)
18
12
  return bc
19
13
 
20
- class Bot(with_metaclass(BotMeta, Actor)):
14
+ class Bot(Actor, metaclass=BotMeta):
21
15
  num = 0
22
16
  def __init__(self, server, channel, name=None):
23
17
  Bot.num += 1
@@ -26,7 +20,7 @@ class Bot(with_metaclass(BotMeta, Actor)):
26
20
  self.channel = channel # often we only care about one channel
27
21
  self.channels = set()
28
22
  self._set_defaults()
29
- log("Bot Spawned: '%s'"%(self.name,), 2)
23
+ self.log("Bot Spawned")
30
24
  channel.join(self)
31
25
  self.server.bots[self.name] = self
32
26
 
@@ -41,7 +35,7 @@ class Bot(with_metaclass(BotMeta, Actor)):
41
35
 
42
36
  def _default_handler(self, action):
43
37
  def _h(*args):
44
- log('Bot %s handling %s: "%s"'%(self.name, action, json.dumps(args)), 3)
38
+ self.log("handling", action, ":", args)
45
39
  return _h
46
40
 
47
41
  def _set_defaults(self):
@@ -74,25 +68,29 @@ class Monitor(Bot):
74
68
  os.mkdir(dp)
75
69
  return os.path.join(dp, str(n.hour))
76
70
 
77
- def log(self, data):
71
+ def report(self, data):
78
72
  self.pub(data)
79
73
  if config.admin.monitor.log:
80
74
  write(data, self._datedir(), True, append=True, newline=True)
81
75
 
82
76
  def _cpu(self):
77
+ import psutil
78
+ from cantools.web import send_mail
83
79
  c = self.current["cpu"] = psutil.cpu_percent()
84
80
  if self.alert.get("cpu"):
85
81
  if c < config.admin.monitor.thresholds.cpu:
86
82
  del self.alert["cpu"]
87
- log("CPU calmed down")
83
+ self.log("CPU calmed down")
88
84
  send_mail(config.admin.contacts, subject="High CPU", body="just ended")
89
85
  else:
90
86
  if c >= config.admin.monitor.thresholds.cpu:
91
87
  self.alert["cpu"] = True
92
- log("CPU just started going crazy")
88
+ self.log("CPU just started going crazy")
93
89
  send_mail(config.admin.contacts, subject="High CPU", body="just started")
94
90
 
95
91
  def _tick(self):
92
+ import psutil
93
+ from cantools.web import fetch
96
94
  self._cpu()
97
95
  dioc = psutil.disk_io_counters()
98
96
  nioc = psutil.net_io_counters()
@@ -139,5 +137,5 @@ class Monitor(Bot):
139
137
  if config.admin.monitor.proxy:
140
138
  data["ips"]["proxy"] = fetch(config.admin.host, "/_report",
141
139
  config.admin.monitor.proxy, True)["ips"]
142
- self.log(data)
140
+ self.report(data)
143
141
  return True
cantools/scripts/start.py CHANGED
@@ -44,8 +44,8 @@ def go():
44
44
  print(cmd)
45
45
  subprocess.call(cmd, shell=True)
46
46
  elif options.web_backend == "dez":
47
- from cantools.web import run_dez_webserver
48
- run_dez_webserver()
47
+ from cantools.web import run_bw
48
+ run_bw()
49
49
  else:
50
50
  error("invalid web_backend: %s"%(options.web_backend,))
51
51
 
cantools/util/__init__.py CHANGED
@@ -7,9 +7,11 @@ from .media import transcode, segment, hlsify, shouldMoveMoov, crop, resizep2, t
7
7
  from .admin import certs, screener
8
8
  from .apper import android
9
9
  from .ai import tox, vox, tellme
10
+ from .package import pipper
10
11
 
11
12
  def init_basic():
12
13
  os.path.isdir("emails") and sys.path.append("emails")
14
+ sys.path.insert(0, ".") # for dynamically loading modules
13
15
 
14
16
  def init_rel():
15
17
  import rel
cantools/util/admin.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os, sys, rel, time, datetime
2
+ from fyg.util import pcount, pcheck, pkill
2
3
  from cantools.util import cmd, output, error, log, set_log, close_log, read, write, confirm, rm
3
4
 
4
5
  coremods = ["screen", "ctstart", "ctpubsub", "ctutil", "ctinit", "dez_reverse_proxy", "dez_websocket_proxy"]
@@ -65,19 +66,6 @@ def certs(dpath="/root", sname=None):
65
66
  log("goodbye")
66
67
  close_log()
67
68
 
68
- def pcount(pname):
69
- log("checking count: %s"%(pname,), important=True)
70
- num = int(output("ps -ef | grep %s | egrep -v 'screener|pcount|grep' | wc -l"%(pname,)))
71
- log("%s count: %s"%(pname, num), 1)
72
- return num
73
-
74
- def pcheck(pname, target, starter):
75
- if target and pcount(pname) != target:
76
- log("not enough %s processes - restarting screen!"%(pname,), 1)
77
- log(output("screen -Q windows"), important=True)
78
- cmd("killall screen; %s"%(starter,))
79
- return True
80
-
81
69
  def binpath(bpath="/usr/bin/"):
82
70
  log("checking %s core modules"%(len(coremods),), important=True)
83
71
  missings = []
@@ -295,11 +283,12 @@ def upcheck(*procs):
295
283
  log("goodbye")
296
284
  close_log()
297
285
 
298
- def vitals(clean=False, thresh=90, dpath="."):
286
+ def vitals(clean=False, thresh=90, dpath=".", conlim=0):
287
+ from cantools.web import email_admins
299
288
  if clean == "False":
300
289
  clean = False
301
290
  thresh = int(thresh)
302
- from cantools.web import email_admins
291
+ conlim = int(conlim)
303
292
  os.chdir(dpath)
304
293
  set_log("cron-vitals.log")
305
294
  log("scanning vitals", important=True)
@@ -310,9 +299,12 @@ def vitals(clean=False, thresh=90, dpath="."):
310
299
  lz.append("inode usage: %s%%"%(inodes,))
311
300
  memuse = float(output("free | grep Mem | awk '{print $3/$2 * 100.0}'"))
312
301
  lz.append("memory usage: %s%%"%(memuse,))
302
+ if conlim:
303
+ concount = int(output("lsof -i | wc -l"))
304
+ lz.append("connections: %s"%(concount,))
313
305
  for l in lz:
314
306
  log(l)
315
- if hdrive > thresh or inodes > thresh or memuse > thresh:
307
+ if hdrive > thresh or inodes > thresh or memuse > thresh or (conlim and concount > conlim):
316
308
  log("threshold exceeded - notifying admins")
317
309
  if hdrive > thresh and clean:
318
310
  log("cleaning up!")
@@ -333,9 +325,9 @@ def sslredirect(port=80):
333
325
  def blacklist(ip):
334
326
  if not confirm("blacklist %s?"%(ip,)):
335
327
  return log("okay, bye!")
336
- from cantools.web import controller
328
+ from cantools.web import shield
337
329
  from cantools import config
338
- controller.setBlacklist()
330
+ shield.setBlacklist()
339
331
  blist = config.web.blacklist.obj()
340
332
  if ip in blist:
341
333
  return log("%s is already blacklisted! reason: %s"%(ip, blist[ip]))
@@ -0,0 +1,8 @@
1
+ from .tox import tox, g4chat, g4image, fzn, duck, pb
2
+ from .vox import vox, kvoices
3
+
4
+ def tellme(prompt, voice="random", identity="gpt-4o-mini", filename="tts", unsaid=False, silent=False):
5
+ print("tellme(%s, %s, %s)"%(voice, identity, filename), prompt)
6
+ resp = tox(prompt, identity)
7
+ unsaid or vox(resp, voice, filename=filename, say=not silent)
8
+ return resp
@@ -0,0 +1,15 @@
1
+ from .duck import duck # not currently working :'(
2
+ from .g4free import g4chat, g4image, gchatters
3
+ from .fzn import fzn
4
+
5
+ # pb
6
+ def pb(question, botname, appid, userkey):
7
+ from pb_py import main as PB
8
+ resp = PB.talk(userkey, appid, "aiaas.pandorabots.com", botname, question)["response"]
9
+ return resp.split("[URL]")[0]
10
+
11
+ # wrapper
12
+ def tox(statement, identity="Anonymous", name=None, mood=None, asker=None, options=None):
13
+ if identity in gchatters:
14
+ return g4chat(statement, identity)
15
+ return fzn(statement, identity, name, mood, asker, options)
@@ -0,0 +1,80 @@
1
+ import random
2
+ from datetime import datetime
3
+
4
+ def nosay():
5
+ return random.choice([
6
+ "i'm confused",
7
+ "you broke me",
8
+ "you're killing me bro",
9
+ "that question ruined me",
10
+ "your words somehow fried my circuits",
11
+ "i literally don't know how to answer that"
12
+ ])
13
+
14
+ def notyet():
15
+ return random.choice([
16
+ "slow down",
17
+ "not so fast",
18
+ "wait a minute",
19
+ "take your time",
20
+ "slow your roll",
21
+ "i need a moment",
22
+ "hold your horses"
23
+ ])
24
+
25
+ # ddg
26
+ DDGAI = {
27
+ "bot": None,
28
+ "last": None
29
+ }
30
+ DUX = ["gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku", "o3-mini", "mistral-small-3"]
31
+ OLDUX = ["o3-mini", "gpt-4o-mini", "claude-3-haiku", "llama-3.1-70b", "mixtral-8x7b"]
32
+ delimiases = {
33
+ "False": False,
34
+ True: "\n",
35
+ "True": "\n",
36
+ "LINE": "\n",
37
+ "PARAGRAPH": "\n",
38
+ "SENTENCE": ".",
39
+ "PHRASE": [".", "!", "?"]
40
+ }
41
+
42
+ def ddgai():
43
+ if not DDGAI["bot"]:
44
+ from duckai import DuckAI
45
+ DDGAI["bot"] = DuckAI()
46
+ return DDGAI["bot"]
47
+
48
+ def toosoon():
49
+ now = datetime.now()
50
+ if DDGAI["last"] and (now - DDGAI["last"]).seconds < 15:
51
+ return True
52
+ DDGAI["last"] = now
53
+
54
+ def duck(prompt, model="gpt-4o-mini", shorten=False, strip=False, timeout=30):
55
+ if toosoon():
56
+ return notyet()
57
+ try:
58
+ resp = ddgai().chat(prompt, model, int(timeout))
59
+ except:
60
+ resp = nosay()
61
+ print(resp)
62
+ if shorten in delimiases:
63
+ shorten = delimiases[shorten]
64
+ if shorten:
65
+ if type(shorten) is not list:
66
+ shorten = [shorten]
67
+ for sho in shorten:
68
+ resp = resp.split(sho).pop(0)
69
+ print("shortened to:")
70
+ print(resp)
71
+ if strip:
72
+ resp = resp.replace("\n", " ").replace(" & ", " and ")
73
+ while " <" in resp and "> " in resp:
74
+ resp = resp[:resp.index(" <") + 1] + resp[resp.index("> ") + 1:]
75
+ while "```" in resp:
76
+ start = resp.index("```") + 3
77
+ resp = resp[:start] + resp[resp.index("```", start) + 3:]
78
+ print("stripped to:")
79
+ print(resp)
80
+ return resp
@@ -0,0 +1,22 @@
1
+ # aiio
2
+ afcfg = {
3
+ "host": "ai.fzn.party",
4
+ "path": "_respond",
5
+ "proto": "https"
6
+ }
7
+
8
+ def fzn(statement, identity="Anonymous", name=None, mood=None, asker=None, options=None):
9
+ from cantools.web import post
10
+ params = {
11
+ "identity": identity,
12
+ "statement": statement,
13
+ "options": options,
14
+ "mood": mood,
15
+ "name": name,
16
+ "asker": asker
17
+ }
18
+ fu = "%s://%s/%s"%(afcfg["proto"], afcfg["host"], afcfg["path"])
19
+ print("fzn", fu, params)
20
+ resp = post(fu, data=params, ctjson=True)
21
+ print(resp)
22
+ return resp
@@ -0,0 +1,73 @@
1
+
2
+ class G4F(object):
3
+ def __init__(self, log=print):
4
+ self.logger = log
5
+ self.client = None
6
+
7
+ def log(self, *msg):
8
+ self.logger("G4F", *msg)
9
+
10
+ def getClient(self):
11
+ if not self.client:
12
+ self.log("setting up client")
13
+ from g4f.client import Client
14
+ self.client = Client()
15
+ return self.client
16
+
17
+ def chat(self, prompt, model="gpt-4o-mini", short=True):
18
+ self.log("chat(%s)"%(model,), prompt)
19
+ messages = [{ "role": "user", "content": prompt }]
20
+ short and messages.insert(0, {
21
+ "role": "user",
22
+ "content": "one sentence responses please"
23
+ })
24
+ return self.getClient().chat.completions.create(
25
+ model=model,
26
+ messages=messages
27
+ ).choices[0].message.content
28
+
29
+ def image(self, prompt, model="flux"):
30
+ self.log("image(%s)"%(model,), prompt)
31
+ return self.getClient().images.generate(
32
+ model=model,
33
+ prompt=prompt,
34
+ response_format="url"
35
+ ).data[0].url
36
+
37
+ def __call__(self, action, prompt, model):
38
+ self.log("calling", action, "with", model, ":", prompt)
39
+ resp = getattr(self, action)(prompt, model)
40
+ self.log("got", resp)
41
+ return resp
42
+
43
+ gchatters = ["gpt-4o-mini", "gpt-4o", "gpt-4.1", "deepseek-v3"]
44
+ gimagers = ["flux", "dalle-3", "gpt-image"]
45
+ VAGENT = None
46
+ g4fer = G4F()
47
+
48
+ def vagent():
49
+ global VAGENT
50
+ if not VAGENT:
51
+ from venvr import getagent
52
+ VAGENT = getagent("g4f", [{
53
+ "requirements": "requirements-slim.txt",
54
+ "git": "xtekky/gpt4free",
55
+ "sym": "g4f"
56
+ }])
57
+ VAGENT.register(G4F)
58
+ return VAGENT
59
+
60
+ def g4do(action, prompt, model):
61
+ try:
62
+ import g4f
63
+ print("found g4f")
64
+ return g4fer(action, prompt, model)
65
+ except:
66
+ print("no g4f - using venvr")
67
+ return vagent().run("G4F", action, prompt, model)
68
+
69
+ def g4image(prompt, model="flux"):
70
+ return g4do("image", prompt, model)
71
+
72
+ def g4chat(prompt, model="gpt-4o-mini"):
73
+ return g4do("chat", prompt, model)